Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dist/swagger-ui-bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/swagger-ui-es-bundle-core.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/swagger-ui-es-bundle-core.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/swagger-ui-es-bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/swagger-ui.css.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/swagger-ui.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/swagger-ui.js.map

Large diffs are not rendered by default.

16 changes: 12 additions & 4 deletions src/core/components/layout-utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export class Select extends React.Component {
allowEmptyValue: PropTypes.bool,
className: PropTypes.string,
disabled: PropTypes.bool,
title: PropTypes.string,
}

static defaultProps = {
Expand Down Expand Up @@ -194,13 +195,20 @@ export class Select extends React.Component {
let { allowedValues, multiple, allowEmptyValue, disabled } = this.props
let value = this.state.value?.toJS?.() || this.state.value

// Support for rich options with labels and descriptions
const hasRichOptions = allowedValues && allowedValues.length > 0 && typeof allowedValues[0] === "object"

return (
<select className={this.props.className} multiple={ multiple } value={value} onChange={ this.onChange } disabled={disabled} >
<select className={this.props.className} multiple={ multiple } value={value} onChange={ this.onChange } disabled={disabled} title={this.props.title} >
{ allowEmptyValue ? <option value="">--</option> : null }
{
allowedValues.map(function (item, key) {
return <option key={ key } value={ String(item) }>{ String(item) }</option>
})
hasRichOptions
? allowedValues.map(function (item, key) {
return <option key={ key } value={ String(item.value) } title={ item.description }>{ item.label || String(item.value) }</option>
})
: allowedValues.map(function (item, key) {
return <option key={ key } value={ String(item) }>{ String(item) }</option>
})
}
</select>
)
Expand Down
29 changes: 29 additions & 0 deletions src/core/components/parameter-row.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,9 +273,11 @@ export default class ParameterRow extends Component {

let paramItems // undefined
let paramEnum // undefined
let paramOneOfConst // undefined
let paramDefaultValue // undefined
let paramExample // undefined
let isDisplayParamEnum = false
let isDisplayOneOfConst = false

if ( param !== undefined && schema ) {
paramItems = schema.get("items")
Expand All @@ -286,6 +288,12 @@ export default class ParameterRow extends Component {
paramDefaultValue = paramItems.get("default")
} else if (schema) {
paramEnum = schema.get("enum")
// Check for oneOf with const values (OpenAPI 3.1 pattern)
const oneOf = schema.get("oneOf")
if (oneOf && oneOf.size && oneOf.every(subSchema => subSchema && subSchema.get && subSchema.get("const") !== undefined)) {
paramOneOfConst = oneOf
isDisplayOneOfConst = true
}
}

if ( paramEnum && paramEnum.size && paramEnum.size > 0) {
Expand Down Expand Up @@ -346,6 +354,27 @@ export default class ParameterRow extends Component {
: null
}

{ (bodyParam || !isExecute) && isDisplayOneOfConst ?
<div className="parameter__enum">
<i>Available values</i> :
<ul>
{paramOneOfConst.map((subSchema, index) => {
const constValue = subSchema.get("const")
const title = subSchema.get("title")
const description = subSchema.get("description")
return (
<li key={index}>
<code>{String(constValue)}</code>
{title ? <span> - {title}</span> : null}
{description ? <span>: {description}</span> : null}
</li>
)
}).toArray()}
</ul>
</div>
: null
}

{ (bodyParam || !isExecute) && paramDefaultValue !== undefined ?
<Markdown className="parameter__default" source={"<i>Default value</i> : " + paramDefaultValue}/>
: null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,39 @@ import DebounceInput from "react-debounce-input"
import { stringify, isImmutable } from "core/utils"

const noop = ()=> {}

/**
* Detects if a schema uses oneOf with const values (OpenAPI 3.1 pattern for enums with descriptions)
* @param {Immutable.Map} schema - the schema
* @returns {boolean} - true if schema uses oneOf with const values
*/
const hasOneOfConst = (schema) => {
const oneOf = schema && schema.get ? schema.get("oneOf") : null
if (!oneOf || !oneOf.size) return false

return oneOf.every((subSchema) => subSchema && subSchema.get && subSchema.get("const") !== undefined)
}

/**
* Extracts const values and their descriptions from a oneOf array
* @param {Immutable.List} oneOf - the oneOf array
* @returns {Array<{value: *, label: string, description: string}>} - array of options with descriptions
*/
const getOneOfConstOptions = (oneOf) => {
if (!oneOf || !oneOf.size) return []

return oneOf.map((subSchema) => {
const value = subSchema.get("const")
const title = subSchema.get("title")
const description = subSchema.get("description")

return {
value,
label: title || String(value),
description: description || ""
}
}).toJS()
}
const JsonSchemaPropShape = {
getComponent: PropTypes.func.isRequired,
value: PropTypes.any,
Expand Down Expand Up @@ -80,6 +113,8 @@ export class JsonSchema_string extends Component {
render() {
let { getComponent, value, schema, errors, required, description, disabled } = this.props
const enumValue = schema && schema.get ? schema.get("enum") : null
const oneOf = schema && schema.get ? schema.get("oneOf") : null
const isOneOfConst = hasOneOfConst(schema)
const format = schema && schema.get ? schema.get("format") : null
const type = schema && schema.get ? schema.get("type") : null
const schemaIn = schema && schema.get ? schema.get("in") : null
Expand All @@ -102,6 +137,18 @@ export class JsonSchema_string extends Component {
onChange={ this.onEnumChange }/>)
}

if (isOneOfConst) {
const Select = getComponent("Select")
const options = getOneOfConstOptions(oneOf)
return (<Select className={ errors.length ? "invalid" : ""}
title={ errors.length ? errors : ""}
allowedValues={ options }
value={ value }
allowEmptyValue={ !required }
disabled={disabled}
onChange={ this.onEnumChange }/>)
}

const isDisabled = disabled || (schemaIn && schemaIn === "formData" && !("FormData" in window))
const Input = getComponent("Input")
if (type && type === "file") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* @prettier
*/

describe("Parameter with oneOf + const pattern (enum with descriptions) in OpenAPI 3.1.0", () => {
beforeEach(() => {
cy.visit(
"/?url=/documents/features/parameter-oneof-const-descriptions.yaml"
)
cy.get("#operations-default-get_test").click()
})

it("should render dropdown for oneOf with const values in Try it out mode", () => {
// Enable try it out
cy.get(".try-out__btn").click()

// Check that the cvv_check parameter has a select dropdown
cy.get("[data-param-name='cvv_check']")
.closest("tr")
.find("select")
.should("exist")
.find("option")
.should("have.length", 9) // 8 values + empty option

// Check that the dropdown contains the const values
cy.get("[data-param-name='cvv_check']")
.closest("tr")
.find("select")
.find("option[value='D']")
.should("exist")
})

it("should display descriptions for oneOf with const values in read-only mode", () => {
// Check that the descriptions are displayed in read-only mode
cy.get("[data-param-name='cvv_check']")
.closest("tr")
.find(".parameter__enum")
.should("exist")
.should("contain", "Suspicious transaction")
.should("contain", "Match")
.should("contain", "No Match")
})

it("should display titles for oneOf with const values when available", () => {
// Check that titles are displayed for the status parameter
cy.get("[data-param-name='status']")
.closest("tr")
.find(".parameter__enum")
.should("exist")
.should("contain", "Pending")
.should("contain", "Processing")
.should("contain", "Completed")
.should("contain", "Cancelled")
})

it("should show dropdown with titles as labels in Try it out mode", () => {
// Enable try it out
cy.get(".try-out__btn").click()

// Check that the status parameter dropdown shows titles as labels
cy.get("[data-param-name='status']")
.closest("tr")
.find("select")
.should("exist")
.find("option")
.contains("Pending")
.should("exist")

cy.get("[data-param-name='status']")
.closest("tr")
.find("select")
.find("option")
.contains("Completed")
.should("exist")
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
openapi: 3.1.0
info:
title: Enum with Descriptions Test API
version: 1.0.0
paths:
/test:
get:
summary: Test endpoint for oneOf with const enum pattern
parameters:
- name: cvv_check
in: query
description: When processed, result from checking the CVV/CVC value on the transaction.
schema:
type: string
title: CVV check
oneOf:
- const: D
description: Suspicious transaction
- const: I
description: Failed data validation check
- const: M
description: Match
- const: N
description: No Match
- const: P
description: Not Processed
- const: S
description: Should have been present
- const: U
description: Issuer unable to process request
- const: X
description: Card does not support verification
- name: status
in: query
description: Status of the order
schema:
type: string
oneOf:
- const: pending
title: Pending
description: Order is awaiting processing
- const: processing
title: Processing
description: Order is being processed
- const: completed
title: Completed
description: Order has been completed
- const: cancelled
title: Cancelled
description: Order has been cancelled
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
message:
type: string