Current section

74 Versions

Jump to

Compare versions

11 files changed
+202 additions
-53 deletions
  @@ -2,6 +2,20 @@
2 2
3 3 All notable changes to this project will be documented in this file.
4 4
5 + ## Unreleased
6 +
7 + ### Added
8 +
9 + - `Zoi.atom/1` type
10 + - `Zoi.string_boolean/1` type
11 + - `Zoi.union/2` custom error messages
12 + - `Zoi.intersection/2` custom error messages
13 + - `Zoi.to_struct/2` transform
14 +
15 + ### Changed
16 +
17 + - `Zoi.boolean/1` does not coerce values besides "true" and "false" anymore. For coercion of other values, use `Zoi.string_boolean/1` type.
18 +
5 19 ## 0.4.0 - 2025-08-14
6 20
7 21 ### Added
  @@ -118,16 +132,16 @@ Zoi.object(schema, %{"name" => "John", "age" => 30}, coerce: true)
118 132
119 133 ### Changed
120 134
121 - - `number/2` now returns proper error message
135 + - `Zoi.number/2` now returns proper error message
122 136
123 137 ## 0.2.0 - 2025-08-05
124 138
125 139 ### Added
126 140
127 - - `mfa` to `refine` and `transform` functions
128 - - accumulator errors to `refine` and `transform` functions
129 - - `array` type
130 - - `length`, `min` and `max` validators for arrays
141 + - `mfa` to `Zoi.refine/2` and `Zoi.transform/2` functions
142 + - accumulator errors to `Zoi.refine/2` and `Zoi.transform/2` functions
143 + - `Zoi.array/2` type
144 + - `Zoi.length/2`, `Zoi.min/2` and `Zoi.max/2` validators for arrays
131 145
132 146 ### Changed
  @@ -15,7 +15,7 @@
15 15 ```elixir
16 16 def deps do
17 17 [
18 - {:zoi, "~> 0.4"}
18 + {:zoi, "~> 0.5"}
19 19 ]
20 20 end
21 21 ```
  @@ -2,7 +2,7 @@
2 2 [{<<"Changelog">>,<<"https://hexdocs.pm/zoi/changelog.html">>},
3 3 {<<"GitHub">>,<<"https://github.com/phcurado/zoi">>}]}.
4 4 {<<"name">>,<<"zoi">>}.
5 - {<<"version">>,<<"0.4.0">>}.
5 + {<<"version">>,<<"0.5.0">>}.
6 6 {<<"description">>,
7 7 <<"Zoi is a schema validation library for Elixir, designed to provide a simple and flexible way to define and validate data.">>}.
8 8 {<<"elixir">>,<<"~> 1.14">>}.
  @@ -10,24 +10,25 @@
10 10 {<<"licenses">>,[<<"Apache-2.0">>]}.
11 11 {<<"files">>,
12 12 [<<".formatter.exs">>,<<"lib">>,<<"lib/zoi.ex">>,<<"lib/zoi">>,
13 - <<"lib/zoi/iso">>,<<"lib/zoi/iso/time.ex">>,<<"lib/zoi/iso/date.ex">>,
13 + <<"lib/zoi/error.ex">>,<<"lib/zoi/regexes.ex">>,<<"lib/zoi/iso">>,
14 14 <<"lib/zoi/iso/naive_datetime.ex">>,<<"lib/zoi/iso/datetime.ex">>,
15 - <<"lib/zoi/error.ex">>,<<"lib/zoi/refinements.ex">>,<<"lib/zoi/type">>,
16 - <<"lib/zoi/type/def.ex">>,<<"lib/zoi/types">>,<<"lib/zoi/types/time.ex">>,
17 - <<"lib/zoi/types/integer.ex">>,<<"lib/zoi/types/default.ex">>,
18 - <<"lib/zoi/types/date.ex">>,<<"lib/zoi/types/intersection.ex">>,
19 - <<"lib/zoi/types/decimal.ex">>,<<"lib/zoi/types/number.ex">>,
20 - <<"lib/zoi/types/nullable.ex">>,<<"lib/zoi/types/object.ex">>,
21 - <<"lib/zoi/types/naive_datetime.ex">>,<<"lib/zoi/types/optional.ex">>,
22 - <<"lib/zoi/types/tuple.ex">>,<<"lib/zoi/types/float.ex">>,
23 - <<"lib/zoi/types/string.ex">>,<<"lib/zoi/types/map.ex">>,
24 - <<"lib/zoi/types/boolean.ex">>,<<"lib/zoi/types/enum.ex">>,
25 - <<"lib/zoi/types/array.ex">>,<<"lib/zoi/types/datetime.ex">>,
26 - <<"lib/zoi/types/union.ex">>,<<"lib/zoi/types/meta.ex">>,
27 - <<"lib/zoi/types/any.ex">>,<<"lib/zoi/iso.ex">>,<<"lib/zoi/type.ex">>,
28 - <<"lib/zoi/context.ex">>,<<"lib/zoi/regexes.ex">>,<<"lib/zoi/errors.ex">>,
29 - <<"lib/zoi/transforms.ex">>,<<"mix.exs">>,<<"README.md">>,
30 - <<"CHANGELOG.md">>]}.
15 + <<"lib/zoi/iso/date.ex">>,<<"lib/zoi/iso/time.ex">>,<<"lib/zoi/type.ex">>,
16 + <<"lib/zoi/context.ex">>,<<"lib/zoi/refinements.ex">>,<<"lib/zoi/types">>,
17 + <<"lib/zoi/types/string.ex">>,<<"lib/zoi/types/atom.ex">>,
18 + <<"lib/zoi/types/enum.ex">>,<<"lib/zoi/types/default.ex">>,
19 + <<"lib/zoi/types/any.ex">>,<<"lib/zoi/types/nullable.ex">>,
20 + <<"lib/zoi/types/decimal.ex">>,<<"lib/zoi/types/object.ex">>,
21 + <<"lib/zoi/types/array.ex">>,<<"lib/zoi/types/integer.ex">>,
22 + <<"lib/zoi/types/float.ex">>,<<"lib/zoi/types/tuple.ex">>,
23 + <<"lib/zoi/types/naive_datetime.ex">>,<<"lib/zoi/types/boolean.ex">>,
24 + <<"lib/zoi/types/meta.ex">>,<<"lib/zoi/types/datetime.ex">>,
25 + <<"lib/zoi/types/map.ex">>,<<"lib/zoi/types/string_boolean.ex">>,
26 + <<"lib/zoi/types/optional.ex">>,<<"lib/zoi/types/date.ex">>,
27 + <<"lib/zoi/types/intersection.ex">>,<<"lib/zoi/types/time.ex">>,
28 + <<"lib/zoi/types/number.ex">>,<<"lib/zoi/types/union.ex">>,
29 + <<"lib/zoi/iso.ex">>,<<"lib/zoi/type">>,<<"lib/zoi/type/def.ex">>,
30 + <<"lib/zoi/errors.ex">>,<<"lib/zoi/transforms.ex">>,<<"mix.exs">>,
31 + <<"README.md">>,<<"CHANGELOG.md">>]}.
31 32 {<<"requirements">>,
32 33 [[{<<"name">>,<<"decimal">>},
33 34 {<<"app">>,<<"decimal">>},
  @@ -176,6 +176,44 @@ defmodule Zoi do
176 176 @doc group: "Basic Types"
177 177 defdelegate boolean(opts \\ []), to: Zoi.Types.Boolean, as: :new
178 178
179 + @doc """
180 + Defines a string boolean type schema.
181 +
182 + This type parses "boolish" string values:
183 + # thruthy values: true, "true", "1", "yes", "on", "y", "enabled"
184 + # falsy values: false, "false", "0", "no", "off", "n", "disabled"
185 +
186 +
187 + ## Example
188 +
189 + iex> schema = Zoi.string_boolean()
190 + iex> Zoi.parse(schema, "true")
191 + {:ok, true}
192 + iex> Zoi.parse(schema, "false")
193 + {:ok, false}
194 + iex> Zoi.parse(schema, "yes")
195 + {:ok, true}
196 + iex> Zoi.parse(schema, "no")
197 + {:ok, false}
198 +
199 + You can also specify custom truthy and falsy values using the `:truthy` and `:falsy` options:
200 + iex> schema = Zoi.string_boolean(truthy: ["yes", "y"], falsy: ["no", "n"])
201 + iex> Zoi.parse(schema, "yes")
202 + {:ok, true}
203 + iex> Zoi.parse(schema, "no")
204 + {:ok, false}
205 +
206 + By default the string boolean type is case insensitive and the input is converted to lowercase during the comparison. You can change this behavior using the `:case` option:
207 +
208 + iex> schema = Zoi.string_boolean(case: "sensitive")
209 + iex> Zoi.parse(schema, "True")
210 + {:error, [%Zoi.Error{message: "invalid type: must be a string boolean"}]}
211 + iex> Zoi.parse(schema, "true")
212 + {:ok, true}
213 + """
214 + @doc group: "Basic Types"
215 + defdelegate string_boolean(opts \\ []), to: Zoi.Types.StringBoolean, as: :new
216 +
179 217 @doc """
180 218 Defines a schema that accepts any type of input.
181 219
  @@ -194,6 +232,19 @@ defmodule Zoi do
194 232 @doc group: "Basic Types"
195 233 defdelegate any(opts \\ []), to: Zoi.Types.Any, as: :new
196 234
235 + @doc """
236 + Defines an atom type schema.
237 +
238 + ## Examples
239 + iex> schema = Zoi.atom()
240 + iex> Zoi.parse(schema, :atom)
241 + {:ok, :atom}
242 + iex> Zoi.parse(schema, "not_an_atom")
243 + {:error, [%Zoi.Error{message: "invalid type: must be an atom"}]}
244 + """
245 + @doc group: "Basic Types"
246 + defdelegate atom(opts \\ []), to: Zoi.Types.Atom, as: :new
247 +
197 248 @doc """
198 249 Makes the schema optional for the `Zoi.object/2` type.
199 250
  @@ -901,6 +952,32 @@ defmodule Zoi do
901 952 |> transform({Zoi.Transforms, :transform, [[:to_upcase]]})
902 953 end
903 954
955 + @doc """
956 + Converts a schema to a struct of the given module.
957 + This is useful for transforming parsed data into a specific struct type.
958 +
959 + ## Example
960 +
961 + defmodule User do
962 + defstruct [:name, :age]
963 + end
964 +
965 + schema = Zoi.object(%{
966 + name: Zoi.string() |> Zoi.required(),
967 + age: Zoi.integer() |> Zoi.required()
968 + })
969 + |> Zoi.to_struct(User)
970 +
971 + Zoi.parse(schema, %{name: "Alice", age: 30})
972 + #=> {:ok, %User{name: "Alice", age: 30}}
973 + """
974 + @doc group: "Transforms"
975 + @spec to_struct(schema :: Zoi.Type.t(), struct :: module()) :: Zoi.Type.t()
976 + def to_struct(schema, module) do
977 + schema
978 + |> transform({Zoi.Transforms, :transform, [[struct: module]]})
979 + end
980 +
904 981 @doc ~S"""
905 982 Adds a custom validation function to the schema.
  @@ -17,6 +17,10 @@ defmodule Zoi.Transforms do
17 17 String.upcase(input)
18 18 end
19 19
20 + defp do_transform(%Zoi.Types.Object{}, input, [struct: struct], _opts) do
21 + struct!(struct, input)
22 + end
23 +
20 24 defp do_transform(_schema, input, _args, _opts) do
21 25 # Default to the input if there is no type pattern match
22 26 {:ok, input}
Loading more files…