diff --git a/scripts/api-diff.js b/scripts/api-diff.js index 041da80..4bbc40c 100644 --- a/scripts/api-diff.js +++ b/scripts/api-diff.js @@ -170,6 +170,13 @@ function schemaReferencesComponent(schema, componentName, visitedRefs = new Set( if (schema.items && schemaReferencesComponent(schema.items, componentName, visitedRefs)) { return true; } + + // Check additionalProperties + if (schema.additionalProperties && typeof schema.additionalProperties === 'object') { + if (schemaReferencesComponent(schema.additionalProperties, componentName, visitedRefs)) { + return true; + } + } return false; } @@ -184,7 +191,10 @@ function findComponentUsage(details, componentName) { // Check direct parameter reference if (p.$ref && p.$ref.includes(`/parameters/${componentName}`)) return true; // Check schema reference if it exists - if (p.schema && p.schema.$ref && p.schema.$ref.includes(`/schemas/${componentName}`)) return true; + if (p.schema && schemaReferencesComponent(p.schema, componentName)) return true; + // Check examples + if (p.examples && Object.values(p.examples).some(e => + e.$ref && e.$ref.includes(`/examples/${componentName}`))) return true; return false; }); if (hasComponent) usage.push('parameters'); @@ -195,8 +205,12 @@ function findComponentUsage(details, componentName) { if (details.requestBody.$ref && details.requestBody.$ref.includes(componentName)) { usage.push('requestBody'); } else if (details.requestBody.content) { - const hasComponent = Object.values(details.requestBody.content).some(c => - c.schema && c.schema.$ref && c.schema.$ref.includes(`/schemas/${componentName}`)); + const hasComponent = Object.values(details.requestBody.content).some(c => { + if (c.schema && schemaReferencesComponent(c.schema, componentName)) return true; + if (c.examples && Object.values(c.examples).some(e => + e.$ref && e.$ref.includes(`/examples/${componentName}`))) return true; + return false; + }); if (hasComponent) usage.push('requestBody'); } } @@ -206,8 +220,16 @@ function findComponentUsage(details, componentName) { const hasComponent = Object.entries(details.responses).some(([code, response]) => { if (response.$ref && response.$ref.includes(`/responses/${componentName}`)) return true; if (response.content) { - return Object.values(response.content).some(c => - c.schema && c.schema.$ref && c.schema.$ref.includes(`/schemas/${componentName}`)); + return Object.values(response.content).some(c => { + if (c.schema && schemaReferencesComponent(c.schema, componentName)) return true; + if (c.examples && Object.values(c.examples).some(e => + e.$ref && e.$ref.includes(`/examples/${componentName}`))) return true; + return false; + }); + } + if (response.headers) { + return Object.values(response.headers).some(h => + schemaReferencesComponent(h.schema, componentName)); } return false; }); diff --git a/tests/fixtures/component-section/current.json b/tests/fixtures/component-section/current.json new file mode 100644 index 0000000..2975ee7 --- /dev/null +++ b/tests/fixtures/component-section/current.json @@ -0,0 +1,81 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Test API", + "description": "A sample API for testing" + }, + "servers": [ + { + "url": "https://api.example.com/v1" + } + ], + "paths": { + "/user/{id}": { + "get": { + "summary": "Get user by ID", + "parameters": [ + { + "$ref": "#/components/parameters/StringUserId" + }, + { + "$ref": "#/components/parameters/NumberUserId" + } + ], + "responses": { + "200": { + "description": "User found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "userCreated": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + } + } + } + } + } + }, + "components": { + "parameters": { + "StringUserId": { + "type": "string", + "description": "The unique identifier for a user", + "example": 1234 + }, + "NumberUserId": { + "type": "number", + "description": "The unique identifier for a user", + "example": 1234 + } + }, + "schemas": { + "User": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "email": { + "$ref": "#/components/schemas/Email" + } + } + }, + "Email": { + "type": "string", + "format": "email" + } + } + } +} diff --git a/tests/fixtures/component-section/expected.md b/tests/fixtures/component-section/expected.md new file mode 100644 index 0000000..b801a73 --- /dev/null +++ b/tests/fixtures/component-section/expected.md @@ -0,0 +1,4 @@ +## Modified +- [GET] `/user/{id}` + - `NumberUserId` modified in parameters + - `User` modified in responses diff --git a/tests/fixtures/component-section/previous.json b/tests/fixtures/component-section/previous.json new file mode 100644 index 0000000..59634d7 --- /dev/null +++ b/tests/fixtures/component-section/previous.json @@ -0,0 +1,80 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Test API", + "description": "A sample API for testing" + }, + "servers": [ + { + "url": "https://api.example.com/v1" + } + ], + "paths": { + "/user/{id}": { + "get": { + "summary": "Get user by ID", + "parameters": [ + { + "$ref": "#/components/parameters/StringUserId" + }, + { + "$ref": "#/components/parameters/NumberUserId" + } + ], + "responses": { + "200": { + "description": "User found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "userCreated": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + } + } + } + } + } + }, + "components": { + "parameters": { + "StringUserId": { + "type": "string", + "description": "The unique identifier for a user", + "example": 1234 + }, + "NumberUserId": { + "type": "number", + "description": "The unique identifier for a user", + "example": 12345 + } + }, + "schemas": { + "User": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "email": { + "$ref": "#/components/schemas/Email" + } + } + }, + "Email": { + "type": "string" + } + } + } +}