Packages
zoi
0.8.3
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.1
0.16.0
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.1
0.9.0
0.9.0-rc.1
0.9.0-rc.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Zoi is a schema validation library for Elixir, designed to provide a simple and flexible way to define and validate data.
Current section
74 Versions
Jump to
Current section
74 Versions
Compare versions
36
files changed
+403
additions
-9
deletions
| @@ -2,6 +2,12 @@ | |
| 2 2 | |
| 3 3 | All notable changes to this project will be documented in this file. |
| 4 4 | |
| 5 | + ## 0.8.3 - 2025-10-31 |
| 6 | + |
| 7 | + ### Added |
| 8 | + |
| 9 | + - All types now implements the `Inspect` protocol. This should improve the ergonomics when working with Zoi types in IEx or when inspecting/debugging it's types. |
| 10 | + |
| 5 11 | ## 0.8.2 - 2025-10-30 |
| 6 12 | |
| 7 13 | ### Added |
| @@ -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.8.2">>}. |
| 5 | + {<<"version">>,<<"0.8.3">>}. |
| 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">>}. |
| @@ -15,7 +15,7 @@ | |
| 15 15 | <<"lib/zoi/iso/datetime.ex">>,<<"lib/zoi/iso/date.ex">>, |
| 16 16 | <<"lib/zoi/iso/time.ex">>,<<"lib/zoi/type.ex">>,<<"lib/zoi/context.ex">>, |
| 17 17 | <<"lib/zoi/json_schema.ex">>,<<"lib/zoi/refinements.ex">>, |
| 18 | - <<"lib/zoi/types">>,<<"lib/zoi/types/string.ex">>, |
| 18 | + <<"lib/zoi/inspect.ex">>,<<"lib/zoi/types">>,<<"lib/zoi/types/string.ex">>, |
| 19 19 | <<"lib/zoi/types/extend.ex">>,<<"lib/zoi/types/nullish.ex">>, |
| 20 20 | <<"lib/zoi/types/atom.ex">>,<<"lib/zoi/types/enum.ex">>, |
| 21 21 | <<"lib/zoi/types/default.ex">>,<<"lib/zoi/types/any.ex">>, |
| @@ -0,0 +1,218 @@ | |
| 1 | + defmodule Zoi.Inspect do |
| 2 | + @moduledoc false |
| 3 | + |
| 4 | + import Inspect.Algebra |
| 5 | + |
| 6 | + @spec inspect_type(Zoi.Type.t(), Inspect.Opts.t(), keyword()) :: Inspect.Algebra.t() |
| 7 | + def inspect_type(type, inspect_opts, opts \\ []) |
| 8 | + |
| 9 | + def inspect_type(%Zoi.Types.Array{} = type, inspect_opts, opts), |
| 10 | + do: inspect_array(type, inspect_opts, opts) |
| 11 | + |
| 12 | + def inspect_type(%Zoi.Types.Default{} = type, inspect_opts, opts), |
| 13 | + do: inspect_default(type, inspect_opts, opts) |
| 14 | + |
| 15 | + def inspect_type(%Zoi.Types.Enum{} = type, inspect_opts, opts), |
| 16 | + do: inspect_enum(type, inspect_opts, opts) |
| 17 | + |
| 18 | + def inspect_type(%Zoi.Types.Intersection{} = type, inspect_opts, opts), |
| 19 | + do: inspect_intersection(type, inspect_opts, opts) |
| 20 | + |
| 21 | + def inspect_type(%Zoi.Types.Literal{} = type, inspect_opts, opts), |
| 22 | + do: inspect_literal(type, inspect_opts, opts) |
| 23 | + |
| 24 | + def inspect_type(%Zoi.Types.Map{} = type, inspect_opts, opts), |
| 25 | + do: inspect_map(type, inspect_opts, opts) |
| 26 | + |
| 27 | + def inspect_type(%Zoi.Types.Object{} = type, inspect_opts, opts), |
| 28 | + do: inspect_object(type, inspect_opts, opts) |
| 29 | + |
| 30 | + def inspect_type(%Zoi.Types.Keyword{} = type, inspect_opts, opts), |
| 31 | + do: inspect_keyword(type, inspect_opts, opts) |
| 32 | + |
| 33 | + def inspect_type(%Zoi.Types.StringBoolean{} = type, inspect_opts, opts), |
| 34 | + do: inspect_string_boolean(type, inspect_opts, opts) |
| 35 | + |
| 36 | + def inspect_type(%Zoi.Types.Struct{} = type, inspect_opts, opts), |
| 37 | + do: inspect_struct(type, inspect_opts, opts) |
| 38 | + |
| 39 | + def inspect_type(%Zoi.Types.Tuple{} = type, inspect_opts, opts), |
| 40 | + do: inspect_tuple(type, inspect_opts, opts) |
| 41 | + |
| 42 | + def inspect_type(%Zoi.Types.Union{} = type, inspect_opts, opts), |
| 43 | + do: inspect_union(type, inspect_opts, opts) |
| 44 | + |
| 45 | + # Default to other types |
| 46 | + def inspect_type(type, inspect_opts, opts), do: do_inspect_type(type, inspect_opts, opts) |
| 47 | + |
| 48 | + defp do_inspect_type(type, inspect_opts, opts) do |
| 49 | + name = inspect_name(type) |
| 50 | + |
| 51 | + list = |
| 52 | + meta_field_list(type) ++ type_common_fields(type) ++ Keyword.get(opts, :extra_fields, []) |
| 53 | + |
| 54 | + container_doc("#Zoi.#{name}<", list, ">", %Inspect.Opts{limit: 8}, fn |
| 55 | + {key, {:doc_group, _, _} = doc}, _opts -> |
| 56 | + # tuple means it was already been converted to doc |
| 57 | + # this usually happens when parsing nested types |
| 58 | + concat("#{key}: ", doc) |
| 59 | + |
| 60 | + {key, value}, _opts -> |
| 61 | + if value == nil do |
| 62 | + empty() |
| 63 | + else |
| 64 | + concat("#{key}: ", to_doc(value, inspect_opts)) |
| 65 | + end |
| 66 | + end) |
| 67 | + end |
| 68 | + |
| 69 | + defp meta_field_list(type) do |
| 70 | + Enum.map([:required, :description], &{&1, Map.get(type.meta, &1)}) |
| 71 | + end |
| 72 | + |
| 73 | + defp type_common_fields(type) do |
| 74 | + Enum.map([:coerce, :strict], &{&1, Map.get(type, &1)}) |
| 75 | + end |
| 76 | + |
| 77 | + defp inspect_name(type) do |
| 78 | + modules = type.__struct__ |> Module.split() |
| 79 | + |
| 80 | + case modules do |
| 81 | + ["Zoi", "ISO", name] -> "ISO." <> Macro.underscore(name) |
| 82 | + list -> List.last(list) |> Macro.underscore() |
| 83 | + end |
| 84 | + end |
| 85 | + |
| 86 | + defp inspect_array(type, inspect_opts, opts) do |
| 87 | + opts = add_extra(opts, inner: inspect_type(type.inner, inspect_opts)) |
| 88 | + do_inspect_type(type, inspect_opts, opts) |
| 89 | + end |
| 90 | + |
| 91 | + defp inspect_default(type, inspect_opts, opts) do |
| 92 | + opts = add_extra(opts, default: type.value) |
| 93 | + inspect_type(type.inner, inspect_opts, opts) |
| 94 | + end |
| 95 | + |
| 96 | + defp inspect_enum(type, inspect_opts, opts) do |
| 97 | + symetric_enum? = Enum.all?(type.values, fn {key, value} -> key == value end) |
| 98 | + |
| 99 | + opts = |
| 100 | + if symetric_enum? do |
| 101 | + # since key equals value, we can just show the values |
| 102 | + add_extra(opts, |
| 103 | + values: Enum.map(type.values, fn {_key, value} -> value end) |
| 104 | + ) |
| 105 | + else |
| 106 | + add_extra(opts, values: type.values) |
| 107 | + end |
| 108 | + |
| 109 | + do_inspect_type(type, inspect_opts, opts) |
| 110 | + end |
| 111 | + |
| 112 | + defp inspect_intersection(type, inspect_opts, opts) do |
| 113 | + schemas_docs = |
| 114 | + container_doc("[", type.schemas, "]", %Inspect.Opts{limit: 5}, fn |
| 115 | + schema, _opts -> inspect_type(schema, inspect_opts) |
| 116 | + end) |
| 117 | + |
| 118 | + opts = |
| 119 | + add_extra(opts, schemas: schemas_docs) |
| 120 | + |
| 121 | + do_inspect_type(type, inspect_opts, opts) |
| 122 | + end |
| 123 | + |
| 124 | + defp inspect_literal(type, inspect_opts, opts) do |
| 125 | + opts = |
| 126 | + add_extra(opts, value: type.value) |
| 127 | + |
| 128 | + do_inspect_type(type, inspect_opts, opts) |
| 129 | + end |
| 130 | + |
| 131 | + defp inspect_map(type, inspect_opts, opts) do |
| 132 | + opts = |
| 133 | + add_extra(opts, |
| 134 | + key: inspect_type(type.key_type, inspect_opts), |
| 135 | + value: inspect_type(type.value_type, inspect_opts) |
| 136 | + ) |
| 137 | + |
| 138 | + do_inspect_type(type, inspect_opts, opts) |
| 139 | + end |
| 140 | + |
| 141 | + defp inspect_object(type, inspect_opts, opts) do |
| 142 | + fields_docs = |
| 143 | + container_doc("%{", type.fields, "}", %Inspect.Opts{limit: 10}, fn |
| 144 | + {key, schema}, _opts -> concat("#{key}: ", inspect_type(schema, inspect_opts)) |
| 145 | + end) |
| 146 | + |
| 147 | + opts = add_extra(opts, fields: fields_docs) |
| 148 | + |
| 149 | + do_inspect_type(type, inspect_opts, opts) |
| 150 | + end |
| 151 | + |
| 152 | + defp inspect_keyword(type, inspect_opts, opts) do |
| 153 | + fields_docs = |
| 154 | + case type.fields do |
| 155 | + fields when is_list(fields) -> |
| 156 | + container_doc("[", fields, "]", %Inspect.Opts{limit: 10}, fn |
| 157 | + {key, schema}, _opts -> concat("#{key}: ", inspect_type(schema, inspect_opts)) |
| 158 | + end) |
| 159 | + |
| 160 | + schema_type -> |
| 161 | + inspect_type(schema_type, inspect_opts) |
| 162 | + end |
| 163 | + |
| 164 | + opts = add_extra(opts, fields: fields_docs) |
| 165 | + |
| 166 | + do_inspect_type(type, inspect_opts, opts) |
| 167 | + end |
| 168 | + |
| 169 | + defp inspect_string_boolean(type, inspect_opts, opts) do |
| 170 | + extra_fields = |
| 171 | + Enum.map([:case, :truthy, :falsy], fn field -> |
| 172 | + {field, Map.get(type, field)} |
| 173 | + end) |
| 174 | + |
| 175 | + opts = add_extra(opts, extra_fields) |
| 176 | + do_inspect_type(type, inspect_opts, opts) |
| 177 | + end |
| 178 | + |
| 179 | + defp inspect_struct(type, inspect_opts, opts) do |
| 180 | + fields_docs = |
| 181 | + container_doc("%{", type.fields, "}", %Inspect.Opts{limit: 10}, fn |
| 182 | + {key, schema}, _opts -> concat("#{key}: ", inspect_type(schema, inspect_opts)) |
| 183 | + end) |
| 184 | + |
| 185 | + opts = add_extra(opts, fields: fields_docs, module: type.module) |
| 186 | + |
| 187 | + do_inspect_type(type, inspect_opts, opts) |
| 188 | + end |
| 189 | + |
| 190 | + defp inspect_tuple(type, inspect_opts, opts) do |
| 191 | + fields_docs = |
| 192 | + container_doc("{", type.fields, "}", %Inspect.Opts{limit: 10}, fn |
| 193 | + field, _opts -> inspect_type(field, inspect_opts) |
| 194 | + end) |
| 195 | + |
| 196 | + opts = add_extra(opts, fields: fields_docs) |
| 197 | + |
| 198 | + do_inspect_type(type, inspect_opts, opts) |
| 199 | + end |
| 200 | + |
| 201 | + defp inspect_union(type, inspect_opts, opts) do |
| 202 | + result = |
| 203 | + container_doc("[", type.schemas, "]", %Inspect.Opts{limit: 5}, fn |
| 204 | + schema, _opts -> inspect_type(schema, inspect_opts) |
| 205 | + end) |
| 206 | + |
| 207 | + opts = |
| 208 | + add_extra(opts, schemas: result) |
| 209 | + |
| 210 | + do_inspect_type(type, inspect_opts, opts) |
| 211 | + end |
| 212 | + |
| 213 | + defp add_extra(opts, opts_to_add) do |
| 214 | + Keyword.update(opts, :extra_fields, opts_to_add, fn existing -> |
| 215 | + existing ++ opts_to_add |
| 216 | + end) |
| 217 | + end |
| 218 | + end |
| @@ -30,4 +30,10 @@ defmodule Zoi.ISO.Date do | |
| 30 30 | quote(do: binary()) |
| 31 31 | end |
| 32 32 | end |
| 33 | + |
| 34 | + defimpl Inspect do |
| 35 | + def inspect(type, opts) do |
| 36 | + Zoi.Inspect.inspect_type(type, opts) |
| 37 | + end |
| 38 | + end |
| 33 39 | end |
| @@ -30,4 +30,10 @@ defmodule Zoi.ISO.DateTime do | |
| 30 30 | quote(do: binary()) |
| 31 31 | end |
| 32 32 | end |
| 33 | + |
| 34 | + defimpl Inspect do |
| 35 | + def inspect(type, opts) do |
| 36 | + Zoi.Inspect.inspect_type(type, opts) |
| 37 | + end |
| 38 | + end |
| 33 39 | end |
Loading more files…