this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

pkg/encoding/openapi: support marshalSchema builtin for openAPI

This was used in the cmd serve demo of
OCS 2023.

There were some issues with error wrapping in
openapi when interacting with tasks, but these seem
to be resolved. I've added TODOs where this used to
be an issue.

Signed-off-by: Marcel van Lohuizen <mpvl@gmail.com>
Change-Id: I9e135178dabd599e357341e54f7452c8a528948f
Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1221920
Reviewed-by: Roger Peppe <rogpeppe@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>

+1334
+3
encoding/openapi/build.go
··· 183 183 func (b *builder) failf(v cue.Value, format string, args ...interface{}) { 184 184 panic(&openapiError{ 185 185 errors.NewMessagef(format, args...), 186 + v.Err(), 186 187 cue.MakePath(b.ctx.path...), 187 188 v.Pos(), 188 189 }) ··· 692 693 // TODO: extract format from specific type. 693 694 694 695 default: 696 + // TODO: consider // TODO(pkg): wrapping may cause issues in the 697 + // builtin package. Seems fine for now though. 695 698 b.failf(v, "unsupported op %v for object type (%v)", op, v) 696 699 return 697 700 }
+12
encoding/openapi/errors.go
··· 18 18 "cuelang.org/go/cue" 19 19 "cuelang.org/go/cue/errors" 20 20 "cuelang.org/go/cue/token" 21 + "cuelang.org/go/internal/core/adt" 22 + "cuelang.org/go/internal/pkg" 21 23 ) 22 24 23 25 var _ errors.Error = &openapiError{} ··· 25 27 // implements cue/Error 26 28 type openapiError struct { 27 29 errors.Message 30 + err error 28 31 path cue.Path 29 32 pos token.Pos 33 + } 34 + 35 + // Bottom implements [pkg.Bottomer]. By doing so we ensure that logic that 36 + // checks for an incomplete error can do so, even if wrapped in an openapiError. 37 + func (e *openapiError) Bottom() *adt.Bottom { 38 + if x, ok := e.err.(pkg.Bottomer); ok { 39 + return x.Bottom() 40 + } 41 + return nil 30 42 } 31 43 32 44 func (e *openapiError) Position() token.Pos {
+2
encoding/openapi/openapi.go
··· 126 126 v, err = cuejson.Extract(name, b) 127 127 } 128 128 if err != nil { 129 + // TODO(pkg): wrapping may cause issues in the builtin package. Seems 130 + // fine for now though. 129 131 return nil, errors.Wrapf(err, token.NoPos, 130 132 "openapi: could not encode %s", name) 131 133 }
+56
pkg/encoding/openapi/openapi.cue
··· 1 + // Copyright 2023 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package openapi 16 + 17 + // #Config represents options for generating OpenAPI. 18 + #Config: { 19 + // version is fixed to 3.0.0 for now. 20 + version!: "3.0.0" 21 + 22 + info?: #Info 23 + 24 + // selfContained causes all non-expanded external references to be included 25 + // in this document. 26 + selfContained: bool | *false 27 + 28 + // expandReferences replaces references with actual objects when generating 29 + // OpenAPI Schema. It is an error for an CUE value to refer to itself 30 + // if this option is used. 31 + expandReferences: bool | *false 32 + } 33 + 34 + // #Info represents metadata about the API. 35 + #Info: { 36 + title!: string 37 + version!: string 38 + summary?: string 39 + description?: string 40 + termsOfService?: string 41 + contact?: #Contact 42 + license?: #License 43 + } 44 + 45 + // #Contact represents contact information for the exposed API. 46 + #Contact: { 47 + name?: string 48 + url?: string 49 + email?: string 50 + } 51 + 52 + // #License represents license information for the exposed API. 53 + #License: { 54 + name!: string 55 + url?: string 56 + }
+94
pkg/encoding/openapi/openapi.go
··· 1 + // Copyright 2023 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + // Package openapi provides OpenAPI encoding and decoding functionality. 16 + // 17 + // This is an EXPERIMENTAL API. 18 + package openapi 19 + 20 + import ( 21 + "cuelang.org/go/cue" 22 + "cuelang.org/go/encoding/openapi" 23 + "cuelang.org/go/internal/core/adt" 24 + "cuelang.org/go/internal/pkg" 25 + "cuelang.org/go/internal/value" 26 + ) 27 + 28 + var ( 29 + selfContainedPath = cue.ParsePath("selfContained") 30 + expandReferencesPath = cue.ParsePath("expandReferences") 31 + infoPath = cue.ParsePath("info") 32 + ) 33 + 34 + // Marshal returns the OpenAPI encoding of schema for the given OpenAPI version. 35 + // The optional config value can be used to make further adjustments. 36 + // 37 + // Experimental: this API may change. 38 + // 39 + // schema can have the following fields: 40 + // 41 + // #Config: { 42 + // // version holds the OpenAPI version to use when marshaling. 43 + // // Currently only "3.0.0" is supported. 44 + // version!: "3.0.0" // currently "3.0.0" only 45 + 46 + // // selfContained causes all non-expanded external references 47 + // // to be included// 48 + // selfContained?: bool 49 + // 50 + // // expandReferences replaces references with actual objects when generating 51 + // // OpenAPI Schema. It is an error for an CUE value to refer to itself 52 + // // if this option is used. 53 + // expandReferences?: bool 54 + // 55 + // // info specifies the info section of the OpenAPI document. To be a valid 56 + // // OpenAPI document, it must include at least the title and version fields. 57 + // info?: { 58 + // title: string 59 + // description: string 60 + // version: string 61 + // } 62 + // } 63 + func MarshalSchema(config cue.Value, schema pkg.Schema) (string, error) { 64 + // TODO: implement a proper struct for schema. 65 + 66 + ctx := value.OpContext(schema) 67 + return marshalSchema(ctx, config, schema) 68 + } 69 + 70 + func marshalSchema(_ *adt.OpContext, config cue.Value, schema pkg.Schema) (string, error) { 71 + selfContained, _ := config.LookupPath(selfContainedPath).Bool() 72 + expandReferences, _ := config.LookupPath(expandReferencesPath).Bool() 73 + 74 + version, err := config.LookupPath(cue.ParsePath("version")).String() 75 + if err != nil { 76 + return "", err 77 + } 78 + 79 + c := &openapi.Config{ 80 + Version: version, 81 + SelfContained: selfContained, 82 + ExpandReferences: expandReferences, 83 + } 84 + 85 + if info := config.LookupPath(infoPath); info.Exists() { 86 + c.Info = info 87 + } 88 + 89 + b, err := openapi.Gen(schema, c) 90 + if err != nil { 91 + return "", err 92 + } 93 + return string(b), err 94 + }
+25
pkg/encoding/openapi/openapi_test.go
··· 1 + // Copyright 2023 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package openapi_test 16 + 17 + import ( 18 + "testing" 19 + 20 + "cuelang.org/go/pkg/internal/builtintest" 21 + ) 22 + 23 + func TestBuiltin(t *testing.T) { 24 + builtintest.Run("openapi", t) 25 + }
+58
pkg/encoding/openapi/pkg.go
··· 1 + // Code generated by cuelang.org/go/pkg/gen. DO NOT EDIT. 2 + 3 + package openapi 4 + 5 + import ( 6 + "cuelang.org/go/internal/core/adt" 7 + "cuelang.org/go/internal/pkg" 8 + ) 9 + 10 + func init() { 11 + pkg.Register("encoding/openapi", p) 12 + } 13 + 14 + var _ = adt.TopKind // in case the adt package isn't used 15 + 16 + var p = &pkg.Package{ 17 + Native: []*pkg.Builtin{{ 18 + Name: "MarshalSchema", 19 + Params: []pkg.Param{ 20 + {Kind: adt.TopKind}, 21 + {Kind: adt.TopKind}, 22 + }, 23 + Result: adt.StringKind, 24 + NonConcrete: true, 25 + Func: func(c *pkg.CallCtxt) { 26 + config, schema := c.Value(0), c.Schema(1) 27 + if c.Do() { 28 + c.Ret, c.Err = marshalSchema(c.OpContext(), config, schema) 29 + } 30 + }, 31 + }}, 32 + CUE: `{ 33 + #Config: { 34 + version!: "3.0.0" 35 + info?: #Info 36 + selfContained: bool | *false 37 + expandReferences: bool | *false 38 + } 39 + #Info: { 40 + title!: string 41 + version!: string 42 + summary?: string 43 + description?: string 44 + termsOfService?: string 45 + contact?: #Contact 46 + license?: #License 47 + } 48 + #Contact: { 49 + name?: string 50 + url?: string 51 + email?: string 52 + } 53 + #License: { 54 + name!: string 55 + url?: string 56 + } 57 + }`, 58 + }
+1083
pkg/encoding/openapi/testdata/gen.txtar
··· 1 + -- in.cue -- 2 + import ( 3 + "encoding/json" 4 + "encoding/openapi" 5 + ) 6 + 7 + // Basic schema marshaling test 8 + basic: { 9 + config: openapi.#Config & { 10 + version: "3.0.0" 11 + info: { 12 + title: "Test API" 13 + version: "1.0.0" 14 + } 15 + } 16 + schema: { 17 + // A User is a person identified by their name and age. 18 + #User: { 19 + name: string 20 + age: int 21 + } 22 + } 23 + result: json.Indent(openapi.MarshalSchema(config, schema), "", " ") 24 + } 25 + 26 + // Self-contained schema test 27 + selfContained: { 28 + config: openapi.#Config & { 29 + version: "3.0.0" 30 + info: { 31 + title: "Test API" 32 + version: "1.0.0" 33 + } 34 + selfContained: true 35 + } 36 + schema: { 37 + #Person: { 38 + name: string 39 + address: #Address 40 + } 41 + #Address: { 42 + street: string 43 + city: string 44 + } 45 + } 46 + result: json.Indent(openapi.MarshalSchema(config, schema), "", " ") 47 + } 48 + 49 + // Expand references test 50 + expandReferences: { 51 + config: openapi.#Config & { 52 + version: "3.0.0" 53 + info: { 54 + title: "Test API" 55 + version: "1.0.0" 56 + } 57 + expandReferences: true 58 + } 59 + schema: { 60 + #Product: { 61 + id: int 62 + name: string 63 + category: #Category 64 + subcategory: #Category 65 + } 66 + #Category: { 67 + name: string 68 + id: int 69 + } 70 + } 71 + result: json.Indent(openapi.MarshalSchema(config, schema), "", " ") 72 + } 73 + 74 + // Test with constraints and validation 75 + constraints: { 76 + config: openapi.#Config & { 77 + version: "3.0.0" 78 + info: { 79 + title: "Validation API" 80 + version: "1.0.0" 81 + } 82 + } 83 + schema: { 84 + #User: { 85 + name: string & len(_) > 0 86 + age: int & >=0 & <=120 87 + email: string & =~"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" 88 + } 89 + } 90 + result: json.Indent(openapi.MarshalSchema(config, schema), "", " ") 91 + } 92 + 93 + // Test with optional and required fields 94 + optional: { 95 + config: openapi.#Config & { 96 + version: "3.0.0" 97 + info: { 98 + title: "Optional Fields API" 99 + version: "1.0.0" 100 + } 101 + } 102 + schema: { 103 + #User: { 104 + name!: string 105 + age?: int 106 + email?: string 107 + metadata?: {...} 108 + } 109 + } 110 + result: json.Indent(openapi.MarshalSchema(config, schema), "", " ") 111 + } 112 + 113 + // Test with arrays and objects 114 + collections: { 115 + config: openapi.#Config & { 116 + version: "3.0.0" 117 + info: { 118 + title: "Collections API" 119 + version: "1.0.0" 120 + } 121 + } 122 + schema: { 123 + #UserList: { 124 + users: [...#User] 125 + total: int 126 + } 127 + #User: { 128 + id: int 129 + name: string 130 + tags: [...string] 131 + } 132 + } 133 + result: json.Indent(openapi.MarshalSchema(config, schema), "", " ") 134 + } 135 + 136 + // Test with enums and unions 137 + enums: { 138 + config: openapi.#Config & { 139 + version: "3.0.0" 140 + info: { 141 + title: "Enums API" 142 + version: "1.0.0" 143 + } 144 + } 145 + schema: { 146 + #Status: "active" | "inactive" | "pending" 147 + #Priority: 1 | 2 | 3 148 + #Task: { 149 + id: int 150 + status: #Status 151 + priority: #Priority 152 + } 153 + } 154 + result: json.Indent(openapi.MarshalSchema(config, schema), "", " ") 155 + } 156 + 157 + // Test error handling - invalid version 158 + invalidVersion: { 159 + config: openapi.#Config & { 160 + version: "invalid.version" 161 + info: { 162 + title: "Test API" 163 + version: "1.0.0" 164 + } 165 + } 166 + schema: { 167 + #User: { 168 + name: string 169 + } 170 + } 171 + result: json.Indent(openapi.MarshalSchema(config, schema), "", " ") 172 + } 173 + 174 + // Test minimal config 175 + minimal: { 176 + config: openapi.#Config & { 177 + version: "3.0.0" 178 + info: { 179 + title: "Minimal API" 180 + version: "1.0" 181 + } 182 + } 183 + schema: { 184 + #Simple: { 185 + value: string 186 + } 187 + } 188 + result: json.Indent(openapi.MarshalSchema(config, schema), "", " ") 189 + } 190 + 191 + // Test with nested structures 192 + nested: { 193 + config: openapi.#Config & { 194 + version: "3.0.0" 195 + info: { 196 + title: "Nested API" 197 + version: "1.0.0" 198 + } 199 + } 200 + schema: { 201 + #Company: { 202 + name: string 203 + departments: [...#Department] 204 + } 205 + #Department: { 206 + name: string 207 + manager: #Employee 208 + employees: [...#Employee] 209 + } 210 + #Employee: { 211 + id: int 212 + name: string 213 + position: string 214 + } 215 + } 216 + result: json.Indent(openapi.MarshalSchema(config, schema), "", " ") 217 + } 218 + -- out/openapi-v3 -- 219 + Errors: 220 + invalidVersion.config.version: conflicting values "3.0.0" and "invalid.version": 221 + ./in.cue:158:10 222 + ./in.cue:159:12 223 + encoding/openapi:3:21 224 + 225 + Result: 226 + import ( 227 + "encoding/json" 228 + "encoding/openapi" 229 + ) 230 + 231 + // Basic schema marshaling test 232 + basic: { 233 + config: { 234 + version: "3.0.0" 235 + info: { 236 + title: "Test API" 237 + version: "1.0.0" 238 + summary?: string 239 + description?: string 240 + termsOfService?: string 241 + contact?: { 242 + name?: string 243 + url?: string 244 + email?: string 245 + } 246 + license?: { 247 + name!: string 248 + url?: string 249 + } 250 + } 251 + selfContained: *false | bool 252 + expandReferences: *false | bool 253 + } 254 + schema: { 255 + // A User is a person identified by their name and age. 256 + #User: { 257 + name: string 258 + age: int 259 + } 260 + } 261 + result: """ 262 + { 263 + "openapi": "3.0.0", 264 + "info": { 265 + "title": "Test API", 266 + "version": "1.0.0" 267 + }, 268 + "paths": {}, 269 + "components": { 270 + "schemas": { 271 + "User": { 272 + "description": "A User is a person identified by their name and age.", 273 + "type": "object", 274 + "required": [ 275 + "name", 276 + "age" 277 + ], 278 + "properties": { 279 + "name": { 280 + "type": "string" 281 + }, 282 + "age": { 283 + "type": "integer" 284 + } 285 + } 286 + } 287 + } 288 + } 289 + } 290 + """ 291 + } 292 + 293 + // Self-contained schema test 294 + selfContained: { 295 + config: { 296 + version: "3.0.0" 297 + info: { 298 + title: "Test API" 299 + version: "1.0.0" 300 + summary?: string 301 + description?: string 302 + termsOfService?: string 303 + contact?: { 304 + name?: string 305 + url?: string 306 + email?: string 307 + } 308 + license?: { 309 + name!: string 310 + url?: string 311 + } 312 + } 313 + selfContained: true 314 + expandReferences: *false | bool 315 + } 316 + schema: { 317 + #Person: { 318 + name: string 319 + address: { 320 + street: string 321 + city: string 322 + } 323 + } 324 + #Address: { 325 + street: string 326 + city: string 327 + } 328 + } 329 + result: """ 330 + { 331 + "openapi": "3.0.0", 332 + "info": { 333 + "title": "Test API", 334 + "version": "1.0.0" 335 + }, 336 + "paths": {}, 337 + "components": { 338 + "schemas": { 339 + "Address": { 340 + "type": "object", 341 + "required": [ 342 + "street", 343 + "city" 344 + ], 345 + "properties": { 346 + "street": { 347 + "type": "string" 348 + }, 349 + "city": { 350 + "type": "string" 351 + } 352 + } 353 + }, 354 + "Person": { 355 + "type": "object", 356 + "required": [ 357 + "name", 358 + "address" 359 + ], 360 + "properties": { 361 + "name": { 362 + "type": "string" 363 + }, 364 + "address": { 365 + "$ref": "#/components/schemas/selfContained.schema.Address" 366 + } 367 + } 368 + }, 369 + "selfContained.schema.Address": { 370 + "type": "object", 371 + "required": [ 372 + "street", 373 + "city" 374 + ], 375 + "properties": { 376 + "street": { 377 + "type": "string" 378 + }, 379 + "city": { 380 + "type": "string" 381 + } 382 + } 383 + } 384 + } 385 + } 386 + } 387 + """ 388 + } 389 + 390 + // Expand references test 391 + expandReferences: { 392 + config: { 393 + version: "3.0.0" 394 + info: { 395 + title: "Test API" 396 + version: "1.0.0" 397 + summary?: string 398 + description?: string 399 + termsOfService?: string 400 + contact?: { 401 + name?: string 402 + url?: string 403 + email?: string 404 + } 405 + license?: { 406 + name!: string 407 + url?: string 408 + } 409 + } 410 + selfContained: *false | bool 411 + expandReferences: true 412 + } 413 + schema: { 414 + #Product: { 415 + id: int 416 + name: string 417 + category: { 418 + name: string 419 + id: int 420 + } 421 + subcategory: { 422 + name: string 423 + id: int 424 + } 425 + } 426 + #Category: { 427 + name: string 428 + id: int 429 + } 430 + } 431 + result: """ 432 + { 433 + "openapi": "3.0.0", 434 + "info": { 435 + "title": "Test API", 436 + "version": "1.0.0" 437 + }, 438 + "paths": {}, 439 + "components": { 440 + "schemas": { 441 + "Category": { 442 + "type": "object", 443 + "required": [ 444 + "name", 445 + "id" 446 + ], 447 + "properties": { 448 + "name": { 449 + "type": "string" 450 + }, 451 + "id": { 452 + "type": "integer" 453 + } 454 + } 455 + }, 456 + "Product": { 457 + "type": "object", 458 + "required": [ 459 + "id", 460 + "name", 461 + "category", 462 + "subcategory" 463 + ], 464 + "properties": { 465 + "id": { 466 + "type": "integer" 467 + }, 468 + "name": { 469 + "type": "string" 470 + }, 471 + "category": { 472 + "type": "object", 473 + "required": [ 474 + "name", 475 + "id" 476 + ], 477 + "properties": { 478 + "name": { 479 + "type": "string" 480 + }, 481 + "id": { 482 + "type": "integer" 483 + } 484 + } 485 + }, 486 + "subcategory": { 487 + "type": "object", 488 + "required": [ 489 + "name", 490 + "id" 491 + ], 492 + "properties": { 493 + "name": { 494 + "type": "string" 495 + }, 496 + "id": { 497 + "type": "integer" 498 + } 499 + } 500 + } 501 + } 502 + } 503 + } 504 + } 505 + } 506 + """ 507 + } 508 + 509 + // Test with constraints and validation 510 + constraints: { 511 + config: { 512 + version: "3.0.0" 513 + info: { 514 + title: "Validation API" 515 + version: "1.0.0" 516 + summary?: string 517 + description?: string 518 + termsOfService?: string 519 + contact?: { 520 + name?: string 521 + url?: string 522 + email?: string 523 + } 524 + license?: { 525 + name!: string 526 + url?: string 527 + } 528 + } 529 + selfContained: *false | bool 530 + expandReferences: *false | bool 531 + } 532 + schema: { 533 + #User: { 534 + name: string & len(_) > 0 535 + age: uint & <=120 536 + email: =~"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" 537 + } 538 + } 539 + result: json.Indent(openapi.MarshalSchema(config, schema), "", " ") 540 + } 541 + 542 + // Test with optional and required fields 543 + optional: { 544 + config: { 545 + version: "3.0.0" 546 + info: { 547 + title: "Optional Fields API" 548 + version: "1.0.0" 549 + summary?: string 550 + description?: string 551 + termsOfService?: string 552 + contact?: { 553 + name?: string 554 + url?: string 555 + email?: string 556 + } 557 + license?: { 558 + name!: string 559 + url?: string 560 + } 561 + } 562 + selfContained: *false | bool 563 + expandReferences: *false | bool 564 + } 565 + schema: { 566 + #User: { 567 + name!: string 568 + age?: int 569 + email?: string 570 + metadata?: {} 571 + } 572 + } 573 + result: """ 574 + { 575 + "openapi": "3.0.0", 576 + "info": { 577 + "title": "Optional Fields API", 578 + "version": "1.0.0" 579 + }, 580 + "paths": {}, 581 + "components": { 582 + "schemas": { 583 + "User": { 584 + "type": "object", 585 + "required": [ 586 + "name" 587 + ], 588 + "properties": { 589 + "name": { 590 + "type": "string" 591 + }, 592 + "age": { 593 + "type": "integer" 594 + }, 595 + "email": { 596 + "type": "string" 597 + }, 598 + "metadata": { 599 + "type": "object" 600 + } 601 + } 602 + } 603 + } 604 + } 605 + } 606 + """ 607 + } 608 + 609 + // Test with arrays and objects 610 + collections: { 611 + config: { 612 + version: "3.0.0" 613 + info: { 614 + title: "Collections API" 615 + version: "1.0.0" 616 + summary?: string 617 + description?: string 618 + termsOfService?: string 619 + contact?: { 620 + name?: string 621 + url?: string 622 + email?: string 623 + } 624 + license?: { 625 + name!: string 626 + url?: string 627 + } 628 + } 629 + selfContained: *false | bool 630 + expandReferences: *false | bool 631 + } 632 + schema: { 633 + #UserList: { 634 + users: [...{ 635 + id: int 636 + name: string 637 + tags: [...string] 638 + }] 639 + total: int 640 + } 641 + #User: { 642 + id: int 643 + name: string 644 + tags: [...string] 645 + } 646 + } 647 + result: """ 648 + { 649 + "openapi": "3.0.0", 650 + "info": { 651 + "title": "Collections API", 652 + "version": "1.0.0" 653 + }, 654 + "paths": {}, 655 + "components": { 656 + "schemas": { 657 + "User": { 658 + "type": "object", 659 + "required": [ 660 + "id", 661 + "name", 662 + "tags" 663 + ], 664 + "properties": { 665 + "id": { 666 + "type": "integer" 667 + }, 668 + "name": { 669 + "type": "string" 670 + }, 671 + "tags": { 672 + "type": "array", 673 + "items": { 674 + "type": "string" 675 + } 676 + } 677 + } 678 + }, 679 + "UserList": { 680 + "type": "object", 681 + "required": [ 682 + "users", 683 + "total" 684 + ], 685 + "properties": { 686 + "users": { 687 + "type": "array", 688 + "items": { 689 + "$ref": "#/components/schemas/collections.schema.User" 690 + } 691 + }, 692 + "total": { 693 + "type": "integer" 694 + } 695 + } 696 + }, 697 + "collections.schema.User": { 698 + "type": "object", 699 + "required": [ 700 + "id", 701 + "name", 702 + "tags" 703 + ], 704 + "properties": { 705 + "id": { 706 + "type": "integer" 707 + }, 708 + "name": { 709 + "type": "string" 710 + }, 711 + "tags": { 712 + "type": "array", 713 + "items": { 714 + "type": "string" 715 + } 716 + } 717 + } 718 + } 719 + } 720 + } 721 + } 722 + """ 723 + } 724 + 725 + // Test with enums and unions 726 + enums: { 727 + config: { 728 + version: "3.0.0" 729 + info: { 730 + title: "Enums API" 731 + version: "1.0.0" 732 + summary?: string 733 + description?: string 734 + termsOfService?: string 735 + contact?: { 736 + name?: string 737 + url?: string 738 + email?: string 739 + } 740 + license?: { 741 + name!: string 742 + url?: string 743 + } 744 + } 745 + selfContained: *false | bool 746 + expandReferences: *false | bool 747 + } 748 + schema: { 749 + #Status: "active" | "inactive" | "pending" 750 + #Priority: 1 | 2 | 3 751 + #Task: { 752 + id: int 753 + status: "active" | "inactive" | "pending" 754 + priority: 1 | 2 | 3 755 + } 756 + } 757 + result: """ 758 + { 759 + "openapi": "3.0.0", 760 + "info": { 761 + "title": "Enums API", 762 + "version": "1.0.0" 763 + }, 764 + "paths": {}, 765 + "components": { 766 + "schemas": { 767 + "Priority": { 768 + "type": "integer", 769 + "enum": [ 770 + 1, 771 + 2, 772 + 3 773 + ] 774 + }, 775 + "Status": { 776 + "type": "string", 777 + "enum": [ 778 + "active", 779 + "inactive", 780 + "pending" 781 + ] 782 + }, 783 + "Task": { 784 + "type": "object", 785 + "required": [ 786 + "id", 787 + "status", 788 + "priority" 789 + ], 790 + "properties": { 791 + "id": { 792 + "type": "integer" 793 + }, 794 + "status": { 795 + "$ref": "#/components/schemas/enums.schema.Status" 796 + }, 797 + "priority": { 798 + "$ref": "#/components/schemas/enums.schema.Priority" 799 + } 800 + } 801 + }, 802 + "enums.schema.Priority": { 803 + "type": "integer", 804 + "enum": [ 805 + 1, 806 + 2, 807 + 3 808 + ] 809 + }, 810 + "enums.schema.Status": { 811 + "type": "string", 812 + "enum": [ 813 + "active", 814 + "inactive", 815 + "pending" 816 + ] 817 + } 818 + } 819 + } 820 + } 821 + """ 822 + } 823 + 824 + // Test error handling - invalid version 825 + invalidVersion: { 826 + config: { 827 + version: _|_ // invalidVersion.config.version: conflicting values "3.0.0" and "invalid.version" 828 + info: { 829 + title: "Test API" 830 + version: "1.0.0" 831 + summary?: string 832 + description?: string 833 + termsOfService?: string 834 + contact?: { 835 + name?: string 836 + url?: string 837 + email?: string 838 + } 839 + license?: { 840 + name!: string 841 + url?: string 842 + } 843 + } 844 + selfContained: *false | bool 845 + expandReferences: *false | bool 846 + } 847 + schema: { 848 + #User: { 849 + name: string 850 + } 851 + } 852 + result: _|_ // invalidVersion.config.version: conflicting values "3.0.0" and "invalid.version" 853 + } 854 + 855 + // Test minimal config 856 + minimal: { 857 + config: { 858 + version: "3.0.0" 859 + info: { 860 + title: "Minimal API" 861 + version: "1.0" 862 + summary?: string 863 + description?: string 864 + termsOfService?: string 865 + contact?: { 866 + name?: string 867 + url?: string 868 + email?: string 869 + } 870 + license?: { 871 + name!: string 872 + url?: string 873 + } 874 + } 875 + selfContained: *false | bool 876 + expandReferences: *false | bool 877 + } 878 + schema: { 879 + #Simple: { 880 + value: string 881 + } 882 + } 883 + result: """ 884 + { 885 + "openapi": "3.0.0", 886 + "info": { 887 + "title": "Minimal API", 888 + "version": "1.0" 889 + }, 890 + "paths": {}, 891 + "components": { 892 + "schemas": { 893 + "Simple": { 894 + "type": "object", 895 + "required": [ 896 + "value" 897 + ], 898 + "properties": { 899 + "value": { 900 + "type": "string" 901 + } 902 + } 903 + } 904 + } 905 + } 906 + } 907 + """ 908 + } 909 + 910 + // Test with nested structures 911 + nested: { 912 + config: { 913 + version: "3.0.0" 914 + info: { 915 + title: "Nested API" 916 + version: "1.0.0" 917 + summary?: string 918 + description?: string 919 + termsOfService?: string 920 + contact?: { 921 + name?: string 922 + url?: string 923 + email?: string 924 + } 925 + license?: { 926 + name!: string 927 + url?: string 928 + } 929 + } 930 + selfContained: *false | bool 931 + expandReferences: *false | bool 932 + } 933 + schema: { 934 + #Company: { 935 + name: string 936 + departments: [...{ 937 + name: string 938 + manager: { 939 + id: int 940 + name: string 941 + position: string 942 + } 943 + employees: [...{ 944 + id: int 945 + name: string 946 + position: string 947 + }] 948 + }] 949 + } 950 + #Department: { 951 + name: string 952 + manager: { 953 + id: int 954 + name: string 955 + position: string 956 + } 957 + employees: [...{ 958 + id: int 959 + name: string 960 + position: string 961 + }] 962 + } 963 + #Employee: { 964 + id: int 965 + name: string 966 + position: string 967 + } 968 + } 969 + result: """ 970 + { 971 + "openapi": "3.0.0", 972 + "info": { 973 + "title": "Nested API", 974 + "version": "1.0.0" 975 + }, 976 + "paths": {}, 977 + "components": { 978 + "schemas": { 979 + "Company": { 980 + "type": "object", 981 + "required": [ 982 + "name", 983 + "departments" 984 + ], 985 + "properties": { 986 + "name": { 987 + "type": "string" 988 + }, 989 + "departments": { 990 + "type": "array", 991 + "items": { 992 + "$ref": "#/components/schemas/nested.schema.Department" 993 + } 994 + } 995 + } 996 + }, 997 + "Department": { 998 + "type": "object", 999 + "required": [ 1000 + "name", 1001 + "manager", 1002 + "employees" 1003 + ], 1004 + "properties": { 1005 + "name": { 1006 + "type": "string" 1007 + }, 1008 + "manager": { 1009 + "$ref": "#/components/schemas/nested.schema.Employee" 1010 + }, 1011 + "employees": { 1012 + "type": "array", 1013 + "items": { 1014 + "$ref": "#/components/schemas/nested.schema.Employee" 1015 + } 1016 + } 1017 + } 1018 + }, 1019 + "Employee": { 1020 + "type": "object", 1021 + "required": [ 1022 + "id", 1023 + "name", 1024 + "position" 1025 + ], 1026 + "properties": { 1027 + "id": { 1028 + "type": "integer" 1029 + }, 1030 + "name": { 1031 + "type": "string" 1032 + }, 1033 + "position": { 1034 + "type": "string" 1035 + } 1036 + } 1037 + }, 1038 + "nested.schema.Department": { 1039 + "type": "object", 1040 + "required": [ 1041 + "name", 1042 + "manager", 1043 + "employees" 1044 + ], 1045 + "properties": { 1046 + "name": { 1047 + "type": "string" 1048 + }, 1049 + "manager": { 1050 + "$ref": "#/components/schemas/nested.schema.Employee" 1051 + }, 1052 + "employees": { 1053 + "type": "array", 1054 + "items": { 1055 + "$ref": "#/components/schemas/nested.schema.Employee" 1056 + } 1057 + } 1058 + } 1059 + }, 1060 + "nested.schema.Employee": { 1061 + "type": "object", 1062 + "required": [ 1063 + "id", 1064 + "name", 1065 + "position" 1066 + ], 1067 + "properties": { 1068 + "id": { 1069 + "type": "integer" 1070 + }, 1071 + "name": { 1072 + "type": "string" 1073 + }, 1074 + "position": { 1075 + "type": "string" 1076 + } 1077 + } 1078 + } 1079 + } 1080 + } 1081 + } 1082 + """ 1083 + }
+1
pkg/register.go
··· 13 13 _ "cuelang.org/go/pkg/encoding/csv" 14 14 _ "cuelang.org/go/pkg/encoding/hex" 15 15 _ "cuelang.org/go/pkg/encoding/json" 16 + _ "cuelang.org/go/pkg/encoding/openapi" 16 17 _ "cuelang.org/go/pkg/encoding/toml" 17 18 _ "cuelang.org/go/pkg/encoding/yaml" 18 19 _ "cuelang.org/go/pkg/html"