Skip to content
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

Validation and error syntax #22

Closed
ryardley opened this issue Jul 24, 2019 · 15 comments
Closed

Validation and error syntax #22

ryardley opened this issue Jul 24, 2019 · 15 comments
Labels
enhancement New feature or request priority

Comments

@ryardley
Copy link
Owner

ryardley commented Jul 24, 2019

Not sure exactly how to do this but having a way to diagnose failure say through an errors array would mean that pdsl could be used to validate form data.

This way I think PDSL could be used for object validation in a way similar to yup but more concise and with more intuitive syntax.

@ryardley
Copy link
Owner Author

ryardley commented Jul 25, 2019

How about this:

const isVerified = p`{
  name: String ||| "Name must be a string" & 
    { length: > 7 } ||| "Length must be greater than 7" ,
  age: (Number & > 18) ||| "Age must be numeric and over 18"
} ||| "You are not verified"`.validate;

Then with white space:

const isVerified = p`{
  name: String                 ||| "Name must be a string" 
    & { length: > 7 }          ||| "Length must be greater than 7" ,
  age: (Number & > 18)         ||| "Age must be numeric and over 18"
}                              ||| "You are not verified"`.validate;

const {errs,valid} = isVerified({name: 5678, age: 20});

console.log(valid); // false
console.log(errs); // ["Name must be a string", "You are not verified"]

Caveats are that because "|||" is a right associated infix operator the commas will need to be respected which makes more sense when you don't use whitespace to align the errors.

This would de-sugar to:

const isVerified = err(
  obj(
    ["name", and(err(String, "Name must be a string"),
      err(obj(['length', gt(7)]), "Length must be greater than 7"))],
    ["age", and(Number, err(gt(18), "You must be over 18"))]
  ),
  "You are not verified"
).validator;

These can also be passed in as error keys so that way you can use localization.

const isVerified = p` {
  name: String         ||| "name_must_be_string",
  age: Number & > 18   ||| "you_must_be_over_18"
}                      ||| "you_are_not_verified"`;

const {errs} = isVerified.validate({name: 5678, age: 20});

console.log(errs.map(t)); // ["Name must be a string", "You are not verified"]

Not totally sold on quotes or hashes. Hashes make parsing easier as we can generate an invisible err operator. Syntax highlighting will differentiate the errors from the code and wrapping them in hashes make them stand out.

@ryardley
Copy link
Owner Author

ryardley commented Jul 25, 2019

hmm... maybe we need a way to give each error a name as well as a message? 🤔

@ryardley ryardley changed the title Provide a way to diagnose why a predicate failed Errors: Provide a way to diagnose why a predicate failed Jul 25, 2019
@ryardley
Copy link
Owner Author

ryardley commented Jul 25, 2019

The other feature yup users might want here would be the where() function which allows validations based on other elements of the object. In PDSL we can do that using || already.

const checkCanDrive = p`{
  name
} & ( 
  { age: <= 18, canDrive: false } || 
  { age: >= 18, canDrive: true } 
)`;

checkCanDrive({ name: "Paul", age: 17, canDrive: true }); //false
checkCanDrive({ name: "Paul", age: 17, canDrive: false }); //true
checkCanDrive({ name: "Mary", age: 18, canDrive: true }); // true 

@ryardley
Copy link
Owner Author

ryardley commented Jul 25, 2019

We would also need a way to pass dynamic data to the error message. 🤔

Perhaps function expressions?

const isVerified = p`{
  name: String ||| ${o => `${o.name} must be a string!`},
  age: Number & > 18 ||| ${o => `${o.age} must be greater than 18`}
} ||| ${o => `${o} is not verified`}`;

const {errs,valid} = isVerified.validate({name: 5678, age: 20});

console.log(valid); // false
console.log(errs); // [`5678 must be a string`, `{name:5678,age:20} is not verified`]

@ryardley
Copy link
Owner Author

ryardley commented Jul 25, 2019

@dmechea @thomasdavis @hannanmumtaz @lukekhamilton got any input here? Could use opinions. I played around with #, :: putting error messages on the left but this was the most readable to me.

@utx0
Copy link

utx0 commented Jul 25, 2019

Sorry bro, JS really isn't my strong point. I'm all golang'n these days.

@ryardley
Copy link
Owner Author

ryardley commented Jul 25, 2019

Alternatives

Use |

const isVerified = p`{
  name: String | "Name must be a string" & 
    { length: > 7 } | "Length must be greater than 7" ,
  age: (Number & > 18) | "Age must be numeric and over 18"
} | "You are not verified"`

Use #

const isVerified = p`{
  name: String # "Name must be a string" & 
    { length: > 7 } # "Length must be greater than 7" ,
  age: (Number & > 18) # "Age must be numeric and over 18"
} # "You are not verified"`.validate;

Use ::

const isVerified = p`{
  name: String :: "Name must be a string" & 
    { length: > 7 } :: "Length must be greater than 7" ,
  age: (Number & > 18) :: "Age must be numeric and over 18"
} :: "You are not verified"`.validate;

Use |||

const isVerified = p`{
  name: String ||| "Name must be a string" & 
    { length: > 7 } ||| "Length must be greater than 7" ,
  age: (Number & > 18) ||| "Age must be numeric and over 18"
} ||| "You are not verified"`.validate;

Use # string

const isVerified = p`{
  name: String #Name must be a string# & 
    { length: > 7 } #Length must be greater than 7# ,
  age: (Number & > 18) #Age must be numeric and over 18#
} #You are not verified#`.validate;

Error strings as prefixes

const isVerified = p`"You are not verified" ||| {
  name: "Name must be a string" ||| String & 
    "Length must be greater than 7" ||| { length: > 7 },
  age: "Age must be numeric and over 18" ||| (Number & > 18) 
}`.validate;

Prefixes with hash operator

const isVerified = p`"You are not verified" # {
  name: "Name must be a string" # String & 
    "Length must be greater than 7" # { length: > 7 },
  age: "Age must be numeric and over 18" # (Number & > 18) 
}`.validate;

@ryardley
Copy link
Owner Author

Sorry bro, JS really isn't my strong point. I'm all golang'n these days.

Still appreciate your input as I am creating my own language. You would have some good new syntax ideas!

@ryardley ryardley changed the title Errors: Provide a way to diagnose why a predicate failed Error syntax Jul 25, 2019
@utx0
Copy link

utx0 commented Jul 26, 2019

To me, this seams a little cryptic.. However I normally find JS to be like that. Biggest thing I love about go is how easy it is to read and understand. Very little having to decode the code.

@ryardley
Copy link
Owner Author

ryardley commented Jul 26, 2019 via email

@PandelisZ
Copy link

PandelisZ commented Aug 16, 2019

Hey! I'm interested in this as a form validator I think it has some potential there for making form validation a bit more consistent across some libraries.

Syntax wise I think you're on to something there I would say it does feel quite natural having something at the end, what actual symbol it is I don't mind. ? # :: it's all the same.

I would like to provide some context for the way I've currently seen form validation done within the tools I use so that y'all all know what the patterns kind of are in this space:

Redux form example via: https://redux-form.com/6.6.3/examples/fieldlevelvalidation/

const required = value => value ? undefined : 'Required'
const maxLength = max => value =>
  value && value.length > max ? `Must be ${max} characters or less` : undefined
const maxLength15 = maxLength(15)
const number = value => value && isNaN(Number(value)) ? 'Must be a number' : undefined
const minValue = min => value =>
  value && value < min ? `Must be at least ${min}` : undefined
const minValue18 = minValue(18)
const email = value =>
  value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) ?
  'Invalid email address' : undefined
const tooOld = value =>
  value && value > 65 ? 'You might be too old for this' : undefined
const aol = value =>
  value && /.+@aol\.com/.test(value) ?
  'Really? You still use AOL for your email?' : undefined

🤮

Formstate via: https://formstate.github.io/#/?id=quick-example

username = new FieldState('').validators((val) => !val && 'username required');

@ryardley
Copy link
Owner Author

ryardley commented Aug 16, 2019

Yep the other thing to note for reference is we should also support default error messages based on configured locale.

const someStringOrFalsy = p`{
  name: string & 
    { length: > 7 },
  age: (Number & > 18)
}`.validate({name:"foo", age:23});// "Property length must be greater then 7"

@ryardley ryardley added enhancement New feature or request priority and removed priority labels Sep 25, 2019
@ryardley ryardley changed the title Error syntax Validation and error syntax Nov 30, 2019
@ryardley
Copy link
Owner Author

ryardley commented Nov 30, 2019

What has been annoying me about most of the above alternatives is that they don't scan well without syntax highlighting. I think the double colon before a string works the best as it gets out of the way and our eyes are used to bookending a string. It is also reasonably easy to parse with a regex (aside from escaped quotes).

const isVerified = p`{
  name: string :: "Name must be a string" & 
    { length: > 7 } :: "Length must be greater than 7",
  age: (number & > 18) :: "Age must be numeric and over 18"
} :: "You are not verified"`.validate;

You can also loose the whitespace if required.

const isVerified = p`{
  name: string ::"Name must be a string" 
    & { length: > 7 } ::"Length must be greater than 7",
  age: (number & > 18) ::"Age must be numeric and over 18"
} ::"You are not verified"`.validate;

Or go nuts with whitespace:

const isVerified = p`{
  name: string               :: "Name must be a string" 
   & { length: > 7 }         :: "Length must be greater than 7",
  age: (number & > 18)       :: "Age must be numeric and over 18"
}                            :: "You are not verified"`.validate;

This was referenced Dec 1, 2019
@ryardley
Copy link
Owner Author

ryardley commented Dec 6, 2019

Happy to say with #104 we have some working validation and from tonight it will be available on the next tag of PDSL.

yarn add pdsl@next @pdsl/babel-plugin-pdsl@next

@ryardley ryardley closed this as completed Dec 6, 2019
@ryardley
Copy link
Owner Author

Just bumping this thread to say that validation syntax has changed and we also have Formik compatability: https://github.com/ryardley/pdsl#validation-with-formik. A new version is available on the next tag v5.2.4+ which will be published in the coming days to the latest tag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request priority
Projects
None yet
Development

No branches or pull requests

3 participants