@zio.dev/zio-blocks 0.0.21 → 0.0.22

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/index.md CHANGED
@@ -80,14 +80,14 @@ val thriftCodec = Schema[Person].derive(ThriftFormat) // Thrift
80
80
  ### Installation
81
81
 
82
82
  ```scala
83
- libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.21"
83
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.22"
84
84
 
85
85
  // Optional format modules:
86
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.21"
87
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.21"
88
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.21"
89
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.21"
90
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.21"
86
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.22"
87
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.22"
88
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.22"
89
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.22"
90
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.22"
91
91
  ```
92
92
 
93
93
  ### Example: Optics
@@ -142,7 +142,7 @@ Chunk is designed for:
142
142
  ### Installation
143
143
 
144
144
  ```scala
145
- libraryDependencies += "dev.zio" %% "zio-blocks-chunk" % "0.0.21"
145
+ libraryDependencies += "dev.zio" %% "zio-blocks-chunk" % "0.0.22"
146
146
  ```
147
147
 
148
148
  ### Example
@@ -173,7 +173,7 @@ val head: Int = nonEmpty.head // Always safe, no Option needed
173
173
 
174
174
  ## Scope
175
175
 
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, and child scope values cannot escape to parent scopes.
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. Child scope values cannot escape to parent scopes, enforced by both the abstract scope-tagged type and the `Unscoped` constraint on `scoped`.
177
177
 
178
178
  ### The Problem
179
179
 
@@ -204,11 +204,13 @@ Scope makes resource leaks a **compile error**, not a runtime bug:
204
204
  import zio.blocks.scope._
205
205
 
206
206
  Scope.global.scoped { scope =>
207
- val db: Database @@ scope.Tag = scope.allocate(Resource(openDatabase()))
207
+ import scope._
208
+
209
+ val db: $[Database] = allocate(Resource(openDatabase()))
208
210
 
209
211
  // Methods are hidden - can't call db.query() directly
210
- // Must use scope $ to access:
211
- val result = (scope $ db)(_.query("SELECT 1"))
212
+ // Must use scope.use to access:
213
+ val result = scope.use(db)(_.query("SELECT 1"))
212
214
 
213
215
  // Trying to return `db` would be a compile error!
214
216
  result // Only pure data escapes
@@ -218,16 +220,17 @@ Scope.global.scoped { scope =>
218
220
 
219
221
  ### Key Features
220
222
 
221
- - **Compile-Time Leak Prevention**: Values tagged with `A @@ S` can only be used with proof of scope access. Returning a scoped value from its scope is a type error.
222
- - **Zero Runtime Overhead**: On the eager path the `@@` tag is erased—`A @@ S` is represented as just `A` when evaluatedwhile deferred/composed computations use a small wrapper/thunk.
223
+ - **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.
224
+ - **Zero Runtime Overhead**: `$[A]` erases to `A` at runtimezero allocation overhead.
223
225
  - **Structured Scopes**: Child scopes nest within parents; resources clean up LIFO when scopes exit.
224
226
  - **Built-in Dependency Injection**: Wire up your application with `Resource.from[T](wires*)` for automatic constructor-based DI.
225
227
  - **AutoCloseable Integration**: Resources implementing `AutoCloseable` have `close()` registered automatically.
228
+ - **Unscoped Constraint**: The `scoped` method requires `Unscoped[A]` evidence on the return type, ensuring only pure data (not resources or closures) can escape.
226
229
 
227
230
  ### Installation
228
231
 
229
232
  ```scala
230
- libraryDependencies += "dev.zio" %% "zio-blocks-scope" % "0.0.21"
233
+ libraryDependencies += "dev.zio" %% "zio-blocks-scope" % "0.0.22"
231
234
  ```
232
235
 
233
236
  ### Example: Basic Resource Management
@@ -241,11 +244,14 @@ final class Database extends AutoCloseable {
241
244
  }
242
245
 
243
246
  Scope.global.scoped { scope =>
244
- // Allocate returns Database @@ scope.Tag (scoped value)
245
- val db = scope.allocate(Resource(new Database))
247
+ import scope._
248
+
249
+ // Allocate returns scope.$[Database] (scoped value)
250
+ val db: $[Database] = allocate(Resource(new Database))
251
+
252
+ // Access via scope.use - result (String) escapes, db does not
253
+ val result = scope.use(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
@@ -269,8 +275,11 @@ val serviceResource: Resource[UserService] = Resource.from[UserService](
269
275
  )
270
276
 
271
277
  Scope.global.scoped { scope =>
272
- val service = scope.allocate(serviceResource)
273
- (scope $ service)(_.createUser("Alice"))
278
+ import scope._
279
+
280
+ val service = allocate(serviceResource)
281
+
282
+ scope.use(service)(_.createUser("Alice"))
274
283
  }
275
284
  // Cleanup runs LIFO: UserService → Database (UserRepo has no cleanup)
276
285
  ```
@@ -279,14 +288,24 @@ Scope.global.scoped { scope =>
279
288
 
280
289
  ```scala
281
290
  Scope.global.scoped { connScope =>
282
- val conn = connScope.allocate(Resource.fromAutoCloseable(new Connection))
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 = connScope.scoped { txScope =>
286
- val tx = txScope.allocate(conn.beginTransaction()) // Returns Resource!
287
- (txScope $ tx)(_.execute("INSERT INTO users VALUES (1, 'Alice')"))
288
- (txScope $ tx)(_.commit())
289
- "success"
296
+ val result: String = scoped { txScope =>
297
+ import txScope._
298
+
299
+ // For-comprehension: flatMap unwraps $[Connection] to Connection,
300
+ // so c.beginTransaction() returns a raw Resource[Transaction]
301
+ for {
302
+ c <- lower(conn)
303
+ tx <- allocate(c.beginTransaction())
304
+ } yield {
305
+ tx.execute("INSERT INTO users VALUES (1, 'Alice')")
306
+ tx.commit()
307
+ "success"
308
+ }
290
309
  }
291
310
  // Transaction closed here, connection still open
292
311
 
@@ -321,7 +340,7 @@ Generating documentation, README files, or any Markdown content programmatically
321
340
  ### Installation
322
341
 
323
342
  ```scala
324
- libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.21"
343
+ libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.22"
325
344
  ```
326
345
 
327
346
  ### Example
@@ -405,7 +424,7 @@ Compile-time type identity with rich metadata. TypeId captures comprehensive inf
405
424
  ### Installation
406
425
 
407
426
  ```scala
408
- libraryDependencies += "dev.zio" %% "zio-blocks-typeid" % "0.0.21"
427
+ libraryDependencies += "dev.zio" %% "zio-blocks-typeid" % "0.0.22"
409
428
  ```
410
429
 
411
430
  ### Example
@@ -448,7 +467,7 @@ A type-indexed heterogeneous collection that stores values by their types with c
448
467
  ### Installation
449
468
 
450
469
  ```scala
451
- libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.21"
470
+ libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.22"
452
471
  ```
453
472
 
454
473
  ### Example
@@ -534,6 +553,7 @@ ZIO Blocks supports **Scala 2.13** and **Scala 3.x** with full source compatibil
534
553
 
535
554
  ### Serialization
536
555
 
556
+ - [Codec & Format](./reference/codec.md) - Codec, Format, BinaryCodec & TextCodec
537
557
  - [JSON](./reference/json.md) - JSON codec and parsing
538
558
  - [JSON Schema](./reference/json-schema.md) - JSON Schema generation and validation
539
559
  - [Formats](./reference/formats.md) - Avro, TOON, MessagePack, BSON, Thrift
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@zio.dev/zio-blocks",
3
3
  "description": "ZIO Blocks Documentation",
4
4
  "license": "Apache-2.0",
5
- "version": "0.0.21",
5
+ "version": "0.0.22",
6
6
  "repository": {
7
7
  "url": "https://github.com/zio/zio-blocks"
8
8
  }
@@ -1,6 +1,7 @@
1
- # Path Interpolator
2
-
3
- ## Overview
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 | Description |
219
- |--------|--------|-------------|
220
- | `\n` | newline | Line feed |
221
- | `\t` | tab | Horizontal tab |
222
- | `\r` | return | Carriage return |
223
- | `\'` | `'` | Single quote |
224
- | `\"` | `"` | Double quote |
225
- | `\\` | `\` | Backslash |
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 | toString Output |
622
- |---------------------------|-----------------|
623
- | `DynamicOptic.root.field("name")` | `.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")` | `<Some>` |
626
- | `DynamicOptic.root.at(0)` | `[0]` |
627
- | `DynamicOptic.root.atIndices(0, 2, 5)` | `[0,2,5]` |
628
- | `DynamicOptic.elements` | `[*]` |
629
- | `DynamicOptic.root.atKey("host")` | `{"host"}` |
630
- | `DynamicOptic.root.atKey(80)` | `{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,384 @@
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.22"
52
+ ```
53
+
54
+ Additional format modules are separate artifacts:
55
+
56
+ ```scala
57
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.22"
58
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.22"
59
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.22"
60
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.22"
61
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.22"
62
+ ```
63
+
64
+ For cross-platform projects (Scala.js):
65
+
66
+ ```scala
67
+ libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.22"
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 fromBytes: Either[SchemaError, Person] = codec.decode(bytes)
231
+
232
+ // String
233
+ val jsonStr: String = codec.encodeToString(person)
234
+ val fromStr: Either[SchemaError, Person] = codec.decode("""{"name":"Alice","age":30}""")
235
+
236
+ // InputStream / OutputStream
237
+ import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
238
+
239
+ val os = new ByteArrayOutputStream()
240
+ codec.encode(person, os)
241
+
242
+ val is = new ByteArrayInputStream(os.toByteArray)
243
+ val fromStream: Either[SchemaError, Person] = codec.decode(is)
244
+ ```
245
+
246
+ ### ToonBinaryCodec Convenience Methods
247
+
248
+ `ToonBinaryCodec[A]` provides the same set of overloads:
249
+
250
+ ```scala
251
+ import zio.blocks.schema._
252
+ import zio.blocks.schema.toon._
253
+
254
+ case class Person(name: String, age: Int)
255
+ object Person {
256
+ implicit val schema: Schema[Person] = Schema.derived
257
+ }
258
+
259
+ val codec = Schema[Person].derive(ToonFormat)
260
+ val person = Person("Alice", 30)
261
+
262
+ // Array[Byte]
263
+ val bytes: Array[Byte] = codec.encode(person)
264
+ val fromBytes: Either[SchemaError, Person] = codec.decode(bytes)
265
+
266
+ // String
267
+ val toonStr: String = codec.encodeToString(person)
268
+ val fromStr: Either[SchemaError, Person] = codec.decode("name: Alice\nage: 30")
269
+ ```
270
+
271
+ ### Summary of Convenience Methods
272
+
273
+ `BinaryCodec` subclasses (JSON, TOON, MessagePack, Avro, Thrift) expose the following convenience overloads (availability may vary by format):
274
+
275
+ | Method | Description |
276
+ |------------------------------------------------------|----------------------------------------------|
277
+ | `encode(value): Array[Byte]` | Encode to a byte array |
278
+ | `decode(input: Array[Byte]): Either[SchemaError, A]` | Decode from a byte array |
279
+ | `encode(value, output: ByteBuffer): Unit` | Encode into a `ByteBuffer` |
280
+ | `decode(input: ByteBuffer): Either[SchemaError, A]` | Decode from a `ByteBuffer` |
281
+ | `encode(value, output: OutputStream): Unit` | Encode into an `OutputStream` (JSON, TOON, Avro) |
282
+ | `decode(input: InputStream): Either[SchemaError, A]` | Decode from an `InputStream` (JSON, TOON, Avro) |
283
+ | `encodeToString(value): String` | Encode to a `String` (JSON, TOON) |
284
+ | `decode(input: String): Either[SchemaError, A]` | Decode from a `String` (JSON, TOON) |
285
+
286
+ The `String`-based methods are available on text-oriented binary codecs (JSON, TOON) but not on purely binary formats like Avro or Thrift.
287
+
288
+ ## Configuring Codecs
289
+
290
+ Format-specific derivers support configuration options that control encoding behavior. Instead of passing a `Format` object to `derive`, you pass a configured `Deriver`:
291
+
292
+ ### JSON Configuration
293
+
294
+ ```scala
295
+ import zio.blocks.schema._
296
+ import zio.blocks.schema.json._
297
+
298
+ case class Person(
299
+ firstName: String,
300
+ lastName: String,
301
+ middleName: Option[String] = None
302
+ )
303
+
304
+ object Person {
305
+ implicit val schema: Schema[Person] = Schema.derived
306
+ }
307
+
308
+ val customDeriver = JsonBinaryCodecDeriver
309
+ .withFieldNameMapper(NameMapper.SnakeCase)
310
+ .withTransientNone(true)
311
+ .withRejectExtraFields(true)
312
+
313
+ val codec = Schema[Person].derive(customDeriver)
314
+
315
+ // Encodes as: {"first_name":"Alice","last_name":"Smith"}
316
+ // (middleName omitted because it is None and transientNone is true)
317
+ val json = codec.encodeToString(Person("Alice", "Smith"))
318
+ ```
319
+
320
+ | Option | Description | Default |
321
+ |---------------------------------|--------------------------------------------------------|------------|
322
+ | `withFieldNameMapper` | Transform field names (Identity, SnakeCase, KebabCase) | `Identity` |
323
+ | `withCaseNameMapper` | Transform variant/case names | `Identity` |
324
+ | `withDiscriminatorKind` | ADT discriminator style (Key, Field, None) | `Key` |
325
+ | `withRejectExtraFields` | Error on unknown fields during decoding | `false` |
326
+ | `withEnumValuesAsStrings` | Encode enum values as strings | `true` |
327
+ | `withTransientNone` | Omit `None` values from output | `true` |
328
+ | `withTransientEmptyCollection` | Omit empty collections from output | `true` |
329
+ | `withTransientDefaultValue` | Omit fields with default values | `true` |
330
+ | `withRequireOptionFields` | Require optional fields in input | `false` |
331
+ | `withRequireCollectionFields` | Require collection fields in input | `false` |
332
+ | `withRequireDefaultValueFields` | Require fields with defaults in input | `false` |
333
+
334
+ ### TOON Configuration
335
+
336
+ ```scala
337
+ import zio.blocks.schema._
338
+ import zio.blocks.schema.toon._
339
+
340
+ case class Person(
341
+ firstName: String,
342
+ lastName: String
343
+ )
344
+
345
+ object Person {
346
+ implicit val schema: Schema[Person] = Schema.derived
347
+ }
348
+
349
+ val customDeriver = ToonBinaryCodecDeriver
350
+ .withFieldNameMapper(NameMapper.SnakeCase)
351
+ .withArrayFormat(ArrayFormat.Tabular)
352
+ .withDiscriminatorKind(DiscriminatorKind.Field("type"))
353
+
354
+ val codec = Schema[Person].derive(customDeriver)
355
+ ```
356
+
357
+ ## Error Handling
358
+
359
+ All `decode` operations return `Either[SchemaError, A]`. `SchemaError` includes path information that pinpoints where in the data structure decoding failed:
360
+
361
+ ```scala
362
+ import zio.blocks.schema._
363
+ import zio.blocks.schema.json._
364
+
365
+ case class Address(street: String, city: String)
366
+ case class Person(name: String, address: Address)
367
+
368
+ object Address {
369
+ implicit val schema: Schema[Address] = Schema.derived
370
+ }
371
+ object Person {
372
+ implicit val schema: Schema[Person] = Schema.derived
373
+ }
374
+
375
+ val codec = Schema[Person].derive(JsonFormat)
376
+
377
+ // Missing required field
378
+ val result = codec.decode("""{"name":"Alice","address":{}}""")
379
+
380
+ result match {
381
+ case Right(person) => println(person)
382
+ case Left(error) => error.errors.foreach(e => println(s"Error: ${e.message}"))
383
+ }
384
+ ```
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.21"
13
+ libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.22"
14
14
  ```
15
15
 
16
16
  ## Core Types