fix: resolve $ref in x-amazon-apigateway-integration (fixes #6045)#8648
fix: resolve $ref in x-amazon-apigateway-integration (fixes #6045)#8648dcabib wants to merge 2 commits into
Conversation
Add JSON Reference ($ref) resolution support to SwaggerParser to handle OpenAPI 3.0 documents that use $ref in x-amazon-apigateway-integration. The implementation handles: - Local JSON Pointer refs (#/path/to/object) - Nested refs (a ref pointing to another ref) - Circular ref detection with warning - Depth limiting (max 10 levels) - External refs (URLs/files) - logged as warning, not supported in local mode - JSON Pointer escaping (~0 for ~, ~1 for /) per RFC 6901 Also resolves $ref in paths, method configs, and security schemes/authorizers. Includes 37 new unit tests covering all edge cases.
roger-zhangg
left a comment
There was a problem hiding this comment.
LGTM - thorough and well-scoped fix for issue #6045.
Summary of review:
-
Root cause correctly identified and fixed. The crash at
integration.get("type").lower()(line 431 in the original) happens because whenintegrationis{"$ref": "..."},.get("type")returnsNone. The fix resolves the$refbefore accessingtype, and also defensively extractsintegration_typeseparately before the.lower()call. -
Resolver logic is sound:
- Circular refs: detected via a
visitedset, logs a warning, returnsNone. - Depth limiting: capped at 10 levels via
_MAX_REF_RESOLUTION_DEPTH. - Nested refs: handled recursively with both circular and depth checks.
- JSON Pointer escaping (RFC 6901):
~1->/,~0->~correctly decoded. - Non-dict resolution targets: returns
None(only dict integrations are meaningful).
- Circular refs: detected via a
-
Security - no path traversal risk. The resolver only supports local JSON Pointer refs (
#/path/...) which traverse the in-memoryself.swaggerdict - never the filesystem. External refs (URLs, file paths) are explicitly rejected with a warning log. No file I/O or network access is possible through this code path. -
Scope is appropriate. The fix resolves
$refin four places where the parser accesses config dicts that could contain refs: (a)x-amazon-apigateway-integration, (b) path configs, (c) method configs, and (d) security schemes/authorizers. All within the existing Swagger/OpenAPI parsing context. -
Test coverage is comprehensive (37 tests). Covers: simple refs, nested refs, circular refs, depth limits, JSON pointer escaping, external URL/file refs, non-string refs, non-dict results, unresolvable refs, the exact issue #6045 reproduction, mock integrations, multiple methods with refs, authorizer refs, and edge cases.
-
CI fully green - all 44 checks pass including integration tests on both Linux and Windows with Python 3.9 and 3.11.
Minor nit (non-blocking): _resolve_object_refs is defined but not used in any of the modified call sites (only _resolve_ref is called directly). It could be useful for future work or could be removed to reduce dead code. Not a blocker.
| from unittest import TestCase | ||
| from unittest.mock import Mock, patch | ||
|
|
||
| from parameterized import parameterized, param |
There was a problem hiding this comment.
[GENERAL] parameterized and param are imported but never used anywhere in this file (no @parameterized.expand decorators, no param(...) constructors — every test is a plain def test_* method). The project's pyproject.toml configures ruff with the Pyflakes rule set enabled (select = ["E", "F", "PL", "I", "TID"]), so this will fail the F401 lint check in CI.
Remove the unused import:
from unittest import TestCase
from unittest.mock import Mock, patch
from samcli.commands.local.lib.swagger.parser import SwaggerParser, MAXREF_RESOLUTION_DEPTH
from samcli.local.apigw.route import Route| def _resolve_object_refs(self, obj: Any, visited: Optional[Set[str]] = None, depth: int = 0) -> Any: | ||
| """ | ||
| Recursively resolve all $ref in an object. | ||
|
|
There was a problem hiding this comment.
[GENERAL] _resolve_object_refs is defined and unit-tested, but is not called from any production code path. All three ref-resolution sites in the PR (get_authorizers, get_routes, and _get_integration) invoke _resolve_ref directly and do not use _resolve_object_refs. A grep across the repository confirms no other caller exists.
Introducing an unused helper adds maintenance burden and can mislead future contributors into thinking it's part of the resolution flow. It also has a subtle correctness gap that will bite whoever wires it up later: when it recurses into children ({k: self._resolve_object_refs(v, visited, depth + 1) ...}) it reuses the same visited set across siblings, but the call into _resolve_ref passes visited.copy(), so visited is effectively always empty and cycle detection relies solely on the depth limit.
Either remove the method (and its dedicated test class) or wire it into a concrete call site in this PR so its semantics are exercised end-to-end.
Which issue(s) does this change fix?
#6045
Why is this change necessary?
When using
$refinx-amazon-apigateway-integration(which is valid per OpenAPI 3.0 spec),sam local start-apicrashes withAttributeError: 'NoneType' object has no attribute 'lower'.The parser receives
{"$ref": "#/components/..."}instead of the actual integration config, and when it tries to call.get("type").lower(), it fails becauseget("type")returnsNone.How does it address the issue?
Added JSON Reference (
$ref) resolution toSwaggerParserinsamcli/commands/local/lib/swagger/parser.py. Before checking fortype, the code now resolves the reference to get the actual integration config.The implementation handles:
#/path/to/object)~0for~,~1for/) per RFC 6901Also added
$refresolution for paths, method configs, and security schemes/authorizers.What side effects does this change have?
None. The fix only affects documents that use
$ref- documents without$refare unaffected. External refs (URLs or file paths) are gracefully skipped with a warning since they're not supported in local mode.Mandatory Checklist
PRs will only be reviewed after checklist is complete
make prpassesmake update-reproducible-reqsif dependencies were changed - N/ABy submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.