77 * Handle SOAP/WSDL import via ?import=true&format=wsdl-link query parameter.
88 */
99
10- import type { IApimClient } from '../clients/iapim-client.js' ;
10+ import type { IApimClient , ApiSpecDialect } from '../clients/iapim-client.js' ;
1111import type { IArtifactStore } from '../clients/iartifact-store.js' ;
1212import type { ApimServiceContext , ResourceDescriptor } from '../models/types.js' ;
1313import type { PublishConfig } from '../models/config.js' ;
@@ -105,8 +105,11 @@ export async function publishApi(
105105
106106/**
107107 * Maps spec file format to APIM ContentFormat for inline import.
108+ * @param specDialect - The spec dialect of a JSON spec. Swagger 2.0 ('swagger2')
109+ * must be imported via `swagger-json` so its native format (and schema/operation
110+ * metadata) is preserved; OpenAPI 3.x ('openapi3') uses `openapi+json`.
108111 */
109- function getImportFormat ( specFormat : string , _apiType ?: string ) : string | undefined {
112+ function getImportFormat ( specFormat : string , _apiType ?: string , specDialect ?: ApiSpecDialect ) : string | undefined {
110113 const apiType = _apiType ?. toLowerCase ( ) ;
111114 if ( apiType === 'a2a' ) {
112115 return undefined ;
@@ -116,9 +119,10 @@ function getImportFormat(specFormat: string, _apiType?: string): string | undefi
116119 case 'yaml' :
117120 return 'openapi' ;
118121 case 'json' :
119- // Swagger 2.0 uses swagger-json, OpenAPI 3.x uses openapi+json.
120- // Default to openapi+json as the more modern format — APIM accepts both.
121- return 'openapi+json' ;
122+ // Match the source dialect: Swagger 2.0 uses swagger-json, OpenAPI 3.x uses
123+ // openapi+json. APIM accepts both, but importing a Swagger 2.0 document as
124+ // openapi+json would convert it to OpenAPI 3.0 and lose fidelity.
125+ return specDialect === 'swagger2' ? 'swagger-json' : 'openapi+json' ;
122126 case 'wsdl' :
123127 return 'wsdl' ;
124128 case 'wadl' :
@@ -181,7 +185,8 @@ async function publishRootApi(
181185 if ( specResult ) {
182186 const properties = json . properties as Record < string , unknown > | undefined ;
183187 const apiType = properties ?. type as string | undefined ;
184- const importFormat = getImportFormat ( specResult . format ?? 'yaml' , apiType ) ;
188+ const specDialect = detectSpecDialect ( specResult . content , specResult . format ) ;
189+ const importFormat = getImportFormat ( specResult . format ?? 'yaml' , apiType , specDialect ) ;
185190
186191 if ( importFormat ) {
187192 // Strip null-valued properties that cause validation errors in
@@ -213,7 +218,7 @@ async function publishRootApi(
213218 } ,
214219 } ;
215220
216- if ( importFormat === 'openapi' || importFormat === 'openapi+json' ) {
221+ if ( importFormat === 'openapi' || importFormat === 'openapi+json' || importFormat === 'swagger-json' ) {
217222 operationIdsWithNullDescription = getOpenApiOperationIdsWithNullDescription (
218223 specResult . content ,
219224 specResult . format
@@ -609,14 +614,36 @@ function getOpenApiOperationIdsWithNullDescription(
609614 }
610615}
611616
617+ /**
618+ * Detects the spec dialect of a JSON spec document. Returns 'swagger2' when the
619+ * content is a Swagger 2.0 JSON document (top-level `"swagger": "2.0"`),
620+ * otherwise 'openapi3'. Only JSON specs are inspected because APIM's Swagger 2.0
621+ * inline import (`swagger-json`) accepts JSON only; YAML specs are treated as
622+ * OpenAPI 3.x.
623+ */
624+ function detectSpecDialect ( content : string , format : string | undefined ) : ApiSpecDialect {
625+ const isJson =
626+ ! format ||
627+ format . toLowerCase ( ) . includes ( 'json' ) ||
628+ content . trimStart ( ) . startsWith ( '{' ) ;
629+ if ( ! isJson ) return 'openapi3' ;
630+ try {
631+ const spec = JSON . parse ( content ) as Record < string , unknown > ;
632+ return spec . swagger === '2.0' ? 'swagger2' : 'openapi3' ;
633+ } catch {
634+ return 'openapi3' ;
635+ }
636+ }
637+
612638/**
613639 * Sanitize an OpenAPI spec before importing into APIM.
614640 * Currently handles:
615641 * - Missing path parameter declarations: APIM rejects specs where a URL
616642 * template contains `{param}` placeholders that are not declared in the
617643 * operation's `parameters` array. We auto-inject the missing declarations
618- * as `{ name, in: "path", required: true, schema: { type: "string" } }`
619- * and log a warning for each one.
644+ * using the document's native parameter shape — OpenAPI 3.x uses
645+ * `{ ..., schema: { type: "string" } }` while Swagger 2.0 uses
646+ * `{ ..., type: "string" }` — and log a warning for each one.
620647 */
621648function sanitizeOpenApiSpec (
622649 content : string ,
@@ -637,6 +664,11 @@ function sanitizeOpenApiSpec(
637664 return content ; // Not valid JSON — return as-is
638665 }
639666
667+ // Swagger 2.0 path parameters are declared inline (`type: string`) rather
668+ // than wrapped in a `schema` object as in OpenAPI 3.x. Inject the matching
669+ // shape so the document stays valid for its native import format.
670+ const isSwagger2 = spec . swagger === '2.0' ;
671+
640672 const paths = spec . paths as Record < string , Record < string , unknown > > | undefined ;
641673 if ( ! paths ) return content ;
642674
@@ -664,13 +696,11 @@ function sanitizeOpenApiSpec(
664696
665697 for ( const name of placeholderNames ) {
666698 if ( ! declaredPathParams . has ( name ) ) {
667- // Inject the missing parameter declaration
668- const newParam : Record < string , unknown > = {
669- name,
670- in : 'path' ,
671- required : true ,
672- schema : { type : 'string' } ,
673- } ;
699+ // Inject the missing parameter declaration using the document's
700+ // native parameter shape (Swagger 2.0 vs OpenAPI 3.x).
701+ const newParam : Record < string , unknown > = isSwagger2
702+ ? { name, in : 'path' , required : true , type : 'string' }
703+ : { name, in : 'path' , required : true , schema : { type : 'string' } } ;
674704 const updatedParams = [ ...params , newParam ] ;
675705 op . parameters = updatedParams ;
676706 // Update params reference for subsequent placeholder checks on this operation
0 commit comments