Skip to content

Commit

Permalink
Add a decoder for lists. (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
Choc13 authored Nov 24, 2021
1 parent b1d7a63 commit 0501dd4
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 2 deletions.
20 changes: 18 additions & 2 deletions src/Symbolica.Extensions.Configuration.FSharp/Bind.fs
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,7 @@ module Bind =

/// <summary>
/// Binds an <see cref="IConfigurationSection" /> as an <see cref="IDictionary" /> by applying the
/// <paramref name="keyBinder" /> to the key and the <paramref name="valueBinder" /> to the value
/// of each child section.
/// <paramref name="keyBinder" /> to the key and the <paramref name="valueBinder" /> to each child section.
/// </summary>
/// <param name="keyBinder">
/// The <see cref="Binder" /> to apply to the key of each child section in order to convert it to the key type.
Expand Down Expand Up @@ -185,6 +184,23 @@ module Bind =
|> Binder.mapFailure (Errors.AllOf >> Error.Many)
|> Binder.map (fun x -> Dictionary(x) :> IDictionary<'key, 'value>)

/// <summary>
/// Binds an <see cref="IConfigurationSection" /> as a <see cref="List" /> by applying the
/// the <paramref name="valueBinder" /> to each child section.
/// </summary>
/// <param name="valueBinder">
/// The <see cref="Binder" /> 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.
/// </param>
let list (valueBinder: Binder<IConfigurationSection, 'value, Error>) : 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
)

/// <summary>Creates a <see cref="Binder" /> from a System.Type.TryParse style parsing function.</summary>
/// <remarks>
/// Useful for creating a parser for a primitive type for which a <c>TryParse</c> function already exists.
Expand Down
88 changes: 88 additions & 0 deletions tests/Symbolica.Extensions.Configuration.FSharp.Tests/BindTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,94 @@ module Dict =
)
) @>

module List =

type CustomType = { Prop: int }

[<Property(Arbitrary = [| typeof<ConfigurationArb> |])>]
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"] @>

[<Property(Arbitrary = [| typeof<ConfigurationArb> |])>]
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 }] @>

[<Property(Arbitrary = [| typeof<ConfigurationArb> |])>]
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<bool>)))
)
+& Error.SectionError(
"2",
Error.Many(Errors.single (Error.ValueError("bar", ValueError.invalidType<bool>)))
)
)
)
) @>

module Bool =
[<Property>]
let ``should be Success value if can be converted to bool`` value =
Expand Down

0 comments on commit 0501dd4

Please sign in to comment.