Skip to content

Commit

Permalink
Merge pull request #54 from diegobfernandez/develop
Browse files Browse the repository at this point in the history
Add Option<'T> support for BindQueryString
  • Loading branch information
dustinmoris authored May 31, 2017
2 parents ad7e986 + 102694f commit 14b0598
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 2 deletions.
32 changes: 30 additions & 2 deletions src/Giraffe/HttpContextExtensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ open System.ComponentModel
open Microsoft.AspNetCore.Http
open Microsoft.Extensions.Primitives
open Microsoft.Extensions.Logging
open Microsoft.FSharp.Reflection
open Microsoft.Net.Http.Headers
open Giraffe.Common

Expand Down Expand Up @@ -87,9 +88,36 @@ type HttpContext with
let strValue = ref (StringValues())
if query.TryGetValue(p.Name, strValue)
then
let converter = TypeDescriptor.GetConverter p.PropertyType
let isOptionType =
p.PropertyType.GetTypeInfo().IsGenericType &&
p.PropertyType.GetGenericTypeDefinition() = typedefof<Option<_>>

let propertyType =
if isOptionType then
p.PropertyType.GetGenericArguments().[0]
else
p.PropertyType

let propertyType =
if propertyType.GetTypeInfo().IsValueType then
(typedefof<Nullable<_>>).MakeGenericType([|propertyType|])
else
propertyType

let converter = TypeDescriptor.GetConverter propertyType

let value = converter.ConvertFromInvariantString(strValue.Value.ToString())
p.SetValue(obj, value, null))

if isOptionType then
let cases = FSharpType.GetUnionCases(p.PropertyType)
let value =
if isNull value then
FSharpValue.MakeUnion(cases.[0], [||])
else
FSharpValue.MakeUnion(cases.[1], [|value|])
p.SetValue(obj, value, null)
else
p.SetValue(obj, value, null))
return obj
}

Expand Down
35 changes: 35 additions & 0 deletions tests/Giraffe.Tests/ModelBindingTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ let getBody (ctx : HttpContext) =
use reader = new StreamReader(ctx.Response.Body, Encoding.UTF8)
reader.ReadToEnd()

[<CLIMutable>]
type ModelWithOption =
{
OptionalInt: int option
OptionalString: string option
}

[<CLIMutable>]
type Customer =
{
Expand Down Expand Up @@ -203,6 +210,34 @@ let ``bindQueryString test`` () =
let body = getBody ctx
Assert.Equal(expected, body)

[<Fact>]
let ``bindQueryString with option property test`` () =
let testRoute queryStr expected =
let queryHandlerWithSome (ctx : HttpContext) =
async {
let! model = ctx.BindQueryString<ModelWithOption>()
Assert.Equal(expected, model)
return! setStatusCode 200 ctx
}

let app = GET >=> route "/" >=> queryHandlerWithSome

let ctx = Substitute.For<HttpContext>()
let query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery queryStr
ctx.Request.Query.ReturnsForAnyArgs(QueryCollection(query) :> IQueryCollection) |> ignore
ctx.Request.Method.ReturnsForAnyArgs "GET" |> ignore
ctx.Request.Path.ReturnsForAnyArgs (PathString("/")) |> ignore
ctx.Response.Body <- new MemoryStream()

ctx
|> app
|> Async.RunSynchronously
|> ignore

testRoute "?OptionalInt=1&OptionalString=Hi" { OptionalInt = Some 1; OptionalString = Some "Hi" }
testRoute "?" { OptionalInt = None; OptionalString = None }
testRoute "?OptionalInt=&OptionalString=" { OptionalInt = None; OptionalString = Some "" }

[<Fact>]
let ``bindModel with JSON content returns correct result`` () =
let ctx = Substitute.For<HttpContext>()
Expand Down

0 comments on commit 14b0598

Please sign in to comment.