Current section

74 Versions

Jump to

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…