Skip to content

fix: resolve $ref in x-amazon-apigateway-integration (fixes #6045)#8648

Open
dcabib wants to merge 2 commits into
aws:developfrom
dcabib:fix/issue-6045-ref-resolution
Open

fix: resolve $ref in x-amazon-apigateway-integration (fixes #6045)#8648
dcabib wants to merge 2 commits into
aws:developfrom
dcabib:fix/issue-6045-ref-resolution

Conversation

@dcabib

@dcabib dcabib commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

Which issue(s) does this change fix?

#6045

Why is this change necessary?

When using $ref in x-amazon-apigateway-integration (which is valid per OpenAPI 3.0 spec), sam local start-api crashes with AttributeError: '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 because get("type") returns None.

How does it address the issue?

Added JSON Reference ($ref) resolution to SwaggerParser in samcli/commands/local/lib/swagger/parser.py. Before checking for type, the code now resolves the reference to get the actual integration config.

The implementation handles:

  • Local JSON Pointer refs (#/path/to/object)
  • Nested refs (a ref pointing to another ref)
  • Circular ref detection (logs warning and skips)
  • 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 added $ref resolution 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 $ref are 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

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

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.
@dcabib dcabib requested a review from a team as a code owner February 12, 2026 12:32
@github-actions github-actions Bot added pr/external stage/needs-triage Automatically applied to new issues and PRs, indicating they haven't been looked at. labels Feb 12, 2026

@roger-zhangg roger-zhangg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - thorough and well-scoped fix for issue #6045.

Summary of review:

  1. Root cause correctly identified and fixed. The crash at integration.get("type").lower() (line 431 in the original) happens because when integration is {"$ref": "..."}, .get("type") returns None. The fix resolves the $ref before accessing type, and also defensively extracts integration_type separately before the .lower() call.

  2. Resolver logic is sound:

    • Circular refs: detected via a visited set, logs a warning, returns None.
    • 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).
  3. Security - no path traversal risk. The resolver only supports local JSON Pointer refs (#/path/...) which traverse the in-memory self.swagger dict - 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.

  4. Scope is appropriate. The fix resolves $ref in 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.

  5. 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.

  6. 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.

@aws-sam-tooling-bot aws-sam-tooling-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Results

Reviewed: 8f25f26..87746a8
Files: 2
Comments: 2

from unittest import TestCase
from unittest.mock import Mock, patch

from parameterized import parameterized, param

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr/external stage/needs-triage Automatically applied to new issues and PRs, indicating they haven't been looked at.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants