Reputation: 16186
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
Upvotes: 5
Views: 212
Reputation: 18746
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"
]
]
Upvotes: 0
Reputation: 1145
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
]
Upvotes: 1