namespace System
namespace Suave
namespace System.Net
module Filters
from Suave
module Operators
from Suave
module Successful
from Suave
val config : obj
Full name: index.config
Multiple items
type IPAddress =
new : newAddress:int64 -> IPAddress + 2 overloads
member Address : int64 with get, set
member AddressFamily : AddressFamily
member Equals : comparand:obj -> bool
member GetAddressBytes : unit -> byte[]
member GetHashCode : unit -> int
member IsIPv6LinkLocal : bool
member IsIPv6Multicast : bool
member IsIPv6SiteLocal : bool
member IsIPv6Teredo : bool
...
Full name: System.Net.IPAddress
--------------------
IPAddress(newAddress: int64) : unit
IPAddress(address: byte []) : unit
IPAddress(address: byte [], scopeid: int64) : unit
field IPAddress.Loopback
module Operators
from Microsoft.FSharp.Core
val app : obj
Full name: index.app
val app : (HttpContext -> Async<HttpContext option>)
Full name: Index.app
val GET : WebPart
Full name: Suave.Filters.GET
val choose : options:WebPart<'a> list -> WebPart<'a>
Full name: Suave.WebPart.choose
val path : pathAfterDomain:string -> WebPart
Full name: Suave.Filters.path
val OK : body:string -> WebPart
Full name: Suave.Successful.OK
val pathScan : pf:PrintfFormat<'a,'b,'c,'d,'t> -> h:('t -> WebPart) -> WebPart
Full name: Suave.Filters.pathScan
val n1 : int
val n2 : int
val sprintf : format:Printf.StringFormat<'T> -> 'T
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val analyse : pf:PrintfFormat<'a,'b,'c,'d,'t> -> string
Full name: Index.analyse
val pf : PrintfFormat<'a,'b,'c,'d,'t>
Multiple items
type PrintfFormat<'Printer,'State,'Residue,'Result> =
new : value:string -> PrintfFormat<'Printer,'State,'Residue,'Result>
member Value : string
Full name: Microsoft.FSharp.Core.PrintfFormat<_,_,_,_>
--------------------
type PrintfFormat<'Printer,'State,'Residue,'Result,'Tuple> =
inherit PrintfFormat<'Printer,'State,'Residue,'Result>
new : value:string -> PrintfFormat<'Printer,'State,'Residue,'Result,'Tuple>
Full name: Microsoft.FSharp.Core.PrintfFormat<_,_,_,_,_>
--------------------
new : value:string -> PrintfFormat<'Printer,'State,'Residue,'Result>
--------------------
new : value:string -> PrintfFormat<'Printer,'State,'Residue,'Result,'Tuple>
val typeof<'T> : Type
Full name: Microsoft.FSharp.Core.Operators.typeof
val ty : Type
property Type.IsPrimitive: bool
property Reflection.MemberInfo.Name: string
val names : string list
Type.GetGenericArguments() : Type []
module Seq
from Microsoft.FSharp.Collections
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>
Full name: Microsoft.FSharp.Collections.Seq.map
val pt : Type
val toList : source:seq<'T> -> 'T list
Full name: Microsoft.FSharp.Collections.Seq.toList
val log1 : string
Full name: Index.log1
val log2 : string
Full name: Index.log2
val pathTemplate : pf:PrintfFormat<'a,'b,'c,'d,'t> -> callback:('t -> unit) -> 'e
Full name: Index.pathTemplate
val callback : ('t -> unit)
type unit = Unit
Full name: Microsoft.FSharp.Core.unit
val failwith : message:string -> 'T
Full name: Microsoft.FSharp.Core.Operators.failwith
Multiple items
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
val now1 : x:HttpContext -> Async<HttpContext option>
Full name: Index.now1
Multiple items
module WebPart
from Suave
--------------------
type WebPart = WebPart<HttpContext>
Full name: Suave.Http.WebPart
--------------------
type WebPart<'a> = 'a -> Async<'a option>
Full name: Suave.WebPart.WebPart<_>
val x : HttpContext
Multiple items
module HttpContext
from Suave.Http
--------------------
type HttpContext =
{request: HttpRequest;
runtime: HttpRuntime;
connection: Connection;
userState: Map<string,obj>;
response: HttpResult;}
member clientIp : trustProxy:bool -> sources:string list -> IPAddress
member clientPort : trustProxy:bool -> sources:string list -> Port
member clientProto : trustProxy:bool -> sources:string list -> string
member clientIpTrustProxy : IPAddress
member clientPortTrustProxy : Port
member clientProtoTrustProxy : string
member isLocal : bool
static member clientIp_ : Property<HttpContext,IPAddress>
static member clientPort_ : Property<HttpContext,Port>
static member clientProto_ : Property<HttpContext,string>
static member connection_ : Property<HttpContext,Connection>
static member isLocal_ : Property<HttpContext,bool>
static member request_ : Property<HttpContext,HttpRequest>
static member response_ : Property<HttpContext,HttpResult>
static member runtime_ : Property<HttpContext,HttpRuntime>
static member userState_ : Property<HttpContext,Map<string,obj>>
Full name: Suave.Http.HttpContext
val async : AsyncBuilder
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
Multiple items
type DateTime =
struct
new : ticks:int64 -> DateTime + 10 overloads
member Add : value:TimeSpan -> DateTime
member AddDays : value:float -> DateTime
member AddHours : value:float -> DateTime
member AddMilliseconds : value:float -> DateTime
member AddMinutes : value:float -> DateTime
member AddMonths : months:int -> DateTime
member AddSeconds : value:float -> DateTime
member AddTicks : value:int64 -> DateTime
member AddYears : value:int -> DateTime
...
end
Full name: System.DateTime
--------------------
DateTime()
(+0 other overloads)
DateTime(ticks: int64) : unit
(+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : unit
(+0 other overloads)
property DateTime.Now: DateTime
DateTime.ToString() : string
DateTime.ToString(provider: IFormatProvider) : string
DateTime.ToString(format: string) : string
DateTime.ToString(format: string, provider: IFormatProvider) : string
val main : argv:'a -> int
Full name: Index.main
val argv : 'a
val time1 : (HttpContext -> Async<HttpContext option>)
namespace Suave.Swagger
module Rest
from Suave.Swagger
module FunnyDsl
from Suave.Swagger
Multiple items
module Swagger
from Suave.Swagger
--------------------
namespace Suave.Swagger
val now2 : x:HttpContext -> Async<HttpContext option>
Full name: Index.now2
val MODEL : m:'t -> x:HttpContext -> Async<HttpContext option>
Full name: Suave.Swagger.Rest.MODEL
val api1 : DocBuildState
Full name: Index.api1
val swagger : SwaggerBuilder
Full name: Suave.Swagger.Swagger.swagger
val route : DocBuildState
val getting : d:DocBuildState -> DocBuildState
Full name: Suave.Swagger.FunnyDsl.getting
val simpleUrl : p:string -> DocBuildState
Full name: Suave.Swagger.FunnyDsl.simpleUrl
val thenReturns : w:(HttpContext -> Async<HttpContext option>) -> d:DocBuildState -> DocBuildState
Full name: Suave.Swagger.FunnyDsl.thenReturns
val description : 'a -> route:DocBuildState -> f:(string -> string) -> x:string -> DocBuildState
Full name: Suave.Swagger.FunnyDsl.description
val Of : x:'a -> 'a
Full name: Suave.Swagger.FunnyDsl.Of
val is : x:'a -> 'a
Full name: Suave.Swagger.FunnyDsl.is
Multiple items
type CLIMutableAttribute =
inherit Attribute
new : unit -> CLIMutableAttribute
Full name: Microsoft.FSharp.Core.CLIMutableAttribute
--------------------
new : unit -> CLIMutableAttribute
type SmsModel =
{Id: int;
Address: string;
Body: string;
Date: DateTime;}
Full name: Index.SmsModel
SmsModel.Id: int
SmsModel.Address: string
Multiple items
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = String
Full name: Microsoft.FSharp.Core.string
SmsModel.Body: string
SmsModel.Date: DateTime
type SendSmsModel =
{Destination: string;
Body: string;}
Full name: Index.SendSmsModel
SendSmsModel.Destination: string
SendSmsModel.Body: string
type SendSmsResult =
{Success: bool;
Sms: SendSmsModel;}
Full name: Index.SendSmsResult
SendSmsResult.Success: bool
type bool = Boolean
Full name: Microsoft.FSharp.Core.bool
SendSmsResult.Sms: SendSmsModel
val readAllRows : context:'a -> uri:'b -> 'c []
Full name: index.readAllRows
val context : 'a
val uri : 'b
val cr : obj
val c : System.IDisposable
val names : obj
val ignore : value:'T -> unit
Full name: Microsoft.FSharp.Core.Operators.ignore
Multiple items
val seq : sequence:seq<'T> -> seq<'T>
Full name: Microsoft.FSharp.Core.Operators.seq
--------------------
type seq<'T> = System.Collections.Generic.IEnumerable<'T>
Full name: Microsoft.FSharp.Collections.seq<_>
val row : obj
val dict : keyValuePairs:seq<'Key * 'Value> -> System.Collections.Generic.IDictionary<'Key,'Value> (requires equality)
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.dict
val toArray : source:seq<'T> -> 'T []
Full name: Microsoft.FSharp.Collections.Seq.toArray
val getContacts : context:'a -> 'b []
Full name: index.getContacts
val uri : obj
val listSentSms : context:'a -> skip:'b option -> limit:'c -> 'd
Full name: index.listSentSms
val skip : 'b option
val limit : 'c
union case Option.Some: Value: 'T -> Option<'T>
val n : 'b
union case Option.None: Option<'T>
val listInboxSms : context:'a -> skip:'b option -> limit:'c -> 'd
Full name: index.listInboxSms
val api : obj
Full name: index.api
val typeof<'T> : System.Type
Full name: Microsoft.FSharp.Core.Operators.typeof
What is Suave?
- Suave is a FSharp lightweight web server principally used to develop REST APIs
How to start with Suave ?
Install Suave NuGet
With Paket
1:
|
paket add nuget Suave -i
|
Or with NuGet
Write a tiny peace of code
1:
2:
3:
4:
5:
6:
7:
8:
|
open Suave
open System.Net
let config =
{ defaultConfig
with bindings =
[ HttpBinding.mk HTTP IPAddress.Loopback 8000us ] }
// startWebServer config (Successful.OK "Hello World!")
|
The URL http://localhost:8000/ should return "Hello World!" as content
Enjoy :)
Routing requests
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
|
open Suave.Filters
open Suave.Operators
open Suave.Successful
let app =
choose // combines 'post' and 'get' WebPart
[ GET >=> choose // combines both WebParts
[ path "/hello" >=> OK "Hello GET"
path "/goodbye" >=> OK "Good bye GET" ]
POST >=> choose // combines both WebParts
[ path "/hello" >=> OK "Hello POST"
path "/goodbye" >=> OK "Good bye POST" ] ]
// passing the final WebPart to the HTTP server
// startWebServer config app
|
Trying ...
phew! it works :)
The pathScan function
1:
2:
3:
4:
5:
6:
7:
|
let app =
GET >=> choose // combines both WebParts
[ path "/hello" >=> OK "Hello GET"
pathScan "/add/%d/%d" (
fun (n1,n2) ->
OK <| sprintf "%d" (n1 + n2)
) ]
|
The pathScan function uses the url template to create a strongly typed function.
So n1 and n2 are integers :)
Nothing is magic
The power of PrintfFormat
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
|
let analyse (pf : PrintfFormat<_,_,_,_,'t>) =
match typeof<'t> with
| ty when ty.IsPrimitive -> sprintf "Primitive type of %s " ty.Name
| ty ->
let names =
ty.GetGenericArguments()
|> Seq.map (fun pt -> pt.Name)
|> Seq.toList
sprintf "Generic of %A" names
let log1 = analyse "/stringify/%d"
let log2 = analyse "/add/%d/%d/%s"
|
log1 and log2 are:
"Primitive type of Int32 "
|
"Generic of ["Int32"; "Int32"; "String"]"
|
So we can write something like:
1:
2:
3:
4:
|
let pathTemplate (pf : PrintfFormat<_,_,_,_,'t>) (callback:'t -> unit)=
failwith "not implemented"
pathTemplate "/add/%d/%d" (fun (n1:int,n2:int) -> failwith "not implemented")
|
How to have named and typed route params ?
The pathScan function doesn't name parameters.
So they can be confused when they are many.
Benefits
- Strongly typed routes
- Better code organization ( routes declared in types )
- Autocompletion when typing routes handlers ( fun )
What is it ?
- Representation of your RESTful API
- Interactive generated documentation
- client SDK generation
- discoverability (like WSDL)
Problem:
- Routes are pure fonctions and NOT discoverables
Solution :
- Wrapping functions to keep informations
- Computation expression
How to swagger your Sauve project ?
Go to
Tiny example
We want to create an API returning current time.
In a " classic " mode
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
|
let now1 : WebPart =
fun (x : HttpContext) ->
async {
return! OK (DateTime.Now.ToString()) x
}
//[<EntryPoint>]
let main argv =
let time1 = GET >=> path "/time1" >=> now1
//startWebServer defaultConfig time1
0 // return an integer exit code
|
With the verbose DSL
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
|
open Suave.Swagger
open Rest
open FunnyDsl
open Swagger
let now2 : WebPart =
fun (x : HttpContext) ->
async {
return! MODEL DateTime.Now x
}
let api1 =
swagger {
for route in getting (simpleUrl "/time" |> thenReturns now2) do
yield description Of route is "What time is it ?"
}
//startWebServer defaultConfig api1.App
|
Concrete and unusual using case
We want to create an API sending and receiving SMS for a personal and domotic projects.
The robot must send SMS using old smartphones.
Xamarin
Suave exists in the Xamarin components store
Project structure
- The UI is in a PCL project using Xamarin.Forms
- The Android project is using the PCL to bind UI elements with their behaviors
- Swagger DSL source code is temporary copied in the Android project.
Focus on the API code
Models
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
|
[<CLIMutable>]
type SmsModel = {
Id:int
Address:string
Body:string
Date:DateTime
}
and [<CLIMutable>] SendSmsModel = {
Destination:string
Body:string
}
and [<CLIMutable>] SendSmsResult = {
Success:bool
Sms:SendSmsModel
}
|
To know about Android
The most of phone's data we need are stored in system's SQlite databases.
So this snippet will help us to get them quickly.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
|
let readAllRows (context:Context) uri =
let cr = context.ContentResolver
use c = cr.Query(Android.Net.Uri.Parse uri, null, null, null, null)
let names = c.GetColumnNames()
c.MoveToFirst() |> ignore
seq {
for _ in 0 .. c.Count-1 do
let row = names
|> Seq.map (
fun name ->
name, c.GetString(c.GetColumnIndex name))
|> dict
yield row
c.MoveToNext() |> ignore
c.Close()
} |> Seq.toArray
|
Then most useful functions will be
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
|
let getContacts context =
let uri = Android.Provider.ContactsContract
.CommonDataKinds.Phone.ContentUri.ToString()
readAllRows context uri
let listSentSms context skip limit =
match skip with
| Some n -> listSms context "content://sms/sent" n limit
| None -> listSms context "content://sms/sent" 0 limit
let listInboxSms context skip limit =
match skip with
| Some n -> listSms context "content://sms/inbox" n limit
| None -> listSms context "content://sms/inbox" 0 limit
// etc ...
|
Now writing API
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
|
let api =
swagger {
for route in getting (simpleUrl "/contacts"
|> thenReturns contacts) do
yield description Of route is "contacts"
for route in getting (simpleUrl "/sms/sent"
|> thenReturns (sentSmsWp context)) do
// this is the raw description of the REST method
yield description Of route is "Get last 20 sent SMS"
// we can provide a type per response code
yield route
|> addResponse 200 "The found messages"
(Some typeof<SmsModel>)
}
|