diff --git a/packages/playground-website/package.json b/packages/playground-website/package.json index 0c3464ae940..dba52d26432 100644 --- a/packages/playground-website/package.json +++ b/packages/playground-website/package.json @@ -69,6 +69,7 @@ "@typespec/playground": "workspace:^", "@typespec/protobuf": "workspace:^", "@typespec/rest": "workspace:^", + "@typespec/samples": "workspace:^", "@typespec/sse": "workspace:^", "@typespec/streams": "workspace:^", "@typespec/versioning": "workspace:^", diff --git a/packages/playground-website/samples/build.ts b/packages/playground-website/samples/build.ts index db15eb986cc..888d9232e97 100644 --- a/packages/playground-website/samples/build.ts +++ b/packages/playground-website/samples/build.ts @@ -1,47 +1,14 @@ // @ts-check -import { buildSamples_experimental } from "@typespec/playground/tooling"; +import { buildPlaygroundSamples } from "@typespec/samples"; import { dirname, resolve } from "path"; import { fileURLToPath } from "url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const packageRoot = resolve(__dirname, ".."); -await buildSamples_experimental(packageRoot, resolve(__dirname, "dist/samples.ts"), { - "API versioning": { - filename: "samples/versioning.tsp", - preferredEmitter: "@typespec/openapi3", - description: "Learn how to version your API using TypeSpec's versioning library.", - }, - "Discriminated unions": { - filename: "samples/unions.tsp", - preferredEmitter: "@typespec/openapi3", - description: "Define discriminated unions for polymorphic types with different variants.", - }, - "HTTP service": { - filename: "samples/http.tsp", - preferredEmitter: "@typespec/openapi3", - compilerOptions: { linterRuleSet: { extends: ["@typespec/http/all"] } }, - description: "Build an HTTP service with routes, parameters, and responses.", - }, - "REST framework": { - filename: "samples/rest.tsp", - preferredEmitter: "@typespec/openapi3", - compilerOptions: { linterRuleSet: { extends: ["@typespec/http/all"] } }, - description: "Use the REST framework for resource-oriented API design patterns.", - }, - "Protobuf Kiosk": { - filename: "samples/kiosk.tsp", - preferredEmitter: "@typespec/protobuf", - description: "Generate Protocol Buffer definitions from TypeSpec models.", - }, - "Json Schema": { - filename: "samples/json-schema.tsp", - preferredEmitter: "@typespec/json-schema", - description: "Emit JSON Schema from TypeSpec type definitions.", - }, - GraphQL: { - filename: "samples/graphql.tsp", - preferredEmitter: "@typespec/graphql", - description: "Generate GraphQL schemas with queries, mutations, and types.", - }, +await buildPlaygroundSamples({ + specsDir: resolve(packageRoot, "../samples/specs"), + outputFile: resolve(__dirname, "dist/samples.ts"), + relativeTo: packageRoot, + defaultEmitter: "@typespec/openapi3", }); diff --git a/packages/playground-website/samples/graphql.tsp b/packages/playground-website/samples/graphql.tsp deleted file mode 100644 index 1e91f9a8db9..00000000000 --- a/packages/playground-website/samples/graphql.tsp +++ /dev/null @@ -1,36 +0,0 @@ -import "@typespec/graphql"; - -using GraphQL; - -@schema -namespace PetStore; - -/** A pet in the store */ -model Pet { - id: string; - name: string; - tag?: string; - status: PetStatus; -} - -/** Pet status in the store */ -enum PetStatus { - Available, - Pending, - Sold, -} - -/** A toy that belongs to a pet */ -model Toy { - id: string; - name: string; - pet: Pet; -} - -@query op getPet(id: string): Pet | null; -@query op listPets(status?: PetStatus, limit?: int32): Pet[]; -@query op getPetToys(petId: string): Toy[]; - -@mutation op createPet(name: string, tag: string, status: PetStatus): Pet; -@mutation op updatePet(id: string, name?: string, status?: PetStatus): Pet | null; -@mutation op deletePet(id: string): boolean; diff --git a/packages/playground-website/samples/kiosk.tsp b/packages/playground-website/samples/kiosk.tsp deleted file mode 100644 index d34155febf5..00000000000 --- a/packages/playground-website/samples/kiosk.tsp +++ /dev/null @@ -1,107 +0,0 @@ -import "@typespec/protobuf"; - -using Protobuf; - -@package({ - name: "kiosk", -}) -namespace KioskExample; - -@TypeSpec.Protobuf.service -interface Display { - /** - * Create a new kiosk. This enrolls the kiosk for sign display. - */ - createKiosk(...Kiosk): Kiosk; - - /** - * List active kiosks. - */ - listKiosks(...WellKnown.Empty): { - @field(1) kiosks: Kiosk[]; - }; - - /** - * Get a kiosk. - */ - getKiosk(@field(1) id: int32): Kiosk; - - /** - * Delete a kiosk. - */ - deleteKiosk(@field(1) id: int32): void; - - /** - * Create a new sign. - */ - createSign(...Sign): Sign; - - /** - * List active signs. - */ - listSigns(...WellKnown.Empty): { - @field(1) signs: Sign[]; - }; - - /** - * Get a sign. - */ - getSign(@field(1) id: int32): Sign; - - /** - * Delete a sign. - */ - deleteSign(@field(1) id: int32): void; - - /** - * Set a sign for display on one or more kiosks - */ - setSignIdForKioskIds(@field(1) kiosk_ids: int32[], @field(2) sign_id: int32): void; - - /** - * Get the sign that should be displayed on a kiosk. - */ - getSignIdForKioskId(@field(1) kiosk_id: int32): GetSignIdResponse; - /** - * Get signs that should be displayed on a kiosk. Streams. - */ - @stream(StreamMode.Out) - getSignIdsforKioskId(@field(1) kiosk_id: int32): GetSignIdResponse; -} - -model Kiosk { - // Output only. - @field(1) id?: int32; - - // Required. - @field(2) name: string; - - @field(3) size: ScreenSize; - @field(4) location: WellKnown.LatLng; - - // Output only. - @field(5) create_time?: WellKnown.Timestamp; -} - -model Sign { - // Output only. - @field(1) id?: int32; - - // Required. - @field(2) name: string; - - @field(3) text: string; - @field(4) image: bytes; - - // Output only. - @field(5) create_time?: WellKnown.Timestamp; -} - -model ScreenSize { - @field(1) width: int32; - @field(2) height: int32; -} - -model GetSignIdResponse { - @field(1) sign_id: int32; -} diff --git a/packages/samples/README.md b/packages/samples/README.md index e26203e4753..4338f97384b 100644 --- a/packages/samples/README.md +++ b/packages/samples/README.md @@ -4,6 +4,18 @@ This project has a collection of samples used to demonstrate and test various Ty It is not published as an npm package. +Samples should teach a distinct TypeSpec concept or realistic workflow. Narrow protocol test +cases belong in the relevant package test suite instead of this collection. + +Each sample directory has a `sample-config.yaml` containing a unique `title` and a +`description`. Set `playground: false` for samples that require multiple files or are not suitable +for the playground. A directory config uses `directory: true` and can provide a gallery `label`, +`order`, or an inherited `playground: false`. + +The TypeSpec playground is generated from the eligible samples in this package. A playground +sample must contain a single `main.tsp`; its nearest `tspconfig.yaml` selects the preferred emitter +and linter rule set, including inherited configuration. + ```bash npm run test # Check Samples match snapshots npm run test:ci # run test same as CI diff --git a/packages/samples/package.json b/packages/samples/package.json index 438b6c5afd0..d039df0934f 100644 --- a/packages/samples/package.json +++ b/packages/samples/package.json @@ -46,6 +46,7 @@ "@typespec/best-practices": "workspace:^", "@typespec/compiler": "workspace:^", "@typespec/events": "workspace:^", + "@typespec/graphql": "workspace:^", "@typespec/html-program-viewer": "workspace:^", "@typespec/http": "workspace:^", "@typespec/http-server-csharp": "workspace:^", @@ -57,7 +58,8 @@ "@typespec/rest": "workspace:^", "@typespec/sse": "workspace:^", "@typespec/streams": "workspace:^", - "@typespec/versioning": "workspace:^" + "@typespec/versioning": "workspace:^", + "yaml": "catalog:" }, "devDependencies": { "@types/node": "catalog:", diff --git a/packages/samples/specs/authentication/sample-config.yaml b/packages/samples/specs/authentication/sample-config.yaml new file mode 100644 index 00000000000..d446705ee5b --- /dev/null +++ b/packages/samples/specs/authentication/sample-config.yaml @@ -0,0 +1,3 @@ +title: Authentication +description: Apply authentication schemes at service, interface, and operation scope. +playground: false diff --git a/packages/samples/specs/binary/sample-config.yaml b/packages/samples/specs/binary/sample-config.yaml new file mode 100644 index 00000000000..9a863c88598 --- /dev/null +++ b/packages/samples/specs/binary/sample-config.yaml @@ -0,0 +1,3 @@ +title: Binary data +description: Model binary request and response payloads. +playground: false diff --git a/packages/samples/specs/documentation/sample-config.yaml b/packages/samples/specs/documentation/sample-config.yaml new file mode 100644 index 00000000000..b8b4726775d --- /dev/null +++ b/packages/samples/specs/documentation/sample-config.yaml @@ -0,0 +1,3 @@ +title: Documentation +description: Add documentation to TypeSpec declarations and emitted API descriptions. +playground: false diff --git a/packages/samples/specs/emitters/graphql/main.tsp b/packages/samples/specs/emitters/graphql/main.tsp new file mode 100644 index 00000000000..066e48d67ec --- /dev/null +++ b/packages/samples/specs/emitters/graphql/main.tsp @@ -0,0 +1,46 @@ +import "@typespec/graphql"; + +using GraphQL; + +@schema +namespace PetStore; + +/** A pet in the store. */ +model Pet { + id: string; + name: string; + tag?: string; + status: PetStatus; +} + +/** A pet's status in the store. */ +enum PetStatus { + Available, + Pending, + Sold, +} + +/** A toy that belongs to a pet. */ +model Toy { + id: string; + name: string; + pet: Pet; +} + +@query +op getPet(id: string): Pet | null; + +@query +op listPets(status?: PetStatus, limit?: int32): Pet[]; + +@query +op getPetToys(petId: string): Toy[]; + +@mutation +op createPet(name: string, tag: string, status: PetStatus): Pet; + +@mutation +op updatePet(id: string, name?: string, status?: PetStatus): Pet | null; + +@mutation +op deletePet(id: string): boolean; diff --git a/packages/samples/specs/emitters/graphql/sample-config.yaml b/packages/samples/specs/emitters/graphql/sample-config.yaml new file mode 100644 index 00000000000..e441137849d --- /dev/null +++ b/packages/samples/specs/emitters/graphql/sample-config.yaml @@ -0,0 +1,3 @@ +title: GraphQL +description: Generate a GraphQL schema with queries, mutations, models, and enums. +order: 3 diff --git a/packages/samples/specs/emitters/graphql/tspconfig.yaml b/packages/samples/specs/emitters/graphql/tspconfig.yaml new file mode 100644 index 00000000000..c8676afee2a --- /dev/null +++ b/packages/samples/specs/emitters/graphql/tspconfig.yaml @@ -0,0 +1,2 @@ +emit: + - "@typespec/graphql" diff --git a/packages/playground-website/samples/json-schema.tsp b/packages/samples/specs/emitters/json-schema/main.tsp similarity index 55% rename from packages/playground-website/samples/json-schema.tsp rename to packages/samples/specs/emitters/json-schema/main.tsp index 1a6ea0eb2ba..95c5e4f635d 100644 --- a/packages/playground-website/samples/json-schema.tsp +++ b/packages/samples/specs/emitters/json-schema/main.tsp @@ -12,32 +12,29 @@ model Person { /** The person's last name. */ lastName: string; - /** Age in years which must be equal to or greater than zero. */ - @minValue(0) age: int32; + /** Age in years, which must be zero or greater. */ + @minValue(0) + age: int32; - /** Person address */ + /** The person's address. */ address: Address; - /** List of nick names */ - @uniqueItems nickNames?: string[]; + /** The person's nicknames. */ + @uniqueItems + nickNames?: string[]; - /** List of cars person owns */ + /** Cars owned by the person. */ cars?: Car[]; } -/** Respresent an address */ model Address { street: string; city: string; country: string; } + model Car { - /** Kind of car */ kind: "ev" | "ice"; - - /** Brand of the car */ brand: string; - - /** Model of the car */ `model`: string; } diff --git a/packages/samples/specs/emitters/json-schema/sample-config.yaml b/packages/samples/specs/emitters/json-schema/sample-config.yaml new file mode 100644 index 00000000000..767b7ab8d1e --- /dev/null +++ b/packages/samples/specs/emitters/json-schema/sample-config.yaml @@ -0,0 +1,3 @@ +title: JSON Schema +description: Generate JSON Schema documents from TypeSpec models and constraints. +order: 2 diff --git a/packages/samples/specs/emitters/json-schema/tspconfig.yaml b/packages/samples/specs/emitters/json-schema/tspconfig.yaml new file mode 100644 index 00000000000..a67708da567 --- /dev/null +++ b/packages/samples/specs/emitters/json-schema/tspconfig.yaml @@ -0,0 +1,2 @@ +emit: + - "@typespec/json-schema" diff --git a/packages/samples/specs/emitters/protobuf-kiosk/main.tsp b/packages/samples/specs/emitters/protobuf-kiosk/main.tsp new file mode 100644 index 00000000000..7dc5dfae896 --- /dev/null +++ b/packages/samples/specs/emitters/protobuf-kiosk/main.tsp @@ -0,0 +1,74 @@ +import "@typespec/protobuf"; + +using Protobuf; + +@package({ + name: "kiosk", +}) +namespace KioskExample; + +@TypeSpec.Protobuf.service +interface Display { + /** Create a new kiosk and enroll it for sign display. */ + createKiosk(...Kiosk): Kiosk; + + /** List active kiosks. */ + listKiosks(...WellKnown.Empty): { + @field(1) kiosks: Kiosk[]; + }; + + /** Get a kiosk. */ + getKiosk(@field(1) id: int32): Kiosk; + + /** Delete a kiosk. */ + deleteKiosk(@field(1) id: int32): void; + + /** Create a new sign. */ + createSign(...Sign): Sign; + + /** List active signs. */ + listSigns(...WellKnown.Empty): { + @field(1) signs: Sign[]; + }; + + /** Get a sign. */ + getSign(@field(1) id: int32): Sign; + + /** Delete a sign. */ + deleteSign(@field(1) id: int32): void; + + /** Set a sign for display on one or more kiosks. */ + setSignIdForKioskIds(@field(1) kioskIds: int32[], @field(2) signId: int32): void; + + /** Get the sign that should be displayed on a kiosk. */ + getSignIdForKioskId(@field(1) kioskId: int32): GetSignIdResponse; + + /** Stream the signs that should be displayed on a kiosk. */ + @stream(StreamMode.Out) + getSignIdsForKioskId(@field(1) kioskId: int32): GetSignIdResponse; +} + +model Kiosk { + @field(1) id?: int32; + @field(2) name: string; + @field(3) size: ScreenSize; + @field(4) location: WellKnown.LatLng; + @field(5) createTime?: WellKnown.Timestamp; +} + +model Sign { + @field(1) id?: int32; + @field(2) name: string; + @field(3) text: string; + @field(4) image: bytes; + @field(5) createTime?: WellKnown.Timestamp; +} + +model ScreenSize { + @field(1) width: int32; + @field(2) height: int32; +} + +model GetSignIdResponse { + @field(1) signId: int32; +} diff --git a/packages/samples/specs/emitters/protobuf-kiosk/sample-config.yaml b/packages/samples/specs/emitters/protobuf-kiosk/sample-config.yaml new file mode 100644 index 00000000000..5015ed97628 --- /dev/null +++ b/packages/samples/specs/emitters/protobuf-kiosk/sample-config.yaml @@ -0,0 +1,3 @@ +title: Protobuf kiosk +description: Generate Protocol Buffer service and message definitions for a kiosk system. +order: 1 diff --git a/packages/samples/specs/emitters/protobuf-kiosk/tspconfig.yaml b/packages/samples/specs/emitters/protobuf-kiosk/tspconfig.yaml new file mode 100644 index 00000000000..daedd9b709b --- /dev/null +++ b/packages/samples/specs/emitters/protobuf-kiosk/tspconfig.yaml @@ -0,0 +1,2 @@ +emit: + - "@typespec/protobuf" diff --git a/packages/samples/specs/emitters/sample-config.yaml b/packages/samples/specs/emitters/sample-config.yaml new file mode 100644 index 00000000000..0e970f53d70 --- /dev/null +++ b/packages/samples/specs/emitters/sample-config.yaml @@ -0,0 +1,3 @@ +directory: true +label: Emitters +order: 3 diff --git a/packages/samples/specs/encoded-names/sample-config.yaml b/packages/samples/specs/encoded-names/sample-config.yaml new file mode 100644 index 00000000000..ed17c381cd0 --- /dev/null +++ b/packages/samples/specs/encoded-names/sample-config.yaml @@ -0,0 +1,3 @@ +title: Encoded names +description: Customize declaration names for specific emitters. +playground: false diff --git a/packages/samples/specs/encoding/sample-config.yaml b/packages/samples/specs/encoding/sample-config.yaml new file mode 100644 index 00000000000..7dc42c72e36 --- /dev/null +++ b/packages/samples/specs/encoding/sample-config.yaml @@ -0,0 +1,3 @@ +title: Value encoding +description: Configure serialization formats for scalar values. +playground: false diff --git a/packages/playground-website/samples/http.tsp b/packages/samples/specs/getting-started/http-service/main.tsp similarity index 72% rename from packages/playground-website/samples/http.tsp rename to packages/samples/specs/getting-started/http-service/main.tsp index 82fe8a0246f..0135d7e9492 100644 --- a/packages/playground-website/samples/http.tsp +++ b/packages/samples/specs/getting-started/http-service/main.tsp @@ -1,6 +1,7 @@ import "@typespec/http"; using Http; + @service(#{ title: "Widget Service" }) namespace DemoService; @@ -21,20 +22,23 @@ model Error { @route("/widgets") @tag("Widgets") interface Widgets { - /** List widgets */ + /** List widgets. */ @get list(): Widget[] | Error; - /** Read widgets */ + /** Read a widget. */ @get read(@path id: Widget.id): Widget | Error; - /** Create a widget */ + /** Create a widget. */ @post create(@body widget: Widget): Widget | Error; - /** Update a widget */ + /** Update a widget. */ @patch update(@path id: Widget.id, @body widget: MergePatchUpdate): Widget | Error; - /** Delete a widget */ + /** Delete a widget. */ @delete delete(@path id: Widget.id): void | Error; - /** Analyze a widget */ - @route("{id}/analyze") @post analyze(@path id: Widget.id): string | Error; + + /** Analyze a widget. */ + @route("{id}/analyze") + @post + analyze(@path id: Widget.id): string | Error; } diff --git a/packages/samples/specs/getting-started/http-service/sample-config.yaml b/packages/samples/specs/getting-started/http-service/sample-config.yaml new file mode 100644 index 00000000000..114db21f9a6 --- /dev/null +++ b/packages/samples/specs/getting-started/http-service/sample-config.yaml @@ -0,0 +1,3 @@ +title: HTTP service +description: Build an HTTP service with routes, parameters, request bodies, and responses. +order: 1 diff --git a/packages/samples/specs/getting-started/http-service/tspconfig.yaml b/packages/samples/specs/getting-started/http-service/tspconfig.yaml new file mode 100644 index 00000000000..c603bb9c94f --- /dev/null +++ b/packages/samples/specs/getting-started/http-service/tspconfig.yaml @@ -0,0 +1,5 @@ +emit: + - "@typespec/openapi3" +linter: + extends: + - "@typespec/http/all" diff --git a/packages/playground-website/samples/rest.tsp b/packages/samples/specs/getting-started/rest-framework/main.tsp similarity index 87% rename from packages/playground-website/samples/rest.tsp rename to packages/samples/specs/getting-started/rest-framework/main.tsp index 3e4ee58d2bb..431495db274 100644 --- a/packages/playground-website/samples/rest.tsp +++ b/packages/samples/specs/getting-started/rest-framework/main.tsp @@ -1,12 +1,12 @@ import "@typespec/http"; import "@typespec/rest"; -@service(#{ title: "Widget Service" }) -namespace DemoService; - using Http; using Rest; +@service(#{ title: "Widget Service" }) +namespace DemoService; + model Widget { @key id: string; weight: int32; @@ -20,5 +20,7 @@ model Error { } interface WidgetService extends Resource.ResourceOperations { - @get @route("customGet") customGet(): Widget; + @get + @route("customGet") + customGet(): Widget; } diff --git a/packages/samples/specs/getting-started/rest-framework/sample-config.yaml b/packages/samples/specs/getting-started/rest-framework/sample-config.yaml new file mode 100644 index 00000000000..6183192fd70 --- /dev/null +++ b/packages/samples/specs/getting-started/rest-framework/sample-config.yaml @@ -0,0 +1,3 @@ +title: REST framework +description: Use resource-oriented REST templates to define a service with less repetition. +order: 2 diff --git a/packages/samples/specs/getting-started/rest-framework/tspconfig.yaml b/packages/samples/specs/getting-started/rest-framework/tspconfig.yaml new file mode 100644 index 00000000000..c603bb9c94f --- /dev/null +++ b/packages/samples/specs/getting-started/rest-framework/tspconfig.yaml @@ -0,0 +1,5 @@ +emit: + - "@typespec/openapi3" +linter: + extends: + - "@typespec/http/all" diff --git a/packages/samples/specs/getting-started/sample-config.yaml b/packages/samples/specs/getting-started/sample-config.yaml new file mode 100644 index 00000000000..6ad35826c10 --- /dev/null +++ b/packages/samples/specs/getting-started/sample-config.yaml @@ -0,0 +1,3 @@ +directory: true +label: Getting Started +order: 1 diff --git a/packages/samples/specs/grpc-kiosk-example/sample-config.yaml b/packages/samples/specs/grpc-kiosk-example/sample-config.yaml new file mode 100644 index 00000000000..c1fdfdfc354 --- /dev/null +++ b/packages/samples/specs/grpc-kiosk-example/sample-config.yaml @@ -0,0 +1,3 @@ +title: Advanced gRPC kiosk +description: Demonstrate a multi-file Protobuf-oriented kiosk service. +playground: false diff --git a/packages/samples/specs/grpc-library-example/sample-config.yaml b/packages/samples/specs/grpc-library-example/sample-config.yaml new file mode 100644 index 00000000000..6b8b2d29b6b --- /dev/null +++ b/packages/samples/specs/grpc-library-example/sample-config.yaml @@ -0,0 +1,3 @@ +title: Protobuf library +description: Build and consume reusable Protobuf declarations. +playground: false diff --git a/packages/samples/specs/init/sample-config.yaml b/packages/samples/specs/init/sample-config.yaml new file mode 100644 index 00000000000..09c903dd11a --- /dev/null +++ b/packages/samples/specs/init/sample-config.yaml @@ -0,0 +1,3 @@ +title: Project initialization +description: Show the service generated by the TypeSpec project initializer. +playground: false diff --git a/packages/playground-website/samples/versioning.tsp b/packages/samples/specs/language/api-versioning/main.tsp similarity index 93% rename from packages/playground-website/samples/versioning.tsp rename to packages/samples/specs/language/api-versioning/main.tsp index e7a0e0ad254..0a5283d487a 100644 --- a/packages/playground-website/samples/versioning.tsp +++ b/packages/samples/specs/language/api-versioning/main.tsp @@ -2,6 +2,8 @@ import "@typespec/http"; import "@typespec/rest"; import "@typespec/versioning"; +using Http; +using Rest; using Versioning; @versioned(Versions) @@ -13,14 +15,13 @@ enum Versions { v2, } -using Http; -using Rest; - model Widget { @key id: string; weight: int32; color: "red" | "blue"; - @added(Versions.v2) name: string; + + @added(Versions.v2) + name: string; } @error diff --git a/packages/samples/specs/language/api-versioning/sample-config.yaml b/packages/samples/specs/language/api-versioning/sample-config.yaml new file mode 100644 index 00000000000..fd3788cf1c9 --- /dev/null +++ b/packages/samples/specs/language/api-versioning/sample-config.yaml @@ -0,0 +1,3 @@ +title: API versioning +description: Evolve an API across versions with version-aware types and operations. +order: 1 diff --git a/packages/samples/specs/language/api-versioning/tspconfig.yaml b/packages/samples/specs/language/api-versioning/tspconfig.yaml new file mode 100644 index 00000000000..a3fe48f13e9 --- /dev/null +++ b/packages/samples/specs/language/api-versioning/tspconfig.yaml @@ -0,0 +1,2 @@ +emit: + - "@typespec/openapi3" diff --git a/packages/playground-website/samples/unions.tsp b/packages/samples/specs/language/discriminated-unions/main.tsp similarity index 83% rename from packages/playground-website/samples/unions.tsp rename to packages/samples/specs/language/discriminated-unions/main.tsp index e49f1f27c2b..a33fc673bbc 100644 --- a/packages/playground-website/samples/unions.tsp +++ b/packages/samples/specs/language/discriminated-unions/main.tsp @@ -1,12 +1,10 @@ import "@typespec/http"; -import "@typespec/rest"; import "@typespec/openapi3"; +using Http; + @service(#{ title: "Widget Service" }) namespace DemoService; -using Rest; -using Http; -using OpenAPI; model WidgetBase { @key id: string; @@ -39,4 +37,5 @@ model Error { message: string; } -@get op read(@path id: string): Widget | Error; +@get +op read(@path id: string): Widget | Error; diff --git a/packages/samples/specs/language/discriminated-unions/sample-config.yaml b/packages/samples/specs/language/discriminated-unions/sample-config.yaml new file mode 100644 index 00000000000..a9ce4bda7ae --- /dev/null +++ b/packages/samples/specs/language/discriminated-unions/sample-config.yaml @@ -0,0 +1,3 @@ +title: Discriminated unions +description: Model polymorphic values with a discriminated union. +order: 2 diff --git a/packages/samples/specs/language/discriminated-unions/tspconfig.yaml b/packages/samples/specs/language/discriminated-unions/tspconfig.yaml new file mode 100644 index 00000000000..a3fe48f13e9 --- /dev/null +++ b/packages/samples/specs/language/discriminated-unions/tspconfig.yaml @@ -0,0 +1,2 @@ +emit: + - "@typespec/openapi3" diff --git a/packages/samples/specs/language/sample-config.yaml b/packages/samples/specs/language/sample-config.yaml new file mode 100644 index 00000000000..9c19a8697ea --- /dev/null +++ b/packages/samples/specs/language/sample-config.yaml @@ -0,0 +1,3 @@ +directory: true +label: Language +order: 2 diff --git a/packages/samples/specs/local-typespec/sample-config.yaml b/packages/samples/specs/local-typespec/sample-config.yaml new file mode 100644 index 00000000000..f0f127fea64 --- /dev/null +++ b/packages/samples/specs/local-typespec/sample-config.yaml @@ -0,0 +1,3 @@ +title: Local TypeSpec installation +description: Configure an editor to use a project-local TypeSpec compiler. +playground: false diff --git a/packages/samples/specs/multipart/sample-config.yaml b/packages/samples/specs/multipart/sample-config.yaml new file mode 100644 index 00000000000..821e94f5001 --- /dev/null +++ b/packages/samples/specs/multipart/sample-config.yaml @@ -0,0 +1,3 @@ +title: Multipart requests +description: Define multipart form data with files and structured fields. +playground: false diff --git a/packages/samples/specs/multiple-types-union/main.tsp b/packages/samples/specs/multiple-types-union/main.tsp deleted file mode 100644 index 346f8e28d2b..00000000000 --- a/packages/samples/specs/multiple-types-union/main.tsp +++ /dev/null @@ -1,34 +0,0 @@ -import "@typespec/rest"; -import "@typespec/openapi"; - -@service(#{ title: "Pet Store Service" }) -namespace PetStore; -using Http; - -model ApiVersionParam { - @header apiVersion: string; -} - -model PetBase { - name: string; -} - -model Dog extends PetBase { - type: "dog"; - nextWalkTime: utcDateTime; -} - -model Cat extends PetBase { - type: "cat"; - catnipDose: int32; -} - -union Pet { - cat: Cat, - dog: Dog, -} - -@route("/") -interface MyService { - getPet(...ApiVersionParam): Body; -} diff --git a/packages/samples/specs/nested/main.tsp b/packages/samples/specs/nested/main.tsp deleted file mode 100644 index c7f142b0742..00000000000 --- a/packages/samples/specs/nested/main.tsp +++ /dev/null @@ -1 +0,0 @@ -import "./nested.tsp"; diff --git a/packages/samples/specs/nested/nested.tsp b/packages/samples/specs/nested/nested.tsp deleted file mode 100644 index 8b8de6e48e4..00000000000 --- a/packages/samples/specs/nested/nested.tsp +++ /dev/null @@ -1,37 +0,0 @@ -import "@typespec/rest"; - -using Http; - -@service(#{ title: "Nested sample" }) -namespace Root { - @route("/sub/a") - namespace SubA { - model Thing { - id: int64; - } - - @route("subsub") - namespace SubSubA { - @post op doSomething(@body thing: Thing): OkResponse & Body; - - // Doesn't conflict with parent namespace! - model Thing { - name: string; - } - } - } - - @route("/sub/b") - namespace SubB { - // Uses the same name as - model Thing { - id: int64; - } - - @post op doSomething(@body thing: Thing): string; - } - - namespace SubC { - @post op anotherOp(thing: Root.SubA.Thing, thing2: SubA.Thing): string; - } -} diff --git a/packages/samples/specs/nullable/main.tsp b/packages/samples/specs/nullable/main.tsp deleted file mode 100644 index e4190004fff..00000000000 --- a/packages/samples/specs/nullable/main.tsp +++ /dev/null @@ -1 +0,0 @@ -import "./nullable.tsp"; diff --git a/packages/samples/specs/nullable/nullable.tsp b/packages/samples/specs/nullable/nullable.tsp deleted file mode 100644 index fb177513100..00000000000 --- a/packages/samples/specs/nullable/nullable.tsp +++ /dev/null @@ -1,31 +0,0 @@ -import "@typespec/rest"; -import "@typespec/openapi"; - -using Http; - -@service(#{ title: "Nullable sample" }) -namespace NullableSample; - -model HasNullables { - str: string; - when: plainTime; - strOrNull: string | null; - modelOrNull: AnotherModel | null; - literalsOrNull: "one" | "two" | null; - manyNullsOneString: null | null | string | null; - manyNullsSomeValues: null | 42 | null | 100 | null; - arr: string[] | null; - // thisWillFail: AnotherModel | string | null; -} - -@route("/test") -namespace NullableMethods { - @get op read( - @query someParam: string | null, - @body modelOrNull: AnotherModel | null, - ): HasNullables; -} - -model AnotherModel { - num: int32; -} diff --git a/packages/samples/specs/openapi-extensions/sample-config.yaml b/packages/samples/specs/openapi-extensions/sample-config.yaml new file mode 100644 index 00000000000..d4e66f4dd5c --- /dev/null +++ b/packages/samples/specs/openapi-extensions/sample-config.yaml @@ -0,0 +1,3 @@ +title: OpenAPI extensions +description: Attach custom OpenAPI extensions to emitted declarations. +playground: false diff --git a/packages/samples/specs/optional/main.tsp b/packages/samples/specs/optional/main.tsp deleted file mode 100644 index 953235bacef..00000000000 --- a/packages/samples/specs/optional/main.tsp +++ /dev/null @@ -1 +0,0 @@ -import "./optional.tsp"; diff --git a/packages/samples/specs/optional/optional.tsp b/packages/samples/specs/optional/optional.tsp deleted file mode 100644 index ddfd3a05e2d..00000000000 --- a/packages/samples/specs/optional/optional.tsp +++ /dev/null @@ -1,29 +0,0 @@ -import "@typespec/rest"; - -using Http; - -@service(#{ title: "Optional sample" }) -namespace OptionalSample; - -model HasOptional { - optionalNoDefault?: string; - optionalString?: string = "default string"; - optionalNumber?: int32 = 123; - optionalBoolean?: boolean = true; - optionalArray?: string[] = #["foo", "bar"]; - optionalUnion?: "foo" | "bar" = "foo"; - optionalEnum?: MyEnum = MyEnum.a; -} - -@route("/test") -namespace OptionalMethods { - @get op read( - @query queryString?: string = "defaultQueryString", - @body queryNumber?: int64 = 123, - ): HasOptional; -} - -enum MyEnum { - a, - b, -} diff --git a/packages/samples/specs/param-decorators/sample-config.yaml b/packages/samples/specs/param-decorators/sample-config.yaml new file mode 100644 index 00000000000..32eb0c2abd2 --- /dev/null +++ b/packages/samples/specs/param-decorators/sample-config.yaml @@ -0,0 +1,3 @@ +title: Parameter decorators +description: Apply decorators through reusable operation parameters. +playground: false diff --git a/packages/samples/specs/petstore/sample-config.yaml b/packages/samples/specs/petstore/sample-config.yaml new file mode 100644 index 00000000000..b32cb866a1d --- /dev/null +++ b/packages/samples/specs/petstore/sample-config.yaml @@ -0,0 +1,3 @@ +title: Extensible Pet Store +description: Extend TypeSpec with a local decorator and custom project files. +playground: false diff --git a/packages/samples/specs/polymorphism/sample-config.yaml b/packages/samples/specs/polymorphism/sample-config.yaml new file mode 100644 index 00000000000..70c2bc8fca3 --- /dev/null +++ b/packages/samples/specs/polymorphism/sample-config.yaml @@ -0,0 +1,3 @@ +title: Polymorphic models +description: Model inheritance and discriminated polymorphic responses. +playground: false diff --git a/packages/samples/specs/rest-metadata-emitter/sample-config.yaml b/packages/samples/specs/rest-metadata-emitter/sample-config.yaml new file mode 100644 index 00000000000..cc8b0a0ed3a --- /dev/null +++ b/packages/samples/specs/rest-metadata-emitter/sample-config.yaml @@ -0,0 +1,3 @@ +title: REST metadata emitter +description: Inspect REST metadata from a custom emitter. +playground: false diff --git a/packages/samples/specs/rest/petstore/sample-config.yaml b/packages/samples/specs/rest/petstore/sample-config.yaml new file mode 100644 index 00000000000..347b239cb9c --- /dev/null +++ b/packages/samples/specs/rest/petstore/sample-config.yaml @@ -0,0 +1,3 @@ +title: REST Pet Store +description: Define a resource-oriented Pet Store service. +playground: false diff --git a/packages/samples/specs/signatures/sample-config.yaml b/packages/samples/specs/signatures/sample-config.yaml new file mode 100644 index 00000000000..a08e5308e92 --- /dev/null +++ b/packages/samples/specs/signatures/sample-config.yaml @@ -0,0 +1,3 @@ +title: Operation signatures +description: Compare operation declaration and template signatures. +playground: false diff --git a/packages/samples/specs/simple/main.tsp b/packages/samples/specs/simple/main.tsp deleted file mode 100644 index 2f519503468..00000000000 --- a/packages/samples/specs/simple/main.tsp +++ /dev/null @@ -1 +0,0 @@ -import "./simple.tsp"; diff --git a/packages/samples/specs/simple/simple.tsp b/packages/samples/specs/simple/simple.tsp deleted file mode 100644 index de103cc73eb..00000000000 --- a/packages/samples/specs/simple/simple.tsp +++ /dev/null @@ -1,9 +0,0 @@ -import "@typespec/rest"; - -using Http; - -@route("/alpha/{id}") -op doAlpha(id: string): string; - -@route("/beta/{id}") -op doBeta(id: string): string; diff --git a/packages/samples/specs/status-code-ranges/sample-config.yaml b/packages/samples/specs/status-code-ranges/sample-config.yaml new file mode 100644 index 00000000000..8bde09833b2 --- /dev/null +++ b/packages/samples/specs/status-code-ranges/sample-config.yaml @@ -0,0 +1,3 @@ +title: Status code ranges +description: Describe HTTP responses covering status code ranges. +playground: false diff --git a/packages/samples/specs/string-template/sample-config.yaml b/packages/samples/specs/string-template/sample-config.yaml new file mode 100644 index 00000000000..0283f256347 --- /dev/null +++ b/packages/samples/specs/string-template/sample-config.yaml @@ -0,0 +1,3 @@ +title: String templates +description: Construct values using TypeSpec string templates. +playground: false diff --git a/packages/samples/specs/tags/sample-config.yaml b/packages/samples/specs/tags/sample-config.yaml new file mode 100644 index 00000000000..b33bcb3e2fa --- /dev/null +++ b/packages/samples/specs/tags/sample-config.yaml @@ -0,0 +1,3 @@ +title: OpenAPI tags +description: Group emitted operations with tags. +playground: false diff --git a/packages/samples/specs/testserver/body-boolean/body-boolean.tsp b/packages/samples/specs/testserver/body-boolean/body-boolean.tsp deleted file mode 100644 index c6f860712c2..00000000000 --- a/packages/samples/specs/testserver/body-boolean/body-boolean.tsp +++ /dev/null @@ -1,67 +0,0 @@ -import "@typespec/rest"; -import "@typespec/openapi"; - -using Http; -using OpenAPI; - -@service(#{ title: "sample" }) -namespace Sample; - -@doc("Error") -@error -model Error { - code: int32; - message: string; -} - -alias NullableBoolean = boolean | null; - -@doc("AutoRest Bool Test Service") -@route("/bool/true") -namespace BoolsTrue { - @doc("Get true Boolean value") - @operationId("bool_getTrue") - @get - op getTrue(): true | Error; - - @doc("Set Boolean value true") - @operationId("bool_putTrue") - @put - op setTrue(@body value: true): - | { - ...Response<200>; - } - | Error; -} - -@doc("AutoRest Bool Test Service") -@route("/bool/false") -namespace BoolsFalse { - @doc("Get true Boolean false") - @operationId("bool_getFalse") - @get - op getFalse(): false | Error; - - @doc("Set Boolean value false") - @operationId("bool_putFalse") - @put - op setFalse(@body value: false): boolean | Error; -} - -@doc("AutoRest Bool Test Service") -@route("/bool/null") -namespace BoolsNull { - @doc("Get null Boolean value") - @operationId("bool_getNull") - @get - op getNull(): NullableBoolean | Error; -} - -@doc("AutoRest Bool Test Service") -@route("/bool/invalid") -namespace BoolsInvalid { - @doc("Get invalid Boolean value") - @operationId("bool_getInvalid") - @get - op getInvalid(): boolean | Error; -} diff --git a/packages/samples/specs/testserver/body-boolean/main.tsp b/packages/samples/specs/testserver/body-boolean/main.tsp deleted file mode 100644 index 7c9a2440442..00000000000 --- a/packages/samples/specs/testserver/body-boolean/main.tsp +++ /dev/null @@ -1 +0,0 @@ -import "./body-boolean.tsp"; diff --git a/packages/samples/specs/testserver/body-complex/body-complex.tsp b/packages/samples/specs/testserver/body-complex/body-complex.tsp deleted file mode 100644 index af6842e1fb8..00000000000 --- a/packages/samples/specs/testserver/body-complex/body-complex.tsp +++ /dev/null @@ -1,284 +0,0 @@ -import "@typespec/rest"; -import "@typespec/openapi"; - -using Http; - -@service(#{ title: "sample" }) -namespace Sample; - -@doc("Error") -@error -model Error { - message: string; -} - -model Basic { - @doc("Basic Id") - id: int32 | null; - - @doc("Name property with a very long description that does not fit on a single line and a line break.") - name: string; - - color: Colors; -} - -enum Colors { - cyan, - Magenta, - YELLOW, - blacK, -} - -model Pet { - id: int32; - name: string; -} - -model IntWrapper { - field1: int32; - field2: int32; -} - -model LongWrapper { - field1: int64; - field2: int64; -} - -model FloatWrapper { - field1: float32; - field2: float32; -} - -model DoubleWrapper { - field1: float64; - field_56_zeros_after_the_dot_and_negative_zero_before_dot_and_this_is_a_long_field_name_on_purpose: float64; -} - -model BooleanWrapper { - field_true: boolean; - field_false: boolean; -} - -model StringWrapper { - field: string; - empty: string; - null: string | null; -} - -model PlainDateWrapper { - field: plainDate; - leap: plainDate; -} - -model DateTimeWrapper { - field: utcDateTime; - now: utcDateTime; -} - -model DurationWrapper { - field: duration; -} - -model BytesWrapper { - field: bytes; -} - -model ArrayWrapper { - field: string[]; -} - -model DictionaryWrapper { - field: Record; -} - -model ReadonlyObj { - @visibility(Lifecycle.Read) id: int32; - @visibility(Lifecycle.Read) size: int32; -} - -@doc("AutoRest Complex Body Test Service") -@route("/complex") -namespace Complex { - @doc("Get complex type {id: 2, name: 'abc', color: 'YELLOW'}") - @route("/basic/valid") - @get - op getValid(): Basic | Error; - - @doc("Please put {id: 2, name: 'abc', color: 'Magenta'}") - @route("/basic/valid") - @put - op putValid(@body value: Basic): - | { - ...Response<200>; - } - | Error; - - @doc("Get complex types with integer properties") - @route("/primitive/integer") - op getInt(): IntWrapper | Error; - - @doc("Put complex types with integer properties") - @route("/primitive/integer") - @put - op putInt(@body value: IntWrapper): - | { - ...Response<200>; - } - | Error; - - @doc("Get complex types with long properties") - @route("/primitive/long") - @get - op getLong(): LongWrapper | Error; - - @doc("Put complex types with long properties") - @route("/primitive/long") - @put - op putLong(@body value: LongWrapper): - | { - ...Response<200>; - } - | Error; - - @doc("Get complex types with float properties") - @route("/primitive/float") - @get - op getFloat(): FloatWrapper | Error; - - @doc("Put complex types with float properties") - @route("/primitive/float") - @put - op putFloat(@body value: FloatWrapper): - | { - ...Response<200>; - } - | Error; - - @doc("Get complex types with double properties") - @route("/primitive/double") - @get - op getDouble(): DoubleWrapper | Error; - - @doc("Put complex types with double properties") - @route("/primitive/double") - @put - op putDouble(@body value: DoubleWrapper): - | { - ...Response<200>; - } - | Error; - - @doc("Get complex types with bool properties") - @route("/primitive/bool") - @get - op getBool(): BooleanWrapper | Error; - - @doc("Put complex types with bool properties") - @route("/primitive/bool") - @put - op putBool(@body value: BooleanWrapper): - | { - ...Response<200>; - } - | Error; - - @doc("Get complex types with string properties") - @route("/primitive/string") - @get - op getString(): StringWrapper | Error; - - @doc("Put complex types with string properties") - @route("/primitive/string") - @put - op putString(@body value: StringWrapper): - | { - ...Response<200>; - } - | Error; - - @doc("Get complex types with date properties") - @route("/primitive/date") - @get - op getPlainDate(): PlainDateWrapper | Error; - - @doc("Put complex types with date properties") - @route("/primitive/date") - @put - op putPlainDate(@body value: PlainDateWrapper): - | { - ...Response<200>; - } - | Error; - - @doc("Get complex types with DateTime properties") - @route("/primitive/datetime") - @get - op getZonedDateTime(): DateTimeWrapper | Error; - - @doc("Put complex types with DateTime properties") - @route("/primitive/datetime") - @put - op putZonedDateTime(@body value: DateTimeWrapper): - | { - ...Response<200>; - } - | Error; - - @doc("Get complex types with Duration properties") - @route("/primitive/duration") - @get - op getZonedDuration(): DurationWrapper | Error; - - @doc("Put complex types with Duration properties") - @route("/primitive/duration") - @put - op putZonedDuration(@body value: DurationWrapper): - | { - ...Response<200>; - } - | Error; - - @doc("Get complex types with bytes properties") - @route("/primitive/bytes") - @get - op getBytes(): BytesWrapper | Error; - - @doc("Put complex types with bytes properties") - @route("/primitive/bytes") - @put - op putBytes(@body value: BytesWrapper): - | { - ...Response<200>; - } - | Error; - - @doc("Get complex types with array properties") - @route("/array/valid") - op getArray(): ArrayWrapper | Error; - - @doc("Put complex types with array properties") - @route("/array/valid") - @put - op putArray(@body value: ArrayWrapper): - | { - ...Response<200>; - } - | Error; - - @route("/dictionary") - namespace Dictionary { - @doc("Get complex types with dictionary property") - @route("/typed/valid") - @get - op getDictionary(): DictionaryWrapper | Error; - - @doc("Put complex types with dictionary property") - @route("/typed/valid") - @put - op putArray(@body value: DictionaryWrapper): - | { - ...Response<200>; - } - | Error; - } -} diff --git a/packages/samples/specs/testserver/body-complex/main.tsp b/packages/samples/specs/testserver/body-complex/main.tsp deleted file mode 100644 index 5714a37045e..00000000000 --- a/packages/samples/specs/testserver/body-complex/main.tsp +++ /dev/null @@ -1 +0,0 @@ -import "./body-complex.tsp"; diff --git a/packages/samples/specs/testserver/body-string/body-string.tsp b/packages/samples/specs/testserver/body-string/body-string.tsp deleted file mode 100644 index 6b2a05632d3..00000000000 --- a/packages/samples/specs/testserver/body-string/body-string.tsp +++ /dev/null @@ -1,139 +0,0 @@ -import "@typespec/rest"; -import "@typespec/openapi"; -/* cspell:disable */ - -using Http; - -@service(#{ title: "sample" }) -namespace Sample; - -@doc("Error") -@error -model Error { - status: int32; - message: string; -} - -@doc("AutoRest String Test Service") -@tag("String Operations") -@route("/string") -namespace String { - @doc("Get null string value") - @route("null") - @get - op getNull(): Body | Error; - - @doc("Put null string value") - @route("null") - @put - op putNull(@body value: string | null): - | { - ...Response<200>; - } - | Error; - - @doc("Get empty string value") - @route("empty") - @get - op getEmpty(): Body<""> | Error; - - @doc("Put empty string value") - @route("empty") - @put - op putEmpty(@body value: ""): - | { - ...Response<200>; - } - | Error; - - @doc("Get mbcs string value '啊齄丂狛狜隣郎隣兀﨩ˊ〞〡¦℡㈱‐ー﹡﹢﹫、〓ⅰⅹ⒈€㈠㈩ⅠⅫ! ̄ぁんァヶΑ︴АЯаяāɡㄅㄩ─╋︵﹄︻︱︳︴ⅰⅹɑɡ〇〾⿻⺁䜣€'") - @route("mbcs") - @get - op getMbcs(): - | Body<"啊齄丂狛狜隣郎隣兀﨩ˊ〞〡¦℡㈱‐ー﹡﹢﹫、〓ⅰⅹ⒈€㈠㈩ⅠⅫ! ̄ぁんァヶΑ︴АЯаяāɡㄅㄩ─╋︵﹄︻︱︳︴ⅰⅹɑɡ〇〾⿻⺁䜣€"> - | Error; - - @doc("Put mbcs string value '啊齄丂狛狜隣郎隣兀﨩ˊ〞〡¦℡㈱‐ー﹡﹢﹫、〓ⅰⅹ⒈€㈠㈩ⅠⅫ! ̄ぁんァヶΑ︴АЯаяāɡㄅㄩ─╋︵﹄︻︱︳︴ⅰⅹɑɡ〇〾⿻⺁䜣€'") - @route("mbcs") - @put - op putMbCs( - @body - value: "啊齄丂狛狜隣郎隣兀﨩ˊ〞〡¦℡㈱‐ー﹡﹢﹫、〓ⅰⅹ⒈€㈠㈩ⅠⅫ! ̄ぁんァヶΑ︴АЯаяāɡㄅㄩ─╋︵﹄︻︱︳︴ⅰⅹɑɡ〇〾⿻⺁䜣€", - ): - | { - ...Response<200>; - } - | Error; - - @doc("Get string value with leading and trailing whitespace 'Now is the time for all good men to come to the aid of their country'") - @route("whitespace") - @get - op getWhitespace(): - | Body<" Now is the time for all good men to come to the aid of their country "> - | Error; - - @doc("Get string value with leading and trailing whitespace 'Now is the time for all good men to come to the aid of their country'") - @route("whitespace") - @put - op putWhitespace( - @body value: " Now is the time for all good men to come to the aid of their country ", - ): - | { - ...Response<200>; - } - | Error; - - @doc("Get value that is base64 encoded") - @route("base64Encoding") - @get - op getBase64Encoding(): Body | Error; - - @doc("Put value that is base64 encoded") - @route("base64Encoding") - @put - op putBase64Encoding(@body value: bytes): - | { - ...Response<200>; - } - | Error; - - @route("enum") - namespace Enums { - @doc("Get non expandable string enum value") - @route("empty") - @get - op getNotExpandable(): Body | Error; - - @doc("Put non expandable string enum value") - @route("empty") - @put - op putNotExpandable(@body value: Colors): - | { - ...Response<200>; - } - | Error; - - @doc("Gets value 'green-color' from a constant") - @route("constant") - @get - op getConstant(): Body | Error; - - @doc("Sends value 'green-color' from a constant") - @route("constant") - @put - op putConstant(@body value: Colors): - | { - ...Response<200>; - } - | Error; - } -} - -// TODO: closed enum https://github.com/Azure/typespec-azure/issues/1036 -enum Colors { - `red color`, - `green-color`, - `blue-color`, -} - -alias RefColorConstant = "green-color"; diff --git a/packages/samples/specs/testserver/body-string/main.tsp b/packages/samples/specs/testserver/body-string/main.tsp deleted file mode 100644 index b767a0fe158..00000000000 --- a/packages/samples/specs/testserver/body-string/main.tsp +++ /dev/null @@ -1 +0,0 @@ -import "./body-string.tsp"; diff --git a/packages/samples/specs/testserver/body-time/body-time.tsp b/packages/samples/specs/testserver/body-time/body-time.tsp deleted file mode 100644 index 3e2e24ed3b8..00000000000 --- a/packages/samples/specs/testserver/body-time/body-time.tsp +++ /dev/null @@ -1,26 +0,0 @@ -import "@typespec/rest"; -import "@typespec/openapi"; - -using Http; - -@service(#{ title: "sample" }) -namespace Sample; - -@doc("Error") -@error -model Error { - status: int32; - message: string; -} - -@doc("AutoRest Time Test Service") -@route("/time") -namespace Time { - @doc("Get time value \"11:34:56\"") - @get - op get(): plainTime | Error; - - @doc("Put time value \"08:07:56\"") - @put - op put(@body value: plainTime): OkResponse | Error; -} diff --git a/packages/samples/specs/testserver/body-time/main.tsp b/packages/samples/specs/testserver/body-time/main.tsp deleted file mode 100644 index 1d39d7f896e..00000000000 --- a/packages/samples/specs/testserver/body-time/main.tsp +++ /dev/null @@ -1 +0,0 @@ -import "./body-time.tsp"; diff --git a/packages/samples/specs/testserver/media-types/main.tsp b/packages/samples/specs/testserver/media-types/main.tsp deleted file mode 100644 index 7a3bdd93322..00000000000 --- a/packages/samples/specs/testserver/media-types/main.tsp +++ /dev/null @@ -1 +0,0 @@ -import "./media-types.tsp"; diff --git a/packages/samples/specs/testserver/media-types/media-types.tsp b/packages/samples/specs/testserver/media-types/media-types.tsp deleted file mode 100644 index acdfd6d5a0f..00000000000 --- a/packages/samples/specs/testserver/media-types/media-types.tsp +++ /dev/null @@ -1,45 +0,0 @@ -import "@typespec/rest"; -import "@typespec/openapi"; - -using Http; -using OpenAPI; - -@service(#{ title: "sample" }) -namespace Sample; - -@doc("Uri or local path to source data.") -model SourcePath { - @minLength(0) - @maxLength(2048) - @doc("File source path.") - source: string; -} - -model Input { - @doc("Input parameter.") - @body - input: SourcePath; -} - -@route("/mediatypes") -namespace MediaTypes { - @doc("Analyze body, that could be different media types.") - @route("analyze") - @operationId("AnalyzeBody") - @post - op analyzeBody( - ...Input, - @header contentType: - | "application/pdf" - | "application/json" - | "image/jpeg" - | "image/png" - | "image/tiff", - ): string; - - @doc("Pass in contentType 'text/plain; encoding=UTF-8' to pass test. Value for input does not matter") - @route("contentTypeWithEncoding") - @operationId("contentTypeWithEncoding") - @post - op contentTypeWithEncoding(...Input, @header contentType: "text/plain"): string; -} diff --git a/packages/samples/specs/testserver/multiple-inheritance/main.tsp b/packages/samples/specs/testserver/multiple-inheritance/main.tsp deleted file mode 100644 index 347345ebc5a..00000000000 --- a/packages/samples/specs/testserver/multiple-inheritance/main.tsp +++ /dev/null @@ -1 +0,0 @@ -import "./multiple-inheritance.tsp"; diff --git a/packages/samples/specs/testserver/multiple-inheritance/multiple-inheritance.tsp b/packages/samples/specs/testserver/multiple-inheritance/multiple-inheritance.tsp deleted file mode 100644 index d87d5b25da0..00000000000 --- a/packages/samples/specs/testserver/multiple-inheritance/multiple-inheritance.tsp +++ /dev/null @@ -1,82 +0,0 @@ -import "@typespec/rest"; -import "@typespec/openapi"; - -using Http; - -@service(#{ title: "sample" }) -namespace Sample; - -model Pet { - name: string; -} - -model Feline extends Pet { - meows: boolean; - hisses: boolean; -} - -model Cat extends Feline { - likesMilk: boolean; -} - -model Kitten extends Cat { - eatsMiceYet: boolean; -} - -model Horse extends Pet { - isAShowHorse: boolean; -} - -@doc("Unexpected error") -@error -model ErrorResponse { - @body body: string; -} - -@route("/multipleInheritance") -namespace MultipleInheritance { - @doc("Get a horse with name 'Fred' and isAShowHorse true") - @route("horse") - op getHorse(): Horse | ErrorResponse; - - @doc("Put a horse with name 'General' and isAShowHorse false") - @route("horse") - @put - op putHorse(@body horse: Horse): string | ErrorResponse; - - @doc("Get a pet with name 'Peanut'") - @route("pet") - op getPet(): Pet | ErrorResponse; - - @doc("Put a pet with name 'Butter'") - @route("pet") - @put - op putPet(@body horse: Pet): string | ErrorResponse; - - @doc("Get a feline where meows and hisses are true") - @route("feline") - op getFeline(): Feline | ErrorResponse; - - @doc("Put a feline who hisses and doesn't meow") - @route("feline") - @put - op putFeline(@body feline: Feline): string | ErrorResponse; - - @doc("Get a cat with name 'Whiskers' where likesMilk, meows, and hisses is true") - @route("cat") - op getCat(): Cat | ErrorResponse; - - @doc("Put a cat with name 'Boots' where likesMilk and hisses is false, meows is true") - @route("cat") - @put - op putCat(@body cat: Cat): string | ErrorResponse; - - @doc("Get a kitten with name 'Gatito' where likesMilk and meows is true, and hisses and eatsMiceYet is false") - @route("kitten") - op getKitten(): Kitten | ErrorResponse; - - @doc("Put a kitten with name 'Kitty' where likesMilk and hisses is false, meows and eatsMiceYet is true") - @route("kitten") - @put - op putKitten(@body kitten: Kitten): string | ErrorResponse; -} diff --git a/packages/samples/specs/todoApp/sample-config.yaml b/packages/samples/specs/todoApp/sample-config.yaml new file mode 100644 index 00000000000..568693e6875 --- /dev/null +++ b/packages/samples/specs/todoApp/sample-config.yaml @@ -0,0 +1,3 @@ +title: Todo application +description: Define a complete HTTP API for a small todo application. +playground: false diff --git a/packages/samples/specs/use-versioned-lib/sample-config.yaml b/packages/samples/specs/use-versioned-lib/sample-config.yaml new file mode 100644 index 00000000000..0558bdb7e51 --- /dev/null +++ b/packages/samples/specs/use-versioned-lib/sample-config.yaml @@ -0,0 +1,3 @@ +title: Versioned library +description: Consume a library whose API evolves across versions. +playground: false diff --git a/packages/samples/specs/versioning/sample-config.yaml b/packages/samples/specs/versioning/sample-config.yaml new file mode 100644 index 00000000000..ec681dc7090 --- /dev/null +++ b/packages/samples/specs/versioning/sample-config.yaml @@ -0,0 +1,3 @@ +title: Advanced versioning +description: Coordinate service and library versions across multiple files. +playground: false diff --git a/packages/samples/specs/visibility/sample-config.yaml b/packages/samples/specs/visibility/sample-config.yaml new file mode 100644 index 00000000000..5629bbf9674 --- /dev/null +++ b/packages/samples/specs/visibility/sample-config.yaml @@ -0,0 +1,3 @@ +title: Lifecycle visibility +description: Control how model properties participate in lifecycle operations. +playground: false diff --git a/packages/samples/src/build-playground-samples.ts b/packages/samples/src/build-playground-samples.ts new file mode 100644 index 00000000000..332ad65ea85 --- /dev/null +++ b/packages/samples/src/build-playground-samples.ts @@ -0,0 +1,94 @@ +import { NodeHost, resolveCompilerOptions, type CompilerOptions } from "@typespec/compiler"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { dirname, relative, resolve, sep } from "node:path"; +import { loadSampleCatalog } from "./sample-config.js"; + +export interface BuildPlaygroundSamplesOptions { + /** Directory containing the canonical sample tree. */ + specsDir: string; + + /** Generated TypeScript module path. */ + outputFile: string; + + /** Directory used to make sample filenames relative for the consuming application. */ + relativeTo: string; + + /** Emitter used when a sample has no inherited emitter configuration. */ + defaultEmitter?: string; +} + +export interface PlaygroundSampleData { + filename: string; + preferredEmitter: string; + content: string; + description: string; + category?: string; + compilerOptions?: CompilerOptions; +} + +/** + * Build the sample module consumed by a TypeSpec playground. + */ +export async function buildPlaygroundSamples( + options: BuildPlaygroundSamplesOptions, +): Promise> { + const specsDir = resolve(options.specsDir); + const catalog = await loadSampleCatalog(specsDir); + const samples: Record = {}; + + for (const entry of catalog.samples.filter((x) => x.playground)) { + const mainFile = resolve(entry.directory, "main.tsp"); + if (entry.tspFiles.length !== 1 || entry.tspFiles[0] !== mainFile) { + throw new Error( + `Playground sample "${entry.id}" must contain exactly one TypeSpec file named main.tsp. ` + + `Set playground: false for multi-file samples.`, + ); + } + + const [resolvedOptions, diagnostics] = await resolveCompilerOptions(NodeHost, { + cwd: entry.directory, + entrypoint: mainFile, + }); + if (diagnostics.length > 0) { + throw new Error( + `Failed to resolve compiler options for playground sample "${entry.id}":\n${diagnostics + .map((x) => x.message) + .join("\n")}`, + ); + } + + const preferredEmitter = resolvedOptions.emit?.[0] ?? options.defaultEmitter; + if (!preferredEmitter) { + throw new Error( + `Playground sample "${entry.id}" has no emitter. Configure one in tspconfig.yaml or provide defaultEmitter.`, + ); + } + + const compilerOptions = resolvedOptions.linterRuleSet + ? { linterRuleSet: resolvedOptions.linterRuleSet } + : undefined; + + samples[entry.config.title] = { + filename: toPosix(relative(resolve(options.relativeTo), mainFile)), + content: await readFile(mainFile, "utf-8"), + preferredEmitter, + description: entry.config.description, + category: entry.category?.label, + ...(compilerOptions ? { compilerOptions } : {}), + }; + } + + const output = [ + `import type { PlaygroundSample } from "@typespec/playground";`, + `const samples: Record = ${JSON.stringify(samples, null, 2)};`, + `export default samples;`, + ].join("\n"); + const outputFile = resolve(options.outputFile); + await mkdir(dirname(outputFile), { recursive: true }); + await writeFile(outputFile, output); + return samples; +} + +function toPosix(path: string): string { + return path.split(sep).join("/"); +} diff --git a/packages/samples/src/index.ts b/packages/samples/src/index.ts index 4a2e207b030..d78a1f412c9 100644 --- a/packages/samples/src/index.ts +++ b/packages/samples/src/index.ts @@ -1 +1,3 @@ +export * from "./build-playground-samples.js"; +export * from "./sample-config.js"; export * from "./sample-snapshot-testing.js"; diff --git a/packages/samples/src/sample-config.ts b/packages/samples/src/sample-config.ts new file mode 100644 index 00000000000..ee54d2c6eee --- /dev/null +++ b/packages/samples/src/sample-config.ts @@ -0,0 +1,288 @@ +import { readdir, readFile } from "node:fs/promises"; +import { dirname, relative, resolve, sep } from "node:path"; +import { parse } from "yaml"; + +export interface SampleConfig { + directory?: false; + title: string; + description: string; + llmstxt?: boolean; + danger?: string; + order?: number; + playground?: boolean; +} + +export interface SampleDirectoryConfig { + directory: true; + label?: string; + danger?: string; + order?: number; + playground?: boolean; +} + +export interface SampleCategory { + id: string; + label: string; + order?: number; +} + +export interface SampleEntry { + id: string; + directory: string; + config: SampleConfig; + category?: SampleCategory; + playground: boolean; + tspFiles: string[]; +} + +export interface SampleCatalog { + samples: SampleEntry[]; + directories: Map; +} + +interface LoadedConfig { + id: string; + directory: string; + config: SampleConfig | SampleDirectoryConfig; +} + +/** + * Discover and validate samples below the given specs directory. + */ +export async function loadSampleCatalog(specsDir: string): Promise { + const root = resolve(specsDir); + const loadedConfigs: LoadedConfig[] = []; + const entrypointDirs = new Set(); + + await walk(root); + + const configByDirectory = new Map(loadedConfigs.map((x) => [x.directory, x])); + const directories = new Map(); + const samples: SampleEntry[] = []; + const titles = new Map(); + + for (const loaded of loadedConfigs) { + if (loaded.config.directory === true) { + directories.set(loaded.id, loaded.config); + continue; + } + + const existingTitle = titles.get(loaded.config.title); + if (existingTitle) { + throw new Error( + `Sample "${loaded.id}" has duplicate title "${loaded.config.title}" also used by "${existingTitle}".`, + ); + } + titles.set(loaded.config.title, loaded.id); + + const tspFiles = await findTspFiles(loaded.directory); + const hasMain = tspFiles.some((x) => x === resolve(loaded.directory, "main.tsp")); + const hasPackage = entrypointDirs.has(loaded.directory); + if (!hasMain && !hasPackage) { + throw new Error( + `Sample "${loaded.id}" must contain main.tsp or a package.json TypeSpec entrypoint.`, + ); + } + + const ancestors = getAncestorDirectories(loaded.directory, root); + const directoryConfigs = ancestors + .map((x) => configByDirectory.get(x)) + .filter( + (x): x is LoadedConfig & { config: SampleDirectoryConfig } => x?.config.directory === true, + ); + const categoryConfig = directoryConfigs.at(-1); + + samples.push({ + id: loaded.id, + directory: loaded.directory, + config: loaded.config, + category: categoryConfig + ? { + id: categoryConfig.id, + label: categoryConfig.config.label ?? formatLabel(categoryConfig.id.split("/").at(-1)!), + order: categoryConfig.config.order, + } + : undefined, + playground: + loaded.config.playground !== false && + directoryConfigs.every((x) => x.config.playground !== false), + tspFiles, + }); + } + + for (const entrypointDir of entrypointDirs) { + const loaded = configByDirectory.get(entrypointDir); + if (!loaded || loaded.config.directory === true) { + throw new Error( + `Sample at "${toPosix(relative(root, entrypointDir))}" is missing sample-config.yaml.`, + ); + } + } + + samples.sort( + (a, b) => + (a.category?.order ?? Number.POSITIVE_INFINITY) - + (b.category?.order ?? Number.POSITIVE_INFINITY) || + (a.config.order ?? Number.POSITIVE_INFINITY) - (b.config.order ?? Number.POSITIVE_INFINITY) || + a.id.localeCompare(b.id), + ); + + return { samples, directories }; + + async function walk(directory: string): Promise { + const entries = await readdir(directory, { withFileTypes: true }); + for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) { + const fullPath = resolve(directory, entry.name); + if (entry.isDirectory()) { + await walk(fullPath); + } else if (entry.name === "sample-config.yaml") { + const id = toPosix(relative(root, directory)); + loadedConfigs.push({ + id, + directory, + config: parseSampleConfig(await readFile(fullPath, "utf-8"), fullPath), + }); + } else if (entry.name === "main.tsp" || entry.name === "package.json") { + entrypointDirs.add(directory); + } + } + } +} + +function parseSampleConfig( + content: string, + configPath: string, +): SampleConfig | SampleDirectoryConfig { + let value: unknown; + try { + value = parse(content); + } catch (error) { + throw new Error(`Invalid YAML in ${configPath}.`, { cause: error }); + } + + if (!isRecord(value)) { + throw new Error(`Sample config at ${configPath} must be a YAML object.`); + } + + if (value.directory === true) { + validateKnownKeys( + value, + new Set(["directory", "label", "danger", "order", "playground"]), + configPath, + ); + validateOptionalString(value, "label", configPath); + validateOptionalString(value, "danger", configPath); + validateOptionalNumber(value, "order", configPath); + validateOptionalBoolean(value, "playground", configPath); + return value as unknown as SampleDirectoryConfig; + } + + validateKnownKeys( + value, + new Set(["directory", "title", "description", "llmstxt", "danger", "order", "playground"]), + configPath, + ); + if (value.directory !== undefined && value.directory !== false) { + throw new Error(`Sample config at ${configPath} has invalid "directory"; expected false.`); + } + validateRequiredString(value, "title", configPath); + validateRequiredString(value, "description", configPath); + validateOptionalBoolean(value, "llmstxt", configPath); + validateOptionalString(value, "danger", configPath); + validateOptionalNumber(value, "order", configPath); + validateOptionalBoolean(value, "playground", configPath); + return value as unknown as SampleConfig; +} + +function validateKnownKeys( + value: Record, + keys: Set, + configPath: string, +): void { + const unknown = Object.keys(value).find((x) => !keys.has(x)); + if (unknown) { + throw new Error(`Sample config at ${configPath} has unknown field "${unknown}".`); + } +} + +async function findTspFiles(directory: string): Promise { + const files: string[] = []; + await walk(directory); + return files.sort(); + + async function walk(current: string): Promise { + for (const entry of await readdir(current, { withFileTypes: true })) { + const fullPath = resolve(current, entry.name); + if (entry.isDirectory()) { + await walk(fullPath); + } else if (entry.name.endsWith(".tsp")) { + files.push(fullPath); + } + } + } +} + +function getAncestorDirectories(directory: string, root: string): string[] { + const ancestors: string[] = []; + let current = dirname(directory); + while (current !== root && current.startsWith(`${root}${sep}`)) { + ancestors.unshift(current); + current = dirname(current); + } + return ancestors; +} + +function formatLabel(value: string): string { + return value + .split("-") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +} + +function toPosix(path: string): string { + return path.split(sep).join("/"); +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function validateRequiredString( + value: Record, + key: string, + configPath: string, +): void { + if (typeof value[key] !== "string" || value[key].trim() === "") { + throw new Error(`Sample config at ${configPath} requires a non-empty "${key}" field.`); + } +} + +function validateOptionalString( + value: Record, + key: string, + configPath: string, +): void { + if (value[key] !== undefined && typeof value[key] !== "string") { + throw new Error(`Sample config at ${configPath} has invalid "${key}"; expected a string.`); + } +} + +function validateOptionalNumber( + value: Record, + key: string, + configPath: string, +): void { + if (value[key] !== undefined && typeof value[key] !== "number") { + throw new Error(`Sample config at ${configPath} has invalid "${key}"; expected a number.`); + } +} + +function validateOptionalBoolean( + value: Record, + key: string, + configPath: string, +): void { + if (value[key] !== undefined && typeof value[key] !== "boolean") { + throw new Error(`Sample config at ${configPath} has invalid "${key}"; expected a boolean.`); + } +} diff --git a/packages/samples/test/build-playground-samples.test.ts b/packages/samples/test/build-playground-samples.test.ts new file mode 100644 index 00000000000..f2d55fda686 --- /dev/null +++ b/packages/samples/test/build-playground-samples.test.ts @@ -0,0 +1,110 @@ +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { buildPlaygroundSamples } from "../src/build-playground-samples.js"; + +describe("build playground samples", () => { + it("derives ordered samples, categories, emitters, and linter options", async () => { + await withTempDirectory(async (root) => { + await writeFiles(root, { + "specs/http/sample-config.yaml": "directory: true\nlabel: HTTP\norder: 1\n", + "specs/http/widgets/sample-config.yaml": + "title: Widgets\ndescription: A widget service.\norder: 1\n", + "specs/http/widgets/main.tsp": "model Widget {}", + "specs/http/base.yaml": + 'emit:\n - "@typespec/openapi3"\nlinter:\n extends:\n - "@typespec/http/all"\n', + "specs/http/widgets/tspconfig.yaml": "extends: ../base.yaml\n", + }); + + const outputFile = resolve(root, "generated/samples.ts"); + const samples = await buildPlaygroundSamples({ + specsDir: resolve(root, "specs"), + outputFile, + relativeTo: root, + }); + + expect(samples.Widgets).toMatchObject({ + filename: "specs/http/widgets/main.tsp", + preferredEmitter: "@typespec/openapi3", + category: "HTTP", + description: "A widget service.", + compilerOptions: { linterRuleSet: { extends: ["@typespec/http/all"] } }, + }); + expect(await readFile(outputFile, "utf-8")).toContain( + "const samples: Record", + ); + }); + }); + + it("uses the default emitter when the sample has no emitter config", async () => { + await withTempDirectory(async (root) => { + await writeFiles(root, { + "specs/widgets/sample-config.yaml": "title: Widgets\ndescription: A widget service.\n", + "specs/widgets/main.tsp": "model Widget {}", + }); + + const samples = await buildPlaygroundSamples({ + specsDir: resolve(root, "specs"), + outputFile: resolve(root, "generated/samples.ts"), + relativeTo: root, + defaultEmitter: "@typespec/openapi3", + }); + + expect(samples.Widgets.preferredEmitter).toBe("@typespec/openapi3"); + }); + }); + + it("rejects samples without a configured or default emitter", async () => { + await withTempDirectory(async (root) => { + await writeFiles(root, { + "specs/widgets/sample-config.yaml": "title: Widgets\ndescription: A widget service.\n", + "specs/widgets/main.tsp": "model Widget {}", + }); + + await expect( + buildPlaygroundSamples({ + specsDir: resolve(root, "specs"), + outputFile: resolve(root, "generated/samples.ts"), + relativeTo: root, + }), + ).rejects.toThrow('Playground sample "widgets" has no emitter'); + }); + }); + + it("rejects eligible multi-file samples", async () => { + await withTempDirectory(async (root) => { + await writeFiles(root, { + "specs/widgets/sample-config.yaml": "title: Widgets\ndescription: A widget service.\n", + "specs/widgets/main.tsp": 'import "./models.tsp";', + "specs/widgets/models.tsp": "model Widget {}", + }); + + await expect( + buildPlaygroundSamples({ + specsDir: resolve(root, "specs"), + outputFile: resolve(root, "generated/samples.ts"), + relativeTo: root, + defaultEmitter: "@typespec/openapi3", + }), + ).rejects.toThrow("must contain exactly one TypeSpec file named main.tsp"); + }); + }); +}); + +async function withTempDirectory(callback: (root: string) => Promise): Promise { + const root = await mkdtemp(resolve(tmpdir(), "typespec-playground-samples-")); + try { + await callback(root); + } finally { + await rm(root, { recursive: true }); + } +} + +async function writeFiles(root: string, files: Record): Promise { + for (const [name, content] of Object.entries(files)) { + const path = resolve(root, name); + await mkdir(resolve(path, ".."), { recursive: true }); + await writeFile(path, content); + } +} diff --git a/packages/samples/test/output/emitters/json-schema/@typespec/json-schema/Address.yaml b/packages/samples/test/output/emitters/json-schema/@typespec/json-schema/Address.yaml new file mode 100644 index 00000000000..66d24b90332 --- /dev/null +++ b/packages/samples/test/output/emitters/json-schema/@typespec/json-schema/Address.yaml @@ -0,0 +1,14 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: Address.yaml +type: object +properties: + street: + type: string + city: + type: string + country: + type: string +required: + - street + - city + - country diff --git a/packages/samples/test/output/emitters/json-schema/@typespec/json-schema/Car.yaml b/packages/samples/test/output/emitters/json-schema/@typespec/json-schema/Car.yaml new file mode 100644 index 00000000000..102dbc176a5 --- /dev/null +++ b/packages/samples/test/output/emitters/json-schema/@typespec/json-schema/Car.yaml @@ -0,0 +1,18 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: Car.yaml +type: object +properties: + kind: + anyOf: + - type: string + const: ev + - type: string + const: ice + brand: + type: string + model: + type: string +required: + - kind + - brand + - model diff --git a/packages/samples/test/output/emitters/json-schema/@typespec/json-schema/Person.yaml b/packages/samples/test/output/emitters/json-schema/@typespec/json-schema/Person.yaml new file mode 100644 index 00000000000..e7de614ac05 --- /dev/null +++ b/packages/samples/test/output/emitters/json-schema/@typespec/json-schema/Person.yaml @@ -0,0 +1,34 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: Person.yaml +type: object +properties: + firstName: + type: string + description: The person's first name. + lastName: + type: string + description: The person's last name. + age: + type: integer + minimum: 0 + maximum: 2147483647 + description: Age in years, which must be zero or greater. + address: + $ref: Address.yaml + description: The person's address. + nickNames: + type: array + items: + type: string + uniqueItems: true + description: The person's nicknames. + cars: + type: array + items: + $ref: Car.yaml + description: Cars owned by the person. +required: + - firstName + - lastName + - age + - address diff --git a/packages/samples/test/output/emitters/protobuf-kiosk/@typespec/protobuf/kiosk.proto b/packages/samples/test/output/emitters/protobuf-kiosk/@typespec/protobuf/kiosk.proto new file mode 100644 index 00000000000..97a48c1c746 --- /dev/null +++ b/packages/samples/test/output/emitters/protobuf-kiosk/@typespec/protobuf/kiosk.proto @@ -0,0 +1,96 @@ +// Generated by Microsoft TypeSpec + +syntax = "proto3"; + +package kiosk; + +import "google/type/latlng.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/empty.proto"; + +message ScreenSize { + int32 width = 1; + int32 height = 2; +} + +message Kiosk { + optional int32 id = 1; + string name = 2; + ScreenSize size = 3; + google.type.LatLng location = 4; + google.protobuf.Timestamp createTime = 5; +} + +message Sign { + optional int32 id = 1; + string name = 2; + string text = 3; + bytes image = 4; + google.protobuf.Timestamp createTime = 5; +} + +message GetSignIdResponse { + int32 signId = 1; +} + +message ListKiosksResponse { + repeated Kiosk kiosks = 1; +} + +message GetKioskRequest { + int32 id = 1; +} + +message DeleteKioskRequest { + int32 id = 1; +} + +message ListSignsResponse { + repeated Sign signs = 1; +} + +message GetSignRequest { + int32 id = 1; +} + +message DeleteSignRequest { + int32 id = 1; +} + +message SetSignIdForKioskIdsRequest { + repeated int32 kioskIds = 1; + int32 signId = 2; +} + +message GetSignIdForKioskIdRequest { + int32 kioskId = 1; +} + +message GetSignIdsForKioskIdRequest { + int32 kioskId = 1; +} + +service Display { + // Create a new kiosk and enroll it for sign display. + rpc CreateKiosk(Kiosk) returns (Kiosk); + // List active kiosks. + rpc ListKiosks(google.protobuf.Empty) returns (ListKiosksResponse); + // Get a kiosk. + rpc GetKiosk(GetKioskRequest) returns (Kiosk); + // Delete a kiosk. + rpc DeleteKiosk(DeleteKioskRequest) returns (google.protobuf.Empty); + // Create a new sign. + rpc CreateSign(Sign) returns (Sign); + // List active signs. + rpc ListSigns(google.protobuf.Empty) returns (ListSignsResponse); + // Get a sign. + rpc GetSign(GetSignRequest) returns (Sign); + // Delete a sign. + rpc DeleteSign(DeleteSignRequest) returns (google.protobuf.Empty); + // Set a sign for display on one or more kiosks. + rpc SetSignIdForKioskIds(SetSignIdForKioskIdsRequest) returns (google.protobuf.Empty); + // Get the sign that should be displayed on a kiosk. + rpc GetSignIdForKioskId(GetSignIdForKioskIdRequest) returns (GetSignIdResponse); + // Stream the signs that should be displayed on a kiosk. + rpc GetSignIdsForKioskId(GetSignIdsForKioskIdRequest) returns (stream GetSignIdResponse); +} diff --git a/packages/samples/test/output/getting-started/http-service/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/getting-started/http-service/@typespec/openapi3/openapi.yaml new file mode 100644 index 00000000000..bcc0f66dfa0 --- /dev/null +++ b/packages/samples/test/output/getting-started/http-service/@typespec/openapi3/openapi.yaml @@ -0,0 +1,200 @@ +openapi: 3.0.0 +info: + title: Widget Service + version: 0.0.0 +tags: + - name: Widgets +paths: + /widgets: + get: + operationId: Widgets_list + description: List widgets. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - Widgets + post: + operationId: Widgets_create + description: Create a widget. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - Widgets + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + /widgets/{id}: + get: + operationId: Widgets_read + description: Read a widget. + parameters: + - name: id + in: path + required: true + schema: + type: string + readOnly: true + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - Widgets + patch: + operationId: Widgets_update + description: Update a widget. + parameters: + - name: id + in: path + required: true + schema: + type: string + readOnly: true + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - Widgets + requestBody: + required: true + content: + application/merge-patch+json: + schema: + $ref: '#/components/schemas/WidgetMergePatchUpdate' + delete: + operationId: Widgets_delete + description: Delete a widget. + parameters: + - name: id + in: path + required: true + schema: + type: string + readOnly: true + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - Widgets + /widgets/{id}/analyze: + post: + operationId: Widgets_analyze + description: Analyze a widget. + parameters: + - name: id + in: path + required: true + schema: + type: string + readOnly: true + responses: + '200': + description: The request has succeeded. + content: + text/plain: + schema: + type: string + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - Widgets +components: + schemas: + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + Widget: + type: object + required: + - id + - weight + - color + properties: + id: + type: string + readOnly: true + weight: + type: integer + format: int32 + color: + type: string + enum: + - red + - blue + WidgetMergePatchUpdate: + type: object + properties: + weight: + type: integer + format: int32 + color: + type: string + enum: + - red + - blue diff --git a/packages/samples/test/output/getting-started/rest-framework/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/getting-started/rest-framework/@typespec/openapi3/openapi.yaml new file mode 100644 index 00000000000..db2259c7177 --- /dev/null +++ b/packages/samples/test/output/getting-started/rest-framework/@typespec/openapi3/openapi.yaml @@ -0,0 +1,200 @@ +openapi: 3.0.0 +info: + title: Widget Service + version: 0.0.0 +tags: [] +paths: + /: + post: + operationId: WidgetService_create + description: Creates a new instance of the resource. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + '201': + description: Resource create operation completed successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WidgetCreate' + get: + operationId: WidgetService_list + description: Lists all instances of the resource. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/WidgetCollectionWithNextLink' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /customGet: + get: + operationId: WidgetService_customGet + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + /{id}: + get: + operationId: WidgetService_get + description: Gets an instance of the resource. + parameters: + - $ref: '#/components/parameters/WidgetKey' + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + patch: + operationId: WidgetService_update + description: Updates an existing instance of the resource. + parameters: + - $ref: '#/components/parameters/WidgetKey' + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WidgetUpdate' + delete: + operationId: WidgetService_delete + description: Deletes an existing instance of the resource. + parameters: + - $ref: '#/components/parameters/WidgetKey' + responses: + '200': + description: Resource deleted successfully. + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +components: + parameters: + WidgetKey: + name: id + in: path + required: true + schema: + type: string + schemas: + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + Widget: + type: object + required: + - id + - weight + - color + properties: + id: + type: string + weight: + type: integer + format: int32 + color: + type: string + enum: + - red + - blue + WidgetCollectionWithNextLink: + type: object + required: + - value + properties: + value: + type: array + items: + $ref: '#/components/schemas/Widget' + description: The items on this page + nextLink: + type: string + format: uri + description: The link to the next page of items + description: Paged response of Widget items + WidgetCreate: + type: object + required: + - weight + - color + properties: + weight: + type: integer + format: int32 + color: + type: string + enum: + - red + - blue + description: Resource create operation model. + WidgetUpdate: + type: object + properties: + weight: + type: integer + format: int32 + color: + type: string + enum: + - red + - blue + description: Resource create or update operation model. diff --git a/packages/samples/test/output/language/api-versioning/@typespec/openapi3/openapi.v1.yaml b/packages/samples/test/output/language/api-versioning/@typespec/openapi3/openapi.v1.yaml new file mode 100644 index 00000000000..53be9c284fa --- /dev/null +++ b/packages/samples/test/output/language/api-versioning/@typespec/openapi3/openapi.v1.yaml @@ -0,0 +1,194 @@ +openapi: 3.0.0 +info: + title: Widget Service + version: v1 +tags: [] +paths: + /: + post: + operationId: WidgetService_create + description: Creates a new instance of the resource. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + '201': + description: Resource create operation completed successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WidgetCreate' + get: + operationId: WidgetService_list + description: Lists all instances of the resource. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/WidgetCollectionWithNextLink' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /{id}: + get: + operationId: WidgetService_get + description: Gets an instance of the resource. + parameters: + - $ref: '#/components/parameters/WidgetKey' + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + patch: + operationId: WidgetService_update + description: Updates an existing instance of the resource. + parameters: + - $ref: '#/components/parameters/WidgetKey' + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WidgetUpdate' + delete: + operationId: WidgetService_delete + description: Deletes an existing instance of the resource. + parameters: + - $ref: '#/components/parameters/WidgetKey' + responses: + '200': + description: Resource deleted successfully. + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +components: + parameters: + WidgetKey: + name: id + in: path + required: true + schema: + type: string + schemas: + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + Versions: + type: string + enum: + - v1 + - v2 + Widget: + type: object + required: + - id + - weight + - color + properties: + id: + type: string + weight: + type: integer + format: int32 + color: + type: string + enum: + - red + - blue + WidgetCollectionWithNextLink: + type: object + required: + - value + properties: + value: + type: array + items: + $ref: '#/components/schemas/Widget' + description: The items on this page + nextLink: + type: string + format: uri + description: The link to the next page of items + description: Paged response of Widget items + WidgetCreate: + type: object + required: + - weight + - color + properties: + weight: + type: integer + format: int32 + color: + type: string + enum: + - red + - blue + description: Resource create operation model. + WidgetUpdate: + type: object + properties: + weight: + type: integer + format: int32 + color: + type: string + enum: + - red + - blue + description: Resource create or update operation model. diff --git a/packages/samples/test/output/language/api-versioning/@typespec/openapi3/openapi.v2.yaml b/packages/samples/test/output/language/api-versioning/@typespec/openapi3/openapi.v2.yaml new file mode 100644 index 00000000000..4ec506447ac --- /dev/null +++ b/packages/samples/test/output/language/api-versioning/@typespec/openapi3/openapi.v2.yaml @@ -0,0 +1,213 @@ +openapi: 3.0.0 +info: + title: Widget Service + version: v2 +tags: [] +paths: + /: + post: + operationId: WidgetService_create + description: Creates a new instance of the resource. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + '201': + description: Resource create operation completed successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WidgetCreate' + get: + operationId: WidgetService_list + description: Lists all instances of the resource. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/WidgetCollectionWithNextLink' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /customGet: + get: + operationId: WidgetService_customGet + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + /{id}: + get: + operationId: WidgetService_get + description: Gets an instance of the resource. + parameters: + - $ref: '#/components/parameters/WidgetKey' + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + patch: + operationId: WidgetService_update + description: Updates an existing instance of the resource. + parameters: + - $ref: '#/components/parameters/WidgetKey' + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WidgetUpdate' + delete: + operationId: WidgetService_delete + description: Deletes an existing instance of the resource. + parameters: + - $ref: '#/components/parameters/WidgetKey' + responses: + '200': + description: Resource deleted successfully. + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +components: + parameters: + WidgetKey: + name: id + in: path + required: true + schema: + type: string + schemas: + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + Versions: + type: string + enum: + - v1 + - v2 + Widget: + type: object + required: + - id + - weight + - color + - name + properties: + id: + type: string + weight: + type: integer + format: int32 + color: + type: string + enum: + - red + - blue + name: + type: string + WidgetCollectionWithNextLink: + type: object + required: + - value + properties: + value: + type: array + items: + $ref: '#/components/schemas/Widget' + description: The items on this page + nextLink: + type: string + format: uri + description: The link to the next page of items + description: Paged response of Widget items + WidgetCreate: + type: object + required: + - weight + - color + - name + properties: + weight: + type: integer + format: int32 + color: + type: string + enum: + - red + - blue + name: + type: string + description: Resource create operation model. + WidgetUpdate: + type: object + properties: + weight: + type: integer + format: int32 + color: + type: string + enum: + - red + - blue + name: + type: string + description: Resource create or update operation model. diff --git a/packages/samples/test/output/language/discriminated-unions/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/language/discriminated-unions/@typespec/openapi3/openapi.yaml new file mode 100644 index 00000000000..bb9ab0a8cd5 --- /dev/null +++ b/packages/samples/test/output/language/discriminated-unions/@typespec/openapi3/openapi.yaml @@ -0,0 +1,119 @@ +openapi: 3.0.0 +info: + title: Widget Service + version: 0.0.0 +tags: [] +paths: + /{id}: + get: + operationId: read + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Widget' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +components: + schemas: + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + HeavyWidget: + type: object + required: + - kind + properties: + kind: + type: string + enum: + - Heavy + allOf: + - $ref: '#/components/schemas/WidgetBase' + LightWidget: + type: object + required: + - kind + properties: + kind: + type: string + enum: + - Light + allOf: + - $ref: '#/components/schemas/WidgetBase' + Widget: + type: object + oneOf: + - $ref: '#/components/schemas/WidgetHeavy' + - $ref: '#/components/schemas/WidgetLight' + discriminator: + propertyName: kind + mapping: + heavy: '#/components/schemas/WidgetHeavy' + light: '#/components/schemas/WidgetLight' + WidgetBase: + type: object + required: + - id + - weight + - color + properties: + id: + type: string + weight: + type: integer + format: int32 + color: + type: string + enum: + - red + - blue + WidgetHeavy: + type: object + required: + - kind + - value + properties: + kind: + type: string + enum: + - heavy + value: + $ref: '#/components/schemas/HeavyWidget' + WidgetKind: + type: string + enum: + - Heavy + - Light + WidgetLight: + type: object + required: + - kind + - value + properties: + kind: + type: string + enum: + - light + value: + $ref: '#/components/schemas/LightWidget' diff --git a/packages/samples/test/output/multiple-types-union/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/multiple-types-union/@typespec/openapi3/openapi.yaml deleted file mode 100644 index e1accc666c2..00000000000 --- a/packages/samples/test/output/multiple-types-union/@typespec/openapi3/openapi.yaml +++ /dev/null @@ -1,68 +0,0 @@ -openapi: 3.0.0 -info: - title: Pet Store Service - version: 0.0.0 -tags: [] -paths: - /: - get: - operationId: MyService_getPet - parameters: - - $ref: '#/components/parameters/ApiVersionParam' - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/Pet' -components: - parameters: - ApiVersionParam: - name: api-version - in: header - required: true - schema: - type: string - schemas: - Cat: - type: object - required: - - type - - catnipDose - properties: - type: - type: string - enum: - - cat - catnipDose: - type: integer - format: int32 - allOf: - - $ref: '#/components/schemas/PetBase' - Dog: - type: object - required: - - type - - nextWalkTime - properties: - type: - type: string - enum: - - dog - nextWalkTime: - type: string - format: date-time - allOf: - - $ref: '#/components/schemas/PetBase' - Pet: - anyOf: - - $ref: '#/components/schemas/Cat' - - $ref: '#/components/schemas/Dog' - PetBase: - type: object - required: - - name - properties: - name: - type: string diff --git a/packages/samples/test/output/nested/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/nested/@typespec/openapi3/openapi.yaml deleted file mode 100644 index 58bd9245d5e..00000000000 --- a/packages/samples/test/output/nested/@typespec/openapi3/openapi.yaml +++ /dev/null @@ -1,90 +0,0 @@ -openapi: 3.0.0 -info: - title: Nested sample - version: 0.0.0 -tags: [] -paths: - /: - post: - operationId: SubC_anotherOp - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - thing: - $ref: '#/components/schemas/SubA.Thing' - thing2: - $ref: '#/components/schemas/SubA.Thing' - required: - - thing - - thing2 - /sub/a/subsub: - post: - operationId: SubSubA_doSomething - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SubA.SubSubA.Thing' - /sub/b: - post: - operationId: SubB_doSomething - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SubB.Thing' -components: - schemas: - SubA.SubSubA.Thing: - type: object - required: - - name - properties: - name: - type: string - SubA.Thing: - type: object - required: - - id - properties: - id: - type: integer - format: int64 - SubB.Thing: - type: object - required: - - id - properties: - id: - type: integer - format: int64 diff --git a/packages/samples/test/output/nullable/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/nullable/@typespec/openapi3/openapi.yaml deleted file mode 100644 index 1c82d2ee049..00000000000 --- a/packages/samples/test/output/nullable/@typespec/openapi3/openapi.yaml +++ /dev/null @@ -1,88 +0,0 @@ -openapi: 3.0.0 -info: - title: Nullable sample - version: 0.0.0 -tags: [] -paths: - /test: - get: - operationId: NullableMethods_read - parameters: - - name: someParam - in: query - required: true - schema: - type: string - nullable: true - explode: false - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/HasNullables' - requestBody: - required: true - content: - application/json: - schema: - type: object - allOf: - - $ref: '#/components/schemas/AnotherModel' - nullable: true -components: - schemas: - AnotherModel: - type: object - required: - - num - properties: - num: - type: integer - format: int32 - HasNullables: - type: object - required: - - str - - when - - strOrNull - - modelOrNull - - literalsOrNull - - manyNullsOneString - - manyNullsSomeValues - - arr - properties: - str: - type: string - when: - type: string - format: time - strOrNull: - type: string - nullable: true - modelOrNull: - type: object - allOf: - - $ref: '#/components/schemas/AnotherModel' - nullable: true - literalsOrNull: - type: string - enum: - - one - - two - nullable: true - manyNullsOneString: - type: string - nullable: true - manyNullsSomeValues: - type: number - enum: - - 42 - - 100 - nullable: true - arr: - type: array - items: - type: string - nullable: true diff --git a/packages/samples/test/output/optional/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/optional/@typespec/openapi3/openapi.yaml deleted file mode 100644 index 8a424c0fcd1..00000000000 --- a/packages/samples/test/output/optional/@typespec/openapi3/openapi.yaml +++ /dev/null @@ -1,70 +0,0 @@ -openapi: 3.0.0 -info: - title: Optional sample - version: 0.0.0 -tags: [] -paths: - /test: - get: - operationId: OptionalMethods_read - parameters: - - name: queryString - in: query - required: false - schema: - type: string - default: defaultQueryString - explode: false - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/HasOptional' - requestBody: - required: false - content: - text/plain: - schema: - type: integer - format: int64 -components: - schemas: - HasOptional: - type: object - properties: - optionalNoDefault: - type: string - optionalString: - type: string - default: default string - optionalNumber: - type: integer - format: int32 - default: 123 - optionalBoolean: - type: boolean - default: true - optionalArray: - type: array - items: - type: string - default: - - foo - - bar - optionalUnion: - type: string - enum: - - foo - - bar - default: foo - optionalEnum: - allOf: - - $ref: '#/components/schemas/MyEnum' - default: a - MyEnum: - type: string - enum: - - a - - b diff --git a/packages/samples/test/output/simple/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/simple/@typespec/openapi3/openapi.yaml deleted file mode 100644 index 3a30689e243..00000000000 --- a/packages/samples/test/output/simple/@typespec/openapi3/openapi.yaml +++ /dev/null @@ -1,39 +0,0 @@ -openapi: 3.0.0 -info: - title: (title) - version: 0.0.0 -tags: [] -paths: - /alpha/{id}: - get: - operationId: doAlpha - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - /beta/{id}: - get: - operationId: doBeta - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string -components: {} diff --git a/packages/samples/test/output/testserver/body-boolean/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/testserver/body-boolean/@typespec/openapi3/openapi.yaml deleted file mode 100644 index 87331fa8aef..00000000000 --- a/packages/samples/test/output/testserver/body-boolean/@typespec/openapi3/openapi.yaml +++ /dev/null @@ -1,142 +0,0 @@ -openapi: 3.0.0 -info: - title: sample - version: 0.0.0 -tags: [] -paths: - /bool/false: - get: - operationId: bool_getFalse - description: Get true Boolean false - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: boolean - enum: - - false - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: bool_putFalse - description: Set Boolean value false - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: boolean - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - text/plain: - schema: - type: boolean - enum: - - false - /bool/invalid: - get: - operationId: bool_getInvalid - description: Get invalid Boolean value - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: boolean - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /bool/null: - get: - operationId: bool_getNull - description: Get null Boolean value - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: boolean - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /bool/true: - get: - operationId: bool_getTrue - description: Get true Boolean value - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: boolean - enum: - - true - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: bool_putTrue - description: Set Boolean value true - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - text/plain: - schema: - type: boolean - enum: - - true -components: - schemas: - Error: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - description: Error diff --git a/packages/samples/test/output/testserver/body-complex/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/testserver/body-complex/@typespec/openapi3/openapi.yaml deleted file mode 100644 index 86af522a240..00000000000 --- a/packages/samples/test/output/testserver/body-complex/@typespec/openapi3/openapi.yaml +++ /dev/null @@ -1,676 +0,0 @@ -openapi: 3.0.0 -info: - title: sample - version: 0.0.0 -tags: [] -paths: - /complex/array/valid: - get: - operationId: Complex_getArray - description: Get complex types with array properties - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/ArrayWrapper' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Complex_putArray - description: Put complex types with array properties - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ArrayWrapper' - /complex/basic/valid: - get: - operationId: Complex_getValid - description: "Get complex type {id: 2, name: 'abc', color: 'YELLOW'}" - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/Basic' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Complex_putValid - description: "Please put {id: 2, name: 'abc', color: 'Magenta'}" - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Basic' - /complex/dictionary/typed/valid: - get: - operationId: Dictionary_getDictionary - description: Get complex types with dictionary property - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/DictionaryWrapper' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Dictionary_putArray - description: Put complex types with dictionary property - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/DictionaryWrapper' - /complex/primitive/bool: - get: - operationId: Complex_getBool - description: Get complex types with bool properties - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/BooleanWrapper' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Complex_putBool - description: Put complex types with bool properties - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/BooleanWrapper' - /complex/primitive/bytes: - get: - operationId: Complex_getBytes - description: Get complex types with bytes properties - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/BytesWrapper' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Complex_putBytes - description: Put complex types with bytes properties - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/BytesWrapper' - /complex/primitive/date: - get: - operationId: Complex_getPlainDate - description: Get complex types with date properties - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/PlainDateWrapper' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Complex_putPlainDate - description: Put complex types with date properties - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/PlainDateWrapper' - /complex/primitive/datetime: - get: - operationId: Complex_getZonedDateTime - description: Get complex types with DateTime properties - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/DateTimeWrapper' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Complex_putZonedDateTime - description: Put complex types with DateTime properties - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/DateTimeWrapper' - /complex/primitive/double: - get: - operationId: Complex_getDouble - description: Get complex types with double properties - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/DoubleWrapper' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Complex_putDouble - description: Put complex types with double properties - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/DoubleWrapper' - /complex/primitive/duration: - get: - operationId: Complex_getZonedDuration - description: Get complex types with Duration properties - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/DurationWrapper' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Complex_putZonedDuration - description: Put complex types with Duration properties - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/DurationWrapper' - /complex/primitive/float: - get: - operationId: Complex_getFloat - description: Get complex types with float properties - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/FloatWrapper' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Complex_putFloat - description: Put complex types with float properties - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/FloatWrapper' - /complex/primitive/integer: - get: - operationId: Complex_getInt - description: Get complex types with integer properties - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/IntWrapper' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Complex_putInt - description: Put complex types with integer properties - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/IntWrapper' - /complex/primitive/long: - get: - operationId: Complex_getLong - description: Get complex types with long properties - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/LongWrapper' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Complex_putLong - description: Put complex types with long properties - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/LongWrapper' - /complex/primitive/string: - get: - operationId: Complex_getString - description: Get complex types with string properties - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/StringWrapper' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Complex_putString - description: Put complex types with string properties - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/StringWrapper' -components: - schemas: - ArrayWrapper: - type: object - required: - - field - properties: - field: - type: array - items: - type: string - Basic: - type: object - required: - - id - - name - - color - properties: - id: - type: integer - format: int32 - nullable: true - description: Basic Id - name: - type: string - description: Name property with a very long description that does not fit on a single line and a line break. - color: - $ref: '#/components/schemas/Colors' - BooleanWrapper: - type: object - required: - - field_true - - field_false - properties: - field_true: - type: boolean - field_false: - type: boolean - BytesWrapper: - type: object - required: - - field - properties: - field: - type: string - format: byte - Colors: - type: string - enum: - - cyan - - Magenta - - YELLOW - - blacK - DateTimeWrapper: - type: object - required: - - field - - now - properties: - field: - type: string - format: date-time - now: - type: string - format: date-time - DictionaryWrapper: - type: object - required: - - field - properties: - field: - type: object - additionalProperties: - type: string - DoubleWrapper: - type: object - required: - - field1 - - field_56_zeros_after_the_dot_and_negative_zero_before_dot_and_this_is_a_long_field_name_on_purpose - properties: - field1: - type: number - format: double - field_56_zeros_after_the_dot_and_negative_zero_before_dot_and_this_is_a_long_field_name_on_purpose: - type: number - format: double - DurationWrapper: - type: object - required: - - field - properties: - field: - type: string - format: duration - Error: - type: object - required: - - message - properties: - message: - type: string - description: Error - FloatWrapper: - type: object - required: - - field1 - - field2 - properties: - field1: - type: number - format: float - field2: - type: number - format: float - IntWrapper: - type: object - required: - - field1 - - field2 - properties: - field1: - type: integer - format: int32 - field2: - type: integer - format: int32 - LongWrapper: - type: object - required: - - field1 - - field2 - properties: - field1: - type: integer - format: int64 - field2: - type: integer - format: int64 - Pet: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int32 - name: - type: string - PlainDateWrapper: - type: object - required: - - field - - leap - properties: - field: - type: string - format: date - leap: - type: string - format: date - ReadonlyObj: - type: object - required: - - id - - size - properties: - id: - type: integer - format: int32 - readOnly: true - size: - type: integer - format: int32 - readOnly: true - StringWrapper: - type: object - required: - - field - - empty - - 'null' - properties: - field: - type: string - empty: - type: string - 'null': - type: string - nullable: true diff --git a/packages/samples/test/output/testserver/body-string/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/testserver/body-string/@typespec/openapi3/openapi.yaml deleted file mode 100644 index f1b759fc6c7..00000000000 --- a/packages/samples/test/output/testserver/body-string/@typespec/openapi3/openapi.yaml +++ /dev/null @@ -1,330 +0,0 @@ -openapi: 3.0.0 -info: - title: sample - version: 0.0.0 -tags: - - name: String Operations -paths: - /string/base64Encoding: - get: - operationId: String_getBase64Encoding - description: Get value that is base64 encoded - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/octet-stream: - schema: - type: string - format: binary - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - put: - operationId: String_putBase64Encoding - description: Put value that is base64 encoded - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - requestBody: - required: true - content: - application/octet-stream: - schema: - type: string - format: binary - /string/empty: - get: - operationId: String_getEmpty - description: Get empty string value - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - enum: - - '' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - put: - operationId: String_putEmpty - description: Put empty string value - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - requestBody: - required: true - content: - text/plain: - schema: - type: string - enum: - - '' - /string/enum/constant: - get: - operationId: Enums_getConstant - description: Gets value 'green-color' from a constant - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/Colors' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - put: - operationId: Enums_putConstant - description: Sends value 'green-color' from a constant - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Colors' - /string/enum/empty: - get: - operationId: Enums_getNotExpandable - description: Get non expandable string enum value - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/Colors' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - put: - operationId: Enums_putNotExpandable - description: Put non expandable string enum value - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Colors' - /string/mbcs: - get: - operationId: String_getMbcs - description: Get mbcs string value '啊齄丂狛狜隣郎隣兀﨩ˊ〞〡¦℡㈱‐ー﹡﹢﹫、〓ⅰⅹ⒈€㈠㈩ⅠⅫ! ̄ぁんァヶΑ︴АЯаяāɡㄅㄩ─╋︵﹄︻︱︳︴ⅰⅹɑɡ〇〾⿻⺁䜣€' - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - enum: - - 啊齄丂狛狜隣郎隣兀﨩ˊ〞〡¦℡㈱‐ー﹡﹢﹫、〓ⅰⅹ⒈€㈠㈩ⅠⅫ! ̄ぁんァヶΑ︴АЯаяāɡㄅㄩ─╋︵﹄︻︱︳︴ⅰⅹɑɡ〇〾⿻⺁䜣€ - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - put: - operationId: String_putMbCs - description: Put mbcs string value '啊齄丂狛狜隣郎隣兀﨩ˊ〞〡¦℡㈱‐ー﹡﹢﹫、〓ⅰⅹ⒈€㈠㈩ⅠⅫ! ̄ぁんァヶΑ︴АЯаяāɡㄅㄩ─╋︵﹄︻︱︳︴ⅰⅹɑɡ〇〾⿻⺁䜣€' - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - requestBody: - required: true - content: - text/plain: - schema: - type: string - enum: - - 啊齄丂狛狜隣郎隣兀﨩ˊ〞〡¦℡㈱‐ー﹡﹢﹫、〓ⅰⅹ⒈€㈠㈩ⅠⅫ! ̄ぁんァヶΑ︴АЯаяāɡㄅㄩ─╋︵﹄︻︱︳︴ⅰⅹɑɡ〇〾⿻⺁䜣€ - /string/null: - get: - operationId: String_getNull - description: Get null string value - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - type: string - nullable: true - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - put: - operationId: String_putNull - description: Put null string value - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - requestBody: - required: true - content: - application/json: - schema: - type: string - nullable: true - /string/whitespace: - get: - operationId: String_getWhitespace - description: Get string value with leading and trailing whitespace 'Now is the time for all good men to come to the aid of their country' - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - enum: - - ' Now is the time for all good men to come to the aid of their country ' - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - put: - operationId: String_putWhitespace - description: Get string value with leading and trailing whitespace 'Now is the time for all good men to come to the aid of their country' - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - tags: - - String Operations - requestBody: - required: true - content: - text/plain: - schema: - type: string - enum: - - ' Now is the time for all good men to come to the aid of their country ' -components: - schemas: - Colors: - type: string - enum: - - red color - - green-color - - blue-color - Error: - type: object - required: - - status - - message - properties: - status: - type: integer - format: int32 - message: - type: string - description: Error diff --git a/packages/samples/test/output/testserver/body-time/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/testserver/body-time/@typespec/openapi3/openapi.yaml deleted file mode 100644 index fe72294ef95..00000000000 --- a/packages/samples/test/output/testserver/body-time/@typespec/openapi3/openapi.yaml +++ /dev/null @@ -1,59 +0,0 @@ -openapi: 3.0.0 -info: - title: sample - version: 0.0.0 -tags: [] -paths: - /time: - get: - operationId: Time_get - description: Get time value "11:34:56" - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - format: time - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - put: - operationId: Time_put - description: Put time value "08:07:56" - parameters: [] - responses: - '200': - description: The request has succeeded. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - text/plain: - schema: - type: string - format: time -components: - schemas: - Error: - type: object - required: - - status - - message - properties: - status: - type: integer - format: int32 - message: - type: string - description: Error diff --git a/packages/samples/test/output/testserver/media-types/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/testserver/media-types/@typespec/openapi3/openapi.yaml deleted file mode 100644 index 8e7c881d4e1..00000000000 --- a/packages/samples/test/output/testserver/media-types/@typespec/openapi3/openapi.yaml +++ /dev/null @@ -1,78 +0,0 @@ -openapi: 3.0.0 -info: - title: sample - version: 0.0.0 -tags: [] -paths: - /mediatypes/analyze: - post: - operationId: AnalyzeBody - description: Analyze body, that could be different media types. - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - requestBody: - required: true - content: - application/pdf: - schema: - $ref: '#/components/schemas/SourcePath' - application/json: - schema: - $ref: '#/components/schemas/SourcePath' - image/jpeg: - schema: - $ref: '#/components/schemas/SourcePath' - image/png: - schema: - $ref: '#/components/schemas/SourcePath' - image/tiff: - schema: - $ref: '#/components/schemas/SourcePath' - description: Input parameter. - /mediatypes/contentTypeWithEncoding: - post: - operationId: contentTypeWithEncoding - description: Pass in contentType 'text/plain; encoding=UTF-8' to pass test. Value for input does not matter - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - requestBody: - required: true - content: - text/plain: - schema: - $ref: '#/components/schemas/SourcePath' - description: Input parameter. -components: - schemas: - Input: - type: object - required: - - input - properties: - input: - allOf: - - $ref: '#/components/schemas/SourcePath' - description: Input parameter. - SourcePath: - type: object - required: - - source - properties: - source: - type: string - minLength: 0 - maxLength: 2048 - description: File source path. - description: Uri or local path to source data. diff --git a/packages/samples/test/output/testserver/multiple-inheritance/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/testserver/multiple-inheritance/@typespec/openapi3/openapi.yaml deleted file mode 100644 index 0e262ffaec4..00000000000 --- a/packages/samples/test/output/testserver/multiple-inheritance/@typespec/openapi3/openapi.yaml +++ /dev/null @@ -1,259 +0,0 @@ -openapi: 3.0.0 -info: - title: sample - version: 0.0.0 -tags: [] -paths: - /multipleInheritance/cat: - get: - operationId: MultipleInheritance_getCat - description: Get a cat with name 'Whiskers' where likesMilk, meows, and hisses is true - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/Cat' - default: - description: Unexpected error - content: - text/plain: - schema: - type: string - put: - operationId: MultipleInheritance_putCat - description: Put a cat with name 'Boots' where likesMilk and hisses is false, meows is true - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - default: - description: Unexpected error - content: - text/plain: - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Cat' - /multipleInheritance/feline: - get: - operationId: MultipleInheritance_getFeline - description: Get a feline where meows and hisses are true - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/Feline' - default: - description: Unexpected error - content: - text/plain: - schema: - type: string - put: - operationId: MultipleInheritance_putFeline - description: Put a feline who hisses and doesn't meow - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - default: - description: Unexpected error - content: - text/plain: - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Feline' - /multipleInheritance/horse: - get: - operationId: MultipleInheritance_getHorse - description: Get a horse with name 'Fred' and isAShowHorse true - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/Horse' - default: - description: Unexpected error - content: - text/plain: - schema: - type: string - put: - operationId: MultipleInheritance_putHorse - description: Put a horse with name 'General' and isAShowHorse false - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - default: - description: Unexpected error - content: - text/plain: - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Horse' - /multipleInheritance/kitten: - get: - operationId: MultipleInheritance_getKitten - description: Get a kitten with name 'Gatito' where likesMilk and meows is true, and hisses and eatsMiceYet is false - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/Kitten' - default: - description: Unexpected error - content: - text/plain: - schema: - type: string - put: - operationId: MultipleInheritance_putKitten - description: Put a kitten with name 'Kitty' where likesMilk and hisses is false, meows and eatsMiceYet is true - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - default: - description: Unexpected error - content: - text/plain: - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Kitten' - /multipleInheritance/pet: - get: - operationId: MultipleInheritance_getPet - description: Get a pet with name 'Peanut' - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/Pet' - default: - description: Unexpected error - content: - text/plain: - schema: - type: string - put: - operationId: MultipleInheritance_putPet - description: Put a pet with name 'Butter' - parameters: [] - responses: - '200': - description: The request has succeeded. - content: - text/plain: - schema: - type: string - default: - description: Unexpected error - content: - text/plain: - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Pet' -components: - schemas: - Cat: - type: object - required: - - likesMilk - properties: - likesMilk: - type: boolean - allOf: - - $ref: '#/components/schemas/Feline' - Feline: - type: object - required: - - meows - - hisses - properties: - meows: - type: boolean - hisses: - type: boolean - allOf: - - $ref: '#/components/schemas/Pet' - Horse: - type: object - required: - - isAShowHorse - properties: - isAShowHorse: - type: boolean - allOf: - - $ref: '#/components/schemas/Pet' - Kitten: - type: object - required: - - eatsMiceYet - properties: - eatsMiceYet: - type: boolean - allOf: - - $ref: '#/components/schemas/Cat' - Pet: - type: object - required: - - name - properties: - name: - type: string diff --git a/packages/samples/test/sample-config.test.ts b/packages/samples/test/sample-config.test.ts new file mode 100644 index 00000000000..762d238863a --- /dev/null +++ b/packages/samples/test/sample-config.test.ts @@ -0,0 +1,114 @@ +import { findTestPackageRoot } from "@typespec/compiler/testing"; +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { loadSampleCatalog } from "../src/sample-config.js"; + +describe("sample config", () => { + it("loads the canonical playground samples in configured order", async () => { + const packageRoot = await findTestPackageRoot(import.meta.url); + const catalog = await loadSampleCatalog(resolve(packageRoot, "specs")); + + expect(catalog.samples.filter((x) => x.playground).map((x) => x.config.title)).toEqual([ + "HTTP service", + "REST framework", + "API versioning", + "Discriminated unions", + "Protobuf kiosk", + "JSON Schema", + "GraphQL", + ]); + }); + + it("inherits playground exclusions from directory configs", async () => { + await withTempCatalog( + { + "legacy/sample-config.yaml": "directory: true\nplayground: false\n", + "legacy/widget/sample-config.yaml": "title: Legacy widget\ndescription: A legacy sample.\n", + "legacy/widget/main.tsp": "model Widget {}", + }, + async (root) => { + const catalog = await loadSampleCatalog(root); + expect(catalog.samples[0].playground).toBe(false); + }, + ); + }); + + it("reports missing required metadata", async () => { + await withTempCatalog( + { + "widget/sample-config.yaml": "title: Widget\n", + "widget/main.tsp": "model Widget {}", + }, + async (root) => { + await expect(loadSampleCatalog(root)).rejects.toThrow( + 'requires a non-empty "description" field', + ); + }, + ); + }); + + it("reports samples without metadata", async () => { + await withTempCatalog({ "widget/main.tsp": "model Widget {}" }, async (root) => { + await expect(loadSampleCatalog(root)).rejects.toThrow("is missing sample-config.yaml"); + }); + }); + + it("reports duplicate sample titles", async () => { + await withTempCatalog( + { + "one/sample-config.yaml": "title: Widget\ndescription: First sample.\n", + "one/main.tsp": "model One {}", + "two/sample-config.yaml": "title: Widget\ndescription: Second sample.\n", + "two/main.tsp": "model Two {}", + }, + async (root) => { + await expect(loadSampleCatalog(root)).rejects.toThrow('has duplicate title "Widget"'); + }, + ); + }); + + it("reports malformed and unknown metadata", async () => { + await withTempCatalog( + { + "widget/sample-config.yaml": + "title: Widget\ndescription: A widget sample.\nunexpected: true\n", + "widget/main.tsp": "model Widget {}", + }, + async (root) => { + await expect(loadSampleCatalog(root)).rejects.toThrow('unknown field "unexpected"'); + }, + ); + }); + + it("reports metadata without a sample entrypoint", async () => { + await withTempCatalog( + { + "widget/sample-config.yaml": "title: Widget\ndescription: A widget sample.\n", + }, + async (root) => { + await expect(loadSampleCatalog(root)).rejects.toThrow( + "must contain main.tsp or a package.json TypeSpec entrypoint", + ); + }, + ); + }); +}); + +async function withTempCatalog( + files: Record, + callback: (root: string) => Promise, +): Promise { + const root = await mkdtemp(resolve(tmpdir(), "typespec-samples-")); + try { + for (const [name, content] of Object.entries(files)) { + const path = resolve(root, name); + await mkdir(resolve(path, ".."), { recursive: true }); + await writeFile(path, content); + } + await callback(root); + } finally { + await rm(root, { recursive: true }); + } +} diff --git a/packages/samples/test/samples.test.ts b/packages/samples/test/samples.test.ts index 734c09a0b39..0d70c0c0cb1 100644 --- a/packages/samples/test/samples.test.ts +++ b/packages/samples/test/samples.test.ts @@ -6,6 +6,8 @@ import { defineSampleSnaphotTests } from "../src/sample-snapshot-testing.js"; const excludedSamples = [ // fails compilation by design to demo language server "local-typespec", + // The GraphQL emitter's Alloy output is validated in its package; the playground compiles this sample. + "emitters/graphql", ]; const pkgRoot = await findTestPackageRoot(import.meta.url); diff --git a/packages/samples/vitest.config.ts b/packages/samples/vitest.config.ts index bd5b317b122..6151b95852f 100644 --- a/packages/samples/vitest.config.ts +++ b/packages/samples/vitest.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { + include: ["test/**/*.test.ts"], environment: "node", testTimeout: 10000, isolate: false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c60b4fdeade..f658c6a212e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1980,6 +1980,9 @@ importers: '@typespec/rest': specifier: workspace:^ version: link:../rest + '@typespec/samples': + specifier: workspace:^ + version: link:../samples '@typespec/sse': specifier: workspace:^ version: link:../sse @@ -2214,6 +2217,9 @@ importers: '@typespec/events': specifier: workspace:^ version: link:../events + '@typespec/graphql': + specifier: workspace:^ + version: link:../graphql '@typespec/html-program-viewer': specifier: workspace:^ version: link:../html-program-viewer @@ -2250,6 +2256,9 @@ importers: '@typespec/versioning': specifier: workspace:^ version: link:../versioning + yaml: + specifier: 'catalog:' + version: 2.9.0 devDependencies: '@types/node': specifier: 'catalog:'