Refactoring validation handling and messaging - javascript

I am working on a react project that uses redux forms. I've looped through the fields to check their validation requirements if need be. This stack works well - but I know I should re-visit this to place parts inside a function
if(field.validate.includes("email")) {
//email
if (!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values[fieldName])) {
errors[fieldName] = 'Invalid email address'
}
}
if(field.validate.includes("minLength")) {
//minLength
if (values[fieldName] !== undefined && values[fieldName].length < 3) {
errors[fieldName] = 'Must be 3 characters or more'
}
}
if(field.validate.includes("required")) {
//required
if (!values[fieldName] && typeof values[fieldName] !== "number") {
errors[fieldName] = 'Required'
}
}
I've tried to write a function that looks like this - but I don't want to break the stack.
messageHandler(field, validation, rule, message){
if(field.validate.includes(validation)) {
if (rule) {
return message;
}
}
}

From what I can see, you're trying to validate a field's content against a set of defined rules.
To me, rules are just functions that can either be successful or not. For the sake of simplicity, let's say that if they return null it means that they are successful and otherwise, we'll return an error message (just a string).
const rules = {
email: value => !/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)
? 'Invalid email address'
: null,
minLength: value => value !== undefined && value.length < 3
? 'Must be 3 characters or more'
: null,
required: value => !value && typeof value !== "number"
? 'Required'
: null,
};
Now, for each rule that we find in field.validate, we'll apply the corresponding rule and collect the result :
const matchingRules = field.validate
.map(formValidate => rules[formValidate])
.filter(x => x); // filter nullish values (rules that are not found)
const errors = matchingRules
.map(fn => fn(values[fieldName]))
.filter(x => x); // filter nullish values (successful rules)
Now, errors contains a list of strings describing how the field failed the different rules, and of course, if errors.length === 0, the test is successful.
You can add as many rules as you want without repeating all the ifs.

Is it acceptable in this project to bring in another lib? I like to use joi for validation. Of course, it is important to understand how validation works under the hood, but it seems like you have a pretty good grasp on that.
Here's an example of how you'd implement this with your current code:
First you would define a schema, which essentially represents your ideal end-state for your filled-in form. Below, I am saying that the form values will include an email, which will be a required string that is at least 3 characters long.
const Joi = require('joi');
const schema = Joi.object({
email: Joi.string().email().required().min(3)
})
Then, when you are ready to validate the form data:
const validation = schema.validate({ email: 'foo#bar.com' });
validation will contain values & errors (if there are any).
You can throw that schema.validate function in a useEffect that fires off whenever the user updates an input, or you can wait until the user is trying to submit the form, whatever your UI requires.
I like it because it is easy to read and write and it's quite flexible.

Related

Is there any option to find whether a text is present in either one of the field in firestore [duplicate]

From the docs:
You can also chain multiple where() methods to create more specific queries (logical AND).
How can I perform an OR query?
Example:
Give me all documents where the field status is open OR upcoming
Give me all documents where the field status == open OR createdAt <= <somedatetime>
OR isn't supported as it's hard for the server to scale it (requires keeping state to dedup). The work around is to issue 2 queries, one for each condition, and dedup on the client.
Edit (Nov 2019):
Cloud Firestore now supports IN queries which are a limited type of OR query.
For the example above you could do:
// Get all documents in 'foo' where status is open or upcmoming
db.collection('foo').where('status','in',['open','upcoming']).get()
However it's still not possible to do a general OR condition involving multiple fields.
With the recent addition of IN queries, Firestore supports "up to 10 equality clauses on the same field with a logical OR"
A possible solution to (1) would be:
documents.where('status', 'in', ['open', 'upcoming']);
See Firebase Guides: Query Operators | in and array-contains-any
suggest to give value for status as well.
ex.
{ name: "a", statusValue = 10, status = 'open' }
{ name: "b", statusValue = 20, status = 'upcoming'}
{ name: "c", statusValue = 30, status = 'close'}
you can query by ref.where('statusValue', '<=', 20) then both 'a' and 'b' will found.
this can save your query cost and performance.
btw, it is not fix all case.
I would have no "status" field, but status related fields, updating them to true or false based on request, like
{ name: "a", status_open: true, status_upcoming: false, status_closed: false}
However, check Firebase Cloud Functions. You could have a function listening status changes, updating status related properties like
{ name: "a", status: "open", status_open: true, status_upcoming: false, status_closed: false}
one or the other, your query could be just
...where('status_open','==',true)...
Hope it helps.
This doesn't solve all cases, but for "enum" fields, you can emulate an "OR" query by making a separate boolean field for each enum-value, then adding a where("enum_<value>", "==", false) for every value that isn't part of the "OR" clause you want.
For example, consider your first desired query:
Give me all documents where the field status is open OR upcoming
You can accomplish this by splitting the status: string field into multiple boolean fields, one for each enum-value:
status_open: bool
status_upcoming: bool
status_suspended: bool
status_closed: bool
To perform your "where status is open or upcoming" query, you then do this:
where("status_suspended", "==", false).where("status_closed", "==", false)
How does this work? Well, because it's an enum, you know one of the values must have true assigned. So if you can determine that all of the other values don't match for a given entry, then by deduction it must match one of the values you originally were looking for.
See also
in/not-in/array-contains-in: https://firebase.google.com/docs/firestore/query-data/queries#in_and_array-contains-any
!=: https://firebase.googleblog.com/2020/09/cloud-firestore-not-equal-queries.html
I don't like everyone saying it's not possible.
it is if you create another "hacky" field in the model to build a composite...
for instance, create an array for each document that has all logical or elements
then query for .where("field", arrayContains: [...]
you can bind two Observables using the rxjs merge operator.
Here you have an example.
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/merge';
...
getCombinatedStatus(): Observable<any> {
return Observable.merge(this.db.collection('foo', ref => ref.where('status','==','open')).valueChanges(),
this.db.collection('foo', ref => ref.where('status','==','upcoming')).valueChanges());
}
Then you can subscribe to the new Observable updates using the above method:
getCombinatedStatus.subscribe(results => console.log(results);
I hope this can help you, greetings from Chile!!
We have the same problem just now, luckily the only possible values for ours are A,B,C,D (4) so we have to query for things like A||B, A||C, A||B||C, D, etc
As of like a few months ago firebase supports a new query array-contains so what we do is make an array and we pre-process the OR values to the array
if (a) {
array addObject:#"a"
}
if (b) {
array addObject:#"b"
}
if (a||b) {
array addObject:#"a||b"
}
etc
And we do this for all 4! values or however many combos there are.
THEN we can simply check the query [document arrayContains:#"a||c"] or whatever type of condition we need.
So if something only qualified for conditional A of our 4 conditionals (A,B,C,D) then its array would contain the following literal strings: #["A", "A||B", "A||C", "A||D", "A||B||C", "A||B||D", "A||C||D", "A||B||C||D"]
Then for any of those OR combinations we can just search array-contains on whatever we may want (e.g. "A||C")
Note: This is only a reasonable approach if you have a few number of possible values to compare OR with.
More info on Array-contains here, since it's newish to firebase docs
If you have a limited number of fields, definitely create new fields with true and false like in the example above. However, if you don't know what the fields are until runtime, you have to just combine queries.
Here is a tags OR example...
// the ids of students in class
const students = [studentID1, studentID2,...];
// get all docs where student.studentID1 = true
const results = this.afs.collection('classes',
ref => ref.where(`students.${students[0]}`, '==', true)
).valueChanges({ idField: 'id' }).pipe(
switchMap((r: any) => {
// get all docs where student.studentID2...studentIDX = true
const docs = students.slice(1).map(
(student: any) => this.afs.collection('classes',
ref => ref.where(`students.${student}`, '==', true)
).valueChanges({ idField: 'id' })
);
return combineLatest(docs).pipe(
// combine results by reducing array
map((a: any[]) => {
const g: [] = a.reduce(
(acc: any[], cur: any) => acc.concat(cur)
).concat(r);
// filter out duplicates by 'id' field
return g.filter(
(b: any, n: number, a: any[]) => a.findIndex(
(v: any) => v.id === b.id) === n
);
}),
);
})
);
Unfortunately there is no other way to combine more than 10 items (use array-contains-any if < 10 items).
There is also no other way to avoid duplicate reads, as you don't know the ID fields that will be matched by the search. Luckily, Firebase has good caching.
For those of you that like promises...
const p = await results.pipe(take(1)).toPromise();
For more info on this, see this article I wrote.
J
OR isn't supported
But if you need that you can do It in your code
Ex : if i want query products where (Size Equal Xl OR XXL : AND Gender is Male)
productsCollectionRef
//1* first get query where can firestore handle it
.whereEqualTo("gender", "Male")
.addSnapshotListener((queryDocumentSnapshots, e) -> {
if (queryDocumentSnapshots == null)
return;
List<Product> productList = new ArrayList<>();
for (DocumentSnapshot snapshot : queryDocumentSnapshots.getDocuments()) {
Product product = snapshot.toObject(Product.class);
//2* then check your query OR Condition because firestore just support AND Condition
if (product.getSize().equals("XL") || product.getSize().equals("XXL"))
productList.add(product);
}
liveData.setValue(productList);
});
For Flutter dart language use this:
db.collection("projects").where("status", whereIn: ["public", "unlisted", "secret"]);
actually I found #Dan McGrath answer working here is a rewriting of his answer:
private void query() {
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("STATUS")
.whereIn("status", Arrays.asList("open", "upcoming")) // you can add up to 10 different values like : Arrays.asList("open", "upcoming", "Pending", "In Progress", ...)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots, #Nullable FirebaseFirestoreException e) {
for (DocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
// I assume you have a model class called MyStatus
MyStatus status= documentSnapshot.toObject(MyStatus.class);
if (status!= null) {
//do somthing...!
}
}
}
});
}

Express-validator how to make a field required only when another field is present

express-validator, how do I make a field required only when another field exists?
const validateUpdateStore = () => {
return [
body('logo').optional().isURL().withMessage('invalid url'),
body('email')
.optional()
.isEmail()
.withMessage('email is invalid')
.trim()
.escape(),
body('phone').optional().isInt().withMessage('integers only!'),
body('account_number').optional().isInt().withMessage('integers only!'),
body('bank_code').optional().isInt().withMessage('integers only!'),
];
};
I'd like to make the field bank_code required only when account_number is provided and vise-versa
Version 6.1.0 of express-validator added support for conditional validators. I do not currently see it in the documentation but there is a pull request with more information. It looks like you should be able to define your validation as follows:
const validateUpdateStore = () => {
return [
body('logo').optional().isURL().withMessage('invalid url'),
body('email')
.optional()
.isEmail()
.withMessage('email is invalid')
.trim()
.escape(),
body('phone').optional().isInt().withMessage('integers only!'),
body('account_number')
.if(body('bank_code').exists()) // if bank code provided
.not().empty() // then account number is also required
.isInt() // along with the rest of the validation
.withMessage('integers only!')
,
body('bank_code')
.if(body('account_number').exists()) // if account number provided
.not().empty() // then bank code is also required
.isInt() // along with the rest of the validation
.withMessage('integers only!')
,
];
};
To add to #Jason's answer here's how you can conditionally validate one field based on the value of another when the objects are in an array and you're using wildcard syntax:
// only validate `target` if `requiresTarget` is true
body('people[*].weight')
.if((value, { req, location, path }) => {
/*
Gets numeric, index value of "*". Adjust your regex as needed
if nested data uses more than one wildcard
*/
const wildcardIndex = parseInt(path.match(/([0-9]+)/)[1])
// return a true value if you want the validation chain to proceed.
// return false value if you want the remainder of the validation chain to be ignored
return req.body.people[wildcardIndex].requiresWeight
})
.isFloat() // only applies this if `requiresWeight` is true
.withMessage('weight must be float'),

How can I remove invalid fields during Joi Schema Data Validation instead of returning an error?

There is this property you provide called stripUnknown that removes fields that were not specified during schema creation, is there something like but which removes invalid fields and returns the valid ones, maybe additionally with errors.
Code sample
For example
var joi = require("#hapi/joi")
let s = joi.object({
name: joi.string(),
username: joi.string()
})
console.log(
s.validate({
name: 32,
age: 43
}, {
stripUnknown: true,
convert: true
})
)
Instead of it alerting the name is invalid only, it can return the value with name removed because it is invalid.
It is not an answer per se, as there is no specific function that supports removal of certain values (at least that I know of). I do not have the ability to comment yet, thus the answer post.
You can use Joi.custom in order to create a custom validator.
The custom accepts a callback(value,helper) and your code could be written as:
const schema = joi.object().custom((value, helper) => {
let validatedObject = {};
if (typeof value.name === 'string') {
validatedObject.name = value.name;
}
if (typeof value.username === 'string') {
validatedObject.username = value.username;
}
return validatedObject;
});
joi.attempt(params, schema, {stripUnknown: true});
This way you can return the value that you think is acceptable.
Also, you can use the helper object in order to retrieve various properties as the schema or the state of the current validation etc.
Hope I helped a bit

Apollo GraphQL - User-friendly type-validation errors

I'm currently building an app which uses Apollo for my GraphQL API. As we know, GraphQL provides a type and non-nullable checking for some fields. Suppose that I want foo field to be a Int and it's non-nullable field, we can do this in the schema (or typedefs)
foo: Int!
Which generates this kind of error
"message": "Variable \"$input\" got invalid value \"foo\"; Expected type Int; Int cannot represent non 32-bit signed integer value: foo"\
However, let's just say that I want to customize the message to something like
"message": "Foo is wrong"
Is it possible to change the default error message? The non-nullable is technically possible if you check it in your resolvers, but I don't think it's possible for the types too.
The only way to somewhat change the error messages thrown during the serialization or parsing of scalars is to provide custom implementations of those scalars. For example, you can copy the source code for your scalar of choice:
const { GraphQLScalarType } = require('graphql')
const inspect = require('graphql/jsutils/inspect').default
function serializeID(rawValue) {
const value = serializeObject(rawValue);
if (typeof value === 'string') {
return value;
}
if (Number.isInteger(value)) {
return String(value);
}
throw new TypeError(`ID cannot represent value: ${inspect(rawValue)}`);
}
function coerceID(value) {
if (typeof value === 'string') {
return value;
}
if (Number.isInteger(value)) {
return value.toString();
}
throw new TypeError(`Oh no! ID cannot represent value: ${inspect(value)}`);
}
const ID = new GraphQLScalarType({
name: 'ID',
description:
'The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.',
serialize: serializeID,
parseValue: coerceID,
parseLiteral(ast) {
return ast.kind === Kind.STRING || ast.kind === Kind.INT
? ast.value
: undefined;
},
})
const resolvers = {
ID,
/* the rest of your resolvers */
}
Now the validation message will instead be:
Variable \"$id\" got invalid value true; Expected type ID; Oh no! ID cannot represent value: true
Changing the first part of the message is not possible, though.
That said, validation errors mean something has gone wrong with your code, either on the client side (when validating inputs) or on the server-side (when validating outputs). Users probably should not be exposed to these errors and should be shown some generic error message instead.

ExtJS ComboBox validation returns unexpected result

I've just been working on a ExtJS script and I have a ComboBox which has
allowBlank = false
and
forceSelection = true
I have an item in the list which acts as a default message which has a display text
Please select...
and no value
''
When I run validate on the ComboBox I get true
No idea why?
According to the documentation when
allowBlank = false
the validation is forced to check for value.length > 0
So I have done my own test in the JS Console
>> if (thisForm.controlManager.controlArray[2].allowBlanks) { if (thisForm.controlManager.controlArray[2].length >= 0) { true; } false; } else { if (thisForm.controlManager.controlArray[2].length > 0) { true; } false; }
and it returned false
So I thought it might a bug in validate method so I tried doing this
>> thisForm.controlManager.controlArray[2].validateValue('')
and got this as a result true
Any one have any kind of idea of what I might be doing wrong or if anything else needs set to get this validate to return false when value is ''.
PS. I've also tried this
>> thisForm.controlManager.controlArray[2].validateValue(' ')
and got the correct result which is false. This made me very confused as I would normally expect '' and ' ' to return the same value in validation.
I know that a workaround would be to set my value to ' ' but I would rather get it working with ''.
Thanks
I just so happened to end up grappling with this same issue, and after some looking around, managed to find a solution which does not require overriding Extjs's standard functionality.
Basically, there is a 'validator' config option for descendents of Ext.form.field.Text which allows programmers to specify a custom validation function for a component (see here).
Basically, your validator function gets called at the start of getErrors() and is evaluated before the rest of the field's standard validation. The validator function takes one argument (the value) and must return either true if the value is valid or an error message string if it is not.
The following config ended up working for my case:
validator: function (value) {
return (value === '/*Your emptytext text*/') ? "blankText" : true;
}
You have to use the emptyText configuration
Ext have this code for validate fields:
validate : function(){
if(this.disabled || this.validateValue(this.processValue(this.getRawValue()))){
this.clearInvalid();
return true;
}
return false;
}
and getRawValue is defined like this:
getRawValue : function(){
var v = this.rendered ? this.el.getValue() : Ext.value(this.value, '');
if(v === this.emptyText){
v = '';
}
return v;
}
so, if the value is equal to the empty text, the returned value is ''

Categories

Resources