简体   繁体   中英

How route /store/category/%s/brand/%s in F# Suave?

I can't figure how setup the router for a path like:

/store/category/%s/brand/%s

I have the web store demo and it work for simple URLs, but I don't see how make more flexible configurations.

This is what I have:

type StrPath = PrintfFormat<(string -> string),unit,string,string,string>
// How do this?
type Str2Path = PrintfFormat<(string -> string),unit,string,string,string>

let withParam (key,value) path = sprintf "%s?%s=%s" path key value

module Store =
    //Don't know what put here
    let browseBrand = sprintf "/store/category/%s/brand/%s"
    //This work ok
    let browseCategory : StrPath = "/store/category/%s"
// I need to capture query parameters
let browseBrand cat brand = request (fun r ->
    Views.browse(cat brand))

let webPart = 
    localizeUICulture >>
    choose [
        path Path.Store.overview >=> overview
        pathScan Path.Store.browseBrand browseBrand
        pathScan Path.Store.browseCategory browseCategory

And what about this?

// note the string tuple as return value
type Str2Path = PrintfFormat<(string -> string -> string),unit,string,string,(string * string)>

module Store =
    // your path
    let browseBrand : Str2Path = "/store/category/%s/brand/%s"

// again, note the tuple as input
let browseBrand (cat, brand) = request (Views.browse(cat brand))

let webPart = 
    localizeUICulture >>
    choose [
        pathScan Store.browseBrand browseBrand
        // ... OMMITED
    ]

what I'd bet you are doing with explicitly typing a PrintfFormat<_,_,_,_> so you can use the same format string to build and consume the url path as I do.

Query params don't seem to work in the url for pathScan here are a few things that do work in pathScan

let clientEvent clientId = sprintf "/client/%i/event" clientId
let summary eventId = sprintf "/event/%i/summary" eventId
// you can use units of measure in your format strings
let getEventValidation () : PrintfFormat<int<EventId> -> _,_,_,_,int<EventId>> = "/event/%i/validation"
let checkoutUploaded () : PrintfFormat<int<CheckoutId> -> _ -> _ ,_,_,_,_> = "/checkout/%i/uploaded/username/%s"

let getEventDownloadNoQuery () : PrintfFormat<int<EventId> -> _,_,_,_,_> = "/event/%i/download"
let userName="userName"
let tabletIdent = "tabletIdent"
let getEventDownload () : PrintfFormat<int<EventId> -> _ -> _ -> _,_,_,_,_> = "/event/%i/download?userName=%s&tabletIdent=%s"

// we can use the actual format string as the method/variable name
// is it a good idea? not sure.
// get participant for edit
let ``get /participant/%i``() : PrintfFormat<int<ParticipantId> -> _,_,_,_,int<ParticipantId>> = "/participant/%i"
let getUsers = "/user"

// we can include the action in the variable name too
// also questionable but possibly useful
let ``post /participant`` = "/participant"


let ``get /client/%i/participant`` () : PrintfFormat<int<ClientId> -> _,_,_,_,int<ClientId>> = "/client/%i/participant"
let ``get /event/%i/participants`` () : PrintfFormat<int<EventId> -> _,_,_,_,int<EventId>> = "/event/%i/participants"
let resultList clientId pId = sprintf "/result/client/%i/participant/%i" clientId pId

Notice for the getEventDownload I had to have 2 different paths, one for the client to generate a proper url, and one for the server. which sucks.

Here's an example webPart that works unrelated to the above examples:

pathScan "/client/%i/customer/%i" (fun (clientId,customerId) -> sprintf "Customer %i, Customer %i" clientId customerId |> OK)

as far as query params I'd think you'd be better off letting the path match, and returning invalid request messages for missing query params or something similar.

Of course you could do branching inside the pathScan match handler.

An example of handling query params:

    let serveResult cn :WebPart =
        fun ctx ->
            let eventIdField = toCamelCase RMeta.EventId
            let pIdField  = toCamelCase RMeta.ParticipantId
            let eventIdOpt = ctx.request.queryParamOpt eventIdField
            let pIdOpt = ctx.request.queryParamOpt pIdField
            match eventIdOpt, pIdOpt with
            | Some(_,Some (ParseInt eventId)), Some(_,Some(ParseInt pId)) ->
                let model = Dal.DataAccess.Results.getResult cn (1<EventId> * eventId) (1<ParticipantId> * pId)
                match model with
                | Some m ->
                    OK (Json.toJson m) // |> Option.getOrDefault' (lazy({ResultRecord.Zero() with EventId = eventId; ParticipantId = pId}))
                | _ -> RequestErrors.NOT_FOUND ctx.request.rawQuery

            | _ ->  RequestErrors.BAD_REQUEST (ctx.request.rawQuery)
            |> fun f -> f ctx

or a WebPart sample of composing with queryParams

let routing () = 
    let (|ParseInt|_|) =
        function
        | null | "" -> None
        | x -> 
            match Int32.TryParse x with
            | true, i -> Some i
            | _ -> None


    // this only returns a string but hopefully it helps imply how more complicated items could be composed
    let queryParamOrFail name (ctx:HttpContext) =
        match ctx.request.queryParam name with
        | Choice1Of2 value -> 
            Choice1Of2 value
        | Choice2Of2 msg ->
            RequestErrors.BAD_REQUEST msg
            |> Choice2Of2
    let queryIntOrFail name =
        queryParamOrFail name
        >> Choice.bind(
            (|ParseInt|_|)
            >> function
                | Some i -> Choice1Of2 i
                | None -> RequestErrors.BAD_REQUEST (sprintf "query param %s was not a number" name) |> Choice2Of2
        )
    let clientQueryPart:WebPart =
        path "/clientQuery" >=>
        (fun ctx ->
            queryIntOrFail "companyId" ctx
            |> function
                | Choice1Of2 v -> sprintf "CompanyId %i" v |> OK
                | Choice2Of2 requestErrorWebPart -> requestErrorWebPart 
            |> fun wp -> wp ctx
        )
    let fullQueryPart:WebPart =
        path "/query" >=>
        (fun ctx ->
            match queryIntOrFail "companyId" ctx, queryIntOrFail "clientId" ctx, queryIntOrFail "customerId" ctx with
            | Choice2Of2 reqErr,_,_ -> reqErr
            | _,Choice2Of2 reqErr,_ -> reqErr
            | _,_,Choice2Of2 reqErr -> reqErr
            | Choice1Of2 compId, Choice1Of2 clientId, Choice1Of2 customerId ->
                sprintf "CompanyId %i, ClientId %i, CustomerId %i" compId clientId customerId
                |> OK
            |> fun wp -> wp ctx
        )

    choose 
        [
            GET >=> choose
                [   
                    path "/" >=> OK "Default GET"
                    path "/hello" >=> OK "Hello GET"
                    pathScan "/whatnumber/%i" ((sprintf "Your number is %i") >> OK)
                    pathScan "/client/%i/customer/%i" (fun (clientId,customerId) -> sprintf "Client %i, Customer %i" clientId customerId |> OK)
                    pathScan "/client/%i/customerQuery" (fun clientId ctx -> 
                        match queryParamOrFail "customerId" ctx with
                        | Choice1Of2 (ParseInt customerId) ->
                            sprintf "Client %i, Customer %i" clientId customerId
                            |> fun msg ->  OK msg ctx
                        | Choice1Of2 _ -> RequestErrors.BAD_REQUEST "query param customerId was not a number" ctx
                        | Choice2Of2 wp -> wp ctx
                    )
                    clientQueryPart
                    fullQueryPart
                    path "/goodbye" >=> OK "Good bye GET"
                ]
            POST >=> choose
                [
                    path "/hello" >=> OK "Hello POST"
                    path "/goodbye" >=> OK "Good bye POST"
                ]
        ]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM