mcp-openapi-schema-explorer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/.devcontainer/devcontainer.json +24 -0
  2. package/.github/dependabot.yml +13 -0
  3. package/.github/workflows/ci.yml +111 -0
  4. package/.husky/pre-commit +6 -0
  5. package/.prettierignore +3 -0
  6. package/.prettierrc.json +12 -0
  7. package/.releaserc.json +23 -0
  8. package/CHANGELOG.md +32 -0
  9. package/CONTRIBUTING.md +67 -0
  10. package/Dockerfile +3 -0
  11. package/LICENSE +21 -0
  12. package/README.md +127 -0
  13. package/dist/src/config.d.ts +15 -0
  14. package/dist/src/config.js +19 -0
  15. package/dist/src/config.js.map +1 -0
  16. package/dist/src/handlers/component-detail-handler.d.ts +14 -0
  17. package/dist/src/handlers/component-detail-handler.js +87 -0
  18. package/dist/src/handlers/component-detail-handler.js.map +1 -0
  19. package/dist/src/handlers/component-map-handler.d.ts +14 -0
  20. package/dist/src/handlers/component-map-handler.js +63 -0
  21. package/dist/src/handlers/component-map-handler.js.map +1 -0
  22. package/dist/src/handlers/handler-utils.d.ts +69 -0
  23. package/dist/src/handlers/handler-utils.js +180 -0
  24. package/dist/src/handlers/handler-utils.js.map +1 -0
  25. package/dist/src/handlers/operation-handler.d.ts +14 -0
  26. package/dist/src/handlers/operation-handler.js +86 -0
  27. package/dist/src/handlers/operation-handler.js.map +1 -0
  28. package/dist/src/handlers/path-item-handler.d.ts +14 -0
  29. package/dist/src/handlers/path-item-handler.js +66 -0
  30. package/dist/src/handlers/path-item-handler.js.map +1 -0
  31. package/dist/src/handlers/top-level-field-handler.d.ts +14 -0
  32. package/dist/src/handlers/top-level-field-handler.js +72 -0
  33. package/dist/src/handlers/top-level-field-handler.js.map +1 -0
  34. package/dist/src/index.d.ts +2 -0
  35. package/dist/src/index.js +177 -0
  36. package/dist/src/index.js.map +1 -0
  37. package/dist/src/rendering/components.d.ts +67 -0
  38. package/dist/src/rendering/components.js +177 -0
  39. package/dist/src/rendering/components.js.map +1 -0
  40. package/dist/src/rendering/document.d.ts +36 -0
  41. package/dist/src/rendering/document.js +147 -0
  42. package/dist/src/rendering/document.js.map +1 -0
  43. package/dist/src/rendering/path-item.d.ts +45 -0
  44. package/dist/src/rendering/path-item.js +141 -0
  45. package/dist/src/rendering/path-item.js.map +1 -0
  46. package/dist/src/rendering/paths.d.ts +26 -0
  47. package/dist/src/rendering/paths.js +78 -0
  48. package/dist/src/rendering/paths.js.map +1 -0
  49. package/dist/src/rendering/types.d.ts +50 -0
  50. package/dist/src/rendering/types.js +12 -0
  51. package/dist/src/rendering/types.js.map +1 -0
  52. package/dist/src/rendering/utils.d.ts +31 -0
  53. package/dist/src/rendering/utils.js +79 -0
  54. package/dist/src/rendering/utils.js.map +1 -0
  55. package/dist/src/services/formatters.d.ts +36 -0
  56. package/dist/src/services/formatters.js +52 -0
  57. package/dist/src/services/formatters.js.map +1 -0
  58. package/dist/src/services/reference-transform.d.ts +27 -0
  59. package/dist/src/services/reference-transform.js +75 -0
  60. package/dist/src/services/reference-transform.js.map +1 -0
  61. package/dist/src/services/spec-loader.d.ts +27 -0
  62. package/dist/src/services/spec-loader.js +77 -0
  63. package/dist/src/services/spec-loader.js.map +1 -0
  64. package/dist/src/types.d.ts +11 -0
  65. package/dist/src/types.js +2 -0
  66. package/dist/src/types.js.map +1 -0
  67. package/dist/src/utils/uri-builder.d.ts +81 -0
  68. package/dist/src/utils/uri-builder.js +121 -0
  69. package/dist/src/utils/uri-builder.js.map +1 -0
  70. package/dist/src/version.d.ts +1 -0
  71. package/dist/src/version.js +4 -0
  72. package/dist/src/version.js.map +1 -0
  73. package/eslint.config.js +88 -0
  74. package/jest.config.js +32 -0
  75. package/justfile +66 -0
  76. package/memory-bank/activeContext.md +139 -0
  77. package/memory-bank/productContext.md +39 -0
  78. package/memory-bank/progress.md +141 -0
  79. package/memory-bank/projectbrief.md +50 -0
  80. package/memory-bank/systemPatterns.md +224 -0
  81. package/memory-bank/techContext.md +131 -0
  82. package/package.json +76 -0
  83. package/scripts/generate-version.js +49 -0
  84. package/src/config.ts +33 -0
  85. package/src/handlers/component-detail-handler.ts +121 -0
  86. package/src/handlers/component-map-handler.ts +92 -0
  87. package/src/handlers/handler-utils.ts +230 -0
  88. package/src/handlers/operation-handler.ts +114 -0
  89. package/src/handlers/path-item-handler.ts +88 -0
  90. package/src/handlers/top-level-field-handler.ts +92 -0
  91. package/src/index.ts +222 -0
  92. package/src/rendering/components.ts +228 -0
  93. package/src/rendering/document.ts +167 -0
  94. package/src/rendering/path-item.ts +157 -0
  95. package/src/rendering/paths.ts +87 -0
  96. package/src/rendering/types.ts +63 -0
  97. package/src/rendering/utils.ts +107 -0
  98. package/src/services/formatters.ts +71 -0
  99. package/src/services/reference-transform.ts +105 -0
  100. package/src/services/spec-loader.ts +88 -0
  101. package/src/types.ts +17 -0
  102. package/src/utils/uri-builder.ts +134 -0
  103. package/src/version.ts +4 -0
  104. package/test/__tests__/e2e/format.test.ts +224 -0
  105. package/test/__tests__/e2e/resources.test.ts +369 -0
  106. package/test/__tests__/e2e/spec-loading.test.ts +172 -0
  107. package/test/__tests__/unit/config.test.ts +39 -0
  108. package/test/__tests__/unit/handlers/component-detail-handler.test.ts +241 -0
  109. package/test/__tests__/unit/handlers/component-map-handler.test.ts +187 -0
  110. package/test/__tests__/unit/handlers/handler-utils.test.ts +255 -0
  111. package/test/__tests__/unit/handlers/operation-handler.test.ts +202 -0
  112. package/test/__tests__/unit/handlers/path-item-handler.test.ts +153 -0
  113. package/test/__tests__/unit/handlers/top-level-field-handler.test.ts +182 -0
  114. package/test/__tests__/unit/rendering/components.test.ts +269 -0
  115. package/test/__tests__/unit/rendering/document.test.ts +172 -0
  116. package/test/__tests__/unit/rendering/path-item.test.ts +197 -0
  117. package/test/__tests__/unit/rendering/paths.test.ts +115 -0
  118. package/test/__tests__/unit/services/formatters.test.ts +109 -0
  119. package/test/__tests__/unit/services/reference-transform.test.ts +320 -0
  120. package/test/__tests__/unit/services/spec-loader.test.ts +214 -0
  121. package/test/__tests__/unit/utils/uri-builder.test.ts +103 -0
  122. package/test/fixtures/complex-endpoint.json +146 -0
  123. package/test/fixtures/empty-api.json +8 -0
  124. package/test/fixtures/multi-component-types.json +55 -0
  125. package/test/fixtures/paths-test.json +61 -0
  126. package/test/fixtures/sample-api.json +68 -0
  127. package/test/fixtures/sample-v2-api.json +39 -0
  128. package/test/setup.ts +32 -0
  129. package/test/utils/console-helpers.ts +48 -0
  130. package/test/utils/mcp-test-helpers.ts +66 -0
  131. package/test/utils/test-types.ts +54 -0
  132. package/tsconfig.json +25 -0
  133. package/tsconfig.test.json +5 -0
@@ -0,0 +1,214 @@
1
+ import { SpecLoaderService } from '../../../../src/services/spec-loader.js';
2
+ import { ReferenceTransformService } from '../../../../src/services/reference-transform.js';
3
+ import { OpenAPIV3 } from 'openapi-types';
4
+
5
+ // Define mock implementations first
6
+ const mockConvertUrlImplementation = jest.fn();
7
+ const mockConvertFileImplementation = jest.fn();
8
+
9
+ // Mock the module, referencing the defined implementations
10
+ // IMPORTANT: The factory function for jest.mock runs BEFORE top-level variable assignments in the module scope.
11
+ // We need to access the mocks indirectly.
12
+ interface Swagger2OpenapiResult {
13
+ openapi: OpenAPIV3.Document;
14
+ options: unknown; // Use unknown for options as we don't have precise types here
15
+ }
16
+
17
+ jest.mock('swagger2openapi', () => {
18
+ // Return an object where the properties are functions that call our mocks
19
+ return {
20
+ convertUrl: (url: string, options: unknown): Promise<Swagger2OpenapiResult> =>
21
+ mockConvertUrlImplementation(url, options) as Promise<Swagger2OpenapiResult>, // Cast return type
22
+ convertFile: (filename: string, options: unknown): Promise<Swagger2OpenapiResult> =>
23
+ mockConvertFileImplementation(filename, options) as Promise<Swagger2OpenapiResult>, // Cast return type
24
+ };
25
+ });
26
+
27
+ describe('SpecLoaderService', () => {
28
+ const mockV3Spec: OpenAPIV3.Document = {
29
+ openapi: '3.0.0',
30
+ info: {
31
+ title: 'Test V3 API',
32
+ version: '1.0.0',
33
+ },
34
+ paths: {},
35
+ };
36
+
37
+ // Simulate the structure returned by swagger2openapi
38
+ const mockS2OResult = {
39
+ openapi: mockV3Spec,
40
+ options: {}, // Add other properties if needed by tests
41
+ };
42
+
43
+ let referenceTransform: ReferenceTransformService;
44
+
45
+ beforeEach(() => {
46
+ // Reset the mock implementations
47
+ mockConvertUrlImplementation.mockReset();
48
+ mockConvertFileImplementation.mockReset();
49
+ referenceTransform = new ReferenceTransformService();
50
+ // Mock the transformDocument method for simplicity in these tests
51
+ jest.spyOn(referenceTransform, 'transformDocument').mockImplementation(spec => spec);
52
+ });
53
+
54
+ describe('loadSpec', () => {
55
+ it('loads local v3 spec using convertFile', async () => {
56
+ mockConvertFileImplementation.mockResolvedValue(mockS2OResult);
57
+ const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
58
+ const spec = await loader.loadSpec();
59
+
60
+ expect(mockConvertFileImplementation).toHaveBeenCalledWith(
61
+ '/path/to/spec.json',
62
+ expect.any(Object)
63
+ );
64
+ expect(mockConvertUrlImplementation).not.toHaveBeenCalled();
65
+ expect(spec).toEqual(mockV3Spec);
66
+ });
67
+
68
+ it('loads remote v3 spec using convertUrl', async () => {
69
+ mockConvertUrlImplementation.mockResolvedValue(mockS2OResult);
70
+ const loader = new SpecLoaderService('http://example.com/spec.json', referenceTransform);
71
+ const spec = await loader.loadSpec();
72
+
73
+ expect(mockConvertUrlImplementation).toHaveBeenCalledWith(
74
+ 'http://example.com/spec.json',
75
+ expect.any(Object)
76
+ );
77
+ expect(mockConvertFileImplementation).not.toHaveBeenCalled();
78
+ expect(spec).toEqual(mockV3Spec);
79
+ });
80
+
81
+ it('loads and converts local v2 spec using convertFile', async () => {
82
+ // Assume convertFile handles v2 internally and returns v3
83
+ mockConvertFileImplementation.mockResolvedValue(mockS2OResult);
84
+ const loader = new SpecLoaderService('/path/to/v2spec.json', referenceTransform);
85
+ const spec = await loader.loadSpec();
86
+
87
+ expect(mockConvertFileImplementation).toHaveBeenCalledWith(
88
+ '/path/to/v2spec.json',
89
+ expect.any(Object)
90
+ );
91
+ expect(mockConvertUrlImplementation).not.toHaveBeenCalled();
92
+ expect(spec).toEqual(mockV3Spec); // Should be the converted v3 spec
93
+ });
94
+
95
+ it('loads and converts remote v2 spec using convertUrl', async () => {
96
+ // Assume convertUrl handles v2 internally and returns v3
97
+ mockConvertUrlImplementation.mockResolvedValue(mockS2OResult);
98
+ const loader = new SpecLoaderService('https://example.com/v2spec.yaml', referenceTransform);
99
+ const spec = await loader.loadSpec();
100
+
101
+ expect(mockConvertUrlImplementation).toHaveBeenCalledWith(
102
+ 'https://example.com/v2spec.yaml',
103
+ expect.any(Object)
104
+ );
105
+ expect(mockConvertFileImplementation).not.toHaveBeenCalled();
106
+ expect(spec).toEqual(mockV3Spec); // Should be the converted v3 spec
107
+ });
108
+
109
+ it('throws error if convertFile fails', async () => {
110
+ const loadError = new Error('File not found');
111
+ mockConvertFileImplementation.mockRejectedValue(loadError);
112
+ const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
113
+
114
+ await expect(loader.loadSpec()).rejects.toThrow(
115
+ 'Failed to load/convert OpenAPI spec from /path/to/spec.json: File not found'
116
+ );
117
+ });
118
+
119
+ it('throws error if convertUrl fails', async () => {
120
+ const loadError = new Error('Network error');
121
+ mockConvertUrlImplementation.mockRejectedValue(loadError);
122
+ const loader = new SpecLoaderService('http://example.com/spec.json', referenceTransform);
123
+
124
+ await expect(loader.loadSpec()).rejects.toThrow(
125
+ 'Failed to load/convert OpenAPI spec from http://example.com/spec.json: Network error'
126
+ );
127
+ });
128
+
129
+ it('throws error if result object is invalid', async () => {
130
+ mockConvertFileImplementation.mockResolvedValue({ options: {} }); // Missing openapi property
131
+ const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
132
+
133
+ await expect(loader.loadSpec()).rejects.toThrow(
134
+ 'Failed to load/convert OpenAPI spec from /path/to/spec.json: Conversion or parsing failed to produce an OpenAPI document.'
135
+ );
136
+ });
137
+ });
138
+
139
+ describe('getSpec', () => {
140
+ it('returns loaded spec after loadSpec called', async () => {
141
+ mockConvertFileImplementation.mockResolvedValue(mockS2OResult);
142
+ const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
143
+ await loader.loadSpec(); // Load first
144
+ const spec = await loader.getSpec();
145
+
146
+ expect(spec).toEqual(mockV3Spec);
147
+ // Ensure loadSpec was only called once implicitly by the first await
148
+ expect(mockConvertFileImplementation).toHaveBeenCalledTimes(1);
149
+ });
150
+
151
+ it('loads spec via convertFile if not already loaded', async () => {
152
+ mockConvertFileImplementation.mockResolvedValue(mockS2OResult);
153
+ const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
154
+ const spec = await loader.getSpec(); // Should trigger loadSpec
155
+
156
+ expect(mockConvertFileImplementation).toHaveBeenCalledWith(
157
+ '/path/to/spec.json',
158
+ expect.any(Object)
159
+ );
160
+ expect(spec).toEqual(mockV3Spec);
161
+ });
162
+
163
+ it('loads spec via convertUrl if not already loaded', async () => {
164
+ mockConvertUrlImplementation.mockResolvedValue(mockS2OResult);
165
+ const loader = new SpecLoaderService('http://example.com/spec.json', referenceTransform);
166
+ const spec = await loader.getSpec(); // Should trigger loadSpec
167
+
168
+ expect(mockConvertUrlImplementation).toHaveBeenCalledWith(
169
+ 'http://example.com/spec.json',
170
+ expect.any(Object)
171
+ );
172
+ expect(spec).toEqual(mockV3Spec);
173
+ });
174
+ });
175
+
176
+ describe('getTransformedSpec', () => {
177
+ // Mock the transformer to return a distinctly modified object
178
+ const mockTransformedSpec = {
179
+ ...mockV3Spec,
180
+ info: { ...mockV3Spec.info, title: 'Transformed API' },
181
+ };
182
+
183
+ beforeEach(() => {
184
+ jest
185
+ .spyOn(referenceTransform, 'transformDocument')
186
+ .mockImplementation(() => mockTransformedSpec);
187
+ });
188
+
189
+ it('returns transformed spec after loading', async () => {
190
+ mockConvertFileImplementation.mockResolvedValue(mockS2OResult);
191
+ const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
192
+ const spec = await loader.getTransformedSpec({ resourceType: 'endpoint', format: 'openapi' }); // Should load then transform
193
+
194
+ expect(mockConvertFileImplementation).toHaveBeenCalledTimes(1); // Ensure loading happened
195
+ const transformSpy = jest.spyOn(referenceTransform, 'transformDocument');
196
+ expect(transformSpy).toHaveBeenCalledWith(
197
+ mockV3Spec,
198
+ expect.objectContaining({ resourceType: 'endpoint', format: 'openapi' })
199
+ );
200
+ expect(spec).toEqual(mockTransformedSpec);
201
+ });
202
+
203
+ it('loads spec if not loaded before transforming', async () => {
204
+ mockConvertFileImplementation.mockResolvedValue(mockS2OResult);
205
+ const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
206
+ await loader.getTransformedSpec({ resourceType: 'endpoint', format: 'openapi' }); // Trigger load
207
+
208
+ expect(mockConvertFileImplementation).toHaveBeenCalledWith(
209
+ '/path/to/spec.json',
210
+ expect.any(Object)
211
+ );
212
+ });
213
+ });
214
+ });
@@ -0,0 +1,103 @@
1
+ import {
2
+ buildComponentDetailUri,
3
+ buildComponentMapUri,
4
+ buildOperationUri,
5
+ buildPathItemUri,
6
+ buildTopLevelFieldUri,
7
+ buildComponentDetailUriSuffix,
8
+ buildComponentMapUriSuffix,
9
+ buildOperationUriSuffix,
10
+ buildPathItemUriSuffix,
11
+ buildTopLevelFieldUriSuffix,
12
+ } from '../../../../src/utils/uri-builder';
13
+
14
+ describe('URI Builder Utilities', () => {
15
+ // --- Full URI Builders ---
16
+
17
+ test('buildComponentDetailUri builds correct URI', () => {
18
+ expect(buildComponentDetailUri('schemas', 'MySchema')).toBe(
19
+ 'openapi://components/schemas/MySchema'
20
+ );
21
+ expect(buildComponentDetailUri('responses', 'NotFound')).toBe(
22
+ 'openapi://components/responses/NotFound'
23
+ );
24
+ // Test with characters that might need encoding if rules change (but currently don't)
25
+ expect(buildComponentDetailUri('parameters', 'user-id')).toBe(
26
+ 'openapi://components/parameters/user-id'
27
+ );
28
+ });
29
+
30
+ test('buildComponentMapUri builds correct URI', () => {
31
+ expect(buildComponentMapUri('schemas')).toBe('openapi://components/schemas');
32
+ expect(buildComponentMapUri('parameters')).toBe('openapi://components/parameters');
33
+ });
34
+
35
+ test('buildOperationUri builds correct URI and encodes path (no leading slash)', () => {
36
+ expect(buildOperationUri('/users', 'get')).toBe('openapi://paths/users/get'); // No leading slash encoded
37
+ expect(buildOperationUri('/users/{userId}', 'post')).toBe(
38
+ 'openapi://paths/users%2F%7BuserId%7D/post' // Path encoded, no leading %2F
39
+ );
40
+ expect(buildOperationUri('/pets/{petId}/uploadImage', 'post')).toBe(
41
+ 'openapi://paths/pets%2F%7BpetId%7D%2FuploadImage/post' // Path encoded, no leading %2F
42
+ );
43
+ expect(buildOperationUri('users', 'get')).toBe('openapi://paths/users/get'); // Handles no leading slash input
44
+ expect(buildOperationUri('users/{userId}', 'post')).toBe(
45
+ 'openapi://paths/users%2F%7BuserId%7D/post' // Handles no leading slash input
46
+ );
47
+ expect(buildOperationUri('/users', 'GET')).toBe('openapi://paths/users/get'); // Method lowercased
48
+ });
49
+
50
+ test('buildPathItemUri builds correct URI and encodes path (no leading slash)', () => {
51
+ expect(buildPathItemUri('/users')).toBe('openapi://paths/users'); // No leading slash encoded
52
+ expect(buildPathItemUri('/users/{userId}')).toBe('openapi://paths/users%2F%7BuserId%7D'); // Path encoded, no leading %2F
53
+ expect(buildPathItemUri('/pets/{petId}/uploadImage')).toBe(
54
+ 'openapi://paths/pets%2F%7BpetId%7D%2FuploadImage' // Path encoded, no leading %2F
55
+ );
56
+ expect(buildPathItemUri('users')).toBe('openapi://paths/users'); // Handles no leading slash input
57
+ expect(buildPathItemUri('users/{userId}')).toBe('openapi://paths/users%2F%7BuserId%7D'); // Handles no leading slash input
58
+ });
59
+
60
+ test('buildTopLevelFieldUri builds correct URI', () => {
61
+ expect(buildTopLevelFieldUri('info')).toBe('openapi://info');
62
+ expect(buildTopLevelFieldUri('paths')).toBe('openapi://paths');
63
+ expect(buildTopLevelFieldUri('components')).toBe('openapi://components');
64
+ });
65
+
66
+ // --- URI Suffix Builders ---
67
+
68
+ test('buildComponentDetailUriSuffix builds correct suffix', () => {
69
+ expect(buildComponentDetailUriSuffix('schemas', 'MySchema')).toBe(
70
+ 'components/schemas/MySchema'
71
+ );
72
+ expect(buildComponentDetailUriSuffix('responses', 'NotFound')).toBe(
73
+ 'components/responses/NotFound'
74
+ );
75
+ });
76
+
77
+ test('buildComponentMapUriSuffix builds correct suffix', () => {
78
+ expect(buildComponentMapUriSuffix('schemas')).toBe('components/schemas');
79
+ expect(buildComponentMapUriSuffix('parameters')).toBe('components/parameters');
80
+ });
81
+
82
+ test('buildOperationUriSuffix builds correct suffix and encodes path (no leading slash)', () => {
83
+ expect(buildOperationUriSuffix('/users', 'get')).toBe('paths/users/get'); // No leading slash encoded
84
+ expect(buildOperationUriSuffix('/users/{userId}', 'post')).toBe(
85
+ 'paths/users%2F%7BuserId%7D/post' // Path encoded, no leading %2F
86
+ );
87
+ expect(buildOperationUriSuffix('users/{userId}', 'post')).toBe(
88
+ 'paths/users%2F%7BuserId%7D/post' // Handles no leading slash input
89
+ );
90
+ expect(buildOperationUriSuffix('/users', 'GET')).toBe('paths/users/get'); // Method lowercased
91
+ });
92
+
93
+ test('buildPathItemUriSuffix builds correct suffix and encodes path (no leading slash)', () => {
94
+ expect(buildPathItemUriSuffix('/users')).toBe('paths/users'); // No leading slash encoded
95
+ expect(buildPathItemUriSuffix('/users/{userId}')).toBe('paths/users%2F%7BuserId%7D'); // Path encoded, no leading %2F
96
+ expect(buildPathItemUriSuffix('users/{userId}')).toBe('paths/users%2F%7BuserId%7D'); // Handles no leading slash input
97
+ });
98
+
99
+ test('buildTopLevelFieldUriSuffix builds correct suffix', () => {
100
+ expect(buildTopLevelFieldUriSuffix('info')).toBe('info');
101
+ expect(buildTopLevelFieldUriSuffix('paths')).toBe('paths');
102
+ });
103
+ });
@@ -0,0 +1,146 @@
1
+ {
2
+ "openapi": "3.0.3",
3
+ "info": {
4
+ "title": "Complex Endpoint Test API",
5
+ "version": "1.0.0"
6
+ },
7
+ "paths": {
8
+ "/api/v1/organizations/{orgId}/projects/{projectId}/tasks": {
9
+ "get": {
10
+ "operationId": "getProjectTasks",
11
+ "summary": "Get Tasks",
12
+ "description": "Retrieve a list of tasks for a specific project.",
13
+ "parameters": [
14
+ {
15
+ "name": "orgId",
16
+ "in": "path",
17
+ "required": true,
18
+ "schema": { "type": "string" }
19
+ },
20
+ {
21
+ "name": "projectId",
22
+ "in": "path",
23
+ "required": true,
24
+ "schema": { "type": "string" }
25
+ },
26
+ {
27
+ "name": "status",
28
+ "in": "query",
29
+ "schema": {
30
+ "type": "string",
31
+ "enum": ["active", "completed"]
32
+ }
33
+ },
34
+ {
35
+ "name": "sort",
36
+ "in": "query",
37
+ "schema": {
38
+ "type": "string",
39
+ "enum": ["created", "updated", "priority"]
40
+ }
41
+ }
42
+ ],
43
+ "responses": {
44
+ "200": {
45
+ "description": "List of tasks",
46
+ "content": {
47
+ "application/json": {
48
+ "schema": {
49
+ "$ref": "#/components/schemas/TaskList"
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
55
+ },
56
+ "post": {
57
+ "operationId": "createProjectTask",
58
+ "summary": "Create Task",
59
+ "description": "Create a new task within a project.",
60
+ "requestBody": {
61
+ "required": true,
62
+ "content": {
63
+ "application/json": {
64
+ "schema": {
65
+ "$ref": "#/components/schemas/CreateTaskRequest"
66
+ }
67
+ }
68
+ }
69
+ },
70
+ "responses": {
71
+ "201": {
72
+ "description": "Task created",
73
+ "content": {
74
+ "application/json": {
75
+ "schema": {
76
+ "$ref": "#/components/schemas/Task"
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ },
85
+ "components": {
86
+ "schemas": {
87
+ "Task": {
88
+ "type": "object",
89
+ "required": ["id", "title", "status"],
90
+ "properties": {
91
+ "id": {
92
+ "type": "string",
93
+ "format": "uuid"
94
+ },
95
+ "title": {
96
+ "type": "string"
97
+ },
98
+ "status": {
99
+ "type": "string",
100
+ "enum": ["active", "completed"]
101
+ },
102
+ "priority": {
103
+ "type": "integer",
104
+ "minimum": 1,
105
+ "maximum": 5
106
+ }
107
+ }
108
+ },
109
+ "TaskList": {
110
+ "type": "object",
111
+ "required": ["items"],
112
+ "properties": {
113
+ "items": {
114
+ "type": "array",
115
+ "items": {
116
+ "$ref": "#/components/schemas/Task"
117
+ }
118
+ },
119
+ "totalCount": {
120
+ "type": "integer"
121
+ }
122
+ }
123
+ },
124
+ "CreateTaskRequest": {
125
+ "type": "object",
126
+ "required": ["title"],
127
+ "properties": {
128
+ "title": {
129
+ "type": "string"
130
+ },
131
+ "status": {
132
+ "type": "string",
133
+ "enum": ["active", "completed"],
134
+ "default": "active"
135
+ },
136
+ "priority": {
137
+ "type": "integer",
138
+ "minimum": 1,
139
+ "maximum": 5,
140
+ "default": 3
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "openapi": "3.0.0",
3
+ "info": {
4
+ "title": "Empty API",
5
+ "version": "1.0.0"
6
+ },
7
+ "paths": {}
8
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "openapi": "3.0.0",
3
+ "info": {
4
+ "title": "Multi Component Type Test API",
5
+ "version": "1.0.0"
6
+ },
7
+ "paths": {
8
+ "/ping": {
9
+ "get": {
10
+ "summary": "Ping",
11
+ "operationId": "ping",
12
+ "responses": {
13
+ "200": {
14
+ "description": "OK",
15
+ "content": {
16
+ "application/json": {
17
+ "schema": {
18
+ "$ref": "#/components/schemas/Pong"
19
+ }
20
+ }
21
+ }
22
+ }
23
+ },
24
+ "parameters": [
25
+ {
26
+ "$ref": "#/components/parameters/TraceId"
27
+ }
28
+ ]
29
+ }
30
+ }
31
+ },
32
+ "components": {
33
+ "schemas": {
34
+ "Pong": {
35
+ "type": "object",
36
+ "properties": {
37
+ "message": {
38
+ "type": "string"
39
+ }
40
+ }
41
+ }
42
+ },
43
+ "parameters": {
44
+ "TraceId": {
45
+ "name": "X-Trace-ID",
46
+ "in": "header",
47
+ "required": false,
48
+ "schema": {
49
+ "type": "string",
50
+ "format": "uuid"
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "openapi": "3.0.3",
3
+ "info": {
4
+ "title": "Path Testing API",
5
+ "version": "1.0.0"
6
+ },
7
+ "paths": {
8
+ "/project/tasks/{taskId}": {
9
+ "get": {
10
+ "summary": "Get task details",
11
+ "parameters": [
12
+ {
13
+ "name": "taskId",
14
+ "in": "path",
15
+ "required": true,
16
+ "schema": { "type": "string" }
17
+ }
18
+ ],
19
+ "responses": {
20
+ "200": {
21
+ "description": "Task details"
22
+ }
23
+ }
24
+ }
25
+ },
26
+ "/article/{articleId}/comment/{commentId}": {
27
+ "get": {
28
+ "summary": "Get comment on article",
29
+ "parameters": [
30
+ {
31
+ "name": "articleId",
32
+ "in": "path",
33
+ "required": true,
34
+ "schema": { "type": "string" }
35
+ },
36
+ {
37
+ "name": "commentId",
38
+ "in": "path",
39
+ "required": true,
40
+ "schema": { "type": "string" }
41
+ }
42
+ ],
43
+ "responses": {
44
+ "200": {
45
+ "description": "Comment details"
46
+ }
47
+ }
48
+ }
49
+ },
50
+ "/sub/sub/sub/sub/folded/entrypoint": {
51
+ "post": {
52
+ "summary": "Deeply nested endpoint",
53
+ "responses": {
54
+ "201": {
55
+ "description": "Created"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,68 @@
1
+ {
2
+ "openapi": "3.0.3",
3
+ "info": {
4
+ "title": "Sample API",
5
+ "version": "1.0.0"
6
+ },
7
+ "paths": {
8
+ "/users": {
9
+ "get": {
10
+ "summary": "List users",
11
+ "operationId": "listUsers",
12
+ "responses": {
13
+ "200": {
14
+ "description": "List of users",
15
+ "content": {
16
+ "application/json": {
17
+ "schema": {
18
+ "$ref": "#/components/schemas/UserList"
19
+ }
20
+ }
21
+ }
22
+ }
23
+ }
24
+ }
25
+ }
26
+ },
27
+ "components": {
28
+ "schemas": {
29
+ "User": {
30
+ "type": "object",
31
+ "required": ["id", "email"],
32
+ "properties": {
33
+ "id": {
34
+ "type": "integer",
35
+ "format": "int64"
36
+ },
37
+ "email": {
38
+ "type": "string",
39
+ "format": "email"
40
+ },
41
+ "name": {
42
+ "type": "string"
43
+ },
44
+ "status": {
45
+ "type": "string",
46
+ "enum": ["active", "inactive"]
47
+ }
48
+ }
49
+ },
50
+ "UserList": {
51
+ "type": "object",
52
+ "required": ["users"],
53
+ "properties": {
54
+ "users": {
55
+ "type": "array",
56
+ "items": {
57
+ "$ref": "#/components/schemas/User"
58
+ }
59
+ },
60
+ "total": {
61
+ "type": "integer",
62
+ "format": "int32"
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }