Have fun with Suave
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

Have fun with Suave and FSharp

Made for fsharpparis

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

1: 
Install-Package Suave

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 :)

screen1

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 ...

screen2

phew! it works :)

Typed routes

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")

Extending Suave

How to have named and typed route params ?

The pathScan function doesn't name parameters.

So they can be confused when they are many.

A solution could be a type provider

https://rflechner.github.io/Suave.RouteTypeProvider/

screen3

Benefits

  • Strongly typed routes
  • Better code organization ( routes declared in types )
  • Autocompletion when typing routes handlers ( fun )

Testing and documenting

Swagger is great !

http://petstore.swagger.io/#/pet

What is it ?

  • Representation of your RESTful API
  • Interactive generated documentation
  • client SDK generation
  • discoverability (like WSDL)

Swagger for Suave ?

Problem:

  • Routes are pure fonctions and NOT discoverables

Solution :

  • Wrapping functions to keep informations
  • Computation expression

How to swagger your Sauve project ?

Go to

Getting started

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
Result

Go to http://localhost:8083/swagger/v2/ui/index.html

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.

So I created a REST API hosted under Android.

The project is here: https://github.com/rflechner/SmsServer

( Wow really beautiful UI ^^ )

Xamarin

Suave exists in the Xamarin components store

xamarin studio

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>)
  }

DEMO