Skip to content

Conversation

giuliohome
Copy link

Problem

When Swagger UI accesses /docs/ endpoint, it calls with_base_path() which triggers clone(). The current implementation uses copy.deepcopy(self._spec) (processed spec) instead of the original raw specification, causing validation failures that don't occur during app startup.

Root Cause

The clone() method at line 207 creates a new instance from self._spec (already processed and resolved) rather than self._raw_spec (original). The deepcopy operation can corrupt internal references and circular dependencies in the processed spec.

Fix

# Before
def clone(self):
    return type(self)(copy.deepcopy(self._spec))

# After  
def clone(self):
    return type(self)(copy.deepcopy(self._raw_spec))

Fixes #2078

@giuliohome
Copy link
Author

Why Validating Processed Specs is Conceptually Wrong

Technical Analysis by Claude AI

The Core Issue

The clone() method validates a processed specification (_spec) instead of the original specification (_raw_spec). This is fundamentally flawed because:

What happens during resolve_refs()

When Connexion processes a raw OpenAPI spec, it:

  1. Resolves all $ref references
  2. Inlines referenced schemas directly into the spec
  3. Changes the structural context of schema elements

Why this breaks validation

Example: Valid Raw Spec

# This passes OpenAPI 3.0 validation
additionalProperties:
  $ref: '#/components/schemas/SomeSchema'
  propertyNames:           # Ignored - not part of $ref validation
    pattern: '^[a-z]+$'

After resolve_refs() Processing

# This FAILS OpenAPI 3.0 validation  
additionalProperties:
  type: object              # Inlined from resolved $ref
  properties: {...}         # Inlined schema content
  propertyNames:            # Now INVALID - OpenAPI 3.0 doesn't support this
    pattern: '^[a-z]+$'

The Conceptual Problem

Raw spec validation checks: "Is this a valid OpenAPI document?"
Processed spec validation checks: "Is this internal representation valid?"

These are fundamentally different questions with different answers.

Real-World Impact

  • Raw spec: Contains $ref + extra fields → Valid (extra fields ignored)
  • Processed spec: Contains inlined schemas + same extra fields → Invalid (extra fields now in wrong context)

The same logical specification becomes invalid purely due to internal processing artifacts.

The Fix

def clone(self):
    return type(self)(copy.deepcopy(self._raw_spec))  # Always validate the original

This ensures clone() always produces a validatable OpenAPI specification, not a corrupted internal representation.

Why This Matters

When Swagger UI calls with_base_path()clone(), it expects to receive a valid OpenAPI spec that can be re-processed and displayed. Giving it a pre-processed spec violates this contract and causes validation failures.


The fundamental principle: Only validate what the user provided, never validate internal processing artifacts.

@chrisinmtown
Copy link
Contributor

I think this might almost be a duplicate of PR #1889? That PR has been ignored by the maintainers for a long time. Now you have come along and done heroic work in figuring it out all over again! Until this fix or something like it gets merged, V3 remains fundamentally broken. I just don't know how to raise the priority of this in the queue.

@giuliohome
Copy link
Author

Thank you @chrisinmtown, let me see if I can propose a slight modification to account for external refs

giuliohome and others added 2 commits September 6, 2025 16:39
Fix clone() to use appropriate spec based on ref types

Use _raw_spec for internal refs only, _spec for external refs.
Prevents validation failures from resolved schema artifacts while
maintaining compatibility with relative refs tests.

Fixes spec-first#2078
@giuliohome
Copy link
Author

New commit pushed with conditional cloning approach

I've updated the implementation to address the concerns about external references from PR #1889. The new approach:

  • For specs with only internal refs (#/components/...): Uses _raw_spec to avoid validation artifacts from resolved schemas
  • For specs with external refs (file.yaml#/...): Uses _spec to preserve resolved external content

This should maintain compatibility with the existing relative_refs tests while fixing the Swagger UI validation issue for specs with internal references only.

The _has_only_internal_refs() helper safely detects reference types by scanning for external file patterns (.yaml, .json) and non-fragment refs.

@RobbeSneyders This approach should address your previous concerns about test failures while still solving the core validation inconsistency issue.

@giuliohome
Copy link
Author

giuliohome commented Sep 6, 2025

To be honest, I see all 802 tests passing (in my local WSL) even with my original one-line fix, but I added the conditional handler to be on the safe side.

Testing Results

During testing, I verified that the clone() method is always called with internal references only, confirming that all 802 tests pass using _raw_spec.

The conditional handler was added for safety to handle potential external reference cases not covered by the current test suite.

Key Findings:

  • ✅ All 802 tests pass with the original one-line fix
  • _has_only_internal_refs() returns True for all test cases
  • ✅ No external references are used in the current test suite
  • ✅ The conditional approach provides defensive coding for edge cases

- Adds test_clone_external_refs.py to validate clone() behavior
- Ensures both internal and external reference scenarios are covered
- Increases test coverage from 802 to 803 tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Swagger UI endpoint crashes at runtime with JSON Schema keywords like propertyNames or patternProperties
2 participants