{"action":"created","issue":{"url":"https://api.github.com/repos/hapijs/joi/issues/546","labels_url":"https://api.github.com/repos/hapijs/joi/issues/546/labels{/name}","comments_url":"https://api.github.com/repos/hapijs/joi/issues/546/comments","events_url":"https://api.github.com/repos/hapijs/joi/issues/546/events","html_url":"https://github.com/hapijs/joi/issues/546","id":56911466,"number":546,"title":"Really custom messages are impossible","user":{"login":"ivan-kleshnin","id":2128182,"avatar_url":"https://avatars.githubusercontent.com/u/2128182?v=3","gravatar_id":"","url":"https://api.github.com/users/ivan-kleshnin","html_url":"https://github.com/ivan-kleshnin","followers_url":"https://api.github.com/users/ivan-kleshnin/followers","following_url":"https://api.github.com/users/ivan-kleshnin/following{/other_user}","gists_url":"https://api.github.com/users/ivan-kleshnin/gists{/gist_id}","starred_url":"https://api.github.com/users/ivan-kleshnin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ivan-kleshnin/subscriptions","organizations_url":"https://api.github.com/users/ivan-kleshnin/orgs","repos_url":"https://api.github.com/users/ivan-kleshnin/repos","events_url":"https://api.github.com/users/ivan-kleshnin/events{/privacy}","received_events_url":"https://api.github.com/users/ivan-kleshnin/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/hapijs/joi/labels/request","name":"request","color":"c7def8"}],"state":"closed","locked":false,"assignee":{"login":"Marsup","id":796194,"avatar_url":"https://avatars.githubusercontent.com/u/796194?v=3","gravatar_id":"","url":"https://api.github.com/users/Marsup","html_url":"https://github.com/Marsup","followers_url":"https://api.github.com/users/Marsup/followers","following_url":"https://api.github.com/users/Marsup/following{/other_user}","gists_url":"https://api.github.com/users/Marsup/gists{/gist_id}","starred_url":"https://api.github.com/users/Marsup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Marsup/subscriptions","organizations_url":"https://api.github.com/users/Marsup/orgs","repos_url":"https://api.github.com/users/Marsup/repos","events_url":"https://api.github.com/users/Marsup/events{/privacy}","received_events_url":"https://api.github.com/users/Marsup/received_events","type":"User","site_admin":false},"milestone":{"url":"https://api.github.com/repos/hapijs/joi/milestones/29","labels_url":"https://api.github.com/repos/hapijs/joi/milestones/29/labels","id":946814,"number":29,"title":"6.0.0","description":"","creator":{"login":"Marsup","id":796194,"avatar_url":"https://avatars.githubusercontent.com/u/796194?v=3","gravatar_id":"","url":"https://api.github.com/users/Marsup","html_url":"https://github.com/Marsup","followers_url":"https://api.github.com/users/Marsup/followers","following_url":"https://api.github.com/users/Marsup/following{/other_user}","gists_url":"https://api.github.com/users/Marsup/gists{/gist_id}","starred_url":"https://api.github.com/users/Marsup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Marsup/subscriptions","organizations_url":"https://api.github.com/users/Marsup/orgs","repos_url":"https://api.github.com/users/Marsup/repos","events_url":"https://api.github.com/users/Marsup/events{/privacy}","received_events_url":"https://api.github.com/users/Marsup/received_events","type":"User","site_admin":false},"open_issues":1,"closed_issues":11,"state":"open","created_at":"2015-01-22T12:40:40Z","updated_at":"2015-02-18T18:46:21Z","due_on":null,"closed_at":null},"comments":21,"created_at":"2015-02-07T13:36:55Z","updated_at":"2015-02-18T19:18:51Z","closed_at":"2015-02-08T16:15:49Z","body":"I want to stick \"Passwords don't match\" message to `verifyPassword` field.\r\n\r\nFirst attempt:\r\n```javascript\r\nverifyPassword: Joi.any().valid(Joi.ref('password')).options({language: {any: {allowOnly: \"Passwords don't match\"}}}) \r\n```\r\n\r\nFailed. Result is \"verifyPassword Passwords don't match\". Joi prefixes message with `label` and it seems there is no way to turn it off.\r\n\r\nSecond attempt:\r\n```javascript\r\nverifyPassword: Joi.any().valid(Joi.ref('password')).options({language: {any: {allowOnly: \"Passwords don't match\"}}}).label('')\r\n```\r\n\r\nFailed. \"smart\" check inside Joi prohibits empty labels..."},"comment":{"url":"https://api.github.com/repos/hapijs/joi/issues/comments/74928632","html_url":"https://github.com/hapijs/joi/issues/546#issuecomment-74928632","issue_url":"https://api.github.com/repos/hapijs/joi/issues/546","id":74928632,"user":{"login":"agraddy","id":6887241,"avatar_url":"https://avatars.githubusercontent.com/u/6887241?v=3","gravatar_id":"","url":"https://api.github.com/users/agraddy","html_url":"https://github.com/agraddy","followers_url":"https://api.github.com/users/agraddy/followers","following_url":"https://api.github.com/users/agraddy/following{/other_user}","gists_url":"https://api.github.com/users/agraddy/gists{/gist_id}","starred_url":"https://api.github.com/users/agraddy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/agraddy/subscriptions","organizations_url":"https://api.github.com/users/agraddy/orgs","repos_url":"https://api.github.com/users/agraddy/repos","events_url":"https://api.github.com/users/agraddy/events{/privacy}","received_events_url":"https://api.github.com/users/agraddy/received_events","type":"User","site_admin":false},"created_at":"2015-02-18T19:18:50Z","updated_at":"2015-02-18T19:18:50Z","body":"Sorry for the block of text and code. The more I've thought about the issue, I think the problems arise from the way Hapi and Joi interface (similar to @Marsup's last couple messages). On it's own, Joi works well as a validation library. However, when it gets paired with Hapi and the way Hapi uses validation, some confusion gets presented.\r\n\r\nA quick preface, one of the reasons I chose to start working with Hapi was because of it's simple and straightforward validation functionality. Working with Joi validation in Hapi is a breath of fresh air compared to other frameworks. The problem is that there are still a few sharp edges in the interface. \r\n\r\nI think a lot of developers when they come across one of these sharp edges assume  \"Well, this is a Joi issue\" so we create Joi issues about messaging that end up with responses like \"It seems to me you are hoping this message would end up somewhere on the front-end, Joi is not for that.\" (@Marsup - https://github.com/hapijs/joi/pull/484#issuecomment-72484690 ) and \"The problem is that you are trying to use joi for direct end-user interaction and it is not what it was designed for. It is for API input validation.\" (@hueniverse https://github.com/hapijs/joi/issues/193#issuecomment-37474924 ).\r\n\r\nIt's clear that the maintainers have a clear purpose for Joi in mind but that purpose is not clearly communicated to users (other than saying \"that's not what's its for\" in issues). No where in the Joi readme does it state that the purpose of Joi is for \"API input validation\" or that Joi is not for messages that appear \"on the front-end\". As a Hapi user, when I read messages like that, it gets really confusing/frustrating because all the Hapi/Joi documentation and tutorials I see seem to indicate that Joi should be used to display end user information. It seems to me that either the messaging on how to achieve end-user errors should be improved or Hapi and/or Joi need to be improved to eliminate the sharp edges.\r\n\r\nI think the easiest way to explain where I'm coming from is to provide a couple simple examples. For the first one, let's assume that I want to create a pirate themed ajax email newsletter signup form. Ideally, I would want to write it something like this:\r\n```javascript\r\nvar Hapi = require('hapi');\r\nvar Joi = require('joi');\r\nvar routes = [];\r\n\r\n// Create a server with a host and port\r\nvar server = new Hapi.Server();\r\nserver.connection({ \r\n    host: 'localhost', \r\n    port: 8000\r\n});\r\n\r\n// Create routes\r\nroutes.push({\r\n    method: 'GET',\r\n    path: '/', \r\n    handler: function (request, reply) {\r\n        var html = '';\r\n        html += '<!DOCTYPE html>';\r\n        html += '<html>';\r\n        html += '<head>';\r\n        html += '<meta charset=\"UTF-8\" />';\r\n        html += '<title>The Pirate List</title>';\r\n        html += '<script>';\r\n        html += 'function init() {';\r\n            html += 'document.getElementById(\"form_email_add\").onsubmit = function(e) {';\r\n            html += 'e.preventDefault();';\r\n                html += 'var httpRequest = new XMLHttpRequest();';\r\n\r\n                html += 'httpRequest.onreadystatechange = function(){';\r\n                    html += 'var data;';\r\n                    html += 'if(httpRequest.readyState == 4){';\r\n                        html += 'data = JSON.parse(httpRequest.responseText);';\r\n                        html += 'if (httpRequest.status === 200) {';\r\n                            html += 'document.getElementById(\"form_box\").innerHTML = data.message;';\r\n                        html += '} else {';\r\n                            html += 'document.getElementById(\"error\").innerHTML = data.message;';\r\n                        html += '}';\r\n                    html += '}';\r\n                html += '};';\r\n\r\n                html += 'httpRequest.open(\"POST\", \"/ajax/email-add\");';\r\n                html += 'httpRequest.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded\");';\r\n                html += 'httpRequest.send(\"email=\" + encodeURIComponent(document.getElementById(\"input_email\").value));';\r\n\r\n                html += 'return false;';\r\n            html += '};';\r\n        html += '}';\r\n\r\n        html += '</script>';\r\n        html += '</head>';\r\n        html += '<body onload=\"init()\">';\r\n\r\n        html += '<p>Join the Pirate List matey!</p>';\r\n        html += '<div id=\"form_box\">';\r\n        html += '<p id=\"error\"></p>';\r\n        html += '<form id=\"form_email_add\" action=\"/ajax/email-add\" method=\"post\">';\r\n        html += '<input id=\"input_email\" type=\"text\" name=\"email\" value=\"\" placeholder=\"Enter your email\" />';\r\n        html += '<input type=\"submit\" value=\"Sign Me Up!\" />';\r\n        html += '</form>';\r\n        html += '</div>';\r\n\r\n        html += '</body>';\r\n        html += '</html>';\r\n        reply(html);\r\n    }\r\n});\r\n\r\n// If you change customMessage to label, the entire form will work.\r\nroutes.push({\r\n    method: 'POST',\r\n    path: '/ajax/email-add', \r\n    config: {\r\n        validate: {\r\n            payload: {\r\n                email: Joi.string().email().required().customMessage('Aaarrrrrggghhhhh!!! It needs to be valid matey!')\r\n            },\r\n            options: { abortEarly: false }\r\n        }\r\n    },\r\n    handler: function (request, reply) {\r\n        console.log('Add ' + request.payload.email + ' to the list.');\r\n        reply({status: 'success', message: 'Welcome aboard matey!'});\r\n    }\r\n});\r\n\r\n// Add the routes\r\nserver.route(routes);\r\n\r\n// Start the server\r\nserver.start(function () {\r\n    console.log('Server running at:', server.info.uri);\r\n});\r\n```\r\n\r\nHow would you recommend setting up a custom message like that?\r\n\r\nAnd here's a typical way I currently use Hapi/Joi. Is this an incorrect way to do things? I'm using the validation messages directly because I don't see any need to change them. Assume this is a fully working contact form:\r\n```javascript\r\nvar Hapi = require('hapi');\r\nvar Joi = require('joi');\r\nvar routes = [];\r\n\r\n// Create a server with a host and port\r\nvar server = new Hapi.Server();\r\nserver.connection({ \r\n    host: 'localhost', \r\n    port: 8000\r\n});\r\n\r\n// Create routes\r\nroutes.push({\r\n    method: 'GET',\r\n    path: '/', \r\n    handler: function (request, reply) {\r\n        var html = '';\r\n        html += '<!DOCTYPE html>';\r\n        html += '<html>';\r\n        html += '<head>';\r\n        html += '<meta charset=\"UTF-8\" />';\r\n        html += '<title>Contact Form</title>';\r\n        html += '<script>';\r\n        html += 'function init() {';\r\n            html += 'document.getElementById(\"form_contact\").onsubmit = function(e) {';\r\n            html += 'e.preventDefault();';\r\n                html += 'var httpRequest = new XMLHttpRequest();';\r\n\r\n                html += 'httpRequest.onreadystatechange = function(){';\r\n                    html += 'var data;';\r\n                    html += 'if(httpRequest.readyState == 4){';\r\n                        html += 'data = JSON.parse(httpRequest.responseText);';\r\n                        html += 'if (httpRequest.status === 200) {';\r\n                            html += 'document.getElementById(\"form_box\").innerHTML = data.message;';\r\n                        html += '} else {';\r\n                            html += 'document.getElementById(\"error\").innerHTML = data.message + \".\";';\r\n                        html += '}';\r\n                    html += '}';\r\n                html += '};';\r\n\r\n                html += 'var output = \"\";';\r\n                html += 'output += \"email=\" + encodeURIComponent(document.getElementById(\"input_email\").value);';\r\n                html += 'output += \"&subject=\" + encodeURIComponent(document.getElementById(\"input_subject\").value);';\r\n                html += 'output += \"&message=\" + encodeURIComponent(document.getElementById(\"textarea_message\").value);';\r\n\r\n                html += 'httpRequest.open(\"POST\", \"/ajax/contact-send\");';\r\n                html += 'httpRequest.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded\");';\r\n                html += 'httpRequest.send(output);';\r\n\r\n                html += 'return false;';\r\n            html += '};';\r\n        html += '}';\r\n\r\n        html += '</script>';\r\n        html += '</head>';\r\n        html += '<body onload=\"init()\">';\r\n\r\n        html += '<p>Contact Form:</p>';\r\n        html += '<div id=\"form_box\">';\r\n        html += '<p id=\"error\"></p>';\r\n        html += '<form id=\"form_contact\" action=\"/ajax/contact-send\" method=\"post\">';\r\n        html += '<input id=\"input_email\" type=\"text\" name=\"email\" value=\"\" placeholder=\"Your email\" /><br/>';\r\n        html += '<input id=\"input_subject\" type=\"text\" name=\"subject\" value=\"\" placeholder=\"Subject\" /><br/>';\r\n        html += '<textarea id=\"textarea_message\" name=\"message\" placeholder=\"Your message\"></textarea><br/>';\r\n        html += '<input type=\"submit\" value=\"Send\" />';\r\n        html += '</form>';\r\n        html += '</div>';\r\n\r\n        html += '</body>';\r\n        html += '</html>';\r\n        reply(html);\r\n    }\r\n});\r\n\r\nroutes.push({\r\n    method: 'POST',\r\n    path: '/ajax/contact-send', \r\n    config: {\r\n        validate: {\r\n            payload: {\r\n                email: Joi.string().email().required().label('Your email'),\r\n                subject: Joi.string().required().label('Subject'),\r\n                message: Joi.string().required().label('Message')\r\n            },\r\n            options: { abortEarly: false }\r\n        }\r\n    },\r\n    handler: function (request, reply) {\r\n        console.log('Handle the contact form.');\r\n        reply({status: 'success', message: 'Thank you! Your message has been sent.'});\r\n    }\r\n});\r\n\r\n// Add the routes\r\nserver.route(routes);\r\n\r\n// Start the server\r\nserver.start(function () {\r\n    console.log('Server running at:', server.info.uri);\r\n});\r\n```\r\n\r\nI may just be completely misunderstanding how to provide errors to end users but it would definitely help if Hapi and/or Joi clearly documented this. I'm guessing the above examples are the incorrect way to handle things so if you get a chance, let me know how those examples should be written. Thanks for all the great work on Hapi and Joi!"}}