@zio.dev/zio-blocks 0.0.27 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guides/query-dsl-extending.md +1 -1
- package/guides/query-dsl-fluent-builder.md +1 -1
- package/guides/query-dsl-reified-optics.md +1 -1
- package/guides/query-dsl-sql.md +1 -1
- package/guides/zio-schema-migration.md +6 -6
- package/index.md +18 -12
- package/package.json +1 -1
- package/reference/allows.md +1059 -34
- package/reference/binding-resolver.md +469 -0
- package/reference/binding.md +1 -1
- package/reference/codec.md +7 -7
- package/reference/docs.md +1 -1
- package/reference/dynamic-optic.md +5 -0
- package/reference/dynamic-schema.md +602 -0
- package/reference/dynamic-value.md +5 -0
- package/reference/json-patch.md +803 -0
- package/reference/json.md +1 -1
- package/reference/media-type.md +2 -2
- package/reference/patch.md +4 -0
- package/reference/schema-evolution/as.md +587 -0
- package/reference/schema-evolution/index.md +50 -0
- package/reference/schema-evolution/into.md +1027 -0
- package/reference/schema-expr.md +2 -2
- package/reference/structural-types.md +369 -0
- package/reference/type-class-derivation.md +1 -1
- package/reference/xml.md +606 -45
- package/scope.md +91 -9
- package/sidebars.js +19 -1
- package/reference/schema-evolution.md +0 -540
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: binding-resolver
|
|
3
|
+
title: "BindingResolver"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`BindingResolver` is the **read-only interface for looking up bindings by type identity** during schema rebinding. Given a type `A`, a resolver searches its internal storage and returns either the matching `Binding` or `None`.
|
|
7
|
+
|
|
8
|
+
```scala
|
|
9
|
+
trait BindingResolver {
|
|
10
|
+
def resolveRecord[A](implicit typeId: TypeId[A]): Option[Binding.Record[A]]
|
|
11
|
+
def resolveVariant[A](implicit typeId: TypeId[A]): Option[Binding.Variant[A]]
|
|
12
|
+
def resolvePrimitive[A](implicit typeId: TypeId[A]): Option[Binding.Primitive[A]]
|
|
13
|
+
def resolveWrapper[A](implicit typeId: TypeId[A]): Option[Binding.Wrapper[A, _]]
|
|
14
|
+
def resolveDynamic(implicit typeId: TypeId[DynamicValue]): Option[Binding.Dynamic]
|
|
15
|
+
def resolveSeq[X](implicit typeId: TypeId[X], u: UnapplySeq[X]): Option[Binding.Seq[u.C, u.A]]
|
|
16
|
+
def resolveSeqFor[C[_], A](typeId: TypeId[C[A]]): Option[Binding.Seq[C, A]]
|
|
17
|
+
def resolveMap[X](implicit typeId: TypeId[X], u: UnapplyMap[X]): Option[Binding.Map[u.M, u.K, u.V]]
|
|
18
|
+
def resolveMapFor[M[_, _], K, V](typeId: TypeId[M[K, V]]): Option[Binding.Map[M, K, V]]
|
|
19
|
+
final def ++(that: BindingResolver): BindingResolver
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`BindingResolver`:
|
|
24
|
+
|
|
25
|
+
- Uses `TypeId` as the lookup key for all resolution methods.
|
|
26
|
+
- Stores sequence and map bindings by their *unapplied type constructor*, so one `List` binding covers `List[Int]`, `List[String]`, and any other element type.
|
|
27
|
+
- Composes via `BindingResolver#++` with left-biased precedence: the left resolver is tried first, the right serves as fallback.
|
|
28
|
+
|
|
29
|
+
## Motivation
|
|
30
|
+
|
|
31
|
+
ZIO Blocks separates schema **structure** from schema **behavior**:
|
|
32
|
+
|
|
33
|
+
- A `Reflect.Unbound[A]` carries only structural metadata—field names, type names, documentation. It contains no Scala functions and is fully serializable.
|
|
34
|
+
- A `Reflect.Bound[A]` pairs each structural node with its `Binding`, enabling actual construction and deconstruction of values.
|
|
35
|
+
|
|
36
|
+
This separation powers a key workflow: serialize a schema as a `DynamicSchema` (unbound), transmit or store it, then **rebind** it on the other side using a `BindingResolver` to recover a fully operational `Schema[A]`.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Schema[Person]
|
|
40
|
+
│ toDynamicSchema
|
|
41
|
+
▼
|
|
42
|
+
DynamicSchema ◄── serializable, no Scala functions
|
|
43
|
+
(Reflect.Unbound[_])
|
|
44
|
+
│ rebind[Person](resolver)
|
|
45
|
+
▼
|
|
46
|
+
Schema[Person] ◄── operational, can encode and decode
|
|
47
|
+
(Reflect.Bound[Person])
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The `BindingResolver` is what bridges the final step: it supplies the `Binding` for each node in the unbound reflect tree.
|
|
51
|
+
|
|
52
|
+
A minimal end-to-end example:
|
|
53
|
+
|
|
54
|
+
```scala
|
|
55
|
+
import zio.blocks.schema._
|
|
56
|
+
import zio.blocks.schema.binding._
|
|
57
|
+
|
|
58
|
+
case class Person(name: String, age: Int)
|
|
59
|
+
|
|
60
|
+
object Person {
|
|
61
|
+
implicit val schema: Schema[Person] = Schema.derived[Person]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
val dynamic: DynamicSchema = Schema[Person].toDynamicSchema
|
|
65
|
+
|
|
66
|
+
val resolver: BindingResolver =
|
|
67
|
+
BindingResolver.empty.bind(Binding.of[Person]) ++ BindingResolver.defaults
|
|
68
|
+
|
|
69
|
+
val rebound: Schema[Person] = dynamic.rebind[Person](resolver)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Predefined Resolvers
|
|
73
|
+
|
|
74
|
+
ZIO Blocks ships three ready-made resolvers. We almost always compose them with `BindingResolver#++` rather than using them in isolation.
|
|
75
|
+
|
|
76
|
+
### `BindingResolver.empty`
|
|
77
|
+
|
|
78
|
+
`BindingResolver.empty` is an empty `Registry` with no bindings. It is the starting point for building a custom registry with `Registry#bind`:
|
|
79
|
+
|
|
80
|
+
```scala
|
|
81
|
+
import zio.blocks.schema.binding._
|
|
82
|
+
|
|
83
|
+
val empty: BindingResolver.Registry = BindingResolver.empty
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `BindingResolver.defaults`
|
|
87
|
+
|
|
88
|
+
`BindingResolver.defaults` is a pre-populated `Registry` covering all primitive types, `java.time` types, `java.util.UUID`, `java.util.Currency`, `DynamicValue`, common sequence types (`List`, `Vector`, `Set`, `IndexedSeq`, `Seq`, `Chunk`), and `Map`. In practice we place it at the right end of a `BindingResolver#++` chain so custom bindings can override it when needed.
|
|
89
|
+
|
|
90
|
+
The types covered by `defaults` include:
|
|
91
|
+
|
|
92
|
+
| Category | Types |
|
|
93
|
+
|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
94
|
+
| Primitives | `Unit`, `Boolean`, `Byte`, `Short`, `Int`, `Long`, `Float`, `Double`, `Char`, `String`, `BigInt`, `BigDecimal` |
|
|
95
|
+
| `java.time` | `DayOfWeek`, `Duration`, `Instant`, `LocalDate`, `LocalDateTime`, `LocalTime`, `Month`, `MonthDay`, `OffsetDateTime`, `OffsetTime`, `Period`, `Year`, `YearMonth`, `ZoneId`, `ZoneOffset`, `ZonedDateTime` |
|
|
96
|
+
| `java.util` | `Currency`, `UUID` |
|
|
97
|
+
| Dynamic | `DynamicValue` |
|
|
98
|
+
| Sequences | `List`, `Vector`, `Set`, `IndexedSeq`, `Seq`, `Chunk` |
|
|
99
|
+
| Maps | `Map` |
|
|
100
|
+
|
|
101
|
+
```scala
|
|
102
|
+
import zio.blocks.schema.binding._
|
|
103
|
+
|
|
104
|
+
val defaults: BindingResolver.Registry = BindingResolver.defaults
|
|
105
|
+
|
|
106
|
+
defaults.resolvePrimitive[Int] // Some(...)
|
|
107
|
+
defaults.resolvePrimitive[java.time.Instant] // Some(...)
|
|
108
|
+
defaults.resolveSeq[List[Int]] // Some(...)
|
|
109
|
+
defaults.resolveMap[Map[String, Int]] // Some(...)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `BindingResolver.reflection` (JVM only)
|
|
113
|
+
|
|
114
|
+
`BindingResolver.reflection` derives `Binding.Record` instances at runtime using Java reflection for case classes. Derived bindings are cached per `TypeId` in a `ConcurrentHashMap`, so the reflection cost is paid only once per type.
|
|
115
|
+
|
|
116
|
+
On Scala.js, `BindingResolver.reflection` is a no-op resolver that returns `None` for every query.
|
|
117
|
+
|
|
118
|
+
```scala
|
|
119
|
+
import zio.blocks.schema._
|
|
120
|
+
import zio.blocks.schema.binding._
|
|
121
|
+
|
|
122
|
+
case class Order(id: Long, item: String, quantity: Int)
|
|
123
|
+
|
|
124
|
+
object Order {
|
|
125
|
+
implicit val schema: Schema[Order] = Schema.derived[Order]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// reflection handles the record; defaults handles Long, String, Int
|
|
129
|
+
val resolver: BindingResolver = BindingResolver.reflection ++ BindingResolver.defaults
|
|
130
|
+
|
|
131
|
+
val rebound: Schema[Order] = Schema[Order].toDynamicSchema.rebind[Order](resolver)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
:::warning
|
|
135
|
+
`BindingResolver.reflection` only derives `Binding.Record` for case classes. It returns `None` for primitives, variants, wrappers, sequences, and maps. Always compose it with `BindingResolver.defaults` to cover those types.
|
|
136
|
+
:::
|
|
137
|
+
|
|
138
|
+
## Building a Registry
|
|
139
|
+
|
|
140
|
+
`BindingResolver.Registry` is an immutable, map-backed resolver. Every `Registry#bind` call returns a **new** `Registry` with the binding added; the original registry is unchanged.
|
|
141
|
+
|
|
142
|
+
### `Registry#bind` for proper types
|
|
143
|
+
|
|
144
|
+
The unified `Registry#bind` method accepts any proper binding—`Record`, `Variant`, `Primitive`, `Wrapper`, or `Dynamic`—and dispatches to the correct internal storage slot automatically. We typically call `Binding.of[A]` to derive the right binding at compile time:
|
|
145
|
+
|
|
146
|
+
```scala
|
|
147
|
+
import zio.blocks.schema._
|
|
148
|
+
import zio.blocks.schema.binding._
|
|
149
|
+
|
|
150
|
+
sealed trait Color
|
|
151
|
+
case object Red extends Color
|
|
152
|
+
case object Blue extends Color
|
|
153
|
+
|
|
154
|
+
object Color {
|
|
155
|
+
implicit val schema: Schema[Color] = Schema.derived[Color]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
case class Palette(primary: Color, name: String)
|
|
159
|
+
|
|
160
|
+
object Palette {
|
|
161
|
+
implicit val schema: Schema[Palette] = Schema.derived[Palette]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
val registry: BindingResolver.Registry =
|
|
165
|
+
BindingResolver.empty
|
|
166
|
+
.bind(Binding.of[Color]) // derives Binding.Variant[Color]
|
|
167
|
+
.bind(Binding.of[Palette]) // derives Binding.Record[Palette]
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`Binding.of[A]` is a compile-time macro that selects the appropriate binding kind based on the type of `A`:
|
|
171
|
+
|
|
172
|
+
- Case class → `Binding.Record`
|
|
173
|
+
- Sealed trait or enum → `Binding.Variant`
|
|
174
|
+
- Scalar type (`Int`, `String`, …) → `Binding.Primitive`
|
|
175
|
+
- Single-field wrapper with a smart constructor → `Binding.Wrapper`
|
|
176
|
+
|
|
177
|
+
:::warning
|
|
178
|
+
Passing a `Binding.Seq` or `Binding.Map` to the unified `Registry#bind` method throws `IllegalArgumentException` at runtime. Use the specialized overloads shown below for collection types.
|
|
179
|
+
:::
|
|
180
|
+
|
|
181
|
+
### `Registry#bind` for sequence types
|
|
182
|
+
|
|
183
|
+
Sequence bindings are keyed by their unapplied type constructor. We supply `[C[_]]` as the explicit type parameter so the compiler uses the constructor—not a specific applied type—as the lookup key:
|
|
184
|
+
|
|
185
|
+
```scala
|
|
186
|
+
import zio.blocks.schema.binding._
|
|
187
|
+
|
|
188
|
+
val registry: BindingResolver.Registry =
|
|
189
|
+
BindingResolver.empty.bind[List](Binding.Seq.list[Nothing])
|
|
190
|
+
|
|
191
|
+
// One binding resolves any element type
|
|
192
|
+
registry.resolveSeq[List[Int]] // Some(...)
|
|
193
|
+
registry.resolveSeq[List[String]] // Some(...)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### `Registry#bind` for map types
|
|
197
|
+
|
|
198
|
+
Map bindings follow the same pattern with `[M[_, _]]` as the type parameter:
|
|
199
|
+
|
|
200
|
+
```scala
|
|
201
|
+
import zio.blocks.schema.binding._
|
|
202
|
+
|
|
203
|
+
val registry: BindingResolver.Registry =
|
|
204
|
+
BindingResolver.empty.bind[Map](Binding.Map.map[Nothing, Nothing])
|
|
205
|
+
|
|
206
|
+
registry.resolveMap[Map[String, Int]] // Some(...)
|
|
207
|
+
registry.resolveMap[Map[Int, String]] // Some(...)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Combining Resolvers
|
|
211
|
+
|
|
212
|
+
The `BindingResolver#++` operator composes two resolvers into a left-biased fallback chain. The left resolver is consulted first; the right is used only when the left returns `None`.
|
|
213
|
+
|
|
214
|
+
```scala
|
|
215
|
+
trait BindingResolver {
|
|
216
|
+
final def ++(that: BindingResolver): BindingResolver
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
This makes it straightforward to layer custom bindings over the built-in defaults:
|
|
221
|
+
|
|
222
|
+
```scala
|
|
223
|
+
import zio.blocks.schema._
|
|
224
|
+
import zio.blocks.schema.binding._
|
|
225
|
+
|
|
226
|
+
case class UserId(value: Long)
|
|
227
|
+
|
|
228
|
+
object UserId {
|
|
229
|
+
implicit val schema: Schema[UserId] =
|
|
230
|
+
Schema[Long].transform(UserId(_), _.value)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Custom bindings shadow matching entries in defaults
|
|
234
|
+
val resolver: BindingResolver =
|
|
235
|
+
BindingResolver.empty.bind(Binding.of[UserId]) ++ BindingResolver.defaults
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`BindingResolver#++` is associative for resolution outcomes: `(a ++ b) ++ c` and `a ++ (b ++ c)` always resolve to the same binding for any type.
|
|
239
|
+
|
|
240
|
+
## Resolution Methods
|
|
241
|
+
|
|
242
|
+
All `resolve*` methods return an `Option` and never throw. They return `None` when no binding is registered for the requested type.
|
|
243
|
+
|
|
244
|
+
### `BindingResolver#resolveRecord`
|
|
245
|
+
|
|
246
|
+
`BindingResolver#resolveRecord` returns the `Binding.Record` for a product type (case class, tuple, module object):
|
|
247
|
+
|
|
248
|
+
```scala
|
|
249
|
+
trait BindingResolver {
|
|
250
|
+
def resolveRecord[A](implicit typeId: TypeId[A]): Option[Binding.Record[A]]
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The `TypeId[A]` witness is satisfied implicitly; we only need to supply the type parameter:
|
|
255
|
+
|
|
256
|
+
```scala
|
|
257
|
+
import zio.blocks.schema._
|
|
258
|
+
import zio.blocks.schema.binding._
|
|
259
|
+
|
|
260
|
+
case class Point(x: Double, y: Double)
|
|
261
|
+
|
|
262
|
+
object Point {
|
|
263
|
+
implicit val schema: Schema[Point] = Schema.derived[Point]
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
val registry = BindingResolver.empty.bind(Binding.of[Point])
|
|
267
|
+
val binding: Option[Binding.Record[Point]] = registry.resolveRecord[Point]
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### `BindingResolver#resolveVariant`
|
|
271
|
+
|
|
272
|
+
`BindingResolver#resolveVariant` returns the `Binding.Variant` for a sum type (sealed trait, Scala 3 enum):
|
|
273
|
+
|
|
274
|
+
```scala
|
|
275
|
+
trait BindingResolver {
|
|
276
|
+
def resolveVariant[A](implicit typeId: TypeId[A]): Option[Binding.Variant[A]]
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```scala
|
|
281
|
+
import zio.blocks.schema._
|
|
282
|
+
import zio.blocks.schema.binding._
|
|
283
|
+
|
|
284
|
+
sealed trait Shape
|
|
285
|
+
case class Circle(radius: Double) extends Shape
|
|
286
|
+
case class Rectangle(w: Double, h: Double) extends Shape
|
|
287
|
+
|
|
288
|
+
object Shape {
|
|
289
|
+
implicit val schema: Schema[Shape] = Schema.derived[Shape]
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
val registry = BindingResolver.empty.bind(Binding.of[Shape])
|
|
293
|
+
val binding: Option[Binding.Variant[Shape]] = registry.resolveVariant[Shape]
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### `BindingResolver#resolvePrimitive`
|
|
297
|
+
|
|
298
|
+
`BindingResolver#resolvePrimitive` returns the `Binding.Primitive` for scalar types such as `Int`, `String`, `java.time.Instant`, and `java.util.UUID`:
|
|
299
|
+
|
|
300
|
+
```scala
|
|
301
|
+
trait BindingResolver {
|
|
302
|
+
def resolvePrimitive[A](implicit typeId: TypeId[A]): Option[Binding.Primitive[A]]
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
```scala
|
|
307
|
+
import zio.blocks.schema.binding._
|
|
308
|
+
|
|
309
|
+
val binding: Option[Binding.Primitive[Int]] = BindingResolver.defaults.resolvePrimitive[Int]
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### `BindingResolver#resolveWrapper`
|
|
313
|
+
|
|
314
|
+
`BindingResolver#resolveWrapper` returns the `Binding.Wrapper` for newtype patterns—single-field case classes and smart-constructor wrappers. The binding holds a `wrap: B => A` and an `unwrap: A => B` function, converting between the inner type `B` and the outer type `A`:
|
|
315
|
+
|
|
316
|
+
```scala
|
|
317
|
+
trait BindingResolver {
|
|
318
|
+
def resolveWrapper[A](implicit typeId: TypeId[A]): Option[Binding.Wrapper[A, _]]
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
```scala
|
|
323
|
+
import zio.blocks.schema._
|
|
324
|
+
import zio.blocks.schema.binding._
|
|
325
|
+
|
|
326
|
+
case class Email(value: String)
|
|
327
|
+
|
|
328
|
+
object Email {
|
|
329
|
+
implicit val schema: Schema[Email] =
|
|
330
|
+
Schema[String].transform(Email(_), _.value)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
val registry = BindingResolver.empty.bind(Binding.of[Email])
|
|
334
|
+
val binding: Option[Binding.Wrapper[Email, _]] = registry.resolveWrapper[Email]
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### `BindingResolver#resolveDynamic`
|
|
338
|
+
|
|
339
|
+
`BindingResolver#resolveDynamic` returns the `Binding.Dynamic` singleton, used for `DynamicValue` nodes in the reflect tree. `BindingResolver.defaults` already includes it, so manual registration is rarely needed:
|
|
340
|
+
|
|
341
|
+
```scala
|
|
342
|
+
trait BindingResolver {
|
|
343
|
+
def resolveDynamic(implicit typeId: TypeId[DynamicValue]): Option[Binding.Dynamic]
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
```scala
|
|
348
|
+
import zio.blocks.schema.binding._
|
|
349
|
+
|
|
350
|
+
val binding: Option[Binding.Dynamic] = BindingResolver.defaults.resolveDynamic
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### `BindingResolver#resolveSeq` and `BindingResolver#resolveSeqFor`
|
|
354
|
+
|
|
355
|
+
`BindingResolver#resolveSeq` uses `UnapplySeq` evidence to decompose an applied type like `List[Int]` into its constructor `List` and element type `Int`, then looks up the binding by constructor. `BindingResolver#resolveSeqFor` is the explicit variant when the constructor and element types are already known as separate parameters:
|
|
356
|
+
|
|
357
|
+
```scala
|
|
358
|
+
trait BindingResolver {
|
|
359
|
+
def resolveSeq[X](implicit typeId: TypeId[X], u: UnapplySeq[X]): Option[Binding.Seq[u.C, u.A]]
|
|
360
|
+
def resolveSeqFor[C[_], A](typeId: TypeId[C[A]]): Option[Binding.Seq[C, A]]
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Because the binding is stored by type constructor, a single registered `List` binding handles any element type:
|
|
365
|
+
|
|
366
|
+
```scala
|
|
367
|
+
import zio.blocks.schema.binding._
|
|
368
|
+
import zio.blocks.typeid.TypeId
|
|
369
|
+
|
|
370
|
+
val defaults = BindingResolver.defaults
|
|
371
|
+
|
|
372
|
+
val listIntBinding: Option[Binding.Seq[List, Int]] = defaults.resolveSeq[List[Int]]
|
|
373
|
+
val listStrBinding: Option[Binding.Seq[List, String]] = defaults.resolveSeq[List[String]]
|
|
374
|
+
|
|
375
|
+
// Explicit form using resolveSeqFor
|
|
376
|
+
val explicit: Option[Binding.Seq[List, Int]] =
|
|
377
|
+
defaults.resolveSeqFor[List, Int](TypeId.of[List[Int]])
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### `BindingResolver#resolveMap` and `BindingResolver#resolveMapFor`
|
|
381
|
+
|
|
382
|
+
`BindingResolver#resolveMap` and `BindingResolver#resolveMapFor` follow the same pattern as their sequence counterparts, applied to key-value collection types:
|
|
383
|
+
|
|
384
|
+
```scala
|
|
385
|
+
trait BindingResolver {
|
|
386
|
+
def resolveMap[X](implicit typeId: TypeId[X], u: UnapplyMap[X]): Option[Binding.Map[u.M, u.K, u.V]]
|
|
387
|
+
def resolveMapFor[M[_, _], K, V](typeId: TypeId[M[K, V]]): Option[Binding.Map[M, K, V]]
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
```scala
|
|
392
|
+
import zio.blocks.schema.binding._
|
|
393
|
+
|
|
394
|
+
val binding: Option[Binding.Map[Map, String, Int]] =
|
|
395
|
+
BindingResolver.defaults.resolveMap[Map[String, Int]]
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Registry Inspection
|
|
399
|
+
|
|
400
|
+
`Registry` exposes several methods to interrogate its state without performing a full resolution. `Registry#contains` checks whether a proper binding (Record, Variant, Primitive, Wrapper, or Dynamic) is registered for type `A`. `Registry#containsSeq` and `Registry#containsMap` check for sequence and map type constructors respectively:
|
|
401
|
+
|
|
402
|
+
```scala
|
|
403
|
+
final class Registry {
|
|
404
|
+
def contains[A](implicit typeId: TypeId[A]): Boolean
|
|
405
|
+
def containsSeq[X](implicit typeId: TypeId[X], u: UnapplySeq[X]): Boolean
|
|
406
|
+
def containsMap[X](implicit typeId: TypeId[X], u: UnapplyMap[X]): Boolean
|
|
407
|
+
def size: Int
|
|
408
|
+
def isEmpty: Boolean
|
|
409
|
+
def nonEmpty: Boolean
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
```scala
|
|
414
|
+
import zio.blocks.schema.binding._
|
|
415
|
+
|
|
416
|
+
val registry = BindingResolver.defaults
|
|
417
|
+
|
|
418
|
+
registry.contains[Int] // true — primitive binding exists
|
|
419
|
+
registry.containsSeq[List[String]] // true — List constructor is bound
|
|
420
|
+
registry.containsMap[Map[Int, Int]] // true — Map constructor is bound
|
|
421
|
+
registry.size // total number of registered bindings
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## Integration with `DynamicSchema`
|
|
425
|
+
|
|
426
|
+
The primary consumer of `BindingResolver` is `DynamicSchema#rebind`. Given an unbound `DynamicSchema`, `DynamicSchema#rebind` walks the `Reflect` tree and queries the resolver for each node's binding, then returns a fully operational `Schema[A]`:
|
|
427
|
+
|
|
428
|
+
```scala
|
|
429
|
+
import zio.blocks.schema._
|
|
430
|
+
import zio.blocks.schema.binding._
|
|
431
|
+
|
|
432
|
+
case class Product(sku: String, price: Double, tags: List[String])
|
|
433
|
+
|
|
434
|
+
object Product {
|
|
435
|
+
implicit val schema: Schema[Product] = Schema.derived[Product]
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
val dynamic: DynamicSchema = Schema[Product].toDynamicSchema
|
|
439
|
+
|
|
440
|
+
// The resolver must cover every concrete type that appears in the schema tree
|
|
441
|
+
val resolver: BindingResolver =
|
|
442
|
+
BindingResolver.empty.bind(Binding.of[Product]) ++ BindingResolver.defaults
|
|
443
|
+
|
|
444
|
+
val rebound: Schema[Product] = dynamic.rebind[Product](resolver)
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
When using `BindingResolver.reflection` on the JVM, individual record types do not need to be registered explicitly—the resolver derives their bindings on demand from the class structure:
|
|
448
|
+
|
|
449
|
+
```scala
|
|
450
|
+
import zio.blocks.schema._
|
|
451
|
+
import zio.blocks.schema.binding._
|
|
452
|
+
|
|
453
|
+
case class Address(street: String, city: String, zip: String)
|
|
454
|
+
|
|
455
|
+
object Address {
|
|
456
|
+
implicit val schema: Schema[Address] = Schema.derived[Address]
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// No explicit bind(Binding.of[Address]) needed on JVM
|
|
460
|
+
val resolver: BindingResolver = BindingResolver.reflection ++ BindingResolver.defaults
|
|
461
|
+
|
|
462
|
+
val rebound: Schema[Address] = Schema[Address].toDynamicSchema.rebind[Address](resolver)
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
:::warning
|
|
466
|
+
If `DynamicSchema#rebind` cannot find a binding for any type present in the unbound schema tree, it throws at runtime. Make sure the resolver covers every concrete type—records, variants, wrappers, primitives, and collections—that appears in the schema.
|
|
467
|
+
:::
|
|
468
|
+
|
|
469
|
+
See [Binding](./binding.md) for details on each binding kind, and [Schema](./schema.md) for the overall structure of the schema system.
|
package/reference/binding.md
CHANGED
|
@@ -339,7 +339,7 @@ When `F[_, _] = NoBinding` in `Reflect[F[_, _], A]` the `Reflect` structure cont
|
|
|
339
339
|
1. **Schema serialization**: Convert schemas to JSON Schema or other formats, making them portable
|
|
340
340
|
2. **Schema rebinding**: Deserialize a schema and rebind it using a `TypeRegistry`, so it becomes type-safe and operational again
|
|
341
341
|
|
|
342
|
-
We will cover schema serialization and rebinding in more detail in the `Reflect` data type documentation page.
|
|
342
|
+
We will cover schema serialization and rebinding in more detail in the `Reflect` data type documentation page. For the full API of the binding lookup mechanism used during rebinding, see [BindingResolver](./binding-resolver.md).
|
|
343
343
|
|
|
344
344
|
## Summary
|
|
345
345
|
|
package/reference/codec.md
CHANGED
|
@@ -48,23 +48,23 @@ val result: Either[SchemaError, Person] = Person.codec.decode(bytes)
|
|
|
48
48
|
To include the base schema module with JSON support, add the following dependency to your `build.sbt`:
|
|
49
49
|
|
|
50
50
|
```scala
|
|
51
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
51
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.28"
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
Additional format modules are separate artifacts:
|
|
55
55
|
|
|
56
56
|
```scala
|
|
57
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.
|
|
58
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.
|
|
59
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.
|
|
60
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.
|
|
61
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.
|
|
57
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.28"
|
|
58
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.28"
|
|
59
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.28"
|
|
60
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.28"
|
|
61
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.28"
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
For cross-platform projects (Scala.js):
|
|
65
65
|
|
|
66
66
|
```scala
|
|
67
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.
|
|
67
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.28"
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Supported Scala versions: 2.13.x and 3.x.
|
package/reference/docs.md
CHANGED
|
@@ -10,7 +10,7 @@ Complete API reference for the zio-blocks-docs module - a zero-dependency GitHub
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
12
12
|
```scala
|
|
13
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.
|
|
13
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.28"
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
## Core Types
|
|
@@ -394,3 +394,8 @@ val opticSchema: Schema[DynamicOptic] = Schema[DynamicOptic]
|
|
|
394
394
|
val path = p".users[0].name"
|
|
395
395
|
val serialized: DynamicValue = opticSchema.toDynamicValue(path)
|
|
396
396
|
```
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
## See Also
|
|
400
|
+
|
|
401
|
+
- [DynamicSchema](./dynamic-schema.md) — use `DynamicSchema#get` with a `DynamicOptic` to navigate schema trees and inspect nested type structures.
|