pimbrouwers / Validus

An extensible F# validation library.
Apache License 2.0
144 stars 10 forks source link

Question: Dependant labels #6

Closed mrboring closed 2 years ago

mrboring commented 3 years ago

Hi

I'm new to F# and have a requirement for validating records in an app I'm writing. Your library was referred to me. One of my requirements is for related fields to both validate (if label A is present then label B should be present and both should validate). Using your Quick Start code I've achieved my goal. However, I was wondering if there is a better way. Here is my code (look for // Added by Me):

#r "nuget: Validus"

open System
open Validus
open Validus.Operators

type PersonDto =
    { FirstName : string
      LastName  : string
      Email     : string
      Age       : int option
      StartDate : DateTime option }

type Name =
    { First : string
      Last  : string }

type Person =
    { Name      : Name
      Email     : string
      Age       : int option
      StartDate : DateTime }

let validatePersonDto (input : PersonDto) : Result<Person, ValidationErrors> =
    // Shared validator for first & last name
    let nameValidator =
        Validators.Default.String.betweenLen 3 64

    // Composing multiple validators to form complex validation rules,
    // overriding default error message (Note: "Validators.String" as
    // opposed to "Validators.Default.String")
    let emailValidator =
        Validators.Default.String.betweenLen 8 512
        <+> Validators.String.pattern "[^@]+@[^\.]+\..+" (sprintf "Please provide a valid %s")

    // Defining a validator for an option value
    let ageValidator =
        Validators.optional (Validators.Default.Int.between 1 100)

    // Defining a validator for an option value that is required
    let dateValidator =
        Validators.Default.required (Validators.Default.DateTime.greaterThan DateTime.Now)

    // Added by Me <<<<<
    let firstNameAndAgeValidator =
        let rule (i : PersonDto) =
            let isValidAge =
                match i.Age with
                | Some age ->
                    if age >= 1 && age <= 100 then
                        true
                    else
                        false
                | None -> false

            let isValidName =
                if i.FirstName.Length >= 3 && i.FirstName.Length <= 64 then
                    true
                else
                    false

            isValidAge && isValidName

        let message =
            sprintf "%s should both be present and valid."

        Validator.create message rule

    validate {
        let! first = nameValidator "First name" input.FirstName
        and! last = nameValidator "Last name" input.LastName
        and! email = emailValidator "Email address" input.Email
        and! age = ageValidator "Age" input.Age
        and! startDate = dateValidator "Start Date" input.StartDate
        and! _ = firstNameAndAgeValidator "FirstName and Age" input // Added by Me <<<<<

        // Construct Person if all validators return Success
        return {
            Name = { First = first; Last = last }
            Email = email
            Age = age
            StartDate = startDate }
    }

//
// Execution
let input : PersonDto =
    { FirstName = "Jo"
      LastName  = "Doe"
      Email     = "john.doe@url.com"
      Age       = Some 163
      StartDate = Some (new DateTime(2958, 1, 1)) }

let res = validatePersonDto input

match res with
| Ok p -> printfn "%A" p
| Error e ->
    e
    |> ValidationErrors.toList
    |> Seq.iter (printfn "%s")

A couple of questions:

  1. ...and! _ = firstNameAndAgeValidator "FirstName and Age" input // Added by Me... I don't like the _ = (I did this as I'm only interested in errors). Is there a better way of doing this?

  2. In firstNameAndAgeValidator could I use the inbuilt Validators? If yes, how?

pimbrouwers commented 3 years ago

Hey @mrboring! Thanks for reaching out. I do apologize for the delay. Normally I am much faster to reply, but I've been (still am) after welcoming my second kid into the world. I will help with this, if it is still needed, when I get back to work on Monday.

mrboring commented 3 years ago

Congratulations on the baby!

There is no hurry regarding my questions. I'm a hobbyist programmer learning F#. I've been looking at different methods of validation and was trying Validus. If you get some time, when you're back at work, I'd be grateful for your feedback. Again, this is low priority.

pimbrouwers commented 2 years ago

Did you figure this out? Or still want some feedback?

On Sun., Dec. 5, 2021, 8:42 a.m. mrboring, @.***> wrote:

Closed #6 https://github.com/pimbrouwers/Validus/issues/6.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/pimbrouwers/Validus/issues/6#event-5716790500, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABDB57MIWFMQEK7OTOGDBNDUPNT3BANCNFSM5IPPVNWQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

mrboring commented 2 years ago

Thanks for asking, but no further feedback is needed.

pimbrouwers commented 2 years ago

My pleasure. Happy hacking!

On Sun., Dec. 5, 2021, 11:33 a.m. mrboring, @.***> wrote:

Thanks for asking, but no further feedback is needed.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/pimbrouwers/Validus/issues/6#issuecomment-986261108, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABDB57JTTYA3G545KWOWK63UPOH4NANCNFSM5IPPVNWQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.