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 =