Suave.Swagger


Scenario 1

We want to create an API returning current time.

Simple solution

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
open System
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Writers
open Suave.Successful

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

Go to http://localhost:8083/time1

Documented solution

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
open Suave.Swagger
open Rest
open FunnyDsl
open Swagger
open Suave
open System

let now : WebPart =
  fun (x : HttpContext) ->
    async {
      // The MODEL helper checks the "Accept" header 
      // and switches between XML and JSON format
      return! MODEL DateTime.Now x
    }

let api1 = 
  swagger {
    // syntax 1
    for route in getting (simpleUrl "/time" |> thenReturns now) do
      yield description Of route is "What time is it?"
  }

[<EntryPoint>]
let main argv = 
  startWebServer defaultConfig api1.App
  0 // return an integer exit codexit code

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

Swagger UI 1


Scenario 2

Creating a service returning calculation results

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
let subtract(a,b) = OK ((a-b).ToString())

let api2 = 
  swagger {
    // For GET
    for route in getting <| urlFormat "/subtract/%d/%d" subtract do
      yield description Of route is "Subtracts two numbers"
    // For POST
    for route in posting <| urlFormat "/subtract/%d/%d" subtract do
      yield description Of route is "Subtracts two numbers"

    // the "add" function can be manually documented
    for route in getOf (pathScan "/add/%d/%d" (fun (a,b) -> OK((a+b).ToString()))) do
      yield description Of route is "Compute a simple addition"
      yield urlTemplate Of route is "/add/{number1}/{number2}"
      yield parameter "number1" Of route  
        (fun p -> { p with Type = (Some typeof<int>); In=Path })
      yield parameter "number2" Of route 
        (fun p -> { p with Type = (Some typeof<int>); In=Path })
}
  • urlFormat will extract parameters from the PrintfFormat, documente them and build the Suave's native "pathScan"
  • we can use directly a pathScan and manually document the pathScan route.

Swagger UI 2

Scenario 3

Returning models

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
[<CLIMutable>] //important for XML serialization
type Pet =
  { Id:int
    Name:string
    Category:PetCategory }
and [<CLIMutable>] PetCategory = 
  { Id:int
    Name:string }

REST functions simulating a database access:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let findPetById id = 
  MODEL
    { 
      Id=id; Name=(sprintf "pet_%d" id)
      Category = { Id=id*100; Name=(sprintf "cat_%d" id) }
    }

let findCategoryById id = 
  MODEL
    { 
      Id=id; Name=(sprintf "cat_%d" id)
    }

let createCategory =
  JsonBody<PetCategory>(fun model -> MODEL { model with Id=(Random().Next()) })

API:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
let api3 = 
  swagger {
      for route in getting <| urlFormat "/pet/%d" findPetById do
        yield description Of route is "Search a pet by id"

        // adding responses informations for HttpCode 200
        yield route |> addResponse 200 "The found pet" (Some typeof<Pet>)
        // the type Pet and referenced PetCategory will be added to swagger definitions

        // supported requests and responses formats
        yield route |> produces "application/json"
        yield route |> produces "application/xml"
        yield route |> consumes "application/json"
        yield route |> consumes "application/xml"

        // can be written shortly
        yield route |> supportsJsonAndXml
      
      for route in getting <| urlFormat "/category/%d" findCategoryById do
        yield description Of route is "Search a category by id"
        yield route 
          |> addResponse 200 "The found category" (Some typeof<PetCategory>)

      for route in posting <| simpleUrl "/category" |> thenReturns createCategory do
        yield description Of route is "Create a category"
        yield route |> addResponse 200 "returns the create model with assigned Id" (Some typeof<PetCategory>)
        yield parameter "category model" Of route (fun p -> { p with Type = (Some typeof<PetCategory>); In=Body })
    }
  
[<EntryPoint>]
let main argv = 
  startWebServer defaultConfig api3.App
  0

Swagger UI 3

namespace System
namespace Suave
module Operators

from Suave
module Filters

from Suave
module Writers

from Suave
module Successful

from Suave
val now1 : x:HttpContext -> Async<HttpContext option>

Full name: Tutorial.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
val OK : body:string -> WebPart

Full name: Suave.Successful.OK
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
Multiple items
type EntryPointAttribute =
  inherit Attribute
  new : unit -> EntryPointAttribute

Full name: Microsoft.FSharp.Core.EntryPointAttribute

--------------------
new : unit -> EntryPointAttribute
val main : argv:string [] -> int

Full name: Tutorial.main
val argv : string []
val time1 : (HttpContext -> Async<HttpContext option>)
val GET : WebPart

Full name: Suave.Filters.GET
val path : pathAfterDomain:string -> WebPart

Full name: Suave.Filters.path
val startWebServer : config:SuaveConfig -> webpart:WebPart -> unit

Full name: Suave.Web.startWebServer
val defaultConfig : SuaveConfig

Full name: Suave.Web.defaultConfig
namespace Suave.Swagger
module Rest

from Suave.Swagger
module FunnyDsl

from Suave.Swagger
Multiple items
module Swagger

from Suave.Swagger

--------------------
namespace Suave.Swagger
val a : int
val b : int
Fork me on GitHub