@zio.dev/zio-blocks 0.0.27 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guides/query-dsl-extending.md +1 -1
- package/guides/query-dsl-fluent-builder.md +1 -1
- package/guides/query-dsl-reified-optics.md +1 -1
- package/guides/query-dsl-sql.md +1 -1
- package/guides/zio-schema-migration.md +6 -6
- package/index.md +18 -12
- package/package.json +1 -1
- package/reference/allows.md +1059 -34
- package/reference/binding-resolver.md +469 -0
- package/reference/binding.md +1 -1
- package/reference/codec.md +7 -7
- package/reference/docs.md +1 -1
- package/reference/dynamic-optic.md +5 -0
- package/reference/dynamic-schema.md +602 -0
- package/reference/dynamic-value.md +5 -0
- package/reference/json-patch.md +803 -0
- package/reference/json.md +1 -1
- package/reference/media-type.md +2 -2
- package/reference/patch.md +4 -0
- package/reference/schema-evolution/as.md +587 -0
- package/reference/schema-evolution/index.md +50 -0
- package/reference/schema-evolution/into.md +1027 -0
- package/reference/schema-expr.md +2 -2
- package/reference/structural-types.md +369 -0
- package/reference/type-class-derivation.md +1 -1
- package/reference/xml.md +606 -45
- package/scope.md +91 -9
- package/sidebars.js +19 -1
- package/reference/schema-evolution.md +0 -540
package/reference/xml.md
CHANGED
|
@@ -3,10 +3,31 @@ id: xml
|
|
|
3
3
|
title: "XML"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
`Xml` is a **sealed trait representing XML nodes**. It provides a type-safe, immutable representation of all valid XML document structures including elements, text nodes, CDATA sections, comments, and processing instructions.
|
|
7
|
+
|
|
8
|
+
```scala
|
|
9
|
+
sealed trait Xml {
|
|
10
|
+
def xmlType: XmlType
|
|
11
|
+
def is(xmlType: XmlType): Boolean
|
|
12
|
+
def as(xmlType: XmlType): Option[xmlType.Type]
|
|
13
|
+
def unwrap(xmlType: XmlType): Option[xmlType.Unwrap]
|
|
14
|
+
def print: String
|
|
15
|
+
def printPretty: String
|
|
16
|
+
def select: XmlSelection
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`Xml` supports:
|
|
21
|
+
- Type-safe representation of all XML node types
|
|
22
|
+
- Fluent navigation and querying with XmlSelection
|
|
23
|
+
- Schema-derived automatic codec generation
|
|
24
|
+
- Zero external dependencies
|
|
25
|
+
- Full cross-platform support (JVM and Scala.js)
|
|
7
26
|
|
|
8
27
|
## Overview
|
|
9
28
|
|
|
29
|
+
Zero-dependency XML codec for ZIO Blocks Schema with cross-platform support.
|
|
30
|
+
|
|
10
31
|
The schema-xml module provides automatic XML codec derivation for any type with a `Schema`. It includes a complete XML AST, fluent navigation API, and support for XML-specific features like attributes and namespaces.
|
|
11
32
|
|
|
12
33
|
Key features:
|
|
@@ -21,14 +42,20 @@ Key features:
|
|
|
21
42
|
|
|
22
43
|
## Installation
|
|
23
44
|
|
|
45
|
+
To use the schema-xml module, add the following dependency to your `build.sbt`:
|
|
46
|
+
|
|
24
47
|
```scala
|
|
25
48
|
libraryDependencies += "dev.zio" %% "zio-blocks-schema-xml" % "0.0.14"
|
|
26
49
|
```
|
|
27
50
|
|
|
28
51
|
## Basic Usage
|
|
29
52
|
|
|
53
|
+
Start by deriving an XML codec from your Schema definition:
|
|
54
|
+
|
|
30
55
|
### Deriving Codecs
|
|
31
56
|
|
|
57
|
+
To create an XML codec, use `Schema[A].derive(XmlFormat)`:
|
|
58
|
+
|
|
32
59
|
```scala
|
|
33
60
|
import zio.blocks.schema._
|
|
34
61
|
import zio.blocks.schema.xml._
|
|
@@ -45,6 +72,8 @@ val codec = Schema[Person].derive(XmlFormat)
|
|
|
45
72
|
|
|
46
73
|
### Encoding to XML
|
|
47
74
|
|
|
75
|
+
Encode your values to XML using the codec:
|
|
76
|
+
|
|
48
77
|
```scala
|
|
49
78
|
import zio.blocks.schema._
|
|
50
79
|
import zio.blocks.schema.xml._
|
|
@@ -56,24 +85,85 @@ object Person {
|
|
|
56
85
|
|
|
57
86
|
val codec = Schema[Person].derive(XmlFormat)
|
|
58
87
|
val person = Person("Alice", 30)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Encode to XML bytes:
|
|
59
91
|
|
|
60
|
-
|
|
92
|
+
```scala
|
|
61
93
|
val bytes: Array[Byte] = codec.encode(person)
|
|
94
|
+
// bytes: Array[Byte] = Array(
|
|
95
|
+
// 60,
|
|
96
|
+
// 80,
|
|
97
|
+
// 101,
|
|
98
|
+
// 114,
|
|
99
|
+
// 115,
|
|
100
|
+
// 111,
|
|
101
|
+
// 110,
|
|
102
|
+
// 62,
|
|
103
|
+
// 60,
|
|
104
|
+
// 110,
|
|
105
|
+
// 97,
|
|
106
|
+
// 109,
|
|
107
|
+
// 101,
|
|
108
|
+
// 62,
|
|
109
|
+
// 65,
|
|
110
|
+
// 108,
|
|
111
|
+
// 105,
|
|
112
|
+
// 99,
|
|
113
|
+
// 101,
|
|
114
|
+
// 60,
|
|
115
|
+
// 47,
|
|
116
|
+
// 110,
|
|
117
|
+
// 97,
|
|
118
|
+
// 109,
|
|
119
|
+
// 101,
|
|
120
|
+
// 62,
|
|
121
|
+
// 60,
|
|
122
|
+
// 97,
|
|
123
|
+
// 103,
|
|
124
|
+
// 101,
|
|
125
|
+
// 62,
|
|
126
|
+
// 51,
|
|
127
|
+
// 48,
|
|
128
|
+
// 60,
|
|
129
|
+
// 47,
|
|
130
|
+
// 97,
|
|
131
|
+
// 103,
|
|
132
|
+
// 101,
|
|
133
|
+
// 62,
|
|
134
|
+
// 60,
|
|
135
|
+
// 47,
|
|
136
|
+
// 80,
|
|
137
|
+
// 101,
|
|
138
|
+
// 114,
|
|
139
|
+
// 115,
|
|
140
|
+
// 111,
|
|
141
|
+
// 110,
|
|
142
|
+
// 62
|
|
143
|
+
// )
|
|
144
|
+
```
|
|
62
145
|
|
|
63
|
-
|
|
146
|
+
Encode to XML string:
|
|
147
|
+
|
|
148
|
+
```scala
|
|
64
149
|
val xmlString: String = codec.encodeToString(person)
|
|
65
|
-
// <Person><name>Alice</name><age>30</age></Person>
|
|
150
|
+
// xmlString: String = "<Person><name>Alice</name><age>30</age></Person>"
|
|
151
|
+
```
|
|
66
152
|
|
|
67
|
-
|
|
153
|
+
Encode to pretty-printed XML:
|
|
154
|
+
|
|
155
|
+
```scala
|
|
68
156
|
val prettyXml = codec.encodeToString(person, WriterConfig.pretty)
|
|
69
|
-
// <Person>
|
|
157
|
+
// prettyXml: String = """<Person>
|
|
70
158
|
// <name>Alice</name>
|
|
71
159
|
// <age>30</age>
|
|
72
|
-
// </Person>
|
|
160
|
+
// </Person>"""
|
|
73
161
|
```
|
|
74
162
|
|
|
75
163
|
### Decoding from XML
|
|
76
164
|
|
|
165
|
+
Decode XML strings or bytes back to your typed values:
|
|
166
|
+
|
|
77
167
|
```scala
|
|
78
168
|
import zio.blocks.schema._
|
|
79
169
|
import zio.blocks.schema.xml._
|
|
@@ -87,12 +177,28 @@ val codec = Schema[Person].derive(XmlFormat)
|
|
|
87
177
|
|
|
88
178
|
// Decode from XML string
|
|
89
179
|
val xml = "<Person><name>Alice</name><age>30</age></Person>"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Decode the XML string and see the result:
|
|
183
|
+
|
|
184
|
+
```scala
|
|
90
185
|
val result: Either[SchemaError, Person] = codec.decode(xml)
|
|
91
|
-
// Right(
|
|
186
|
+
// result: Either[SchemaError, Person] = Right(
|
|
187
|
+
// Person(name = "Alice", age = 30)
|
|
188
|
+
// )
|
|
189
|
+
```
|
|
92
190
|
|
|
93
|
-
|
|
191
|
+
You can also decode from bytes:
|
|
192
|
+
|
|
193
|
+
```scala
|
|
94
194
|
val bytes = xml.getBytes("UTF-8")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```scala
|
|
95
198
|
val fromBytes: Either[SchemaError, Person] = codec.decode(bytes)
|
|
199
|
+
// fromBytes: Either[SchemaError, Person] = Right(
|
|
200
|
+
// Person(name = "Alice", age = 30)
|
|
201
|
+
// )
|
|
96
202
|
```
|
|
97
203
|
|
|
98
204
|
## XML AST
|
|
@@ -110,6 +216,8 @@ Xml
|
|
|
110
216
|
|
|
111
217
|
### Creating XML Nodes
|
|
112
218
|
|
|
219
|
+
Construct XML nodes directly using the case class constructors:
|
|
220
|
+
|
|
113
221
|
```scala
|
|
114
222
|
import zio.blocks.schema.xml._
|
|
115
223
|
import zio.blocks.chunk.Chunk
|
|
@@ -136,7 +244,7 @@ val pi = Xml.ProcessingInstruction("xml-stylesheet", "href=\"style.css\"")
|
|
|
136
244
|
|
|
137
245
|
### XmlName
|
|
138
246
|
|
|
139
|
-
`XmlName` represents an element or attribute name with optional namespace:
|
|
247
|
+
`XmlName` represents an element or attribute name with optional namespace. Create instances with different namespace configurations:
|
|
140
248
|
|
|
141
249
|
```scala
|
|
142
250
|
import zio.blocks.schema.xml.XmlName
|
|
@@ -185,9 +293,11 @@ val commentNode = XmlBuilder.comment("comment text")
|
|
|
185
293
|
|
|
186
294
|
## Configuration
|
|
187
295
|
|
|
296
|
+
The schema-xml module provides configuration options for both parsing and writing:
|
|
297
|
+
|
|
188
298
|
### WriterConfig
|
|
189
299
|
|
|
190
|
-
|
|
300
|
+
Use `WriterConfig` to control XML output formatting:
|
|
191
301
|
|
|
192
302
|
```scala
|
|
193
303
|
import zio.blocks.schema.xml.WriterConfig
|
|
@@ -268,11 +378,13 @@ object Person {
|
|
|
268
378
|
|
|
269
379
|
val codec = Schema[Person].derive(XmlFormat)
|
|
270
380
|
val person = Person("123", "active", "Alice", 30)
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Encode the person with attributes:
|
|
384
|
+
|
|
385
|
+
```scala
|
|
271
386
|
val xml = codec.encodeToString(person)
|
|
272
|
-
// <Person
|
|
273
|
-
// <name>Alice</name>
|
|
274
|
-
// <age>30</age>
|
|
275
|
-
// </Person>
|
|
387
|
+
// xml: String = "<Person><id>123</id><active>active</active><name>Alice</name><age>30</age></Person>"
|
|
276
388
|
```
|
|
277
389
|
|
|
278
390
|
The `@xmlAttribute` annotation accepts an optional custom name:
|
|
@@ -300,19 +412,18 @@ object Feed {
|
|
|
300
412
|
|
|
301
413
|
val codec = Schema[Feed].derive(XmlFormat)
|
|
302
414
|
val feed = Feed("My Blog", "2024-01-01T00:00:00Z")
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Encode the feed with a namespace prefix:
|
|
418
|
+
|
|
419
|
+
```scala
|
|
303
420
|
val xml = codec.encodeToString(feed)
|
|
304
|
-
// <
|
|
305
|
-
// <title>My Blog</title>
|
|
306
|
-
// <updated>2024-01-01T00:00:00Z</updated>
|
|
307
|
-
// </atom:Feed>
|
|
421
|
+
// xml: String = "<Feed><title>My Blog</title><updated>2024-01-01T00:00:00Z</updated></Feed>"
|
|
308
422
|
```
|
|
309
423
|
|
|
310
424
|
Without a prefix (default namespace):
|
|
311
425
|
|
|
312
426
|
```scala
|
|
313
|
-
import zio.blocks.schema._
|
|
314
|
-
import zio.blocks.schema.xml._
|
|
315
|
-
|
|
316
427
|
@xmlNamespace(uri = "http://www.w3.org/2005/Atom")
|
|
317
428
|
case class Feed(title: String)
|
|
318
429
|
|
|
@@ -322,10 +433,13 @@ object Feed {
|
|
|
322
433
|
|
|
323
434
|
val codec = Schema[Feed].derive(XmlFormat)
|
|
324
435
|
val feed = Feed("My Blog")
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Encode the feed with default namespace:
|
|
439
|
+
|
|
440
|
+
```scala
|
|
325
441
|
val xml = codec.encodeToString(feed)
|
|
326
|
-
// <Feed
|
|
327
|
-
// <title>My Blog</title>
|
|
328
|
-
// </Feed>
|
|
442
|
+
// xml: String = "<Feed><title>My Blog</title></Feed>"
|
|
329
443
|
```
|
|
330
444
|
|
|
331
445
|
## XmlSelection
|
|
@@ -334,6 +448,8 @@ val xml = codec.encodeToString(feed)
|
|
|
334
448
|
|
|
335
449
|
### Navigation
|
|
336
450
|
|
|
451
|
+
Navigate to child elements, filter by type, and extract content:
|
|
452
|
+
|
|
337
453
|
```scala
|
|
338
454
|
import zio.blocks.schema.xml._
|
|
339
455
|
|
|
@@ -357,17 +473,43 @@ val books = xml.select.get("library").get("books")
|
|
|
357
473
|
|
|
358
474
|
// Navigate by index
|
|
359
475
|
val firstBook = books.get("book")(0)
|
|
476
|
+
```
|
|
360
477
|
|
|
361
|
-
|
|
478
|
+
Extract text content from the first book:
|
|
479
|
+
|
|
480
|
+
```scala
|
|
362
481
|
val title: Either[XmlError, String] = firstBook.get("title").text
|
|
363
|
-
//
|
|
482
|
+
// title: Either[XmlError, String] = Left(
|
|
483
|
+
// zio.blocks.schema.xml.XmlError: Expected single value but got 0
|
|
484
|
+
// )
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
You can also navigate descendants recursively:
|
|
364
488
|
|
|
365
|
-
|
|
489
|
+
```scala
|
|
366
490
|
val allTitles = xml.select.descendant("title")
|
|
491
|
+
// allTitles: XmlSelection = XmlSelection(
|
|
492
|
+
// Right(
|
|
493
|
+
// IndexedSeq(
|
|
494
|
+
// Element(
|
|
495
|
+
// name = XmlName(localName = "title", prefix = None, namespace = None),
|
|
496
|
+
// attributes = IndexedSeq(),
|
|
497
|
+
// children = IndexedSeq(Text("Functional Programming"))
|
|
498
|
+
// ),
|
|
499
|
+
// Element(
|
|
500
|
+
// name = XmlName(localName = "title", prefix = None, namespace = None),
|
|
501
|
+
// attributes = IndexedSeq(),
|
|
502
|
+
// children = IndexedSeq(Text("Advanced Scala"))
|
|
503
|
+
// )
|
|
504
|
+
// )
|
|
505
|
+
// )
|
|
506
|
+
// )
|
|
367
507
|
```
|
|
368
508
|
|
|
369
509
|
### Filtering
|
|
370
510
|
|
|
511
|
+
Filter selections by node type or custom predicates:
|
|
512
|
+
|
|
371
513
|
```scala
|
|
372
514
|
import zio.blocks.schema.xml._
|
|
373
515
|
|
|
@@ -384,6 +526,8 @@ val filtered = selection.filter(xml => xml.is(XmlType.Element))
|
|
|
384
526
|
|
|
385
527
|
### Terminal Operations
|
|
386
528
|
|
|
529
|
+
Execute a selection to extract values or convert to other formats:
|
|
530
|
+
|
|
387
531
|
```scala
|
|
388
532
|
import zio.blocks.schema.xml._
|
|
389
533
|
|
|
@@ -408,6 +552,8 @@ val allText: String = selection.textContent
|
|
|
408
552
|
|
|
409
553
|
### Combinators
|
|
410
554
|
|
|
555
|
+
Combine and transform selections using monadic operations:
|
|
556
|
+
|
|
411
557
|
```scala
|
|
412
558
|
import zio.blocks.schema.xml._
|
|
413
559
|
|
|
@@ -433,6 +579,8 @@ val withFallback = selection1.orElse(selection2)
|
|
|
433
579
|
|
|
434
580
|
### Creating Patches
|
|
435
581
|
|
|
582
|
+
Create patches for add, remove, replace, and attribute operations:
|
|
583
|
+
|
|
436
584
|
```scala
|
|
437
585
|
import zio.blocks.schema._
|
|
438
586
|
import zio.blocks.schema.xml._
|
|
@@ -464,6 +612,8 @@ val removeAttrPatch = XmlPatch.removeAttribute(path, "id")
|
|
|
464
612
|
|
|
465
613
|
### Position Options
|
|
466
614
|
|
|
615
|
+
Position options control where new content is inserted relative to the target:
|
|
616
|
+
|
|
467
617
|
```scala
|
|
468
618
|
import zio.blocks.schema.xml.XmlPatch.Position
|
|
469
619
|
|
|
@@ -475,6 +625,8 @@ Position.AppendChild // Insert as last child of target
|
|
|
475
625
|
|
|
476
626
|
### Applying Patches
|
|
477
627
|
|
|
628
|
+
Apply a patch to an XML document to produce a modified result:
|
|
629
|
+
|
|
478
630
|
```scala
|
|
479
631
|
import zio.blocks.schema._
|
|
480
632
|
import zio.blocks.schema.xml._
|
|
@@ -488,6 +640,8 @@ val result: Either[XmlError, Xml] = patch(xml)
|
|
|
488
640
|
|
|
489
641
|
### Composing Patches
|
|
490
642
|
|
|
643
|
+
Combine multiple patches to apply transformations in sequence:
|
|
644
|
+
|
|
491
645
|
```scala
|
|
492
646
|
import zio.blocks.schema._
|
|
493
647
|
import zio.blocks.schema.xml._
|
|
@@ -503,6 +657,175 @@ val patch2 = XmlPatch.add(
|
|
|
503
657
|
val combined = patch1 ++ patch2
|
|
504
658
|
```
|
|
505
659
|
|
|
660
|
+
## XmlEncoder and XmlDecoder
|
|
661
|
+
|
|
662
|
+
For more fine-grained control over XML serialization, use the separate `XmlEncoder` and `XmlDecoder` traits:
|
|
663
|
+
|
|
664
|
+
### XmlEncoder
|
|
665
|
+
|
|
666
|
+
`XmlEncoder[A]` provides type-safe XML encoding:
|
|
667
|
+
|
|
668
|
+
```scala
|
|
669
|
+
import zio.blocks.schema._
|
|
670
|
+
import zio.blocks.schema.xml._
|
|
671
|
+
|
|
672
|
+
// Automatic derivation from Schema
|
|
673
|
+
case class Person(name: String, age: Int)
|
|
674
|
+
object Person {
|
|
675
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
676
|
+
implicit val encoder: XmlEncoder[Person] = XmlEncoder.fromSchema
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
val person = Person("Alice", 30)
|
|
680
|
+
val xml: Xml = XmlEncoder[Person].encode(person)
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
#### Creating custom encoders
|
|
684
|
+
|
|
685
|
+
Create custom encoders from functions or using contravariance:
|
|
686
|
+
|
|
687
|
+
```scala
|
|
688
|
+
import zio.blocks.schema.xml._
|
|
689
|
+
|
|
690
|
+
// Create from a function
|
|
691
|
+
val customEncoder: XmlEncoder[Int] = XmlEncoder.instance(n =>
|
|
692
|
+
Xml.Element("number", Xml.Text(n.toString))
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
// Map with contravariance - encode a wrapper type
|
|
696
|
+
case class UserId(value: Int)
|
|
697
|
+
|
|
698
|
+
val userIdEncoder: XmlEncoder[UserId] =
|
|
699
|
+
customEncoder.contramap[UserId](_.value)
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
#### Using implicit resolution
|
|
703
|
+
|
|
704
|
+
Leverage implicit resolution for automatic encoder derivation:
|
|
705
|
+
|
|
706
|
+
```scala
|
|
707
|
+
import zio.blocks.schema._
|
|
708
|
+
import zio.blocks.schema.xml._
|
|
709
|
+
|
|
710
|
+
case class Product(id: String, price: Double)
|
|
711
|
+
object Product {
|
|
712
|
+
implicit val schema: Schema[Product] = Schema.derived
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// No explicit encoder needed - derives automatically
|
|
716
|
+
def encodeProduct[A](value: A)(implicit encoder: XmlEncoder[A]): Xml =
|
|
717
|
+
encoder.encode(value)
|
|
718
|
+
|
|
719
|
+
val result = encodeProduct(Product("item-1", 99.99))
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
### XmlDecoder
|
|
723
|
+
|
|
724
|
+
`XmlDecoder[A]` provides type-safe XML decoding with error handling:
|
|
725
|
+
|
|
726
|
+
```scala
|
|
727
|
+
import zio.blocks.schema._
|
|
728
|
+
import zio.blocks.schema.xml._
|
|
729
|
+
|
|
730
|
+
// Automatic derivation from Schema
|
|
731
|
+
case class Person(name: String, age: Int)
|
|
732
|
+
object Person {
|
|
733
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
734
|
+
implicit val decoder: XmlDecoder[Person] = XmlDecoder.fromSchema
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
val xml = Xml.Element("Person",
|
|
738
|
+
Xml.Element("name", Xml.Text("Alice")),
|
|
739
|
+
Xml.Element("age", Xml.Text("30"))
|
|
740
|
+
)
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
Decode the XML:
|
|
744
|
+
|
|
745
|
+
```scala
|
|
746
|
+
val result: Either[XmlError, Person] = XmlDecoder[Person].decode(xml)
|
|
747
|
+
// result: Either[XmlError, Person] = Right(Person(name = "Alice", age = 30))
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
#### Creating custom decoders
|
|
751
|
+
|
|
752
|
+
Create custom decoders from functions or using covariance:
|
|
753
|
+
|
|
754
|
+
```scala
|
|
755
|
+
import zio.blocks.schema.xml._
|
|
756
|
+
import zio.blocks.chunk.Chunk
|
|
757
|
+
|
|
758
|
+
// Create from a function
|
|
759
|
+
val numberDecoder: XmlDecoder[Int] = XmlDecoder.instance { xml =>
|
|
760
|
+
xml match {
|
|
761
|
+
case Xml.Element(_, _, Chunk(Xml.Text(text), _*)) =>
|
|
762
|
+
text.toIntOption.toRight(XmlError("Invalid number"))
|
|
763
|
+
case _ => Left(XmlError("Expected number element"))
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Map for covariance - decode to a wrapper type
|
|
768
|
+
case class UserId(value: Int)
|
|
769
|
+
|
|
770
|
+
val userIdDecoder: XmlDecoder[UserId] =
|
|
771
|
+
numberDecoder.map(UserId(_))
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
#### Error handling with decoders
|
|
775
|
+
|
|
776
|
+
Handle decoding errors gracefully with fallback strategies:
|
|
777
|
+
|
|
778
|
+
```scala
|
|
779
|
+
import zio.blocks.schema._
|
|
780
|
+
import zio.blocks.schema.xml._
|
|
781
|
+
|
|
782
|
+
case class Person(name: String, age: Int)
|
|
783
|
+
object Person {
|
|
784
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
def decodeWithFallback[A](
|
|
788
|
+
xml: Xml,
|
|
789
|
+
fallback: A
|
|
790
|
+
)(implicit decoder: XmlDecoder[A]): A = {
|
|
791
|
+
decoder.decode(xml).getOrElse(fallback)
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
val invalidXml = Xml.Element("Empty")
|
|
795
|
+
val defaultPerson = Person("Unknown", 0)
|
|
796
|
+
val result = decodeWithFallback(invalidXml, defaultPerson)
|
|
797
|
+
// Person("Unknown", 0)
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
#### Combining encoders and decoders
|
|
801
|
+
|
|
802
|
+
Round-trip values by encoding and decoding:
|
|
803
|
+
|
|
804
|
+
```scala
|
|
805
|
+
import zio.blocks.schema._
|
|
806
|
+
import zio.blocks.schema.xml._
|
|
807
|
+
import zio.blocks.schema.xml.syntax._
|
|
808
|
+
|
|
809
|
+
case class Message(id: String, text: String)
|
|
810
|
+
object Message {
|
|
811
|
+
implicit val schema: Schema[Message] = Schema.derived
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Round-trip: encode then decode
|
|
815
|
+
val message = Message("msg-1", "Hello")
|
|
816
|
+
val encoded: Xml = message.toXml
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
Decode the encoded value:
|
|
820
|
+
|
|
821
|
+
```scala
|
|
822
|
+
val result: Either[XmlError, Message] =
|
|
823
|
+
implicitly[XmlDecoder[Message]].decode(encoded)
|
|
824
|
+
// result: Either[XmlError, Message] = Right(
|
|
825
|
+
// Message(id = "msg-1", text = "Hello")
|
|
826
|
+
// )
|
|
827
|
+
```
|
|
828
|
+
|
|
506
829
|
## Extension Syntax
|
|
507
830
|
|
|
508
831
|
When a `Schema` is in scope, use convenient extension methods:
|
|
@@ -539,29 +862,40 @@ val fromBytes: Either[SchemaError, Person] = bytes.fromXml[Person]
|
|
|
539
862
|
|
|
540
863
|
## Printing XML
|
|
541
864
|
|
|
542
|
-
|
|
865
|
+
Format XML documents using compact or pretty-printed output. You can convert XML to string with different formatting options:
|
|
543
866
|
|
|
544
867
|
```scala
|
|
545
868
|
import zio.blocks.schema.xml._
|
|
546
869
|
|
|
547
870
|
val xml = Xml.Element("person", Xml.Element("name", Xml.Text("Alice")))
|
|
871
|
+
```
|
|
548
872
|
|
|
549
|
-
|
|
873
|
+
Compact output:
|
|
874
|
+
|
|
875
|
+
```scala
|
|
550
876
|
val compact: String = xml.print
|
|
551
|
-
// <person><name>Alice</name></person>
|
|
877
|
+
// compact: String = "<person><name>Alice</name></person>"
|
|
878
|
+
```
|
|
552
879
|
|
|
553
|
-
|
|
880
|
+
Pretty-printed output:
|
|
881
|
+
|
|
882
|
+
```scala
|
|
554
883
|
val pretty: String = xml.printPretty
|
|
555
|
-
// <person>
|
|
884
|
+
// pretty: String = """<person>
|
|
556
885
|
// <name>Alice</name>
|
|
557
|
-
// </person>
|
|
886
|
+
// </person>"""
|
|
887
|
+
```
|
|
558
888
|
|
|
559
|
-
|
|
889
|
+
Custom configuration:
|
|
890
|
+
|
|
891
|
+
```scala
|
|
560
892
|
val custom: String = xml.print(WriterConfig(indentStep = 4))
|
|
561
893
|
```
|
|
562
894
|
|
|
563
895
|
## Type Testing and Access
|
|
564
896
|
|
|
897
|
+
Test and extract values from XML nodes using type guards and unwrapping:
|
|
898
|
+
|
|
565
899
|
```scala
|
|
566
900
|
import zio.blocks.schema.xml._
|
|
567
901
|
|
|
@@ -608,9 +942,43 @@ All standard ZIO Blocks Schema types are supported:
|
|
|
608
942
|
- Options (`Option[A]`)
|
|
609
943
|
- Wrappers (newtypes)
|
|
610
944
|
|
|
945
|
+
## XmlBinaryCodec
|
|
946
|
+
|
|
947
|
+
`XmlBinaryCodec[A]` is the low-level codec interface that bridges Schema definitions with XML serialization. While usually derived automatically, you can work with it directly:
|
|
948
|
+
|
|
949
|
+
```scala
|
|
950
|
+
import zio.blocks.schema._
|
|
951
|
+
import zio.blocks.schema.xml._
|
|
952
|
+
|
|
953
|
+
case class Person(name: String, age: Int)
|
|
954
|
+
object Person {
|
|
955
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// Get the underlying binary codec
|
|
959
|
+
val codec: XmlBinaryCodec[Person] =
|
|
960
|
+
Schema[Person].derive(XmlBinaryCodecDeriver)
|
|
961
|
+
|
|
962
|
+
// Encode to Xml directly
|
|
963
|
+
val person = Person("Alice", 30)
|
|
964
|
+
val xml: Xml = codec.encodeValue(person)
|
|
965
|
+
|
|
966
|
+
// Decode from Xml directly
|
|
967
|
+
val decoded: Either[XmlError, Person] = codec.decodeValue(xml)
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
**XmlBinaryCodec supports all Schema types:**
|
|
971
|
+
- Primitives (Int, String, Boolean, etc.)
|
|
972
|
+
- Java time types (Instant, LocalDate, Duration, etc.)
|
|
973
|
+
- Records (case classes with field-level configuration)
|
|
974
|
+
- Variants (sealed traits with discriminators)
|
|
975
|
+
- Collections (List, Vector, Map, etc.)
|
|
976
|
+
- Optional fields (Option[A])
|
|
977
|
+
- Custom wrappers and dynamic values
|
|
978
|
+
|
|
611
979
|
## Error Handling
|
|
612
980
|
|
|
613
|
-
All decoding operations return `Either[SchemaError, A]` or `Either[XmlError, A]
|
|
981
|
+
All decoding operations return `Either[SchemaError, A]` or `Either[XmlError, A]`. The `XmlError` type provides detailed error information:
|
|
614
982
|
|
|
615
983
|
```scala
|
|
616
984
|
import zio.blocks.schema._
|
|
@@ -629,12 +997,45 @@ val result = codec.decode(invalid)
|
|
|
629
997
|
|
|
630
998
|
result match {
|
|
631
999
|
case Right(person) => println(s"Decoded: $person")
|
|
632
|
-
case Left(error) =>
|
|
1000
|
+
case Left(error) =>
|
|
633
1001
|
println(s"Error: ${error.getMessage}")
|
|
634
1002
|
// Error information includes parse location and context
|
|
635
1003
|
}
|
|
636
1004
|
```
|
|
637
1005
|
|
|
1006
|
+
`XmlError` provides detailed error information for debugging:
|
|
1007
|
+
|
|
1008
|
+
```scala
|
|
1009
|
+
import zio.blocks.schema.xml._
|
|
1010
|
+
|
|
1011
|
+
val error = XmlError("Parse failed")
|
|
1012
|
+
|
|
1013
|
+
// Error message
|
|
1014
|
+
val message: String = error.getMessage
|
|
1015
|
+
|
|
1016
|
+
// Get error message for inspection
|
|
1017
|
+
val errorMsg: String = error.getMessage
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
Error handling best practices:
|
|
1021
|
+
|
|
1022
|
+
```scala
|
|
1023
|
+
import zio.blocks.schema._
|
|
1024
|
+
import zio.blocks.schema.xml._
|
|
1025
|
+
|
|
1026
|
+
case class Config(database: String, port: Int)
|
|
1027
|
+
object Config {
|
|
1028
|
+
implicit val schema: Schema[Config] = Schema.derived
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
def loadConfig(xml: String): Either[String, Config] = {
|
|
1032
|
+
val codec = Schema[Config].derive(XmlFormat)
|
|
1033
|
+
codec.decode(xml).left.map { error =>
|
|
1034
|
+
s"Configuration error: ${error.getMessage}\nCheck XML format and required fields."
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
```
|
|
1038
|
+
|
|
638
1039
|
## Cross-Platform Support
|
|
639
1040
|
|
|
640
1041
|
The XML module works across all platforms:
|
|
@@ -646,8 +1047,12 @@ All features including parsing, writing, navigation, and patching work identical
|
|
|
646
1047
|
|
|
647
1048
|
## Examples
|
|
648
1049
|
|
|
1050
|
+
These examples demonstrate common use cases and patterns with the XML module:
|
|
1051
|
+
|
|
649
1052
|
### Complete Example with Attributes and Namespaces
|
|
650
1053
|
|
|
1054
|
+
Define a schema with attributes and namespaces, then encode and decode:
|
|
1055
|
+
|
|
651
1056
|
```scala
|
|
652
1057
|
import zio.blocks.schema._
|
|
653
1058
|
import zio.blocks.schema.xml._
|
|
@@ -675,26 +1080,41 @@ val entry = Entry(
|
|
|
675
1080
|
updated = "2024-01-01T00:00:00Z",
|
|
676
1081
|
author = Author("Alice", "alice@example.com")
|
|
677
1082
|
)
|
|
1083
|
+
```
|
|
678
1084
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
1085
|
+
Encode the entry with pretty printing:
|
|
1086
|
+
|
|
1087
|
+
```scala
|
|
1088
|
+
val xmlStr = codec.encodeToString(entry, WriterConfig.pretty)
|
|
1089
|
+
// xmlStr: String = """<Entry>
|
|
1090
|
+
// <id>entry-1</id>
|
|
683
1091
|
// <title>First Post</title>
|
|
684
1092
|
// <updated>2024-01-01T00:00:00Z</updated>
|
|
685
1093
|
// <author>
|
|
686
1094
|
// <name>Alice</name>
|
|
687
1095
|
// <email>alice@example.com</email>
|
|
688
1096
|
// </author>
|
|
689
|
-
// </
|
|
1097
|
+
// </Entry>"""
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
Decode the XML back to a typed value:
|
|
690
1101
|
|
|
691
|
-
|
|
692
|
-
val decoded = codec.decode(
|
|
693
|
-
//
|
|
1102
|
+
```scala
|
|
1103
|
+
val decoded = codec.decode(xmlStr)
|
|
1104
|
+
// decoded: Either[SchemaError, Entry] = Right(
|
|
1105
|
+
// Entry(
|
|
1106
|
+
// id = "entry-1",
|
|
1107
|
+
// title = "First Post",
|
|
1108
|
+
// updated = "2024-01-01T00:00:00Z",
|
|
1109
|
+
// author = Author(name = "Alice", email = "alice@example.com")
|
|
1110
|
+
// )
|
|
1111
|
+
// )
|
|
694
1112
|
```
|
|
695
1113
|
|
|
696
1114
|
### Navigation and Transformation
|
|
697
1115
|
|
|
1116
|
+
Find elements, extract data, and apply patches to modify XML:
|
|
1117
|
+
|
|
698
1118
|
```scala
|
|
699
1119
|
import zio.blocks.schema._
|
|
700
1120
|
import zio.blocks.schema.xml._
|
|
@@ -741,3 +1161,144 @@ val patch = XmlPatch.add(
|
|
|
741
1161
|
|
|
742
1162
|
val updated = patch(xml)
|
|
743
1163
|
```
|
|
1164
|
+
|
|
1165
|
+
## Real-World Examples
|
|
1166
|
+
|
|
1167
|
+
Learn by examining practical examples of XML codecs in action:
|
|
1168
|
+
|
|
1169
|
+
### RSS Feed Parsing
|
|
1170
|
+
|
|
1171
|
+
Parse RSS feeds by defining a schema and decoding XML:
|
|
1172
|
+
|
|
1173
|
+
```scala
|
|
1174
|
+
import zio.blocks.schema._
|
|
1175
|
+
import zio.blocks.schema.xml._
|
|
1176
|
+
|
|
1177
|
+
@xmlNamespace(uri = "http://www.rss.org/", prefix = "rss")
|
|
1178
|
+
case class Item(
|
|
1179
|
+
@xmlAttribute() guid: String,
|
|
1180
|
+
title: String,
|
|
1181
|
+
link: String,
|
|
1182
|
+
pubDate: String,
|
|
1183
|
+
description: String
|
|
1184
|
+
)
|
|
1185
|
+
|
|
1186
|
+
@xmlNamespace(uri = "http://www.rss.org/", prefix = "rss")
|
|
1187
|
+
case class Channel(
|
|
1188
|
+
title: String,
|
|
1189
|
+
link: String,
|
|
1190
|
+
description: String,
|
|
1191
|
+
items: List[Item]
|
|
1192
|
+
)
|
|
1193
|
+
|
|
1194
|
+
object Channel {
|
|
1195
|
+
implicit val itemSchema: Schema[Item] = Schema.derived
|
|
1196
|
+
implicit val schema: Schema[Channel] = Schema.derived
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// Parse RSS feed from string
|
|
1200
|
+
val feedXml = """<rss:Channel xmlns:rss="http://www.rss.org/">
|
|
1201
|
+
<title>Tech Blog</title>
|
|
1202
|
+
<link>https://example.com</link>
|
|
1203
|
+
<description>Latest tech articles</description>
|
|
1204
|
+
<items>
|
|
1205
|
+
<Item guid="1">
|
|
1206
|
+
<title>Functional Programming in Scala</title>
|
|
1207
|
+
<link>https://example.com/fp</link>
|
|
1208
|
+
<pubDate>2024-01-01</pubDate>
|
|
1209
|
+
<description>Deep dive into FP concepts</description>
|
|
1210
|
+
</Item>
|
|
1211
|
+
</items>
|
|
1212
|
+
</rss:Channel>"""
|
|
1213
|
+
|
|
1214
|
+
val codec = Schema[Channel].derive(XmlFormat)
|
|
1215
|
+
val result: Either[SchemaError, Channel] = codec.decode(feedXml)
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
### Atom Feed with Advanced Features
|
|
1219
|
+
|
|
1220
|
+
Work with Atom feeds using attributes, multiple entries, and custom configurations:
|
|
1221
|
+
|
|
1222
|
+
```scala
|
|
1223
|
+
import zio.blocks.schema._
|
|
1224
|
+
import zio.blocks.schema.xml._
|
|
1225
|
+
|
|
1226
|
+
@xmlNamespace(uri = "http://www.w3.org/2005/Atom", prefix = "atom")
|
|
1227
|
+
case class Entry(
|
|
1228
|
+
@xmlAttribute() id: String,
|
|
1229
|
+
title: String,
|
|
1230
|
+
author: String,
|
|
1231
|
+
updated: String,
|
|
1232
|
+
@xmlAttribute("href") link: String
|
|
1233
|
+
)
|
|
1234
|
+
|
|
1235
|
+
@xmlNamespace(uri = "http://www.w3.org/2005/Atom", prefix = "atom")
|
|
1236
|
+
case class Feed(
|
|
1237
|
+
@xmlAttribute() id: String,
|
|
1238
|
+
title: String,
|
|
1239
|
+
updated: String,
|
|
1240
|
+
entries: List[Entry]
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1243
|
+
object Feed {
|
|
1244
|
+
implicit val entrySchema: Schema[Entry] = Schema.derived
|
|
1245
|
+
implicit val schema: Schema[Feed] = Schema.derived
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// Encode feed to XML with custom formatting
|
|
1249
|
+
val feed = Feed(
|
|
1250
|
+
id = "urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6",
|
|
1251
|
+
title = "Example Feed",
|
|
1252
|
+
updated = "2024-01-01T18:30:02Z",
|
|
1253
|
+
entries = List(
|
|
1254
|
+
Entry(
|
|
1255
|
+
id = "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a",
|
|
1256
|
+
title = "Atom-Powered Robots Run Amok",
|
|
1257
|
+
author = "John Doe",
|
|
1258
|
+
updated = "2024-01-01T18:30:02Z",
|
|
1259
|
+
link = "http://example.org/2024/01/entry"
|
|
1260
|
+
)
|
|
1261
|
+
)
|
|
1262
|
+
)
|
|
1263
|
+
|
|
1264
|
+
val codec = Schema[Feed].derive(XmlFormat)
|
|
1265
|
+
val xmlOutput = codec.encodeToString(feed, WriterConfig.pretty)
|
|
1266
|
+
```
|
|
1267
|
+
|
|
1268
|
+
### Sitemap XML
|
|
1269
|
+
|
|
1270
|
+
Generate sitemap XML with URLs and optional metadata fields:
|
|
1271
|
+
|
|
1272
|
+
```scala
|
|
1273
|
+
import zio.blocks.schema._
|
|
1274
|
+
import zio.blocks.schema.xml._
|
|
1275
|
+
|
|
1276
|
+
case class Url(
|
|
1277
|
+
loc: String,
|
|
1278
|
+
lastmod: Option[String],
|
|
1279
|
+
changefreq: Option[String],
|
|
1280
|
+
priority: Option[Double]
|
|
1281
|
+
)
|
|
1282
|
+
|
|
1283
|
+
case class Urlset(
|
|
1284
|
+
urls: List[Url]
|
|
1285
|
+
)
|
|
1286
|
+
|
|
1287
|
+
object Urlset {
|
|
1288
|
+
implicit val urlSchema: Schema[Url] = Schema.derived
|
|
1289
|
+
implicit val schema: Schema[Urlset] = Schema.derived
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// Build and encode sitemap
|
|
1293
|
+
val sitemap = Urlset(List(
|
|
1294
|
+
Url("https://example.com", Some("2024-01-01"), Some("monthly"), Some(1.0)),
|
|
1295
|
+
Url("https://example.com/about", Some("2024-01-01"), Some("monthly"), Some(0.8)),
|
|
1296
|
+
Url("https://example.com/contact", None, Some("monthly"), Some(0.5))
|
|
1297
|
+
))
|
|
1298
|
+
|
|
1299
|
+
val codec = Schema[Urlset].derive(XmlFormat)
|
|
1300
|
+
val sitemapXml = codec.encodeToString(sitemap, WriterConfig(
|
|
1301
|
+
indentStep = 2,
|
|
1302
|
+
includeDeclaration = true
|
|
1303
|
+
))
|
|
1304
|
+
```
|