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

Add TypeHandler for fieldless and single case DUs #100

Open
lucasteles opened this issue Nov 8, 2023 · 0 comments
Open

Add TypeHandler for fieldless and single case DUs #100

lucasteles opened this issue Nov 8, 2023 · 0 comments

Comments

@lucasteles
Copy link

Using single-case DU to strongly type values is very common and also just fieldless enum-like DUs:

type Email = Email of string
type PersonId = PersonId of Guid
type Light = On | Off

So it would be great to have TypeHandler support for those cases

I've been using an implementation that looks like this:

module TypeHandlers =
    type SingleCaseUnionTypeHandler<'t>() =
        inherit SqlMapper.TypeHandler<'t>()

        let caseInfo =
            FSharpType.GetUnionCases(typedefof<'t>)
            |> Array.tryExactlyOne
            |> Option.filter (fun c -> c.GetFields().Length = 1)

        override _.Parse(value) =
            match caseInfo with
            | None -> failwith $"Unable to map type #{typedefof<'t>.Name}"
            | Some case -> FSharpValue.MakeUnion(case, [| value |]) :?> 't

        override this.SetValue(parameter, value) =
            match caseInfo with
            | None -> failwith $"Unable to map type #{typedefof<'t>.Name}"
            | Some case -> parameter.Value <- FSharpValue.GetUnionFields(value, case.DeclaringType) |> snd |> Seq.head

    type SimpleUnionTypeHandler<'t when 't: equality>() =
        inherit SqlMapper.TypeHandler<'t>()

        let cases =
            FSharpType.GetUnionCases(typeof<'t>)
            |> Array.filter (fun c -> c.GetFields() |> Array.isEmpty)

        override _.Parse(value) =
            cases
            |> Array.tryFind (fun c -> c.Name = string value)
            |> Option.map (fun c -> FSharpValue.MakeUnion(c, [||]))
            |> function
                | None -> failwith $"Unable to map type #{typedefof<'t>.Name}"
                | Some case -> case :?> 't

        override this.SetValue(parameter, value) =
            parameter.Value <-
                cases
                |> Array.tryFind value.Equals
                |> Option.map (fun c -> box c.Name)
                |> Option.toObj

    let registerUnion<'t when 't: equality> =
        let cases = typedefof<'t> |> FSharpType.GetUnionCases

        if cases |> Array.forall (fun c -> c.GetFields().Length = 0) then
            SqlMapper.AddTypeHandler(SimpleUnionTypeHandler<'t>())

        if cases.Length = 1 then
            SqlMapper.AddTypeHandler(SingleCaseUnionTypeHandler<'t>())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant