microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
e966efd30e552f4e84d5ae7224515199738ddd8e

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/openapi3/test/test-openapi-output.ts

940lines · modecode

1import { expectDiagnostics } from "@cadl-lang/compiler/testing";
2import { deepStrictEqual, ok, strictEqual } from "assert";
3import { createOpenAPITestRunner, oapiForModel, openApiFor } from "./test-host.js";
4
5describe("openapi3: definitions", () => {
6 it("defines models", async () => {
7 const res = await oapiForModel(
8 "Foo",
9 `model Foo {
10 x: int32;
11 };`
12 );
13
14 ok(res.isRef);
15 deepStrictEqual(res.schemas.Foo, {
16 type: "object",
17 properties: {
18 x: { type: "integer", format: "int32" },
19 },
20 required: ["x"],
21 });
22 });
23
24 it("doesn't define anonymous or unconnected models", async () => {
25 const res = await oapiForModel(
26 "{ ... Foo }",
27 `model Foo {
28 x: int32;
29 };`
30 );
31
32 ok(!res.isRef);
33 strictEqual(Object.keys(res.schemas).length, 0);
34 deepStrictEqual(res.useSchema, {
35 type: "object",
36 properties: {
37 x: { type: "integer", format: "int32" },
38 },
39 required: ["x"],
40 "x-cadl-name": "(anonymous model)",
41 });
42 });
43
44 it("defines templated models", async () => {
45 const res = await oapiForModel(
46 "Foo<int32>",
47 `model Foo<T> {
48 x: T;
49 };`
50 );
51
52 ok(res.isRef);
53 ok(res.schemas.Foo_int32, "expected definition named Foo_int32");
54 deepStrictEqual(res.schemas.Foo_int32, {
55 type: "object",
56 properties: {
57 x: { type: "integer", format: "int32" },
58 },
59 required: ["x"],
60 });
61 });
62
63 it("defines templated models when template param is in a namespace", async () => {
64 const res = await oapiForModel(
65 "Foo<Test.M>",
66 `
67 namespace Test {
68 model M {}
69 }
70 model Foo<T> {
71 x: T;
72 };`
73 );
74
75 ok(res.isRef);
76 ok(res.schemas["Foo_Test.M"], "expected definition named Foo_Test.M");
77 deepStrictEqual(res.schemas["Foo_Test.M"], {
78 type: "object",
79 properties: {
80 x: { $ref: "#/components/schemas/Test.M" },
81 },
82 required: ["x"],
83 });
84 });
85
86 it("defines models extended from models", async () => {
87 const res = await oapiForModel(
88 "Bar",
89 `
90 model Foo {
91 y: int32;
92 };
93 model Bar extends Foo {}`
94 );
95
96 ok(res.isRef);
97 ok(res.schemas.Foo, "expected definition named Foo");
98 ok(res.schemas.Bar, "expected definition named Bar");
99 deepStrictEqual(res.schemas.Bar, {
100 type: "object",
101 properties: {},
102 allOf: [{ $ref: "#/components/schemas/Foo" }],
103 });
104
105 deepStrictEqual(res.schemas.Foo, {
106 type: "object",
107 properties: { y: { type: "integer", format: "int32" } },
108 required: ["y"],
109 });
110 });
111
112 it("emits models extended from models when parent is emitted", async () => {
113 const res = await openApiFor(
114 `
115 model Parent {
116 x?: int32;
117 };
118 model Child extends Parent {
119 y?: int32;
120 }
121 namespace Test {
122 @route("/") op test(): Parent;
123 }
124 `
125 );
126 deepStrictEqual(res.components.schemas.Parent, {
127 type: "object",
128 properties: { x: { type: "integer", format: "int32" } },
129 });
130 deepStrictEqual(res.components.schemas.Child, {
131 type: "object",
132 allOf: [{ $ref: "#/components/schemas/Parent" }],
133 properties: { y: { type: "integer", format: "int32" } },
134 });
135 });
136
137 it("ignore uninstantiated template types", async () => {
138 const res = await openApiFor(
139 `
140 model Parent {
141 x?: int32;
142 };
143 model TParent<T> extends Parent {
144 t: T;
145 }
146 model Child extends TParent<string> {
147 y?: int32;
148 }
149 namespace Test {
150 @route("/") op test(): Parent;
151 }
152 `
153 );
154 ok(
155 !("TParent" in res.components.schemas),
156 "Parent templated type shouldn't be includd in OpenAPI"
157 );
158 deepStrictEqual(res.components.schemas.Parent, {
159 type: "object",
160 properties: { x: { type: "integer", format: "int32" } },
161 });
162 deepStrictEqual(res.components.schemas.TParent_string, {
163 type: "object",
164 properties: { t: { type: "string" } },
165 required: ["t"],
166 allOf: [{ $ref: "#/components/schemas/Parent" }],
167 });
168 deepStrictEqual(res.components.schemas.Child, {
169 type: "object",
170 allOf: [{ $ref: "#/components/schemas/TParent_string" }],
171 properties: { y: { type: "integer", format: "int32" } },
172 });
173 });
174
175 it("shouldn't emit instantiated template child types that are only used in is", async () => {
176 const res = await openApiFor(
177 `
178 model Parent {
179 x?: int32;
180 };
181 model TParent<T> extends Parent {
182 t: T;
183 }
184 model Child is TParent<string> {
185 y?: int32;
186 }
187 namespace Test {
188 @route("/") op test(): Parent;
189 }
190 `
191 );
192 ok(
193 !("TParent_string" in res.components.schemas),
194 "Parent instantiated templated type shouldn't be includd in OpenAPI"
195 );
196 });
197
198 it("defines models with properties extended from models", async () => {
199 const res = await oapiForModel(
200 "Bar",
201 `
202 model Foo {
203 y: int32;
204 };
205 model Bar extends Foo {
206 x: int32;
207 }`
208 );
209
210 ok(res.isRef);
211 ok(res.schemas.Foo, "expected definition named Foo");
212 ok(res.schemas.Bar, "expected definition named Bar");
213 deepStrictEqual(res.schemas.Bar, {
214 type: "object",
215 properties: { x: { type: "integer", format: "int32" } },
216 allOf: [{ $ref: "#/components/schemas/Foo" }],
217 required: ["x"],
218 });
219
220 deepStrictEqual(res.schemas.Foo, {
221 type: "object",
222 properties: { y: { type: "integer", format: "int32" } },
223 required: ["y"],
224 });
225 });
226
227 it("defines models extended from templated models", async () => {
228 const res = await oapiForModel(
229 "Bar",
230 `
231 model Foo<T> {
232 y: T;
233 };
234 model Bar extends Foo<int32> {}`
235 );
236
237 ok(res.isRef);
238 ok(res.schemas["Foo_int32"] === undefined, "no definition named Foo_int32");
239 ok(res.schemas.Bar, "expected definition named Bar");
240 deepStrictEqual(res.schemas.Bar, {
241 type: "object",
242 properties: { y: { type: "integer", format: "int32" } },
243 required: ["y"],
244 });
245 });
246
247 it("defines models with properties extended from templated models", async () => {
248 const res = await oapiForModel(
249 "Bar",
250 `
251 model Foo<T> {
252 y: T;
253 };
254 model Bar extends Foo<int32> {
255 x: int32
256 }`
257 );
258
259 ok(res.isRef);
260 ok(res.schemas.Foo_int32, "expected definition named Foo_int32");
261 ok(res.schemas.Bar, "expected definition named Bar");
262 deepStrictEqual(res.schemas.Bar, {
263 type: "object",
264 properties: { x: { type: "integer", format: "int32" } },
265 allOf: [{ $ref: "#/components/schemas/Foo_int32" }],
266 required: ["x"],
267 });
268
269 deepStrictEqual(res.schemas.Foo_int32, {
270 type: "object",
271 properties: { y: { type: "integer", format: "int32" } },
272 required: ["y"],
273 });
274 });
275
276 it("defines templated models with properties extended from templated models", async () => {
277 const res = await oapiForModel(
278 "Bar<int32>",
279 `
280 model Foo<T> {
281 y: T;
282 };
283 model Bar<T> extends Foo<T> {
284 x: T
285 }`
286 );
287
288 ok(res.isRef);
289 ok(res.schemas.Foo_int32, "expected definition named Foo_int32");
290 ok(res.schemas.Bar_int32, "expected definition named Bar_int32");
291 deepStrictEqual(res.schemas.Bar_int32, {
292 type: "object",
293 properties: { x: { type: "integer", format: "int32" } },
294 allOf: [{ $ref: "#/components/schemas/Foo_int32" }],
295 required: ["x"],
296 });
297
298 deepStrictEqual(res.schemas.Foo_int32, {
299 type: "object",
300 properties: { y: { type: "integer", format: "int32" } },
301 required: ["y"],
302 });
303 });
304
305 it("defines models with no properties extended", async () => {
306 const res = await oapiForModel(
307 "Bar",
308 `
309 model Foo {};
310 model Bar extends Foo {};`
311 );
312
313 ok(res.isRef);
314 ok(res.schemas.Foo, "expected definition named Foo");
315 ok(res.schemas.Bar, "expected definition named Bar");
316 deepStrictEqual(res.schemas.Bar, {
317 type: "object",
318 properties: {},
319 allOf: [{ $ref: "#/components/schemas/Foo" }],
320 });
321
322 deepStrictEqual(res.schemas.Foo, {
323 type: "object",
324 properties: {},
325 });
326 });
327
328 it("defines models with no properties extended twice", async () => {
329 const res = await oapiForModel(
330 "Baz",
331 `
332 model Foo { x: int32 };
333 model Bar extends Foo {};
334 model Baz extends Bar {};`
335 );
336
337 ok(res.isRef);
338 ok(res.schemas.Foo, "expected definition named Foo");
339 ok(res.schemas.Bar, "expected definition named Bar");
340 ok(res.schemas.Baz, "expected definition named Baz");
341 deepStrictEqual(res.schemas.Baz, {
342 type: "object",
343 properties: {},
344 allOf: [{ $ref: "#/components/schemas/Bar" }],
345 });
346
347 deepStrictEqual(res.schemas.Bar, {
348 type: "object",
349 properties: {},
350 allOf: [{ $ref: "#/components/schemas/Foo" }],
351 });
352
353 deepStrictEqual(res.schemas.Foo, {
354 type: "object",
355 properties: {
356 x: {
357 format: "int32",
358 type: "integer",
359 },
360 },
361 required: ["x"],
362 });
363 });
364
365 it("defines enum types", async () => {
366 const res = await oapiForModel(
367 "Pet",
368 `
369 enum PetType {
370 Dog, Cat
371 }
372 model Pet { type: PetType };
373 `
374 );
375 ok(res.isRef);
376 strictEqual(res.schemas.Pet.properties.type.$ref, "#/components/schemas/PetType");
377 deepStrictEqual(res.schemas.PetType.enum, ["Dog", "Cat"]);
378 });
379
380 it("defines enum types with custom values", async () => {
381 const res = await oapiForModel(
382 "Pet",
383 `
384 enum PetType {
385 Dog: 0, Cat: 1
386 }
387 model Pet { type: PetType };
388 `
389 );
390 ok(res.isRef);
391 strictEqual(res.schemas.Pet.properties.type.$ref, "#/components/schemas/PetType");
392 deepStrictEqual(res.schemas.PetType.enum, [0, 1]);
393 });
394
395 it("defines known values", async () => {
396 const res = await oapiForModel(
397 "Pet",
398 `
399 enum KnownPetType {
400 Dog, Cat
401 }
402
403 @knownValues(KnownPetType)
404 model PetType is string {}
405 model Pet { type: PetType };
406 `
407 );
408 ok(res.isRef);
409 strictEqual(res.schemas.Pet.properties.type.$ref, "#/components/schemas/PetType");
410 deepStrictEqual(res.schemas.PetType, {
411 oneOf: [{ type: "string" }, { type: "string", enum: ["Dog", "Cat"] }],
412 });
413 });
414
415 it("defines nullable properties", async () => {
416 const res = await oapiForModel(
417 "Pet",
418 `
419 model Pet {
420 name: string | null;
421 };
422 `
423 );
424 ok(res.isRef);
425 deepStrictEqual(res.schemas.Pet, {
426 type: "object",
427 properties: {
428 name: {
429 type: "string",
430 nullable: true,
431 "x-cadl-name": "Cadl.string | Cadl.null",
432 },
433 },
434 required: ["name"],
435 });
436 });
437
438 it("defines nullable array", async () => {
439 const res = await oapiForModel(
440 "Pet",
441 `
442 model Pet {
443 name: int32[] | null;
444 };
445 `
446 );
447 ok(res.isRef);
448 deepStrictEqual(res.schemas.Pet, {
449 type: "object",
450 properties: {
451 name: {
452 type: "array",
453 items: {
454 type: "integer",
455 format: "int32",
456 },
457 nullable: true,
458 "x-cadl-name": "Cadl.int32[] | Cadl.null",
459 },
460 },
461 required: ["name"],
462 });
463 });
464
465 it("defines enums with a nullable variant", async () => {
466 const res = await oapiForModel(
467 "Pet",
468 `
469 model Pet {
470 type: "cat" | "dog" | null;
471 };
472 `
473 );
474 ok(res.isRef);
475 deepStrictEqual(res.schemas.Pet, {
476 type: "object",
477 properties: {
478 type: {
479 type: "string",
480 enum: ["cat", "dog"],
481 nullable: true,
482 "x-cadl-name": "cat | dog | Cadl.null",
483 },
484 },
485 required: ["type"],
486 });
487 });
488
489 it("throws diagnostics for empty enum definitions", async () => {
490 const runner = await createOpenAPITestRunner();
491
492 const diagnostics = await runner.diagnose(`
493 enum PetType {
494 }
495 model Pet { type: PetType };
496 @route("/")
497 namespace root {
498 op read(): Pet;
499 }
500 `);
501
502 expectDiagnostics(diagnostics, {
503 code: "@cadl-lang/openapi3/union-unsupported",
504 message:
505 "Empty unions are not supported for OpenAPI v3 - enums must have at least one value.",
506 });
507 });
508
509 it("defines request bodies as unions of models", async () => {
510 const openApi = await openApiFor(`
511 model Cat {
512 meow: int32;
513 }
514 model Dog {
515 bark: string;
516 }
517 @route("/")
518 namespace root {
519 @post op create(@body body: Cat | Dog): { ...Response<200> };
520 }
521 `);
522 ok(openApi.components.schemas.Cat, "expected definition named Cat");
523 ok(openApi.components.schemas.Dog, "expected definition named Dog");
524 deepStrictEqual(openApi.paths["/"].post.requestBody.content["application/json"].schema, {
525 "x-cadl-name": "Cat | Dog",
526 anyOf: [{ $ref: "#/components/schemas/Cat" }, { $ref: "#/components/schemas/Dog" }],
527 });
528 });
529
530 it("defines request bodies as unions of model and non-model types", async () => {
531 const openApi = await openApiFor(`
532 model Cat {
533 meow: int32;
534 }
535 @route("/")
536 namespace root {
537 @post op create(@body body: Cat | string): { ...Response<200> };
538 }
539 `);
540 ok(openApi.components.schemas.Cat, "expected definition named Cat");
541 deepStrictEqual(openApi.paths["/"].post.requestBody.content["application/json"].schema, {
542 "x-cadl-name": "Cat | Cadl.string",
543 anyOf: [{ $ref: "#/components/schemas/Cat" }, { type: "string" }],
544 });
545 });
546
547 it("defines request bodies aliased to a union of models", async () => {
548 const openApi = await openApiFor(`
549 model Cat {
550 meow: int32;
551 }
552 model Dog {
553 bark: string;
554 }
555 alias Pet = Cat | Dog;
556 @route("/")
557 namespace root {
558 @post op create(@body body: Pet): { ...Response<200> };
559 }
560 `);
561 ok(openApi.components.schemas.Cat, "expected definition named Cat");
562 ok(openApi.components.schemas.Dog, "expected definition named Dog");
563 deepStrictEqual(openApi.paths["/"].post.requestBody.content["application/json"].schema, {
564 "x-cadl-name": "Cat | Dog",
565 anyOf: [{ $ref: "#/components/schemas/Cat" }, { $ref: "#/components/schemas/Dog" }],
566 });
567 });
568
569 it("defines response bodies as unions of models", async () => {
570 const openApi = await openApiFor(`
571 model Cat {
572 meow: int32;
573 }
574 model Dog {
575 bark: string;
576 }
577 @route("/")
578 namespace root {
579 op read(): { @body body: Cat | Dog };
580 }
581 `);
582 ok(openApi.components.schemas.Cat, "expected definition named Cat");
583 ok(openApi.components.schemas.Dog, "expected definition named Dog");
584 deepStrictEqual(openApi.paths["/"].get.responses["200"].content["application/json"].schema, {
585 "x-cadl-name": "Cat | Dog",
586 anyOf: [{ $ref: "#/components/schemas/Cat" }, { $ref: "#/components/schemas/Dog" }],
587 });
588 });
589
590 it("defines response bodies as unions of model and non-model types", async () => {
591 const openApi = await openApiFor(`
592 model Cat {
593 meow: int32;
594 }
595 @route("/")
596 namespace root {
597 op read(): { @body body: Cat | string };
598 }
599 `);
600 ok(openApi.components.schemas.Cat, "expected definition named Cat");
601 deepStrictEqual(openApi.paths["/"].get.responses["200"].content["application/json"].schema, {
602 "x-cadl-name": "Cat | Cadl.string",
603 anyOf: [{ $ref: "#/components/schemas/Cat" }, { type: "string" }],
604 });
605 });
606
607 it("defines response bodies aliased to a union from models", async () => {
608 const openApi = await openApiFor(`
609 model Cat {
610 meow: int32;
611 }
612 model Dog {
613 bark: string;
614 }
615 alias Pet = Cat | Dog;
616 @route("/")
617 namespace root {
618 op read(): { @body body: Pet };
619 }
620 `);
621 ok(openApi.components.schemas.Cat, "expected definition named Cat");
622 ok(openApi.components.schemas.Dog, "expected definition named Dog");
623 deepStrictEqual(openApi.paths["/"].get.responses["200"].content["application/json"].schema, {
624 "x-cadl-name": "Cat | Dog",
625 anyOf: [{ $ref: "#/components/schemas/Cat" }, { $ref: "#/components/schemas/Dog" }],
626 });
627 });
628
629 it("defines response bodies unioned in OkResponse as unions of models", async () => {
630 const openApi = await openApiFor(`
631 model Cat {
632 meow: int32;
633 }
634 model Dog {
635 bark: string;
636 }
637 @route("/")
638 namespace root {
639 op read(): OkResponse<Cat | Dog>;
640 }
641 `);
642 ok(openApi.components.schemas.Cat, "expected definition named Cat");
643 ok(openApi.components.schemas.Dog, "expected definition named Dog");
644 deepStrictEqual(openApi.paths["/"].get.responses["200"].content["application/json"].schema, {
645 "x-cadl-name": "Cat | Dog",
646 anyOf: [{ $ref: "#/components/schemas/Cat" }, { $ref: "#/components/schemas/Dog" }],
647 });
648 });
649
650 it("defines unions with named variants similarly to unnamed unions (it ignores variant names)", async () => {
651 const openApi = await openApiFor(`
652 model Cat {
653 meow: int32;
654 }
655 model Dog {
656 bark: string;
657 }
658 union Pet { cat: Cat, dog: Dog }
659 @route("/")
660 namespace root {
661 op read(): { @body body: Pet };
662 }
663 `);
664 ok(openApi.components.schemas.Cat, "expected definition named Cat");
665 ok(openApi.components.schemas.Dog, "expected definition named Dog");
666 ok(openApi.components.schemas.Pet, "expected definition named Pet");
667 deepStrictEqual(openApi.components.schemas.Pet, {
668 anyOf: [{ $ref: "#/components/schemas/Cat" }, { $ref: "#/components/schemas/Dog" }],
669 });
670 deepStrictEqual(openApi.paths["/"].get.responses["200"].content["application/json"].schema, {
671 $ref: "#/components/schemas/Pet",
672 });
673 });
674
675 it("defines oneOf schema for unions with @oneOf decorator", async () => {
676 const openApi = await openApiFor(`
677 model Cat {
678 meow: int32;
679 }
680 model Dog {
681 bark: string;
682 }
683 @oneOf
684 union Pet { cat: Cat, dog: Dog }
685 @route("/")
686 namespace root {
687 op read(): { @body body: Pet };
688 }
689 `);
690 ok(openApi.components.schemas.Cat, "expected definition named Cat");
691 ok(openApi.components.schemas.Dog, "expected definition named Dog");
692 ok(openApi.components.schemas.Pet, "expected definition named Pet");
693 deepStrictEqual(openApi.components.schemas.Pet, {
694 oneOf: [{ $ref: "#/components/schemas/Cat" }, { $ref: "#/components/schemas/Dog" }],
695 });
696 deepStrictEqual(openApi.paths["/"].get.responses["200"].content["application/json"].schema, {
697 $ref: "#/components/schemas/Pet",
698 });
699 });
700});
701
702describe("openapi3: primitives", () => {
703 const cases = [
704 ["int8", { type: "integer", format: "int8" }],
705 ["int16", { type: "integer", format: "int16" }],
706 ["int32", { type: "integer", format: "int32" }],
707 ["int64", { type: "integer", format: "int64" }],
708 ["safeint", { type: "integer", format: "int64" }],
709 ["uint8", { type: "integer", format: "uint8" }],
710 ["uint16", { type: "integer", format: "uint16" }],
711 ["uint32", { type: "integer", format: "uint32" }],
712 ["uint64", { type: "integer", format: "uint64" }],
713 ["float32", { type: "number", format: "float" }],
714 ["float64", { type: "number", format: "double" }],
715 ["string", { type: "string" }],
716 ["boolean", { type: "boolean" }],
717 ["plainDate", { type: "string", format: "date" }],
718 ["zonedDateTime", { type: "string", format: "date-time" }],
719 ["plainTime", { type: "string", format: "time" }],
720 ["duration", { type: "string", format: "duration" }],
721 ["bytes", { type: "string", format: "byte" }],
722 ];
723
724 for (const test of cases) {
725 it("knows schema for " + test[0], async () => {
726 const res = await oapiForModel(
727 "Pet",
728 `
729 model Pet { name: ${test[0]} };
730 `
731 );
732
733 const schema = res.schemas.Pet.properties.name;
734 deepStrictEqual(schema, test[1]);
735 });
736 }
737});
738
739describe("openapi3: literals", () => {
740 const cases = [
741 ["1", { type: "number", enum: [1] }],
742 ['"hello"', { type: "string", enum: ["hello"] }],
743 ["false", { type: "boolean", enum: [false] }],
744 ["true", { type: "boolean", enum: [true] }],
745 ];
746
747 for (const test of cases) {
748 it("knows schema for " + test[0], async () => {
749 const res = await oapiForModel(
750 "Pet",
751 `
752 model Pet { name: ${test[0]} };
753 `
754 );
755
756 const schema = res.schemas.Pet.properties.name;
757 deepStrictEqual(schema, test[1]);
758 });
759 }
760});
761
762describe("openapi3: operations", () => {
763 it("define operations with param with defaults", async () => {
764 const res = await openApiFor(
765 `
766 @route("/")
767 namespace root {
768 @get()
769 op read(@query queryWithDefault?: string = "defaultValue"): string;
770 }
771 `
772 );
773
774 deepStrictEqual(res.paths["/"].get.parameters[0].schema.default, "defaultValue");
775 });
776
777 it("define operations with param with decorators", async () => {
778 const res = await openApiFor(
779 `
780 @route("/thing")
781 namespace root {
782 @get
783 @route("{name}")
784 op getThing(
785 @pattern("^[a-zA-Z0-9-]{3,24}$")
786 @path name: string,
787
788 @minValue(1)
789 @maxValue(10)
790 @query count: int32
791 ): string;
792 }
793 `
794 );
795
796 const getThing = res.paths["/thing/{name}"].get;
797 ok(getThing);
798 ok(getThing.parameters[0].schema);
799 ok(getThing.parameters[0].schema.pattern);
800 strictEqual(getThing.parameters[0].schema.pattern, "^[a-zA-Z0-9-]{3,24}$");
801
802 ok(getThing.parameters[1].schema);
803 ok(getThing.parameters[1].schema.minimum);
804 ok(getThing.parameters[1].schema.maximum);
805 strictEqual(getThing.parameters[1].schema.minimum, 1);
806 strictEqual(getThing.parameters[1].schema.maximum, 10);
807 });
808});
809
810describe("openapi3: request", () => {
811 describe("binary request", () => {
812 it("bytes request should default to application/json byte", async () => {
813 const res = await openApiFor(
814 `
815 @route("/")
816 namespace root {
817 @post op read(@body body: bytes): {};
818 }
819 `
820 );
821
822 const requestBody = res.paths["/"].post.requestBody;
823 ok(requestBody);
824 strictEqual(requestBody.content["application/json"].schema.type, "string");
825 strictEqual(requestBody.content["application/json"].schema.format, "byte");
826 });
827
828 it("bytes request should respect @header contentType and use binary format when not json or text", async () => {
829 const res = await openApiFor(
830 `
831 @route("/")
832 namespace root {
833 @post op read(@header contentType: "image/png", @body body: bytes): {};
834 }
835 `
836 );
837
838 const requestBody = res.paths["/"].post.requestBody;
839 ok(requestBody);
840 strictEqual(requestBody.content["image/png"].schema.type, "string");
841 strictEqual(requestBody.content["image/png"].schema.format, "binary");
842 });
843 });
844});
845
846describe("openapi3: extension decorator", () => {
847 it("adds an arbitrary extension to a model", async () => {
848 const oapi = await openApiFor(
849 `
850 @extension("x-model-extension", "foobar")
851 model Pet {
852 name: string;
853 }
854 @route("/")
855 namespace root {
856 @get()
857 op read(): Pet;
858 }
859 `
860 );
861 ok(oapi.components.schemas.Pet);
862 strictEqual(oapi.components.schemas.Pet["x-model-extension"], "foobar");
863 });
864
865 it("adds an arbitrary extension to an operation", async () => {
866 const oapi = await openApiFor(
867 `
868 model Pet {
869 name: string;
870 }
871 @route("/")
872 namespace root {
873 @get()
874 @extension("x-operation-extension", "barbaz")
875 op list(): Pet[];
876 }
877 `
878 );
879 ok(oapi.paths["/"].get);
880 strictEqual(oapi.paths["/"].get["x-operation-extension"], "barbaz");
881 });
882
883 it("adds an arbitrary extension to a parameter", async () => {
884 const oapi = await openApiFor(
885 `
886 model Pet {
887 name: string;
888 }
889 model PetId {
890 @path
891 @extension("x-parameter-extension", "foobaz")
892 petId: string;
893 }
894 @route("/Pets")
895 namespace root {
896 @get()
897 op get(... PetId): Pet;
898 }
899 `
900 );
901 ok(oapi.paths["/Pets/{petId}"].get);
902 strictEqual(
903 oapi.paths["/Pets/{petId}"].get.parameters[0]["$ref"],
904 "#/components/parameters/PetId"
905 );
906 strictEqual(oapi.components.parameters.PetId.name, "petId");
907 strictEqual(oapi.components.parameters.PetId["x-parameter-extension"], "foobaz");
908 });
909
910 it("check format and pattern decorator on model", async () => {
911 const oapi = await openApiFor(
912 `
913 model Pet extends PetId {
914 @pattern("^[a-zA-Z0-9-]{3,24}$")
915 name: string;
916 }
917 model PetId {
918 @path
919 @pattern("^[a-zA-Z0-9-]{3,24}$")
920 @format("UUID")
921 petId: string;
922 }
923 @route("/Pets")
924 namespace root {
925 @get()
926 op get(... PetId): Pet;
927 }
928 `
929 );
930 ok(oapi.paths["/Pets/{petId}"].get);
931 strictEqual(
932 oapi.paths["/Pets/{petId}"].get.parameters[0]["$ref"],
933 "#/components/parameters/PetId"
934 );
935 strictEqual(oapi.components.parameters.PetId.name, "petId");
936 strictEqual(oapi.components.schemas.Pet.properties.name.pattern, "^[a-zA-Z0-9-]{3,24}$");
937 strictEqual(oapi.components.parameters.PetId.schema.format, "UUID");
938 strictEqual(oapi.components.parameters.PetId.schema.pattern, "^[a-zA-Z0-9-]{3,24}$");
939 });
940});
941