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
Add better handling for schema errors in Methods #49
Comments
Hi @Bandit. Not against this at all, however, I can see it tripping up things like inserts (using a callback would prevent getting back the new document |
@cleverbeagle great point, I definitely didn't consider that. What's the best way to deal with schema validation then? As is we're better off not using What if we replaced the Documents.schema.validate(doc); // replaces Meteor's check(), and throws a Meteor.Error on failure
try {
return Documents.insert({
owner: Meteor.userId(),
...doc,
});
} catch (exception) {
throw new Meteor.Error('500', exception);
} Thoughts? (As an aside it would help to keep the code DRY as repeating the object going to |
Looks like you also need to convert the default error from SimpleSchema.defineValidationErrorTransform(error => {
const ddpError = new Meteor.Error(error.message);
ddpError.error = 'validation-error';
ddpError.details = error.details;
return ddpError;
}); [Edit] had to tweak it slightly to have it show the error message correctly in SimpleSchema.defineValidationErrorTransform((error) => {
const ddpError = new Meteor.Error('validation-error', error.message);
ddpError.error = 'validation-error';
ddpError.details = error.details;
return ddpError;
}); Not sure where the best place to put that would be? |
Hi @Bandit,
I output the |
@pilarArr nice, great idea. How do you deal with AutoValues like |
@pilarArr @Bandit looking at this code, it seems like the separation is between the Edit: |
@Bandit I had that problem too, I had to make a new schema to do the validation by picking the values I wanted to check, and then validate against that schema like this:
I read the @cleverbeagle I'll look into making that function, I've also run into an unnamed error in the client when doing inserts/updates that I don't see in either console that I have yet to trace, I'll keep you updated. I also like |
@cleverbeagle I think the best way to do it is as @pilarArr has done it in his example - by putting the Putting The only obvious pitfall with using |
Have finally got a pattern working that I'm happy with. Keen to know your thoughts: Items methods: 'items.insert': function itemsInsert(doc) {
const preparedDoc = {
...doc,
owner: Meteor.user().selectedAccountId,
};
try {
Items.schema.validate(preparedDoc);
return Items.insert(preparedDoc);
} catch (exception) {
throwError(exception);
}
},
'items.update': function itemsUpdate(doc) {
const itemId = doc._id;
const preparedDoc = {
$set: _.omit(doc, ['owner']),
};
try {
Items.schema.validate(preparedDoc, { modifier: true });
Items.update(itemId, preparedDoc);
return itemId; // Return _id so we can redirect to item after update.
} catch (exception) {
throwError(exception);
}
}, Items schema: Items.schema = new SimpleSchema({
_id: {
type: String,
label: 'The ID of this item',
optional: true,
custom() {
// only required on an update
if (!this.isSet && this.operator === '$set') return 'required';
},
},
owner: {
type: String,
label: 'The ID of the account this item belongs to',
optional: true,
custom() {
// only required on an insert
if (!this.isSet && !this.operator) return 'required';
},
},
createdAt: {
type: String,
label: 'The date this item was created',
optional: true,
autoValue() {
if (this.isInsert) return (new Date()).toISOString();
},
},
updatedAt: {
type: String,
label: 'The date this item was last updated',
optional: true,
autoValue() {
if (this.isInsert || this.isUpdate) return (new Date()).toISOString();
},
},
title: {
type: String,
label: 'The name of the item',
},
[...]
});
import { Meteor } from 'meteor/meteor';
export default (exception) => {
const message =
(exception.sanitizedError && exception.sanitizedError.message)
? exception.sanitizedError.message
: exception.message || exception.reason || exception;
throw new Meteor.Error(500, message);
}; I had to remove the |
Hi @Bandit, Your code looks good, a couple notes. Either if you are validating via
And if you have to add an
But if you solution works and you are happy with it, then that's it, I just like it better this way. The problem with
|
While I can see the importance of being able to reuse a schema, this seems like a lot of hoops to jump vs. writing the additional That said, the errors problems seems manageable, perhaps just a matter of adjusting what value we throw for an error when something goes wrong. @pilarArr ignoring the |
So not being able to reuse the schema I'd already defined was driving me slightly crazy (DRY anyone?). Having to manually enter the field names to work-around the autoValues issue defeats the purpose to some extent. I ended up doing this: /imports/api/Utility/server/schema.js: const getSchemaFieldTypes = (schema, includeId) => {
const typeMap = {};
if (includeId) typeMap._id = String;
Object.entries(schema).forEach(
([field, spec]) => {
if (typeof spec == 'object') {
if (!spec.hasOwnProperty('autoValue')) {
typeMap[field] = spec.type;
}
}
else {
typeMap[field] = spec;
}
}
);
return typeMap;
}
export { getSchemaFieldTypes }; Then you can do something like this: const DataSets = new Mongo.Collection('DataSets');
DataSets.schema = {
name: {
type: String,
label: 'The name of the dataset.',
},
description: String,
};
DataSets.attachSchema(new SimpleSchema(DataSets.schema)); ...
import { getSchemaFieldTypes } from '../Utility/server/schema';
Meteor.methods({
'datasets.insert': function datasetsInsert(dataset) {
check(dataset, getSchemaFieldTypes(DataSets.schema));
try {
return DataSets.insert(dataset);
} catch (exception) {
throw new Meteor.Error('500', exception);
}
},
'datasets.update': function datasetsUpdate(dataset) {
check(dataset, getSchemaFieldTypes(DataSets.schema, true));
try {
const datasetId = dataset._id;
DataSets.update(datasetId, { $set: dataset });
return datasetId; // Return _id so we can redirect to dataset after update.
} catch (exception) {
throw new Meteor.Error('500', exception);
}
},
... |
Clever 👍 Personally I'm fine with adding the
Your approach does have the added benefit of still utilising the |
It's certainly nice to be able to catch and display the errors like your approach does (as I've just found out ;)). Avoiding removing /imports/api/Utility/server/schema.js: import { Meteor } from 'meteor/meteor';
const getSchemaFieldTypes = (schema, doc, includeId) => {
const typeMap = {};
if (includeId) typeMap._id = String;
Object.entries(schema).forEach(
([field, spec]) => {
// Only consider top-level field schema specs.
if (!field.includes(".")) {
let optional = false;
if (typeof spec == 'object') {
if (!spec.hasOwnProperty('autoValue')) {
typeMap[field] = spec.type;
}
if (spec.hasOwnProperty('optional')) {
optional = typeof spec.optional == 'function' ? spec.optional() : spec.optional || false;
}
}
else {
typeMap[field] = spec;
}
if (optional && !doc.hasOwnProperty(field) && spec.hasOwnProperty('defaultValue')) {
doc[field] = spec.defaultValue;
}
}
}
);
return typeMap;
}
const throwSchemaException = (exception) => {
const message =
(exception.santizedError && exception.sanitizedError.message)
? exception.sanitizedError.message
: exception.message || exception.reason || exception;
throw new Meteor.Error(500, message);
};
export { getSchemaFieldTypes, throwSchemaException }; (I'm guessing Meteor.methods({
'datasets.insert': function datasetsInsert(dataset) {
check(dataset, getSchemaFieldTypes(DataSets.schema, dataset));
try {
DataSets.simpleSchema().validate(dataset);
return DataSets.insert(dataset);
} catch (exception) {
throwSchemaException(exception);
}
},
'datasets.update': function datasetsUpdate(dataset) {
check(dataset, getSchemaFieldTypes(DataSets.schema, dataset, true));
try {
const datasetId = dataset._id;
delete(dataset._id);
DataSets.simpleSchema().validate(dataset);
DataSets.update(datasetId, { $set: dataset });
return datasetId; // Return _id so we can redirect to dataset after update.
} catch (exception) {
throwSchemaException(exception);
}
},
... |
@OliverColeman this is really nice:
It may be worth mentioning this in the simple-schema repo as I feel a lot of folks are frustrated by this, too. |
@cleverbeagle I wrote that - you can tell because there's a typo in it: exception.santizedError && exception.sanitizedError.message => exception.san 🤣 |
Logging this here: http://forum.cleverbeagle.com/t/schema-unique-document-fields/110/5?u=cleverbeagle. Going to add this v.NEXT. |
Took me ages to finally track this down. If
collection2
denies an insert/update based on schema validation, nothing happens at all on the client, and nothing is printed in the server console.This can be resolved by changing the best practice method code to listen for the error from
collection2
as per its documentationI think the boilerplate code should be updated accordingly?
The text was updated successfully, but these errors were encountered: