Skip to content

Commit 08f5ad0

Browse files
committed
docs/concept: how CUE works with OpenAPI
This adds a concept guide demonstrating how CUE works with OpenAPI. Much of the comparative discussion from https://cuelang.org/docs/concept/schema-definition-use-case/#json-schema--openapi is omitted, keeping the document focused on the pragmatic and possible. cue-lang/cue#3133 was opened as part of this change, which asks the project to support full round-tripping from OpenAPI to CUE and back again; or to decide and document which OpenAPI elements aren't supported. For cue-lang/docs-and-content#72 Preview-Path: /docs/concept/how-cue-works-with-openapi/ Signed-off-by: Jonathan Matthews <[email protected]> Change-Id: I9f9968d381d516f0e189616871919ddb063f11d3 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cuelang.org/+/1192024 Reviewed-by: Daniel Martí <[email protected]> TryBot-Result: CUEcueckoo <[email protected]> Reviewed-by: Paul Jolly <[email protected]>
1 parent 24ebac4 commit 08f5ad0

File tree

4 files changed

+559
-0
lines changed

4 files changed

+559
-0
lines changed
+236
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
---
2+
title: How CUE works with OpenAPI
3+
tags:
4+
- encodings
5+
- cue command
6+
authors:
7+
- jpluscplusm
8+
- myitcv
9+
toc_hide: true
10+
---
11+
12+
{{{with _script_ "en" "HIDDEN_ set up caches"}}}
13+
export GOMODCACHE=/caches/gomodcache
14+
export GOCACHE=/caches/gobuild
15+
{{{end}}}
16+
17+
CUE works with the
18+
[OpenAPI 3.0.0 standard](https://github.com/OAI/OpenAPI-Specification/tree/3.0.0)
19+
for the description of REST APIs by supporting the use and import of OpenAPI
20+
`components.schemas` data schemas, and the export of CUE definitions into the
21+
same namespace.
22+
23+
CUE is usually more clear and concise than the equivalent OpenAPI. However,
24+
given that they meet different needs for different types of users, CUE's
25+
ability to round-trip between CUE and OpenAPI's data schema subset acts as a
26+
useful bridge between their two worlds.
27+
28+
## Reading and writing OpenAPI with the `cue` command
29+
30+
The `cue` command can convert CUE schemas into OpenAPI's `components.schemas`.
31+
CUE files can be converted into OpenAPI so long as they only specify
32+
definitions and metadata (`info`, `$version`, etc) at their top-level.
33+
34+
Let's start with a trivial CUE schema that we want to convert to OpenAPI:
35+
36+
{{{with upload "en" "schema.cue"}}}
37+
-- schema.cue --
38+
// A schema for the pet API.
39+
package api
40+
41+
$version: "v1.2.3"
42+
// A Pet is a pet that we handle.
43+
#Pet: {
44+
// A pet has a name.
45+
name!: string
46+
// We only handle certain kinds of pets.
47+
kind!: #Kind
48+
// Centenarian pets are not handled.
49+
age?: uint & <100 // TODO: increase limit if the tortoise proposal is accepted.
50+
...
51+
}
52+
53+
// Kind encodes the different pets we handle.
54+
#Kind: "cat" | "dog" | "goldfish"
55+
{{{end}}}
56+
57+
The [`cue def`]({{< relref "docs/reference/cli/cue-def" >}}) command normalizes
58+
the schema, and optionally converts it into another format:
59+
60+
{{{with script "en" "cue def schema.cue"}}}
61+
cue def schema.cue -o api.pet.yaml --out openapi+yaml
62+
{{{end}}}
63+
64+
{{{with _script_ "en" "HIDDEN_ move api.pet.yaml sideways"}}}
65+
mv api.pet.yaml .api.pet.yaml
66+
{{{end}}}
67+
68+
The OpenAPI `info.title` field can be extracted from the top-level CUE comment,
69+
or can be specified directly.
70+
The same goes for OpenAPI's `info.version` field, which is extracted from CUE's
71+
top-level `$version` field if not specified directly.
72+
73+
Be aware of just how *long* an equivalent OpenAPI definition can become - not
74+
all formats possess CUE's succinctness and economy of expression!
75+
The `cue def` command creates this file:
76+
77+
{{{with upload "en" "api.pet.yaml"}}}
78+
-- api.pet.yaml --
79+
openapi: 3.0.0
80+
info:
81+
title: A schema for the pet API.
82+
version: v1.2.3
83+
paths: {}
84+
components:
85+
schemas:
86+
Kind:
87+
description: Kind encodes the different pets we handle.
88+
type: string
89+
enum:
90+
- cat
91+
- dog
92+
- goldfish
93+
Pet:
94+
description: A Pet is a pet that we handle.
95+
type: object
96+
required:
97+
- name
98+
- kind
99+
properties:
100+
name:
101+
description: A pet has a name.
102+
type: string
103+
kind:
104+
$ref: '#/components/schemas/Kind'
105+
age:
106+
description: Centenarian pets are not handled.
107+
type: integer
108+
minimum: 0
109+
maximum: 100
110+
exclusiveMaximum: true
111+
{{{end}}}
112+
113+
{{{with _script_ "en" "HIDDEN_ diff api.pet.yaml"}}}
114+
diff -wu api.pet.yaml .api.pet.yaml
115+
rm .api.pet.yaml # tidy up
116+
{{{end}}}
117+
118+
Because CUE is more expressive than OpenAPI, it isn't possible to generate a
119+
precise OpenAPI equivalent for *every* CUE schema. CUE does the best conversion
120+
it can, limited by what OpenAPI's data schemas can represent.
121+
122+
[`cue import`]({{< relref "docs/reference/cli/cue-import" >}}) can perform the
123+
reverse operation, taking the OpenAPI definition emitted above and converting
124+
it back to CUE:
125+
126+
{{{with script "en" "import openapi yaml"}}}
127+
cue import -p api api.pet.yaml
128+
{{{end}}}
129+
130+
{{{with _script_ "en" "HIDDEN_ move api.pet.cue sideways"}}}
131+
mv api.pet.cue .api.pet.cue
132+
{{{end}}}
133+
134+
This produces the following CUE, which is as close to the original `schema.cue`
135+
as OpenAPI's capabilities currently permit:
136+
137+
{{{with upload "en" "api.pet.cue"}}}
138+
-- api.pet.cue --
139+
// A schema for the pet API.
140+
package api
141+
142+
info: {
143+
title: *"A schema for the pet API." | string
144+
version: *"v1.2.3" | string
145+
}
146+
// Kind encodes the different pets we handle.
147+
#Kind: "cat" | "dog" | "goldfish"
148+
149+
// A Pet is a pet that we handle.
150+
#Pet: {
151+
// A pet has a name.
152+
name: string
153+
kind: #Kind
154+
155+
// Centenarian pets are not handled.
156+
age?: int & >=0 & <100
157+
...
158+
}
159+
{{{end}}}
160+
161+
{{{with _script_ "en" "HIDDEN_ diff api.pet.cue"}}}
162+
diff -wu api.pet.cue .api.pet.cue
163+
rm .api.pet.cue # tidy up
164+
{{{end}}}
165+
166+
## Using CUE's Go API
167+
168+
CUE can also generate OpenAPI through its Go API.
169+
170+
Generating an OpenAPI definition can be as simple as this:
171+
172+
{{{with _script_ "en" "go mod init"}}}
173+
go mod init mod.example
174+
{{{end}}}
175+
176+
{{{with upload "en" "go emit openapi main"}}}
177+
-- main.go --
178+
package main
179+
180+
import (
181+
"bytes"
182+
"encoding/json"
183+
"fmt"
184+
"log"
185+
186+
"cuelang.org/go/cue/cuecontext"
187+
"cuelang.org/go/cue/load"
188+
"cuelang.org/go/encoding/openapi"
189+
)
190+
191+
func main() {
192+
ctx := cuecontext.New()
193+
insts := load.Instances([]string{"schema.cue"}, nil)
194+
v := ctx.BuildInstance(insts[0])
195+
196+
// Generate the OpenAPI schema from the value loaded from schema.cue
197+
b, err := openapi.Gen(v, nil)
198+
if err != nil {
199+
log.Fatal(err)
200+
}
201+
202+
// Render as indented JSON
203+
var out bytes.Buffer
204+
if err := json.Indent(&out, b, "", " "); err != nil {
205+
log.Fatal(err)
206+
}
207+
fmt.Printf("%s\n", out.Bytes())
208+
}
209+
{{{end}}}
210+
211+
{{{with _script_ "en" "go mod tidy"}}}
212+
#ellipsis 0
213+
go get cuelang.org/go@${CUELANG_CUE_LATEST}
214+
#ellipsis 0
215+
go mod tidy
216+
{{{end}}}
217+
218+
{{{with script "en" "go run"}}}
219+
#ellipsis 10
220+
go run .
221+
{{{end}}}
222+
223+
The [`encoding/openapi`](https://pkg.go.dev/cuelang.org/go/encoding/openapi)
224+
package provides options to make a definition self-contained, to filter
225+
constraints, and so on. The *expanding references* option enables the
226+
"Structural OpenAPI" form required by CRDs targeting Kubernetes version 1.15
227+
and later.
228+
229+
## Related content
230+
231+
- CUE supports OpenAPI's `components.schemas` namespace, and metadata such as the `info` field --
232+
{{<issue 3133/>}} tracks the support of other namespaces defined by the OpenAPI standard
233+
- The [OpenAPI 3.0.0 specification](https://github.com/OAI/OpenAPI-Specification/tree/3.0.0)
234+
- {{< linkto/related/reference "cli/cue-def" >}}
235+
- {{< linkto/related/reference "cli/cue-import" >}}
236+
- The [`encoding/openapi`](https://pkg.go.dev/cuelang.org/go/encoding/openapi) package
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package site
2+
{
3+
content: {
4+
docs: {
5+
concept: {
6+
"how-cue-works-with-openapi": {
7+
page: {
8+
cache: {
9+
upload: {
10+
"schema.cue": "IhWYgY2d4XJ14dVZmRAuFEOvvNEGp0VQ4d4KmUiGPOI="
11+
"api.pet.yaml": "a59sa6kiZ8MIhIuFnjuABOwBKtJb6/0GvCczVlyVRrc="
12+
"api.pet.cue": "pfphGZPbu+Bc4/4G9UHYevOhe+M796iWteICO8ORgbg="
13+
"go emit openapi main": "iJ3M385hdYxCdbNbUSo2fUp8UxBgDb0Kra0vI/FTaFE="
14+
}
15+
multi_step: {
16+
hash: "3AL269A7I4O2UGG9L7R6CMJ7NQ924KQM0LG8COQNQG9S8BUJ8VEG===="
17+
scriptHash: "5JNL3Q47AL0NUVFJA6OLQ28R6PVKQCQTRNKUO3BRGGFHPLF77K4G===="
18+
steps: [{
19+
doc: ""
20+
cmd: "export GOMODCACHE=/caches/gomodcache"
21+
exitCode: 0
22+
output: ""
23+
}, {
24+
doc: ""
25+
cmd: "export GOCACHE=/caches/gobuild"
26+
exitCode: 0
27+
output: ""
28+
}, {
29+
doc: ""
30+
cmd: "cue def schema.cue -o api.pet.yaml --out openapi+yaml"
31+
exitCode: 0
32+
output: ""
33+
}, {
34+
doc: ""
35+
cmd: "mv api.pet.yaml .api.pet.yaml"
36+
exitCode: 0
37+
output: ""
38+
}, {
39+
doc: ""
40+
cmd: "diff -wu api.pet.yaml .api.pet.yaml"
41+
exitCode: 0
42+
output: ""
43+
}, {
44+
doc: ""
45+
cmd: "rm .api.pet.yaml # tidy up"
46+
exitCode: 0
47+
output: ""
48+
}, {
49+
doc: ""
50+
cmd: "cue import -p api api.pet.yaml"
51+
exitCode: 0
52+
output: ""
53+
}, {
54+
doc: ""
55+
cmd: "mv api.pet.cue .api.pet.cue"
56+
exitCode: 0
57+
output: ""
58+
}, {
59+
doc: ""
60+
cmd: "diff -wu api.pet.cue .api.pet.cue"
61+
exitCode: 0
62+
output: ""
63+
}, {
64+
doc: ""
65+
cmd: "rm .api.pet.cue # tidy up"
66+
exitCode: 0
67+
output: ""
68+
}, {
69+
doc: ""
70+
cmd: "go mod init mod.example"
71+
exitCode: 0
72+
output: """
73+
go: creating new go.mod: module mod.example
74+
75+
"""
76+
}, {
77+
doc: "#ellipsis 0"
78+
cmd: "go get cuelang.org/[email protected]"
79+
exitCode: 0
80+
output: """
81+
...
82+
83+
"""
84+
}, {
85+
doc: "#ellipsis 0"
86+
cmd: "go mod tidy"
87+
exitCode: 0
88+
output: """
89+
...
90+
91+
"""
92+
}, {
93+
doc: "#ellipsis 10"
94+
cmd: "go run ."
95+
exitCode: 0
96+
output: """
97+
{
98+
"openapi": "3.0.0",
99+
"info": {
100+
"title": "A schema for the pet API.",
101+
"version": "v1.2.3"
102+
},
103+
"paths": {},
104+
"components": {
105+
"schemas": {
106+
"Kind": {
107+
...
108+
109+
"""
110+
}]
111+
}
112+
}
113+
}
114+
}
115+
}
116+
}
117+
}
118+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package site
2+
3+
content: docs: concept: "how-cue-works-with-openapi": page: _

0 commit comments

Comments
 (0)