Skip to content

Commit 5163e23

Browse files
robert-hebel-sbkirill-of-turov
authored andcommitted
feat(oas32): add basic OpenAPI 3.2.0 support (swagger-api#10721)
1 parent e9fe117 commit 5163e23

55 files changed

Lines changed: 2858 additions & 93 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/add-oas-support.md

Lines changed: 153 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@ See [full mapping table with examples](#step-1d-map-specification-changes-to-swa
152152
5.**Single quotes** → ✅ Use double quotes
153153
6.**Skipping spec examples** → ✅ Use as test fixtures
154154
7.**Hardcoded assumptions** → ✅ Verify everything in spec
155+
8.**`props.Ori` in ComponentWrapper** → ✅ Use `props.originalComponent` (see Pattern 3)
156+
9.**Copying entire Info component for minor version** → ✅ Use OpenAPIVersion wrapper + `getComponent("OAS{PREV}Info")`
157+
10.**OAS{VERSION} logic in OAS{PREV} plugin** → ✅ Each version's logic lives in its own plugin
158+
11.**`isOAS{PREV}` wrapper on minor version** → ✅ Only add if regex overlaps; minor bumps don't need it
159+
12.**New HTTP method in core `validOperationMethods`** → ✅ Add to `OPERATION_METHODS` + wrap with `createOnlyOAS{VERSION}SelectorWrapper`
160+
13.**Assuming new OAS meta-schema = new JSON Schema dialect** → ✅ Verify — OAS 3.2 still uses JSON Schema 2020-12
161+
14.**Inline version guard in wrap-components** → ✅ Always use `createOnlyOAS{VERSION}ComponentWrapper`; never write `(Original, system) => (props) => { if (...isOAS{VERSION}...) }` by hand — even expanding an existing guard (`isOAS31 || isOAS32`) is wrong; add dedicated wrap-components in the new plugin instead
155162

156163
### ✅ Pre-Submit Checklist
157164

@@ -268,11 +275,26 @@ export const createOnlyOAS{VERSION}SelectorWrapper =
268275
```
269276

270277
### Pattern 3: Component Wrappers
278+
279+
**⚠️ IMPORTANT: `originalComponent` prop name, not `Ori`**
280+
281+
`createOnlyOAS{VERSION}ComponentWrapper` passes the original component as `originalComponent` in props (NOT `Ori` — that's the OAS3/OAS30ComponentWrapFactory convention). Use `const { originalComponent: Ori } = props` to access it.
282+
283+
**Pattern A — Reuse previous version's component via getComponent:**
271284
```javascript
272285
const ComponentWrapper = createOnlyOAS{VERSION}ComponentWrapper(({ getSystem }) => {
273286
const system = getSystem()
274-
const OAS{VERSION}Component = system.getComponent("OAS{VERSION}Component", true)
275-
return <OAS{VERSION}Component />
287+
const OAS{PREV_VERSION}Component = system.getComponent("OAS{PREV_VERSION}Component", true)
288+
return <OAS{PREV_VERSION}Component />
289+
})
290+
```
291+
292+
**Pattern B — Render original component with extra props (e.g. version badge):**
293+
```javascript
294+
// Use `originalComponent` (NOT `Ori`) — that's the prop name createOnlyOAS{VERSION}ComponentWrapper passes
295+
const OpenAPIVersionWrapper = createOnlyOAS{VERSION}ComponentWrapper((props) => {
296+
const { originalComponent: Ori } = props
297+
return <Ori oasVersion="{VERSION}" />
276298
})
277299
```
278300

@@ -839,15 +861,25 @@ export const createSystemSelector =
839861
}
840862

841863
/**
842-
* Creates a component wrapper that only renders for OAS {VERSION}
864+
* Creates a component wrapper that only renders for OAS {VERSION}.
865+
* When active, passes `originalComponent` (the unwrapped original) and
866+
* `getSystem` as extra props. Access the original via:
867+
* const { originalComponent: Ori } = props
868+
* NOT via `props.Ori` — that's the OAS3/OAS30ComponentWrapFactory convention.
843869
*/
844870
export const createOnlyOAS{VERSION}ComponentWrapper =
845-
(Component) =>
846-
({ ...props }) => {
847-
const { specSelectors } = props
848-
const isOAS{VERSION} = specSelectors.isOAS{VERSION}()
871+
(Component) => (Original, system) => (props) => {
872+
if (system.specSelectors.isOAS{VERSION}()) {
873+
return (
874+
<Component
875+
{...props}
876+
originalComponent={Original}
877+
getSystem={system.getSystem}
878+
/>
879+
)
880+
}
849881

850-
return isOAS{VERSION} ? <Component {...props} /> : props.Ori()
882+
return <Original {...props} />
851883
}
852884

853885
/**
@@ -1008,6 +1040,8 @@ export const selectIsOAS{VERSION} = (state, system) => () => {
10081040

10091041
**File:** `src/core/plugins/oas{VERSION_NUMBER}/spec-extensions/wrap-selectors.js`
10101042

1043+
**⚠️ Minor version: DON'T wrap `isOAS{PREV}` unless the regex actually matches both versions.** For example, OAS 3.1's `isOAS31` regex (`/^3\.1\./`) will never match `3.2.x`, so wrapping it to return `false` is dead code. Only add the `isOAS{PREV}` override if the previous version's detection regex would incorrectly match the new version.
1044+
10111045
**Template:**
10121046
```javascript
10131047
/**
@@ -1016,11 +1050,17 @@ export const selectIsOAS{VERSION} = (state, system) => () => {
10161050

10171051
import { createOnlyOAS{VERSION}SelectorWrapper } from "../fn.js"
10181052

1019-
// Wrap previous version selectors if behavior changes
1020-
// Example:
1021-
// export const isOAS{PREVIOUS_VERSION} = createOnlyOAS{VERSION}SelectorWrapper((state) => () => false)
1053+
// Ensure OAS {VERSION} specs are recognized as OAS 3.x (needed when major version number didn't change)
1054+
export const isOAS3 =
1055+
(oriSelector, system) =>
1056+
(state, ...args) => {
1057+
const isOAS{VERSION} = system.specSelectors.isOAS{VERSION}()
1058+
return isOAS{VERSION} || oriSelector(...args)
1059+
}
10221060

1023-
// This makes isOAS{PREVIOUS_VERSION} return false when spec is OAS {VERSION}
1061+
// ONLY add isOAS{PREV} wrapper if the previous version's regex could match this new version.
1062+
// For a minor version bump (e.g. 3.1 → 3.2), the previous regex won't match, so DON'T add this.
1063+
// export const isOAS{PREV} = createOnlyOAS{VERSION}SelectorWrapper((state) => () => false)
10241064
```
10251065

10261066
### Step 6: Create Version Pragma Filter Component
@@ -1135,23 +1175,51 @@ export default NewFeature
11351175

11361176
**File:** `src/core/plugins/oas{VERSION_NUMBER}/wrap-components/info.jsx`
11371177

1138-
**Template:**
1178+
**⚠️ Minor version (e.g. 3.2): don't create a new Info component.** If the Info object is identical to the previous version, reuse `OAS{PREV_VERSION}Info` via `getComponent` instead of copying the whole component. Only create a new Info component for major versions with significant structural changes.
1179+
1180+
**Template (minor version — reuse previous Info):**
11391181
```javascript
11401182
/**
11411183
* @prettier
11421184
*/
11431185

1186+
import React from "react"
11441187
import { createOnlyOAS{VERSION}ComponentWrapper } from "../fn.js"
11451188

11461189
const InfoWrapper = createOnlyOAS{VERSION}ComponentWrapper(({ getSystem }) => {
11471190
const system = getSystem()
1148-
const OAS{VERSION}Info = system.getComponent("OAS{VERSION}Info", true)
1149-
return <OAS{VERSION}Info />
1191+
const OAS{PREV_VERSION}Info = system.getComponent("OAS{PREV_VERSION}Info", true)
1192+
return <OAS{PREV_VERSION}Info />
11501193
})
11511194

11521195
export default InfoWrapper
11531196
```
11541197

1198+
**Also — OpenAPIVersion wrapper (minor version — change version badge only):**
1199+
1200+
For minor versions where the only Info difference is the version badge, use the OpenAPIVersion wrapper pattern instead of wrapping InfoContainer at all:
1201+
1202+
```javascript
1203+
// wrap-components/openapi-version.jsx
1204+
/**
1205+
* @prettier
1206+
*/
1207+
import React from "react"
1208+
import { createOnlyOAS{VERSION}ComponentWrapper } from "../fn.js"
1209+
1210+
export default createOnlyOAS{VERSION}ComponentWrapper((props) => {
1211+
const { originalComponent: Ori } = props // NOT `Ori` from props directly — use `originalComponent`
1212+
return <Ori oasVersion="{MAJOR}.{MINOR}" />
1213+
})
1214+
```
1215+
1216+
Then register it in index.js:
1217+
```javascript
1218+
wrapComponents: {
1219+
OpenAPIVersion: OpenAPIVersionWrapper, // changes the version badge shown in the Info header
1220+
}
1221+
```
1222+
11551223
### Step 9: Implement afterLoad Hook
11561224

11571225
**File:** `src/core/plugins/oas{VERSION_NUMBER}/after-load.js`
@@ -1531,11 +1599,23 @@ Closes #{ISSUE_NUMBER}
15311599
6. **Test with real specs** - Use actual OAS {VERSION} examples
15321600
7. **Don't modify core unnecessarily** - Use plugin architecture
15331601
8. **Version regex must be exact** - Follow pattern from OAS 3.1
1534-
9. **Component wrappers return Ori()** - When not active version
1602+
9. **Component wrappers render `<Original {...props} />`** - When not active version (handled by `createOnlyOAS{VERSION}ComponentWrapper` automatically)
15351603
10. **afterLoad runs after all plugins** - Safe to modify system
1604+
11. **`originalComponent` not `Ori`** - `createOnlyOAS{VERSION}ComponentWrapper` passes the original as `originalComponent` prop. Use `const { originalComponent: Ori } = props`. The `Ori` name comes from `OAS30ComponentWrapFactory` which uses a different signature.
1605+
12. **Don't copy the entire Info component for minor versions** - Use the OpenAPIVersion wrapper to change the version badge, and reuse the previous version's Info via `getComponent("OAS{PREV}Info", true)` instead of copying.
1606+
13. **Don't add version-specific logic to previous version's plugin** - OAS32 logic belongs in the OAS32 plugin, not in OAS31 plugin files (e.g. don't add `isOAS32` checks to `oas31/wrap-components/license.jsx`).
1607+
20. **Always use `createOnlyOAS{VERSION}ComponentWrapper` in wrap-components — never inline the version guard** - Each wrap-component in a plugin should use the factory (`createOnlyOAS{VERSION}ComponentWrapper`), not a hand-written `(Original, system) => (props) => { if (system.specSelectors.isOAS{VERSION}()) ... }`. When a later version (OAS32) also needs to reuse the same OAS31 component, it gets its own dedicated wrap-component in the OAS32 plugin — don't expand the guard to `isOAS31 || isOAS32` in the OAS31 file. The OAS31 wrappers for contact/license should use `createOnlyOAS31ComponentWrapper` and nothing else; OAS32 contact/license wrappers live in `oas32/wrap-components/` and handle the OAS32 case.
1608+
14. **Don't add `isOAS{PREV}` wrap-selector unless the regex actually overlaps** - For minor versions, the previous version's regex already won't match (e.g. OAS31's `/^3\.1\./` won't match `3.2.x`). Adding the wrapper is dead code.
1609+
15. **New HTTP methods: use `OPERATION_METHODS` in `spec/selectors.js`, not `operationsWithRootInherited` wrapper** - Adding the method to `OPERATION_METHODS` makes the core `operations` selector collect those ops. The `validOperationMethods` wrapper (guarded by `createOnlyOAS{VERSION}SelectorWrapper`) then controls whether the UI renders them. No need for an `operationsWithRootInherited` wrapper.
1610+
16. **Don't add new HTTP methods to the core `validOperationMethods` constant** - Adding `"query"` to the base constant in `spec/selectors.js` affects ALL OAS versions. Instead, add it only via `createOnlyOAS{VERSION}SelectorWrapper` in your plugin's `spec-extensions/wrap-selectors.js`.
1611+
17. **Don't create selectors only used in tests** - Selectors like `selectHasQueryOperations` that are never called from production code should not exist. Remove them.
1612+
18. **OAS meta-schema URL ≠ new JSON Schema dialect** - The URL `https://spec.openapis.org/oas/3.2/schema/...` is the OAS 3.2 document structure schema, NOT a new JSON Schema version. OAS 3.2 uses JSON Schema 2020-12, the same as OAS 3.1. Don't list "new JSON Schema version" as a feature unless it actually changes.
1613+
19. **Verify "not yet implemented" feature list against previous version** - Features like `pathItems in Components` were already in OAS 3.1, not new in 3.2. Check what's actually new before listing it.
15361614

15371615
## JSON Schema Version Changes
15381616

1617+
**⚠️ First, verify whether the JSON Schema version actually changed.** The OAS meta-schema URL (e.g. `https://spec.openapis.org/oas/3.2/schema/...`) describes the OAS document structure — it is NOT a new JSON Schema dialect. OAS 3.2 uses JSON Schema 2020-12, the same as OAS 3.1. Only create a new `json-schema-{VERSION}` plugin if the actual JSON Schema dialect changed (as it did from OAS 3.0 Draft-07 → OAS 3.1 JSON Schema 2020-12).
1618+
15391619
If the new OAS version uses a different JSON Schema version:
15401620

15411621
1. **Create json-schema-{VERSION} plugin** (separate from OAS plugin)
@@ -1574,16 +1654,24 @@ Example from OAS 3.1 using JSON Schema 2020-12:
15741654
- Backward-compatible additions
15751655
- New optional fields
15761656
- Enhanced existing features
1577-
- Same JSON Schema version
1657+
- Same JSON Schema version (verify before assuming it changed)
15781658
- Incremental improvements
15791659

15801660
**Implementation approach:**
15811661
- Lighter plugin with focused additions
15821662
- Fewer component wrappers needed
15831663
- Selective selector additions
1584-
- Reuse most of previous version logic
1664+
- Reuse most of previous version logic via `getComponent("OAS{PREV}...", true)`
15851665
- Simpler afterLoad modifications
15861666

1667+
**Key decisions for minor versions (lessons from OAS 3.2):**
1668+
1669+
1. **Version badge only changed?** Use `OpenAPIVersion` wrapper + reuse `OAS{PREV}Info` — don't create a new Info component.
1670+
2. **New HTTP method (e.g. QUERY)?** Add it to `OPERATION_METHODS` in `src/core/plugins/spec/selectors.js` AND add it to `validOperationMethods` via `createOnlyOAS{VERSION}SelectorWrapper`. Don't add it to the core `validOperationMethods` constant (affects all versions).
1671+
3. **`isOAS{PREV}` wrapper needed?** Only if the previous regex also matches the new version string. Minor version bumps (3.1 → 3.2) don't need it.
1672+
4. **JSON Schema version comment?** Verify it actually changed. OAS 3.2 uses JSON Schema 2020-12, the same as OAS 3.1. The new OAS meta-schema URL (`https://spec.openapis.org/oas/3.2/schema/...`) describes OAS document structure, not a new JSON Schema dialect.
1673+
5. **"Not yet implemented" list?** Double-check each item against the *previous* version's changelog — some may already be implemented.
1674+
15871675
## Example: Adding OAS 4.0 (Major)
15881676

15891677
**Version detection:**
@@ -1632,16 +1720,56 @@ export const isOAS32 = (jsSpec) => {
16321720

16331721
**Directory:** `src/core/plugins/oas32/`
16341722

1635-
**Extend OAS 3.1:**
1723+
**Lighter component wrapping — OAS 3.2 specific patterns:**
1724+
1725+
```javascript
1726+
// wrap-components/openapi-version.jsx — change the version badge only
1727+
// Use `originalComponent` (NOT `Ori`) — that's what createOnlyOAS32ComponentWrapper passes
1728+
export default createOnlyOAS32ComponentWrapper((props) => {
1729+
const { originalComponent: Ori } = props
1730+
return <Ori oasVersion="3.2" />
1731+
})
1732+
1733+
// wrap-components/info.jsx — reuse OAS31Info, don't create a new one
1734+
export default createOnlyOAS32ComponentWrapper(({ getSystem }) => {
1735+
const system = getSystem()
1736+
const OAS31Info = system.getComponent("OAS31Info", true)
1737+
return <OAS31Info />
1738+
})
1739+
1740+
// wrap-components/contact.jsx and license.jsx — same pattern (OAS32 logic belongs HERE, not in OAS31)
1741+
export default createOnlyOAS32ComponentWrapper((props) => {
1742+
const { getSystem } = props
1743+
const system = getSystem()
1744+
const OAS31Contact = system.getComponent("OAS31Contact", true)
1745+
return <OAS31Contact {...props} />
1746+
})
1747+
```
1748+
1749+
**New HTTP method (QUERY) — two-part approach:**
1750+
1751+
Part 1: Add to `OPERATION_METHODS` in `src/core/plugins/spec/selectors.js` so the `operations` selector collects QUERY ops:
1752+
```javascript
1753+
// spec/selectors.js
1754+
export const OPERATION_METHODS = [
1755+
"get", "put", "post", "delete", "options", "head", "patch", "trace", "query",
1756+
]
1757+
```
1758+
1759+
Part 2: Add to `validOperationMethods` via `createOnlyOAS32SelectorWrapper` so the UI only renders them for OAS 3.2:
16361760
```javascript
1637-
// oas31-extensions/fn.js
1638-
// Import functions from oas31 and extend as needed
1761+
// oas32/spec-extensions/wrap-selectors.js
1762+
export const validOperationMethods = createOnlyOAS32SelectorWrapper(
1763+
() => (oriSelector, system) => system.oas32Selectors.validOperationMethods()
1764+
)
1765+
1766+
// oas32/selectors.js
1767+
export const validOperationMethods = () => [
1768+
"get", "put", "post", "delete", "options", "head", "patch", "trace", "query",
1769+
]
16391770
```
16401771

1641-
**Lighter component wrapping:**
1642-
- Only wrap components that change
1643-
- Reuse most OAS 3.1 logic
1644-
- Minimal selector additions
1772+
Do NOT add `"query"` to the core `validOperationMethods` constant — that would affect all versions.
16451773

16461774
## Final Checklist
16471775

CLAUDE.md

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# CLAUDE.md - Swagger UI Codebase Guide
22

3-
> **Last Updated:** 2026-01-21
4-
> **Version:** 5.31.0
3+
> **Last Updated:** 2026-02-24
4+
> **Version:** 5.32.0 (in development)
55
> **Purpose:** Comprehensive guide for AI assistants working with the Swagger UI codebase
66
77
---
@@ -124,6 +124,7 @@ Swagger UI uses a **sophisticated plugin system** powered by Redux. The core sys
124124
- `logs` - Logging
125125
- `oas3` - OpenAPI 3.0.x support
126126
- `oas31` - OpenAPI 3.1.x support
127+
- `oas32` - OpenAPI 3.2.x support
127128
- `on-complete` - Completion callbacks
128129
- `request-snippets` - Code snippet generation
129130
- `safe-render` - Safe component rendering
@@ -571,6 +572,42 @@ Each plugin has:
571572

572573
See documentation: `docs/customization/plugin-api.md`
573574

575+
### Cross-Plugin Import Guidelines
576+
577+
**IMPORTANT:** Avoid cross-plugin imports to maintain plugin independence and modularity.
578+
579+
**Pattern to Follow:**
580+
- Each plugin should be self-contained with its own components, utilities, and functions
581+
- When OAS version plugins (oas3, oas31, oas32) need similar functionality, create self-contained copies within each plugin
582+
- Wrap components should import from their own plugin's components, not from other plugins
583+
584+
**Example Structure:**
585+
```
586+
src/core/plugins/oas32/
587+
├── json-schema-2020-12-extensions/
588+
│ ├── components/ # Self-contained components
589+
│ │ └── keywords/
590+
│ │ ├── Description.jsx
591+
│ │ └── Properties.jsx
592+
│ ├── wrap-components/ # Wrappers for components
593+
│ │ └── keywords/
594+
│ │ ├── Description.jsx # Imports from ../../components/
595+
│ │ └── Properties.jsx # Not from ../../../../oas31/
596+
│ └── fn.js # Self-contained utilities
597+
```
598+
599+
**Why This Matters:**
600+
- Prevents tight coupling between plugins
601+
- Makes plugins easier to test in isolation
602+
- Allows independent versioning and updates
603+
- Reduces risk of breaking changes across plugins
604+
- Improves code maintainability
605+
606+
**Exceptions:**
607+
- Shared core utilities in `src/core/utils/` are acceptable
608+
- System-level functions in `src/core/system.js` are acceptable
609+
- Base components in `src/core/components/` are acceptable
610+
574611
### Preset System
575612

576613
**Base Preset:** `src/core/presets/base.js`
@@ -813,6 +850,7 @@ dist/ # Build output (generated)
813850
- OAS 2.0: Use `src/core/plugins/swagger-client/`
814851
- OAS 3.0.x: Use `src/core/plugins/oas3/`
815852
- OAS 3.1.x: Use `src/core/plugins/oas31/`
853+
- OAS 3.2.x: Use `src/core/plugins/oas32/`
816854

817855
**Adding Test Specs:**
818856
- Add to `test/e2e-cypress/static/documents/`
@@ -839,6 +877,7 @@ dist/ # Build output (generated)
839877
13. **Use the plugin architecture** - don't modify core unnecessarily
840878
14. **Preserve backward compatibility** unless explicitly breaking
841879
15. **Run full test suite before submitting PR**
880+
16. **Keep plugins self-contained** - avoid cross-plugin imports (see [Cross-Plugin Import Guidelines](#cross-plugin-import-guidelines))
842881

843882
### DON'Ts ❌
844883

@@ -857,6 +896,7 @@ dist/ # Build output (generated)
857896
13. **Don't ignore Cypress test failures**
858897
14. **Don't add dependencies without justification**
859898
15. **Don't break the build** - verify with `npm run build`
899+
16. **Don't import from other plugins** - create self-contained copies instead (e.g., don't import from `oas31` in `oas32`)
860900

861901
### When Working with AI Assistants
862902

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ The OpenAPI Specification has undergone 5 revisions since initial creation in 20
3535

3636
| Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes |
3737
|--------------------|--------------|-------------------------------------------------------------|-----------------------------------------------------------------------|
38+
| 5.32.0 | TBD | 2.0, 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.0.4, 3.1.0, 3.1.1, 3.1.2, 3.2.0 | [next release](https://github.com/swagger-api/swagger-ui/tree/master) |
3839
| 5.19.0 | 2025-02-17 | 2.0, 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.0.4, 3.1.0, 3.1.1, 3.1.2 | [tag v5.19.0](https://github.com/swagger-api/swagger-ui/tree/v5.19.0) |
3940
| 5.0.0 | 2023-06-12 | 2.0, 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.1.0 | [tag v5.0.0](https://github.com/swagger-api/swagger-ui/tree/v5.0.0) |
4041
| 4.0.0 | 2021-11-03 | 2.0, 3.0.0, 3.0.1, 3.0.2, 3.0.3 | [tag v4.0.0](https://github.com/swagger-api/swagger-ui/tree/v4.0.0) |

0 commit comments

Comments
 (0)