@@ -142,6 +142,30 @@ conform!(%{user: %{name: "chris", age: -31}}, user_schema)
142142 (norm) lib/ norm.ex: 44 : Norm .conform! / 2
143143```
144144
145+ Schema's are designed to allow systems to grow over time. They provide this
146+ functionality in two ways. The first is that any unspecified fields in the input
147+ are passed through when conforming the input. The second is that all keys in a
148+ schema are optional. This means that all of these are valid:
149+
150+ ``` elixir
151+ user_schema = schema (%{
152+ name: spec (is_binary ()),
153+ age: spec (is_integer ()),
154+ })
155+
156+ conform! (%{}, user_schema)
157+ => %{}
158+ conform! (%{age: 31 }, user_schema)
159+ => %{age: 31 }
160+ conform! (%{foo: :foo , bar: :bar }, user_schema)
161+ => %{foo: :foo , bar: :bar }
162+ ```
163+
164+ If you're used to more restrictive systems for managing data these might seem
165+ like odd choices. We'll see how to specify required keys when we discuss Selections.
166+
167+ #### Structs
168+
145169You can also create specs from structs:
146170
147171``` elixir
@@ -183,23 +207,12 @@ Schemas accomodate growth by disregarding any unspecified keys in the input map.
183207This allows callers to start sending new data over time without coordination
184208with the consuming function.
185209
186- ### Selections
210+ ### Selections and optionality
187211
188- You may have noticed that there's no way to specify optional keys in
189- a schema. This may seem like an oversight but its actually an intentional
190- design decision. Whether a key should be present in a schema is determined
191- by the call site and not by the schema itself. For instance think about
192- the assigns in a plug conn. When are the assigns optional? It depends on
193- where you are in the pipeline.
194-
195- Schemas also force all keys to match at all times. This is generally
196- useful as it limits your ability to introduce errors. But it also limits
197- schema growth and turns changes that should be non-breaking into breaking
198- changes.
199-
200- In order to support both of these scenarios Norm provides the
201- ` selection/2 ` function. ` selection/2 ` allows you to specify exactly the
202- keys you require from a schema at the place where you require them.
212+ We said that all of the fields in a schema are optional. In order to specify
213+ the keys that are required in a specific use case we can use a Selection. The
214+ Selections takes a schema and a list of keys - or keys to lists of keys - that
215+ must be present in the schema.
203216
204217``` elixir
205218user_schema = schema (%{
@@ -211,13 +224,33 @@ user_schema = schema(%{
211224just_age = selection (user_schema, [user: [:age ]])
212225
213226conform! (%{user: %{name: " chris" , age: 31 }}, just_age)
214- => %{user: %{age: 31 }}
227+ => %{user: %{age: 31 , name: " chris " }}
215228
216- # Selection also disregards unspecified keys
217- conform! (%{user: %{name: " chris" , age: 31 , unspecified: nil }, other_stuff: :foo }, just_age)
218- => %{user: %{age: 31 }}
229+ conform! (%{user: %{name: " chris" }}, just_age)
230+ ** (Norm .MismatchError ) Could not conform input:
231+ val: %{name: " chris" } in : :user / :age fails: :required
232+ (norm) lib/ norm.ex: 387 : Norm .conform! / 2
219233```
220234
235+ If you need to mark all fields in a schema as required you can elide the list
236+ of keys like so:
237+
238+ ``` elixir
239+ user_schema = schema (%{
240+ user: schema (%{
241+ name: spec (is_binary ()),
242+ age: spec (is_integer ()),
243+ })
244+ })
245+
246+ # Require all fields recursively
247+ conform! (%{user: %{name: " chris" , age: 31 }}, selection (user_schema))
248+ ```
249+
250+ Selections are an important tool because they give control over optionality
251+ back to the call site. This allows callers to determine what they actually need
252+ and makes schema's much more reusable.
253+
221254### Patterns
222255
223256Norm provides a way to specify alternative specs using the ` alt/1 `
@@ -363,9 +396,7 @@ working to make improvements.
363396Norm is being actively worked on. Any contributions are very welcome. Here is a
364397limited set of ideas that are coming soon.
365398
366- - [ ] Support generators for other primitive types (floats, etc.)
367399- [ ] More streamlined specification of keyword lists.
368- - [ ] selections shouldn't need a path if you just want to match all the keys in the schema
369400- [ ] Support "sets" of literal values
370401- [ ] specs for functions and anonymous functions
371402- [ ] easier way to do dispatch based on schema keys
0 commit comments