From 4c1a3bb72836ff5f77a897cc23f03cceb35b86b6 Mon Sep 17 00:00:00 2001 From: Eivind Grimstad Date: Thu, 19 Feb 2026 12:12:14 +0100 Subject: [PATCH] feat: Add ci workflow to check that linting works --- .github/workflows/ci.yml | 76 +++++++ .github/workflows/pr-validate.yml | 13 -- specs/reference-spec-with-errors.json | 284 ++++++++++++++++++++++++++ specs/reference-spec.json | 6 +- 4 files changed, 364 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/pr-validate.yml create mode 100644 specs/reference-spec-with-errors.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1660ed6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,76 @@ +name: CI + +on: + pull_request: + branches: + - main + +jobs: + validate-pr: + uses: entur/gha-meta/.github/workflows/verify-pr.yml@v1 + + lint-reference-spec: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + - name: Install Spectral + run: npm install -g @stoplight/spectral-cli + - name: Lint OpenAPI + shell: bash + run: | + set -o errexit + set -o nounset + set -o pipefail + + annotations=$(mktemp) + markdown=$(mktemp) + spectral lint ./specs/reference-spec.json --ruleset ".spectral.yml" -F hint -f github-actions -o.github-actions "$annotations" -f markdown -o.markdown="$markdown" && rc=$? || rc=$? + + #Return code 2 means that spectral command failed to run (not linting errors), so fail workflow. + if [ "$rc" -eq 2 ]; then + echo "::error::Spectral execution error (exit code $rc)." + exit $rc + fi + + if [ "$rc" -eq 1 ]; then + cat $annotations + cat $markdown >> $GITHUB_STEP_SUMMARY + echo + echo "::error::There were linting errors (exit code $rc)." + exit $rc + fi + + lint-reference-spec-with-errors: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + - name: Install Spectral + run: npm install -g @stoplight/spectral-cli + - name: Lint OpenAPI + shell: bash + run: | + set -o errexit + set -o nounset + set -o pipefail + + annotations=$(mktemp) + markdown=$(mktemp) + spectral lint ./specs/reference-spec-with-errors.json --ruleset ".spectral.yml" -F hint -f github-actions -o.github-actions "$annotations" -f markdown -o.markdown="$markdown" && rc=$? || rc=$? + + #Return code 2 means that spectral command failed to run (not linting errors), so fail workflow. + if [ "$rc" -eq 2 ]; then + echo "::error::Spectral execution error (exit code $rc)." + exit $rc + fi + + if [ "$rc" -ne 1 ]; then + echo "::error::There were no linting errors when there should be (exit code $rc)." + exit $rc + else + cat $annotations + cat $markdown >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/pr-validate.yml b/.github/workflows/pr-validate.yml deleted file mode 100644 index 3614c41..0000000 --- a/.github/workflows/pr-validate.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Validate PR - -on: - pull_request: - branches: - - main - types: - - opened - - edited - -jobs: - validate-pr: - uses: entur/gha-meta/.github/workflows/verify-pr.yml@v1 diff --git a/specs/reference-spec-with-errors.json b/specs/reference-spec-with-errors.json new file mode 100644 index 0000000..13fa2fc --- /dev/null +++ b/specs/reference-spec-with-errors.json @@ -0,0 +1,284 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Items API", + "version": "1.0.0", + "description": "A long and descriptive description", + "termsOfService": "https://developer.entur.org/terms-of-service", + "contact": { + "name": "API Support", + "url": "https://entur.no", + "email": "support@entur.no" + }, + "x-entur-metadata": { + "id": "Items", + "owner": "api", + "audience": "public" + } + }, + + "tags": [ + {"name": "Items"} + ], + "servers": [ + { + "url": "https://api.entur.io/product/v1", + "description": "Production" + }, + { + "url": "https://api.staging.entur.io/product/v1", + "description": "Staging" + } + ], + "components": { + "parameters": { + "Item-Id": { + "name": "itemId", + "description": "The item ID.", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "example": "100" + }, + "Page": { + "description": "Results page you want to retrieve (0..N)", + "in": "query", + "required": true, + "name": "page", + "schema": { "type": "integer", "example": 0 } + }, + "Page-Size": { + "description": "Number of records per page.", + "in": "query", + "name": "size", + "schema": { "type": "integer", "example": 20 } + } + }, + "schemas": { + "Item": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "100" + }, + "name": { + "type": "string", + "example": "Item 100" + } + }, + "example": { + "id": "100", + "name": "Item 100" + } + }, + "Items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + "example": [ + { + "id": "100", + "name": "Item 100" + }, + { + "id": "101", + "name": "Item 101" + } + ] + }, + "Problem": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "https://example.com/probs/invalid-parameter" + }, + "title": { + "type": "string", + "example": "Invalid parameter" + }, + "detail": { + "type": "string", + "example": "The parameter 'exampleParameter' is invalid." + }, + "status": { + "type": "integer", + "example": 400 + } + }, + "example": { + "type": "https://example.com/probs/invalid-parameter", + "title": "Invalid parameter", + "detail": "The parameter 'exampleParameter' is invalid.", + "status": 400 + } + } + }, + "securitySchemes": { + "jwt": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" } + } + }, + "paths": { + "/items": { + "get": { + "tags": [ + "Items" + ], + "parameters": [ + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/Page-Size" + }, + { + "name": "status", + "description": "Filters items to return on status", + "in": "query", + "schema": { + "type": "string" + }, + "example": "completed" + } + ], + "summary": "Get items", + "description": "Get items matching a status filter, with pagination support.", + "operationId": "getItems", + "responses": { + "200": { + "description": "Get items", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Items" + } + } + } + } + }, + "x-entur-permissions": { + "value": "items:les" + } + }, + "post": { + "tags": [ + "Items" + ], + "security": [{"jwt": []}], + "summary": "Create an item", + "description": "Create a new item, given in request body.", + "operationId": "createItem", + "requestBody": { + "description": "Item to create", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + } + }, + "x-entur-permissions": { + "description": "You need access to read organisations, and create items.", + "value": { + "all": [ + "organisations:les", + { + "any": [ + "items:opprett", + "items-global:opprett" + ] + } + ] + } + } + } + }, + "/items/{itemId}": { + "parameters": [ + { + "$ref": "#/components/parameters/Item-Id" + } + ], + "get": { + "tags": [ + "Items" + ], + "summary": "Get an item", + "description": "Get an item with given id.", + "operationId": "getItem", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/Problem" + } + } + } + } + } + }, + "put": { + "tags": [ + "Items" + ], + "security": [{"jwt": []}], + "summary": "Update an item", + "description": "Update an item according to given item request body.", + "operationId": "updateItem", + "requestBody": { + "description": "Item to update", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + } + } + } + } + } +} diff --git a/specs/reference-spec.json b/specs/reference-spec.json index 73fa62e..5601d68 100644 --- a/specs/reference-spec.json +++ b/specs/reference-spec.json @@ -1,7 +1,7 @@ { "openapi": "3.1.0", "info": { - "title": "Example", + "title": "Items", "version": "1.0.0", "description": "A long and descriptive description", "termsOfService": "https://developer.entur.org/terms-of-service", @@ -11,7 +11,9 @@ "email": "support@entur.no" }, "x-entur-metadata": { - "id": "example" + "id": "items", + "owner": "team-api", + "audience": "partner" } },