Saon is an F# library to parse and validate json payloads and query strings. It builds on top of the System.Text.Json
and Microsoft.AspNetCore.Http.Features
packages.
WARNING: despite the version number, Saon is still in the very early stages. I'm still experimenting with the
API since I'm not satisfied how the json converters and validation functions are composed together.
Saon is ready to download from Nuget.
At the moment, there are four package you can download:
-
Saon
: this is an umbrella package that includes all the other packages. If you're using Saon in an Asp.Net Core
application, you should use this package
Saon.Json
: this package contains the functions to parse json data
Saon.Query
: this package contains the functions to parse query strings
Saon.Shared
: this is the core package that defines the base types and functions
Saon can be easily integrated with Giraffe to provide json and query strings parsing and validation. You can find
more information in the tutorial.
1:
2:
3:
4:
5:
|
let webApp =
choose [
POST >=> route "/" >=> Helper.bindJson Dto.parseContactDetails Handler.createContact
GET >=> route "/" >=> Helper.bindQuery Dto.parseLookup Handler.lookupContact
]
|
One of the goals of this library is to enable developers to parse idiomatic json payloads and convert them to
strictly typed F# objects.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
|
type Email = Email of string
type ContactDetails =
{ Name : string
Email : Email }
let parseEmail =
Validate.isNotEmptyOrWhitespace
/> Validate.isEmail
/> Convert.withFunction Email
let parseContactDetails = jsonObjectParser {
let! name = Json.property "name" (Json.string /> Validate.isNotEmptyOrWhitespace)
let! email = Json.property "email" (Json.string /> parseEmail)
return { Name = name; Email = email }
}
|
You can find more information about the json module in the tutorial.
Saon includes a module to parse query strings represented by the IQueryStringCollection
interface.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
|
type LookupType = Name | Email
let parseLookupType paramName = function
| "name" -> ParserResult.success Name
| "email" -> ParserResult.success Email
| _ -> ParserResult.validationFail "type" paramName "must be one of 'name', 'email'"
type Lookup =
| Name of string
| Email of string
let parseLookup = queryParser {
let! lookupType = Query.parameter "type" (Query.string /> parseLookupType)
match lookupType with
| LookupType.LookupName ->
let! value = Query.parameter "value" Query.string
return LookupName value
| LookupType.LookupEmail ->
let! value = Query.parameter "value" (Query.string /> Validate.isEmail)
return Email value |> LookupEmail
}
|
You can find more information about the query module in the tutorial.
Saon includes a range of validation and conversion functions. You can use them as building block to build your
very own parsers! You can read more in the tutorial.
1:
2:
3:
|
let parsePositiveAmount = Convert.stringToDecimal /> Validate.isGreaterThan 0m
let parseSize = parsePositiveAmount /> Convert.withFunction Size
let parsePrice = parsePositiveAmount /> Convert.withFunction Price
|
This library is published under the Apache-2.0 license. You can find a copy of the
license in the repository.
The project is hosted on GitHub, where you can report issues
and open pull requests.
namespace Saon
namespace Saon.Json
module Operators
from Saon
namespace Saon.Query
namespace System
namespace System.Text
val webApp : obj
type Handler<'T> =
delegate of obj * 'T -> unit
Multiple items
union case Email.Email: string -> Email
--------------------
type Email = | Email of string
Multiple items
val string : value:'T -> string
--------------------
type string = System.String
type ContactDetails =
{Name: string;
Email: Email;}
ContactDetails.Name: string
Multiple items
ContactDetails.Email: Email
--------------------
type Email = | Email of string
val parseEmail : (string -> string -> ParserResult<Email>)
module Validate
from Saon
val isNotEmptyOrWhitespace : propName:string -> value:string -> ParserResult<string>
val isEmail : propName:string -> value:string -> ParserResult<string>
module Convert
from Saon
val withFunction : func:('T -> 'R) -> string -> value:'T -> ParserResult<'R>
val parseContactDetails : obj
Multiple items
module Json
from Saon.Json
--------------------
namespace Saon.Json
type LookupType =
| Name
| Email
union case LookupType.Name: LookupType
Multiple items
union case LookupType.Email: LookupType
--------------------
type Email = | Email of string
val parseLookupType : paramName:string -> _arg1:string -> ParserResult<LookupType>
val paramName : string
Multiple items
module ParserResult
from Saon
--------------------
type ParserResult<'T> =
| ParsingFailed of field: string option * message: string
| ValidationFailed of ValidationFailedMap
| Success of 'T
val success : value:'a -> ParserResult<'a>
val validationFail : typ:string -> propName:string -> msg:string -> ParserResult<'a>
type Lookup =
| Name of string
| Email of string
union case Lookup.Name: string -> Lookup
Multiple items
union case Lookup.Email: string -> Lookup
--------------------
type Email = | Email of string
val parseLookup : (AspNetCore.Http.IQueryCollection -> ParserResult<obj>)
val queryParser : ParserBuilder<AspNetCore.Http.IQueryCollection>
val lookupType : LookupType
Multiple items
module Query
from Saon.Query
--------------------
namespace Saon.Query
val parameter : paramName:string -> func:Transformer<Extensions.Primitives.StringValues,'T> -> query:AspNetCore.Http.IQueryCollection -> ParserResult<'T> * AspNetCore.Http.IQueryCollection
val string : propName:string -> value:Extensions.Primitives.StringValues -> ParserResult<string>
val parsePositiveAmount : (string -> string -> ParserResult<decimal>)
val stringToDecimal : propName:string -> value:string -> ParserResult<decimal>
val isGreaterThan : minValue:'T -> propName:string -> value:'T -> ParserResult<'T> (requires comparison)
val parseSize : (string -> string -> ParserResult<obj>)
val parsePrice : (string -> string -> ParserResult<obj>)