Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NullReferenceException when using routeBind with Sum Type #206

Closed
mamcx opened this issue Jan 13, 2018 · 10 comments
Closed

NullReferenceException when using routeBind with Sum Type #206

mamcx opened this issue Jan 13, 2018 · 10 comments
Labels
bug Reproducible bug in progress Issue is already being adressed as part of ongoing work
Milestone

Comments

@mamcx
Copy link

mamcx commented Jan 13, 2018

This is a weird one:

type Sorted =
        | Name
        | Code

type QueryCustomer = {sort:Customers.Sorted}

        routeBind<QueryCustomer> "/customers/{sortBy}" ( fun sortBy ->
            LOG.Information("PATH: /customers/{Sort}", sortBy.sort)
            let str = 
                match sortBy.sort with
                | Customers.Name -> "n"
                | Customers.Code -> "n"

When a wrong path is called (like /customers/bla) it get converted to this:

{{sort = null;}}

Instead of give a 404 as expected. Is very unexpected that Giraffe somehow broke the union type here.

Giraffe Version="0.1.0-beta-700"

@JonCanning
Copy link
Contributor

JonCanning commented Jan 13, 2018

routeBind creates a dictionary and uses JObject to convert that into a type

let routeBind<'T> (route: string) (routeHandler : 'T -> HttpHandler) : HttpHandler =

For this to work with a union, you're going to have to create a JsonConverter so it knows how to convert the string. It's a bit hairy.

[<JsonConverter(typeof<SortedConverter>)>]
type Sorted =
| Name
| Code
  static member convert value =
    match value.ToString().ToLower() with
    | "name" -> Name
    | "code" -> Code
    | value -> invalidArg "value" value
and 
  SortedConverter() =
    inherit JsonConverter()
      override __.CanConvert t = t = typeof<Sorted>
      override __.WriteJson(_, _, _) = invalidOp "WriteJson"
      override __.ReadJson(reader, _, _, _) =
        reader.Value
        |> Sorted.convert
        |> box

I imagine something smarter could be achieved using reflection...

@mamcx
Copy link
Author

mamcx commented Jan 13, 2018

This is no the expected behavior from a native F# library. I understand why is like that, so maybe is better to clarify to have a CLI_MUTABLE struct? Or get a map instead?

@dustinmoris
Copy link
Member

Hi @mamcx, yes we should document this until we find time to add union support. I think it would be worth to have a look if there is already an F# library which extends Json.NET for better F# type support otherwise we could write our own converters using reflection as @JonCanning suggested.

@mamcx
Copy link
Author

mamcx commented Jan 14, 2018 via email

@dustinmoris
Copy link
Member

Hi @mamcx,

In your example the route /customers/bla technically matches the pattern /customers/{sortBy}, but bla is an invalid value for Sorted.

I think there are a few ways how this could be handled:

  • If bla fails to parse to Sorted then throw exception
  • if bla fails to parse to Sorted then abort route (which will eventually end up at the 404 if no other handler picks up the request)
  • If bla fails to parse to Sorted then set the value to null

Probably the first or second option make the most sense. Sounds like you prefer the second, is that correct?

Also regarding FSharpLu, did you get routeBind working with that library or did you refer to FSharpLu as a resource to look up how they parse union types?

@dustinmoris dustinmoris added bug Reproducible bug help wanted Community contribution or any kind of help much appreciated labels Feb 11, 2018
@mamcx
Copy link
Author

mamcx commented Feb 11, 2018

I expect the second to hold. The reason is that is exactly why I using the union type and not just a string, ie, why "int" work, but "Int Or String" not?

In python, I could use a regex to parse the posibilities.

@dustinmoris
Copy link
Member

Ok, makes sense...

Could you please explain what you meant by

I'm using https://github.com/Microsoft/fsharplu/wiki/fsharplu.json and it
serialize right the F# types.

How did you use FSharpLu with the previous version of Giraffe?

@mamcx
Copy link
Author

mamcx commented Feb 11, 2018

This is how I do this:

open Microsoft.FSharpLu
let toJson (value:'T) =
    Json.Compact.serialize value

let contentJson (value:'T): HttpHandler =
    let json = value |> toJson
    setHttpHeader "Content-Type" "application/json; charset=utf-8"
    >=> setBodyAsString json

let getCustomer(sort) =
    use db = openConn()
    ServerApi.getCustomer(db, sort)
    |> contentJson

I just replace the json calls with my own things, without inject into Giraffe (I don't like much magic ;), also waiting for the support to streaming large data to see how it could be...)

@mamcx
Copy link
Author

mamcx commented Feb 12, 2018

I think the problem is more universal. For example:

[<CLIMutable>]
type LoginViewModel =
    {
        Email : string
        Password : string
    }

let handlePostToken =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            let! model = ctx.BindJsonAsync<LoginViewModel>()

            // authenticate user
            
            let tokenResult = generateToken model.Email

            return! json tokenResult next ctx
        }

In BindJsonAsync it get a System.NullReferenceException (when try to post with empty data (like POST /auth). The thing is binding is parsing and that could fail. I'm ok with wrap into a try/except but then at least provide a custom exception so is clear the problem is parsing and not something else. Or make it a Option instead.

@dustinmoris dustinmoris added in progress Issue is already being adressed as part of ongoing work and removed help wanted Community contribution or any kind of help much appreciated labels Feb 13, 2018
@dustinmoris dustinmoris added this to the 1.1.0 milestone Feb 13, 2018
dustinmoris added a commit that referenced this issue Feb 14, 2018
Improved model binding which addresses several issues. Related to #206 and #121.
@dustinmoris
Copy link
Member

Hi, this should be fixed in today's 1.1.0 release. Please let me know if you encounter any more issues!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Reproducible bug in progress Issue is already being adressed as part of ongoing work
Projects
None yet
Development

No branches or pull requests

3 participants