@zio.dev/zio-blocks 0.0.26 → 0.0.27
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 +1195 -0
- package/index.md +14 -11
- package/package.json +1 -1
- package/reference/allows.md +352 -0
- package/reference/codec.md +10 -10
- package/reference/docs.md +1 -1
- package/reference/media-type.md +2 -2
- package/reference/modifier.md +9 -9
- package/reference/schema-error.md +569 -0
- package/reference/schema-expr.md +2 -2
- package/reference/schema.md +29 -0
- package/reference/type-class-derivation.md +329 -324
- package/reference/validation.md +1 -1
- package/reference/xml.md +743 -0
- package/scope.md +150 -8
- package/sidebars.js +2 -0
|
@@ -0,0 +1,1195 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: zio-schema-migration
|
|
3
|
+
title: "Migrating from ZIO Schema to ZIO Blocks Schema"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
This guide helps you migrate an application that uses [ZIO Schema](https://github.com/zio/zio-schema) (version 1.x) to [ZIO Blocks Schema](https://github.com/zio/zio-blocks) (the schema module of ZIO Blocks). It covers the conceptual differences between the two libraries, provides a systematic mapping of data types, and shows how to rewrite the most common patterns in the idiomatic ZIO Blocks style.
|
|
7
|
+
|
|
8
|
+
**What we will cover:**
|
|
9
|
+
|
|
10
|
+
- Prerequisites and dependency changes
|
|
11
|
+
- The core architectural shift from `Schema[A]` as a sealed trait to `Schema[A]` as a thin wrapper over `Reflect[F, A]`
|
|
12
|
+
- Migrating schema definitions for primitives, records, enums, collections, optional values, and newtypes
|
|
13
|
+
- Replacing the annotation/modifier system
|
|
14
|
+
- Adapting codec derivation to the unified `Format + Deriver` model
|
|
15
|
+
- Replacing `DynamicValue` usage
|
|
16
|
+
- Migrating optics and accessor patterns
|
|
17
|
+
- Migrating diff, patch, and schema evolution patterns
|
|
18
|
+
- Handling types and features that no longer have a direct analogue
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
### Dependency Changes
|
|
25
|
+
|
|
26
|
+
Replace the ZIO Schema dependency group with ZIO Blocks Schema:
|
|
27
|
+
|
|
28
|
+
**Before (ZIO Schema 1.x):**
|
|
29
|
+
|
|
30
|
+
```scala
|
|
31
|
+
libraryDependencies += "dev.zio" %% "zio-schema" % "1.x.x"
|
|
32
|
+
libraryDependencies += "dev.zio" %% "zio-schema-derivation" % "1.x.x"
|
|
33
|
+
libraryDependencies += "dev.zio" %% "zio-schema-json" % "1.x.x"
|
|
34
|
+
libraryDependencies += "dev.zio" %% "zio-schema-protobuf" % "1.x.x"
|
|
35
|
+
libraryDependencies += "dev.zio" %% "zio-schema-avro" % "1.x.x"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**After (ZIO Blocks Schema):**
|
|
39
|
+
|
|
40
|
+
```scala
|
|
41
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.27"
|
|
42
|
+
// Optional codec modules:
|
|
43
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.27"
|
|
44
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.27"
|
|
45
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.27"
|
|
46
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.27"
|
|
47
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.27"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Key points:
|
|
51
|
+
- JSON codec support is now built into `zio-blocks-schema` — no separate JSON module.
|
|
52
|
+
- There is no separate `zio-blocks-schema-derivation` dependency; derivation is built in.
|
|
53
|
+
- The `scala-reflect` provided dependency (required in ZIO Schema for Scala 2) is still needed for Scala 2 macro derivation — add it the same way as before.
|
|
54
|
+
- ZIO Blocks Schema has **zero runtime dependency on ZIO itself**. You do not need `zio` on your classpath for schema operations.
|
|
55
|
+
|
|
56
|
+
### Package Rename
|
|
57
|
+
|
|
58
|
+
All imports change from `zio.schema` to `zio.blocks.schema`:
|
|
59
|
+
|
|
60
|
+
```scala
|
|
61
|
+
// Before
|
|
62
|
+
import zio.schema._
|
|
63
|
+
import zio.schema.annotation._
|
|
64
|
+
import zio.schema.codec._
|
|
65
|
+
import zio.schema.meta._
|
|
66
|
+
|
|
67
|
+
// After
|
|
68
|
+
import zio.blocks.schema._
|
|
69
|
+
import zio.blocks.schema.binding._
|
|
70
|
+
import zio.blocks.schema.derive._
|
|
71
|
+
import zio.blocks.schema.patch._
|
|
72
|
+
import zio.blocks.schema.json._
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## The Core Architecture Shift
|
|
78
|
+
|
|
79
|
+
The most important thing to understand when migrating is that `Schema[A]` is no longer a sealed trait hierarchy — it is a thin case class:
|
|
80
|
+
|
|
81
|
+
```scala
|
|
82
|
+
// ZIO Schema 1.x: Schema is a sealed trait with ~20 concrete cases
|
|
83
|
+
sealed trait Schema[A] {
|
|
84
|
+
def annotations: Chunk[Any]
|
|
85
|
+
def defaultValue: Either[String, A]
|
|
86
|
+
// ...
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ZIO Blocks Schema: Schema is a case class wrapping Reflect
|
|
90
|
+
final case class Schema[A](reflect: Reflect.Bound[A])
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The structural description lives in `Reflect[F[_, _], A]`, a sealed trait with eight node types. The `F` type parameter distinguishes a *bound* reflect (with runtime constructors and deconstructors) from an *unbound* one (structural information only):
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
Reflect[F, A]
|
|
97
|
+
├── Reflect.Record[F, A] — case classes and other product types
|
|
98
|
+
├── Reflect.Variant[F, A] — sealed traits, enums, Option, Either
|
|
99
|
+
├── Reflect.Sequence[F, A, C[_]] — List, Vector, Set, Chunk, etc.
|
|
100
|
+
├── Reflect.Map[F, K, V, M[_,_]] — Map[K, V]
|
|
101
|
+
├── Reflect.Primitive[F, A] — Int, String, UUID, java.time.*, etc.
|
|
102
|
+
├── Reflect.Wrapper[F, A, B] — opaque types and validated newtypes
|
|
103
|
+
├── Reflect.Dynamic[F] — escape hatch for schema-agnostic data
|
|
104
|
+
└── Reflect.Deferred[F, A] — recursive (self-referential) types
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
As a result, you will rarely pattern match on `Schema[A]` directly — instead, you work through `schema.reflect` when you need to inspect structure.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Migrating Schema Definitions
|
|
112
|
+
|
|
113
|
+
### Schema Derivation
|
|
114
|
+
|
|
115
|
+
Automatic derivation syntax is essentially unchanged:
|
|
116
|
+
|
|
117
|
+
```scala
|
|
118
|
+
// ZIO Schema 1.x — Scala 2: type inferred from ascription; Scala 3: type param required
|
|
119
|
+
import zio.schema._
|
|
120
|
+
final case class Person(name: String, age: Int)
|
|
121
|
+
object Person {
|
|
122
|
+
implicit val schema: Schema[Person] = DeriveSchema.gen[Person]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ZIO Blocks Schema — identical call in Scala 2 and Scala 3
|
|
126
|
+
import zio.blocks.schema._
|
|
127
|
+
final case class Person(name: String, age: Int)
|
|
128
|
+
object Person {
|
|
129
|
+
implicit val schema: Schema[Person] = Schema.derived[Person]
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`Schema.derived[A]` works identically in both Scala 2 and Scala 3 in ZIO Blocks Schema. There is no separate `DeriveSchema` import and no arity limit.
|
|
134
|
+
|
|
135
|
+
### Primitives
|
|
136
|
+
|
|
137
|
+
All 30 primitive types from ZIO Schema are present in ZIO Blocks Schema with the same coverage: `Unit`, `Boolean`, `Byte`, `Short`, `Int`, `Long`, `Float`, `Double`, `Char`, `String`, `BigInt`, `BigDecimal`, all `java.time.*` types, `Currency`, and `UUID`.
|
|
138
|
+
|
|
139
|
+
Implicit schemas are available in the same way:
|
|
140
|
+
|
|
141
|
+
```scala
|
|
142
|
+
// ZIO Schema 1.x
|
|
143
|
+
val s: Schema[Int] = Schema[Int]
|
|
144
|
+
val s: Schema[java.time.Instant] = Schema[java.time.Instant]
|
|
145
|
+
|
|
146
|
+
// ZIO Blocks Schema — identical call sites
|
|
147
|
+
val s: Schema[Int] = Schema[Int]
|
|
148
|
+
val s: Schema[java.time.Instant] = Schema[java.time.Instant]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The underlying representation changes: ZIO Schema uses `Schema.Primitive[A](standardType: StandardType[A])`, while ZIO Blocks Schema uses `Reflect.Primitive[F, A](primitiveType: PrimitiveType[A], ...)`. Both carry default values and ordering, but in ZIO Blocks the primitive type also carries an embedded `Validation[A]` constraint (see the [Migrating Validation](#migrating-validation) section below).
|
|
152
|
+
|
|
153
|
+
### Records (Case Classes)
|
|
154
|
+
|
|
155
|
+
**Before (ZIO Schema 1.x):**
|
|
156
|
+
|
|
157
|
+
```scala
|
|
158
|
+
import zio.schema._
|
|
159
|
+
|
|
160
|
+
final case class Address(street: String, city: String, postCode: String)
|
|
161
|
+
final case class Person(name: String, age: Int, address: Address)
|
|
162
|
+
|
|
163
|
+
object Person {
|
|
164
|
+
implicit val schema: Schema[Person] = DeriveSchema.gen
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**After (ZIO Blocks Schema):**
|
|
169
|
+
|
|
170
|
+
```scala
|
|
171
|
+
import zio.blocks.schema._
|
|
172
|
+
|
|
173
|
+
final case class Address(street: String, city: String, postCode: String)
|
|
174
|
+
final case class Person(name: String, age: Int, address: Address)
|
|
175
|
+
|
|
176
|
+
object Person {
|
|
177
|
+
implicit val schema: Schema[Person] = Schema.derived[Person]
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The derivation call is identical. Internally, ZIO Blocks generates a `Reflect.Record` node (a single generic type, not the arity-specialised `CaseClass1`..`CaseClass22` of ZIO Schema), so there is no 22-field arity limit.
|
|
182
|
+
|
|
183
|
+
If you were writing schemas manually using `Schema.CaseClass2[...]` or similar, you will need to rewrite those. The equivalent in ZIO Blocks is to write the `Reflect.Record` directly or, preferably, just use `Schema.derived[A]`:
|
|
184
|
+
|
|
185
|
+
```scala
|
|
186
|
+
// ZIO Schema 1.x — manual construction for a 2-field record
|
|
187
|
+
val personSchema: Schema[Person] =
|
|
188
|
+
Schema.CaseClass2[String, Int, Person](
|
|
189
|
+
id0 = TypeId.fromTypeName("Person"),
|
|
190
|
+
field01 = Schema.Field("name", Schema[String], get0 = _.name, set0 = (p, v) => p.copy(name = v)),
|
|
191
|
+
field02 = Schema.Field("age", Schema[Int], get0 = _.age, set0 = (p, v) => p.copy(age = v)),
|
|
192
|
+
construct0 = Person(_, _)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
// ZIO Blocks Schema — prefer derivation; no manual CaseClass* required
|
|
196
|
+
val personSchema: Schema[Person] = Schema.derived[Person]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Manual construction is still possible in ZIO Blocks Schema (by assembling a `Reflect.Record` directly), but it is substantially more involved because you must supply a `Binding.Record` with explicit `Constructor[A]` and `Deconstructor[A]` implementations that use the unboxed register system. Automatic derivation is strongly preferred.
|
|
200
|
+
|
|
201
|
+
### Sealed Traits / Enums (Sum Types)
|
|
202
|
+
|
|
203
|
+
**Before (ZIO Schema 1.x):**
|
|
204
|
+
|
|
205
|
+
```scala
|
|
206
|
+
import zio.schema._
|
|
207
|
+
|
|
208
|
+
sealed trait Shape
|
|
209
|
+
case class Circle(radius: Double) extends Shape
|
|
210
|
+
case class Rectangle(width: Double, height: Double) extends Shape
|
|
211
|
+
|
|
212
|
+
object Shape {
|
|
213
|
+
implicit val schema: Schema[Shape] = DeriveSchema.gen
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**After (ZIO Blocks Schema):**
|
|
218
|
+
|
|
219
|
+
```scala
|
|
220
|
+
import zio.blocks.schema._
|
|
221
|
+
|
|
222
|
+
sealed trait Shape
|
|
223
|
+
case class Circle(radius: Double) extends Shape
|
|
224
|
+
case class Rectangle(width: Double, height: Double) extends Shape
|
|
225
|
+
|
|
226
|
+
object Shape {
|
|
227
|
+
implicit val schema: Schema[Shape] = Schema.derived[Shape]
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Again, the call is identical. Internally, ZIO Blocks generates a `Reflect.Variant` node. There is no 22-case arity limit.
|
|
232
|
+
|
|
233
|
+
### Optional Values
|
|
234
|
+
|
|
235
|
+
ZIO Schema has a first-class `Schema.Optional[A]` node. In ZIO Blocks, `Option[A]` is modeled as a `Reflect.Variant` with two cases (`None` and `Some`). From a user perspective this is transparent — implicit schemas for `Option[A]` exist in the same form:
|
|
236
|
+
|
|
237
|
+
```scala
|
|
238
|
+
// ZIO Schema 1.x
|
|
239
|
+
val optSchema: Schema[Option[String]] = Schema[Option[String]]
|
|
240
|
+
|
|
241
|
+
// ZIO Blocks Schema — identical
|
|
242
|
+
val optSchema: Schema[Option[String]] = Schema[Option[String]]
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
For value-type `Option` variants (e.g., `Option[Int]`), ZIO Blocks provides specialised implicit instances (`Schema.optionInt`, `Schema.optionLong`, etc.) that avoid boxing. These are resolved automatically by the compiler — no code change required.
|
|
246
|
+
|
|
247
|
+
:::tip
|
|
248
|
+
The internal modeling difference (variant vs. dedicated node) is only relevant if you are pattern-matching on the raw `Schema` or `Reflect` structure. In that case, replace any match on `Schema.Optional(inner, _)` with a check on `reflect.isOption` and use `reflect.optionInnerType` to retrieve the inner reflect:
|
|
249
|
+
|
|
250
|
+
```scala
|
|
251
|
+
// ZIO Schema 1.x — pattern matching on Optional
|
|
252
|
+
schema match {
|
|
253
|
+
case Schema.Optional(inner, _) => // use inner
|
|
254
|
+
case _ => // ...
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ZIO Blocks Schema — use the isOption predicate
|
|
258
|
+
// optionInnerType returns Option[Reflect[F, ?]] where F matches the enclosing Reflect's binding
|
|
259
|
+
val r = schema.reflect
|
|
260
|
+
if (r.isOption) {
|
|
261
|
+
val inner: Option[Reflect[binding.Binding, ?]] = r.optionInnerType
|
|
262
|
+
// use inner
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
:::
|
|
266
|
+
|
|
267
|
+
### Either
|
|
268
|
+
|
|
269
|
+
In ZIO Schema, `Either[A, B]` is a first-class `Schema.Either[A, B]` node. In ZIO Blocks, it is modeled as a two-case `Reflect.Variant`. The implicit schema is provided automatically:
|
|
270
|
+
|
|
271
|
+
```scala
|
|
272
|
+
// ZIO Schema 1.x
|
|
273
|
+
val eitherSchema: Schema[Either[String, Int]] = Schema.either[String, Int]
|
|
274
|
+
|
|
275
|
+
// ZIO Blocks Schema
|
|
276
|
+
val eitherSchema: Schema[Either[String, Int]] = Schema[Either[String, Int]]
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
:::warning
|
|
280
|
+
ZIO Blocks Schema does not have a `Fallback[A, B]` type. If you were using `Schema.Fallback` for partial decoding, you will need to model that with a custom `Reflect.Variant` or handle it in your codec logic directly.
|
|
281
|
+
:::
|
|
282
|
+
|
|
283
|
+
### Collections
|
|
284
|
+
|
|
285
|
+
All standard collection types are supported with the same implicit schema pattern:
|
|
286
|
+
|
|
287
|
+
```scala
|
|
288
|
+
// ZIO Schema 1.x
|
|
289
|
+
Schema[List[String]]
|
|
290
|
+
Schema[Vector[Int]]
|
|
291
|
+
Schema[Chunk[Double]]
|
|
292
|
+
Schema[Set[String]]
|
|
293
|
+
Schema[Map[String, Int]]
|
|
294
|
+
|
|
295
|
+
// ZIO Blocks Schema — identical call sites
|
|
296
|
+
Schema[List[String]]
|
|
297
|
+
Schema[Vector[Int]]
|
|
298
|
+
Schema[Chunk[Double]] // uses zio.blocks.chunk.Chunk
|
|
299
|
+
Schema[Set[String]]
|
|
300
|
+
Schema[Map[String, Int]]
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Note that `Chunk` is now `zio.blocks.chunk.Chunk` (not `zio.Chunk`). This is a zero-dependency replacement with the same API surface for typical usage.
|
|
304
|
+
|
|
305
|
+
ZIO Schema's `NonEmptyChunk` and `NonEmptyMap` schemas do not have direct equivalents in ZIO Blocks Schema. The recommended approach is to model them as wrapper types:
|
|
306
|
+
|
|
307
|
+
```scala
|
|
308
|
+
// ZIO Schema 1.x — NonEmptyChunk implicit schema
|
|
309
|
+
val schema: Schema[NonEmptyChunk[String]] = Schema[NonEmptyChunk[String]]
|
|
310
|
+
|
|
311
|
+
// ZIO Blocks Schema — model as a validated wrapper
|
|
312
|
+
import zio.blocks.schema._
|
|
313
|
+
|
|
314
|
+
final case class NonEmptyList[A] private (values: List[A])
|
|
315
|
+
object NonEmptyList {
|
|
316
|
+
def apply[A](head: A, tail: A*): NonEmptyList[A] = new NonEmptyList(head :: tail.toList)
|
|
317
|
+
|
|
318
|
+
implicit def schema[A](implicit element: Schema[A]): Schema[NonEmptyList[A]] =
|
|
319
|
+
Schema[List[A]].transform(
|
|
320
|
+
to = list =>
|
|
321
|
+
if (list.nonEmpty) new NonEmptyList(list)
|
|
322
|
+
else throw SchemaError.validationFailed("List must not be empty"),
|
|
323
|
+
from = _.values
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Newtypes and Opaque Types
|
|
329
|
+
|
|
330
|
+
ZIO Schema uses `Schema.transform` (which wraps a `Transform` node) for both validated newtypes and lossless wrappers:
|
|
331
|
+
|
|
332
|
+
```scala
|
|
333
|
+
// ZIO Schema 1.x
|
|
334
|
+
implicit val bigDecimalSchema: Schema[BigDecimal] =
|
|
335
|
+
Schema.primitive[java.math.BigDecimal].transform(BigDecimal(_), _.bigDecimal)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
ZIO Blocks Schema has `Schema[A].transform(to: A => B, from: B => A)` which produces a `Reflect.Wrapper` node. The `to` and `from` functions are total but can throw to indicate failure. The method also requires an implicit `TypeId[B]`, which is derived automatically by the macro system for any concrete named type — you will not need to supply it manually for ordinary case classes:
|
|
339
|
+
|
|
340
|
+
```scala
|
|
341
|
+
// ZIO Blocks Schema
|
|
342
|
+
case class Email(value: String)
|
|
343
|
+
object Email {
|
|
344
|
+
// TypeId[Email] is resolved implicitly from the macro-derived instance
|
|
345
|
+
implicit val schema: Schema[Email] =
|
|
346
|
+
Schema[String].transform(
|
|
347
|
+
to = str =>
|
|
348
|
+
if (str.contains('@')) Email(str)
|
|
349
|
+
else throw SchemaError.validationFailed("Not a valid email address"),
|
|
350
|
+
from = _.value
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
For simple lossless wrappers where no validation is needed, the pattern is the same but without the error throw:
|
|
356
|
+
|
|
357
|
+
```scala
|
|
358
|
+
// ZIO Blocks Schema — simple newtype wrapper
|
|
359
|
+
case class UserId(value: Long)
|
|
360
|
+
object UserId {
|
|
361
|
+
implicit val schema: Schema[UserId] =
|
|
362
|
+
Schema[Long].transform(UserId(_), _.value)
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
:::warning
|
|
367
|
+
In ZIO Schema, `transformOrFail` accepted `A => Either[String, B]` return types. In ZIO Blocks, `transform` uses total functions that throw on failure — use `throw SchemaError.validationFailed(message)` in the `to` function to signal failure. There is no `transformOrFail` method.
|
|
368
|
+
|
|
369
|
+
If you encounter a "could not find implicit value for parameter typeId: TypeId[B]" error, ensure the target type `B` is a concrete, named class or object (not an anonymous structural type or a type alias to a primitive). For primitive-backed aliases such as `type Meters = Double`, wrap in a `case class` instead.
|
|
370
|
+
:::
|
|
371
|
+
|
|
372
|
+
### Lazy / Recursive Schemas
|
|
373
|
+
|
|
374
|
+
**Before (ZIO Schema 1.x):**
|
|
375
|
+
|
|
376
|
+
```scala
|
|
377
|
+
import zio.schema._
|
|
378
|
+
|
|
379
|
+
case class Tree(value: Int, children: List[Tree])
|
|
380
|
+
object Tree {
|
|
381
|
+
implicit lazy val schema: Schema[Tree] = DeriveSchema.gen
|
|
382
|
+
// Or manually with Schema.defer:
|
|
383
|
+
// implicit lazy val schema: Schema[Tree] = Schema.CaseClass2(
|
|
384
|
+
// ..., field02 = Schema.Field("children", Schema.defer(Schema.list(schema)), ...)
|
|
385
|
+
// )
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**After (ZIO Blocks Schema):**
|
|
390
|
+
|
|
391
|
+
```scala
|
|
392
|
+
import zio.blocks.schema._
|
|
393
|
+
|
|
394
|
+
case class Tree(value: Int, children: List[Tree])
|
|
395
|
+
object Tree {
|
|
396
|
+
implicit val schema: Schema[Tree] = Schema.derived[Tree]
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Recursive types are handled automatically by the macro. Internally, ZIO Blocks generates a `Reflect.Deferred` node that uses thread-local cycle detection — you do not need to use `Schema.defer` manually. The `implicit val` (not `lazy val`) is sufficient.
|
|
401
|
+
|
|
402
|
+
If you were wrapping a recursive reference manually with `Schema.defer(...)`, simply remove that wrapper — recursive references inside `Schema.derived` are handled for you.
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Migrating Annotations and Modifiers
|
|
407
|
+
|
|
408
|
+
ZIO Schema uses an open `Chunk[Any]` annotation system. ZIO Blocks Schema replaces this with a strongly-typed, sealed `Modifier` hierarchy.
|
|
409
|
+
|
|
410
|
+
### Transient Fields
|
|
411
|
+
|
|
412
|
+
```scala
|
|
413
|
+
// ZIO Schema 1.x
|
|
414
|
+
import zio.schema.annotation._
|
|
415
|
+
|
|
416
|
+
final case class User(name: String, @transientField password: String)
|
|
417
|
+
object User {
|
|
418
|
+
implicit val schema: Schema[User] = DeriveSchema.gen
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ZIO Blocks Schema
|
|
422
|
+
import zio.blocks.schema._
|
|
423
|
+
|
|
424
|
+
// Transient fields must have a default value in ZIO Blocks Schema.
|
|
425
|
+
// Because transient fields are excluded from serialization, the decoder
|
|
426
|
+
// needs a default to reconstruct the object without that field in the input.
|
|
427
|
+
final case class User(name: String, @Modifier.transient() password: String = "")
|
|
428
|
+
object User {
|
|
429
|
+
implicit val schema: Schema[User] = Schema.derived[User]
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Field Renaming
|
|
434
|
+
|
|
435
|
+
```scala
|
|
436
|
+
// ZIO Schema 1.x — @fieldName annotation
|
|
437
|
+
import zio.schema.annotation._
|
|
438
|
+
|
|
439
|
+
final case class Product(@fieldName("product_name") name: String, price: Double)
|
|
440
|
+
|
|
441
|
+
// ZIO Blocks Schema — @Modifier.rename annotation
|
|
442
|
+
import zio.blocks.schema._
|
|
443
|
+
|
|
444
|
+
final case class Product(@Modifier.rename("product_name") name: String, price: Double)
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Field Aliases (for Decoding)
|
|
448
|
+
|
|
449
|
+
```scala
|
|
450
|
+
// ZIO Schema 1.x — @fieldNameAliases annotation
|
|
451
|
+
import zio.schema.annotation._
|
|
452
|
+
|
|
453
|
+
final case class Config(@fieldNameAliases("max-size", "max_size") maxSize: Int)
|
|
454
|
+
|
|
455
|
+
// ZIO Blocks Schema — @Modifier.alias annotation (one alias per annotation)
|
|
456
|
+
import zio.blocks.schema._
|
|
457
|
+
|
|
458
|
+
final case class Config(
|
|
459
|
+
@Modifier.alias("max-size")
|
|
460
|
+
@Modifier.alias("max_size")
|
|
461
|
+
maxSize: Int
|
|
462
|
+
)
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Codec-Specific Configuration
|
|
466
|
+
|
|
467
|
+
```scala
|
|
468
|
+
// ZIO Schema 1.x — no standard mechanism; each codec module defines its own
|
|
469
|
+
// e.g., @fieldDefaultValue, @optionalField, or codec-specific annotations
|
|
470
|
+
|
|
471
|
+
// ZIO Blocks Schema — use @Modifier.config with convention "format.property"
|
|
472
|
+
import zio.blocks.schema._
|
|
473
|
+
|
|
474
|
+
final case class Message(
|
|
475
|
+
@Modifier.config("protobuf.field-id", "1") id: Long,
|
|
476
|
+
@Modifier.config("protobuf.field-id", "2") content: String
|
|
477
|
+
)
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Discriminator and Case Name Annotations
|
|
481
|
+
|
|
482
|
+
```scala
|
|
483
|
+
// ZIO Schema 1.x
|
|
484
|
+
import zio.schema.annotation._
|
|
485
|
+
|
|
486
|
+
@discriminatorName("type")
|
|
487
|
+
sealed trait Event
|
|
488
|
+
@caseName("user_created")
|
|
489
|
+
case class UserCreated(userId: String) extends Event
|
|
490
|
+
|
|
491
|
+
// ZIO Blocks Schema — use Modifier.rename on the case, Modifier.config for discriminator
|
|
492
|
+
import zio.blocks.schema._
|
|
493
|
+
|
|
494
|
+
sealed trait Event
|
|
495
|
+
@Modifier.rename("user_created")
|
|
496
|
+
case class UserCreated(userId: String) extends Event
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
For discriminator key configuration on the enclosing sealed trait, use `Modifier.config` on the reflect node after derivation:
|
|
500
|
+
|
|
501
|
+
```scala
|
|
502
|
+
implicit val schema: Schema[Event] =
|
|
503
|
+
Schema.derived[Event].modifier(Modifier.config("json.discriminator", "type"))
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Programmatic Annotation
|
|
507
|
+
|
|
508
|
+
ZIO Schema allows adding annotations at any time via `schema.annotate(annotation)`. In ZIO Blocks, you add modifiers:
|
|
509
|
+
|
|
510
|
+
```scala
|
|
511
|
+
// ZIO Schema 1.x
|
|
512
|
+
val schema2 = schema.annotate(someAnnotation)
|
|
513
|
+
|
|
514
|
+
// ZIO Blocks Schema
|
|
515
|
+
val schema2 = schema.modifier(Modifier.config("key", "value"))
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## Migrating Codec Derivation
|
|
521
|
+
|
|
522
|
+
### The Unified Format Model
|
|
523
|
+
|
|
524
|
+
ZIO Schema has separate codec APIs in each codec sub-module (e.g., `JsonCodec.jsonCodec`, `ProtobufCodec.protobufCodec`). ZIO Blocks Schema introduces a unified `codec.Format` interface that all codec modules implement. Codecs are derived via a consistent call:
|
|
525
|
+
|
|
526
|
+
```scala
|
|
527
|
+
// ZIO Schema 1.x — each codec module has its own factory
|
|
528
|
+
// JsonCodec.jsonCodec returns a zio.json.JsonCodec (a text codec from the zio-json library)
|
|
529
|
+
import zio.schema.codec.JsonCodec
|
|
530
|
+
val jsonCodec = JsonCodec.jsonCodec(Person.schema)
|
|
531
|
+
|
|
532
|
+
// ProtobufCodec.protobufCodec returns a BinaryCodec[A] (Chunk[Byte] in / out)
|
|
533
|
+
import zio.schema.codec.ProtobufCodec
|
|
534
|
+
val protoCodec: BinaryCodec[Person] = ProtobufCodec.protobufCodec(Person.schema)
|
|
535
|
+
|
|
536
|
+
// ZIO Blocks Schema — all codecs via schema.derive(Format); return type inferred
|
|
537
|
+
import zio.blocks.schema._
|
|
538
|
+
import zio.blocks.schema.json.JsonFormat
|
|
539
|
+
val jsonCodec = Person.schema.derive(JsonFormat) // inferred: JsonBinaryCodec[Person]
|
|
540
|
+
|
|
541
|
+
import zio.blocks.schema.avro.AvroFormat
|
|
542
|
+
val avroCodec = Person.schema.derive(AvroFormat)
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
Derived codecs are cached per `(Schema, Format)` pair — subsequent calls to `schema.derive(JsonFormat)` return the same instance.
|
|
546
|
+
|
|
547
|
+
### Encoding and Decoding
|
|
548
|
+
|
|
549
|
+
The codec interface differs significantly between the two libraries:
|
|
550
|
+
|
|
551
|
+
```scala
|
|
552
|
+
// ZIO Schema 1.x — Protobuf (true BinaryCodec: Chunk[Byte] in/out)
|
|
553
|
+
import zio.schema.codec.ProtobufCodec
|
|
554
|
+
val codec: BinaryCodec[Person] = ProtobufCodec.protobufCodec(Person.schema)
|
|
555
|
+
val encoded: Chunk[Byte] = codec.encode(Person("Alice", 30))
|
|
556
|
+
val decoded: Either[DecodeError, Person] = codec.decode(encoded)
|
|
557
|
+
|
|
558
|
+
// ZIO Schema 1.x — JSON (zio-json JsonCodec: String in/out)
|
|
559
|
+
import zio.schema.codec.JsonCodec
|
|
560
|
+
val jsonCodec = JsonCodec.jsonCodec(Person.schema)
|
|
561
|
+
val json: String = jsonCodec.encodeJson(Person("Alice", 30), None).toString
|
|
562
|
+
val fromJson: Either[String, Person] = jsonCodec.decodeJson(json)
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
```scala
|
|
566
|
+
// ZIO Blocks Schema — all formats use ByteBuffer (binary) or CharBuffer (text)
|
|
567
|
+
import zio.blocks.schema._
|
|
568
|
+
import zio.blocks.schema.json.JsonFormat
|
|
569
|
+
import java.nio.ByteBuffer
|
|
570
|
+
|
|
571
|
+
val person = Person("Alice", 30)
|
|
572
|
+
|
|
573
|
+
// Encode
|
|
574
|
+
val buffer = ByteBuffer.allocate(1024)
|
|
575
|
+
Person.schema.encode(JsonFormat)(buffer)(person)
|
|
576
|
+
|
|
577
|
+
// Decode
|
|
578
|
+
buffer.flip()
|
|
579
|
+
val result: Either[SchemaError, Person] = Person.schema.decode(JsonFormat)(buffer)
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
ZIO Blocks codecs use `java.nio.ByteBuffer` for binary formats and `java.nio.CharBuffer` for text formats, and do not depend on `zio-json` or any other external codec library.
|
|
583
|
+
|
|
584
|
+
### JSON Codec
|
|
585
|
+
|
|
586
|
+
JSON support is built into the core `zio-blocks-schema` module — no separate dependency is needed:
|
|
587
|
+
|
|
588
|
+
```scala
|
|
589
|
+
// ZIO Schema 1.x — requires a separate zio-schema-json module
|
|
590
|
+
libraryDependencies += "dev.zio" %% "zio-schema-json" % "1.x.x"
|
|
591
|
+
import zio.schema.codec.JsonCodec
|
|
592
|
+
val codec = JsonCodec.jsonCodec(Person.schema) // returns zio.json.JsonCodec
|
|
593
|
+
|
|
594
|
+
// ZIO Blocks Schema — built into zio-blocks-schema; no extra dependency
|
|
595
|
+
import zio.blocks.schema.json.JsonFormat
|
|
596
|
+
val codec = Person.schema.derive(JsonFormat) // returns JsonBinaryCodec[Person]
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Streaming Codecs
|
|
600
|
+
|
|
601
|
+
ZIO Schema's streaming codec methods (`streamEncoder`, `streamDecoder`) integrated with `ZStream`. ZIO Blocks Schema codecs are format-level `encode`/`decode` operations over `ByteBuffer` or `CharBuffer` — they do not depend on ZIO's streaming primitives. If you need streaming, wrap the codec in your effect system's streaming abstraction.
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## Migrating DynamicValue
|
|
606
|
+
|
|
607
|
+
### Structure Changes
|
|
608
|
+
|
|
609
|
+
The `DynamicValue` ADT is significantly simplified in ZIO Blocks — from 15 cases down to 6. The key differences:
|
|
610
|
+
|
|
611
|
+
- ZIO Schema's `DynamicValue.Primitive[A](value: A, standardType: StandardType[A])` stores the raw value and its `StandardType` inline. ZIO Blocks wraps the scalar in a `PrimitiveValue` case class instead.
|
|
612
|
+
- `DynamicValue.Record` drops the `TypeId` parameter and uses `Chunk[(String, DynamicValue)]` instead of `ListMap[String, DynamicValue]`.
|
|
613
|
+
- `Option`, `Either`, and `Tuple` are no longer dedicated ADT cases — they are represented structurally using `Variant` and `Record`.
|
|
614
|
+
|
|
615
|
+
| ZIO Schema | ZIO Blocks Schema |
|
|
616
|
+
|---|---|
|
|
617
|
+
| `Primitive[A](value: A, standardType: StandardType[A])` | `Primitive(value: PrimitiveValue)` |
|
|
618
|
+
| `Record(id: TypeId, values: ListMap[String, DynamicValue])` | `Record(fields: Chunk[(String, DynamicValue)])` — no TypeId |
|
|
619
|
+
| `Enumeration(id: TypeId, value: (String, DynamicValue))` | `Variant(caseName: String, value: DynamicValue)` |
|
|
620
|
+
| `Sequence(values: Chunk[DynamicValue])` | `Sequence(elements: Chunk[DynamicValue])` |
|
|
621
|
+
| `Dictionary(entries: Chunk[(DynamicValue, DynamicValue)])` | `Map(entries: Chunk[(DynamicValue, DynamicValue)])` |
|
|
622
|
+
| `SomeValue(value: DynamicValue)` | `Variant("Some", Record(Chunk("value" -> ...)))` |
|
|
623
|
+
| `NoneValue` | `Variant("None", Null)` |
|
|
624
|
+
| `LeftValue(value: DynamicValue)` | `Variant("Left", Record(Chunk("value" -> ...)))` |
|
|
625
|
+
| `RightValue(value: DynamicValue)` | `Variant("Right", Record(Chunk("value" -> ...)))` |
|
|
626
|
+
| `Tuple(left, right)` | `Record(Chunk("_1" -> left, "_2" -> right))` |
|
|
627
|
+
| `SetValue(values: Set[DynamicValue])` | `Sequence(elements: Chunk[DynamicValue])` |
|
|
628
|
+
| `BothValue(left, right)` | No direct equivalent (used by `Fallback`, which is removed) |
|
|
629
|
+
| `DynamicAst(ast: MetaSchema)` | No direct equivalent |
|
|
630
|
+
| `Singleton[A](instance: A)` | No direct equivalent |
|
|
631
|
+
| `Error(message: String)` | No direct equivalent — use `SchemaError` |
|
|
632
|
+
|
|
633
|
+
### Primitive Values
|
|
634
|
+
|
|
635
|
+
In ZIO Schema, primitive values are stored inline in `DynamicValue.Primitive[A](value: A, standardType: StandardType[A])`. There is no separate `PrimitiveValue` type. In ZIO Blocks, a sealed `PrimitiveValue` ADT wraps each primitive:
|
|
636
|
+
|
|
637
|
+
```scala
|
|
638
|
+
// ZIO Schema 1.x — value and StandardType are separate constructor arguments
|
|
639
|
+
import zio.schema.{DynamicValue, StandardType}
|
|
640
|
+
val pv: DynamicValue = DynamicValue.Primitive(42, StandardType[Int])
|
|
641
|
+
val ps: DynamicValue = DynamicValue.Primitive("hello", StandardType[String])
|
|
642
|
+
|
|
643
|
+
// ZIO Blocks Schema — value is wrapped in a PrimitiveValue case class
|
|
644
|
+
import zio.blocks.schema.{DynamicValue, PrimitiveValue}
|
|
645
|
+
val pv: DynamicValue = DynamicValue.Primitive(PrimitiveValue.Int(42))
|
|
646
|
+
val ps: DynamicValue = DynamicValue.Primitive(PrimitiveValue.String("hello"))
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
The `PrimitiveValue` case names (`Int`, `Long`, `String`, `Boolean`, `Double`, etc.) match the Scala primitive names.
|
|
650
|
+
|
|
651
|
+
### Converting Between Typed Values and DynamicValue
|
|
652
|
+
|
|
653
|
+
```scala
|
|
654
|
+
// ZIO Schema 1.x
|
|
655
|
+
// toDynamic is a method on Schema[A], not on the value itself
|
|
656
|
+
val dv: DynamicValue = Person.schema.toDynamic(person)
|
|
657
|
+
// toTypedValue requires an implicit Schema[Person] in scope
|
|
658
|
+
val back: Either[String, Person] = dv.toTypedValue[Person]
|
|
659
|
+
|
|
660
|
+
// ZIO Blocks Schema
|
|
661
|
+
val dv: DynamicValue = Person.schema.toDynamicValue(person)
|
|
662
|
+
val back: Either[SchemaError, Person] = Person.schema.fromDynamicValue(dv)
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
Two things change: `toDynamic` is renamed `toDynamicValue` (still on `Schema[A]`), and `toTypedValue` is replaced by `schema.fromDynamicValue`. The error type changes from `String` to `SchemaError`.
|
|
666
|
+
|
|
667
|
+
### DynamicValue Operations
|
|
668
|
+
|
|
669
|
+
ZIO Blocks `DynamicValue` has a rich operation API that was absent in ZIO Schema. Where ZIO Schema required you to convert back to a typed value to manipulate data, you can now operate directly on `DynamicValue`:
|
|
670
|
+
|
|
671
|
+
```scala
|
|
672
|
+
import zio.blocks.schema._
|
|
673
|
+
import zio.blocks.chunk.Chunk
|
|
674
|
+
|
|
675
|
+
val record = DynamicValue.Record(
|
|
676
|
+
Chunk(
|
|
677
|
+
"name" -> DynamicValue.Primitive(PrimitiveValue.String("Alice")),
|
|
678
|
+
"age" -> DynamicValue.Primitive(PrimitiveValue.Int(30))
|
|
679
|
+
)
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
// Navigate — get(fieldName) returns DynamicValueSelection (supports chaining)
|
|
683
|
+
// Call .one to extract a single value as Either[SchemaError, DynamicValue]
|
|
684
|
+
val name: Either[SchemaError, DynamicValue] = record.get("name").one
|
|
685
|
+
|
|
686
|
+
// Modify — set returns DynamicValue directly (silent no-op if path not found)
|
|
687
|
+
// Use setOrFail to get an Either on missing paths
|
|
688
|
+
val updated: DynamicValue = record.set(
|
|
689
|
+
DynamicOptic.root.field("name"),
|
|
690
|
+
DynamicValue.Primitive(PrimitiveValue.String("Bob"))
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
// Diff
|
|
694
|
+
val other = DynamicValue.Record(Chunk(
|
|
695
|
+
"name" -> DynamicValue.Primitive(PrimitiveValue.String("Bob")),
|
|
696
|
+
"age" -> DynamicValue.Primitive(PrimitiveValue.Int(31))
|
|
697
|
+
))
|
|
698
|
+
val patch = record.diff(other)
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
---
|
|
702
|
+
|
|
703
|
+
## Migrating Schema Introspection
|
|
704
|
+
|
|
705
|
+
### MetaSchema → DynamicSchema
|
|
706
|
+
|
|
707
|
+
ZIO Schema has `MetaSchema` (a type-erased structural description of a schema) and `schema.ast` to convert to it. ZIO Blocks uses `DynamicSchema` for the same purpose:
|
|
708
|
+
|
|
709
|
+
```scala
|
|
710
|
+
// ZIO Schema 1.x
|
|
711
|
+
val meta: MetaSchema = schema.ast
|
|
712
|
+
val back: Schema[_] = meta.toSchema
|
|
713
|
+
|
|
714
|
+
// ZIO Blocks Schema
|
|
715
|
+
val dynamic: DynamicSchema = schema.toDynamicSchema
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
`DynamicSchema` wraps a `Reflect[NoBinding, _]` — the full structural description without runtime constructors or deconstructors. Use it for:
|
|
719
|
+
|
|
720
|
+
- Runtime structural validation of `DynamicValue` instances
|
|
721
|
+
- Dynamic schema loading from configuration or network
|
|
722
|
+
- Schema inspection without compile-time type information
|
|
723
|
+
|
|
724
|
+
```scala
|
|
725
|
+
// ZIO Blocks Schema — validate a DynamicValue against a schema
|
|
726
|
+
val personSchema: Schema[Person] = Schema.derived[Person]
|
|
727
|
+
val dynSchema: DynamicSchema = personSchema.toDynamicSchema
|
|
728
|
+
|
|
729
|
+
val value = DynamicValue.Record(Chunk(
|
|
730
|
+
"name" -> DynamicValue.Primitive(PrimitiveValue.String("Alice")),
|
|
731
|
+
"age" -> DynamicValue.Primitive(PrimitiveValue.Int(30))
|
|
732
|
+
))
|
|
733
|
+
|
|
734
|
+
dynSchema.conforms(value) // true
|
|
735
|
+
dynSchema.check(value) // None (no error)
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
:::warning
|
|
739
|
+
ZIO Schema's `Migration` system for schema-to-schema migration (i.e., automatically migrating values from one version of a type to another) is **not yet available** in ZIO Blocks Schema. The `schema.migrate[B](newSchema)` and `schema.coerce[B](newSchema)` methods do not exist. If your application relies on schema migration, you have two options:
|
|
740
|
+
|
|
741
|
+
1. Implement migration logic manually using `DynamicValue` transformations and `DynamicSchema` for validation.
|
|
742
|
+
2. Wait for schema migration support to be added to ZIO Blocks Schema (it is on the roadmap).
|
|
743
|
+
:::
|
|
744
|
+
|
|
745
|
+
### Schema Serialization
|
|
746
|
+
|
|
747
|
+
ZIO Schema supports serializing a schema itself (via `schema.serializable`). ZIO Blocks Schema does not have a direct equivalent at this time. All schema metadata types (`DynamicOptic`, `DynamicPatch`, `Modifier`, `Validation`, etc.) have `Schema` instances and are individually serializable, but there is no single `Schema[Schema[A]]` that round-trips the full structural description.
|
|
748
|
+
|
|
749
|
+
---
|
|
750
|
+
|
|
751
|
+
## Migrating Optics
|
|
752
|
+
|
|
753
|
+
ZIO Schema uses an `AccessorBuilder` pattern that delegates optic creation to an external `zio-schema-optics` module. ZIO Blocks Schema includes a complete, first-class optics system in the core module.
|
|
754
|
+
|
|
755
|
+
### Generating Optics
|
|
756
|
+
|
|
757
|
+
**Before (ZIO Schema 1.x with `zio-schema-optics`):**
|
|
758
|
+
|
|
759
|
+
```scala
|
|
760
|
+
import zio.schema._
|
|
761
|
+
import zio.schema.optics._
|
|
762
|
+
|
|
763
|
+
case class Person(name: String, age: Int)
|
|
764
|
+
object Person {
|
|
765
|
+
implicit val schema: Schema[Person] = DeriveSchema.gen
|
|
766
|
+
val (name, age) = schema.makeAccessors(ZioOpticsBuilder)
|
|
767
|
+
}
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
**After (ZIO Blocks Schema):**
|
|
771
|
+
|
|
772
|
+
Optics are generated by the macro derivation and placed directly in the companion object as `Lens` instances via a `CompanionOptics` mechanism. In Scala 3, they are generated automatically. In Scala 2, use `Schema.derived[Person]` and access fields by calling `schema.reflect.asRecord.get.lensByName[String]("name")`, or use the macro-derived companion optics pattern:
|
|
773
|
+
|
|
774
|
+
```scala
|
|
775
|
+
// Scala 3 — optics generated in companion via macro
|
|
776
|
+
import zio.blocks.schema._
|
|
777
|
+
|
|
778
|
+
case class Person(name: String, age: Int)
|
|
779
|
+
object Person extends CompanionOptics[Person] {
|
|
780
|
+
implicit val schema: Schema[Person] = Schema.derived[Person]
|
|
781
|
+
// Scala 3 macro generates: val name: Lens[Person, String] = ...
|
|
782
|
+
// val age: Lens[Person, Int] = ...
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Usage
|
|
786
|
+
val lens: Lens[Person, String] = Person.name
|
|
787
|
+
val person = Person("Alice", 30)
|
|
788
|
+
lens.modify(person, _.toUpperCase) // Person("ALICE", 30)
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
```scala
|
|
792
|
+
// Scala 2 — obtain lenses from the schema
|
|
793
|
+
import zio.blocks.schema._
|
|
794
|
+
|
|
795
|
+
case class Person(name: String, age: Int)
|
|
796
|
+
object Person {
|
|
797
|
+
implicit val schema: Schema[Person] = Schema.derived[Person]
|
|
798
|
+
|
|
799
|
+
val name: Lens[Person, String] =
|
|
800
|
+
schema.reflect.asRecord.get.lensByName[String]("name").get
|
|
801
|
+
val age: Lens[Person, Int] =
|
|
802
|
+
schema.reflect.asRecord.get.lensByName[Int]("age").get
|
|
803
|
+
}
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### Using Optics
|
|
807
|
+
|
|
808
|
+
The four optic types in ZIO Blocks are `Lens`, `Prism`, `Optional`, and `Traversal`. Their usage API is similar to standard optics libraries:
|
|
809
|
+
|
|
810
|
+
```scala
|
|
811
|
+
import zio.blocks.schema._
|
|
812
|
+
|
|
813
|
+
case class Person(name: String, age: Int)
|
|
814
|
+
object Person {
|
|
815
|
+
implicit val schema: Schema[Person] = Schema.derived[Person]
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Obtain lens (Scala 2 example)
|
|
819
|
+
val nameLens: Lens[Person, String] =
|
|
820
|
+
Person.schema.reflect.asRecord.get.lensByName[String]("name").get
|
|
821
|
+
|
|
822
|
+
val person = Person("Alice", 30)
|
|
823
|
+
|
|
824
|
+
// Get
|
|
825
|
+
val name: String = nameLens.get(person) // "Alice"
|
|
826
|
+
|
|
827
|
+
// Modify
|
|
828
|
+
val upper: Person = nameLens.modify(person, _.toUpperCase) // Person("ALICE", 30)
|
|
829
|
+
|
|
830
|
+
// Replace — note: ZIO Blocks uses replace, not set (unlike Monocle and many other optics libraries)
|
|
831
|
+
val renamed: Person = nameLens.replace(person, "Bob") // Person("Bob", 30)
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
For sealed traits, use `Prism`:
|
|
835
|
+
|
|
836
|
+
```scala
|
|
837
|
+
import zio.blocks.schema._
|
|
838
|
+
|
|
839
|
+
sealed trait Shape
|
|
840
|
+
case class Circle(radius: Double) extends Shape
|
|
841
|
+
case class Rectangle(w: Double, h: Double) extends Shape
|
|
842
|
+
|
|
843
|
+
object Shape {
|
|
844
|
+
implicit val schema: Schema[Shape] = Schema.derived[Shape]
|
|
845
|
+
|
|
846
|
+
val circlePrism: Prism[Shape, Circle] =
|
|
847
|
+
schema.reflect.asVariant.get.prismByName[Circle]("Circle").get
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
val shape: Shape = Circle(5.0)
|
|
851
|
+
Shape.circlePrism.getOption(shape) // Some(Circle(5.0))
|
|
852
|
+
Shape.circlePrism.reverseGet(Circle(3.0)) // Circle(3.0): Shape
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
### Schema Expressions (New in ZIO Blocks)
|
|
856
|
+
|
|
857
|
+
ZIO Blocks introduces `SchemaExpr[S, A]`, a typed expression language built on top of optics. There is no equivalent in ZIO Schema. These allow you to build inspectable, composable predicates and computations:
|
|
858
|
+
|
|
859
|
+
```scala
|
|
860
|
+
import zio.blocks.schema._
|
|
861
|
+
|
|
862
|
+
case class Product(name: String, price: Double, inStock: Boolean)
|
|
863
|
+
object Product {
|
|
864
|
+
implicit val schema: Schema[Product] = Schema.derived[Product]
|
|
865
|
+
val priceLens: Lens[Product, Double] =
|
|
866
|
+
schema.reflect.asRecord.get.lensByName[Double]("price").get
|
|
867
|
+
val inStockLens: Lens[Product, Boolean] =
|
|
868
|
+
schema.reflect.asRecord.get.lensByName[Boolean]("inStock").get
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Build a typed predicate expression
|
|
872
|
+
val cheapAndInStock: SchemaExpr[Product, Boolean] =
|
|
873
|
+
(Product.priceLens < 100.0) && (Product.inStockLens === true)
|
|
874
|
+
|
|
875
|
+
// Evaluate against data — eval returns Either[OpticCheck, Seq[A]]
|
|
876
|
+
// Right(Seq(true)) on success
|
|
877
|
+
// Left(OpticCheck) if a prism in the path did not match
|
|
878
|
+
val p = Product("Widget", 49.99, inStock = true)
|
|
879
|
+
cheapAndInStock.eval(p) // Right(Seq(true))
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
---
|
|
883
|
+
|
|
884
|
+
## Migrating Diff and Patch
|
|
885
|
+
|
|
886
|
+
### Diff
|
|
887
|
+
|
|
888
|
+
ZIO Schema uses `Differ.fromSchema(schema).diff(a, b)` or the convenience method `schema.diff(a, b)`. ZIO Blocks Schema uses the same convenience method:
|
|
889
|
+
|
|
890
|
+
```scala
|
|
891
|
+
// ZIO Schema 1.x
|
|
892
|
+
val patch: Patch[Person] = Person.schema.diff(person1, person2)
|
|
893
|
+
|
|
894
|
+
// ZIO Blocks Schema
|
|
895
|
+
val patch: Patch[Person] = Person.schema.diff(person1, person2)
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
The call site is identical, but the underlying `Patch` types are different.
|
|
899
|
+
|
|
900
|
+
### Patch Application
|
|
901
|
+
|
|
902
|
+
```scala
|
|
903
|
+
// ZIO Schema 1.x
|
|
904
|
+
val result: Either[String, Person] = Person.schema.patch(person, patch)
|
|
905
|
+
|
|
906
|
+
// ZIO Blocks Schema
|
|
907
|
+
val result: Either[SchemaError, Person] = Person.schema.patch(person, patch)
|
|
908
|
+
// or equivalently:
|
|
909
|
+
val result: Either[SchemaError, Person] = patch.apply(person, PatchMode.Strict)
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
The error type changes from `String` to `SchemaError`.
|
|
913
|
+
|
|
914
|
+
### Creating Patches Programmatically
|
|
915
|
+
|
|
916
|
+
ZIO Schema has no structured API for creating patches programmatically. ZIO Blocks Schema provides one through `Patch` smart constructors:
|
|
917
|
+
|
|
918
|
+
```scala
|
|
919
|
+
import zio.blocks.schema._
|
|
920
|
+
import zio.blocks.schema.patch._
|
|
921
|
+
|
|
922
|
+
case class Person(name: String, age: Int)
|
|
923
|
+
object Person {
|
|
924
|
+
implicit val schema: Schema[Person] = Schema.derived[Person]
|
|
925
|
+
val nameLens: Lens[Person, String] =
|
|
926
|
+
schema.reflect.asRecord.get.lensByName[String]("name").get
|
|
927
|
+
val ageLens: Lens[Person, Int] =
|
|
928
|
+
schema.reflect.asRecord.get.lensByName[Int]("age").get
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Set a field
|
|
932
|
+
val renamePatch: Patch[Person] = Patch.set(Person.nameLens, "Bob")
|
|
933
|
+
|
|
934
|
+
// Compose patches
|
|
935
|
+
val combined: Patch[Person] = renamePatch ++ Patch.set(Person.ageLens, 31)
|
|
936
|
+
|
|
937
|
+
// Apply
|
|
938
|
+
val updated: Either[SchemaError, Person] = combined(Person("Alice", 30), PatchMode.Strict)
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
---
|
|
942
|
+
|
|
943
|
+
## Migrating Type Class Derivation
|
|
944
|
+
|
|
945
|
+
### Before (ZIO Schema 1.x)
|
|
946
|
+
|
|
947
|
+
ZIO Schema does not have a general `Deriver[TC]` interface. Each codec module implements its own derivation logic independently. There is no way to derive an arbitrary user-defined type class from a `Schema[A]`.
|
|
948
|
+
|
|
949
|
+
### After (ZIO Blocks Schema)
|
|
950
|
+
|
|
951
|
+
ZIO Blocks Schema introduces `Deriver[TC]`, a unified interface for deriving any type class `TC[_]` from a schema. This replaces ad-hoc codec-specific derivation:
|
|
952
|
+
|
|
953
|
+
```scala
|
|
954
|
+
import zio.blocks.schema._
|
|
955
|
+
import zio.blocks.schema.binding._
|
|
956
|
+
import zio.blocks.schema.derive.Deriver
|
|
957
|
+
import zio.blocks.docs.Doc
|
|
958
|
+
import zio.blocks.typeid.TypeId
|
|
959
|
+
|
|
960
|
+
// Define a type class
|
|
961
|
+
trait Show[A] {
|
|
962
|
+
def show(a: A): String
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// Implement Deriver[Show]
|
|
966
|
+
object DeriveShow extends Deriver[Show] {
|
|
967
|
+
|
|
968
|
+
def derivePrimitive[A](
|
|
969
|
+
primitiveType: PrimitiveType[A],
|
|
970
|
+
typeId: TypeId[A],
|
|
971
|
+
binding: Binding[BindingType.Primitive, A],
|
|
972
|
+
doc: Doc,
|
|
973
|
+
modifiers: Seq[Modifier.Reflect],
|
|
974
|
+
defaultValue: Option[A],
|
|
975
|
+
examples: Seq[A]
|
|
976
|
+
): Lazy[Show[A]] = Lazy {
|
|
977
|
+
new Show[A] {
|
|
978
|
+
def show(a: A): String = a.toString
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
def deriveRecord[F[_, _], A](
|
|
983
|
+
fields: IndexedSeq[Term[F, A, _]],
|
|
984
|
+
typeId: TypeId[A],
|
|
985
|
+
binding: Binding[BindingType.Record, A],
|
|
986
|
+
doc: Doc,
|
|
987
|
+
modifiers: Seq[Modifier.Reflect],
|
|
988
|
+
defaultValue: Option[A],
|
|
989
|
+
examples: Seq[A]
|
|
990
|
+
)(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[A]] = {
|
|
991
|
+
val recordBinding = binding.asInstanceOf[Binding.Record[A]]
|
|
992
|
+
val recordFields = fields.asInstanceOf[IndexedSeq[Term[Binding, A, _]]]
|
|
993
|
+
val recordReflect = new Reflect.Record[Binding, A](recordFields, typeId, recordBinding, doc, modifiers)
|
|
994
|
+
Lazy {
|
|
995
|
+
new Show[A] {
|
|
996
|
+
private lazy val resolvedShows: IndexedSeq[Show[Any]] =
|
|
997
|
+
fields.map(f => D.instance(f.value.metadata).asInstanceOf[Lazy[Show[Any]]].force)
|
|
998
|
+
def show(a: A): String = {
|
|
999
|
+
val regs = Registers(recordReflect.usedRegisters)
|
|
1000
|
+
recordBinding.deconstructor.deconstruct(regs, RegisterOffset.Zero, a)
|
|
1001
|
+
val fieldStrs = fields.indices.map { i =>
|
|
1002
|
+
val v = recordReflect.registers(i).get(regs, RegisterOffset.Zero)
|
|
1003
|
+
s"${fields(i).name} = ${resolvedShows(i).show(v)}"
|
|
1004
|
+
}
|
|
1005
|
+
s"${typeId.name}(${fieldStrs.mkString(", ")})"
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// ... deriveVariant, deriveSequence, deriveMap, deriveDynamic, deriveWrapper
|
|
1012
|
+
// (see the DeriveShowExample in the examples module for full implementation)
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Derive Show for any type
|
|
1016
|
+
case class Person(name: String, age: Int)
|
|
1017
|
+
object Person {
|
|
1018
|
+
implicit val schema: Schema[Person] = Schema.derived[Person]
|
|
1019
|
+
implicit val show: Show[Person] = schema.derive(DeriveShow)
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
Person.show.show(Person("Alice", 30)) // Person(name = "Alice", age = 30)
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
---
|
|
1026
|
+
|
|
1027
|
+
## Migrating Validation
|
|
1028
|
+
|
|
1029
|
+
### Before (ZIO Schema 1.x)
|
|
1030
|
+
|
|
1031
|
+
ZIO Schema uses a composable `Validation[A]` ADT as an annotation, attached via `@validate(...)` or `.validation(...)`:
|
|
1032
|
+
|
|
1033
|
+
```scala
|
|
1034
|
+
import zio.schema._
|
|
1035
|
+
import zio.schema.validation._
|
|
1036
|
+
import zio.schema.annotation._
|
|
1037
|
+
|
|
1038
|
+
case class User(
|
|
1039
|
+
@validate(Validation.greaterThan(0)) age: Int,
|
|
1040
|
+
@validate(Validation.minLength(3)) name: String
|
|
1041
|
+
)
|
|
1042
|
+
object User {
|
|
1043
|
+
implicit val schema: Schema[User] = DeriveSchema.gen
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
schema.validate(User(-1, "Al"))
|
|
1047
|
+
// Returns Chunk[ValidationError] with violations
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
### After (ZIO Blocks Schema)
|
|
1051
|
+
|
|
1052
|
+
ZIO Blocks Schema has a simpler, non-composable `Validation[A]` that is embedded inside `PrimitiveType[A]` and checked during `DynamicSchema.check`. It is not composable with `And`/`Or`/`Not`:
|
|
1053
|
+
|
|
1054
|
+
```scala
|
|
1055
|
+
import zio.blocks.schema._
|
|
1056
|
+
|
|
1057
|
+
// Validation is checked during DynamicSchema.check — not during fromDynamicValue
|
|
1058
|
+
val dynSchema = Schema[Int].toDynamicSchema
|
|
1059
|
+
|
|
1060
|
+
val valid = DynamicValue.Primitive(PrimitiveValue.Int(5))
|
|
1061
|
+
val invalid = DynamicValue.Primitive(PrimitiveValue.Int(-1))
|
|
1062
|
+
|
|
1063
|
+
dynSchema.conforms(valid) // true
|
|
1064
|
+
dynSchema.conforms(invalid) // true (no validation constraint on the base Int schema)
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
For validated types, use `Schema[A].transform` with a throwing `to` function, which signals failure during `fromDynamicValue`:
|
|
1068
|
+
|
|
1069
|
+
```scala
|
|
1070
|
+
import zio.blocks.schema._
|
|
1071
|
+
|
|
1072
|
+
// Validated positive integer
|
|
1073
|
+
val positiveIntSchema: Schema[Int] =
|
|
1074
|
+
Schema[Int].transform(
|
|
1075
|
+
to = n => if (n > 0) n else throw SchemaError.validationFailed("Must be positive"),
|
|
1076
|
+
from = identity
|
|
1077
|
+
)
|
|
1078
|
+
|
|
1079
|
+
positiveIntSchema.fromDynamicValue(
|
|
1080
|
+
DynamicValue.Primitive(PrimitiveValue.Int(-1))
|
|
1081
|
+
)
|
|
1082
|
+
// Left(SchemaError: Must be positive)
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
For struct-level validation across multiple fields, implement validation in the `to` function of a wrapper:
|
|
1086
|
+
|
|
1087
|
+
```scala
|
|
1088
|
+
import zio.blocks.schema._
|
|
1089
|
+
|
|
1090
|
+
final case class AgeRange(min: Int, max: Int)
|
|
1091
|
+
object AgeRange {
|
|
1092
|
+
implicit val schema: Schema[AgeRange] = Schema.derived[AgeRange]
|
|
1093
|
+
// Schema-level validation is handled through the derived schema's
|
|
1094
|
+
// DynamicSchema.check, or by adding custom validation in a wrapping transform.
|
|
1095
|
+
}
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
:::info
|
|
1099
|
+
If you rely heavily on ZIO Schema's composable validation (chaining `And`, `Or`, `Not`, `Transform` validators), you will need to implement that logic in the `to` function of a `Schema.transform` wrapper, or in application-level validation code. ZIO Blocks Schema's built-in `Validation` is deliberately simpler: it covers the most common primitive constraints without the complexity of a full combinator library.
|
|
1100
|
+
:::
|
|
1101
|
+
|
|
1102
|
+
---
|
|
1103
|
+
|
|
1104
|
+
## Migrating the Fail Schema
|
|
1105
|
+
|
|
1106
|
+
ZIO Schema provides `Schema.fail[A](message: String)` to represent the absence of schema information:
|
|
1107
|
+
|
|
1108
|
+
```scala
|
|
1109
|
+
// ZIO Schema 1.x
|
|
1110
|
+
val missing: Schema[MyType] = Schema.fail("No schema available for MyType")
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
ZIO Blocks Schema has no equivalent `Fail` schema node. The recommended approach is to leave the implicit schema undefined and let the compiler report the missing instance, or to throw from a type class derivation:
|
|
1114
|
+
|
|
1115
|
+
```scala
|
|
1116
|
+
// ZIO Blocks Schema — no Schema.fail; use a compile error or a runtime exception approach
|
|
1117
|
+
// If you need a runtime sentinel, use Schema[DynamicValue] or create a minimal placeholder:
|
|
1118
|
+
val placeholder: Schema[DynamicValue] = Schema[DynamicValue]
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
---
|
|
1122
|
+
|
|
1123
|
+
## Summary of Missing Features
|
|
1124
|
+
|
|
1125
|
+
The following ZIO Schema features do not yet have equivalents in ZIO Blocks Schema:
|
|
1126
|
+
|
|
1127
|
+
| Feature | Status |
|
|
1128
|
+
|---|---|
|
|
1129
|
+
| `Schema.fail` / fail schemas | Not available |
|
|
1130
|
+
| `Schema.migrate[B]` / `Schema.coerce[B]` | Not available — schema migration is planned |
|
|
1131
|
+
| `MetaSchema` / schema serialization | Partial — `DynamicSchema` covers structural inspection; full schema round-trip is not available |
|
|
1132
|
+
| `Fallback[A, B]` schema | Not available |
|
|
1133
|
+
| `NonEmptyChunk` / `NonEmptyMap` schemas | Not available — use wrapper types |
|
|
1134
|
+
| `Schema.Singleton` / singleton schemas | Not available |
|
|
1135
|
+
| `DynamicValue.BothValue` / `DynamicValue.DynamicAst` | Not available |
|
|
1136
|
+
| Composable `Validation` (`And`, `Or`, `Not`) | Not available — use `transform` with throwing functions |
|
|
1137
|
+
| Streaming codec methods (`streamEncoder`, `streamDecoder`) | Not available — wrap codecs in your effect system |
|
|
1138
|
+
| ZIO `Chunk` (from `zio-core`) | Replaced by `zio.blocks.chunk.Chunk` |
|
|
1139
|
+
|
|
1140
|
+
---
|
|
1141
|
+
|
|
1142
|
+
## Running the Examples
|
|
1143
|
+
|
|
1144
|
+
All code from this guide is available as runnable examples in the `schema-examples` module.
|
|
1145
|
+
|
|
1146
|
+
**1. Clone the repository and navigate to the project:**
|
|
1147
|
+
|
|
1148
|
+
```bash
|
|
1149
|
+
git clone https://github.com/zio/zio-blocks.git
|
|
1150
|
+
cd zio-blocks
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
**2. Run individual examples with sbt:**
|
|
1154
|
+
|
|
1155
|
+
```bash
|
|
1156
|
+
# Step 1: Schema derivation, primitives, and DynamicValue roundtrip
|
|
1157
|
+
sbt "schema-examples/runMain ziosschemamigration.Step1SchemaDerivedAndPrimitives"
|
|
1158
|
+
|
|
1159
|
+
# Step 2: Modifiers and transform (annotations, newtypes)
|
|
1160
|
+
sbt "schema-examples/runMain ziosschemamigration.Step2ModifiersAndTransform"
|
|
1161
|
+
|
|
1162
|
+
# Step 3: Optics (Lens, Prism) and DynamicSchema validation
|
|
1163
|
+
sbt "schema-examples/runMain ziosschemamigration.Step3OpticsAndDynamicSchema"
|
|
1164
|
+
|
|
1165
|
+
# Step 4: Diff and patch
|
|
1166
|
+
sbt "schema-examples/runMain ziosschemamigration.Step4DiffAndPatch"
|
|
1167
|
+
|
|
1168
|
+
# Complete example: end-to-end e-commerce domain
|
|
1169
|
+
sbt "schema-examples/runMain ziosschemamigration.CompleteMigrationExample"
|
|
1170
|
+
|
|
1171
|
+
# Type class derivation — deriving Show from a Schema
|
|
1172
|
+
sbt "schema-examples/runMain typeclassderivation.DeriveShowExample"
|
|
1173
|
+
|
|
1174
|
+
# Type class derivation — deriving a random generator from a Schema
|
|
1175
|
+
sbt "schema-examples/runMain typeclassderivation.DeriveGenExample"
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
**3. Or compile all examples at once:**
|
|
1179
|
+
|
|
1180
|
+
```bash
|
|
1181
|
+
sbt "schema-examples/compile"
|
|
1182
|
+
```
|
|
1183
|
+
|
|
1184
|
+
---
|
|
1185
|
+
|
|
1186
|
+
## Going Further
|
|
1187
|
+
|
|
1188
|
+
- [Schema Reference](../reference/schema.md) — full `Schema[A]` API
|
|
1189
|
+
- [Reflect Reference](../reference/reflect.md) — the `Reflect[F, A]` node types
|
|
1190
|
+
- [Binding Reference](../reference/binding.md) — constructors, deconstructors, and the register system
|
|
1191
|
+
- [Optics Reference](../reference/optics.md) — `Lens`, `Prism`, `Optional`, `Traversal`
|
|
1192
|
+
- [Type Class Derivation Guide](../reference/type-class-derivation.md) — implementing `Deriver[TC]`
|
|
1193
|
+
- [Codec Reference](../reference/codec.md) — the `Format` and `Codec` infrastructure
|
|
1194
|
+
- [DynamicValue Reference](../reference/dynamic-value.md) — the `DynamicValue` API
|
|
1195
|
+
- [Validation Reference](../reference/validation.md) — built-in validation constraints
|