RestProvider


Minimal type provider sample

This sample discusses the implementation of the server-side component of the minimal type provider demo that is discussed on the home page. You can also find the complete source code for the server in the GitHub repository. We're going to use lightweight F# library Suave to build the web server and a helper function toJson that turns an F# record into JSON (also in the full source code).

REST provider protocol as F# records

The first thing to do is to define records that model the relevant parts of the REST provider protocol. These directly map to JSON values, so writing the code that returns the JSON data will be easy. The request to / and /city returns information about provided type, which is just an array of members - we're going to represent this as Member[] in F#. The type of a member can be either nested provided type, or a primitive type. Those are repesented by TypeNested and TypePrimitive:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
/// Type of a member that is primitive type (int)
type TypePrimitive =
  { kind:string // This is always "primitive"
    ``type``:obj
    endpoint:string }

/// Type of a member that is another provided type
type TypeNested =
  { kind:string // This is always "nested"
    endpoint:string }

/// Represents a single member of a provided type
type Member =
  { name:string
    returns:obj // TypePrimitive or TypeNested
    trace:string[] }

Implementing the service

The service handles requests to / (the root type returning different cities), /city (the "city" type with members for the various indicators) and /data (to actually load the data based on a trace).

Using Suave, this can be easily expressed using the choose combinator that takes a list of WebParts and runs the first one that can handle a request. We use path "/city" to specify the required path. The actual code to handle the request is hidden in (...) and we look at it next:

1: 
2: 
3: 
4: 
5: 
let app =
  choose [
    path "/" >=> (...)
    path "/city" >=> (...)
    path "/data" >=> (...) ]

Providing root and city types

We are writing very minimal sample here, so the types representing the root (with city names as members) and city (with indicators as members) do not load data from anywhere and instead return just a constant F# value. Those directly correspond to the examples on the homepage, so we won't repeat everything here. The main point is that you could easily generate the records and arrays from any data source that you wish:

1: 
2: 
3: 
4: 
5: 
6: 
path "/" >=>
  ( [| { name="London"; trace=[|"London"|]
         returns={kind="nested"; endpoint="/city"} }
       { name="New York"; trace=[|"NYC"|]
         returns={kind="nested"; endpoint="/city"} } |]
    |> toJson |> Successful.OK )

For the root type, we return members named London and New York. They are both of a nested type provided by the /city endpoint (below) and they append an array of values that identify which city we are accessing to the trace.

The implementation creates an array of Member types and passes it to the helper toJson function, which serializes the data into JSON and then to Successful.OK, which creates a WebPart that returns the resulting JSON as HTTP 200 response. The handler for /city is similar:

1: 
2: 
3: 
4: 
5: 
6: 
path "/city" >=>
  ( [| { name="Population"; trace=[|"Population"|]
         returns={kind="primitive"; ``type``="int"; endpoint="/data"} }
       { name="Settled"; trace=[|"Settled"|]
         returns={kind="primitive"; ``type``="int"; endpoint="/data"} } |]
    |> toJson |> Successful.OK )

The type of the returns field is obj, so we can set it to a value of the TypeNested record (as we did in the first case) or the TypePrimitive record (as we did here). For primitive types, we set type="int", which tells the provider what should be the return type of the member (the example here is very simple, but you can use more complex primitive types - see the protocol page for more info).

Returning data at runtime

When the provided code cities.London.Population is evaluated, the provider sends a POST request to the /data endpoint. The body of the request includes trace values generated by the individual members that were called along the way. In the above case, the request will include body London&Population. The handler for the /data endpoint turns the body into a set and uses pattern matching with a helper active pattern Contains to detect which of the values should be returned:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
path "/data" >=> request (fun r ->
  match set (Utils.ASCII.toString(r.rawForm).Split('&')) with
  | Contains "London" & Contains "Population" -> Successful.OK "538689"
  | Contains "NYC" & Contains "Population" -> Successful.OK "550405"
  | Contains "London" & Contains "Settled" -> Successful.OK "-43"
  | Contains "NYC" & Contains "Settled" -> Successful.OK "1624"
  | _ -> RequestErrors.BAD_REQUEST "Wrong trace" )

We are using the request function from Suave to get access to the r.rawForm value, which contains the body of the request. The Contains active pattern that lets us nicely decide which value to return is defined as follows:

1: 
let (|Contains|_|) k s = if Set.contains k s then Some() else None

Summary

In this tutorial, we looked at the simplest server that exposes data for the REST provider. You can find the complete source code for the server in the GitHub repository and there are also couple of other examples you can explore. For more information abotu the protocol, see a dedicated REST provider protocol page.

namespace System
namespace System.IO
namespace System.Collections
namespace System.Collections.Generic
namespace Newtonsoft
namespace Newtonsoft.Json
namespace Suave
module Filters

from Suave
module Operators

from Suave
val serializer : JsonSerializer

Full name: Minimal.serializer
Multiple items
type JsonSerializer =
  new : unit -> JsonSerializer
  member Binder : SerializationBinder with get, set
  member CheckAdditionalContent : bool with get, set
  member ConstructorHandling : ConstructorHandling with get, set
  member Context : StreamingContext with get, set
  member ContractResolver : IContractResolver with get, set
  member Converters : JsonConverterCollection
  member Culture : CultureInfo with get, set
  member DateFormatHandling : DateFormatHandling with get, set
  member DateFormatString : string with get, set
  ...

Full name: Newtonsoft.Json.JsonSerializer

--------------------
JsonSerializer() : unit
JsonSerializer.Create() : JsonSerializer
JsonSerializer.Create(settings: JsonSerializerSettings) : JsonSerializer
val toJson : value:'a -> string

Full name: Minimal.toJson
val value : 'a
val sb : Text.StringBuilder
namespace System.Text
Multiple items
type StringBuilder =
  new : unit -> StringBuilder + 5 overloads
  member Append : value:string -> StringBuilder + 18 overloads
  member AppendFormat : format:string * arg0:obj -> StringBuilder + 4 overloads
  member AppendLine : unit -> StringBuilder + 1 overload
  member Capacity : int with get, set
  member Chars : int -> char with get, set
  member Clear : unit -> StringBuilder
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EnsureCapacity : capacity:int -> int
  member Equals : sb:StringBuilder -> bool
  ...

Full name: System.Text.StringBuilder

--------------------
Text.StringBuilder() : unit
Text.StringBuilder(capacity: int) : unit
Text.StringBuilder(value: string) : unit
Text.StringBuilder(value: string, capacity: int) : unit
Text.StringBuilder(capacity: int, maxCapacity: int) : unit
Text.StringBuilder(value: string, startIndex: int, length: int, capacity: int) : unit
val tw : StringWriter
Multiple items
type StringWriter =
  inherit TextWriter
  new : unit -> StringWriter + 3 overloads
  member Close : unit -> unit
  member Encoding : Encoding
  member GetStringBuilder : unit -> StringBuilder
  member ToString : unit -> string
  member Write : value:char -> unit + 2 overloads

Full name: System.IO.StringWriter

--------------------
StringWriter() : unit
StringWriter(formatProvider: IFormatProvider) : unit
StringWriter(sb: Text.StringBuilder) : unit
StringWriter(sb: Text.StringBuilder, formatProvider: IFormatProvider) : unit
JsonSerializer.Serialize(jsonWriter: JsonWriter, value: obj) : unit
JsonSerializer.Serialize(textWriter: TextWriter, value: obj) : unit
JsonSerializer.Serialize(textWriter: TextWriter, value: obj, objectType: Type) : unit
JsonSerializer.Serialize(jsonWriter: JsonWriter, value: obj, objectType: Type) : unit
Text.StringBuilder.ToString() : string
Text.StringBuilder.ToString(startIndex: int, length: int) : string
val k : 'a (requires comparison)
val s : Set<'a> (requires comparison)
Multiple items
module Set

from Microsoft.FSharp.Collections

--------------------
type Set<'T (requires comparison)> =
  interface IComparable
  interface IEnumerable
  interface IEnumerable<'T>
  interface ICollection<'T>
  new : elements:seq<'T> -> Set<'T>
  member Add : value:'T -> Set<'T>
  member Contains : value:'T -> bool
  override Equals : obj -> bool
  member IsProperSubsetOf : otherSet:Set<'T> -> bool
  member IsProperSupersetOf : otherSet:Set<'T> -> bool
  ...

Full name: Microsoft.FSharp.Collections.Set<_>

--------------------
new : elements:seq<'T> -> Set<'T>
val contains : element:'T -> set:Set<'T> -> bool (requires comparison)

Full name: Microsoft.FSharp.Collections.Set.contains
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
type TypePrimitive =
  {kind: string;
   type: obj;
   endpoint: string;}

Full name: Minimal.TypePrimitive


 Type of a member that is primitive type (int)
TypePrimitive.kind: string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
type obj = Object

Full name: Microsoft.FSharp.Core.obj
TypePrimitive.endpoint: string
type TypeNested =
  {kind: string;
   endpoint: string;}

Full name: Minimal.TypeNested


 Type of a member that is another provided type
TypeNested.kind: string
TypeNested.endpoint: string
type Member =
  {name: string;
   returns: obj;
   trace: string [];}

Full name: Minimal.Member


 Represents a single member of a provided type
Member.name: string
Member.returns: obj
Member.trace: string []
val app : obj

Full name: minimal.app
(...)
val path : pathAfterDomain:string -> WebPart

Full name: Suave.Filters.path
module Successful

from Suave
val OK : body:string -> WebPart

Full name: Suave.Successful.OK
val request : apply:(HttpRequest -> HttpContext -> 'a) -> context:HttpContext -> 'a

Full name: Suave.Http.request
val r : HttpRequest
val set : elements:seq<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
namespace Suave.Utils
module ASCII

from Suave.Utils
val toString : b:byte [] -> string

Full name: Suave.Utils.ASCII.toString
HttpRequest.rawForm: byte []
active recognizer Contains: 'a -> Set<'a> -> unit option

Full name: Minimal.( |Contains|_| )
module RequestErrors

from Suave
val BAD_REQUEST : body:string -> WebPart

Full name: Suave.RequestErrors.BAD_REQUEST
Fork me on GitHub