(spec: OpenApiSpec | null, url: string)
| 317 | } |
| 318 | |
| 319 | private validateSpec(spec: OpenApiSpec | null, url: string): void { |
| 320 | if (!spec || typeof spec !== 'object') { |
| 321 | throw new Error( |
| 322 | `Invalid OpenAPI spec from ${url}: expected a JSON object.` |
| 323 | ); |
| 324 | } |
| 325 | |
| 326 | if (typeof spec.openapi !== 'string' || !spec.openapi.startsWith('3.')) { |
| 327 | throw new Error( |
| 328 | `Invalid OpenAPI spec from ${url}: expected an OpenAPI 3.x document with an "openapi" field.` |
| 329 | ); |
| 330 | } |
| 331 | |
| 332 | if (!spec.paths || typeof spec.paths !== 'object') { |
| 333 | throw new Error( |
| 334 | `Invalid OpenAPI spec from ${url}: expected a "paths" object.` |
| 335 | ); |
| 336 | } |
| 337 | |
| 338 | for (const path of Object.keys(spec.paths)) { |
| 339 | if (!path.startsWith('/') || path.startsWith('//')) { |
| 340 | throw new Error( |
| 341 | `Invalid OpenAPI spec from ${url}: path "${path}" must be a relative API path.` |
| 342 | ); |
| 343 | } |
| 344 | } |
| 345 | } |
| 346 | |
| 347 | /** |
| 348 | * Check if cached spec is expired |
no test coverage detected