@zio.dev/zio-blocks 0.0.33 → 0.0.55
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/adr/2026-07-18-data-migration.md +123 -0
- package/guides/async-getting-started.md +687 -0
- package/guides/compile-time-resource-safety-with-scope.md +21 -16
- package/guides/getting-started-with-mux.md +1395 -0
- package/guides/query-dsl-extending.md +161 -102
- package/guides/query-dsl-fluent-builder.md +217 -157
- package/guides/query-dsl-reified-optics.md +12 -10
- package/guides/query-dsl-sql.md +640 -165
- package/guides/sql-checked-interpolation.md +173 -0
- package/guides/sql-transactions.md +286 -0
- package/guides/telemetry-guide.md +1130 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +248 -389
- package/package.json +1 -1
- package/plans/config-follow-up-prs.md +188 -0
- package/plans/config-pr-assessment-roadmap.md +310 -0
- package/reference/MuxDataFlow.jsx +250 -0
- package/reference/async.md +1499 -0
- package/reference/chunk.md +3533 -308
- package/reference/codegen/case-class.md +436 -0
- package/reference/codegen/emitter-config.md +383 -0
- package/reference/codegen/examples.md +664 -0
- package/reference/codegen/field.md +316 -0
- package/reference/codegen/index.md +317 -0
- package/reference/codegen/scala-emitter.md +392 -0
- package/reference/codegen/scala-file.md +276 -0
- package/reference/codegen/sealed-trait.md +408 -0
- package/reference/codegen/type-definition.md +340 -0
- package/reference/codegen/type-ref.md +201 -0
- package/reference/combinators.md +347 -117
- package/reference/config/config-decoder.md +460 -0
- package/reference/config/config-source.md +489 -0
- package/reference/config/errors.md +278 -0
- package/reference/config/flags.md +369 -0
- package/reference/config/formats.md +314 -0
- package/reference/config/index.md +304 -0
- package/reference/config/rollout.md +336 -0
- package/reference/context.md +9 -52
- package/reference/data-migration.md +269 -0
- package/reference/datastar/attributes.md +302 -0
- package/reference/datastar/events.md +234 -0
- package/reference/datastar/index.md +256 -0
- package/reference/datastar/signals.md +230 -0
- package/reference/datastar/sse.md +295 -0
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/bulk-creation.md +96 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +745 -0
- package/reference/endpoint/path-codec.md +225 -0
- package/reference/endpoint/route-pattern.md +194 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +199 -0
- package/reference/html.md +1424 -0
- package/reference/htmx/attribute-values.md +359 -0
- package/reference/htmx/hx-encoding.md +111 -0
- package/reference/htmx/hx-params.md +204 -0
- package/reference/htmx/hx-swap.md +276 -0
- package/reference/htmx/hx-sync.md +251 -0
- package/reference/htmx/hx-target.md +314 -0
- package/reference/htmx/hx-trigger.md +457 -0
- package/reference/htmx/hx-url-update.md +239 -0
- package/reference/htmx/index.md +807 -0
- package/reference/htmx/response-headers.md +240 -0
- package/reference/http-model/headers.md +735 -0
- package/reference/http-model/index.md +49 -0
- package/reference/http-model/model.md +1517 -0
- package/reference/http-model/schema-codecs.md +522 -0
- package/reference/http-model/schema.md +750 -0
- package/reference/http-model/server-sent-event.md +341 -0
- package/reference/jwt.md +195 -0
- package/reference/maybe.md +943 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.md +254 -0
- package/reference/mux.mdx +828 -0
- package/reference/openapi.md +1351 -0
- package/reference/projection.md +654 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -98
- package/reference/resource-management/scope.md +28 -220
- package/reference/resource-management/wire.md +5 -55
- package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
- package/reference/ringbuffer/MpscDiagram.jsx +618 -0
- package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
- package/reference/ringbuffer/SpscDiagram.jsx +677 -0
- package/reference/ringbuffer/advanced.mdx +109 -0
- package/reference/ringbuffer/index.mdx +145 -0
- package/reference/ringbuffer/mpmc.mdx +185 -0
- package/reference/ringbuffer/mpsc.mdx +164 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +416 -0
- package/reference/{allows.md → schema/allows.md} +4 -100
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +3 -4
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +510 -0
- package/reference/schema/built-in-codecs/csv.md +564 -0
- package/reference/schema/built-in-codecs/index.md +77 -0
- package/reference/schema/built-in-codecs/json/index.md +295 -0
- package/reference/schema/built-in-codecs/json/json-config.md +217 -0
- package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
- package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
- package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
- package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
- package/reference/schema/built-in-codecs/messagepack.md +508 -0
- package/reference/schema/built-in-codecs/thrift.md +433 -0
- package/reference/schema/built-in-codecs/toon.md +1078 -0
- package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
- package/reference/schema/built-in-codecs/yaml.md +552 -0
- package/reference/{codec.md → schema/codec.md} +11 -11
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +196 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +52 -0
- package/reference/schema/migration.md +297 -0
- package/reference/{modifier.md → schema/modifier.md} +58 -7
- package/reference/{optics.md → schema/optics.md} +2 -2
- package/reference/{patch.md → schema/patch.md} +1 -1
- package/{path-interpolator.md → reference/schema/path-interpolator.md} +167 -72
- package/reference/schema/reflect-transformer.md +140 -0
- package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/schema/schema-search.md +263 -0
- package/reference/{schema.md → schema/schema.md} +22 -2
- package/reference/{structural-types.md → schema/structural-types.md} +1 -1
- package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
- package/reference/smithy.md +1032 -0
- package/reference/sql/db-codec-deriver.md +71 -0
- package/reference/sql/db-codec.md +687 -0
- package/reference/sql/db-con.md +271 -0
- package/reference/sql/db-connection.md +153 -0
- package/reference/sql/db-param-writer.md +77 -0
- package/reference/sql/db-param.md +66 -0
- package/reference/sql/db-result-reader.md +148 -0
- package/reference/sql/db-tx.md +114 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +288 -0
- package/reference/sql/index.md +341 -0
- package/reference/sql/repo.md +600 -0
- package/reference/sql/sql-dialect.md +73 -0
- package/reference/sql/sql-logger.md +62 -0
- package/reference/sql/sql-name-mapper.md +70 -0
- package/reference/sql/table-metadata.md +134 -0
- package/reference/sql/table.md +448 -0
- package/reference/sql/transactor-zio.md +399 -0
- package/reference/sql/transactor.md +363 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/core/index.md +32 -0
- package/reference/streams/core/pipeline.md +854 -0
- package/reference/streams/core/sink.md +1404 -0
- package/reference/streams/core/stream.md +3236 -0
- package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
- package/reference/streams/execution-and-compatibility/index.md +35 -0
- package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
- package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
- package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
- package/reference/streams/index.md +726 -0
- package/reference/streams/primitives/index.md +30 -0
- package/reference/streams/primitives/reader.md +1992 -0
- package/reference/streams/primitives/writer.md +1201 -0
- package/reference/telemetry/common/any-value.md +90 -0
- package/reference/telemetry/common/attribute-key.md +87 -0
- package/reference/telemetry/common/attributes.md +118 -0
- package/reference/telemetry/common/index.md +39 -0
- package/reference/telemetry/common/instrumentation-scope.md +24 -0
- package/reference/telemetry/common/resource.md +34 -0
- package/reference/telemetry/index.md +311 -0
- package/reference/telemetry/logging/index.md +197 -0
- package/reference/telemetry/logging/log-enrichment.md +72 -0
- package/reference/telemetry/logging/log-formatter.md +100 -0
- package/reference/telemetry/logging/log-record-processor.md +56 -0
- package/reference/telemetry/logging/log-record.md +44 -0
- package/reference/telemetry/logging/log-writer.md +64 -0
- package/reference/telemetry/logging/logger-provider.md +142 -0
- package/reference/telemetry/logging/logger.md +83 -0
- package/reference/telemetry/logging/severity.md +62 -0
- package/reference/telemetry/metrics/index.md +150 -0
- package/reference/telemetry/metrics/instruments.md +183 -0
- package/reference/telemetry/metrics/labeled-instruments.md +74 -0
- package/reference/telemetry/metrics/meter-provider.md +76 -0
- package/reference/telemetry/metrics/meter.md +98 -0
- package/reference/telemetry/metrics/metric-data.md +57 -0
- package/reference/telemetry/otel/custom-exporter.md +216 -0
- package/reference/telemetry/otel/index.md +212 -0
- package/reference/telemetry/tracing/index.md +155 -0
- package/reference/telemetry/tracing/sampler.md +89 -0
- package/reference/telemetry/tracing/span-builder.md +57 -0
- package/reference/telemetry/tracing/span-context.md +39 -0
- package/reference/telemetry/tracing/span-data.md +32 -0
- package/reference/telemetry/tracing/span-kind.md +55 -0
- package/reference/telemetry/tracing/span-processor.md +53 -0
- package/reference/telemetry/tracing/span-status.md +47 -0
- package/reference/telemetry/tracing/span.md +117 -0
- package/reference/telemetry/tracing/tracer-provider.md +91 -0
- package/reference/telemetry/tracing/tracer.md +52 -0
- package/reference/typeid.md +5 -83
- package/sidebars.js +376 -43
- package/undocumented-report.md +528 -270
- package/reference/formats.md +0 -694
- package/reference/http-model.md +0 -1716
- package/reference/streams.md +0 -989
- package/ringbuffer.md +0 -249
- /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
- /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
- /package/reference/{lazy.md → schema/lazy.md} +0 -0
- /package/reference/{reflect.md → schema/reflect.md} +0 -0
- /package/reference/{registers.md → schema/registers.md} +0 -0
- /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
- /package/reference/{syntax.md → schema/syntax.md} +0 -0
- /package/reference/{validation.md → schema/validation.md} +0 -0
|
@@ -0,0 +1,1032 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: smithy
|
|
3
|
+
title: "Smithy"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`zio-blocks-smithy` is a **Smithy IDL parser and AST library** providing a complete representation of Smithy 2.0 API models. It enables parsing Smithy IDL text into rich data structures, querying shape definitions, and pretty-printing models back to valid IDL syntax—all without external dependencies.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Add the library to your build configuration:
|
|
11
|
+
|
|
12
|
+
```scala
|
|
13
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-smithy" % "0.0.55"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Supported Scala versions: 2.13.x and 3.x
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
Parse Smithy IDL text into a model, query shapes, and serialize back:
|
|
21
|
+
|
|
22
|
+
```scala
|
|
23
|
+
import zio.blocks.smithy._
|
|
24
|
+
|
|
25
|
+
val smithyText = """$version: "2"
|
|
26
|
+
namespace com.example.api
|
|
27
|
+
|
|
28
|
+
structure User {
|
|
29
|
+
@required
|
|
30
|
+
id: String
|
|
31
|
+
name: String
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
operation GetUser {
|
|
35
|
+
input: GetUserInput
|
|
36
|
+
output: User
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
structure GetUserInput {
|
|
40
|
+
@required
|
|
41
|
+
id: String
|
|
42
|
+
}
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
// Parse IDL text into a model
|
|
46
|
+
val result = SmithyModel.parse(smithyText)
|
|
47
|
+
|
|
48
|
+
// Access shapes and data
|
|
49
|
+
result match {
|
|
50
|
+
case Right(model) =>
|
|
51
|
+
model.findShape("User").foreach { userDef =>
|
|
52
|
+
println(s"Found shape: ${userDef.name}")
|
|
53
|
+
}
|
|
54
|
+
case Left(error) =>
|
|
55
|
+
println(s"Parse error: ${error.message}")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Serialize back to IDL
|
|
59
|
+
result.foreach { model =>
|
|
60
|
+
val idlText = model.prettyPrint
|
|
61
|
+
println(idlText)
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Core Types
|
|
66
|
+
|
|
67
|
+
The library provides core types that work together to parse, query, and serialize Smithy models. The main types work together in a parsing → querying → serialization pipeline:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
Smithy IDL Text
|
|
71
|
+
↓
|
|
72
|
+
SmithyModel.parse (public API)
|
|
73
|
+
↓
|
|
74
|
+
SmithyModel (contains shapes, metadata, traits)
|
|
75
|
+
├─ shapes: List[ShapeDefinition]
|
|
76
|
+
│ └─ shape: Shape (sealed trait — central type)
|
|
77
|
+
│ ├─ StructureShape(members: List[MemberDefinition])
|
|
78
|
+
│ ├─ ListShape(member: MemberDefinition)
|
|
79
|
+
│ ├─ MapShape(key: MemberDefinition, value: MemberDefinition)
|
|
80
|
+
│ ├─ ServiceShape(operations, resources, errors)
|
|
81
|
+
│ ├─ OperationShape(input, output, errors)
|
|
82
|
+
│ ├─ UnionShape(members: List[MemberDefinition])
|
|
83
|
+
│ ├─ EnumShape(members: List[EnumMember])
|
|
84
|
+
│ ├─ ResourceShape(identifiers, create, read, update, delete, list, ...)
|
|
85
|
+
│ ├─ StringShape, BooleanShape, IntegerShape, and 10 more simple shapes
|
|
86
|
+
│ └─ ... (20 subtypes in total — see the Shape Catalog below)
|
|
87
|
+
├─ MemberDefinition(name: String, target: ShapeId, traits: List[TraitApplication])
|
|
88
|
+
├─ TraitApplication(id: ShapeId, value: Option[NodeValue])
|
|
89
|
+
├─ ShapeId (namespace + name identifier)
|
|
90
|
+
└─ NodeValue (metadata values: String, Number, Boolean, Array, Object, Null)
|
|
91
|
+
↓
|
|
92
|
+
SmithyModel.prettyPrint (public API)
|
|
93
|
+
↓
|
|
94
|
+
Smithy IDL Text
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The root container for a Smithy model. Contains version, namespace, shapes, metadata, and trait applications. The case class and companion object expose the following API:
|
|
98
|
+
|
|
99
|
+
```scala
|
|
100
|
+
case class SmithyModel(
|
|
101
|
+
version: String, // Smithy version (e.g., "2")
|
|
102
|
+
namespace: String,
|
|
103
|
+
useStatements: List[ShapeId],
|
|
104
|
+
metadata: Map[String, NodeValue],
|
|
105
|
+
shapes: List[ShapeDefinition],
|
|
106
|
+
applyStatements: List[ApplyStatement] = Nil
|
|
107
|
+
) {
|
|
108
|
+
def findShape(name: String): Option[ShapeDefinition]
|
|
109
|
+
def allShapeIds: List[ShapeId]
|
|
110
|
+
def prettyPrint: String
|
|
111
|
+
def prettyPrint(indent: Int): String
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
object SmithyModel {
|
|
115
|
+
def parse(input: String): Either[SmithyError, SmithyModel]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Shape Catalog
|
|
120
|
+
|
|
121
|
+
`Shape` is a sealed trait with 20 subtypes, covering the shape categories the Smithy specification defines. Every shape carries a `Shape#name` and a list of applied `Shape#traits`; the families differ in what else they hold:
|
|
122
|
+
|
|
123
|
+
```scala
|
|
124
|
+
sealed trait Shape {
|
|
125
|
+
def name: String
|
|
126
|
+
def traits: List[TraitApplication]
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
| Family | Subtypes | What they add |
|
|
131
|
+
| --------- | -------: | ---------------------------------------- |
|
|
132
|
+
| Simple | 13 | Nothing — name and traits only |
|
|
133
|
+
| Enum | 2 | A member list of allowed values |
|
|
134
|
+
| Aggregate | 4 | Member definitions naming target shapes |
|
|
135
|
+
| Service | 3 | `ShapeId` references to other shapes |
|
|
136
|
+
|
|
137
|
+
A parsed shape is wrapped in a `ShapeDefinition`, which pairs the name with the shape. `Shape` also carries its own `Shape#name`, so the two agree and either can be read:
|
|
138
|
+
|
|
139
|
+
```scala
|
|
140
|
+
final case class ShapeDefinition(name: String, shape: Shape)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Simple Shapes
|
|
144
|
+
|
|
145
|
+
Thirteen shapes carry no structure beyond their name and traits. They differ only in which IDL keyword produces them and what the target protocol is expected to do with them:
|
|
146
|
+
|
|
147
|
+
| Type | IDL keyword | Represents |
|
|
148
|
+
| ----------------- | ------------ | --------------------------------- |
|
|
149
|
+
| `BlobShape` | `blob` | Arbitrary binary data |
|
|
150
|
+
| `BooleanShape` | `boolean` | True/false values |
|
|
151
|
+
| `StringShape` | `string` | UTF-8 text |
|
|
152
|
+
| `ByteShape` | `byte` | 8-bit signed integer |
|
|
153
|
+
| `ShortShape` | `short` | 16-bit signed integer |
|
|
154
|
+
| `IntegerShape` | `integer` | 32-bit signed integer |
|
|
155
|
+
| `LongShape` | `long` | 64-bit signed integer |
|
|
156
|
+
| `FloatShape` | `float` | Single-precision IEEE 754 |
|
|
157
|
+
| `DoubleShape` | `double` | Double-precision IEEE 754 |
|
|
158
|
+
| `BigIntegerShape` | `bigInteger` | Arbitrarily large signed integer |
|
|
159
|
+
| `BigDecimalShape` | `bigDecimal` | Arbitrary-precision decimal |
|
|
160
|
+
| `TimestampShape` | `timestamp` | A point in time |
|
|
161
|
+
| `DocumentShape` | `document` | Protocol-agnostic open content |
|
|
162
|
+
|
|
163
|
+
Because they share one shape, parsing them produces values that differ only in their type:
|
|
164
|
+
|
|
165
|
+
```scala
|
|
166
|
+
import zio.blocks.smithy._
|
|
167
|
+
|
|
168
|
+
val simpleModel = SmithyModel.parse(
|
|
169
|
+
"""$version: "2"
|
|
170
|
+
|namespace com.example
|
|
171
|
+
|blob Payload
|
|
172
|
+
|timestamp CreatedAt
|
|
173
|
+
|document Metadata
|
|
174
|
+
|""".stripMargin
|
|
175
|
+
).toOption.get
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Each definition names the shape and holds the corresponding subtype:
|
|
179
|
+
|
|
180
|
+
```scala
|
|
181
|
+
simpleModel.shapes
|
|
182
|
+
// res1: List[ShapeDefinition] = List(
|
|
183
|
+
// ShapeDefinition(
|
|
184
|
+
// name = "Payload",
|
|
185
|
+
// shape = BlobShape(name = "Payload", traits = List())
|
|
186
|
+
// ),
|
|
187
|
+
// ShapeDefinition(
|
|
188
|
+
// name = "CreatedAt",
|
|
189
|
+
// shape = TimestampShape(name = "CreatedAt", traits = List())
|
|
190
|
+
// ),
|
|
191
|
+
// ShapeDefinition(
|
|
192
|
+
// name = "Metadata",
|
|
193
|
+
// shape = DocumentShape(name = "Metadata", traits = List())
|
|
194
|
+
// )
|
|
195
|
+
// )
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`DocumentShape` is the one to reach for when a field's contents are not known at model time — it is the Smithy equivalent of an open JSON value, and no member list constrains it.
|
|
199
|
+
|
|
200
|
+
### Enum Shapes
|
|
201
|
+
|
|
202
|
+
`EnumShape` and `IntEnumShape` each hold a fixed set of permitted values. They differ in the value type and in whether the value is optional:
|
|
203
|
+
|
|
204
|
+
```scala
|
|
205
|
+
final case class EnumShape(
|
|
206
|
+
name: String,
|
|
207
|
+
traits: List[TraitApplication] = Nil,
|
|
208
|
+
members: List[EnumMember] = Nil
|
|
209
|
+
) extends Shape
|
|
210
|
+
|
|
211
|
+
final case class IntEnumShape(
|
|
212
|
+
name: String,
|
|
213
|
+
traits: List[TraitApplication] = Nil,
|
|
214
|
+
members: List[IntEnumMember] = Nil
|
|
215
|
+
) extends Shape
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`EnumMember` carries an `Option[String]`, because the IDL allows a bare member name; `IntEnumMember` carries a required `Int`, because an integer enum has no name to fall back on:
|
|
219
|
+
|
|
220
|
+
```scala
|
|
221
|
+
final case class EnumMember(name: String, value: Option[String] = None, traits: List[TraitApplication] = Nil)
|
|
222
|
+
final case class IntEnumMember(name: String, value: Int, traits: List[TraitApplication] = Nil)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
A string enum with explicit values fills in each `value`:
|
|
226
|
+
|
|
227
|
+
```scala
|
|
228
|
+
val colorModel = SmithyModel.parse(
|
|
229
|
+
"""$version: "2"
|
|
230
|
+
|namespace com.example
|
|
231
|
+
|enum Color {
|
|
232
|
+
| RED = "red"
|
|
233
|
+
| GREEN = "green"
|
|
234
|
+
|}
|
|
235
|
+
|""".stripMargin
|
|
236
|
+
).toOption.get
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Each member pairs the declared name with the string it maps to:
|
|
240
|
+
|
|
241
|
+
```scala
|
|
242
|
+
colorModel.shapes
|
|
243
|
+
// res2: List[ShapeDefinition] = List(
|
|
244
|
+
// ShapeDefinition(
|
|
245
|
+
// name = "Color",
|
|
246
|
+
// shape = EnumShape(
|
|
247
|
+
// name = "Color",
|
|
248
|
+
// traits = List(),
|
|
249
|
+
// members = List(
|
|
250
|
+
// EnumMember(name = "RED", value = Some("red"), traits = List()),
|
|
251
|
+
// EnumMember(name = "GREEN", value = Some("green"), traits = List())
|
|
252
|
+
// )
|
|
253
|
+
// )
|
|
254
|
+
// )
|
|
255
|
+
// )
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Omitting the values leaves `value` absent rather than duplicating the name, so a consumer wanting the effective wire value reads `EnumMember#value` and falls back to `EnumMember#name`:
|
|
259
|
+
|
|
260
|
+
```scala
|
|
261
|
+
val suitModel = SmithyModel.parse(
|
|
262
|
+
"""$version: "2"
|
|
263
|
+
|namespace com.example
|
|
264
|
+
|enum Suit {
|
|
265
|
+
| CLUB
|
|
266
|
+
| HEART
|
|
267
|
+
|}
|
|
268
|
+
|""".stripMargin
|
|
269
|
+
).toOption.get
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The distinction survives parsing, which is what lets a round trip reproduce the original document:
|
|
273
|
+
|
|
274
|
+
```scala
|
|
275
|
+
suitModel.shapes
|
|
276
|
+
// res3: List[ShapeDefinition] = List(
|
|
277
|
+
// ShapeDefinition(
|
|
278
|
+
// name = "Suit",
|
|
279
|
+
// shape = EnumShape(
|
|
280
|
+
// name = "Suit",
|
|
281
|
+
// traits = List(),
|
|
282
|
+
// members = List(
|
|
283
|
+
// EnumMember(name = "CLUB", value = None, traits = List()),
|
|
284
|
+
// EnumMember(name = "HEART", value = None, traits = List())
|
|
285
|
+
// )
|
|
286
|
+
// )
|
|
287
|
+
// )
|
|
288
|
+
// )
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
An integer enum requires a value for every member, so `IntEnumMember` holds an `Int` rather than an option:
|
|
292
|
+
|
|
293
|
+
```scala
|
|
294
|
+
val cardModel = SmithyModel.parse(
|
|
295
|
+
"""$version: "2"
|
|
296
|
+
|namespace com.example
|
|
297
|
+
|intEnum FaceCard {
|
|
298
|
+
| JACK = 11
|
|
299
|
+
| QUEEN = 12
|
|
300
|
+
|}
|
|
301
|
+
|""".stripMargin
|
|
302
|
+
).toOption.get
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Reading the members gives the integers directly:
|
|
306
|
+
|
|
307
|
+
```scala
|
|
308
|
+
cardModel.shapes
|
|
309
|
+
// res4: List[ShapeDefinition] = List(
|
|
310
|
+
// ShapeDefinition(
|
|
311
|
+
// name = "FaceCard",
|
|
312
|
+
// shape = IntEnumShape(
|
|
313
|
+
// name = "FaceCard",
|
|
314
|
+
// traits = List(),
|
|
315
|
+
// members = List(
|
|
316
|
+
// IntEnumMember(name = "JACK", value = 11, traits = List()),
|
|
317
|
+
// IntEnumMember(name = "QUEEN", value = 12, traits = List())
|
|
318
|
+
// )
|
|
319
|
+
// )
|
|
320
|
+
// )
|
|
321
|
+
// )
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Aggregate Shapes
|
|
325
|
+
|
|
326
|
+
Four shapes compose other shapes, and all of them do it through `MemberDefinition` — a name, the `ShapeId` of the target, and any traits on the member itself:
|
|
327
|
+
|
|
328
|
+
| Type | IDL keyword | Members |
|
|
329
|
+
| ---------------- | ----------- | ---------------------------------------------------- |
|
|
330
|
+
| `ListShape` | `list` | `member: MemberDefinition` |
|
|
331
|
+
| `MapShape` | `map` | `key` and `value`, both `MemberDefinition` |
|
|
332
|
+
| `StructureShape` | `structure` | `members: List[MemberDefinition]` |
|
|
333
|
+
| `UnionShape` | `union` | `members: List[MemberDefinition]`, one set at a time |
|
|
334
|
+
|
|
335
|
+
A structure's members name their targets by `ShapeId`, not by nested shape, so the model stays flat and a member's target is resolved by lookup:
|
|
336
|
+
|
|
337
|
+
```scala
|
|
338
|
+
val userModel = SmithyModel.parse(
|
|
339
|
+
"""$version: "2"
|
|
340
|
+
|namespace com.example
|
|
341
|
+
|structure User {
|
|
342
|
+
| @required
|
|
343
|
+
| id: String
|
|
344
|
+
| tags: TagList
|
|
345
|
+
|}
|
|
346
|
+
|list TagList {
|
|
347
|
+
| member: String
|
|
348
|
+
|}
|
|
349
|
+
|""".stripMargin
|
|
350
|
+
).toOption.get
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Both shapes appear at the top level, and the `tags` member of `User` points at `TagList` by reference:
|
|
354
|
+
|
|
355
|
+
```scala
|
|
356
|
+
userModel.shapes
|
|
357
|
+
// res5: List[ShapeDefinition] = List(
|
|
358
|
+
// ShapeDefinition(
|
|
359
|
+
// name = "User",
|
|
360
|
+
// shape = StructureShape(
|
|
361
|
+
// name = "User",
|
|
362
|
+
// traits = List(),
|
|
363
|
+
// members = List(
|
|
364
|
+
// MemberDefinition(
|
|
365
|
+
// name = "id",
|
|
366
|
+
// target = ShapeId(namespace = "", name = "String"),
|
|
367
|
+
// traits = List(
|
|
368
|
+
// TraitApplication(
|
|
369
|
+
// id = ShapeId(namespace = "smithy.api", name = "required"),
|
|
370
|
+
// value = None
|
|
371
|
+
// )
|
|
372
|
+
// )
|
|
373
|
+
// ),
|
|
374
|
+
// MemberDefinition(
|
|
375
|
+
// name = "tags",
|
|
376
|
+
// target = ShapeId(namespace = "", name = "TagList"),
|
|
377
|
+
// traits = List()
|
|
378
|
+
// )
|
|
379
|
+
// )
|
|
380
|
+
// )
|
|
381
|
+
// ),
|
|
382
|
+
// ShapeDefinition(
|
|
383
|
+
// name = "TagList",
|
|
384
|
+
// shape = ListShape(
|
|
385
|
+
// name = "TagList",
|
|
386
|
+
// traits = List(),
|
|
387
|
+
// member = MemberDefinition(
|
|
388
|
+
// name = "member",
|
|
389
|
+
// target = ShapeId(namespace = "", name = "String"),
|
|
390
|
+
// traits = List()
|
|
391
|
+
// )
|
|
392
|
+
// )
|
|
393
|
+
// )
|
|
394
|
+
// )
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
:::note[`member` has no default, `traits` does]
|
|
398
|
+
`ListShape` and `MapShape` declare their `Shape#traits` parameter with a default *before* the parameters that have none, so positional construction does not work — `ListShape("TagList", member = m)` compiles while `ListShape("TagList", m)` does not. `StructureShape` and `UnionShape` default their member lists, so both forms work there.
|
|
399
|
+
:::
|
|
400
|
+
|
|
401
|
+
### Service Shapes
|
|
402
|
+
|
|
403
|
+
`ServiceShape`, `OperationShape`, and `ResourceShape` describe an API rather than a value, and all of their cross-references are `ShapeId`s:
|
|
404
|
+
|
|
405
|
+
```scala
|
|
406
|
+
final case class ServiceShape(
|
|
407
|
+
name: String,
|
|
408
|
+
traits: List[TraitApplication] = Nil,
|
|
409
|
+
version: Option[String] = None,
|
|
410
|
+
operations: List[ShapeId] = Nil,
|
|
411
|
+
resources: List[ShapeId] = Nil,
|
|
412
|
+
errors: List[ShapeId] = Nil
|
|
413
|
+
) extends Shape
|
|
414
|
+
|
|
415
|
+
final case class OperationShape(
|
|
416
|
+
name: String,
|
|
417
|
+
traits: List[TraitApplication] = Nil,
|
|
418
|
+
input: Option[ShapeId] = None,
|
|
419
|
+
output: Option[ShapeId] = None,
|
|
420
|
+
errors: List[ShapeId] = Nil
|
|
421
|
+
) extends Shape
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
`ResourceShape` is the largest shape in the module, because a Smithy resource binds identifiers, five named lifecycle operations, and three further reference lists:
|
|
425
|
+
|
|
426
|
+
| Field | Type | Meaning |
|
|
427
|
+
| ---------------------- | ---------------------- | ------------------------------------------------- |
|
|
428
|
+
| `identifiers` | `Map[String, ShapeId]` | Identifier names mapped to their target shapes |
|
|
429
|
+
| `create` | `Option[ShapeId]` | Create lifecycle operation |
|
|
430
|
+
| `read` | `Option[ShapeId]` | Read lifecycle operation |
|
|
431
|
+
| `update` | `Option[ShapeId]` | Update lifecycle operation |
|
|
432
|
+
| `delete` | `Option[ShapeId]` | Delete lifecycle operation |
|
|
433
|
+
| `list` | `Option[ShapeId]` | List lifecycle operation |
|
|
434
|
+
| `operations` | `List[ShapeId]` | Instance operations that are not lifecycle ones |
|
|
435
|
+
| `collectionOperations` | `List[ShapeId]` | Operations on the collection rather than one item |
|
|
436
|
+
| `resources` | `List[ShapeId]` | Child resources |
|
|
437
|
+
|
|
438
|
+
The five lifecycle fields are separate rather than a map, which is what makes "does this resource support deletion?" a field access instead of a lookup:
|
|
439
|
+
|
|
440
|
+
```scala
|
|
441
|
+
val resourceModel = SmithyModel.parse(
|
|
442
|
+
"""$version: "2"
|
|
443
|
+
|namespace com.example
|
|
444
|
+
|resource FooResource {
|
|
445
|
+
| identifiers: {id: FooId}
|
|
446
|
+
| read: GetFoo
|
|
447
|
+
| list: ListFoos
|
|
448
|
+
|}
|
|
449
|
+
|""".stripMargin
|
|
450
|
+
).toOption.get
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Unset lifecycle operations stay `None`, so an absent `create` is distinguishable from one bound to an operation:
|
|
454
|
+
|
|
455
|
+
```scala
|
|
456
|
+
resourceModel.shapes
|
|
457
|
+
// res6: List[ShapeDefinition] = List(
|
|
458
|
+
// ShapeDefinition(
|
|
459
|
+
// name = "FooResource",
|
|
460
|
+
// shape = ResourceShape(
|
|
461
|
+
// name = "FooResource",
|
|
462
|
+
// traits = List(),
|
|
463
|
+
// identifiers = Map("id" -> ShapeId(namespace = "", name = "FooId")),
|
|
464
|
+
// create = None,
|
|
465
|
+
// read = Some(ShapeId(namespace = "", name = "GetFoo")),
|
|
466
|
+
// update = None,
|
|
467
|
+
// delete = None,
|
|
468
|
+
// list = Some(ShapeId(namespace = "", name = "ListFoos")),
|
|
469
|
+
// operations = List(),
|
|
470
|
+
// collectionOperations = List(),
|
|
471
|
+
// resources = List()
|
|
472
|
+
// )
|
|
473
|
+
// )
|
|
474
|
+
// )
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Shape References
|
|
478
|
+
|
|
479
|
+
Every cross-shape reference is a `ShapeRef`, a sealed trait with exactly two cases:
|
|
480
|
+
|
|
481
|
+
```scala
|
|
482
|
+
sealed trait ShapeRef
|
|
483
|
+
|
|
484
|
+
final case class ShapeId(namespace: String, name: String) extends ShapeRef
|
|
485
|
+
|
|
486
|
+
object ShapeId {
|
|
487
|
+
final case class Member(shape: ShapeId, memberName: String) extends ShapeRef
|
|
488
|
+
def parse(s: String): Either[String, ShapeRef]
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
`ShapeRef` exists to give `ShapeId` and `ShapeId.Member` a common supertype without a Scala 3 union type, so the same signatures compile on 2.13.
|
|
493
|
+
|
|
494
|
+
Rendering follows the IDL: a shape is `namespace#name`, and a member appends `$` and the member name:
|
|
495
|
+
|
|
496
|
+
```scala
|
|
497
|
+
ShapeId("com.example", "User").toString
|
|
498
|
+
// res7: String = "com.example#User"
|
|
499
|
+
ShapeId.Member(ShapeId("com.example", "User"), "id").toString
|
|
500
|
+
// res8: String = "com.example#User$id"
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
`ShapeId.parse` reads either form back, choosing the case by whether a `$` is present:
|
|
504
|
+
|
|
505
|
+
```scala
|
|
506
|
+
ShapeId.parse("com.example#User")
|
|
507
|
+
// res9: Either[String, ShapeRef] = Right(
|
|
508
|
+
// ShapeId(namespace = "com.example", name = "User")
|
|
509
|
+
// )
|
|
510
|
+
ShapeId.parse("com.example#User$id")
|
|
511
|
+
// res10: Either[String, ShapeRef] = Right(
|
|
512
|
+
// Member(
|
|
513
|
+
// shape = ShapeId(namespace = "com.example", name = "User"),
|
|
514
|
+
// memberName = "id"
|
|
515
|
+
// )
|
|
516
|
+
// )
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Malformed input is reported rather than thrown, and the message names the rule that failed:
|
|
520
|
+
|
|
521
|
+
```scala
|
|
522
|
+
ShapeId.parse("User")
|
|
523
|
+
// res11: Either[String, ShapeRef] = Left(
|
|
524
|
+
// "ShapeId must contain '#' separator, got: User"
|
|
525
|
+
// )
|
|
526
|
+
ShapeId.parse("com.example#User$id$extra")
|
|
527
|
+
// res12: Either[String, ShapeRef] = Left(
|
|
528
|
+
// "Member reference must have exactly one separator, got: com.example#User$id$extra"
|
|
529
|
+
// )
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### Resolving a Reference
|
|
533
|
+
|
|
534
|
+
A reference names a shape; it does not contain one. Resolving means looking the target up in the model, which `SmithyModel#findShape` does by **name** rather than by `ShapeId`:
|
|
535
|
+
|
|
536
|
+
```scala
|
|
537
|
+
final case class SmithyModel(...) {
|
|
538
|
+
def findShape(name: String): Option[ShapeDefinition]
|
|
539
|
+
def allShapeIds: List[ShapeId]
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Following a structure member to its target definition is therefore a lookup on the name inside the `ShapeId`:
|
|
544
|
+
|
|
545
|
+
```scala
|
|
546
|
+
val tagsTarget = userModel.shapes
|
|
547
|
+
.collectFirst { case ShapeDefinition("User", s: StructureShape) => s }
|
|
548
|
+
.flatMap(_.members.find(_.name == "tags"))
|
|
549
|
+
.map(_.target)
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
The member's target resolves to the `list` shape declared alongside it:
|
|
553
|
+
|
|
554
|
+
```scala
|
|
555
|
+
tagsTarget
|
|
556
|
+
// res13: Option[ShapeId] = Some(ShapeId(namespace = "", name = "TagList"))
|
|
557
|
+
tagsTarget.flatMap(id => userModel.findShape(id.name))
|
|
558
|
+
// res14: Option[ShapeDefinition] = Some(
|
|
559
|
+
// ShapeDefinition(
|
|
560
|
+
// name = "TagList",
|
|
561
|
+
// shape = ListShape(
|
|
562
|
+
// name = "TagList",
|
|
563
|
+
// traits = List(),
|
|
564
|
+
// member = MemberDefinition(
|
|
565
|
+
// name = "member",
|
|
566
|
+
// target = ShapeId(namespace = "", name = "String"),
|
|
567
|
+
// traits = List()
|
|
568
|
+
// )
|
|
569
|
+
// )
|
|
570
|
+
// )
|
|
571
|
+
// )
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
Looking up by name rather than by `ShapeId` is not a shortcut — it matches what the parser produces. An IDL target written without a namespace prefix becomes a `ShapeId` whose namespace is the **empty string**, not the model's namespace and not `smithy.api`:
|
|
575
|
+
|
|
576
|
+
```scala
|
|
577
|
+
userModel.shapes.collect { case ShapeDefinition(_, s: ListShape) => s.member.target }
|
|
578
|
+
// res15: List[ShapeId] = List(ShapeId(namespace = "", name = "String"))
|
|
579
|
+
resourceModel.shapes.collect { case ShapeDefinition(_, s: ResourceShape) => s.identifiers }
|
|
580
|
+
// res16: List[Map[String, ShapeId]] = List(
|
|
581
|
+
// Map("id" -> ShapeId(namespace = "", name = "FooId"))
|
|
582
|
+
// )
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
Trait identifiers are the exception: the parser resolves those against the prelude, so `@required` arrives fully qualified:
|
|
586
|
+
|
|
587
|
+
```scala
|
|
588
|
+
userModel.shapes
|
|
589
|
+
.collect { case ShapeDefinition("User", s: StructureShape) => s }
|
|
590
|
+
.flatMap(_.members.flatMap(_.traits.map(_.id)))
|
|
591
|
+
// res17: List[ShapeId] = List(
|
|
592
|
+
// ShapeId(namespace = "smithy.api", name = "required")
|
|
593
|
+
// )
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
Two consequences follow. A reference to a prelude shape such as `String` has no definition in the parsed model, so resolving it yields nothing — a consumer generating code has to recognize prelude names itself:
|
|
597
|
+
|
|
598
|
+
```scala
|
|
599
|
+
userModel.findShape("String")
|
|
600
|
+
// res18: Option[ShapeDefinition] = None
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
And a `ShapeId` taken from a parsed model does not necessarily round-trip through `ShapeId.parse`, because the renderer emits the empty namespace as a bare `#` that the parser then rejects:
|
|
604
|
+
|
|
605
|
+
```scala
|
|
606
|
+
ShapeId("", "String").toString
|
|
607
|
+
// res19: String = "#String"
|
|
608
|
+
ShapeId.parse(ShapeId("", "String").toString)
|
|
609
|
+
// res20: Either[String, ShapeRef] = Left("ShapeId namespace cannot be empty")
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
:::warning[Namespaces on references are not populated]
|
|
613
|
+
Because unprefixed targets carry an empty namespace, comparing `ShapeId#namespace` on a parsed reference tells you nothing unless the IDL spelled the namespace out. `SmithyModel#findShape` matching on name alone is consistent with that, but it also means a model that uses a `use` statement to import `other.ns#User` while declaring its own `User` cannot distinguish the two by reference alone.
|
|
614
|
+
:::
|
|
615
|
+
|
|
616
|
+
## Parsing
|
|
617
|
+
|
|
618
|
+
Parse Smithy IDL text into structured models using `SmithyModel.parse`, handle errors, and validate round-trips.
|
|
619
|
+
|
|
620
|
+
### Basic Parsing
|
|
621
|
+
|
|
622
|
+
Parse Smithy IDL text and handle the result:
|
|
623
|
+
|
|
624
|
+
```scala
|
|
625
|
+
import zio.blocks.smithy._
|
|
626
|
+
|
|
627
|
+
val smithyText = """$version: "2"
|
|
628
|
+
namespace com.example
|
|
629
|
+
|
|
630
|
+
string Name
|
|
631
|
+
"""
|
|
632
|
+
|
|
633
|
+
SmithyModel.parse(smithyText) match {
|
|
634
|
+
case Right(model) =>
|
|
635
|
+
println(s"Parsed ${model.shapes.length} shapes")
|
|
636
|
+
case Left(error) =>
|
|
637
|
+
println(s"Error: ${error.message}")
|
|
638
|
+
}
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Handling Parse Errors
|
|
642
|
+
|
|
643
|
+
Access error details including line and column information when parsing fails. `SmithyError` provides detailed context to help locate and fix issues in your Smithy definitions:
|
|
644
|
+
|
|
645
|
+
```scala
|
|
646
|
+
import zio.blocks.smithy._
|
|
647
|
+
|
|
648
|
+
val invalidSmithy = """$version: "2"
|
|
649
|
+
namespace com.example
|
|
650
|
+
|
|
651
|
+
structure User {
|
|
652
|
+
invalid syntax
|
|
653
|
+
}
|
|
654
|
+
"""
|
|
655
|
+
|
|
656
|
+
SmithyModel.parse(invalidSmithy) match {
|
|
657
|
+
case Right(_) =>
|
|
658
|
+
println("Unexpected success")
|
|
659
|
+
case Left(error) =>
|
|
660
|
+
println(s"Error at line ${error.line}, column ${error.column}: ${error.message}")
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### Round-Trip Validation
|
|
665
|
+
|
|
666
|
+
Verify a model parses correctly by round-tripping (parse → serialize → parse again):
|
|
667
|
+
|
|
668
|
+
```scala
|
|
669
|
+
import zio.blocks.smithy._
|
|
670
|
+
|
|
671
|
+
val original = """$version: "2"
|
|
672
|
+
namespace com.example
|
|
673
|
+
|
|
674
|
+
string MyString
|
|
675
|
+
"""
|
|
676
|
+
|
|
677
|
+
val parsed = SmithyModel.parse(original)
|
|
678
|
+
val reprinted = parsed.map(_.prettyPrint)
|
|
679
|
+
val reparsed = reprinted.flatMap(SmithyModel.parse)
|
|
680
|
+
|
|
681
|
+
println(reparsed.isRight) // true if round-trip succeeds
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
## Querying & Traversing Shapes
|
|
685
|
+
|
|
686
|
+
Once you have a parsed model, query shapes by name, pattern match on shape types, and traverse their members.
|
|
687
|
+
|
|
688
|
+
### Finding Shapes
|
|
689
|
+
|
|
690
|
+
Locate shapes by name or retrieve all shape identifiers:
|
|
691
|
+
|
|
692
|
+
```scala
|
|
693
|
+
import zio.blocks.smithy._
|
|
694
|
+
|
|
695
|
+
val model = SmithyModel.parse("""$version: "2"
|
|
696
|
+
namespace example
|
|
697
|
+
|
|
698
|
+
structure User {
|
|
699
|
+
id: String
|
|
700
|
+
name: String
|
|
701
|
+
}
|
|
702
|
+
""").toOption.get
|
|
703
|
+
|
|
704
|
+
// Find by name
|
|
705
|
+
model.findShape("User").foreach { shapeDef =>
|
|
706
|
+
println(s"Found: ${shapeDef.name}")
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Get all shape IDs
|
|
710
|
+
val allIds = model.allShapeIds
|
|
711
|
+
println(s"Total shapes: ${allIds.length}")
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
### Pattern Matching on Shapes
|
|
715
|
+
|
|
716
|
+
Determine shape type and access type-specific properties:
|
|
717
|
+
|
|
718
|
+
```scala
|
|
719
|
+
import zio.blocks.smithy._
|
|
720
|
+
|
|
721
|
+
val model = SmithyModel.parse("""$version: "2"
|
|
722
|
+
namespace example
|
|
723
|
+
|
|
724
|
+
structure User { id: String }
|
|
725
|
+
list UserIds { member: String }
|
|
726
|
+
""").toOption.get
|
|
727
|
+
|
|
728
|
+
model.findShape("User").foreach { shapeDef =>
|
|
729
|
+
shapeDef.shape match {
|
|
730
|
+
case struct: StructureShape =>
|
|
731
|
+
println(s"Structure with ${struct.members.length} members")
|
|
732
|
+
case list: ListShape =>
|
|
733
|
+
println(s"List of ${list.member.target}")
|
|
734
|
+
case _ =>
|
|
735
|
+
println("Other shape type")
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
### Traversing Members
|
|
741
|
+
|
|
742
|
+
Iterate over structure/union members and inspect their traits:
|
|
743
|
+
|
|
744
|
+
```scala
|
|
745
|
+
import zio.blocks.smithy._
|
|
746
|
+
|
|
747
|
+
val model = SmithyModel.parse("""$version: "2"
|
|
748
|
+
namespace example
|
|
749
|
+
|
|
750
|
+
structure User {
|
|
751
|
+
@required
|
|
752
|
+
id: String
|
|
753
|
+
name: String
|
|
754
|
+
}
|
|
755
|
+
""").toOption.get
|
|
756
|
+
|
|
757
|
+
model.findShape("User").foreach { shapeDef =>
|
|
758
|
+
shapeDef.shape match {
|
|
759
|
+
case struct: StructureShape =>
|
|
760
|
+
struct.members.foreach { member =>
|
|
761
|
+
val required = member.traits.exists(_.id.name == "required")
|
|
762
|
+
println(s"${member.name}: ${member.target} (required: $required)")
|
|
763
|
+
}
|
|
764
|
+
case _ => ()
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
## Building Models Programmatically
|
|
770
|
+
|
|
771
|
+
Construct Smithy models in code by creating shapes, adding traits, and assembling them into a complete model.
|
|
772
|
+
|
|
773
|
+
### Creating Shapes
|
|
774
|
+
|
|
775
|
+
Programmatically construct shapes and assemble them into a complete model:
|
|
776
|
+
|
|
777
|
+
```scala
|
|
778
|
+
import zio.blocks.smithy._
|
|
779
|
+
|
|
780
|
+
val userStructure = StructureShape(
|
|
781
|
+
"User",
|
|
782
|
+
traits = Nil,
|
|
783
|
+
members = List(
|
|
784
|
+
MemberDefinition(
|
|
785
|
+
"id",
|
|
786
|
+
ShapeId("smithy.api", "String"),
|
|
787
|
+
traits = List(TraitApplication.required)
|
|
788
|
+
),
|
|
789
|
+
MemberDefinition(
|
|
790
|
+
"name",
|
|
791
|
+
ShapeId("smithy.api", "String"),
|
|
792
|
+
traits = Nil
|
|
793
|
+
)
|
|
794
|
+
)
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
val model = SmithyModel(
|
|
798
|
+
version = "2",
|
|
799
|
+
namespace = "com.example",
|
|
800
|
+
useStatements = Nil,
|
|
801
|
+
metadata = Map.empty,
|
|
802
|
+
shapes = List(ShapeDefinition("User", userStructure))
|
|
803
|
+
)
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### Adding Traits
|
|
807
|
+
|
|
808
|
+
Attach metadata traits to shapes during construction. `TraitApplication` provides companion object helper methods like `required`, `documentation`, and others for common traits:
|
|
809
|
+
|
|
810
|
+
```scala
|
|
811
|
+
import zio.blocks.smithy._
|
|
812
|
+
|
|
813
|
+
val serviceShape = ServiceShape(
|
|
814
|
+
"UserService",
|
|
815
|
+
traits = List(
|
|
816
|
+
TraitApplication.documentation("User management API")
|
|
817
|
+
),
|
|
818
|
+
version = Some("1.0"),
|
|
819
|
+
operations = List(
|
|
820
|
+
ShapeId("com.example", "GetUser"),
|
|
821
|
+
ShapeId("com.example", "CreateUser")
|
|
822
|
+
),
|
|
823
|
+
resources = Nil,
|
|
824
|
+
errors = Nil
|
|
825
|
+
)
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
## Serializing Models
|
|
829
|
+
|
|
830
|
+
Convert models back to valid Smithy IDL text using `prettyPrint`, with options for custom formatting.
|
|
831
|
+
|
|
832
|
+
### Basic Serialization
|
|
833
|
+
|
|
834
|
+
Convert a model to valid Smithy IDL text:
|
|
835
|
+
|
|
836
|
+
```scala
|
|
837
|
+
import zio.blocks.smithy._
|
|
838
|
+
|
|
839
|
+
val model = SmithyModel(
|
|
840
|
+
version = "2",
|
|
841
|
+
namespace = "com.example",
|
|
842
|
+
useStatements = Nil,
|
|
843
|
+
metadata = Map.empty,
|
|
844
|
+
shapes = List(
|
|
845
|
+
ShapeDefinition("Name", StringShape("Name"))
|
|
846
|
+
)
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
val idlText = model.prettyPrint
|
|
850
|
+
println(idlText)
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
### Custom Indentation
|
|
854
|
+
|
|
855
|
+
Control indentation width when serializing models:
|
|
856
|
+
|
|
857
|
+
```scala
|
|
858
|
+
import zio.blocks.smithy._
|
|
859
|
+
|
|
860
|
+
val model = SmithyModel(
|
|
861
|
+
version = "2",
|
|
862
|
+
namespace = "com.example",
|
|
863
|
+
useStatements = Nil,
|
|
864
|
+
metadata = Map.empty,
|
|
865
|
+
shapes = List(
|
|
866
|
+
ShapeDefinition("Data", StructureShape(
|
|
867
|
+
"Data",
|
|
868
|
+
traits = Nil,
|
|
869
|
+
members = List(
|
|
870
|
+
MemberDefinition("field1", ShapeId("smithy.api", "String")),
|
|
871
|
+
MemberDefinition("field2", ShapeId("smithy.api", "String"))
|
|
872
|
+
)
|
|
873
|
+
))
|
|
874
|
+
)
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
val compact = model.prettyPrint(indent = 2)
|
|
878
|
+
val verbose = model.prettyPrint(indent = 8)
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
## Common Use-Cases
|
|
882
|
+
|
|
883
|
+
See how to apply Smithy parsing and querying to real-world workflows: code generation, validation, and model transformation.
|
|
884
|
+
|
|
885
|
+
### Use-Case 1: Code Generation
|
|
886
|
+
|
|
887
|
+
Load a Smithy model and generate code for each operation:
|
|
888
|
+
|
|
889
|
+
```scala
|
|
890
|
+
import zio.blocks.smithy._
|
|
891
|
+
|
|
892
|
+
val model = SmithyModel.parse("""$version: "2"
|
|
893
|
+
namespace api
|
|
894
|
+
|
|
895
|
+
service MyService {
|
|
896
|
+
operations: [GetUser, CreateUser]
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
@http(method: "GET", uri: "/users/{id}")
|
|
900
|
+
operation GetUser {
|
|
901
|
+
input: GetUserInput
|
|
902
|
+
output: User
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
@http(method: "POST", uri: "/users")
|
|
906
|
+
operation CreateUser {
|
|
907
|
+
input: CreateUserInput
|
|
908
|
+
output: User
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
structure User { id: String, name: String }
|
|
912
|
+
structure GetUserInput { @required id: String }
|
|
913
|
+
structure CreateUserInput { @required name: String }
|
|
914
|
+
""").toOption.get
|
|
915
|
+
|
|
916
|
+
// Generate code stubs for each operation by pattern matching:
|
|
917
|
+
|
|
918
|
+
model.shapes.foreach { shapeDef =>
|
|
919
|
+
shapeDef.shape match {
|
|
920
|
+
case op: OperationShape =>
|
|
921
|
+
println(s"// Generate operation: ${op.name}")
|
|
922
|
+
op.input.foreach(in => println(s"// input: ${in.name}"))
|
|
923
|
+
op.output.foreach(out => println(s"// output: ${out.name}"))
|
|
924
|
+
case _ => ()
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
### Use-Case 2: Validation & Analysis
|
|
930
|
+
|
|
931
|
+
Find deprecated shapes and analyze trait coverage:
|
|
932
|
+
|
|
933
|
+
```scala
|
|
934
|
+
import zio.blocks.smithy._
|
|
935
|
+
|
|
936
|
+
val model = SmithyModel.parse("""$version: "2"
|
|
937
|
+
namespace example
|
|
938
|
+
|
|
939
|
+
@deprecated
|
|
940
|
+
structure LegacyUser { id: String }
|
|
941
|
+
|
|
942
|
+
structure ModernUser {
|
|
943
|
+
@required
|
|
944
|
+
id: String
|
|
945
|
+
email: String
|
|
946
|
+
}
|
|
947
|
+
""").toOption.get
|
|
948
|
+
|
|
949
|
+
// Find all deprecated shapes:
|
|
950
|
+
|
|
951
|
+
val deprecated = model.shapes.filter { shapeDef =>
|
|
952
|
+
shapeDef.shape.traits.exists(_.id.name == "deprecated")
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
println(s"Deprecated shapes: ${deprecated.map(_.name)}")
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
### Use-Case 3: Model Transformation
|
|
959
|
+
|
|
960
|
+
Parse, modify, and re-serialize a model with updated metadata:
|
|
961
|
+
|
|
962
|
+
```scala
|
|
963
|
+
import zio.blocks.smithy._
|
|
964
|
+
|
|
965
|
+
val original = """$version: "2"
|
|
966
|
+
namespace com.example
|
|
967
|
+
|
|
968
|
+
string UserId
|
|
969
|
+
"""
|
|
970
|
+
|
|
971
|
+
val modified = SmithyModel.parse(original).map { model =>
|
|
972
|
+
// Add metadata to the model:
|
|
973
|
+
|
|
974
|
+
val newMetadata = model.metadata + ("version" -> NodeValue.String("1.0"))
|
|
975
|
+
model.copy(metadata = newMetadata)
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
modified.foreach { model =>
|
|
979
|
+
println(model.prettyPrint)
|
|
980
|
+
}
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
## Running the Examples
|
|
984
|
+
|
|
985
|
+
All code from this guide is available as runnable examples in the `smithy-examples` module. Examples demonstrate different aspects of the Smithy library.
|
|
986
|
+
|
|
987
|
+
**1. Clone the repository and navigate to the project:**
|
|
988
|
+
|
|
989
|
+
```bash
|
|
990
|
+
git clone https://github.com/zio/zio-blocks.git
|
|
991
|
+
cd zio-blocks
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
**2. Run individual examples with sbt:**
|
|
995
|
+
|
|
996
|
+
### Step 1: Basic Parsing and Querying
|
|
997
|
+
|
|
998
|
+
Parse Smithy IDL text, find shapes by name, and access their structure and metadata:
|
|
999
|
+
|
|
1000
|
+
```bash
|
|
1001
|
+
sbt "smithy-examples/runMain smithyexample.BasicParsingAndQuerying"
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
### Step 2: Building Models Programmatically
|
|
1005
|
+
|
|
1006
|
+
Construct Smithy models in code by creating shapes, adding traits, and assembling them into a complete model:
|
|
1007
|
+
|
|
1008
|
+
```bash
|
|
1009
|
+
sbt "smithy-examples/runMain smithyexample.BuildingModelsAndTraits"
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
### Step 3: Validation and Analysis
|
|
1013
|
+
|
|
1014
|
+
Analyze Smithy models for completeness, find deprecated shapes, check for documentation, and validate API contracts:
|
|
1015
|
+
|
|
1016
|
+
```bash
|
|
1017
|
+
sbt "smithy-examples/runMain smithyexample.ValidationAndAnalysis"
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
### Step 4: Complete Example — Book Store API
|
|
1021
|
+
|
|
1022
|
+
A comprehensive end-to-end workflow showing a complete book store API model with parsing, entity analysis, error handling, code generation, and statistics:
|
|
1023
|
+
|
|
1024
|
+
```bash
|
|
1025
|
+
sbt "smithy-examples/runMain smithyexample.BookStoreAPI"
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
**3. Or compile all examples at once:**
|
|
1029
|
+
|
|
1030
|
+
```bash
|
|
1031
|
+
sbt "smithy-examples/compile"
|
|
1032
|
+
```
|