Mongoose conditional required validation - validation

I'm using mongoose and trying to set a custom validation that tells the property shall be required (ie. not empty) if another property value is set to something. I'm using the code below:
thing: {
type: String,
validate: [
function validator(val) {
return this.type === 'other' && val === '';
}, '{PATH} is required'
]}
If I save a model with {"type":"other", "thing":""} it fails correctly.
If I save a model with {"type":"other", "thing": undefined} or {"type":"other", "thing": null} or {"type":"other"} the validate function is never executed, and "invalid" data is written to the DB.

As of mongoose 3.9.1, you can pass a function to the required parameter in the schema definition. That resolves this problem.
See also the conversation at mongoose: https://github.com/Automattic/mongoose/issues/941

For whatever reason, the Mongoose designers decided that custom validations should not be considered if the value for a field is null, making conditional required validations inconvenient. The easiest way I found to get around this was to use a highly unique default value that I consider to be "like null".
var LIKE_NULL = '13d2aeca-54e8-4d37-9127-6459331ed76d';
var conditionalRequire = {
validator: function (value) {
return this.type === 'other' && val === LIKE_NULL;
},
msg: 'Some message',
};
var Model = mongoose.Schema({
type: { type: String },
someField: { type: String, default: LIKE_NULL, validate: conditionalRequire },
});
// Under no condition should the "like null" value actually get persisted
Model.pre("save", function (next) {
if (this.someField == LIKE_NULL) this.someField = null;
next()
});
A complete hack, but it has worked for me so far.

Try adding this validation to the type attribute, then adjust your validation accordingly. E.g.:
function validator(val) {
val === 'other' && this.thing === '';
}

thing: {
type: String,
required: function()[{
return this.type === 'other';
}, 'YOUR CUSTOM ERROR MSG HERE']
}

Related

Conditional validation of the input field vue3 vuelidate

I'm building conditional form with vue and stuck on how conditionally validate input field based on the previous users choice. I'll appreciate any help.
in the form I have dropdown menu with payment choices (terminal, payconiq, cash etc).
Second input field is the link user need to add. If user choose terminal, he should add to link input an IP Address. If not - url address.
I receive a paymentId from options with #click, send it to the store and get it from the store with computed property.
The problem is that my vuelidate does not read the condition
const typefromStore = computed(() => paymentStore.paymentType) // here I get typeId from store
const validations = {
description: { required },
type: { required },
link: {
required,
type: typefromStore.value === 1? ipAddress : url // always checks for url and give an error if I need to input IP address
},
}
I read documentation, but I didn't find how to check second input field based on the value of the previous. Only cases when previous field is invalid. But data from dropdown list is always valid.
I need to use the value of 'type' somehow to check conditionally value of link.
Found the solution. May be will help someone. Needed to put validation in computed. No store needed
let newMethod = reactive({
instanceId: 1,
description: '',
type: -1,
link: '',
active: true,
})
const rules = computed(() => {
const localRules = {
description: { required },
type: { required },
link: {},
}
if (newMethod.type === 1) {
localRules.link = {
ipAddress,
}
}
else {
localRules.link = {
url,
}
}
return localRules
})
const v$ = useVuelidate(rules, newMethod, { $autoDirty: true })

How to use custom GraphQLScalarType with default GraphQL types?

I created a GraphQLScalarType to prevent arguments with empty strings. The issue however is if I don't pass the argument at all when calling the mutation, the mutation succeeds and gives the field a value of null.
Usually, I'd wrap the type in a GraphQLNonNull type e.g
GraphQLNonNull(GraphQLString). But that doesn't work with my custom scalar type.
function validateEmptyStrings(value) {
if (typeof value !== 'string') {
throw new TypeError('Value must be a string')
}
if (value === "") {
throw new TypeError('Value cannot be empty')
}
return value
}
const NonEmptyString = new GraphQLScalarType({
name: 'NonEmptyString',
serialize: validateEmptyStrings,
parseValue: validateEmptyStrings,
})
My mutation below
addClient: {
type: ClientType,
args: {
name: {type: NonEmptyString, required: true},
},
resolve(parent, args) {
const client = new Client ({
name: args.name,
})
return client.save()
}
}
Wrapping the arg 'name' type like GraphQLNonNull(NonEmptyString) doesn't work, and neither does the required: true do anything
Apparently, Wrapping the arg 'name' type like GraphQLNonNull(NonEmptyString) does indeed work.
I'm not sure how I skipped that part. Thanks Matthew Herbst for making me take a second look

Define Knockout validation rule that takes an observable parameter using typescript

I have defined a validation rule like this
ko.validation.rules["studentValidation"] = {
validator: (val: any, params: any) => {
return (this.IsInRequiredRangeForStudent(params.DateOfBirth) && val === false);
}
}
IsInRequiredRangeForStudent = (dateOfBirth: any) () => {
//my implementation
}
Here is my ViewModel class, where i consume and apply this rule on an observable
this.isStudent = ko.observable<boolean>(isStudent).extend({
studentValidation: {
message: "Invalid student option!",
params: {
DateOfBirth: this.dateOfBirth()
}
}
});
In my validation rule implementation, I always get params.DateOfBirth as null. What I am doing wrong here?
params.DateOfBirth can be null for several reasons. But firstly I would check one scenario. There is a chance that when you extending isStudent observable, you define validation params assigning value of dateOfBirth observable. But the value is evaluated at the moment of assigning, I don't see the rest of your code but it's highly possible that dateOfBirth observable is null at the moment of assigning to params. So every further check of params.DateOfBirth may return NULL value.
Please try following:
this.isStudent = ko.observable<boolean>(isStudent).extend({
studentValidation: {
message: "Invalid student option!",
params: {
DateOfBirth: this.dateOfBirth
}
}
});
and this:
ko.validation.rules["studentValidation"] = {
validator: (val: any, params: any) => {
return (this.IsInRequiredRangeForStudent(params.DateOfBirth()) && val === false);
}
}
What it changes? It defines params.DateOfBirth as function (not a value), so you can evaluate its value on every validation call.

Polymer paper-input custom validator issue

I am trying to implement a custom validator for a paper-input. In this particular case, the control should accept positive numbers. However, not only only will the control only accept positive numbers, it will also run some other custom validation logic to determine if the entry falls within a constantly changing (dynamic & calculated) upper and lower limit. Ideally, the paper-input control's error-message text will also change depending on what part of the custom validator check failed.
In the past, I was able to implement this sort of thing with the gold-email-input element. In that case, the control checks for an entry that matches a regular expression for email addresses (i.e. implements a type-check). It also calls a backend api to see if the email address entered (as it is being typed), already exists in a database. If it exists in the database, the control fails validation and updates the control's validation error-message with a custom message. If it does not exist, it passes validation. As you might have imagined by this description, this was for a user registration UI element whereby the provided email should not already exist in the current list of user accounts. Here is an excerpt of that working code below for your reference:
<gold-email-input id="userEmail" label="Email" required auto-validate value="{{userEmail}}" error-message$="{{_getEmailErrorMsg(0)}}" invalid="{{_emailInvalid}}" validator="_validateEmail"></gold-email-input>
<iron-signals on-iron-signal-email-used="_accountFound" on-iron-signal-email-available="_accountNotFound"></iron-signals>
<script>
var emailErrors = ["Provide a valid email address", "Address already used"];
// Register the polymer element
Polymer({
properties: {
userEmail: {type: String, value: null},
validated: {type: Boolean, notify: true}, //overall validity state of entire element
_emailInvalid: {type: Boolean, value: true, observer: "_validityChanged"}, // validity state of email input itself
},
ready: function() {
// Called before attached
this.$.userEmail.validate = this._validateEmail.bind(this);
},
_accountFound: function() {
// Listener function intended to fire when the user email address/account was found
console.log(this.nodeName + " accountFound listener called\n");
this.$.userEmail.errorMessage = this._getEmailErrorMsg(1);
this._emailInvalid = true;
},
_accountNotFound: function() {
// Listener function intended to fire when the user email address/account was not found
console.log(this.nodeName + " accountNotFound listener called\n");
this.$.userEmail.errorMessage = this._getEmailErrorMsg(0);
this._emailInvalid = false;
},
_checkAccountExistance: function() {
if (this.userEmail !== undefined && this.userEmail != null) {
this.$.user.checkEmailAvailability(this.userEmail);
} else {
this._emailInvalid = true;
}
},
_getEmailErrorMsg: function(code) {
if (code !== undefined && code != null) {
return emailErrors[code];
} else {
return "";
}
},
_validateEmail: function() {
// Custom validator function for email input (also checks if email has already been associated to any user accounts)
console.log(this.nodeName + " validateEmail validator called\n");
// Check if proper email address format (W3C Spec Regex used)
var validEntry = /^[a-zA-Z0-9.!#$%&�*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(this.userEmail);
if (validEntry) {
this._emailInvalid = false;
this._checkAccountExistance();
} else {
this._emailInvalid = true;
}
}
_validityChanged: function(newVal, oldVal) {
// set the containing/parent element's overall validity state
this.validated = (!this._nameInvalid && !this._pwInvalid && !this._phoneInvalid && !this._emailInvalid && !this._countryInvalid && !this._regionInvalid && !this._cityInvalid);
},
});
</script>
Now, if I try to implement a similar approach with the paper-input component, it does not work. The custom validator function does not get called at any point. Is there something inherently different with paper-input compared to gold-email-input? Should it not treat validation the same way?
<paper-input id="xpos" label="Horizontal Position" required auto-validate value="{{XPos}}" error-message="Provide the x position" invalid="{{_xInvalid}}" validator="_validatePosition"></paper-input>
<script>
// Register the polymer element
Polymer({
properties: {
xPos: {type: Number},
validated: {type: Boolean, notify: true}, //overall validity state of entire element
_xInvalid: {type: Boolean, value: true, observer: "_validityChanged"}, // validity state of xpos input itself
},
ready: function() {
// Called before attached
this.$.xpos.validate = this._validatePosition.bind(this);
},
_validatePosition: function() {
console.log(this.nodeName + " validatePosition validator called\n");
// perform some validation code here like the gold-email-input example above
}
});
</script>

Sails.js and Waterline: dynamic validation by DB request

I use Sails 11.1 and Waterline 2.11.2 with a MongoDB database.
I would like to validate data inserted in my "Article" model using a in validator for 1 attribute.
Before, I was doing the job with lifecycle callbacks (beforeCreate and beforeUpdate especially), but it makes double code.
Here you have the model, truncated with just the attribute in question :
module.exports =
{
schema: true,
autoCreatedAt: false,
autoUpdatedAt: false,
attributes:
{
theme:
{
model: 'Theme',
required: true
}
}
}
I know how to define it statically:
in: ['something', 'something other']
I know how to call constants I defined in my constants.js file :
defaultsTo: function ()
{
return String(sails.config.constants.articleDefaultTheme);
}
But I would like to get all themes in my DB, to have a dynamic in validation. So, I wrote this :
theme:
{
model: 'Theme',
required: true,
in: function ()
{
Theme.find()
.exec(function (err, themes)
{
if (err)
{
return next({ error: 'DB error' });
}
else if (themes.length === 0)
{
return next({ error: 'themes not found' });
}
else
{
var theme_ids = [];
themes.forEach(function (theme, i)
{
theme_ids[i] = theme.theme_id;
});
return theme_ids;
}
});
}
}
But it's not working, I have always the "1 attribute is invalid" error. If I write them statically, or if I check in the beforeCreate method with another DB request, it works normally.
If I sails.log() the returned variable, all the themes ids are here.
I tried to JSON.stringify() the returned variable, and also to JSON.parse(JSON.stringify()) it. I also tried to convert the theme.theme_id as a string with the String() function, but nothing else...
What am I doing wrong? Or is it a bug?
You can also check my question here : Waterline GitHub issues
Models's configuration at your attributes scope at in field of course will throw an error, because it should not use a function, especially your function is not return anything, also if you force it to return something, it will return Promise that Theme.find()... did.
Try use different approach. There are exist Model Lifecycle Callbacks. You can use something like beforeCreate, or beforeValidate to manually checking your dynamic Theme, if it's not valid, return an error.
Or if it's achievable using standard DB relation, just use simple DB relation instead.

Resources