Skip to content

Commit fb2c7bc

Browse files
committed
Enhance round-trip comparison by refining ignored properties for API imports
1 parent 9ae4ef9 commit fb2c7bc

3 files changed

Lines changed: 40 additions & 7 deletions

File tree

.squad/agents/apimexpert/history.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,13 @@ the SDK surface, reference docs, or ad-hoc observation.
106106
- Classic Developer/Premium SKU only.
107107
- Docs: <https://learn.microsoft.com/rest/api/apimanagement/policy-restriction> · <https://learn.microsoft.com/azure/templates/microsoft.apimanagement/service/policyrestrictions>
108108

109+
### 2026-06-20: Link-import vs inline-import operation fidelity (round-trip)
110+
111+
When comparing a *link-imported* API (e.g. Petstore via `swagger-link`/`openapi-link`) against the `apiops publish` *inline-imported* result, operation payloads diverge in two import-path-only ways — neither is an apiops bug:
112+
113+
1. **`schemaId`/`typeName` on representations.** APIM binds operation request/response representations to an API-level schema **only on link import**. Inline import (`format: openapi, value: <spec>`, what publish uses) does NOT rebind, so the target has no `schemaId`. The schema *content* is identical and still extracted as API-level Schemas. → strip `schemaId`, `typeName` (and any derived schema token) on operation resources.
114+
115+
2. **Parameter/header `description`.** Link import drops `templateParameters`/`queryParameters`/header descriptions; inline import preserves them from the spec. So the published **target is more faithful** than the link-imported source. Authoritative descriptions live in the API schema. → strip `description` on parameter-shaped objects (`{ name, …, values }`).
116+
117+
Round-trip comparison harness (`tests/integration/all-resource-types`) normalizes both via `RepresentationSchemaRefIgnoredProperties` and `ParameterIgnoredProperties`. Symptom if not stripped: every operation shows a `properties.request/responses/templateParameters` DIFF present-on-one-side-only.
118+

tests/integration/all-resource-types/Compare-ApimInstance.ps1

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,19 @@ $StripTimestampProperties = @(
5858
$RequestResponseIgnoredProperties = @('description')
5959

6060
# Properties ignored on representation objects (have 'contentType' or 'schemaId'):
61-
# - description: SOAP/WSDL import generates descriptions that vary
62-
# - schemaId/typeName: Operation reconciliation strips these before PATCH because
63-
# APIM rebinds representation schema refs during import. Values are therefore
64-
# not stable for round-trip comparison and are ignored for operation resources.
61+
# - description: varies by import path.
62+
# - schemaId/typeName: unstable schema refs APIM rebinds on import; stripped for ops.
63+
# - __schemaSemantic: synthetic token from Add-RepresentationSchemaSemantics. Only
64+
# link-imported source gets schemaId (and thus this token); inline-imported target
65+
# doesn't. Schema content is compared separately via API-level Schemas, so strip it.
6566
$RepresentationIgnoredProperties = @('description')
66-
$RepresentationSchemaRefIgnoredProperties = @('schemaId', 'typeName')
67+
$RepresentationSchemaRefIgnoredProperties = @('schemaId', 'typeName', '__schemaSemantic')
68+
69+
# Properties ignored on parameter/header objects (have 'name' + 'values':
70+
# templateParameters, queryParameters, response headers):
71+
# - description: link import (source) drops them; inline import (target) keeps them.
72+
# Authoritative copies live in the API schema, compared separately.
73+
$ParameterIgnoredProperties = @('description')
6774

6875
# Cache of normalized API schema semantics per instance/api, keyed as:
6976
# "{instance}|{apiName}" => @{ schemaId => normalizedSchemaJson }
@@ -78,7 +85,8 @@ $NormalizationContext = New-CompareNormalizationContext `
7885
-StripTimestampProperties $StripTimestampProperties `
7986
-RequestResponseIgnoredProperties $RequestResponseIgnoredProperties `
8087
-RepresentationIgnoredProperties $RepresentationIgnoredProperties `
81-
-RepresentationSchemaRefIgnoredProperties $RepresentationSchemaRefIgnoredProperties
88+
-RepresentationSchemaRefIgnoredProperties $RepresentationSchemaRefIgnoredProperties `
89+
-ParameterIgnoredProperties $ParameterIgnoredProperties
8290

8391
# ── Helpers ─────────────────────────────────────────────────────────────────
8492

tests/integration/all-resource-types/modules/CompareSemantics.psm1

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,8 @@ function New-CompareNormalizationContext {
194194
[string[]] $StripTimestampProperties = @(),
195195
[string[]] $RequestResponseIgnoredProperties = @(),
196196
[string[]] $RepresentationIgnoredProperties = @(),
197-
[string[]] $RepresentationSchemaRefIgnoredProperties = @()
197+
[string[]] $RepresentationSchemaRefIgnoredProperties = @(),
198+
[string[]] $ParameterIgnoredProperties = @()
198199
)
199200

200201
return [ordered]@{
@@ -210,6 +211,7 @@ function New-CompareNormalizationContext {
210211
RequestResponseIgnoredProperties = $RequestResponseIgnoredProperties
211212
RepresentationIgnoredProperties = $RepresentationIgnoredProperties
212213
RepresentationSchemaRefIgnoredProperties = $RepresentationSchemaRefIgnoredProperties
214+
ParameterIgnoredProperties = $ParameterIgnoredProperties
213215
}
214216
}
215217

@@ -266,12 +268,18 @@ function ConvertTo-NormalizedPropertyValue {
266268
$out = [ordered]@{}
267269
$isRequestResponse = $Value.Contains('representations')
268270
$isRepresentation = $Value.Contains('contentType') -or $Value.Contains('schemaId')
271+
# Parameter/header objects (templateParameters, queryParameters, response
272+
# headers) are shaped as { name, type, values, ... } with no representations
273+
# and no contentType/schemaId.
274+
$isParameter = (-not $isRequestResponse) -and (-not $isRepresentation) -and `
275+
$Value.Contains('name') -and $Value.Contains('values')
269276
foreach ($key in ($Value.Keys | Sort-Object)) {
270277
if ($IsRoot -and $key -in $Context.StripReadOnlyProperties) { continue }
271278
if ($key -in $Context.StripTimestampProperties) { continue }
272279
if ($isRequestResponse -and $key -in $Context.RequestResponseIgnoredProperties) { continue }
273280
if ($isRepresentation -and $key -in $Context.RepresentationIgnoredProperties) { continue }
274281
if ($IgnoreRepresentationSchemaRefs -and $isRepresentation -and $key -in $Context.RepresentationSchemaRefIgnoredProperties) { continue }
282+
if ($isParameter -and $key -in $Context.ParameterIgnoredProperties) { continue }
275283
$out[$key] = ConvertTo-NormalizedPropertyValue -Value $Value[$key] -Context $Context -IgnoreRepresentationSchemaRefs:$IgnoreRepresentationSchemaRefs
276284
}
277285
return $out
@@ -281,12 +289,19 @@ function ConvertTo-NormalizedPropertyValue {
281289
$out = [ordered]@{}
282290
$isRequestResponse = $null -ne ($Value.PSObject.Properties | Where-Object { $_.Name -eq 'representations' })
283291
$isRepresentation = $null -ne ($Value.PSObject.Properties | Where-Object { $_.Name -eq 'contentType' -or $_.Name -eq 'schemaId' })
292+
# Parameter/header objects (templateParameters, queryParameters, response
293+
# headers) are shaped as { name, type, values, ... } with no representations
294+
# and no contentType/schemaId.
295+
$hasName = $null -ne ($Value.PSObject.Properties | Where-Object { $_.Name -eq 'name' })
296+
$hasValues = $null -ne ($Value.PSObject.Properties | Where-Object { $_.Name -eq 'values' })
297+
$isParameter = (-not $isRequestResponse) -and (-not $isRepresentation) -and $hasName -and $hasValues
284298
foreach ($prop in ($Value.PSObject.Properties | Sort-Object Name)) {
285299
if ($IsRoot -and $prop.Name -in $Context.StripReadOnlyProperties) { continue }
286300
if ($prop.Name -in $Context.StripTimestampProperties) { continue }
287301
if ($isRequestResponse -and $prop.Name -in $Context.RequestResponseIgnoredProperties) { continue }
288302
if ($isRepresentation -and $prop.Name -in $Context.RepresentationIgnoredProperties) { continue }
289303
if ($IgnoreRepresentationSchemaRefs -and $isRepresentation -and $prop.Name -in $Context.RepresentationSchemaRefIgnoredProperties) { continue }
304+
if ($isParameter -and $prop.Name -in $Context.ParameterIgnoredProperties) { continue }
290305
$out[$prop.Name] = ConvertTo-NormalizedPropertyValue -Value $prop.Value -Context $Context -IgnoreRepresentationSchemaRefs:$IgnoreRepresentationSchemaRefs
291306
}
292307
return $out

0 commit comments

Comments
 (0)