Skip to content

Commit 0e291e2

Browse files
committed
Add support for if/then/else conditional schemas
1 parent fd52447 commit 0e291e2

30 files changed

Lines changed: 1704 additions & 7 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ on the real value-add for your organization.
1616
- **Smart pruning** - Automatically removes unused types by default (configurable)
1717

1818
### Type System
19-
- **Union types** - Full support for `oneOf`, `anyOf`, and `allOf` with intelligent type merging and `runtime.Either[A, B]` for two-element unions
19+
- **Union types** - Full support for `oneOf`, `anyOf`, `allOf`, and `if`/`then`/`else` with intelligent type merging, `runtime.Either[A, B]` for two-element unions, and `runtime.Conditional[T, E]` for conditional schemas
2020
- **Additional properties** - Handle dynamic fields with `map[string]interface{}` or custom types
2121
- **Validation** - Built-in validation using [go-playground/validator](https://github.com/go-playground/validator) with `Validate()` methods on generated types
2222
- **Custom extensions** - for fine-grained control over code generation

docs/union-types.md

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# Union Types
22

3-
Union types in OpenAPI allow schemas to accept multiple different types or structures. The `oapi-codegen` generator handles `allOf`, `anyOf`, and `oneOf` with intelligent type generation based on the number and nature of the variants.
3+
Union types in OpenAPI allow schemas to accept multiple different types or structures. The `oapi-codegen` generator handles `allOf`, `anyOf`, `oneOf`, and `if`/`then`/`else` with intelligent type generation based on the number and nature of the variants.
44

55
## Overview
66

77
- **`allOf`**: Merges all schemas into a single struct with all fields combined
88
- **`anyOf`**: Can match any of the specified schemas
99
- **`oneOf`**: Must match exactly one of the specified schemas
10+
- **`if`/`then`/`else`**: Conditional schemas (OpenAPI 3.1) - uses `runtime.Conditional[T, E]`
1011

1112
The generator applies smart optimizations based on the union structure:
1213

@@ -167,3 +168,83 @@ The `runtime.JSONMerge` function combines all the JSON parts into a single objec
167168

168169
[View complex union example](https://github.com/doordash-oss/oapi-codegen-dd/tree/main/examples/union/allof-anyof-oneof/){:target="_blank"}
169170

171+
---
172+
173+
## Conditional Schemas (`if`/`then`/`else`)
174+
175+
OpenAPI 3.1 supports JSON Schema conditional keywords. The generator
176+
treats `then` and `else` as structural branches. The `if` schema is
177+
ignored - it's a validation predicate (e.g. "if kind equals typeA")
178+
with no structural properties. `if` is also a reserved keyword in Go.
179+
180+
### Both Branches - `runtime.Conditional`
181+
182+
When both `then` and `else` are present, the generator creates a
183+
`runtime.Conditional[T, E]` wrapper with named variant types. Unlike
184+
`runtime.Either` (`.A`/`.B`), `Conditional` uses `.Then`/`.Else`
185+
fields and `.IsThen()`/`.IsElse()` methods.
186+
187+
**OpenAPI Spec:**
188+
189+
```yaml
190+
--8<-- "ifthenelse/basic/api.yaml:20:42"
191+
```
192+
193+
**Generated Go Code:**
194+
195+
```go
196+
--8<-- "ifthenelse/basic/gen.go:16:19"
197+
```
198+
199+
The variant types use `_Then` and `_Else` suffixes:
200+
201+
```go
202+
--8<-- "ifthenelse/basic/gen.go:86:98"
203+
```
204+
205+
Usage:
206+
207+
```go
208+
if resource.Resource_IfThenElse.IsThen() {
209+
fmt.Println(resource.Resource_IfThenElse.Then.FieldA)
210+
}
211+
if resource.Resource_IfThenElse.IsElse() {
212+
fmt.Println(resource.Resource_IfThenElse.Else.FieldB)
213+
}
214+
```
215+
216+
[View basic example](https://github.com/doordash-oss/oapi-codegen-dd/tree/main/examples/ifthenelse/basic/){:target="_blank"}
217+
218+
### Single Branch - Flat Merge
219+
220+
When only `then` or only `else` is present, the branch properties
221+
are flat-merged into the parent struct. No wrapper type is created.
222+
223+
**OpenAPI Spec:**
224+
225+
```yaml
226+
--8<-- "ifthenelse/then-only/api.yaml:20:35"
227+
```
228+
229+
**Generated Go Code:**
230+
231+
```go
232+
--8<-- "ifthenelse/then-only/gen.go:12:16"
233+
```
234+
235+
The `timeout` and `retries` fields from the `then` branch appear
236+
directly on the `Config` struct.
237+
238+
[View then-only example](https://github.com/doordash-oss/oapi-codegen-dd/tree/main/examples/ifthenelse/then-only/){:target="_blank"}
239+
240+
### With `$ref` Branches
241+
242+
When `then`/`else` reference component schemas, the generated
243+
`Conditional` uses the referenced type names directly:
244+
245+
```go
246+
--8<-- "ifthenelse/with-refs/gen.go:98:100"
247+
```
248+
249+
[View with-refs example](https://github.com/doordash-oss/oapi-codegen-dd/tree/main/examples/ifthenelse/with-refs/){:target="_blank"}
250+

examples/ifthenelse/basic/api.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
openapi: "3.1.0"
2+
info:
3+
version: 1.0.0
4+
title: Basic if/then/else
5+
description: Both then and else branches produce a union (Either) type
6+
paths:
7+
/resources:
8+
get:
9+
operationId: listResources
10+
responses:
11+
'200':
12+
description: OK
13+
content:
14+
application/json:
15+
schema:
16+
$ref: '#/components/schemas/Resource'
17+
18+
components:
19+
schemas:
20+
Resource:
21+
type: object
22+
properties:
23+
kind:
24+
type: string
25+
if:
26+
properties:
27+
kind:
28+
const: "typeA"
29+
then:
30+
type: object
31+
properties:
32+
fieldA:
33+
type: string
34+
valueA:
35+
type: integer
36+
else:
37+
type: object
38+
properties:
39+
fieldB:
40+
type: boolean
41+
valueB:
42+
type: number

examples/ifthenelse/basic/cfg.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# yaml-language-server: $schema=../../configuration-schema.json
2+
package: gen
3+
skip-prune: true

examples/ifthenelse/basic/gen.go

Lines changed: 119 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package gen
2+
3+
//go:generate go run github.com/doordash-oss/oapi-codegen-dd/v3/cmd/oapi-codegen -config cfg.yaml api.yaml
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
openapi: "3.1.0"
2+
info:
3+
version: 1.0.0
4+
title: if/then/else inside allOf
5+
description: Conditional branches nested inside an allOf composition
6+
paths:
7+
/resources:
8+
get:
9+
operationId: listResources
10+
responses:
11+
'200':
12+
description: OK
13+
content:
14+
application/json:
15+
schema:
16+
$ref: '#/components/schemas/SpecialResource'
17+
18+
components:
19+
schemas:
20+
BaseResource:
21+
type: object
22+
properties:
23+
id:
24+
type: string
25+
name:
26+
type: string
27+
28+
SpecialResource:
29+
allOf:
30+
- $ref: '#/components/schemas/BaseResource'
31+
- type: object
32+
properties:
33+
category:
34+
type: string
35+
if:
36+
properties:
37+
category:
38+
const: "premium"
39+
then:
40+
type: object
41+
properties:
42+
premiumFeature:
43+
type: string
44+
else:
45+
type: object
46+
properties:
47+
standardLimit:
48+
type: integer
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# yaml-language-server: $schema=../../configuration-schema.json
2+
package: gen
3+
skip-prune: true

0 commit comments

Comments
 (0)