diff --git a/package-lock.json b/package-lock.json index 6a23afa591..989fb6f0ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7677,11 +7677,12 @@ } }, "node_modules/@stoplight/spectral-core": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.22.0.tgz", - "integrity": "sha512-4hTxMDs4TFUG4/jKjaZttA65gNuV2PCKI9+51I+J4nL6ylo17DlbW+sl6byKnBuV/85HxaV33ri5fEGlp8lTSA==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.24.0.tgz", + "integrity": "sha512-abKt++GFFaqwKiGTfPZ5O3nIv8s+1V7J5awCjWPBtCXn7ZKTEPzEF8tBYsM3ne+RMrQ8ORkHzKFu9QZhenmkPw==", "license": "Apache-2.0", "dependencies": { + "@scarf/scarf": "^1.4.0", "@stoplight/better-ajv-errors": "1.0.3", "@stoplight/json": "~3.21.0", "@stoplight/path": "1.3.2", @@ -7760,13 +7761,14 @@ "license": "0BSD" }, "node_modules/@stoplight/spectral-formats": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-formats/-/spectral-formats-1.8.2.tgz", - "integrity": "sha512-c06HB+rOKfe7tuxg0IdKDEA5XnjL2vrn/m/OVIIxtINtBzphZrOgtRn7epQ5bQF5SWp84Ue7UJWaGgDwVngMFw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-formats/-/spectral-formats-1.9.0.tgz", + "integrity": "sha512-OmHZpeBdlWbDxAI43HR08vyVXY2D++YHBx9AWMQ6S2DH8aWWCvk64BJOJk161XsHJQuy/vnC8tRBIIKwSQPAsA==", "license": "Apache-2.0", "dependencies": { + "@scarf/scarf": "^1.4.0", "@stoplight/json": "^3.17.0", - "@stoplight/spectral-core": "^1.19.2", + "@stoplight/spectral-core": "^1.23.0", "@types/json-schema": "^7.0.7", "tslib": "^2.8.1" }, @@ -7967,15 +7969,16 @@ "license": "0BSD" }, "node_modules/@stoplight/spectral-rulesets": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-rulesets/-/spectral-rulesets-1.22.1.tgz", - "integrity": "sha512-DaaQJioKuYkRsOuKIJfX2ek7G7f6OCU3CI3K7ABaOcTFMiHj29SJLDdb04mCjXZFXMlXHjmCl2ZpKW6heieXpw==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-rulesets/-/spectral-rulesets-1.23.0.tgz", + "integrity": "sha512-svAajFVBpHScoAAjPcgf0dgC5afkQ+psvjjH/NYBDq/U0QLrn2QvmfRVJiqO0UetMed3Aiu87BvXyFGQA9j54Q==", "license": "Apache-2.0", "dependencies": { - "@asyncapi/specs": "^6.8.0", + "@asyncapi/specs": "6.11.1", + "@scarf/scarf": "^1.4.0", "@stoplight/better-ajv-errors": "1.0.3", "@stoplight/json": "^3.17.0", - "@stoplight/spectral-core": "^1.19.4", + "@stoplight/spectral-core": "^1.23.0", "@stoplight/spectral-formats": "^1.8.1", "@stoplight/spectral-functions": "^1.9.1", "@stoplight/spectral-runtime": "^1.1.2", @@ -26674,11 +26677,11 @@ "@rjsf/validator-ajv8": "6.8.0", "@scalar/openapi-parser": "0.29.19", "@sentry/electron": "^7.17.0", - "@stoplight/spectral-core": "^1.22.0", - "@stoplight/spectral-formats": "^1.8.2", + "@stoplight/spectral-core": "^1.23.0", + "@stoplight/spectral-formats": "^1.9.0", "@stoplight/spectral-ref-resolver": "^1.0.5", "@stoplight/spectral-ruleset-bundler": "1.7.0", - "@stoplight/spectral-rulesets": "^1.22.1", + "@stoplight/spectral-rulesets": "^1.23.0", "@tailwindcss/typography": "^0.5.16", "@tanstack/react-query": "^5.101.0", "@tanstack/react-virtual": "3.13.12", @@ -26877,10 +26880,10 @@ "license": "Apache-2.0", "dependencies": { "@seald-io/nedb": "^4.1.1", - "@stoplight/spectral-core": "^1.22.0", - "@stoplight/spectral-formats": "^1.8.2", + "@stoplight/spectral-core": "^1.23.0", + "@stoplight/spectral-formats": "^1.9.0", "@stoplight/spectral-ruleset-bundler": "1.7.0", - "@stoplight/spectral-rulesets": "^1.22.1", + "@stoplight/spectral-rulesets": "^1.23.0", "@stoplight/types": "^14.1.1", "commander": "^12.1.0", "consola": "^3.4.2", diff --git a/packages/insomnia-inso/package.json b/packages/insomnia-inso/package.json index a506e41d79..a31be872d9 100644 --- a/packages/insomnia-inso/package.json +++ b/packages/insomnia-inso/package.json @@ -53,10 +53,10 @@ }, "dependencies": { "@seald-io/nedb": "^4.1.1", - "@stoplight/spectral-core": "^1.22.0", - "@stoplight/spectral-formats": "^1.8.2", + "@stoplight/spectral-core": "^1.23.0", + "@stoplight/spectral-formats": "^1.9.0", "@stoplight/spectral-ruleset-bundler": "1.7.0", - "@stoplight/spectral-rulesets": "^1.22.1", + "@stoplight/spectral-rulesets": "^1.23.0", "@stoplight/types": "^14.1.1", "commander": "^12.1.0", "consola": "^3.4.2", diff --git a/packages/insomnia-inso/src/commands/fixtures/openapi-3.1-valid.yaml b/packages/insomnia-inso/src/commands/fixtures/openapi-3.1-valid.yaml new file mode 100644 index 0000000000..40b60b0547 --- /dev/null +++ b/packages/insomnia-inso/src/commands/fixtures/openapi-3.1-valid.yaml @@ -0,0 +1,141 @@ +openapi: 3.1.1 +info: + title: Pet Store (OpenAPI 3.1 sample) + version: 1.0.0 + summary: Sample document exercising OpenAPI 3.1 features + description: Used to test Insomnia's lint support for OpenAPI 3.1. + contact: + email: support@insomnia.rest + license: + name: Apache 2.0 + identifier: Apache-2.0 +jsonSchemaDialect: https://spec.openapis.org/oas/3.1/dialect/base +servers: + - url: https://api.example.com/v1 +tags: + - name: pets +paths: + /pets: + get: + operationId: listPets + summary: List pets + description: List pets + tags: [pets] + parameters: + - name: limit + in: query + schema: + type: [integer, 'null'] + exclusiveMinimum: 0 + - name: status + in: query + schema: + const: available + responses: + '200': + description: A list of pets + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + post: + operationId: createPet + summary: Create a pet + description: Create a pet + tags: [pets] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NewPet' + responses: + '201': + description: Created + /pets/{petId}: + parameters: + - name: petId + in: path + required: true + schema: + type: integer + get: + operationId: getPet + summary: Get a pet + description: Get a pet + tags: [pets] + responses: + '200': + description: The pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + /uploads: + post: + operationId: upload + summary: Upload a picture + description: Upload a picture + tags: [pets] + requestBody: + content: + application/octet-stream: {} + image/png: + schema: + type: string + contentMediaType: image/png + contentEncoding: base64 + responses: + '204': + description: Uploaded +webhooks: + petAdopted: + post: + operationId: onPetAdopted + summary: Fired when a pet is adopted + description: Fired when a pet is adopted + tags: [pets] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + responses: + '200': + description: Acknowledged +components: + schemas: + NewPet: + type: object + required: [name] + properties: + name: + type: string + examples: [Rex] + tag: + type: [string, 'null'] + nickname: + $ref: '#/components/schemas/Nickname' + description: Sibling keyword next to $ref (allowed in 3.1) + Nickname: + type: string + examples: [Buddy] + Pet: + allOf: + - $ref: '#/components/schemas/NewPet' + - type: object + required: [id] + properties: + id: + type: integer + format: int64 + securitySchemes: + mtls: + type: mutualTLS + bearer: + type: http + scheme: bearer +security: + - bearer: [] diff --git a/packages/insomnia-inso/src/commands/fixtures/openapi-3.2-valid.yaml b/packages/insomnia-inso/src/commands/fixtures/openapi-3.2-valid.yaml new file mode 100644 index 0000000000..9f85d4a4fc --- /dev/null +++ b/packages/insomnia-inso/src/commands/fixtures/openapi-3.2-valid.yaml @@ -0,0 +1,265 @@ +openapi: 3.2.0 +$self: https://example.com/specs/pet-store-3.2.yaml +info: + title: Pet Store (OpenAPI 3.2 sample) + version: 1.0.0 + summary: Sample document exercising OpenAPI 3.2 features + description: Used to test Insomnia's lint support for OpenAPI 3.2. + contact: + email: support@insomnia.rest + license: + name: Apache 2.0 + identifier: Apache-2.0 +jsonSchemaDialect: https://spec.openapis.org/oas/3.1/dialect/base +servers: + - name: production + url: https://{env}.example.com/v1 + description: Main API server + variables: + env: + default: api + enum: [api, staging] +tags: + - name: pets + summary: Pets + description: Everything about pets + kind: nav + - name: pet-events + summary: Pet events + parent: pets + kind: nav +paths: + /pets: + get: + tags: [pets] + operationId: listPets + summary: List pets + description: List pets + parameters: + - name: limit + in: query + schema: + type: integer + maximum: 100 + responses: + '200': + description: A list of pets + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + examples: + sample: + summary: Two pets + value: + - id: 1 + name: Rex + type: dog + - id: 2 + name: Tom + type: cat + post: + tags: [pets] + operationId: createPet + summary: Create a pet + description: Create a pet + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + # New in 3.2: the QUERY method + query: + tags: [pets] + operationId: searchPets + summary: Search pets with a request body + description: Search pets with a request body + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + type: + $ref: '#/components/schemas/PetType' + responses: + '200': + description: Matching pets + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + # New in 3.2: custom / non-standard HTTP methods + additionalOperations: + PURGE: + tags: [pets] + operationId: purgePetCache + summary: Purge the pets cache + description: Purge the pets cache + responses: + '204': + description: Cache purged + /pets/{petId}: + parameters: + - name: petId + in: path + required: true + schema: + type: integer + get: + tags: [pets] + operationId: getPet + summary: Get a pet + description: Get a pet + responses: + '200': + description: The pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + '404': + description: Not found + content: + application/json: + schema: + $ref: '#/components/schemas/Animal' + /pets/{petId}/events: + parameters: + - name: petId + in: path + required: true + schema: + type: integer + get: + tags: [pet-events] + operationId: streamPetEvents + summary: Stream pet events (Server-Sent Events) + description: Stream pet events (Server-Sent Events) + security: + - oauth: [read:pets] + responses: + '200': + description: Event stream + content: + # New in 3.2: sequential media types described with itemSchema + text/event-stream: + itemSchema: + type: object + required: [data] + properties: + event: + type: string + data: + type: string + id: + type: string + /pets/lookup: + get: + tags: [pets] + operationId: lookupPets + summary: Lookup using a whole query string + description: Lookup using a whole query string + parameters: + # New in 3.2: `querystring` location + - name: filter + in: querystring + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + type: string + type: + $ref: '#/components/schemas/PetType' + responses: + '200': + description: Matching pets + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' +webhooks: + petAdopted: + post: + operationId: onPetAdopted + summary: Fired when a pet is adopted + description: Fired when a pet is adopted + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + responses: + '200': + description: Acknowledged +components: + schemas: + PetType: + type: string + enum: [dog, cat, bird] + Pet: + type: object + required: [id, name, type] + properties: + id: + type: integer + format: int64 + name: + type: string + type: + $ref: '#/components/schemas/PetType' + xml: + # New in 3.2: xml.nodeType replaces attribute/wrapped + nodeType: element + name: pet + Animal: + type: object + required: [kind] + properties: + kind: + type: string + discriminator: + propertyName: kind + mapping: + dog: '#/components/schemas/Pet' + # New in 3.2: fallback schema when no mapping matches + defaultMapping: '#/components/schemas/Pet' + securitySchemes: + oauth: + type: oauth2 + description: OAuth 2.0 with device flow + # New in 3.2: authorization server metadata + oauth2MetadataUrl: https://auth.example.com/.well-known/oauth-authorization-server + flows: + deviceAuthorization: + deviceAuthorizationUrl: https://auth.example.com/device + tokenUrl: https://auth.example.com/token + scopes: + read:pets: Read pets + legacyKey: + type: apiKey + in: header + name: X-Api-Key + # New in 3.2: mark a scheme as deprecated + deprecated: true +security: + - oauth: [read:pets] diff --git a/packages/insomnia-inso/src/commands/lint-specification.test.ts b/packages/insomnia-inso/src/commands/lint-specification.test.ts index 5097d22d23..29a05e9477 100644 --- a/packages/insomnia-inso/src/commands/lint-specification.test.ts +++ b/packages/insomnia-inso/src/commands/lint-specification.test.ts @@ -1,3 +1,4 @@ +import fs from 'node:fs'; import path from 'node:path'; import { describe, expect, it } from 'vitest'; @@ -100,4 +101,32 @@ tags: const result = await lintSpecification({ specContent: badSpec }); expect(result.isValid).toBe(false); }); + + // Exercises type arrays with null, const, examples arrays, $ref siblings, contentMediaType, mutualTLS, + // webhooks and license identifier. + it('should lint a valid OpenAPI 3.1 spec without any results', async () => { + const specContent = fs.readFileSync(path.join(__dirname, 'fixtures/openapi-3.1-valid.yaml'), 'utf8'); + const result = await lintSpecification({ specContent }); + expect(result.results).toEqual([]); + expect(result.isValid).toBe(true); + }); + + // Exercises $self, query, additionalOperations, in: querystring, itemSchema, tag parent/kind, + // xml.nodeType, discriminator.defaultMapping, oauth2MetadataUrl, device flow and deprecated schemes. + it('should lint a valid OpenAPI 3.2 spec without any results', async () => { + const specContent = fs.readFileSync(path.join(__dirname, 'fixtures/openapi-3.2-valid.yaml'), 'utf8'); + const result = await lintSpecification({ specContent }); + expect(result.results).toEqual([]); + expect(result.isValid).toBe(true); + }); + + it('should still report structural errors on an OpenAPI 3.2 spec', async () => { + const specContent = fs + .readFileSync(path.join(__dirname, 'fixtures/openapi-3.2-valid.yaml'), 'utf8') + .replace(' version: 1.0.0\n', '') + .replace(' query:\n', ' query:\n notAnOperationField: true\n'); + const result = await lintSpecification({ specContent }); + expect(result.isValid).toBe(false); + expect(result.results?.map(r => r.path.join('.'))).toEqual(['info', 'paths./pets.query']); + }); }); diff --git a/packages/insomnia/package.json b/packages/insomnia/package.json index e40ac9265a..efe47956ca 100644 --- a/packages/insomnia/package.json +++ b/packages/insomnia/package.json @@ -71,11 +71,11 @@ "@rjsf/validator-ajv8": "6.8.0", "@scalar/openapi-parser": "0.29.19", "@sentry/electron": "^7.17.0", - "@stoplight/spectral-core": "^1.22.0", - "@stoplight/spectral-formats": "^1.8.2", + "@stoplight/spectral-core": "^1.23.0", + "@stoplight/spectral-formats": "^1.9.0", "@stoplight/spectral-ref-resolver": "^1.0.5", "@stoplight/spectral-ruleset-bundler": "1.7.0", - "@stoplight/spectral-rulesets": "^1.22.1", + "@stoplight/spectral-rulesets": "^1.23.0", "@tailwindcss/typography": "^0.5.16", "@tanstack/react-query": "^5.101.0", "@tanstack/react-virtual": "3.13.12",