-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Really custom messages are impossible #546
Comments
This is intended because the message is supposed to provide context for consumers of the API, not to be presented to end-users. |
I agree with @ivan-kleshnin. It would really help if Joi took into account front-end uses. I use Joi to help power front-end validation, and it's always frustrating to see responses in Joi discussions that simply say "That's a front-end issue; Joi isn't meant for front-end validation so it's not getting added." On the front page, Joi is described as an "Object schema description language and validator for JavaScript objects." It does not mention anywhere that Joi is only designed for API uses. This creates a disconnect and frustration when a developer starts off with Hapi and uses what they think is a "validator for Javascript objects" but when they start browsing issues find out it's actually a "validator for Javascript objects used in an API." To my knowledge there is not a front-end validation library for Hapi so Joi is used by front-end Hapi developers. Is there another validation library for the front-end that I'm missing? What do you recommend for front-end validation? Why is there such a strong aversion to adding a simple addition like the "customMessage" mentioned in the other thread that would greatly benefit front-end Hapi/Joi developers? |
@agraddy, oh man I share your pain... Validation is one of the truly platform agnostic processes. It's a great opportunity to reuse existing knowledge (rare luxury). We want to use the same language, the same library everywhere being it frontend, backend, hybrid, react native or whatever comes whenever it's possible. Memory footprint is a most valueable resource. No single validation aspect is platform bound. Those "messages" issue is a cake to solve. But as Joi developers insist on support "API-only" users and are deaf to multiple requests to change it... Well maybe it's time to reconsider library choice. I'm perfectly sure I don't want to use multiple validation libraries in single project just for the matter of syntax and stupid limitations. |
Seems reasonable to add some sort of message prefix that will indicate "don't add context". Something like |
I'm not saying it can't be done, I'm saying the reason why you want it is bogus to me. I know from experience that this will never be enough, we'll have to add i18n to joi to satisfy everyone, and that's something that'll probably never be part of joi. Now ignoring the reason for that request, yes removing the prefix is technically perfectly achievable, I'll get on it. |
Couldn't disagree more with your reasoning but I'll better skip on this. |
Disagree on what ? The need for i18n or the fact that I don't see i18n coming to joi ? |
I didn't say a word about i18n. You were given enough arguments for message control in all related threads, including this one, which are boring to repeat. I18n should be in client code. Mechanism to change messages belongs to library. |
That's a real pleasure to argue with you. Wanting custom messages and not considering i18n is very short sighted, anyway it's almost done. |
@ivan-kleshnin the technical details of your request are fine (removing the automatically added prefix) but the reason is not. You want joi to manage your client interface and @Marsup's point is that this change will not accomplish that. No reason to make this discussion unpleasant. We're resolving it the best we can. |
@hueniverse, my reason was actually quite else but that somehow lost in discussion. Thanks anyway. |
I'll make a fourth try to explain. You guys make a serie of assumptions. Every one of them is dubious.
Compromise solution is quite easy. Then argument |
I've never said it was designed for API, I've said messages shouldn't be forwarded to end-users as is, mainly for i18n reasons, and I maintain that i18n can't be part of the core joi features, it doesn't mean it can't be i18n-friendly. What's keeping your from making a utility function to fulfill your need ? Something like this : function validate (schema, object) {
var result = schema.validate(object);
if (result.errors) {
// You have everything Joi uses for messages at your disposal in result.errors.details
return i18n.transform(result.errors);
}
return true;
} If this is not enough I'll try to think of something else. |
Thanks, I'll try to experiment with this approach. |
Wow, it seems to have gotten heated in here for a bit. I think this is an important discussion. This is a busy week for me, but I'm going to try to put together some thoughts later in the week. Thanks everybody for taking the time to work through issues. |
Now that it's possible to remove the prefix, what do you think is missing ? |
Hmm, not yet had the chance to use Joi, but it looks like it's exactly what I need. On the other hand maybe I would allow passing custom numeric codes for errors. verifyPassword: Joi.any().valid('pass').options({ errCode: 5 }) and put it in the err object. |
Errors already contain a property |
some kind? |
Nope but probably should be, the shape is the following : {
message: 'failure message',
path: 'dotted path to the property that failed',
type: 'type of failure',
context: 'object with some context, depends on the type of check'
} I've yet to find a good way to document it. |
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. A 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. I 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 - #484 (comment) ) 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 #193 (comment) ). It'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. I 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: var Hapi = require('hapi');
var Joi = require('joi');
var routes = [];
// Create a server with a host and port
var server = new Hapi.Server();
server.connection({
host: 'localhost',
port: 8000
});
// Create routes
routes.push({
method: 'GET',
path: '/',
handler: function (request, reply) {
var html = '';
html += '<!DOCTYPE html>';
html += '<html>';
html += '<head>';
html += '<meta charset="UTF-8" />';
html += '<title>The Pirate List</title>';
html += '<script>';
html += 'function init() {';
html += 'document.getElementById("form_email_add").onsubmit = function(e) {';
html += 'e.preventDefault();';
html += 'var httpRequest = new XMLHttpRequest();';
html += 'httpRequest.onreadystatechange = function(){';
html += 'var data;';
html += 'if(httpRequest.readyState == 4){';
html += 'data = JSON.parse(httpRequest.responseText);';
html += 'if (httpRequest.status === 200) {';
html += 'document.getElementById("form_box").innerHTML = data.message;';
html += '} else {';
html += 'document.getElementById("error").innerHTML = data.message;';
html += '}';
html += '}';
html += '};';
html += 'httpRequest.open("POST", "/ajax/email-add");';
html += 'httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");';
html += 'httpRequest.send("email=" + encodeURIComponent(document.getElementById("input_email").value));';
html += 'return false;';
html += '};';
html += '}';
html += '</script>';
html += '</head>';
html += '<body onload="init()">';
html += '<p>Join the Pirate List matey!</p>';
html += '<div id="form_box">';
html += '<p id="error"></p>';
html += '<form id="form_email_add" action="/ajax/email-add" method="post">';
html += '<input id="input_email" type="text" name="email" value="" placeholder="Enter your email" />';
html += '<input type="submit" value="Sign Me Up!" />';
html += '</form>';
html += '</div>';
html += '</body>';
html += '</html>';
reply(html);
}
});
// If you change customMessage to label, the entire form will work.
routes.push({
method: 'POST',
path: '/ajax/email-add',
config: {
validate: {
payload: {
email: Joi.string().email().required().customMessage('Aaarrrrrggghhhhh!!! It needs to be valid matey!')
},
options: { abortEarly: false }
}
},
handler: function (request, reply) {
console.log('Add ' + request.payload.email + ' to the list.');
reply({status: 'success', message: 'Welcome aboard matey!'});
}
});
// Add the routes
server.route(routes);
// Start the server
server.start(function () {
console.log('Server running at:', server.info.uri);
}); How would you recommend setting up a custom message like that? And 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: var Hapi = require('hapi');
var Joi = require('joi');
var routes = [];
// Create a server with a host and port
var server = new Hapi.Server();
server.connection({
host: 'localhost',
port: 8000
});
// Create routes
routes.push({
method: 'GET',
path: '/',
handler: function (request, reply) {
var html = '';
html += '<!DOCTYPE html>';
html += '<html>';
html += '<head>';
html += '<meta charset="UTF-8" />';
html += '<title>Contact Form</title>';
html += '<script>';
html += 'function init() {';
html += 'document.getElementById("form_contact").onsubmit = function(e) {';
html += 'e.preventDefault();';
html += 'var httpRequest = new XMLHttpRequest();';
html += 'httpRequest.onreadystatechange = function(){';
html += 'var data;';
html += 'if(httpRequest.readyState == 4){';
html += 'data = JSON.parse(httpRequest.responseText);';
html += 'if (httpRequest.status === 200) {';
html += 'document.getElementById("form_box").innerHTML = data.message;';
html += '} else {';
html += 'document.getElementById("error").innerHTML = data.message + ".";';
html += '}';
html += '}';
html += '};';
html += 'var output = "";';
html += 'output += "email=" + encodeURIComponent(document.getElementById("input_email").value);';
html += 'output += "&subject=" + encodeURIComponent(document.getElementById("input_subject").value);';
html += 'output += "&message=" + encodeURIComponent(document.getElementById("textarea_message").value);';
html += 'httpRequest.open("POST", "/ajax/contact-send");';
html += 'httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");';
html += 'httpRequest.send(output);';
html += 'return false;';
html += '};';
html += '}';
html += '</script>';
html += '</head>';
html += '<body onload="init()">';
html += '<p>Contact Form:</p>';
html += '<div id="form_box">';
html += '<p id="error"></p>';
html += '<form id="form_contact" action="/ajax/contact-send" method="post">';
html += '<input id="input_email" type="text" name="email" value="" placeholder="Your email" /><br/>';
html += '<input id="input_subject" type="text" name="subject" value="" placeholder="Subject" /><br/>';
html += '<textarea id="textarea_message" name="message" placeholder="Your message"></textarea><br/>';
html += '<input type="submit" value="Send" />';
html += '</form>';
html += '</div>';
html += '</body>';
html += '</html>';
reply(html);
}
});
routes.push({
method: 'POST',
path: '/ajax/contact-send',
config: {
validate: {
payload: {
email: Joi.string().email().required().label('Your email'),
subject: Joi.string().required().label('Subject'),
message: Joi.string().required().label('Message')
},
options: { abortEarly: false }
}
},
handler: function (request, reply) {
console.log('Handle the contact form.');
reply({status: 'success', message: 'Thank you! Your message has been sent.'});
}
});
// Add the routes
server.route(routes);
// Start the server
server.start(function () {
console.log('Server running at:', server.info.uri);
}); I 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! |
I agree on there being confusion about how to handle things and to me it comes down to the fact that there are not many examples and blog posts etc about how to do things with Hapi. The most popular one (i think) is https://medium.com/@_expr/the-pursuit-of-hapi-ness-d82777afaa4b and is from Hapi 6.0 so yeah... I think the best way to handle errors in a custom way is to use |
@agraddy I think the customMessage won't be necessary with the next version, the problem I have with such API is this doesn't give any hint about what really went wrong. The correct way to write your pirate example in the future would be : email: Joi.string().email().required().language({
string: {
email: '!!Aaarrrrrggghhhhh!!! It needs to be valid matey!' // Note the !! escape prefix
}
}) Eventually adding the same message in Now if your property is nested into objects or arrays, joi will of course give context around that sentence, because that's what it's made for, targeted errors. I do consider any more advanced error transformation should be outside of joi (be it hapi or anything else), using the details property of the error joi provides. |
I think documenting the format would be a good start |
The problem with these examples is that they are completely unrealistic for any user-facing site. hapi's default configuration assumes an API server, not a user-facing HTML site. This means the default validation error is JSON with enough information a developer or machine can figure out what is wrong. What you are asking is clearly intended for normal users, not developers. This means the JSON response is not going to be useful either way. You will need to use either the |
I'm not sure how these examples are unrealistic. Could you explain? I've been using hapi for almost a year in a similar manner to the examples I provided. Meaning, I first create routes for a base API, then I create AJAX routes that use server.methods (for caching) and inject() to access the API routes, and I send the JSON data back in AJAX to jQuery/Handlebar-based views (the work is private otherwise I would provide links). With the addition of label(), I've found the error messages work well for normal users. Take the newsletter sign up. Are you saying that Hapi shouldn't be used at all in a newsletter sign up page (wrong tool for the job) or are you saying a newsletter sign up page should be structured different in Hapi (right tool but example is coded wrong)? @Marsup, thanks for the "!!" addition. "!!" feels a little dirty/unintuitive to me, but it definitely works, although it sounds like I'm doing things wrong. I haven't looked at the details property which seems like it would help a lot. Do you know why hapi strips out the details property? Is it a security issue or code cruft? Is the stripping of details an item we should create an issue and/or pull request for in hapi or is there a specific reason it's stripped off? |
@agraddy I missed the part where your client is an AJAX code that gets JSON error messages, extracts them and shows them to the user. This use case was never part of the design as the errors generated by joi were meant to be consumed by developers or machines, not end users. I am not arguing against the use case, just that it was never our goal here. The assumption was that anything exposed to users will go through a Your approach tried to accomplish this directly via the joi schema and some logic in your client code to handle multiple errors etc. That's neat but since it was not part of the use cases, a bit rough and limited (the language settings can't really handle proper grammar and complex errors). I think the right approach here is to label things and then have a post validation method that performs the actual user interface response. I don't remember the state of |
It shouldn't feel dirtier than any other templating engine out there. For reference by stripping I meant this line, that's actually more a cherry picking of the |
Your response times are really impressive! Thanks! I'm going to go through everything and put together a response when I get a chance to look closer at the responses and dig into the code @Marsup linked to (it may take about a week or two for me to go through everything with my current schedule). |
Regarding type, it doesn't always work out so smoothly. In my example above 'any.allowOnly' doesn't really tell me what the error was. The syntax does not match the semantics, which is that two fields don't match. I think my question is, can I create a more meaningful custom type |
Another type of validation where generic I'm probably missing something, thus the comments. 😄 |
The semantic is good enough I think, the context tells you that the only valid values are in I don't see your point about CC validation. |
It's a corner case, which occurs on nearly every site out there, but the semantics here definitely do not implicate that two these two (password) fields should match. Intuitively
Yeah, I was little confused myself when I re-read that. The point I wanted to make was that if I created a custom validation for credit cards, the error type would not result in something useful to an external developer. A credit card validation should result in something like "creditCard.invalid". I don't intend to be overly harsh but my overall feeling for the validation result object is that I cannot expose it to developers using an API that I provide to them. Basically, I cannot use this format out of the box if I write an API for 3rd party use. I need to re-translate nearly everything to make it more intuitive. Reason being that I don't want the developer using my API to have to learn my API plus the Joi API just to understand validation. However, it might be that this is not something important to the project and that is understandable. You can't be everything for everybody. 😄 |
Valids makes sense for any equality (single or multi), be it values or references. In your case putting a reference doesn't make it a rule, it's transformed into Your CC example is even more confusing because that's a case we deal with, and it has the correct error type. Having a translator for joi errors doesn't strike me as overly complicated, but I welcome any suggestions to help you do your task. It is indeed important to the project, errors are here to help, and I believe custom types and validations will achieve this better than any new API I could make. |
Maybe what I'm really looking for is #577. Even though I used CC validation as an example, after re-reviewing the docs I'm actually not sure how I would implement this in Joi without the proposed changes in #577. 💭 If you're not using a crazy regex, maybe you can provide an example of CC validation in the examples folder? |
If the current CC validation isn't enough can you create another issue about what's wrong ? |
I was going to jump on the "messages aren't user friendly" bandwagon, but the more I think about it I don't see how it is doable for the joi developers. If you could return a seperate error code for each type of sub-error level, i.e. "210= integer below minimum" or "220 = integer above maximum", or at the error code level i.e. "200=integer failed (to cover both of the above)", the developer can write his own return message library. (Im using joi 6.9.0) |
We do return errors codes, just not ints. I'm in the process of documenting it but any help is welcome. |
For anyone who may need a solution to this: https://github.com/dialexa/relish |
@jamesdixon Love relish as a plugin solution. I have also come across https://github.com/eddyystop/joi-errors-for-forms which is simpler but really does the trick for forms. Personally, I like the idea of joi haveing hard standards on how error messages are formatted and using plugins to read those error messages and translate them however you want. |
That's not a bug, it's a feature? Thanks for the solution to this conundrum @jamesdixon. ✌️ |
Custom messages, including i18n, are possible in Joi: https://medium.com/@Yuschick/building-custom-localised-error-messages-with-joi-4a348d8cc2ba |
I'm also really struggling to see the use-case of this, especially if a custom error message is provided, it really should be using that message. A context object is already provided, which users can use. I've solved this issue by using a regex to replace the key: |
I'm going to lock this topic as it's not going anywhere. If you have a problem with the current way of handling errors, open another issue. |
I want to stick "Passwords don't match" message to
verifyPassword
field.First attempt:
Failed. Result is "verifyPassword Passwords don't match". Joi prefixes message with
label
and it seems there is no way to turn it off.Second attempt:
Failed. "smart" check inside Joi prohibits empty labels...
The text was updated successfully, but these errors were encountered: