Skip to content

Commit

Permalink
feat!: allow default values for optional map keys (#32)
Browse files Browse the repository at this point in the history
BREAKING-CHANGE: Raises minimum Elixir version to 1.12
  • Loading branch information
mhanberg committed Oct 11, 2024
1 parent 71585cc commit f4f799c
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 27 deletions.
34 changes: 17 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,27 @@ jobs:

strategy:
matrix:
otp: [23.x, 24.x, 25.x, 26.x]
elixir: [1.10.x, 1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x]
otp: [25.x, 26.x, 27.x]
elixir: [1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x]
exclude:
- otp: 26.x
elixir: 1.10.x
- otp: 26.x
elixir: 1.11.x
- otp: 27.x
elixir: 1.12.x
- otp: 27.x
elixir: 1.13.x
- otp: 27.x
elixir: 1.14.x
- otp: 27.x
elixir: 1.15.x
- otp: 27.x
elixir: 1.16.x
- otp: 27.x
elixir: 1.16.x
- otp: 26.x
elixir: 1.12.x
- otp: 26.x
elixir: 1.13.x
- otp: 25.x
elixir: 1.11.x
- otp: 25.x
elixir: 1.12.x
- otp: 25.x
elixir: 1.10.x
- otp: 24.x
elixir: 1.10.x
- otp: 23.x
elixir: 1.15.x

steps:
- uses: actions/checkout@v2
Expand All @@ -56,14 +56,14 @@ jobs:

formatter:
runs-on: ubuntu-latest
name: Formatter (1.15.x/26.x)
name: Formatter (1.17.x/27.x)

steps:
- uses: actions/checkout@v2
- uses: erlef/setup-beam@v1
with:
otp-version: 26.x
elixir-version: 1.15.x
otp-version: 27.x
elixir-version: 1.17.x
- uses: actions/cache@v3
with:
path: |
Expand Down
36 changes: 29 additions & 7 deletions lib/schematic.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ defmodule Schematic do

defmodule OptionalKey do
@enforce_keys [:key]
defstruct [:key]
defstruct [:key, default: :__SCHEMATIC_EMPTY_DEFAULT__]

@opaque t :: %__MODULE__{
key: {any(), any()} | any()
key: {any(), any()} | any(),
default: any()
}
end

Expand Down Expand Up @@ -460,16 +461,20 @@ defmodule Schematic do
If the key _is_ provided, it must unify according to the given schematic.
Likewise, using `dump/2` will also omit that key.
You can also provide a default value for an optional key with `optional/2`.
Likewise, using `dump/2` will also omit that key, unless it has a default value.
```elixir
iex> schematic = map(%{
...> "title" => str(),
...> optional("description") => str()
...> optional("description") => str(),
...> optional("kind", "technology") => str()
...> })
iex> {:ok, %{"title" => "Elixir 101", "description" => "An amazing programming course."}} = unify(schematic, %{"title" => "Elixir 101", "description" => "An amazing programming course."})
iex> {:ok, %{"title" => "Elixir 101"}} = unify(schematic, %{"title" => "Elixir 101"})
iex> {:ok, %{"title" => "Elixir 101"}} = dump(schematic, %{"title" => "Elixir 101"})
iex> {:ok, %{"title" => "Elixir 101", "description" => "An amazing programming course.", "kind" => "technology"}} = unify(schematic, %{"title" => "Elixir 101", "description" => "An amazing programming course."})
iex> {:ok, %{"title" => "Elixir 101", "kind" => "computer science"}} = unify(schematic, %{"title" => "Elixir 101", "kind" => "computer science"})
iex> {:ok, %{"title" => "Elixir 101", "kind" => "computer science"}} = dump(schematic, %{"title" => "Elixir 101", "kind" => "computer science"})
iex> {:ok, %{"title" => "Elixir 101", "kind" => "technology"}} = dump(schematic, %{"title" => "Elixir 101"})
```
## With `:keys` and `:values`
Expand Down Expand Up @@ -603,6 +608,13 @@ defmodule Schematic do
end

if not Map.has_key?(input, from_key) and match?(%OptionalKey{}, bpk) do
acc =
if bpk.default != :__SCHEMATIC_EMPTY_DEFAULT__ do
Map.put(acc, to_key, bpk.default)
else
acc
end

[{:ok, acc}, {:errors, errors}]
else
case Schematic.Unification.unify(schematic, input[from_key], dir) do
Expand Down Expand Up @@ -1017,4 +1029,14 @@ defmodule Schematic do
def optional(key) do
%OptionalKey{key: key}
end

@doc """
Specifies an optional key and also a default value in the case the key is not present.
See `map/1` for more examples and explanation.
"""
@spec optional(any(), any()) :: OptionalKey.t()
def optional(key, default) do
%OptionalKey{key: key, default: default}
end
end
4 changes: 2 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule Schematic.MixProject do
description: "Data validation and transformation",
package: package(),
version: "0.3.1",
elixir: "~> 1.10",
elixir: "~> 1.12",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
source_url: "https://github.com/mhanberg/schematic",
Expand Down Expand Up @@ -41,7 +41,7 @@ defmodule Schematic.MixProject do
[
{:telemetry, "~> 0.4 or ~> 1.0"},
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
{:stream_data, "~> 0.5.0", only: [:dev, :test], runtime: false}
{:stream_data, "~> 1.1", only: [:dev, :test], runtime: false}
]
end

Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
"stream_data": {:hex, :stream_data, "1.1.2", "05499eaec0443349ff877aaabc6e194e82bda6799b9ce6aaa1aadac15a9fdb4d", [:mix], [], "hexpm", "129558d2c77cbc1eb2f4747acbbea79e181a5da51108457000020a906813a1a9"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"},
}
14 changes: 14 additions & 0 deletions test/schematic_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,20 @@ defmodule SchematicTest do
unify(schematic, %{type: 10, name: 10})
end

test "optional keys with default value" do
schematic =
map(%{
optional(:name, "mitch") => str(),
type: int()
})

assert {:ok, %{type: 10, name: "mitch"}} == unify(schematic, %{type: 10})
assert {:ok, %{type: 10, name: "bob"}} == unify(schematic, %{type: 10, name: "bob"})

assert {:error, %{name: "expected a string"}} ==
unify(schematic, %{type: 10, name: 10})
end

test "empty map" do
schematic = map()

Expand Down

0 comments on commit f4f799c

Please sign in to comment.