@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
package/index.md
CHANGED
|
@@ -81,14 +81,14 @@ val thriftCodec = Schema[Person].derive(ThriftFormat) // Thrift
|
|
|
81
81
|
### Installation
|
|
82
82
|
|
|
83
83
|
```scala
|
|
84
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
84
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.27"
|
|
85
85
|
|
|
86
86
|
// Optional format modules:
|
|
87
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.
|
|
88
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.
|
|
89
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.
|
|
90
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.
|
|
91
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.
|
|
87
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.27"
|
|
88
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.27"
|
|
89
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.27"
|
|
90
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.27"
|
|
91
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.27"
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
### Example: Optics
|
|
@@ -143,7 +143,7 @@ Chunk is designed for:
|
|
|
143
143
|
### Installation
|
|
144
144
|
|
|
145
145
|
```scala
|
|
146
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-chunk" % "0.0.
|
|
146
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-chunk" % "0.0.27"
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
### Example
|
|
@@ -227,11 +227,12 @@ Scope.global.scoped { scope =>
|
|
|
227
227
|
- **Built-in Dependency Injection**: Wire up your application with `Resource.from[T](wires*)` for automatic constructor-based DI.
|
|
228
228
|
- **AutoCloseable Integration**: Resources implementing `AutoCloseable` have `close()` registered automatically.
|
|
229
229
|
- **Unscoped Constraint**: The `scoped` method requires `Unscoped[A]` evidence on the return type, ensuring only pure data (not resources or closures) can escape.
|
|
230
|
+
- **Actionable Runtime Errors**: If a scope reference escapes and is used after closing, `allocate`, `open()`, and `$` throw `IllegalStateException` with a detailed message explaining what went wrong, the common causes, and how to fix it—no silent null returns.
|
|
230
231
|
|
|
231
232
|
### Installation
|
|
232
233
|
|
|
233
234
|
```scala
|
|
234
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-scope" % "0.0.
|
|
235
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-scope" % "0.0.27"
|
|
235
236
|
```
|
|
236
237
|
|
|
237
238
|
### Example: Basic Resource Management
|
|
@@ -334,7 +335,7 @@ Generating documentation, README files, or any Markdown content programmatically
|
|
|
334
335
|
### Installation
|
|
335
336
|
|
|
336
337
|
```scala
|
|
337
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.
|
|
338
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.27"
|
|
338
339
|
```
|
|
339
340
|
|
|
340
341
|
### Example
|
|
@@ -418,7 +419,7 @@ Compile-time type identity with rich metadata. TypeId captures comprehensive inf
|
|
|
418
419
|
### Installation
|
|
419
420
|
|
|
420
421
|
```scala
|
|
421
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-typeid" % "0.0.
|
|
422
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-typeid" % "0.0.27"
|
|
422
423
|
```
|
|
423
424
|
|
|
424
425
|
### Example
|
|
@@ -461,7 +462,7 @@ A type-indexed heterogeneous collection that stores values by their types with c
|
|
|
461
462
|
### Installation
|
|
462
463
|
|
|
463
464
|
```scala
|
|
464
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.
|
|
465
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.27"
|
|
465
466
|
```
|
|
466
467
|
|
|
467
468
|
### Example
|
|
@@ -557,6 +558,7 @@ ZIO Blocks supports **Scala 2.13** and **Scala 3.x** with full source compatibil
|
|
|
557
558
|
### Data Operations
|
|
558
559
|
|
|
559
560
|
- [Patching](./reference/patch.md) - Serializable data transformations
|
|
561
|
+
- [SchemaError](./reference/schema-error.md) - Structured error type for schema operations
|
|
560
562
|
- [Validation](./reference/validation.md) - Data validation and error handling
|
|
561
563
|
- [Schema Evolution](./reference/schema-evolution.md) - Migration and compatibility
|
|
562
564
|
|
|
@@ -571,6 +573,7 @@ ZIO Blocks supports **Scala 2.13** and **Scala 3.x** with full source compatibil
|
|
|
571
573
|
|
|
572
574
|
### Guides
|
|
573
575
|
|
|
576
|
+
- [Migrating from ZIO Schema](./guides/zio-schema-migration.md) - Step-by-step guide to migrating from ZIO Schema 1.x to ZIO Blocks Schema
|
|
574
577
|
- [Query DSL Part 1: Expressions](./guides/query-dsl-reified-optics.md) - Build type-safe, composable query expressions
|
|
575
578
|
- [Query DSL Part 2: SQL Generation](./guides/query-dsl-sql.md) - Translate query expressions into SQL
|
|
576
579
|
- [Query DSL Part 3: Extending the Expression Language](./guides/query-dsl-extending.md) - Add custom operators beyond SchemaExpr
|
package/package.json
CHANGED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: allows
|
|
3
|
+
title: "Allows"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`Allows[A, S]` is a compile-time capability token that proves, at the call site, that type `A` satisfies the structural grammar `S`.
|
|
7
|
+
|
|
8
|
+
`Allows` does **not** require or use `Schema[A]`. It inspects the Scala type structure of `A` directly at compile time, using nothing but the Scala type system. Any `Schema[A]` that appears alongside `Allows` in examples is the library author's own separate constraint — it is not imposed by `Allows` itself.
|
|
9
|
+
|
|
10
|
+
## Motivation
|
|
11
|
+
|
|
12
|
+
ZIO Blocks (ZIO Schema 2) gives library authors a powerful way to build data-oriented DSLs. A library can accept `A: Schema` and use the schema at runtime to serialize, deserialize, query, or transform values of `A`. But `Allows` is useful even without a Schema — it can enforce structural preconditions on *any* generic function.
|
|
13
|
+
|
|
14
|
+
The gap is **structural preconditions**. Many generic functions only make sense for a subset of types:
|
|
15
|
+
|
|
16
|
+
- A CSV serializer requires flat records of scalars.
|
|
17
|
+
- An RDBMS layer cannot handle nested records as column values.
|
|
18
|
+
- An event bus expects a sealed trait of flat record cases.
|
|
19
|
+
- A JSON document store allows arbitrarily nested records but not `DynamicValue` leaves.
|
|
20
|
+
|
|
21
|
+
Today, these constraints can only be checked at runtime, producing confusing errors deep inside library internals.
|
|
22
|
+
|
|
23
|
+
`Allows[A, S]` closes this gap: the constraint is verified at the **call site**, at compile time, with precise, path-aware error messages and concrete fix suggestions.
|
|
24
|
+
|
|
25
|
+
## The Upper Bound Semantics
|
|
26
|
+
|
|
27
|
+
`Allows[A, S]` is an upper bound. A type `A` that uses only a strict subset of what `S` permits also satisfies it — just as `A <: Foo` does not require that `A` uses every method of `Foo`.
|
|
28
|
+
|
|
29
|
+
```scala
|
|
30
|
+
// Allows[UserRow, Record[Primitive | Optional[Primitive]]] is satisfied even if
|
|
31
|
+
// UserRow has no Option fields — the Optional branch is simply never needed.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Grammar Nodes
|
|
35
|
+
|
|
36
|
+
All grammar nodes extend `Allows.Structural`.
|
|
37
|
+
|
|
38
|
+
| Node | Matches |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `Primitive` | **Any** scalar — catch-all for all 30 Schema 2 primitive types |
|
|
41
|
+
| `Primitive.Boolean` | `scala.Boolean` only |
|
|
42
|
+
| `Primitive.Int` | `scala.Int` only |
|
|
43
|
+
| `Primitive.Long` | `scala.Long` only |
|
|
44
|
+
| `Primitive.Double` | `scala.Double` only |
|
|
45
|
+
| `Primitive.Float` | `scala.Float` only |
|
|
46
|
+
| `Primitive.String` | `java.lang.String` only |
|
|
47
|
+
| `Primitive.BigDecimal` | `scala.BigDecimal` only |
|
|
48
|
+
| `Primitive.BigInt` | `scala.BigInt` only |
|
|
49
|
+
| `Primitive.Unit` | `scala.Unit` only |
|
|
50
|
+
| `Primitive.Byte` | `scala.Byte` only |
|
|
51
|
+
| `Primitive.Short` | `scala.Short` only |
|
|
52
|
+
| `Primitive.Char` | `scala.Char` only |
|
|
53
|
+
| `Primitive.UUID` | `java.util.UUID` only |
|
|
54
|
+
| `Primitive.Currency` | `java.util.Currency` only |
|
|
55
|
+
| `Primitive.Instant` / `LocalDate` / `LocalDateTime` / … | Each specific `java.time.*` type |
|
|
56
|
+
| `Record[A]` | A case class / product type whose every field satisfies `A`. Vacuously true for zero-field records. Sealed traits and enums are **automatically unwrapped**: each case is checked individually, so no `Variant` node is needed. |
|
|
57
|
+
| `Sequence[A]` | `List`, `Vector`, `Set`, `Array`, `Chunk`, … whose element type satisfies `A` |
|
|
58
|
+
| `Map[K, V]` | `Map`, `HashMap`, … whose key satisfies `K` and value satisfies `V` |
|
|
59
|
+
| `Optional[A]` | `Option[X]` where the inner type `X` satisfies `A` |
|
|
60
|
+
| `Wrapped[A]` | A ZIO Prelude `Newtype`/`Subtype` wrapper whose underlying type satisfies `A` |
|
|
61
|
+
| `Dynamic` | `DynamicValue` — the schema-less escape hatch |
|
|
62
|
+
| `Self` | Recursive self-reference back to the entire enclosing `Allows[A, S]` grammar |
|
|
63
|
+
| `` `\|` `` | Union of two grammar nodes: `A \| B`. In Scala 2 write `` A `\|` B `` in infix position. |
|
|
64
|
+
|
|
65
|
+
Every specific `Primitive.Xxx` node also satisfies the catch-all `Primitive`. This means a type annotated with `Primitive.Int` is valid wherever `Primitive` or `Primitive | Primitive.Long` is required.
|
|
66
|
+
|
|
67
|
+
## Specific Primitives
|
|
68
|
+
|
|
69
|
+
The `Primitive` parent class is the catch-all: it accepts any of the 30 Schema 2 primitive types. For stricter control — such as when the target serialisation format only supports a subset — use the specific subtype nodes in `Allows.Primitive`:
|
|
70
|
+
|
|
71
|
+
```scala
|
|
72
|
+
import zio.blocks.schema.comptime.Allows
|
|
73
|
+
import Allows._
|
|
74
|
+
|
|
75
|
+
// Only JSON-representable scalars (no UUID, Char, java.time.*)
|
|
76
|
+
type JsonPrimitive =
|
|
77
|
+
Primitive.Boolean | Primitive.Int | Primitive.Long |
|
|
78
|
+
Primitive.Double | Primitive.String | Primitive.BigDecimal |
|
|
79
|
+
Primitive.BigInt | Primitive.Unit
|
|
80
|
+
|
|
81
|
+
def toJson[A](doc: A)(using Allows[A, Record[JsonPrimitive | Self]]): String = ???
|
|
82
|
+
|
|
83
|
+
// Only numeric types
|
|
84
|
+
type Numeric = Primitive.Int | Primitive.Long | Primitive.Double | Primitive.Float |
|
|
85
|
+
Primitive.BigInt | Primitive.BigDecimal
|
|
86
|
+
|
|
87
|
+
def aggregate[A](data: A)(using Allows[A, Record[Numeric]]): Double = ???
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A type annotated with `Primitive.Int` satisfies `Primitive` (the catch-all) because `Primitive.Int extends Primitive`:
|
|
91
|
+
|
|
92
|
+
```scala
|
|
93
|
+
import zio.blocks.schema.comptime.Allows
|
|
94
|
+
import Allows._
|
|
95
|
+
|
|
96
|
+
val ev: Allows[Int, Primitive] = implicitly // Primitive (catch-all) — ✓
|
|
97
|
+
val sp: Allows[Int, Primitive.Int] = implicitly // Primitive.Int (specific) — ✓
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### JSON Document Store Example
|
|
101
|
+
|
|
102
|
+
JSON's primitive value set is `null | boolean | number | string`. Types such as `UUID`, `Char`, and all `java.time.*` types have no native JSON representation and must be encoded as strings at the application layer. Using `JsonPrimitive` instead of the catch-all `Primitive` enforces this at compile time.
|
|
103
|
+
|
|
104
|
+
A JSON document grammar is straightforward: a JSON value is either a record (JSON object) or a sequence (JSON array), and `Self` handles all nesting:
|
|
105
|
+
|
|
106
|
+
```scala
|
|
107
|
+
import zio.blocks.schema.comptime.Allows
|
|
108
|
+
import Allows._
|
|
109
|
+
|
|
110
|
+
type JsonPrimitive =
|
|
111
|
+
Primitive.Boolean | Primitive.Int | Primitive.Long | Primitive.Double |
|
|
112
|
+
Primitive.String | Primitive.BigDecimal | Primitive.BigInt | Primitive.Unit
|
|
113
|
+
|
|
114
|
+
type Json = Record[JsonPrimitive | Self] | Sequence[JsonPrimitive | Self]
|
|
115
|
+
|
|
116
|
+
def toJson[A](doc: A)(using Allows[A, Json]): String = ???
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`Self` recurses back to `Json` at every nested position, so `List[String]` satisfies `Sequence[JsonPrimitive | Self]` (String is JsonPrimitive), `List[Author]` satisfies it too (Author satisfies `Record[JsonPrimitive | Self]` via Self), and top-level arrays work directly.
|
|
120
|
+
|
|
121
|
+
A type with a UUID or Instant field fails at compile time:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
[error] Schema shape violation at WithUUID.id: found Primitive(java.util.UUID),
|
|
125
|
+
required Primitive.Boolean | Primitive.Int | ... | Primitive.String | ...
|
|
126
|
+
UUID is not a JSON-native type — encode it as Primitive.String.
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Union Syntax
|
|
130
|
+
|
|
131
|
+
Union types express "or" in the grammar.
|
|
132
|
+
|
|
133
|
+
**Scala 3** uses native union type syntax:
|
|
134
|
+
|
|
135
|
+
```scala
|
|
136
|
+
import zio.blocks.schema.comptime.Allows
|
|
137
|
+
import Allows._
|
|
138
|
+
|
|
139
|
+
def writeCsv[A](rows: Seq[A])(using
|
|
140
|
+
Allows[A, Record[Primitive | Optional[Primitive]]]
|
|
141
|
+
): Unit = ???
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Scala 2** uses the `` `\|` `` infix operator from `Allows`:
|
|
145
|
+
|
|
146
|
+
```scala
|
|
147
|
+
import zio.blocks.schema.comptime.Allows
|
|
148
|
+
import Allows._
|
|
149
|
+
|
|
150
|
+
def writeCsv[A](rows: Seq[A])(implicit
|
|
151
|
+
ev: Allows[A, Record[Primitive | Optional[Primitive]]]
|
|
152
|
+
): Unit = ???
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Both spellings compile and produce the same semantic behavior. The grammar is identical — the only difference is how the union type is expressed.
|
|
156
|
+
|
|
157
|
+
## Use Cases
|
|
158
|
+
|
|
159
|
+
### Flat Record (CSV, RDBMS Row)
|
|
160
|
+
|
|
161
|
+
```scala
|
|
162
|
+
import zio.blocks.schema.Schema
|
|
163
|
+
import zio.blocks.schema.comptime.Allows
|
|
164
|
+
import Allows._
|
|
165
|
+
|
|
166
|
+
// Flat record: only primitives and optional primitives allowed
|
|
167
|
+
def writeCsv[A: Schema](rows: Seq[A])(using
|
|
168
|
+
Allows[A, Record[Primitive | Optional[Primitive]]]
|
|
169
|
+
): Unit = ???
|
|
170
|
+
|
|
171
|
+
// RDBMS INSERT: primitives, optional primitives, or string-keyed maps (JSONB)
|
|
172
|
+
def insert[A: Schema](value: A)(using
|
|
173
|
+
Allows[A, Record[Primitive | Optional[Primitive] | Allows.Map[Primitive, Primitive]]]
|
|
174
|
+
): String = ???
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
If a user passes a type with nested records, they get a precise compile-time error:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
[error] Schema shape violation at UserWithAddress.address: found Record(Address),
|
|
181
|
+
required Primitive | Optional[Primitive] | Map[Primitive, Primitive]
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Event Bus / Message Broker
|
|
185
|
+
|
|
186
|
+
Published events are typically sealed traits of flat record cases. No `Variant` node is needed — sealed traits are automatically unwrapped:
|
|
187
|
+
|
|
188
|
+
```scala
|
|
189
|
+
import zio.blocks.schema.Schema
|
|
190
|
+
import zio.blocks.schema.comptime.Allows
|
|
191
|
+
import Allows._
|
|
192
|
+
|
|
193
|
+
// DomainEvent is a sealed trait; its cases must each satisfy Record[Primitive | Sequence[Primitive]]
|
|
194
|
+
def publish[A: Schema](event: A)(using
|
|
195
|
+
Allows[A, Record[Primitive | Optional[Primitive] | Sequence[Primitive]]]
|
|
196
|
+
): Unit = ???
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
If a case of the sealed trait has a nested record field, the error names that case and field:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
[error] Schema shape violation at DomainEvent.OrderPlaced.items.<element>:
|
|
203
|
+
found Record(OrderItem), required Primitive | Optional[Primitive] | Sequence[Primitive]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### JSON Document Store (Recursive)
|
|
207
|
+
|
|
208
|
+
A document store accepts arbitrarily nested records but not `DynamicValue` leaves. The `Self` node expresses the recursive grammar:
|
|
209
|
+
|
|
210
|
+
```scala
|
|
211
|
+
import zio.blocks.schema.Schema
|
|
212
|
+
import zio.blocks.schema.comptime.Allows
|
|
213
|
+
import Allows._
|
|
214
|
+
|
|
215
|
+
type JsonDocument =
|
|
216
|
+
Record[Primitive | Self | Optional[Primitive | Self] | Sequence[Primitive | Self] | Allows.Map[Primitive, Primitive | Self]]
|
|
217
|
+
|
|
218
|
+
def toJson[A: Schema](doc: A)(implicit ev: Allows[A, JsonDocument]): String = ???
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
This grammar allows:
|
|
222
|
+
- `case class Author(name: String, email: String)` — Record[Primitive] ✓
|
|
223
|
+
- `case class Book(title: String, author: Author, tags: List[String])` — Record with Self-nested record and Sequence[Primitive] ✓
|
|
224
|
+
- `case class Category(name: String, subcategories: List[Category])` — recursive ✓
|
|
225
|
+
|
|
226
|
+
But rejects:
|
|
227
|
+
- `case class Bad(name: String, payload: DynamicValue)` — DynamicValue is not in the grammar ✗
|
|
228
|
+
|
|
229
|
+
### GraphQL / Tree Structures (Self)
|
|
230
|
+
|
|
231
|
+
```scala
|
|
232
|
+
import zio.blocks.schema.Schema
|
|
233
|
+
import zio.blocks.schema.comptime.Allows
|
|
234
|
+
import Allows._
|
|
235
|
+
|
|
236
|
+
def graphqlType[A: Schema]()(using
|
|
237
|
+
Allows[A, Record[Primitive | Optional[Self] | Sequence[Self]]]
|
|
238
|
+
): String = ???
|
|
239
|
+
|
|
240
|
+
// Works:
|
|
241
|
+
case class TreeNode(value: Int, children: List[TreeNode])
|
|
242
|
+
object TreeNode { implicit val schema: Schema[TreeNode] = Schema.derived }
|
|
243
|
+
// graphqlType[TreeNode]() — compiles fine
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## The `Self` Grammar Node
|
|
247
|
+
|
|
248
|
+
`Self` refers back to the entire enclosing `Allows[A, S]` grammar. It allows the grammar to describe recursive data structures.
|
|
249
|
+
|
|
250
|
+
**Non-recursive types** satisfy `Self`-containing grammars without issue: if no field ever recurses back to the root type, the `Self` position is never reached, and the constraint is vacuously satisfied.
|
|
251
|
+
|
|
252
|
+
**Mutual recursion** between two or more distinct types is a compile-time error:
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
[error] Mutually recursive types are not supported by Allows.
|
|
256
|
+
Cycle: Forest -> Tree -> Forest
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## `Wrapped[A]` and Newtypes
|
|
260
|
+
|
|
261
|
+
The `Wrapped[A]` node matches ZIO Prelude `Newtype` and `Subtype` wrappers. The underlying type must satisfy `A`.
|
|
262
|
+
|
|
263
|
+
```scala
|
|
264
|
+
// ZIO Prelude Newtype pattern:
|
|
265
|
+
import zio.prelude.Newtype
|
|
266
|
+
object ProductCode extends Newtype[String]
|
|
267
|
+
type ProductCode = ProductCode.Type
|
|
268
|
+
|
|
269
|
+
given Schema[ProductCode] =
|
|
270
|
+
Schema[String].transform(_.asInstanceOf[ProductCode], _.asInstanceOf[String])
|
|
271
|
+
|
|
272
|
+
// ProductCode satisfies Wrapped[Primitive] — its underlying String is Primitive
|
|
273
|
+
val ev: Allows[ProductCode, Wrapped[Primitive]] = implicitly
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Scala 3 opaque types** are resolved to their underlying type by the macro (they are transparent), so `opaque type UserId = UUID` satisfies `Primitive` (not `Wrapped[Primitive]`):
|
|
277
|
+
|
|
278
|
+
```scala
|
|
279
|
+
opaque type UserId = java.util.UUID
|
|
280
|
+
// UserId satisfies Allows[UserId, Primitive] — resolved to UUID (a primitive)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Sealed Traits and Enums (Auto-Unwrap)
|
|
284
|
+
|
|
285
|
+
Sealed traits and enums are **automatically unwrapped** by the macro. Whenever a sealed type is encountered at any grammar check position, the macro recursively checks every case against the same grammar. This makes a `Variant` grammar node unnecessary.
|
|
286
|
+
|
|
287
|
+
```scala
|
|
288
|
+
import zio.blocks.schema.comptime.Allows
|
|
289
|
+
import Allows._
|
|
290
|
+
|
|
291
|
+
sealed trait Shape
|
|
292
|
+
case class Circle(radius: Double) extends Shape
|
|
293
|
+
case class Rectangle(width: Double, height: Double) extends Shape
|
|
294
|
+
case object Point extends Shape
|
|
295
|
+
|
|
296
|
+
// No Variant node — Shape is auto-unwrapped, all cases checked against Record[Primitive]
|
|
297
|
+
val ev: Allows[Shape, Record[Primitive]] = implicitly
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Auto-unwrap is recursive: if a case is itself a sealed trait, its cases are unwrapped too, to any depth.
|
|
301
|
+
|
|
302
|
+
Union branches (`A | B`) work naturally with auto-unwrap: unused branches are fine under `Allows` upper-bound semantics.
|
|
303
|
+
|
|
304
|
+
## Error Messages
|
|
305
|
+
|
|
306
|
+
When a type does not satisfy the grammar, the macro reports:
|
|
307
|
+
|
|
308
|
+
1. **The path** to the violating field: `Order.items.<element>`
|
|
309
|
+
2. **What was found**: `Record(OrderItem)`
|
|
310
|
+
3. **What was required**: `Primitive | Sequence[Primitive]`
|
|
311
|
+
4. **A hint** where applicable
|
|
312
|
+
|
|
313
|
+
Multiple violations are reported in a single compilation pass — the user sees all problems at once.
|
|
314
|
+
|
|
315
|
+
Example:
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
[error] Schema shape violation at UserWithAddress.address: found Record(Address),
|
|
319
|
+
required Primitive | Optional[Primitive] | Map[Primitive, Primitive]
|
|
320
|
+
[error] Hint: Type 'Address' does not match any allowed shape
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Singleton / Zero-Field Records
|
|
324
|
+
|
|
325
|
+
`Record[A]` is vacuously true for case objects and zero-field records, since there are no fields to violate the constraint:
|
|
326
|
+
|
|
327
|
+
```scala
|
|
328
|
+
import zio.blocks.schema.Schema
|
|
329
|
+
import zio.blocks.schema.comptime.Allows
|
|
330
|
+
import Allows._
|
|
331
|
+
|
|
332
|
+
case object EmptyEvent
|
|
333
|
+
implicit val schema: Schema[EmptyEvent.type] = Schema.derived
|
|
334
|
+
|
|
335
|
+
val ev: Allows[EmptyEvent.type, Record[Primitive]] = implicitly // vacuously true
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Runtime Cost
|
|
339
|
+
|
|
340
|
+
`Allows[A, S]` carries **zero runtime overhead**. The macro emits a reference to a single private singleton `Allows.instance` cast to the required type. There is no per-call-site allocation.
|
|
341
|
+
|
|
342
|
+
## Scala 2 vs Scala 3
|
|
343
|
+
|
|
344
|
+
| Feature | Scala 2 | Scala 3 |
|
|
345
|
+
|---|---|---|
|
|
346
|
+
| Union syntax | `` A `\|` B `` infix | `A \| B` native union type |
|
|
347
|
+
| Summon syntax | `implicitly[Allows[A, S]]` | `summon[Allows[A, S]]` or `implicitly` |
|
|
348
|
+
| Evidence parameter | `(implicit ev: Allows[A, S])` | `(using Allows[A, S])` |
|
|
349
|
+
| Opaque type detection | ZIO Prelude only | Scala 3 opaque types + ZIO Prelude + neotype |
|
|
350
|
+
| Derivation keyword | `Schema.derived` implicit | `Schema.derived` or `derives Schema` |
|
|
351
|
+
|
|
352
|
+
Both Scala versions produce the same macro behavior and the same error messages.
|
package/reference/codec.md
CHANGED
|
@@ -32,8 +32,8 @@ case class Person(name: String, age: Int)
|
|
|
32
32
|
object Person {
|
|
33
33
|
// Derive a schema for Person (required for codec derivation)
|
|
34
34
|
implicit val schema: Schema[Person] = Schema.derived
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
// Derive a JSON codec from the schema
|
|
36
|
+
implicit val codec: JsonBinaryCodec[Person] = schema.derive(JsonFormat)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// Encode
|
|
@@ -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.27"
|
|
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.27"
|
|
58
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.27"
|
|
59
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.27"
|
|
60
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.27"
|
|
61
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.27"
|
|
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.27"
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Supported Scala versions: 2.13.x and 3.x.
|
|
@@ -116,7 +116,7 @@ object Person {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// Pass a Format object to get a codec for that format
|
|
119
|
-
val jsonCodec: JsonBinaryCodec[Person] = Schema[Person].derive
|
|
119
|
+
val jsonCodec: JsonBinaryCodec[Person] = Schema[Person].derive(JsonFormat)
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
This works with any format:
|
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.27"
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
## Core Types
|
package/reference/media-type.md
CHANGED
|
@@ -75,13 +75,13 @@ textAny.matches(html) // true
|
|
|
75
75
|
Add the following to your `build.sbt`:
|
|
76
76
|
|
|
77
77
|
```scala
|
|
78
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-mediatype" % "0.0.
|
|
78
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-mediatype" % "0.0.27"
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
For cross-platform projects (Scala.js):
|
|
82
82
|
|
|
83
83
|
```scala
|
|
84
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-mediatype" % "0.0.
|
|
84
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-mediatype" % "0.0.27"
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
Supported Scala versions: 2.13.x and 3.x.
|
package/reference/modifier.md
CHANGED
|
@@ -38,12 +38,12 @@ object User extends CompanionOptics[User] {
|
|
|
38
38
|
.derived[User]
|
|
39
39
|
.modifier(Modifier.config("db.table-name", "users"))
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
implicit val jsonCodec: JsonBinaryCodec[User] =
|
|
42
|
+
schema
|
|
43
|
+
.deriving(JsonBinaryCodecDeriver)
|
|
44
|
+
.modifier(User.name, Modifier.rename("username"))
|
|
45
|
+
.modifier(User.cache, Modifier.transient())
|
|
46
|
+
.derive
|
|
47
47
|
|
|
48
48
|
lazy val id : Lens[User, String] = $(_.id)
|
|
49
49
|
lazy val name : Lens[User, String] = $(_.name)
|
|
@@ -86,9 +86,9 @@ object User extends CompanionOptics[User] {
|
|
|
86
86
|
implicit val schema: Schema[User] =
|
|
87
87
|
Schema.derived[User]
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
implicit val jsonCodec: JsonBinaryCodec[User] =
|
|
90
|
+
schema
|
|
91
|
+
.derive(JsonBinaryCodecDeriver)
|
|
92
92
|
}
|
|
93
93
|
```
|
|
94
94
|
|