meteor-relay
meteor-relay copied to clipboard
Communicating validation errors to the client
This isn't an issue so much as a discussion/suggestion - please close if this is the inappropriate place for it.
Currently, when validation fails, the client sees an Internal server error, while the server gets the exact error (e.g. "message": "Invalid email address").
I can see scenarios where it would be helpful to get the validation error on the client side to show to the user on the UI.
Is this possible to do now (and I'm just not seeing how) or if not, is there a better best practice to follow?
Thanks in advance for a great package!
Thanks for opening this discussion @HemalR. I wasn't sure how to handle this, so went with what seemed the safer option.
From the api perspective, the obvious solution is to allow pipelines to use the onError hook to replace the validation errors with a client safe error (such as Meteor.Error) that is sent to the client. However, the validation is done before the pipeline runs, so there is never an opportunity for the onError hooks to be added. Maybe there could be some way to indicate when in the pipeline validation should happen.
Two other options are:
- Validated Methods allows running the validation on the client. We could probably do the same thing - extract the schema for the methods and publications and add it to the client, and validate the args before calling the method or subscribing to a publication.
- Add an option to the method/publication config on whether to send the validation errors to the client.
It seems most Meteor code defaults to not sending the validation details to the client, such as the check package or Validated Methods with simple schema. We probably could do something similar to the check package and send a vague error that validation failed to the client instead of an Internal server error.
Option 2 seems more straightforward IMO (to implement and use both).
We had a similar case where we wanted to make our custom error class be safely forwarded to the client and adding getters for a few fields (details, isClientSafe, and reason) was enough (as far as I remember). It's also required to make this a "safe" error, i.e., not make it appear in the server console. (Our case is to have the same error handling in GraphQL API, REST API, and Meteor methods.)
Thus, I suggest doing the validation on the server and wrapping the error in a Meteor-friendly way.
What if there was an optional onValidationError, which, if present, gets called with the validation error in question, and if absent, defaults to the behaviour as it currently is?
Two other options are:
- Validated Methods allows running the validation on the client. We could probably do the same thing - extract the schema for the methods and publications and add it to the client, and validate the args before calling the method or subscribing to a publication.
- Add an option to the method/publication config on whether to send the validation errors to the client.
I suppose the benefit of Option 1 would be immediate client-side validation, which would be a nice UX win and feels inline with Meteor's isomorphic nature. But the downside would be importing zod into the client bundle – it's larger than all of svelte which is crazy to me. Maybe this is a non-issue if Meteor supports tree shaking eventually. I'm not sure.
It seems most Meteor code defaults to not sending the validation details to the client, such as the
checkpackage or Validated Methods with simple schema. We probably could do something similar to thecheckpackage and send a vague error that validation failed to the client instead of anInternal server error.
IMO, one of the downsides of check are the vague errors. One of the nice things about zod are the error messages and the ability to customize them.
So there is yet no option to reuse the zod validation definition on the client or return a more useful error? I would have to reimplement the validation on the client?
Having worked with the library some more, I think this may be doable anyway:
import { z } from "zod";
export const zodTestSchema = z.object( {
foo: z.string(),
});
export const testMethod = createMethod({
schema: zodTestSchema,
run(element) {
if (!this.userId) {
throw new Meteor.Error("not-authorised", "You must be logged in.");
}
},
} );
// On the client check methodData against zodTestSchema with zod
import { zodTestSchema } from "./methodFile";
function isDataValid( methodData ) {
try {
zodTestSchema.parse( methodData );
} catch ( error ) {
console.error( error );
return false;
}
return true;
}
isDataValid( { foo: `bar` } ); // true
isDataValid( { foo: 3 } ); // false
I'm planning to work on an update to the package soon, including adding a solution for this issue.
My current plan is to add an option for methods and publications, probably clientSafeSchema. When true, it will do two things:
- send the validation error to the client
- include the schema on the client, so the client code can use the schema directly. It might also run the validation on the client before calling a method/subscribing to a publication, though I'm unsure on this part.
It would be awesome to use this for publication schemas vs Simpl Schema (for which I am duplicating zod schema to generate TS types)!
Having worked with the library some more, I think this may be doable anyway:
import { z } from "zod"; export const zodTestSchema = z.object( { foo: z.string(), }); export const testMethod = createMethod({ schema: zodTestSchema, run(element) { if (!this.userId) { throw new Meteor.Error("not-authorised", "You must be logged in."); } }, } );// On the client check methodData against zodTestSchema with zod import { zodTestSchema } from "./methodFile"; function isDataValid( methodData ) { try { zodTestSchema.parse( methodData ); } catch ( error ) { console.error( error ); return false; } return true; } isDataValid( { foo: `bar` } ); // true isDataValid( { foo: 3 } ); // false
Question, @zodern - is the approach I outlined here okay or does importing a zod schema in this way cause any security holes?
One of the design goals of zodern:relay was to make it as difficult as possible to accidentally include server code in the client. This is achieved by zodern:relay removing all code from the method and publication files on the client, and replacing it with simple exports that call the methods or subscribe to the publications.
The one exception is using the stub option for methods, which tells zodern:relay to add the code for the stub on the client. clientSafeSchema would be a second exception, which would add the schema code to the client.
Hmm, based on your comment it sounds like my code proposal shouldn't work and yet I've been using that pattern in my code as I refactor.
Did I misunderstand your comment or is the package smart enough to allow the export of the schema to the client knowing that it is safe to do so or something else?