Skip to content

Instantly share code, notes, and snippets.

@sheridanchris
Last active January 13, 2023 23:40
Show Gist options
  • Save sheridanchris/6d60f50e9782106ac999dee2c73031b4 to your computer and use it in GitHub Desktop.
Save sheridanchris/6d60f50e9782106ac999dee2c73031b4 to your computer and use it in GitHub Desktop.
Validation problem details w/ Falco & Validus
type ProblemDetails = {
Type: string
Title: string
Status: int
Detail: string
Instance: string
Errors: Map<string, string list>
}
[<RequireQualifiedAccess>]
module ProblemDetails =
let createValidationProblemDetails instance errors = {
Type = "https://httpstatuses.com/400"
Title = "One or more validation errors occured."
Status = 400
Detail = "Please refer to the errors property for additional details."
Instance = instance
Errors = errors
}
[<RequireQualifiedAccess>]
module Request =
let mapValidateJson
(validator: 'a -> ValidationResult<'b>)
(onSuccess: 'b -> HttpHandler)
(onValidationErrors: ValidationErrors -> HttpHandler)
=
let handleOk (record: 'a) : HttpHandler =
match validator record with
| Ok result -> onSuccess result
| Error validationErrors -> onValidationErrors validationErrors
Request.mapJson handleOk
[<RequireQualifiedAccess>]
module Response =
let validationProblemDetails (instance: string) (errors: ValidationErrors) : HttpHandler =
errors
|> ValidationErrors.toMap
|> ProblemDetails.createValidationProblemDetails instance
|> fun response -> Response.withStatusCode 400 >> Response.ofJson response
type BudgetName = private BudgetName of string
type PositiveDecimal = private PositiveDecimal of decimal
[<RequireQualifiedAccess>]
module BudgetName =
let value (BudgetName name) = name
let create name =
let validator = Check.String.notEmpty <+> Check.String.lessThanLen 150
validate {
let! name = validator "Budget name" name
return BudgetName name
}
[<RequireQualifiedAccess>]
module PositiveDecimal =
let value (PositiveDecimal decimal) = decimal
let create value = validate {
let! name = Check.Decimal.greaterThanOrEqualTo 0m "amount" value
return PositiveDecimal name
}
module CreateBudget =
type CreateBudgetRequest = { Name: string; MonthlyIncome: decimal }
type ValidatedCreateBudgetRequest = {
Name: BudgetName
MonthlyIncome: PositiveDecimal
}
let validateRequest (createBudgetRequest: CreateBudgetRequest) = validate {
let! budgetName = BudgetName.create createBudgetRequest.Name
let! monthlyIncome = PositiveDecimal.create createBudgetRequest.MonthlyIncome
return {
Name = budgetName
MonthlyIncome = monthlyIncome
}
}
let handler (saveBudget: Provider.SaveBudget) : HttpHandler =
let createBudget (request: ValidatedCreateBudgetRequest) : HttpHandler =
fun ctx -> task {
// ...
return ()
}
let handleValidationErrors = Response.validationProblemDetails "/budget"
Request.mapValidateJson validateRequest createBudget handleValidationErrors
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment