diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..6505289 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,65 @@ +name: lint + +on: + push: + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.26" + + - name: Install openapi CLI + run: go install github.com/speakeasy-api/openapi/cmd/openapi@v0.0.0-20260622002648-cfc99c3a8316 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Install yamllint + run: pip install yamllint + + - name: yamllint YAML specs + shell: bash + run: | + shopt -s globstar nullglob + files=(openapi/**/*.yaml openapi/**/*.yml) + if [ ${#files[@]} -eq 0 ]; then + echo "No YAML spec files found, skipping yamllint." + else + yamllint -c .yamllint.yml "${files[@]}" + fi + + - name: Validate JSON spec syntax + shell: bash + run: | + shopt -s globstar nullglob + files=(openapi/**/*.json) + if [ ${#files[@]} -eq 0 ]; then + echo "No JSON spec files found, skipping JSON syntax check." + else + for f in "${files[@]}"; do + jq empty "$f" + done + fi + + - name: Validate OpenAPI specs + shell: bash + run: | + shopt -s globstar nullglob + files=(openapi/**/*.yaml openapi/**/*.yml openapi/**/*.json) + if [ ${#files[@]} -eq 0 ]; then + echo "No spec files found, skipping OpenAPI validation." + else + for f in "${files[@]}"; do + echo "Validating $f" + openapi spec validate "$f" + done + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ab5ce52 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,27 @@ +name: release + +on: + push: + branches: + - main + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build asset bundle + env: + SHA: ${{ github.sha }} + run: tar czf "morphic-test-assets-${SHA}.tar.gz" openapi/ + + - name: Create GitHub release + uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 + with: + tag_name: ${{ github.sha }} + name: ${{ github.sha }} + files: morphic-test-assets-${{ github.sha }}.tar.gz diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..18f94b0 --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,10 @@ +extends: default + +rules: + line-length: + max: 120 + level: warning + document-start: disable + indentation: + spaces: consistent + indent-sequences: whatever diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..864ea15 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,92 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this repo is + +`morphic-test-assets` is a versioned, external corpus of real-world API specs used to test +[morphic](https://github.com/dexpace/morphic), a spec-to-SDK compiler. Morphic parses specs +(OpenAPI today; Swagger, GraphQL, protobuf, TypeSpec planned) into a spec-agnostic intermediate +representation (IR). This repo holds spec fixtures — and eventually their expected IR output — +independent of morphic's own internal `testdata/` golden fixtures, so morphic's tooling and CI can +pull in a growing corpus without vendoring it into the morphic repo itself. + +There is no application code here. This repo is fixtures, directory conventions, and CI. + +## Project status + +Early scaffold. Only OpenAPI v3.x is in scope for now. One seed fixture exists: +`openapi/v3.1/petstore/spec.yaml`. `openapi/v3.0/` and `openapi/v3.2/` are placeholder directories +(`.gitkeep`) awaiting real examples. `expected_ir_file` goldens are deliberately deferred — morphic's +IR format is still evolving (Milestone 1 in the morphic repo), so hand-written or generated goldens +would go stale almost immediately. + +## Directory & naming conventions + +- Spec-type directories live at the repo root: `openapi/` (future: `graphql/`, `protobuf/`, + `typespec/`, ...). +- One directory per spec version inside each: `v3.0/`, `v3.1/`, `v3.2/`. +- One directory per API inside that: `///`. +- Each API directory holds `spec_file` and, once available, `expected_ir_file` (e.g. `spec.yaml` + + `spec-ir.json`). +- APIs split across multiple spec documents use numbered files instead: `spec-1.yml`, `spec-2.yml`, + with matching `spec-1-ir.json`, `spec-2-ir.json` once IR goldens are added. +- Spec files keep their original serialization format (YAML or JSON) when added — don't normalize + or convert format; that's needless transformation and risks drifting from the upstream source. + +## Fixture quality bar + +- A fixture must be genuinely valid OpenAPI, not merely "looks right." Before adding or changing + any spec file, validate it locally: + ```bash + go install github.com/speakeasy-api/openapi/cmd/openapi@v0.0.0-20260622002648-cfc99c3a8316 + openapi spec validate path/to/spec.yaml + ``` + This is the exact same parser library morphic's own compiler depends on + (`github.com/speakeasy-api/openapi`) — if a spec doesn't validate cleanly here, morphic won't + compile it either. +- Don't assume a fixture's origin without checking. A prior fixture + (`openapi/v3.1/petstore/spec.yaml`) was assumed at first to be the official OAI example; tracing + it (diffing against public repos) showed it was actually `speakeasy-api/openapi`'s own test + fixture, and it had one real defect (`$vocabulary` as a bare string instead of the + JSON-Schema-2020-12-required object) that `openapi spec validate` caught and that was fixed in + this repo's copy. Verify provenance and correctness rather than trusting a filename or a + familiar-looking shape. +- Don't gate validity on Spectral or other OpenAPI-3.0-era linters for 3.1 fixtures that use + `$id`-based JSON Schema 2020-12 references — they raise false-positive `invalid-ref` errors + because they don't do full `$id` resolution. `openapi spec validate` is the ground truth here. +- Don't gate on `openapi spec lint` (the same CLI's OWASP/security/style ruleset) either — it's + calibrated for hand-authored new APIs, not a corpus of arbitrary real-world specs, and produces + dozens of stylistic/security-best-practice findings that aren't fixture defects. + +## CI + +- `.github/workflows/lint.yml` (push, PR): `yamllint` on YAML spec files (config: `.yamllint.yml`), + `jq empty` on JSON spec files, `openapi spec validate` on every spec file. +- `.github/workflows/release.yml` (push to `main` only — this single trigger is meant to cover both + "every commit" and "every PR merge," since a merge is itself a push to `main`): bundles every + spec-type directory into `morphic-test-assets-.tar.gz` and publishes it as a + GitHub Release tagged with that same full 40-character commit SHA. +- No semantic versioning: consumers pin to a specific commit SHA / release, not a version range. + +## Repository rules + +These mirror the conventions already in force in `dexpace/morphic` and other dexpace repos, kept +consistent so contributors don't have to context-switch between repos. + +- Branch from `main`: `type/short-desc` (e.g. `feat/github-api-fixture`, + `fix/petstore-vocabulary`, `ci/pin-openapi-cli-version`). +- No CODEOWNERS, PR/issue templates, or CODE_OF_CONDUCT.md exist in this repo — don't assume them + or invent content for files that aren't there. + +## Commits & pull requests + +- **Conventional Commits**: `type(scope): subject`, imperative mood, subject line only (no period, + ≤72 chars). Common types here: `feat` (new fixture or spec-type support), `fix` (fixture defect), + `ci`, `docs`, `chore`. Scope is the touched spec type/API when it narrows things down + (`openapi/petstore`, `ci`) — omit it when the change is repo-wide. +- PR description: Summary / Test plan. Keep PRs scoped to one logical change (one new fixture, or + one CI change) — split unrelated changes into separate PRs. +- Write self-contained, human-framed titles/descriptions. No LLM/session artifacts, no internal + audit/finding IDs, no "remediation"/"audit sweep" framing. State the problem, the change, and the + rationale on their own terms. diff --git a/README.md b/README.md index 3ff8a63..17284a3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,65 @@ -# morphic-test-assets -A set of assets used to test the morphic framework and CLI +

+ + + dexpace + +

+ +

morphic-test-assets

+ +

+ lint + release + License +

+ +A versioned corpus of real-world API specs used to test +[morphic](https://github.com/dexpace/morphic), a spec-to-SDK compiler. Morphic +parses specs (OpenAPI today; Swagger, GraphQL, protobuf, and TypeSpec planned) +into a spec-agnostic Intermediate Representation (IR). This repo holds spec +fixtures — and eventually their expected IR output — independent of morphic's +own internal `testdata/` golden fixtures, so morphic's tooling and CI can pull +in a growing, external corpus. + +## Structure + +One directory per spec format at the repo root, then one per spec version, +then one per API: + +``` +openapi/ + v3.0/ + v3.1/ + petstore/ + spec.yaml + v3.2/ +``` + +Each API directory holds `spec_file` and, once available, `expected_ir_file` +(e.g. `spec.yaml` + `spec-ir.json`). IR golden files are omitted for now — +morphic's IR format is still evolving, so hand-written or generated goldens +would go stale immediately. Spec files keep their original serialization +format (YAML or JSON) rather than being normalized to one. + +APIs split across multiple spec documents use numbered files instead: +`spec-1.yml`, `spec-2.yml`, with matching `spec-1-ir.json`, `spec-2-ir.json` +once IR goldens are added. + +Only OpenAPI v3.x is in scope for now. + +## CI + +- **`lint.yml`** (push, PR): validates every file under `openapi/**` — + `yamllint` for YAML syntax, `jq` for JSON syntax, and + [`openapi spec validate`](https://github.com/speakeasy-api/openapi) (the + same OpenAPI parser morphic itself depends on) for structural OpenAPI + validity. +- **`release.yml`** (push to `main`): bundles every spec-type directory into + `morphic-test-assets-.tar.gz` and publishes it as a GitHub + Release tagged with the full commit SHA. One release per commit on `main`. + +## Versioning + +There's no semantic version — each release is tagged with the exact commit +SHA it was built from. Consumers pin to a specific SHA/release rather than a +version range. diff --git a/docs/assets/dexpace-wordmark-dark.svg b/docs/assets/dexpace-wordmark-dark.svg new file mode 100644 index 0000000..e3a3c8a --- /dev/null +++ b/docs/assets/dexpace-wordmark-dark.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/docs/assets/dexpace-wordmark-light.svg b/docs/assets/dexpace-wordmark-light.svg new file mode 100644 index 0000000..727bee4 --- /dev/null +++ b/docs/assets/dexpace-wordmark-light.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/openapi/v3.0/.gitkeep b/openapi/v3.0/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/openapi/v3.1/petstore/spec.yaml b/openapi/v3.1/petstore/spec.yaml new file mode 100644 index 0000000..9fdea76 --- /dev/null +++ b/openapi/v3.1/petstore/spec.yaml @@ -0,0 +1,292 @@ +openapi: 3.1.0 +info: + title: Swagger Petstore - OpenAPI 3.1 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.1 specification. + You can find out more about + Swagger at [https://swagger.io](https://swagger.io). + termsOfService: https://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.10 + summary: Pet Store 3.1 + x-namespace: swagger +externalDocs: + description: Find out more about Swagger + url: https://swagger.io +servers: +- url: /api/v31 +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: https://swagger.io +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet. + description: Update an existing pet by Id. + operationId: updatePet + requestBody: + description: Pet object that needs to be updated in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + description: A Pet in JSON Format + required: + - id + writeOnly: true + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: A Pet in XML Format + required: + - id + writeOnly: true + required: true + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: A Pet in XML Format + readOnly: true + application/json: + schema: + $ref: '#/components/schemas/Pet' + description: A Pet in JSON Format + readOnly: true + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "405": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Add a new pet to the store. + description: Add a new pet to the store. + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + description: A Pet in JSON Format + required: + - id + writeOnly: true + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: A Pet in XML Format + required: + - id + writeOnly: true + required: true + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: A Pet in XML Format + readOnly: true + application/json: + schema: + $ref: '#/components/schemas/Pet' + description: A Pet in JSON format + readOnly: true + "405": + description: Invalid input + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by it's identifier. + description: Returns a pet when 0 < ID <= 10. ID > 10 or non-integers will + simulate API error conditions. + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: integer + format: int64 + description: param ID of pet that needs to be fetched + exclusiveMaximum: 10 + exclusiveMinimum: 1 + responses: + "200": + description: The pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + description: A Pet in JSON format + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: A Pet in XML format + "400": + description: Invalid ID supplied + "404": + description: Pet not found + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + - api_key: [] +components: + schemas: + Category: + $id: /api/v31/components/schemas/category + description: Category + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + Pet: + $schema: https://json-schema.org/draft/2020-12/schema + description: Pet + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + description: Pet Category + name: + type: string + examples: + - doggie + photoUrls: + type: array + items: + type: string + xml: + name: photoUrl + xml: + wrapped: true + tags: + type: array + items: + $ref: '#/components/schemas/Tag' + xml: + wrapped: true + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + availableInstances: + type: integer + format: int32 + examples: + - "7" + exclusiveMaximum: 10 + exclusiveMinimum: 1 + swagger-extension: true + petDetailsId: + type: integer + format: int64 + $ref: /api/v31/components/schemas/petdetails#pet_details_id + petDetails: + $ref: /api/v31/components/schemas/petdetails + required: + - name + - photoUrls + xml: + name: Pet + PetDetails: + $id: /api/v31/components/schemas/petdetails + $schema: https://json-schema.org/draft/2020-12/schema + $vocabulary: + https://spec.openapis.org/oas/3.1/schema-base: true + properties: + id: + type: integer + format: int64 + $anchor: pet_details_id + examples: + - "10" + category: + $ref: /api/v31/components/schemas/category + description: PetDetails Category + tag: + $ref: /api/v31/components/schemas/tag + xml: + name: PetDetails + Tag: + $id: /api/v31/components/schemas/tag + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore31.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + mutual_tls: + type: mutualTLS + api_key: + type: apiKey + name: api_key + in: header +webhooks: + newPet: + post: + requestBody: + description: Information about a new pet in the system + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + description: Webhook Pet + responses: + "200": + description: Return a 200 status to indicate that the data was received + successfully diff --git a/openapi/v3.2/.gitkeep b/openapi/v3.2/.gitkeep new file mode 100644 index 0000000..e69de29