From 0501dd42c542f9ff4940bde057e39d3256b1be4d Mon Sep 17 00:00:00 2001 From: Matt Thornton Date: Wed, 24 Nov 2021 13:48:08 +0000 Subject: [PATCH] Add a decoder for lists. (#32) --- .../Bind.fs | 20 ++++- .../BindTests.fs | 88 +++++++++++++++++++ 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/src/Symbolica.Extensions.Configuration.FSharp/Bind.fs b/src/Symbolica.Extensions.Configuration.FSharp/Bind.fs index d3bba2b..32709ca 100644 --- a/src/Symbolica.Extensions.Configuration.FSharp/Bind.fs +++ b/src/Symbolica.Extensions.Configuration.FSharp/Bind.fs @@ -154,8 +154,7 @@ module Bind = /// /// Binds an as an by applying the - /// to the key and the to the value - /// of each child section. + /// to the key and the to each child section. /// /// /// The to apply to the key of each child section in order to convert it to the key type. @@ -185,6 +184,23 @@ module Bind = |> Binder.mapFailure (Errors.AllOf >> Error.Many) |> Binder.map (fun x -> Dictionary(x) :> IDictionary<'key, 'value>) + /// + /// Binds an as a by applying the + /// the to each child section. + /// + /// + /// The to apply to each child section in order to convert it to the key type. + /// Note this can be either a simple value binder or a binder for a more complex type that spans + /// multiple child sections. + /// + let list (valueBinder: Binder) : Binder<'config, 'value list, Error> = + dict Binder.ask valueBinder + |> Binder.map ( + Seq.sortBy (fun x -> x.Key) + >> Seq.map (fun x -> x.Value) + >> List.ofSeq + ) + /// Creates a from a System.Type.TryParse style parsing function. /// /// Useful for creating a parser for a primitive type for which a TryParse function already exists. diff --git a/tests/Symbolica.Extensions.Configuration.FSharp.Tests/BindTests.fs b/tests/Symbolica.Extensions.Configuration.FSharp.Tests/BindTests.fs index 3c2f45e..016431b 100644 --- a/tests/Symbolica.Extensions.Configuration.FSharp.Tests/BindTests.fs +++ b/tests/Symbolica.Extensions.Configuration.FSharp.Tests/BindTests.fs @@ -678,6 +678,94 @@ module Dict = ) ) @> +module List = + + type CustomType = { Prop: int } + + [ |])>] + let ``should be success ordered by keys if all values can be decoded`` path = + let section = + { Children = + [ { Children = Seq.empty + Path = "foo" |> ConfigPathSegment + Value = "foo" } + { Children = Seq.empty + Path = "bar" |> ConfigPathSegment + Value = "bar" } + { Children = Seq.empty + Path = "4" |> ConfigPathSegment + Value = "4" } + { Children = Seq.empty + Path = "0" |> ConfigPathSegment + Value = "0" } + { Children = Seq.empty + Path = "1" |> ConfigPathSegment + Value = "1" } ] + Path = path + Value = null } + + test + <@ Bind.list (Bind.value Bind.string) + |> Binder.eval section = Success["0" + "1" + "4" + "bar" + "foo"] @> + + [ |])>] + let ``should be success if key and complex value can be decoded`` path key value = + let bindCustomType = + bind { + let! prop = Bind.valueAt "prop" Bind.int + return { Prop = prop } + } + + let section = + { Children = + [ { Children = + [ { Children = Seq.empty + Path = "prop" |> ConfigPathSegment + Value = value |> string } ] + Path = key + Value = null } ] + Path = path + Value = null } + + test <@ Bind.list bindCustomType |> Binder.eval section = Success[{ Prop = value }] @> + + [ |])>] + let ``should be failure if any value cannot be decoded`` path = + let section = + { Children = + [ { Children = Seq.empty + Path = "1" |> ConfigPathSegment + Value = "foo" } + { Children = Seq.empty + Path = "2" |> ConfigPathSegment + Value = "bar" } + { Children = Seq.empty + Path = "3" |> ConfigPathSegment + Value = "true" } ] + Path = path + Value = null } + + test + <@ Bind.list (Bind.value Bind.bool) + |> Binder.eval section = Failure( + Error.Many( + Errors.AllOf( + Error.SectionError( + "1", + Error.Many(Errors.single (Error.ValueError("foo", ValueError.invalidType))) + ) + +& Error.SectionError( + "2", + Error.Many(Errors.single (Error.ValueError("bar", ValueError.invalidType))) + ) + ) + ) + ) @> + module Bool = [] let ``should be Success value if can be converted to bool`` value =