typespec1-11 Exercise 3: Migrating a Small OpenAPI File =============================================================== loans.yaml (hand-written, OpenAPI 3.0.3: bearer auth on the create, an optional request body, a query enum, an allOf, a nullable property): openapi: 3.0.3 info: title: Loan Service version: 1.2.0 paths: /loans: get: operationId: listLoans summary: List loans parameters: - name: status in: query schema: { $ref: '#/components/schemas/LoanStatus' } - name: X-Request-Id in: header schema: { type: string } responses: '200': description: OK content: application/json: schema: type: array items: { $ref: '#/components/schemas/Loan' } post: operationId: createLoan summary: Lend a book security: - bearerAuth: [] requestBody: required: false content: application/json: schema: { $ref: '#/components/schemas/NewLoan' } responses: '201': description: Created headers: Location: schema: { type: string } content: application/json: schema: { $ref: '#/components/schemas/Loan' } '422': description: Invalid components: securitySchemes: bearerAuth: type: http scheme: bearer schemas: LoanStatus: type: string enum: [active, returned, overdue] NewLoan: type: object required: [isbn, member] properties: isbn: { type: string, pattern: '^[0-9]{13}$' } member: { type: string } note: { type: string, nullable: true } Loan: allOf: - $ref: '#/components/schemas/NewLoan' - type: object required: [id, status] properties: id: { type: string, format: uuid } status: { $ref: '#/components/schemas/LoanStatus' } Converted with: tsp-openapi3 loans.yaml --output-dir converted --namespace Loans import "@typespec/http"; import "@typespec/openapi"; import "@typespec/openapi3"; using Http; using OpenAPI; @service(#{ title: "Loan Service" }) @info(#{ version: "1.2.0" }) namespace Loans; enum LoanStatus { active, returned, overdue, } model NewLoan { @pattern("^[0-9]{13}$") isbn: string; member: string; note?: string | null; } model Loan { ...NewLoan; @format("uuid") id: string; status: LoanStatus; } @route("/loans") @get @summary("List loans") op listLoans( @query(#{ explode: true }) status?: LoanStatus, @header `X-Request-Id`?: string, ): Body; @route("/loans") @post @summary("Lend a book") op createLoan(@body body: NewLoan): | { @statusCode statusCode: 201; @header Location?: string; @body body: Loan; } | { @statusCode statusCode: 422; }; Compiled back to OpenAPI 3.0.0 ('Compilation completed successfully'), then: oasdiff changelog loans.yaml roundtrip.yaml error api-security-removed POST /loans the endpoint scheme security `bearerAuth` was removed from the API error request-body-became-required POST /loans request body became required warning response-property-all-of-removed GET /loans removed `#/components/schemas/NewLoan, subschema #2` from the `items/` response property `allOf` list for the response status `200` warning response-property-all-of-removed GET /loans This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together. warning response-body-all-of-removed POST /loans removed `#/components/schemas/NewLoan, subschema #2` from the response body `allOf` list for the response status `201` warning response-body-all-of-removed POST /loans This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together. info api-security-component-removed POST /loans the component security scheme `bearerAuth` was removed info api-version-not-bumped POST /loans a breaking change was detected but the version is still `1.2.0` info response-optional-property-added GET /loans added the optional property `items/note` to the response with the `200` status info response-property-type-specialized GET /loans the `items/` response's property `type` was narrowed from `any` to `object` for status `200` info response-property-type-specialized GET /loans This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together. info response-required-property-added GET /loans added the required property `items/id` to the response with the `200` status info response-required-property-added GET /loans added the required property `items/isbn` to the response with the `200` status info response-required-property-added GET /loans added the required property `items/member` to the response with the `200` status info response-required-property-added GET /loans added the required property `items/status` to the response with the `200` status info response-body-type-specialized POST /loans the response's body `type` was narrowed from `any` to `object` for status `201` info response-body-type-specialized POST /loans This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together. info response-optional-property-added POST /loans added the optional property `note` to the response with the `201` status info response-optional-property-added POST /loans This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together. info response-required-property-added POST /loans added the required property `id` to the response with the `201` status info response-required-property-added POST /loans This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together. info response-required-property-added POST /loans added the required property `isbn` to the response with the `201` status info response-required-property-added POST /loans This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together. info response-required-property-added POST /loans added the required property `member` to the response with the `201` status info response-required-property-added POST /loans This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together. info response-required-property-added POST /loans added the required property `status` to the response with the `201` status info response-required-property-added POST /loans This compared the `allOf` branches one at a time, so another branch may still guarantee what this one dropped. Run with --flatten-allof to merge the branches and compare what they describe together. WHAT WAS REALLY LOST 1. SECURITY. 'bearerAuth' is gone from POST /loans and from the components (api-security-removed, error). The converted TypeSpec has no @useAuth. 2. 'required: false' on the request body became required (request-body-became-required, error). The converted operation takes '@body body: NewLoan', which is required. WHAT IS ONLY NOISE (as far as I can tell) The allOf warnings and the many 'required property added' / 'type narrowed' info lines: the converter turned the allOf into a spread (...NewLoan), so Loan now lists its properties directly instead of through allOf. oasdiff compares allOf branches one at a time and says so itself ('another branch may still guarantee what this one dropped'). The structure of the data is the same; the shape of the schema is not. The 'api-version-not-bumped' info is oasdiff pointing out a breaking change with an unchanged version number. ALSO NOTICE The 422 response came through without a body, and the Location header came through as optional ('Location?'), because the original did not say otherwise. The nullable 'note' became 'string | null' (an optional property; the original was nullable but not optional in the sense that it was not listed as required either, so the optionality is faithful). WHY THIS WORKS AS AN ANSWER --------------------------- It reproduces the chapter's Petstore finding on a file small enough to read completely, and separates real losses from differences in how the same structure is written. Fixing the migration would mean adding @useAuth(BearerAuth) and making the body optional, then diffing again.