restf#suave

Nesting Suave WebPart


I've been playing around Suave for the very first time and clearly there's something I don't understand. What I'm trying to achieve is to implement a simple Rest API:

For now for simplicity I focus on GET methods only.

My very basic piece of code is here:

[<AutoOpen>]
module RestFul =    

    let JSON v =     
        let jsonSerializerSettings = new JsonSerializerSettings()
        jsonSerializerSettings.ContractResolver <- new CamelCasePropertyNamesContractResolver()

        JsonConvert.SerializeObject(v, jsonSerializerSettings)
        |> OK 
        >=> Writers.setMimeType "application/json; charset=utf-8"

    let fromJson<'a> json =
        JsonConvert.DeserializeObject(json, typeof<'a>) :?> 'a    

    let getResourceFromReq<'a> (req : HttpRequest) = 
        let getString rawForm = System.Text.Encoding.UTF8.GetString(rawForm)
        req.rawForm |> getString |> fromJson<'a>

    type RestResource<'a> = {
        GetById : int -> 'a option
        GetPricesById : int -> 'a option
    }

    let rest resource =

        let handleResource requestError = function
            | Some r -> r |> JSON
            | _ -> requestError

        let getResourceById = 
            resource.GetById >> handleResource (NOT_FOUND "Resource not found")

        let getPricesById = 
            resource.GetPricesById >> handleResource (NOT_FOUND "Resource not found")

        choose [
            GET >=> pathScan "/instrument/%d" getResourceById
            GET >=> pathScan "/instrument/%d/prices" getPricesById
        ]


module Main =
    [<EntryPoint>]
    let main argv = 

        let webPart = rest {
                GetById = fun i -> Some i // placeholder
                GetPricesById = fun i -> Some i // placeholder, it'll be a list eventually
            }

        startWebServer defaultConfig webPart
        0

When I define the WebPart in this way:

choose [
    GET >=> pathScan "/instrument/%d" getResourceById // Returns the instrument static data
    GET >=> pathScan "/instrument/%d/prices" getPricesById // Returns price list for the instrument
]

Then everything works fine. I'm wondering if there's a way to nest the webparts, e.g. like this:

// My idea about the code - doesn't compile
choose [
    pathScan "/instrument/%d" getResourceById >=> choose [
        GET // Returns the instrument static data
        GET >=> path "/prices" >=> (somehow refer to the previous id and get prices)  // Returns price list for the instrument
    ]
]

Also - as I'm learning about the RestAPIs there might be a gap in my reasoning. I think that nesting prices endpoint in that way makes it simply clear that prices are considered a property of an instrument (feel free to correct me if I'm wrong).


Solution

  • Right, so accessing a previous request is kind of anti suave ;) we want things to be able to happen independently regardless of what just happened. So perhaps a better way of thinking solving this would be simply appending prices to the end of the path?

    choose [
        GET >=> pathScan "/instrument/%d" getResourceById 
        GET >=> pathScan "/instrument/%d/prices" getPricesById
    ]