@zio.dev/zio-blocks 0.0.33 → 0.0.51
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/compile-time-resource-safety-with-scope.md +16 -17
- package/guides/getting-started-with-mux.md +1507 -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 +246 -165
- package/guides/telemetry-guide.md +1069 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +292 -50
- 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 +651 -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.md +158 -0
- package/reference/context.md +4 -4
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +825 -0
- package/reference/endpoint/path-codec.md +237 -0
- package/reference/endpoint/route-pattern.md +196 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +212 -0
- package/reference/html.md +1120 -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 +855 -0
- package/reference/http-model/index.md +47 -0
- package/reference/http-model/model.md +1481 -0
- package/reference/http-model/schema.md +747 -0
- package/reference/maybe.md +826 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +823 -0
- package/reference/openapi.md +1351 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -2
- package/reference/resource-management/scope.md +28 -12
- package/reference/resource-management/wire.md +3 -7
- 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 +151 -0
- package/reference/ringbuffer/mpsc.mdx +132 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +344 -0
- package/reference/{allows.md → schema/allows.md} +4 -4
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +2 -3
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +480 -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} +10 -10
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +50 -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} +165 -72
- 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.md → schema/schema.md} +12 -0
- 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 +533 -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 +146 -0
- package/reference/sql/db-tx.md +82 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +254 -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 +353 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/concurrent-operators.md +106 -0
- package/reference/streams/index.md +653 -0
- package/reference/streams/pipeline.md +718 -0
- package/reference/streams/reader.md +1284 -0
- package/reference/streams/scala-2-compatibility.md +55 -0
- package/reference/streams/sink.md +1426 -0
- package/reference/streams/stream.md +2526 -0
- package/reference/streams/writer.md +1045 -0
- package/reference/streams/zero-boxing.md +275 -0
- package/reference/telemetry.md +693 -0
- package/reference/typeid.md +5 -19
- package/sidebars.js +238 -43
- 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,1351 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: openapi
|
|
3
|
+
title: "OpenAPI"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`zio-blocks-openapi` is a **complete, type-safe OpenAPI 3.1 data model** for building API documentation programmatically. It provides immutable case classes and sealed traits representing every OpenAPI concept—operations, parameters, security schemes, and components—enabling you to construct OpenAPI documents in compile-time-safe Scala and export them as JSON for consumption by tools like Swagger UI, Redoc, and API validators.
|
|
7
|
+
|
|
8
|
+
Core types: `OpenAPI`, `Info`, `Paths`, `PathItem`, `Operation`, `Parameter`, `RequestBody`, `Response`, `Components`, `SchemaObject`, `SecurityScheme`, `ReferenceOr`.
|
|
9
|
+
|
|
10
|
+
```scala
|
|
11
|
+
final case class OpenAPI(
|
|
12
|
+
openapi: String,
|
|
13
|
+
info: Info,
|
|
14
|
+
servers: Option[Chunk[Server]] = None,
|
|
15
|
+
paths: Option[Paths] = None,
|
|
16
|
+
components: Option[Components] = None,
|
|
17
|
+
security: Option[Chunk[SecurityRequirement]] = None
|
|
18
|
+
)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Introduction
|
|
22
|
+
|
|
23
|
+
OpenAPI documents are the **lingua franca for API specifications**. They define request/response contracts, authentication methods, and data schemas in a standardized JSON or YAML format that external tools consume. Building these documents manually in JSON is error-prone; maintaining them as your API evolves is tedious.
|
|
24
|
+
|
|
25
|
+
The OpenAPI module bridges the gap by letting you author API specs as Scala code—leveraging the type system for compile-time correctness—then export to standard JSON that any OpenAPI tool understands. You get type safety during authoring plus interoperability with the entire OpenAPI ecosystem.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```scala
|
|
30
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-openapi" % "0.0.51"
|
|
31
|
+
|
|
32
|
+
// You'll also need the schema module for Schema[A] integration:
|
|
33
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.51"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For Scala.js:
|
|
37
|
+
|
|
38
|
+
```scala
|
|
39
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-openapi" % "0.0.51"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Supported Scala versions: 2.13.x and 3.x.
|
|
43
|
+
|
|
44
|
+
## How They Work Together
|
|
45
|
+
|
|
46
|
+
The OpenAPI module follows a clear workflow:
|
|
47
|
+
|
|
48
|
+
**1. Define your data types** using ZIO Blocks `Schema`:
|
|
49
|
+
|
|
50
|
+
```scala
|
|
51
|
+
import zio.blocks.openapi._
|
|
52
|
+
import zio.blocks.docs._
|
|
53
|
+
import zio.blocks.chunk._
|
|
54
|
+
|
|
55
|
+
import zio.blocks.schema._
|
|
56
|
+
|
|
57
|
+
case class User(id: Int, name: String, email: String)
|
|
58
|
+
object User {
|
|
59
|
+
implicit val schema: Schema[User] = Schema.derived
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
case class ErrorResponse(code: Int, message: String)
|
|
63
|
+
object ErrorResponse {
|
|
64
|
+
implicit val schema: Schema[ErrorResponse] = Schema.derived
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**2. Create an OpenAPI document** by composing types:
|
|
69
|
+
|
|
70
|
+
```scala
|
|
71
|
+
val api = OpenAPI(
|
|
72
|
+
openapi = "3.1.0",
|
|
73
|
+
info = Info(
|
|
74
|
+
title = "User API",
|
|
75
|
+
version = "1.0.0",
|
|
76
|
+
description = Some(md"API for managing users")
|
|
77
|
+
),
|
|
78
|
+
paths = Some(Paths(ChunkMap(
|
|
79
|
+
"/users" -> PathItem(
|
|
80
|
+
get = Some(Operation(
|
|
81
|
+
summary = Some(md"List all users"),
|
|
82
|
+
description = Some(md"Returns a paginated list of users"),
|
|
83
|
+
responses = Responses(ChunkMap(
|
|
84
|
+
"200" -> ReferenceOr.Value(Response(
|
|
85
|
+
description = md"Successful response",
|
|
86
|
+
content = ChunkMap(
|
|
87
|
+
"application/json" -> MediaType(
|
|
88
|
+
schema = Some(ReferenceOr.Value(
|
|
89
|
+
Schema[List[User]].toOpenAPISchema
|
|
90
|
+
))
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
))
|
|
94
|
+
))
|
|
95
|
+
))
|
|
96
|
+
),
|
|
97
|
+
"/users/{id}" -> PathItem(
|
|
98
|
+
get = Some(Operation(
|
|
99
|
+
summary = Some(md"Get a user by ID"),
|
|
100
|
+
parameters = Chunk(
|
|
101
|
+
ReferenceOr.Value(Parameter(
|
|
102
|
+
name = "id",
|
|
103
|
+
in = ParameterLocation.Path,
|
|
104
|
+
required = true,
|
|
105
|
+
schema = Some(ReferenceOr.Value(
|
|
106
|
+
Schema[Int].toOpenAPISchema
|
|
107
|
+
))
|
|
108
|
+
))
|
|
109
|
+
),
|
|
110
|
+
responses = Responses(ChunkMap(
|
|
111
|
+
"200" -> ReferenceOr.Value(Response(
|
|
112
|
+
description = md"User found",
|
|
113
|
+
content = ChunkMap(
|
|
114
|
+
"application/json" -> MediaType(
|
|
115
|
+
schema = Some(ReferenceOr.Value(
|
|
116
|
+
Schema[User].toOpenAPISchema
|
|
117
|
+
))
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
)),
|
|
121
|
+
"404" -> ReferenceOr.Value(Response(
|
|
122
|
+
description = md"User not found",
|
|
123
|
+
content = ChunkMap(
|
|
124
|
+
"application/json" -> MediaType(
|
|
125
|
+
schema = Some(ReferenceOr.Value(
|
|
126
|
+
Schema[ErrorResponse].toOpenAPISchema
|
|
127
|
+
))
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
))
|
|
131
|
+
))
|
|
132
|
+
))
|
|
133
|
+
)
|
|
134
|
+
))),
|
|
135
|
+
components = Some(Components(
|
|
136
|
+
schemas = ChunkMap(
|
|
137
|
+
Schema[User].toRefSchema._2._1 -> ReferenceOr.Value(Schema[User].toRefSchema._2._2),
|
|
138
|
+
Schema[ErrorResponse].toRefSchema._2._1 -> ReferenceOr.Value(Schema[ErrorResponse].toRefSchema._2._2)
|
|
139
|
+
)
|
|
140
|
+
))
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**3. Serialize to JSON** for tools to consume:
|
|
145
|
+
|
|
146
|
+
```scala
|
|
147
|
+
import zio.blocks.openapi.OpenAPICodec._
|
|
148
|
+
|
|
149
|
+
val json = openAPICodec.encodeValue(api)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**4. Render or serve** the JSON (e.g., to Swagger UI):
|
|
153
|
+
|
|
154
|
+
```scala
|
|
155
|
+
import zio.blocks.schema.json._
|
|
156
|
+
|
|
157
|
+
val jsonString = Json.jsonCodec.encodeToString(json, WriterConfig.withIndentionStep2)
|
|
158
|
+
// jsonString: String = """{
|
|
159
|
+
// "openapi": "3.1.0",
|
|
160
|
+
// "info": {
|
|
161
|
+
// "title": "User API",
|
|
162
|
+
// "version": "1.0.0",
|
|
163
|
+
// "description": "API for managing users\n\n"
|
|
164
|
+
// },
|
|
165
|
+
// "paths": {
|
|
166
|
+
// "/users": {
|
|
167
|
+
// "get": {
|
|
168
|
+
// "responses": {
|
|
169
|
+
// "200": {
|
|
170
|
+
// "description": "Successful response\n\n",
|
|
171
|
+
// "content": {
|
|
172
|
+
// "application/json": {
|
|
173
|
+
// "schema": {
|
|
174
|
+
// "items": {
|
|
175
|
+
// "properties": {
|
|
176
|
+
// "id": {
|
|
177
|
+
// "type": "integer",
|
|
178
|
+
// "maximum": 2147483647,
|
|
179
|
+
// "minimum": -2147483648
|
|
180
|
+
// },
|
|
181
|
+
// "name": {
|
|
182
|
+
// "type": "string"
|
|
183
|
+
// },
|
|
184
|
+
// "email": {
|
|
185
|
+
// "type": "string"
|
|
186
|
+
// }
|
|
187
|
+
// },
|
|
188
|
+
// "type": "object",
|
|
189
|
+
// "required": [
|
|
190
|
+
// "id",
|
|
191
|
+
// "name",
|
|
192
|
+
// "email"
|
|
193
|
+
// ],
|
|
194
|
+
// "title": "User"
|
|
195
|
+
// },
|
|
196
|
+
// "type": [
|
|
197
|
+
// "array",
|
|
198
|
+
// "null"
|
|
199
|
+
// ],
|
|
200
|
+
// "title": "List[User]"
|
|
201
|
+
// }
|
|
202
|
+
// }
|
|
203
|
+
// }
|
|
204
|
+
// }
|
|
205
|
+
// },
|
|
206
|
+
// "summary": "List all users\n\n",
|
|
207
|
+
// ...
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Type Relationships Diagram
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
OpenAPI (root document)
|
|
214
|
+
├─ info: Info (metadata)
|
|
215
|
+
├─ servers: Option[Chunk[Server]]
|
|
216
|
+
├─ paths: Option[Paths] (map of path strings to PathItem)
|
|
217
|
+
│ └─ PathItem
|
|
218
|
+
│ ├─ get: Operation
|
|
219
|
+
│ ├─ post: Operation
|
|
220
|
+
│ ├─ put: Operation
|
|
221
|
+
│ └─ ... (other HTTP methods)
|
|
222
|
+
│ ├─ parameters: Chunk[ReferenceOr[Parameter]]
|
|
223
|
+
│ ├─ requestBody: ReferenceOr[RequestBody]
|
|
224
|
+
│ │ └─ content: Map[String, MediaType]
|
|
225
|
+
│ │ └─ schema: ReferenceOr[SchemaObject]
|
|
226
|
+
│ └─ responses: Responses
|
|
227
|
+
│ └─ Map[statusCode, ReferenceOr[Response]]
|
|
228
|
+
│ └─ content: Map[String, MediaType]
|
|
229
|
+
│ └─ schema: ReferenceOr[SchemaObject]
|
|
230
|
+
├─ components: Option[Components]
|
|
231
|
+
│ ├─ schemas: ChunkMap[String, ReferenceOr[SchemaObject]]
|
|
232
|
+
│ ├─ responses: ChunkMap[String, ReferenceOr[Response]]
|
|
233
|
+
│ ├─ parameters: ChunkMap[String, ReferenceOr[Parameter]]
|
|
234
|
+
│ └─ securitySchemes: ChunkMap[String, ReferenceOr[SecurityScheme]]
|
|
235
|
+
└─ security: Option[Chunk[SecurityRequirement]]
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Common Patterns
|
|
239
|
+
|
|
240
|
+
### Building Reusable Schema Components
|
|
241
|
+
|
|
242
|
+
Avoid duplicating schema definitions by moving them to `components.schemas`:
|
|
243
|
+
|
|
244
|
+
```scala
|
|
245
|
+
import zio.blocks.openapi._
|
|
246
|
+
import zio.blocks.docs._
|
|
247
|
+
import zio.blocks.chunk._
|
|
248
|
+
import zio.blocks.schema._
|
|
249
|
+
|
|
250
|
+
case class User(id: Int, name: String, email: String)
|
|
251
|
+
object User {
|
|
252
|
+
implicit val schema: Schema[User] = Schema.derived
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
val userSchemaComponent = Schema[User].toRefSchema
|
|
256
|
+
// Returns: (ReferenceOr.Ref(...), ("User", SchemaObject(...)))
|
|
257
|
+
// Use the ref in operations, store the component in components.schemas
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Using `ReferenceOr` for Inline vs. Referenced Schemas
|
|
261
|
+
|
|
262
|
+
`ReferenceOr[A]` is a sealed trait with two cases:
|
|
263
|
+
|
|
264
|
+
- **`ReferenceOr.Ref`**: Points to a schema in `#/components/schemas/<name>`
|
|
265
|
+
- **`ReferenceOr.Value`**: Inline schema definition
|
|
266
|
+
|
|
267
|
+
Prefer `Ref` for reusable schemas; use `Value` for simple, one-off schemas:
|
|
268
|
+
|
|
269
|
+
```scala
|
|
270
|
+
import zio.blocks.openapi._
|
|
271
|
+
import zio.blocks.docs._
|
|
272
|
+
import zio.blocks.chunk._
|
|
273
|
+
import zio.blocks.schema._
|
|
274
|
+
|
|
275
|
+
// Reusable: use Ref
|
|
276
|
+
val userRef = ReferenceOr.Ref(Reference(`$ref` = "#/components/schemas/User"))
|
|
277
|
+
|
|
278
|
+
// One-off: use Value
|
|
279
|
+
val simpleString = ReferenceOr.Value(Schema[String].toOpenAPISchema)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Security Schemes
|
|
283
|
+
|
|
284
|
+
Define authentication methods in `components.securitySchemes`:
|
|
285
|
+
|
|
286
|
+
```scala
|
|
287
|
+
import zio.blocks.openapi._
|
|
288
|
+
import zio.blocks.docs._
|
|
289
|
+
import zio.blocks.chunk._
|
|
290
|
+
import zio.blocks.schema._
|
|
291
|
+
|
|
292
|
+
val apiKeyScheme = SecurityScheme.APIKey(
|
|
293
|
+
name = "X-API-Key",
|
|
294
|
+
in = APIKeyLocation.Header,
|
|
295
|
+
description = Some(md"API key for authentication")
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
val oauthScheme = SecurityScheme.OAuth2(
|
|
299
|
+
flows = OAuthFlows(
|
|
300
|
+
authorizationCode = Some(OAuthFlow(
|
|
301
|
+
authorizationUrl = Some("https://example.com/oauth/authorize"),
|
|
302
|
+
tokenUrl = Some("https://example.com/oauth/token"),
|
|
303
|
+
scopes = ChunkMap("read" -> "Read access", "write" -> "Write access")
|
|
304
|
+
))
|
|
305
|
+
),
|
|
306
|
+
description = Some(md"OAuth 2.0 authorization")
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
val components = Components(
|
|
310
|
+
securitySchemes = ChunkMap(
|
|
311
|
+
"api_key" -> ReferenceOr.Value(apiKeyScheme),
|
|
312
|
+
"oauth2" -> ReferenceOr.Value(oauthScheme)
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Path Parameters vs. Query Parameters
|
|
318
|
+
|
|
319
|
+
Distinguish parameter locations using `ParameterLocation`:
|
|
320
|
+
|
|
321
|
+
```scala
|
|
322
|
+
import zio.blocks.openapi._
|
|
323
|
+
import zio.blocks.docs._
|
|
324
|
+
import zio.blocks.chunk._
|
|
325
|
+
import zio.blocks.schema._
|
|
326
|
+
|
|
327
|
+
val pathParam = Parameter(
|
|
328
|
+
name = "id",
|
|
329
|
+
in = ParameterLocation.Path,
|
|
330
|
+
required = true,
|
|
331
|
+
schema = Some(ReferenceOr.Value(Schema[Int].toOpenAPISchema))
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
val queryParam = Parameter(
|
|
335
|
+
name = "limit",
|
|
336
|
+
in = ParameterLocation.Query,
|
|
337
|
+
required = false,
|
|
338
|
+
schema = Some(ReferenceOr.Value(Schema[Int].toOpenAPISchema))
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
val headerParam = Parameter(
|
|
342
|
+
name = "X-Custom-Header",
|
|
343
|
+
in = ParameterLocation.Header,
|
|
344
|
+
required = false,
|
|
345
|
+
schema = Some(ReferenceOr.Value(Schema[String].toOpenAPISchema))
|
|
346
|
+
)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Integration Points
|
|
350
|
+
|
|
351
|
+
The OpenAPI module integrates tightly with other ZIO Blocks components:
|
|
352
|
+
|
|
353
|
+
- **Schema Integration**: All OpenAPI types have `Schema.derived` instances, enabling round-trip serialization via `DynamicValue`. Use `Schema[A].toOpenAPISchema` to convert any schema to an OpenAPI component.
|
|
354
|
+
- **Markdown Support**: Description fields use the in-house `Doc` type, which supports CommonMark rendering. This ensures markdown descriptions round-trip correctly.
|
|
355
|
+
- **JSON AST**: All codecs operate on the `Json` AST from `zio-blocks-schema`, not external JSON libraries. To render as YAML, pipe the `Json` through `zio-blocks-schema-yaml` separately.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## OpenAPI
|
|
360
|
+
|
|
361
|
+
`OpenAPI` is the root document object representing a complete OpenAPI 3.1 specification.
|
|
362
|
+
|
|
363
|
+
### Definition
|
|
364
|
+
|
|
365
|
+
Every OpenAPI document requires:
|
|
366
|
+
- **`openapi`**: Version string (typically `"3.1.0"`)
|
|
367
|
+
- **`info`**: Metadata about the API (`Info`)
|
|
368
|
+
|
|
369
|
+
Optional top-level fields include:
|
|
370
|
+
- **`servers`**: Server definitions for the API (`Chunk[Server]`)
|
|
371
|
+
- **`paths`**: Map of endpoint paths to operations (`Paths`)
|
|
372
|
+
- **`components`**: Reusable schemas, responses, parameters, and other components (`Components`)
|
|
373
|
+
- **`security`**: Security requirements applied to the API (`Chunk[SecurityRequirement]`)
|
|
374
|
+
|
|
375
|
+
Response definitions are modeled per `Operation`, not as a top-level field on `OpenAPI`.
|
|
376
|
+
|
|
377
|
+
### Creating an OpenAPI Document
|
|
378
|
+
|
|
379
|
+
To construct an `OpenAPI` document:
|
|
380
|
+
|
|
381
|
+
```scala
|
|
382
|
+
import zio.blocks.openapi._
|
|
383
|
+
import zio.blocks.docs._
|
|
384
|
+
import zio.blocks.chunk._
|
|
385
|
+
import zio.blocks.schema._
|
|
386
|
+
|
|
387
|
+
val minimalApi = OpenAPI(
|
|
388
|
+
openapi = "3.1.0",
|
|
389
|
+
info = Info(title = "My API", version = "1.0.0")
|
|
390
|
+
)
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Add paths, operations, and components as shown in the "How They Work Together" section above.
|
|
394
|
+
|
|
395
|
+
### Serialization
|
|
396
|
+
|
|
397
|
+
Encode an `OpenAPI` document to `Json` AST:
|
|
398
|
+
|
|
399
|
+
```scala
|
|
400
|
+
import zio.blocks.openapi._
|
|
401
|
+
import zio.blocks.openapi.OpenAPICodec._
|
|
402
|
+
import zio.blocks.docs._
|
|
403
|
+
import zio.blocks.chunk._
|
|
404
|
+
import zio.blocks.schema._
|
|
405
|
+
|
|
406
|
+
import zio.blocks.schema.json._
|
|
407
|
+
|
|
408
|
+
val myApi = OpenAPI(openapi = "3.1.0", info = Info(title = "My API", version = "1.0.0"))
|
|
409
|
+
val encoded: Json = openAPICodec.encodeValue(myApi)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Decode from `Json` AST back to an `OpenAPI` instance:
|
|
413
|
+
|
|
414
|
+
```scala
|
|
415
|
+
import zio.blocks.openapi._
|
|
416
|
+
import zio.blocks.openapi.OpenAPICodec._
|
|
417
|
+
import zio.blocks.docs._
|
|
418
|
+
import zio.blocks.chunk._
|
|
419
|
+
import zio.blocks.schema._
|
|
420
|
+
|
|
421
|
+
import zio.blocks.schema.json._
|
|
422
|
+
|
|
423
|
+
val myApi = OpenAPI(openapi = "3.1.0", info = Info(title = "My API", version = "1.0.0"))
|
|
424
|
+
val encoded: Json = openAPICodec.encodeValue(myApi)
|
|
425
|
+
val decoded: OpenAPI = openAPICodec.decodeValue(encoded)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Info
|
|
431
|
+
|
|
432
|
+
`Info` contains metadata about the API: title, version, contact, and license.
|
|
433
|
+
|
|
434
|
+
### Definition
|
|
435
|
+
|
|
436
|
+
Required fields:
|
|
437
|
+
- **`title`**: API name (e.g., `"User API"`)
|
|
438
|
+
- **`version`**: API version (e.g., `"1.0.0"`)
|
|
439
|
+
|
|
440
|
+
Optional fields:
|
|
441
|
+
- **`description`**: Markdown-formatted description (`Doc`)
|
|
442
|
+
- **`termsOfService`**: Terms of service URL
|
|
443
|
+
- **`contact`**: Contact information (`Contact`)
|
|
444
|
+
- **`license`**: License information (`License`)
|
|
445
|
+
|
|
446
|
+
### Creating Info
|
|
447
|
+
|
|
448
|
+
```scala
|
|
449
|
+
import zio.blocks.openapi._
|
|
450
|
+
import zio.blocks.docs._
|
|
451
|
+
import zio.blocks.chunk._
|
|
452
|
+
import zio.blocks.schema._
|
|
453
|
+
|
|
454
|
+
val info = Info(
|
|
455
|
+
title = "Pet Store API",
|
|
456
|
+
version = "3.0.0",
|
|
457
|
+
description = Some(md"API for managing a pet store"),
|
|
458
|
+
contact = Some(Contact(
|
|
459
|
+
name = Some("API Support"),
|
|
460
|
+
url = Some("https://example.com/support"),
|
|
461
|
+
email = Some("support@example.com")
|
|
462
|
+
)),
|
|
463
|
+
license = Some(License(
|
|
464
|
+
name = "Apache 2.0",
|
|
465
|
+
identifier = Some("Apache-2.0")
|
|
466
|
+
))
|
|
467
|
+
)
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## Paths & PathItem
|
|
473
|
+
|
|
474
|
+
`Paths` represents the collection of URL paths and their operations. `PathItem` groups HTTP methods (GET, POST, PUT, etc.) on a single path.
|
|
475
|
+
|
|
476
|
+
### Definition
|
|
477
|
+
|
|
478
|
+
`Paths` is a wrapper case class with two fields:
|
|
479
|
+
- **`paths`**: `ChunkMap[String, PathItem]`, where keys are path strings (e.g., `"/users/{id}"`)
|
|
480
|
+
- **`extensions`**: `ChunkMap[String, Json]`, for OpenAPI specification extensions
|
|
481
|
+
|
|
482
|
+
`PathItem` contains optional fields for each HTTP method:
|
|
483
|
+
- **`get`, `post`, `put`, `delete`, `patch`, `head`, `options`, `trace`**: `Operation` instances
|
|
484
|
+
- **`parameters`**: Path-level parameters shared by all methods on this path
|
|
485
|
+
- **`servers`**: Optional server overrides for this path
|
|
486
|
+
|
|
487
|
+
### Creating Path Items
|
|
488
|
+
|
|
489
|
+
To define a path with multiple operations:
|
|
490
|
+
|
|
491
|
+
```scala
|
|
492
|
+
import zio.blocks.openapi._
|
|
493
|
+
import zio.blocks.docs._
|
|
494
|
+
import zio.blocks.chunk._
|
|
495
|
+
import zio.blocks.schema._
|
|
496
|
+
|
|
497
|
+
val userPaths = Paths(ChunkMap(
|
|
498
|
+
"/users" -> PathItem(
|
|
499
|
+
get = Some(Operation(
|
|
500
|
+
summary = Some(md"List users"),
|
|
501
|
+
responses = Responses(ChunkMap(
|
|
502
|
+
"200" -> ReferenceOr.Value(Response(
|
|
503
|
+
description = md"User list",
|
|
504
|
+
content = ChunkMap(
|
|
505
|
+
"application/json" -> MediaType(schema = None)
|
|
506
|
+
)
|
|
507
|
+
))
|
|
508
|
+
))
|
|
509
|
+
)),
|
|
510
|
+
post = Some(Operation(
|
|
511
|
+
summary = Some(md"Create user"),
|
|
512
|
+
requestBody = Some(ReferenceOr.Value(RequestBody(
|
|
513
|
+
description = Some(md"User data"),
|
|
514
|
+
content = ChunkMap(
|
|
515
|
+
"application/json" -> MediaType(schema = None)
|
|
516
|
+
),
|
|
517
|
+
required = true
|
|
518
|
+
))),
|
|
519
|
+
responses = Responses(ChunkMap(
|
|
520
|
+
"201" -> ReferenceOr.Value(Response(
|
|
521
|
+
description = md"User created",
|
|
522
|
+
content = ChunkMap(
|
|
523
|
+
"application/json" -> MediaType(schema = None)
|
|
524
|
+
)
|
|
525
|
+
))
|
|
526
|
+
))
|
|
527
|
+
))
|
|
528
|
+
),
|
|
529
|
+
"/users/{id}" -> PathItem(
|
|
530
|
+
parameters = Chunk(
|
|
531
|
+
ReferenceOr.Value(Parameter(
|
|
532
|
+
name = "id",
|
|
533
|
+
in = ParameterLocation.Path,
|
|
534
|
+
required = true,
|
|
535
|
+
schema = Some(ReferenceOr.Value(Schema[String].toOpenAPISchema))
|
|
536
|
+
))
|
|
537
|
+
),
|
|
538
|
+
get = Some(Operation(
|
|
539
|
+
summary = Some(md"Get user by ID"),
|
|
540
|
+
responses = Responses(ChunkMap(
|
|
541
|
+
"200" -> ReferenceOr.Value(Response(
|
|
542
|
+
description = md"User found",
|
|
543
|
+
content = ChunkMap(
|
|
544
|
+
"application/json" -> MediaType(schema = None)
|
|
545
|
+
)
|
|
546
|
+
)),
|
|
547
|
+
"404" -> ReferenceOr.Value(Response(
|
|
548
|
+
description = md"User not found",
|
|
549
|
+
content = ChunkMap(
|
|
550
|
+
"application/json" -> MediaType(schema = None)
|
|
551
|
+
)
|
|
552
|
+
))
|
|
553
|
+
))
|
|
554
|
+
))
|
|
555
|
+
)
|
|
556
|
+
))
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## Operation
|
|
562
|
+
|
|
563
|
+
`Operation` represents a single HTTP operation (GET, POST, etc.) on a path.
|
|
564
|
+
|
|
565
|
+
### Definition
|
|
566
|
+
|
|
567
|
+
Key fields:
|
|
568
|
+
- **`responses`**: Required. Map of status codes to response definitions
|
|
569
|
+
- **`operationId`**: Unique operation identifier
|
|
570
|
+
- **`summary`**: Short description
|
|
571
|
+
- **`description`**: Detailed markdown description
|
|
572
|
+
- **`parameters`**: Path, query, header, and cookie parameters
|
|
573
|
+
- **`requestBody`**: Request payload definition
|
|
574
|
+
- **`deprecated`**: Whether the operation is deprecated
|
|
575
|
+
- **`tags`**: Group operations in documentation (e.g., `"users"`, `"products"`)
|
|
576
|
+
|
|
577
|
+
### Defining an Operation
|
|
578
|
+
|
|
579
|
+
With summary, description, and parameters:
|
|
580
|
+
|
|
581
|
+
```scala
|
|
582
|
+
import zio.blocks.openapi._
|
|
583
|
+
import zio.blocks.docs._
|
|
584
|
+
import zio.blocks.chunk._
|
|
585
|
+
import zio.blocks.schema._
|
|
586
|
+
|
|
587
|
+
val getUser = Operation(
|
|
588
|
+
tags = Chunk("users"),
|
|
589
|
+
summary = Some(md"Retrieve user"),
|
|
590
|
+
description = Some(md"Fetches a single user by ID"),
|
|
591
|
+
operationId = Some("getUserById"),
|
|
592
|
+
parameters = Chunk(
|
|
593
|
+
ReferenceOr.Value(Parameter(
|
|
594
|
+
name = "id",
|
|
595
|
+
in = ParameterLocation.Path,
|
|
596
|
+
required = true,
|
|
597
|
+
schema = Some(ReferenceOr.Value(Schema[String].toOpenAPISchema)),
|
|
598
|
+
description = Some(md"User ID")
|
|
599
|
+
))
|
|
600
|
+
),
|
|
601
|
+
responses = Responses(ChunkMap(
|
|
602
|
+
"200" -> ReferenceOr.Value(Response(
|
|
603
|
+
description = md"User found",
|
|
604
|
+
content = ChunkMap(
|
|
605
|
+
"application/json" -> MediaType(schema = None)
|
|
606
|
+
)
|
|
607
|
+
)),
|
|
608
|
+
"404" -> ReferenceOr.Value(Response(
|
|
609
|
+
description = md"User not found",
|
|
610
|
+
content = ChunkMap(
|
|
611
|
+
"application/json" -> MediaType(schema = None)
|
|
612
|
+
)
|
|
613
|
+
))
|
|
614
|
+
))
|
|
615
|
+
)
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
## Parameter
|
|
621
|
+
|
|
622
|
+
`Parameter` represents query, path, header, or cookie parameters in a request.
|
|
623
|
+
|
|
624
|
+
### Definition
|
|
625
|
+
|
|
626
|
+
Required fields:
|
|
627
|
+
- **`name`**: Parameter name (e.g., `"id"`, `"limit"`)
|
|
628
|
+
- **`in`**: Location—`Path`, `Query`, `Header`, or `Cookie` (`ParameterLocation`)
|
|
629
|
+
- **`schema`**: Data type of the parameter (`ReferenceOr[SchemaObject]`)
|
|
630
|
+
|
|
631
|
+
Optional fields:
|
|
632
|
+
- **`description`**: Markdown description
|
|
633
|
+
- **`required`**: Whether the parameter is mandatory (default: `false`)
|
|
634
|
+
- **`deprecated`**: Whether the parameter is deprecated
|
|
635
|
+
- **`allowEmptyValue`**: Whether empty string values are allowed
|
|
636
|
+
|
|
637
|
+
### Creating Parameters
|
|
638
|
+
|
|
639
|
+
Path parameter (required):
|
|
640
|
+
|
|
641
|
+
```scala
|
|
642
|
+
import zio.blocks.openapi._
|
|
643
|
+
import zio.blocks.docs._
|
|
644
|
+
import zio.blocks.chunk._
|
|
645
|
+
import zio.blocks.schema._
|
|
646
|
+
|
|
647
|
+
val idPathParam = Parameter(
|
|
648
|
+
name = "id",
|
|
649
|
+
in = ParameterLocation.Path,
|
|
650
|
+
required = true,
|
|
651
|
+
schema = Some(ReferenceOr.Value(Schema[String].toOpenAPISchema)),
|
|
652
|
+
description = Some(md"User identifier")
|
|
653
|
+
)
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
Query parameter (optional with default):
|
|
657
|
+
|
|
658
|
+
```scala
|
|
659
|
+
import zio.blocks.openapi._
|
|
660
|
+
import zio.blocks.docs._
|
|
661
|
+
import zio.blocks.chunk._
|
|
662
|
+
import zio.blocks.schema._
|
|
663
|
+
|
|
664
|
+
val limitQueryParam = Parameter(
|
|
665
|
+
name = "limit",
|
|
666
|
+
in = ParameterLocation.Query,
|
|
667
|
+
required = false,
|
|
668
|
+
schema = Some(ReferenceOr.Value(Schema[Int].toOpenAPISchema)),
|
|
669
|
+
description = Some(md"Maximum number of results (default: 20)")
|
|
670
|
+
)
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
Header parameter:
|
|
674
|
+
|
|
675
|
+
```scala
|
|
676
|
+
import zio.blocks.openapi._
|
|
677
|
+
import zio.blocks.docs._
|
|
678
|
+
import zio.blocks.chunk._
|
|
679
|
+
import zio.blocks.schema._
|
|
680
|
+
|
|
681
|
+
val authHeaderParam = Parameter(
|
|
682
|
+
name = "X-API-Key",
|
|
683
|
+
in = ParameterLocation.Header,
|
|
684
|
+
required = true,
|
|
685
|
+
schema = Some(ReferenceOr.Value(Schema[String].toOpenAPISchema)),
|
|
686
|
+
description = Some(md"API key for authentication")
|
|
687
|
+
)
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
---
|
|
691
|
+
|
|
692
|
+
## RequestBody & Response
|
|
693
|
+
|
|
694
|
+
`RequestBody` defines the structure of a request payload. `Response` defines the structure and status of a response.
|
|
695
|
+
|
|
696
|
+
### RequestBody Definition
|
|
697
|
+
|
|
698
|
+
Key fields:
|
|
699
|
+
- **`content`**: Map of MIME types to `MediaType` definitions
|
|
700
|
+
- **`description`**: Optional markdown description
|
|
701
|
+
- **`required`**: Whether the request body is mandatory (default: `false`)
|
|
702
|
+
|
|
703
|
+
### Creating a RequestBody
|
|
704
|
+
|
|
705
|
+
```scala
|
|
706
|
+
import zio.blocks.openapi._
|
|
707
|
+
import zio.blocks.docs._
|
|
708
|
+
import zio.blocks.chunk._
|
|
709
|
+
import zio.blocks.schema._
|
|
710
|
+
import zio.blocks.schema.json._
|
|
711
|
+
|
|
712
|
+
case class User(name: String, email: String)
|
|
713
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
714
|
+
|
|
715
|
+
val createUserBody = RequestBody(
|
|
716
|
+
description = Some(md"User data to create"),
|
|
717
|
+
content = ChunkMap(
|
|
718
|
+
"application/json" -> MediaType(
|
|
719
|
+
schema = Some(ReferenceOr.Value(Schema[User].toOpenAPISchema)),
|
|
720
|
+
example = Some(Json.Object(Chunk(
|
|
721
|
+
"name" -> Json.String("John Doe"),
|
|
722
|
+
"email" -> Json.String("john@example.com")
|
|
723
|
+
)))
|
|
724
|
+
)
|
|
725
|
+
),
|
|
726
|
+
required = true
|
|
727
|
+
)
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### Response Definition
|
|
731
|
+
|
|
732
|
+
Key fields:
|
|
733
|
+
- **`description`**: Required. Markdown description of the response
|
|
734
|
+
- **`content`**: Map of MIME types to `MediaType` definitions
|
|
735
|
+
- **`headers`**: Optional response headers
|
|
736
|
+
- **`links`**: Optional links to related operations
|
|
737
|
+
|
|
738
|
+
### Creating a Response
|
|
739
|
+
|
|
740
|
+
```scala
|
|
741
|
+
import zio.blocks.openapi._
|
|
742
|
+
import zio.blocks.docs._
|
|
743
|
+
import zio.blocks.chunk._
|
|
744
|
+
import zio.blocks.schema._
|
|
745
|
+
|
|
746
|
+
case class User2(id: Int, name: String, email: String)
|
|
747
|
+
object User2 { implicit val schema: Schema[User2] = Schema.derived }
|
|
748
|
+
case class ErrorResponse2(code: Int, message: String)
|
|
749
|
+
object ErrorResponse2 { implicit val schema: Schema[ErrorResponse2] = Schema.derived }
|
|
750
|
+
|
|
751
|
+
val successResponse = Response(
|
|
752
|
+
description = md"User successfully created",
|
|
753
|
+
content = ChunkMap(
|
|
754
|
+
"application/json" -> MediaType(
|
|
755
|
+
schema = Some(ReferenceOr.Value(Schema[User2].toOpenAPISchema))
|
|
756
|
+
)
|
|
757
|
+
)
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
val errorResponse = Response(
|
|
761
|
+
description = md"Request validation failed",
|
|
762
|
+
content = ChunkMap(
|
|
763
|
+
"application/json" -> MediaType(
|
|
764
|
+
schema = Some(ReferenceOr.Value(Schema[ErrorResponse2].toOpenAPISchema))
|
|
765
|
+
)
|
|
766
|
+
)
|
|
767
|
+
)
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
### Responses
|
|
771
|
+
|
|
772
|
+
`Responses` is a map of HTTP status codes to `ReferenceOr[Response]`:
|
|
773
|
+
|
|
774
|
+
```scala
|
|
775
|
+
import zio.blocks.openapi._
|
|
776
|
+
import zio.blocks.docs._
|
|
777
|
+
import zio.blocks.chunk._
|
|
778
|
+
import zio.blocks.schema._
|
|
779
|
+
|
|
780
|
+
val ok = Response(description = md"Created", content = ChunkMap())
|
|
781
|
+
val err = Response(description = md"Bad request", content = ChunkMap())
|
|
782
|
+
|
|
783
|
+
val responses = Responses(ChunkMap(
|
|
784
|
+
"201" -> ReferenceOr.Value(ok),
|
|
785
|
+
"400" -> ReferenceOr.Value(err),
|
|
786
|
+
"401" -> ReferenceOr.Value(Response(
|
|
787
|
+
description = md"Unauthorized",
|
|
788
|
+
content = ChunkMap()
|
|
789
|
+
)),
|
|
790
|
+
"500" -> ReferenceOr.Value(Response(
|
|
791
|
+
description = md"Internal server error",
|
|
792
|
+
content = ChunkMap()
|
|
793
|
+
))
|
|
794
|
+
))
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
---
|
|
798
|
+
|
|
799
|
+
## MediaType
|
|
800
|
+
|
|
801
|
+
`MediaType` specifies the schema and encoding for a particular MIME type in a request or response.
|
|
802
|
+
|
|
803
|
+
### Definition
|
|
804
|
+
|
|
805
|
+
Key fields:
|
|
806
|
+
- **`schema`**: Data type for this MIME type (`ReferenceOr[SchemaObject]`)
|
|
807
|
+
- **`example`**: Example value as `Json`
|
|
808
|
+
- **`encoding`**: Encoding rules for multipart form data
|
|
809
|
+
- **`extensions`**: Custom vendor extensions (`x-*` fields)
|
|
810
|
+
|
|
811
|
+
### Creating MediaType
|
|
812
|
+
|
|
813
|
+
With schema and example:
|
|
814
|
+
|
|
815
|
+
```scala
|
|
816
|
+
import zio.blocks.openapi._
|
|
817
|
+
import zio.blocks.docs._
|
|
818
|
+
import zio.blocks.chunk._
|
|
819
|
+
import zio.blocks.schema._
|
|
820
|
+
import zio.blocks.schema.json._
|
|
821
|
+
|
|
822
|
+
case class User(id: Int, name: String, email: String)
|
|
823
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
824
|
+
|
|
825
|
+
val jsonMedia = MediaType(
|
|
826
|
+
schema = Some(ReferenceOr.Value(Schema[User].toOpenAPISchema)),
|
|
827
|
+
example = Some(Json.Object(
|
|
828
|
+
"id" -> Json.Number(1),
|
|
829
|
+
"name" -> Json.String("Alice"),
|
|
830
|
+
"email" -> Json.String("alice@example.com")
|
|
831
|
+
))
|
|
832
|
+
)
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
For form data:
|
|
836
|
+
|
|
837
|
+
```scala
|
|
838
|
+
import zio.blocks.openapi._
|
|
839
|
+
import zio.blocks.docs._
|
|
840
|
+
import zio.blocks.chunk._
|
|
841
|
+
import zio.blocks.schema._
|
|
842
|
+
|
|
843
|
+
val formMedia = MediaType(
|
|
844
|
+
schema = Some(ReferenceOr.Value(Schema[Map[String, String]].toOpenAPISchema)),
|
|
845
|
+
encoding = ChunkMap(
|
|
846
|
+
"file" -> Encoding(
|
|
847
|
+
contentType = Some("application/octet-stream")
|
|
848
|
+
)
|
|
849
|
+
)
|
|
850
|
+
)
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
---
|
|
854
|
+
|
|
855
|
+
## Components
|
|
856
|
+
|
|
857
|
+
`Components` stores reusable schema and security definitions referenced throughout the document.
|
|
858
|
+
|
|
859
|
+
### Definition
|
|
860
|
+
|
|
861
|
+
Key fields:
|
|
862
|
+
- **`schemas`**: Reusable schema objects (`ChunkMap[String, ReferenceOr[SchemaObject]]`)
|
|
863
|
+
- **`responses`**: Reusable response definitions
|
|
864
|
+
- **`parameters`**: Reusable parameter definitions
|
|
865
|
+
- **`securitySchemes`**: Authentication method definitions
|
|
866
|
+
- **`examples`**, **`requestBodies`**, **`headers`**, **`links`**, **`callbacks`**: Additional reusable components
|
|
867
|
+
|
|
868
|
+
### Creating Components
|
|
869
|
+
|
|
870
|
+
```scala
|
|
871
|
+
import zio.blocks.openapi._
|
|
872
|
+
import zio.blocks.docs._
|
|
873
|
+
import zio.blocks.chunk._
|
|
874
|
+
import zio.blocks.schema._
|
|
875
|
+
|
|
876
|
+
case class User(id: Int, name: String, email: String)
|
|
877
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
878
|
+
case class ErrorResponse(code: Int, message: String)
|
|
879
|
+
object ErrorResponse { implicit val schema: Schema[ErrorResponse] = Schema.derived }
|
|
880
|
+
|
|
881
|
+
val components = Components(
|
|
882
|
+
schemas = ChunkMap(
|
|
883
|
+
Schema[User].toRefSchema._2._1 -> ReferenceOr.Value(Schema[User].toRefSchema._2._2),
|
|
884
|
+
Schema[ErrorResponse].toRefSchema._2._1 -> ReferenceOr.Value(Schema[ErrorResponse].toRefSchema._2._2)
|
|
885
|
+
),
|
|
886
|
+
parameters = ChunkMap(
|
|
887
|
+
"id" -> ReferenceOr.Value(Parameter(
|
|
888
|
+
name = "id",
|
|
889
|
+
in = ParameterLocation.Path,
|
|
890
|
+
required = true,
|
|
891
|
+
schema = Some(ReferenceOr.Value(Schema[String].toOpenAPISchema))
|
|
892
|
+
))
|
|
893
|
+
),
|
|
894
|
+
responses = ChunkMap(
|
|
895
|
+
"NotFound" -> ReferenceOr.Value(Response(
|
|
896
|
+
description = md"Resource not found",
|
|
897
|
+
content = ChunkMap(
|
|
898
|
+
"application/json" -> MediaType(
|
|
899
|
+
schema = Some(ReferenceOr.Value(
|
|
900
|
+
Schema[ErrorResponse].toOpenAPISchema
|
|
901
|
+
))
|
|
902
|
+
)
|
|
903
|
+
)
|
|
904
|
+
)),
|
|
905
|
+
"Unauthorized" -> ReferenceOr.Value(Response(
|
|
906
|
+
description = md"Unauthorized access",
|
|
907
|
+
content = ChunkMap()
|
|
908
|
+
))
|
|
909
|
+
),
|
|
910
|
+
securitySchemes = ChunkMap(
|
|
911
|
+
"api_key" -> ReferenceOr.Value(SecurityScheme.APIKey(
|
|
912
|
+
name = "X-API-Key",
|
|
913
|
+
in = APIKeyLocation.Header,
|
|
914
|
+
description = Some(md"API key header")
|
|
915
|
+
))
|
|
916
|
+
)
|
|
917
|
+
)
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
---
|
|
921
|
+
|
|
922
|
+
## SchemaObject
|
|
923
|
+
|
|
924
|
+
`SchemaObject` wraps a JSON Schema 2020-12 definition with OpenAPI-specific extensions like discriminator, XML metadata, and examples.
|
|
925
|
+
|
|
926
|
+
### Definition
|
|
927
|
+
|
|
928
|
+
`SchemaObject` contains:
|
|
929
|
+
- **`jsonSchema`**: Raw JSON Schema 2020-12 as `Json` AST
|
|
930
|
+
- **`discriminator`**: Polymorphism discriminator for oneOf/anyOf
|
|
931
|
+
- **`xml`**: XML serialization metadata
|
|
932
|
+
- **`example`**: Example value for documentation
|
|
933
|
+
- **`extensions`**: Custom `x-*` fields
|
|
934
|
+
|
|
935
|
+
### Creating SchemaObject
|
|
936
|
+
|
|
937
|
+
Directly from a `Schema[A]`:
|
|
938
|
+
|
|
939
|
+
```scala
|
|
940
|
+
import zio.blocks.openapi._
|
|
941
|
+
import zio.blocks.docs._
|
|
942
|
+
import zio.blocks.chunk._
|
|
943
|
+
import zio.blocks.schema._
|
|
944
|
+
|
|
945
|
+
case class User(id: Int, name: String, email: String)
|
|
946
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
947
|
+
|
|
948
|
+
val userSchema = Schema[User].toOpenAPISchema
|
|
949
|
+
// Returns a SchemaObject with the User type's JSON Schema
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
Or with additional OpenAPI metadata:
|
|
953
|
+
|
|
954
|
+
```scala
|
|
955
|
+
import zio.blocks.openapi._
|
|
956
|
+
import zio.blocks.docs._
|
|
957
|
+
import zio.blocks.chunk._
|
|
958
|
+
import zio.blocks.schema._
|
|
959
|
+
import zio.blocks.schema.json._
|
|
960
|
+
|
|
961
|
+
case class User(id: Int, name: String, email: String)
|
|
962
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
963
|
+
|
|
964
|
+
val enrichedSchema = SchemaObject(
|
|
965
|
+
jsonSchema = Schema[User].toJsonSchema.toJson,
|
|
966
|
+
discriminator = None,
|
|
967
|
+
xml = Some(XML(
|
|
968
|
+
name = Some("user"),
|
|
969
|
+
namespace = None,
|
|
970
|
+
prefix = None,
|
|
971
|
+
attribute = false,
|
|
972
|
+
wrapped = false
|
|
973
|
+
)),
|
|
974
|
+
example = Some(Json.Object(
|
|
975
|
+
"id" -> Json.Number(1),
|
|
976
|
+
"name" -> Json.String("John")
|
|
977
|
+
)),
|
|
978
|
+
extensions = ChunkMap(
|
|
979
|
+
"x-generated" -> Json.String("true"),
|
|
980
|
+
"x-version" -> Json.String("1.0.0")
|
|
981
|
+
)
|
|
982
|
+
)
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
### Converting Schemas to SchemaObject
|
|
986
|
+
|
|
987
|
+
Use the `SchemaOps` extension methods on any `Schema[A]`:
|
|
988
|
+
|
|
989
|
+
```scala
|
|
990
|
+
import zio.blocks.openapi._
|
|
991
|
+
import zio.blocks.docs._
|
|
992
|
+
import zio.blocks.chunk._
|
|
993
|
+
import zio.blocks.schema._
|
|
994
|
+
|
|
995
|
+
case class User(id: Int, name: String, email: String)
|
|
996
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
997
|
+
|
|
998
|
+
val schemaObj: SchemaObject = Schema[User].toOpenAPISchema
|
|
999
|
+
val (ref, component) = Schema[User].toRefSchema
|
|
1000
|
+
// ref = ReferenceOr.Ref pointing to #/components/schemas/User
|
|
1001
|
+
// component = ("User", SchemaObject(...))
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
---
|
|
1005
|
+
|
|
1006
|
+
## ReferenceOr
|
|
1007
|
+
|
|
1008
|
+
`ReferenceOr[A]` is a sealed trait representing the OpenAPI pattern of choosing between a `$ref` and an inline value.
|
|
1009
|
+
|
|
1010
|
+
### Definition
|
|
1011
|
+
|
|
1012
|
+
Two cases:
|
|
1013
|
+
- **`ReferenceOr.Ref`**: Points to a definition at `#/components/<type>/<name>`
|
|
1014
|
+
- **`ReferenceOr.Value`**: Inline definition without reference
|
|
1015
|
+
|
|
1016
|
+
### Using ReferenceOr
|
|
1017
|
+
|
|
1018
|
+
Prefer `Ref` for reusable components:
|
|
1019
|
+
|
|
1020
|
+
```scala
|
|
1021
|
+
import zio.blocks.openapi._
|
|
1022
|
+
import zio.blocks.docs._
|
|
1023
|
+
import zio.blocks.chunk._
|
|
1024
|
+
import zio.blocks.schema._
|
|
1025
|
+
|
|
1026
|
+
val userRef = ReferenceOr.Ref(Reference(`$ref` = "#/components/schemas/User"))
|
|
1027
|
+
|
|
1028
|
+
val responseRef = ReferenceOr.Ref(Reference(
|
|
1029
|
+
`$ref` = "#/components/responses/NotFound"
|
|
1030
|
+
))
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
Use `Value` for inline, one-off definitions:
|
|
1034
|
+
|
|
1035
|
+
```scala
|
|
1036
|
+
import zio.blocks.openapi._
|
|
1037
|
+
import zio.blocks.docs._
|
|
1038
|
+
import zio.blocks.chunk._
|
|
1039
|
+
import zio.blocks.schema._
|
|
1040
|
+
|
|
1041
|
+
case class User(id: Int, name: String, email: String)
|
|
1042
|
+
object User { implicit val schema: Schema[User] = Schema.derived }
|
|
1043
|
+
|
|
1044
|
+
val inlineUser = ReferenceOr.Value(Schema[User].toOpenAPISchema)
|
|
1045
|
+
|
|
1046
|
+
val inlineError = ReferenceOr.Value(Response(
|
|
1047
|
+
description = md"Quick error",
|
|
1048
|
+
content = ChunkMap()
|
|
1049
|
+
))
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
### Pattern Matching on ReferenceOr
|
|
1053
|
+
|
|
1054
|
+
```scala
|
|
1055
|
+
import zio.blocks.openapi._
|
|
1056
|
+
import zio.blocks.docs._
|
|
1057
|
+
import zio.blocks.chunk._
|
|
1058
|
+
import zio.blocks.schema._
|
|
1059
|
+
|
|
1060
|
+
def describeRef[A](ref: ReferenceOr[A]): String = ref match {
|
|
1061
|
+
case ReferenceOr.Ref(r) => s"Reference to ${r.`$ref`}"
|
|
1062
|
+
case ReferenceOr.Value(_) => "Inline value"
|
|
1063
|
+
}
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
---
|
|
1067
|
+
|
|
1068
|
+
## SecurityScheme
|
|
1069
|
+
|
|
1070
|
+
`SecurityScheme` is a sealed trait representing different authentication methods. Variants include API Key, HTTP Basic/Bearer, OAuth 2.0, OpenID Connect, and Mutual TLS.
|
|
1071
|
+
|
|
1072
|
+
### Definition
|
|
1073
|
+
|
|
1074
|
+
Sealed trait variants:
|
|
1075
|
+
- **`APIKey`**: API key in header, query, or cookie
|
|
1076
|
+
- **`HTTP`**: HTTP authentication (Basic, Bearer, etc.)
|
|
1077
|
+
- **`OAuth2`**: OAuth 2.0 authorization flows
|
|
1078
|
+
- **`OpenIdConnect`**: OpenID Connect discovery
|
|
1079
|
+
- **`MutualTLS`**: Mutual TLS certificate-based
|
|
1080
|
+
|
|
1081
|
+
### Creating Security Schemes
|
|
1082
|
+
|
|
1083
|
+
API Key authentication:
|
|
1084
|
+
|
|
1085
|
+
```scala
|
|
1086
|
+
import zio.blocks.openapi._
|
|
1087
|
+
import zio.blocks.docs._
|
|
1088
|
+
import zio.blocks.chunk._
|
|
1089
|
+
import zio.blocks.schema._
|
|
1090
|
+
|
|
1091
|
+
val apiKeySecurity = SecurityScheme.APIKey(
|
|
1092
|
+
name = "X-API-Key",
|
|
1093
|
+
in = APIKeyLocation.Header,
|
|
1094
|
+
description = Some(md"API key required in header")
|
|
1095
|
+
)
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
HTTP Bearer token:
|
|
1099
|
+
|
|
1100
|
+
```scala
|
|
1101
|
+
import zio.blocks.openapi._
|
|
1102
|
+
import zio.blocks.docs._
|
|
1103
|
+
import zio.blocks.chunk._
|
|
1104
|
+
import zio.blocks.schema._
|
|
1105
|
+
|
|
1106
|
+
val bearerSecurity = SecurityScheme.HTTP(
|
|
1107
|
+
scheme = "bearer",
|
|
1108
|
+
bearerFormat = Some("JWT"),
|
|
1109
|
+
description = Some(md"JWT bearer token")
|
|
1110
|
+
)
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
OAuth 2.0:
|
|
1114
|
+
|
|
1115
|
+
```scala
|
|
1116
|
+
import zio.blocks.openapi._
|
|
1117
|
+
import zio.blocks.docs._
|
|
1118
|
+
import zio.blocks.chunk._
|
|
1119
|
+
import zio.blocks.schema._
|
|
1120
|
+
|
|
1121
|
+
val oauthSecurity = SecurityScheme.OAuth2(
|
|
1122
|
+
flows = OAuthFlows(
|
|
1123
|
+
authorizationCode = Some(OAuthFlow(
|
|
1124
|
+
authorizationUrl = Some("https://example.com/oauth/authorize"),
|
|
1125
|
+
tokenUrl = Some("https://example.com/oauth/token"),
|
|
1126
|
+
scopes = ChunkMap(
|
|
1127
|
+
"read:users" -> "Read user data",
|
|
1128
|
+
"write:users" -> "Modify user data"
|
|
1129
|
+
)
|
|
1130
|
+
))
|
|
1131
|
+
),
|
|
1132
|
+
description = Some(md"OAuth 2.0 authorization")
|
|
1133
|
+
)
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
:::note
|
|
1137
|
+
The `OAuthFlows` type supports multiple flow types: `implicit`, `password`, `clientCredentials`, and `authorizationCode`. Choose the flow that matches your OAuth 2.0 configuration.
|
|
1138
|
+
:::
|
|
1139
|
+
|
|
1140
|
+
OpenID Connect:
|
|
1141
|
+
|
|
1142
|
+
```scala
|
|
1143
|
+
import zio.blocks.openapi._
|
|
1144
|
+
import zio.blocks.docs._
|
|
1145
|
+
import zio.blocks.chunk._
|
|
1146
|
+
import zio.blocks.schema._
|
|
1147
|
+
|
|
1148
|
+
val oidcSecurity = SecurityScheme.OpenIdConnect(
|
|
1149
|
+
openIdConnectUrl = "https://example.com/.well-known/openid-configuration",
|
|
1150
|
+
description = Some(md"OpenID Connect discovery")
|
|
1151
|
+
)
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
---
|
|
1155
|
+
|
|
1156
|
+
## Discriminator
|
|
1157
|
+
|
|
1158
|
+
`Discriminator` specifies how to distinguish between different variants in a polymorphic schema (using `oneOf` or `anyOf`).
|
|
1159
|
+
|
|
1160
|
+
### Definition
|
|
1161
|
+
|
|
1162
|
+
Key fields:
|
|
1163
|
+
- **`propertyName`**: Field name used to discriminate (e.g., `"type"`, `"kind"`)
|
|
1164
|
+
- **`mapping`**: Optional explicit mapping of discriminator values to schema references
|
|
1165
|
+
|
|
1166
|
+
### Creating a Discriminator
|
|
1167
|
+
|
|
1168
|
+
Simple discriminator by property name:
|
|
1169
|
+
|
|
1170
|
+
```scala
|
|
1171
|
+
import zio.blocks.openapi._
|
|
1172
|
+
import zio.blocks.docs._
|
|
1173
|
+
import zio.blocks.chunk._
|
|
1174
|
+
import zio.blocks.schema._
|
|
1175
|
+
|
|
1176
|
+
val discriminator = Discriminator(propertyName = "type")
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
With explicit value-to-schema mapping:
|
|
1180
|
+
|
|
1181
|
+
```scala
|
|
1182
|
+
import zio.blocks.openapi._
|
|
1183
|
+
import zio.blocks.docs._
|
|
1184
|
+
import zio.blocks.chunk._
|
|
1185
|
+
import zio.blocks.schema._
|
|
1186
|
+
|
|
1187
|
+
val mappedDiscriminator = Discriminator(
|
|
1188
|
+
propertyName = "kind",
|
|
1189
|
+
mapping = ChunkMap(
|
|
1190
|
+
"user" -> "#/components/schemas/User",
|
|
1191
|
+
"admin" -> "#/components/schemas/Admin",
|
|
1192
|
+
"guest" -> "#/components/schemas/Guest"
|
|
1193
|
+
)
|
|
1194
|
+
)
|
|
1195
|
+
```
|
|
1196
|
+
|
|
1197
|
+
---
|
|
1198
|
+
|
|
1199
|
+
## Server & ServerVariable
|
|
1200
|
+
|
|
1201
|
+
`Server` specifies base URLs and server-specific variables. `ServerVariable` allows parameterization of server URLs.
|
|
1202
|
+
|
|
1203
|
+
### Definition
|
|
1204
|
+
|
|
1205
|
+
`Server` contains:
|
|
1206
|
+
- **`url`**: Server URL (may contain variable placeholders like `{base_path}`)
|
|
1207
|
+
- **`description`**: Optional description
|
|
1208
|
+
- **`variables`**: Map of variable names to `ServerVariable`
|
|
1209
|
+
|
|
1210
|
+
`ServerVariable` contains:
|
|
1211
|
+
- **`enum`**: List of allowed values
|
|
1212
|
+
- **`default`**: Default value
|
|
1213
|
+
- **`description`**: Description of the variable
|
|
1214
|
+
|
|
1215
|
+
### Creating Servers
|
|
1216
|
+
|
|
1217
|
+
Single static server:
|
|
1218
|
+
|
|
1219
|
+
```scala
|
|
1220
|
+
import zio.blocks.openapi._
|
|
1221
|
+
import zio.blocks.docs._
|
|
1222
|
+
import zio.blocks.chunk._
|
|
1223
|
+
import zio.blocks.schema._
|
|
1224
|
+
|
|
1225
|
+
val server = Server(
|
|
1226
|
+
url = "https://api.example.com",
|
|
1227
|
+
description = Some(md"Production API")
|
|
1228
|
+
)
|
|
1229
|
+
```
|
|
1230
|
+
|
|
1231
|
+
Server with variables:
|
|
1232
|
+
|
|
1233
|
+
```scala
|
|
1234
|
+
import zio.blocks.openapi._
|
|
1235
|
+
import zio.blocks.docs._
|
|
1236
|
+
import zio.blocks.chunk._
|
|
1237
|
+
import zio.blocks.schema._
|
|
1238
|
+
|
|
1239
|
+
val variableServer = Server(
|
|
1240
|
+
url = "https://{host}:{port}/{basePath}",
|
|
1241
|
+
description = Some(md"Development API with variables"),
|
|
1242
|
+
variables = ChunkMap(
|
|
1243
|
+
"host" -> ServerVariable(
|
|
1244
|
+
default = "localhost",
|
|
1245
|
+
`enum` = Chunk("localhost", "staging.example.com", "api.example.com"),
|
|
1246
|
+
description = Some(md"API host")
|
|
1247
|
+
),
|
|
1248
|
+
"port" -> ServerVariable(
|
|
1249
|
+
default = "8080",
|
|
1250
|
+
`enum` = Chunk("8080", "443"),
|
|
1251
|
+
description = Some(md"Port number")
|
|
1252
|
+
),
|
|
1253
|
+
"basePath" -> ServerVariable(
|
|
1254
|
+
default = "v1",
|
|
1255
|
+
`enum` = Chunk("v1", "v2"),
|
|
1256
|
+
description = Some(md"API version path")
|
|
1257
|
+
)
|
|
1258
|
+
)
|
|
1259
|
+
)
|
|
1260
|
+
```
|
|
1261
|
+
|
|
1262
|
+
:::note
|
|
1263
|
+
`ServerVariable` contains enumerable values for each variable. The field is named to avoid the reserved `enum` keyword in Scala.
|
|
1264
|
+
:::
|
|
1265
|
+
|
|
1266
|
+
---
|
|
1267
|
+
|
|
1268
|
+
## Tag
|
|
1269
|
+
|
|
1270
|
+
`Tag` groups related operations under a heading in generated documentation.
|
|
1271
|
+
|
|
1272
|
+
### Definition
|
|
1273
|
+
|
|
1274
|
+
Key fields:
|
|
1275
|
+
- **`name`**: Tag identifier (e.g., `"users"`, `"products"`)
|
|
1276
|
+
- **`description`**: Markdown description of the tag
|
|
1277
|
+
- **`externalDocs`**: Link to external documentation
|
|
1278
|
+
|
|
1279
|
+
### Creating Tags
|
|
1280
|
+
|
|
1281
|
+
```scala
|
|
1282
|
+
import zio.blocks.openapi._
|
|
1283
|
+
import zio.blocks.docs._
|
|
1284
|
+
import zio.blocks.chunk._
|
|
1285
|
+
import zio.blocks.schema._
|
|
1286
|
+
|
|
1287
|
+
val userTag = Tag(
|
|
1288
|
+
name = "users",
|
|
1289
|
+
description = Some(md"User management operations")
|
|
1290
|
+
)
|
|
1291
|
+
|
|
1292
|
+
val productsTag = Tag(
|
|
1293
|
+
name = "products",
|
|
1294
|
+
description = Some(md"Product catalog operations"),
|
|
1295
|
+
externalDocs = Some(ExternalDocumentation(
|
|
1296
|
+
url = "https://docs.example.com/products",
|
|
1297
|
+
description = Some(md"Full product API documentation")
|
|
1298
|
+
))
|
|
1299
|
+
)
|
|
1300
|
+
```
|
|
1301
|
+
|
|
1302
|
+
---
|
|
1303
|
+
|
|
1304
|
+
## Common Extension Fields
|
|
1305
|
+
|
|
1306
|
+
All types support custom `x-*` extension fields for vendor-specific metadata. These extensions are preserved during encoding/decoding:
|
|
1307
|
+
|
|
1308
|
+
```scala
|
|
1309
|
+
import zio.blocks.openapi._
|
|
1310
|
+
import zio.blocks.docs._
|
|
1311
|
+
import zio.blocks.chunk._
|
|
1312
|
+
import zio.blocks.schema._
|
|
1313
|
+
import zio.blocks.schema.json._
|
|
1314
|
+
|
|
1315
|
+
val operationWithExtensions = Operation(
|
|
1316
|
+
summary = Some(md"Get user"),
|
|
1317
|
+
responses = Responses(ChunkMap(
|
|
1318
|
+
"200" -> ReferenceOr.Value(Response(
|
|
1319
|
+
description = md"User found",
|
|
1320
|
+
content = ChunkMap()
|
|
1321
|
+
))
|
|
1322
|
+
)),
|
|
1323
|
+
extensions = ChunkMap(
|
|
1324
|
+
"x-internal" -> Json.Boolean(true),
|
|
1325
|
+
"x-rate-limit" -> Json.Number(100),
|
|
1326
|
+
"x-deprecated-at" -> Json.String("2024-01-01")
|
|
1327
|
+
)
|
|
1328
|
+
)
|
|
1329
|
+
```
|
|
1330
|
+
|
|
1331
|
+
---
|
|
1332
|
+
|
|
1333
|
+
## Round-Tripping with Schema
|
|
1334
|
+
|
|
1335
|
+
All OpenAPI types have `Schema.derived` instances, enabling serialization through `DynamicValue`:
|
|
1336
|
+
|
|
1337
|
+
```scala
|
|
1338
|
+
import zio.blocks.openapi._
|
|
1339
|
+
import zio.blocks.docs._
|
|
1340
|
+
import zio.blocks.chunk._
|
|
1341
|
+
import zio.blocks.schema._
|
|
1342
|
+
|
|
1343
|
+
val myApi = OpenAPI(openapi = "3.1.0", info = Info(title = "My API", version = "1.0.0"))
|
|
1344
|
+
val openAPISchema = Schema[OpenAPI]
|
|
1345
|
+
|
|
1346
|
+
val apiDynamic = openAPISchema.toDynamicValue(myApi)
|
|
1347
|
+
|
|
1348
|
+
val apiRestored = openAPISchema.fromDynamicValue(apiDynamic)
|
|
1349
|
+
```
|
|
1350
|
+
|
|
1351
|
+
This enables integration with other ZIO Blocks modules that work with `DynamicValue`.
|