@zio.dev/zio-blocks 0.0.21 → 0.0.24
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 +758 -0
- package/guides/query-dsl-fluent-builder.md +1287 -0
- package/guides/query-dsl-reified-optics.md +494 -0
- package/guides/query-dsl-sql.md +680 -0
- package/index.md +57 -34
- package/package.json +1 -1
- package/path-interpolator.md +24 -23
- package/reference/codec.md +386 -0
- package/reference/docs.md +1 -1
- package/reference/dynamic-optic.md +396 -0
- package/reference/formats.md +68 -12
- package/reference/json-schema.md +14 -11
- package/reference/json.md +2 -2
- package/reference/lazy.md +361 -0
- package/reference/media-type.md +460 -0
- package/reference/modifier.md +340 -0
- package/reference/optics.md +4 -0
- package/reference/schema-expr.md +669 -0
- package/reference/schema.md +1 -0
- package/reference/syntax.md +11 -11
- package/reference/type-class-derivation.md +1960 -0
- package/scope.md +754 -502
- package/sidebars.js +18 -1
- package/undocumented-report.md +331 -0
package/index.md
CHANGED
|
@@ -23,6 +23,7 @@ The philosophy is simple: **use what you need, nothing more**. Each block is ind
|
|
|
23
23
|
| **Docs** | GitHub Flavored Markdown parsing and rendering | ✅ Available |
|
|
24
24
|
| **TypeId** | Compile-time type identity with rich metadata | ✅ Available |
|
|
25
25
|
| **Context** | Type-indexed heterogeneous collections | ✅ Available |
|
|
26
|
+
| **MediaType** | Type-safe IANA media types with 2,600+ predefined types | ✅ Available |
|
|
26
27
|
| **Streams** | Pull-based streaming primitives | 🚧 In Development |
|
|
27
28
|
|
|
28
29
|
## Core Principles
|
|
@@ -80,14 +81,14 @@ val thriftCodec = Schema[Person].derive(ThriftFormat) // Thrift
|
|
|
80
81
|
### Installation
|
|
81
82
|
|
|
82
83
|
```scala
|
|
83
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
84
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.24"
|
|
84
85
|
|
|
85
86
|
// Optional format modules:
|
|
86
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.
|
|
87
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.
|
|
88
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.
|
|
89
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.
|
|
90
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.
|
|
87
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.24"
|
|
88
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.24"
|
|
89
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.24"
|
|
90
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.24"
|
|
91
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.24"
|
|
91
92
|
```
|
|
92
93
|
|
|
93
94
|
### Example: Optics
|
|
@@ -142,7 +143,7 @@ Chunk is designed for:
|
|
|
142
143
|
### Installation
|
|
143
144
|
|
|
144
145
|
```scala
|
|
145
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-chunk" % "0.0.
|
|
146
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-chunk" % "0.0.24"
|
|
146
147
|
```
|
|
147
148
|
|
|
148
149
|
### Example
|
|
@@ -173,7 +174,7 @@ val head: Int = nonEmpty.head // Always safe, no Option needed
|
|
|
173
174
|
|
|
174
175
|
## Scope
|
|
175
176
|
|
|
176
|
-
Compile-time verified resource safety for synchronous Scala code. Scope prevents resource leaks at compile time by tagging values with an unnameable type-level identity—values allocated in a scope can only be used within that scope
|
|
177
|
+
Compile-time verified resource safety for synchronous Scala code. Scope prevents resource leaks at compile time by tagging values with an unnameable type-level identity—values allocated in a scope can only be used within that scope. Child scope values cannot escape to parent scopes, enforced by both the abstract scope-tagged type and the `Unscoped` constraint on `scoped`.
|
|
177
178
|
|
|
178
179
|
### The Problem
|
|
179
180
|
|
|
@@ -201,51 +202,56 @@ Using(openDatabase()) { db =>
|
|
|
201
202
|
Scope makes resource leaks a **compile error**, not a runtime bug:
|
|
202
203
|
|
|
203
204
|
```scala
|
|
204
|
-
import zio.blocks.scope
|
|
205
|
+
import zio.blocks.scope.*
|
|
205
206
|
|
|
206
207
|
Scope.global.scoped { scope =>
|
|
207
|
-
|
|
208
|
+
import scope.*
|
|
209
|
+
|
|
210
|
+
val db: $[Database] = allocate(Resource(openDatabase()))
|
|
208
211
|
|
|
209
212
|
// Methods are hidden - can't call db.query() directly
|
|
210
|
-
// Must use
|
|
211
|
-
val result = (
|
|
213
|
+
// Must use $ to access:
|
|
214
|
+
val result: String = $(db)(_.query("SELECT 1"))
|
|
212
215
|
|
|
213
216
|
// Trying to return `db` would be a compile error!
|
|
214
|
-
result // Only pure data escapes
|
|
217
|
+
result // Only pure data (String) escapes
|
|
215
218
|
}
|
|
216
219
|
// db.close() called automatically
|
|
217
220
|
```
|
|
218
221
|
|
|
219
222
|
### Key Features
|
|
220
223
|
|
|
221
|
-
- **Compile-Time Leak Prevention**: Values
|
|
222
|
-
- **Zero Runtime Overhead**:
|
|
224
|
+
- **Compile-Time Leak Prevention**: Values of type `scope.$[A]` are opaque and unique to each scope instance. Returning a scoped value from its scope is a type error.
|
|
225
|
+
- **Zero Runtime Overhead**: `$[A]` erases to `A` at runtime—zero allocation overhead.
|
|
223
226
|
- **Structured Scopes**: Child scopes nest within parents; resources clean up LIFO when scopes exit.
|
|
224
227
|
- **Built-in Dependency Injection**: Wire up your application with `Resource.from[T](wires*)` for automatic constructor-based DI.
|
|
225
228
|
- **AutoCloseable Integration**: Resources implementing `AutoCloseable` have `close()` registered automatically.
|
|
229
|
+
- **Unscoped Constraint**: The `scoped` method requires `Unscoped[A]` evidence on the return type, ensuring only pure data (not resources or closures) can escape.
|
|
226
230
|
|
|
227
231
|
### Installation
|
|
228
232
|
|
|
229
233
|
```scala
|
|
230
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-scope" % "0.0.
|
|
234
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-scope" % "0.0.24"
|
|
231
235
|
```
|
|
232
236
|
|
|
233
237
|
### Example: Basic Resource Management
|
|
234
238
|
|
|
235
239
|
```scala
|
|
236
|
-
import zio.blocks.scope
|
|
240
|
+
import zio.blocks.scope.*
|
|
237
241
|
|
|
238
|
-
final class Database extends AutoCloseable
|
|
242
|
+
final class Database extends AutoCloseable:
|
|
239
243
|
def query(sql: String): String = s"Result: $sql"
|
|
240
244
|
def close(): Unit = println("Database closed")
|
|
241
|
-
}
|
|
242
245
|
|
|
243
246
|
Scope.global.scoped { scope =>
|
|
244
|
-
|
|
245
|
-
|
|
247
|
+
import scope.*
|
|
248
|
+
|
|
249
|
+
// Allocate returns $[Database] (scoped value)
|
|
250
|
+
val db: $[Database] = allocate(Resource(new Database))
|
|
251
|
+
|
|
252
|
+
// Access via $ - result (String) escapes, db does not
|
|
253
|
+
val result: String = $(db)(_.query("SELECT * FROM users"))
|
|
246
254
|
|
|
247
|
-
// Access via scope $ - result (String) escapes, db does not
|
|
248
|
-
val result = (scope $ db)(_.query("SELECT * FROM users"))
|
|
249
255
|
println(result)
|
|
250
256
|
}
|
|
251
257
|
// Output: Result: SELECT * FROM users
|
|
@@ -255,7 +261,7 @@ Scope.global.scoped { scope =>
|
|
|
255
261
|
### Example: Dependency Injection
|
|
256
262
|
|
|
257
263
|
```scala
|
|
258
|
-
import zio.blocks.scope
|
|
264
|
+
import zio.blocks.scope.*
|
|
259
265
|
|
|
260
266
|
case class Config(dbUrl: String)
|
|
261
267
|
class Database(config: Config) extends AutoCloseable { ... }
|
|
@@ -269,8 +275,11 @@ val serviceResource: Resource[UserService] = Resource.from[UserService](
|
|
|
269
275
|
)
|
|
270
276
|
|
|
271
277
|
Scope.global.scoped { scope =>
|
|
272
|
-
|
|
273
|
-
|
|
278
|
+
import scope.*
|
|
279
|
+
|
|
280
|
+
val service = allocate(serviceResource)
|
|
281
|
+
|
|
282
|
+
$(service)(_.createUser("Alice"))
|
|
274
283
|
}
|
|
275
284
|
// Cleanup runs LIFO: UserService → Database (UserRepo has no cleanup)
|
|
276
285
|
```
|
|
@@ -279,13 +288,17 @@ Scope.global.scoped { scope =>
|
|
|
279
288
|
|
|
280
289
|
```scala
|
|
281
290
|
Scope.global.scoped { connScope =>
|
|
282
|
-
|
|
291
|
+
import connScope.*
|
|
292
|
+
|
|
293
|
+
val conn = allocate(Resource.fromAutoCloseable(new Connection))
|
|
283
294
|
|
|
284
295
|
// Transaction lives in child scope - cleaned up before connection
|
|
285
|
-
val result =
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
296
|
+
val result: String = scoped { txScope =>
|
|
297
|
+
import txScope.*
|
|
298
|
+
val c = lower(conn)
|
|
299
|
+
val tx = $(c)(_.beginTransaction()).allocate
|
|
300
|
+
$(tx)(_.execute("INSERT INTO users VALUES (1, 'Alice')"))
|
|
301
|
+
$(tx)(_.commit())
|
|
289
302
|
"success"
|
|
290
303
|
}
|
|
291
304
|
// Transaction closed here, connection still open
|
|
@@ -321,7 +334,7 @@ Generating documentation, README files, or any Markdown content programmatically
|
|
|
321
334
|
### Installation
|
|
322
335
|
|
|
323
336
|
```scala
|
|
324
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.
|
|
337
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.24"
|
|
325
338
|
```
|
|
326
339
|
|
|
327
340
|
### Example
|
|
@@ -405,7 +418,7 @@ Compile-time type identity with rich metadata. TypeId captures comprehensive inf
|
|
|
405
418
|
### Installation
|
|
406
419
|
|
|
407
420
|
```scala
|
|
408
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-typeid" % "0.0.
|
|
421
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-typeid" % "0.0.24"
|
|
409
422
|
```
|
|
410
423
|
|
|
411
424
|
### Example
|
|
@@ -448,7 +461,7 @@ A type-indexed heterogeneous collection that stores values by their types with c
|
|
|
448
461
|
### Installation
|
|
449
462
|
|
|
450
463
|
```scala
|
|
451
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.
|
|
464
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.24"
|
|
452
465
|
```
|
|
453
466
|
|
|
454
467
|
### Example
|
|
@@ -529,11 +542,13 @@ ZIO Blocks supports **Scala 2.13** and **Scala 3.x** with full source compatibil
|
|
|
529
542
|
### Optics & Navigation
|
|
530
543
|
|
|
531
544
|
- [Optics](./reference/optics.md) - Lenses, prisms, and traversals
|
|
545
|
+
- [SchemaExpr](./reference/schema-expr.md) - Schema-aware expressions for queries and validation
|
|
532
546
|
- [Path Interpolator](./path-interpolator.md) - Type-safe path construction
|
|
533
547
|
- [DynamicValue](./reference/dynamic-value.md) - Schema-less dynamic values
|
|
534
548
|
|
|
535
549
|
### Serialization
|
|
536
550
|
|
|
551
|
+
- [Codec & Format](./reference/codec.md) - Codec, Format, BinaryCodec & TextCodec
|
|
537
552
|
- [JSON](./reference/json.md) - JSON codec and parsing
|
|
538
553
|
- [JSON Schema](./reference/json-schema.md) - JSON Schema generation and validation
|
|
539
554
|
- [Formats](./reference/formats.md) - Avro, TOON, MessagePack, BSON, Thrift
|
|
@@ -552,3 +567,11 @@ ZIO Blocks supports **Scala 2.13** and **Scala 3.x** with full source compatibil
|
|
|
552
567
|
- [TypeId](./reference/typeid.md) - Type identity and metadata
|
|
553
568
|
- [Context](./reference/context.md) - Type-indexed heterogeneous collections
|
|
554
569
|
- [Docs (Markdown)](./reference/docs.md) - Markdown parsing and rendering
|
|
570
|
+
- [MediaType](./reference/media-type.md) - Type-safe IANA media types
|
|
571
|
+
|
|
572
|
+
### Guides
|
|
573
|
+
|
|
574
|
+
- [Query DSL Part 1: Expressions](./guides/query-dsl-reified-optics.md) - Build type-safe, composable query expressions
|
|
575
|
+
- [Query DSL Part 2: SQL Generation](./guides/query-dsl-sql.md) - Translate query expressions into SQL
|
|
576
|
+
- [Query DSL Part 3: Extending the Expression Language](./guides/query-dsl-extending.md) - Add custom operators beyond SchemaExpr
|
|
577
|
+
- [Query DSL Part 4: A Fluent SQL Builder](./guides/query-dsl-fluent-builder.md) - Build type-safe SELECT, UPDATE, INSERT, DELETE statements
|
package/package.json
CHANGED
package/path-interpolator.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
---
|
|
2
|
+
id: path-interpolator
|
|
3
|
+
title: "Path Interpolator"
|
|
4
|
+
---
|
|
4
5
|
|
|
5
6
|
The path interpolator `p"..."` is a compile-time string interpolator for constructing `DynamicOptic` instances in ZIO Blocks. It provides a clean, concise syntax for building optic paths that navigate through complex data structures, with all parsing and validation happening at compile time for zero runtime overhead.
|
|
6
7
|
|
|
@@ -215,14 +216,14 @@ p"<A><B><C>" // Nested variants
|
|
|
215
216
|
|
|
216
217
|
String and character literals support standard escape sequences:
|
|
217
218
|
|
|
218
|
-
| Escape | Result
|
|
219
|
-
|
|
220
|
-
| `\n` | newline | Line feed
|
|
221
|
-
| `\t` | tab
|
|
222
|
-
| `\r` | return
|
|
223
|
-
| `\'` | `'`
|
|
224
|
-
| `\"` | `"`
|
|
225
|
-
| `\\` | `\`
|
|
219
|
+
| Escape | Result | Description |
|
|
220
|
+
|--------|---------|-----------------|
|
|
221
|
+
| `\n` | newline | Line feed |
|
|
222
|
+
| `\t` | tab | Horizontal tab |
|
|
223
|
+
| `\r` | return | Carriage return |
|
|
224
|
+
| `\'` | `'` | Single quote |
|
|
225
|
+
| `\"` | `"` | Double quote |
|
|
226
|
+
| `\\` | `\` | Backslash |
|
|
226
227
|
|
|
227
228
|
**Examples:**
|
|
228
229
|
|
|
@@ -618,19 +619,19 @@ val same = p".users[*].email"
|
|
|
618
619
|
|
|
619
620
|
**Examples:**
|
|
620
621
|
|
|
621
|
-
| DynamicOptic Construction
|
|
622
|
-
|
|
623
|
-
| `DynamicOptic.root.field("name")`
|
|
622
|
+
| DynamicOptic Construction | toString Output |
|
|
623
|
+
|------------------------------------------------------|-------------------|
|
|
624
|
+
| `DynamicOptic.root.field("name")` | `.name` |
|
|
624
625
|
| `DynamicOptic.root.field("address").field("street")` | `.address.street` |
|
|
625
|
-
| `DynamicOptic.root.caseOf("Some")`
|
|
626
|
-
| `DynamicOptic.root.at(0)`
|
|
627
|
-
| `DynamicOptic.root.atIndices(0, 2, 5)`
|
|
628
|
-
| `DynamicOptic.elements`
|
|
629
|
-
| `DynamicOptic.root.atKey("host")`
|
|
630
|
-
| `DynamicOptic.root.atKey(80)`
|
|
631
|
-
| `DynamicOptic.mapValues`
|
|
632
|
-
| `DynamicOptic.mapKeys`
|
|
633
|
-
| `DynamicOptic.wrapped`
|
|
626
|
+
| `DynamicOptic.root.caseOf("Some")` | `<Some>` |
|
|
627
|
+
| `DynamicOptic.root.at(0)` | `[0]` |
|
|
628
|
+
| `DynamicOptic.root.atIndices(0, 2, 5)` | `[0,2,5]` |
|
|
629
|
+
| `DynamicOptic.elements` | `[*]` |
|
|
630
|
+
| `DynamicOptic.root.atKey("host")` | `{"host"}` |
|
|
631
|
+
| `DynamicOptic.root.atKey(80)` | `{80}` |
|
|
632
|
+
| `DynamicOptic.mapValues` | `{*}` |
|
|
633
|
+
| `DynamicOptic.mapKeys` | `{*:}` |
|
|
634
|
+
| `DynamicOptic.wrapped` | `.~` |
|
|
634
635
|
|
|
635
636
|
## Summary
|
|
636
637
|
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: codec
|
|
3
|
+
title: "Codec"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`Codec[DecodeInput, EncodeOutput, Value]` is the base abstraction for encoding and decoding values between a specific input representation and a specific output representation. It forms the foundation of ZIO Blocks' multi-format serialization system, enabling a single `Schema[A]` to derive codecs for JSON, Avro, TOON, MessagePack, Thrift, and other formats that are integrated via the `Codec`/`Format` system. BSON support is provided separately via `BsonSchemaCodec`, which is not a subtype of `codec.Codec` and is not derived via `Schema.derive(format)`.
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
`Codec` defines two abstract methods that every concrete codec must implement:
|
|
11
|
+
|
|
12
|
+
```scala
|
|
13
|
+
abstract class Codec[DecodeInput, EncodeOutput, Value] {
|
|
14
|
+
def encode(value: Value, output: EncodeOutput): Unit
|
|
15
|
+
def decode(input: DecodeInput): Either[SchemaError, Value]
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- **`encode`** writes the encoded form of `value` into `output`. The output parameter is typically a mutable buffer (`ByteBuffer`, `CharBuffer`) that the caller provides.
|
|
20
|
+
- **`decode`** reads from `input` and returns either a `SchemaError` describing the failure or the decoded value.
|
|
21
|
+
|
|
22
|
+
End users rarely interact with `Codec` directly. Instead, they work with format-specific subclasses like `JsonBinaryCodec[A]` or `ToonBinaryCodec[A]`, which add convenience methods for common input/output types.
|
|
23
|
+
|
|
24
|
+
Given a `Schema[A]`, you can derive a codec for any supported format by calling `Schema[A].derive(format)`, which uses the `Deriver` associated with that format to generate the appropriate codec instance. For example, to derive a JSON codec:
|
|
25
|
+
|
|
26
|
+
```scala
|
|
27
|
+
import zio.blocks.schema._
|
|
28
|
+
import zio.blocks.schema.json._
|
|
29
|
+
|
|
30
|
+
case class Person(name: String, age: Int)
|
|
31
|
+
|
|
32
|
+
object Person {
|
|
33
|
+
// Derive a schema for Person (required for codec derivation)
|
|
34
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
35
|
+
// Derive a JSON codec from the schema
|
|
36
|
+
implicit val codec: JsonBinaryCodec[Person] = schema.derive[JsonFormat.type](JsonFormat)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Encode
|
|
40
|
+
val bytes: Array[Byte] = Person.codec.encode(Person("Alice", 30))
|
|
41
|
+
|
|
42
|
+
// Decode
|
|
43
|
+
val result: Either[SchemaError, Person] = Person.codec.decode(bytes)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
To include the base schema module with JSON support, add the following dependency to your `build.sbt`:
|
|
49
|
+
|
|
50
|
+
```scala
|
|
51
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.24"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Additional format modules are separate artifacts:
|
|
55
|
+
|
|
56
|
+
```scala
|
|
57
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.24"
|
|
58
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.24"
|
|
59
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.24"
|
|
60
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.24"
|
|
61
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.24"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
For cross-platform projects (Scala.js):
|
|
65
|
+
|
|
66
|
+
```scala
|
|
67
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.24"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Supported Scala versions: 2.13.x and 3.x.
|
|
71
|
+
|
|
72
|
+
## BinaryCodec and TextCodec
|
|
73
|
+
|
|
74
|
+
The codec system in ZIO Blocks is organized as a layered hierarchy:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
Codec[DecodeInput, EncodeOutput, Value]
|
|
78
|
+
├── BinaryCodec[A] = Codec[ByteBuffer, ByteBuffer, A] (ByteBuffer ↔ A)
|
|
79
|
+
│ ├── JsonBinaryCodec[A]
|
|
80
|
+
│ ├── AvroBinaryCodec[A]
|
|
81
|
+
│ ├── ToonBinaryCodec[A]
|
|
82
|
+
│ ├── ThriftBinaryCodec[A]
|
|
83
|
+
│ └── MessagePackBinaryCodec[A]
|
|
84
|
+
└── TextCodec[A] = Codec[CharBuffer, CharBuffer, A] (CharBuffer ↔ A)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
1. **`BinaryCodec[A]`** fixes both the input and output to `ByteBuffer` and is the base class for all codecs that operate on binary data:
|
|
88
|
+
|
|
89
|
+
```scala
|
|
90
|
+
abstract class BinaryCodec[A] extends Codec[ByteBuffer, ByteBuffer, A]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
2. **`TextCodec[A]`** fixes both the input and output to `CharBuffer`:
|
|
94
|
+
|
|
95
|
+
```scala
|
|
96
|
+
abstract class TextCodec[A] extends Codec[CharBuffer, CharBuffer, A]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
All built-in serialization formats (JSON, Avro, TOON, MessagePack, Thrift) extend `BinaryCodec`. Despite JSON being a text format, the JSON codec operates on UTF-8 encoded bytes for performance.
|
|
100
|
+
|
|
101
|
+
`TextCodec` exists for formats that operate on character data rather than raw bytes. No built-in formats currently use `TextCodec`, but it is available for custom text-based formats.
|
|
102
|
+
|
|
103
|
+
## Deriving Codecs
|
|
104
|
+
|
|
105
|
+
### Using Schema.derive
|
|
106
|
+
|
|
107
|
+
The primary way to obtain a codec is through `Schema[A].derive`:
|
|
108
|
+
|
|
109
|
+
```scala
|
|
110
|
+
import zio.blocks.schema._
|
|
111
|
+
import zio.blocks.schema.json._
|
|
112
|
+
|
|
113
|
+
case class Person(name: String, age: Int)
|
|
114
|
+
object Person {
|
|
115
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Pass a Format object to get a codec for that format
|
|
119
|
+
val jsonCodec: JsonBinaryCodec[Person] = Schema[Person].derive[JsonFormat.type](JsonFormat)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This works with any format:
|
|
123
|
+
|
|
124
|
+
```scala
|
|
125
|
+
import zio.blocks.schema._
|
|
126
|
+
import zio.blocks.schema.json._
|
|
127
|
+
import zio.blocks.schema.toon._
|
|
128
|
+
|
|
129
|
+
case class Person(name: String, age: Int)
|
|
130
|
+
object Person {
|
|
131
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
val jsonCodec = Schema[Person].derive(JsonFormat)
|
|
135
|
+
val toonCodec = Schema[Person].derive(ToonFormat)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Using Schema.deriving for Customization
|
|
139
|
+
|
|
140
|
+
For more control over the derived codec, use `deriving` to get a `DerivationBuilder`. This lets you override instances for specific substructures or inject modifiers before finalizing:
|
|
141
|
+
|
|
142
|
+
```scala
|
|
143
|
+
import zio.blocks.schema._
|
|
144
|
+
import zio.blocks.schema.json._
|
|
145
|
+
|
|
146
|
+
case class Person(name: String, age: Int)
|
|
147
|
+
object Person extends CompanionOptics[Person] {
|
|
148
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
149
|
+
val name = $(_.name)
|
|
150
|
+
val age = $(_.age)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Override the codec for the "name" field
|
|
154
|
+
val customNameCodec = new JsonBinaryCodec[String] {
|
|
155
|
+
def decodeValue(in: JsonReader, default: String): String = in.readString(default)
|
|
156
|
+
def encodeValue(x: String, out: JsonWriter): Unit = out.writeVal(x.toUpperCase)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
val codec: JsonBinaryCodec[Person] = Schema[Person]
|
|
160
|
+
.deriving(JsonFormat.deriver)
|
|
161
|
+
.instance(Person.name, customNameCodec)
|
|
162
|
+
.derive
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Using Schema#decode and Schema#encode
|
|
166
|
+
|
|
167
|
+
`Schema` also provides `decode` and `encode` methods that internally call `derive` (with caching) and then delegate to the codec:
|
|
168
|
+
|
|
169
|
+
```scala
|
|
170
|
+
import zio.blocks.schema._
|
|
171
|
+
import zio.blocks.schema.json._
|
|
172
|
+
import java.nio.ByteBuffer
|
|
173
|
+
|
|
174
|
+
case class Person(name: String, age: Int)
|
|
175
|
+
object Person {
|
|
176
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Encode directly from Schema
|
|
180
|
+
val buffer = ByteBuffer.allocate(1024)
|
|
181
|
+
Schema[Person].encode(JsonFormat)(buffer)(Person("Alice", 30))
|
|
182
|
+
|
|
183
|
+
// Decode directly from Schema
|
|
184
|
+
buffer.flip()
|
|
185
|
+
val result: Either[SchemaError, Person] = Schema[Person].decode(JsonFormat)(buffer)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Using a Deriver Directly
|
|
189
|
+
|
|
190
|
+
Each `Format` object contains a `Deriver[TC]` that can also be passed to `derive`:
|
|
191
|
+
|
|
192
|
+
```scala
|
|
193
|
+
import zio.blocks.schema._
|
|
194
|
+
import zio.blocks.schema.json._
|
|
195
|
+
|
|
196
|
+
case class Person(name: String, age: Int)
|
|
197
|
+
object Person {
|
|
198
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// These are equivalent:
|
|
202
|
+
val codec1 = Schema[Person].derive(JsonFormat)
|
|
203
|
+
val codec2 = Schema[Person].derive(JsonFormat.deriver)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Passing a `Deriver` directly is useful when working with custom or configured derivers (see [Configuring Codecs](#configuring-codecs)).
|
|
207
|
+
|
|
208
|
+
## Convenience Methods on Format-Specific Codecs
|
|
209
|
+
|
|
210
|
+
While the base `Codec` class defines only `encode(value, output)` and `decode(input)`, format-specific subclasses like `JsonBinaryCodec` and `ToonBinaryCodec` add convenience overloads for common I/O types.
|
|
211
|
+
|
|
212
|
+
### JsonBinaryCodec Convenience Methods
|
|
213
|
+
|
|
214
|
+
`JsonBinaryCodec[A]` provides the following overloads beyond the base `ByteBuffer` API:
|
|
215
|
+
|
|
216
|
+
```scala
|
|
217
|
+
import zio.blocks.schema._
|
|
218
|
+
import zio.blocks.schema.json._
|
|
219
|
+
|
|
220
|
+
case class Person(name: String, age: Int)
|
|
221
|
+
object Person {
|
|
222
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
val codec = Schema[Person].derive(JsonFormat)
|
|
226
|
+
val person = Person("Alice", 30)
|
|
227
|
+
|
|
228
|
+
// Array[Byte]
|
|
229
|
+
val bytes: Array[Byte] = codec.encode(person)
|
|
230
|
+
val fromBytes1: Either[SchemaError, Person] = codec.decode(bytes)
|
|
231
|
+
val fromBytes2: Either[SchemaError, Person] = codec.decode(bytes, 0, bytes.length)
|
|
232
|
+
|
|
233
|
+
// String
|
|
234
|
+
val jsonStr: String = codec.encodeToString(person)
|
|
235
|
+
val fromStr: Either[SchemaError, Person] = codec.decode("""{"name":"Alice","age":30}""")
|
|
236
|
+
|
|
237
|
+
// InputStream / OutputStream
|
|
238
|
+
import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
|
|
239
|
+
|
|
240
|
+
val os = new ByteArrayOutputStream()
|
|
241
|
+
codec.encode(person, os)
|
|
242
|
+
|
|
243
|
+
val is = new ByteArrayInputStream(os.toByteArray)
|
|
244
|
+
val fromStream: Either[SchemaError, Person] = codec.decode(is)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### ToonBinaryCodec Convenience Methods
|
|
248
|
+
|
|
249
|
+
`ToonBinaryCodec[A]` provides the same set of overloads:
|
|
250
|
+
|
|
251
|
+
```scala
|
|
252
|
+
import zio.blocks.schema._
|
|
253
|
+
import zio.blocks.schema.toon._
|
|
254
|
+
|
|
255
|
+
case class Person(name: String, age: Int)
|
|
256
|
+
object Person {
|
|
257
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
val codec = Schema[Person].derive(ToonFormat)
|
|
261
|
+
val person = Person("Alice", 30)
|
|
262
|
+
|
|
263
|
+
// Array[Byte]
|
|
264
|
+
val bytes: Array[Byte] = codec.encode(person)
|
|
265
|
+
val fromBytes: Either[SchemaError, Person] = codec.decode(bytes)
|
|
266
|
+
|
|
267
|
+
// String
|
|
268
|
+
val toonStr: String = codec.encodeToString(person)
|
|
269
|
+
val fromStr: Either[SchemaError, Person] = codec.decode("name: Alice\nage: 30")
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Summary of Convenience Methods
|
|
273
|
+
|
|
274
|
+
`BinaryCodec` subclasses (JSON, TOON, MessagePack, Avro, Thrift) expose the following convenience overloads (availability may vary by format):
|
|
275
|
+
|
|
276
|
+
| Method | Description |
|
|
277
|
+
|--------------------------------------------------------------------------|--------------------------------------------------|
|
|
278
|
+
| `encode(value): Array[Byte]` | Encode to a byte array |
|
|
279
|
+
| `decode(input: Array[Byte]): Either[SchemaError, A]` | Decode from a byte array |
|
|
280
|
+
| `decode(input: Array[Byte], from: Int, to: Int): Either[SchemaError, A]` | Decode from a byte array slice |
|
|
281
|
+
| `encode(value, output: ByteBuffer): Unit` | Encode into a `ByteBuffer` |
|
|
282
|
+
| `decode(input: ByteBuffer): Either[SchemaError, A]` | Decode from a `ByteBuffer` |
|
|
283
|
+
| `encode(value, output: OutputStream): Unit` | Encode into an `OutputStream` (JSON, TOON, Avro) |
|
|
284
|
+
| `decode(input: InputStream): Either[SchemaError, A]` | Decode from an `InputStream` (JSON, TOON, Avro) |
|
|
285
|
+
| `encodeToString(value): String` | Encode to a `String` (JSON, TOON) |
|
|
286
|
+
| `decode(input: String): Either[SchemaError, A]` | Decode from a `String` (JSON, TOON) |
|
|
287
|
+
|
|
288
|
+
The `String`-based methods are available on text-oriented binary codecs (JSON, TOON) but not on purely binary formats like Avro or Thrift.
|
|
289
|
+
|
|
290
|
+
## Configuring Codecs
|
|
291
|
+
|
|
292
|
+
Format-specific derivers support configuration options that control encoding behavior. Instead of passing a `Format` object to `derive`, you pass a configured `Deriver`:
|
|
293
|
+
|
|
294
|
+
### JSON Configuration
|
|
295
|
+
|
|
296
|
+
```scala
|
|
297
|
+
import zio.blocks.schema._
|
|
298
|
+
import zio.blocks.schema.json._
|
|
299
|
+
|
|
300
|
+
case class Person(
|
|
301
|
+
firstName: String,
|
|
302
|
+
lastName: String,
|
|
303
|
+
middleName: Option[String] = None
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
object Person {
|
|
307
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
val customDeriver = JsonBinaryCodecDeriver
|
|
311
|
+
.withFieldNameMapper(NameMapper.SnakeCase)
|
|
312
|
+
.withTransientNone(true)
|
|
313
|
+
.withRejectExtraFields(true)
|
|
314
|
+
|
|
315
|
+
val codec = Schema[Person].derive(customDeriver)
|
|
316
|
+
|
|
317
|
+
// Encodes as: {"first_name":"Alice","last_name":"Smith"}
|
|
318
|
+
// (middleName omitted because it is None and transientNone is true)
|
|
319
|
+
val json = codec.encodeToString(Person("Alice", "Smith"))
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
| Option | Description | Default |
|
|
323
|
+
|---------------------------------|--------------------------------------------------------|------------|
|
|
324
|
+
| `withFieldNameMapper` | Transform field names (Identity, SnakeCase, KebabCase) | `Identity` |
|
|
325
|
+
| `withCaseNameMapper` | Transform variant/case names | `Identity` |
|
|
326
|
+
| `withDiscriminatorKind` | ADT discriminator style (Key, Field, None) | `Key` |
|
|
327
|
+
| `withRejectExtraFields` | Error on unknown fields during decoding | `false` |
|
|
328
|
+
| `withEnumValuesAsStrings` | Encode enum values as strings | `true` |
|
|
329
|
+
| `withTransientNone` | Omit `None` values from output | `true` |
|
|
330
|
+
| `withTransientEmptyCollection` | Omit empty collections from output | `true` |
|
|
331
|
+
| `withTransientDefaultValue` | Omit fields with default values | `true` |
|
|
332
|
+
| `withRequireOptionFields` | Require optional fields in input | `false` |
|
|
333
|
+
| `withRequireCollectionFields` | Require collection fields in input | `false` |
|
|
334
|
+
| `withRequireDefaultValueFields` | Require fields with defaults in input | `false` |
|
|
335
|
+
|
|
336
|
+
### TOON Configuration
|
|
337
|
+
|
|
338
|
+
```scala
|
|
339
|
+
import zio.blocks.schema._
|
|
340
|
+
import zio.blocks.schema.toon._
|
|
341
|
+
|
|
342
|
+
case class Person(
|
|
343
|
+
firstName: String,
|
|
344
|
+
lastName: String
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
object Person {
|
|
348
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
val customDeriver = ToonBinaryCodecDeriver
|
|
352
|
+
.withFieldNameMapper(NameMapper.SnakeCase)
|
|
353
|
+
.withArrayFormat(ArrayFormat.Tabular)
|
|
354
|
+
.withDiscriminatorKind(DiscriminatorKind.Field("type"))
|
|
355
|
+
|
|
356
|
+
val codec = Schema[Person].derive(customDeriver)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Error Handling
|
|
360
|
+
|
|
361
|
+
All `decode` operations return `Either[SchemaError, A]`. `SchemaError` includes path information that pinpoints where in the data structure decoding failed:
|
|
362
|
+
|
|
363
|
+
```scala
|
|
364
|
+
import zio.blocks.schema._
|
|
365
|
+
import zio.blocks.schema.json._
|
|
366
|
+
|
|
367
|
+
case class Address(street: String, city: String)
|
|
368
|
+
case class Person(name: String, address: Address)
|
|
369
|
+
|
|
370
|
+
object Address {
|
|
371
|
+
implicit val schema: Schema[Address] = Schema.derived
|
|
372
|
+
}
|
|
373
|
+
object Person {
|
|
374
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
val codec = Schema[Person].derive(JsonFormat)
|
|
378
|
+
|
|
379
|
+
// Missing required field
|
|
380
|
+
val result = codec.decode("""{"name":"Alice","address":{}}""")
|
|
381
|
+
|
|
382
|
+
result match {
|
|
383
|
+
case Right(person) => println(person)
|
|
384
|
+
case Left(error) => error.errors.foreach(e => println(s"Error: ${e.message}"))
|
|
385
|
+
}
|
|
386
|
+
```
|