Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugins): Show multipart/form-data example and schema #10166

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
8 changes: 4 additions & 4 deletions src/core/components/parameters/parameters.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ export default class Parameters extends Component {
})
}
}

onChangeMediaType = ({ value, pathMethod }) => {
let { specActions, oas3Selectors, oas3Actions } = this.props
const userHasEditedBody = oas3Selectors.hasUserEditedBody(...pathMethod)
const shouldRetainRequestBodyValue = oas3Selectors.shouldRetainRequestBodyValue(...pathMethod)
oas3Actions.setRequestContentType({ value, pathMethod })
oas3Actions.initRequestBodyValidateError({ pathMethod })
if (!userHasEditedBody) {
if(!shouldRetainRequestBodyValue) {
if (!shouldRetainRequestBodyValue) {
oas3Actions.setRequestBodyValue({ value: undefined, pathMethod })
}
specActions.clearResponse(...pathMethod)
Expand Down Expand Up @@ -166,7 +166,7 @@ export default class Parameters extends Component {
enabled={tryItOutEnabled}
onCancelClick={this.props.onCancelClick}
onTryoutClick={onTryoutClick}
onResetClick={() => onResetClick(pathMethod)}/>
onResetClick={() => onResetClick(pathMethod)} />
) : null}
</div>
{this.state.parametersVisible ? <div className="parameters-container">
Expand Down Expand Up @@ -226,7 +226,7 @@ export default class Parameters extends Component {
this.onChangeMediaType({ value, pathMethod })
}}
className="body-param-content-type"
ariaLabel="Request content type"
ariaLabel="Request content type"
controlId={controlId}
/>
</label>
Expand Down
195 changes: 108 additions & 87 deletions src/core/plugins/oas3/components/request-body.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,50 +15,49 @@ export const getDefaultRequestBodyValue = (requestBody, mediaType, activeExample
? mediaTypeValue.getIn([
"examples",
activeExamplesKey,
"value"
"value",
])
: exampleSchema

const exampleValue = fn.getSampleSchema(
schema,
mediaType,
{
includeWriteOnly: true
includeWriteOnly: true,
},
mediaTypeExample
mediaTypeExample,
)
return stringify(exampleValue)
}



const RequestBody = ({
userHasEditedBody,
requestBody,
requestBodyValue,
requestBodyInclusionSetting,
requestBodyErrors,
getComponent,
getConfigs,
specSelectors,
fn,
contentType,
isExecute,
specPath,
onChange,
onChangeIncludeEmpty,
activeExamplesKey,
updateActiveExamplesKey,
setRetainRequestBodyValueFlag
}) => {
userHasEditedBody,
requestBody,
requestBodyValue,
requestBodyInclusionSetting,
requestBodyErrors,
getComponent,
getConfigs,
specSelectors,
fn,
contentType,
isExecute,
specPath,
onChange,
onChangeIncludeEmpty,
activeExamplesKey,
updateActiveExamplesKey,
setRetainRequestBodyValueFlag,
}) => {
const handleFile = (e) => {
onChange(e.target.files[0])
}
const setIsIncludedOptions = (key) => {
let options = {
key,
shouldDispatchInit: false,
defaultValue: true
defaultValue: true,
}
let currentInclusion = requestBodyInclusionSetting.get(key, "no value")
if (currentInclusion === "no value") {
Expand Down Expand Up @@ -87,7 +86,7 @@ const RequestBody = ({
const rawExamplesOfMediaType = mediaTypeValue.get("examples", null)
const sampleForMediaType = rawExamplesOfMediaType?.map((container, key) => {
const val = container?.get("value", null)
if(val) {
if (val) {
container = container.set("value", getDefaultRequestBodyValue(
requestBody,
contentType,
Expand All @@ -103,15 +102,15 @@ const RequestBody = ({
}
requestBodyErrors = List.isList(requestBodyErrors) ? requestBodyErrors : List()

if(!mediaTypeValue.size) {
if (!mediaTypeValue.size) {
return null
}

const isObjectContent = mediaTypeValue.getIn(["schema", "type"]) === "object"
const isBinaryFormat = mediaTypeValue.getIn(["schema", "format"]) === "binary"
const isBase64Format = mediaTypeValue.getIn(["schema", "format"]) === "base64"

if(
if (
contentType === "application/octet-stream"
|| contentType.indexOf("image/") === 0
|| contentType.indexOf("audio/") === 0
Expand All @@ -121,7 +120,7 @@ const RequestBody = ({
) {
const Input = getComponent("Input")

if(!isExecute) {
if (!isExecute) {
return <i>
Example values are not available for <code>{contentType}</code> media types.
</i>
Expand All @@ -130,11 +129,12 @@ const RequestBody = ({
return <Input type={"file"} onChange={handleFile} />
}

const isContentTypeMultipart = contentType.indexOf("multipart/") === 0
if (
isObjectContent &&
(
contentType === "application/x-www-form-urlencoded" ||
contentType.indexOf("multipart/") === 0
isContentTypeMultipart
) &&
schemaForMediaType.get("properties", OrderedMap()).size > 0
) {
Expand All @@ -144,67 +144,71 @@ const RequestBody = ({
requestBodyValue = Map.isMap(requestBodyValue) ? requestBodyValue : OrderedMap()

return <div className="table-container">
{ requestBodyDescription &&
{requestBodyDescription &&
<Markdown source={requestBodyDescription} />
}
<table>
<tbody>
{
Map.isMap(bodyProperties) && bodyProperties.entrySeq().map(([key, schema]) => {
if (schema.get("readOnly")) return
{
Map.isMap(bodyProperties) && bodyProperties.entrySeq().map(([key, schema]) => {
if (schema.get("readOnly")) return

const oneOf = schema.get("oneOf")?.get(0)?.toJS()
const anyOf = schema.get("anyOf")?.get(0)?.toJS()
schema = fromJS(fn.mergeJsonSchema(schema.toJS(), oneOf ?? anyOf ?? {}))
const oneOf = schema.get("oneOf")?.get(0)?.toJS()
const anyOf = schema.get("anyOf")?.get(0)?.toJS()
schema = fromJS(fn.mergeJsonSchema(schema.toJS(), oneOf ?? anyOf ?? {}))

let commonExt = showCommonExtensions ? getCommonExtensions(schema) : null
const required = schemaForMediaType.get("required", List()).includes(key)
const type = schema.get("type")
const format = schema.get("format")
const description = schema.get("description")
const currentValue = requestBodyValue.getIn([key, "value"])
const currentErrors = requestBodyValue.getIn([key, "errors"]) || requestBodyErrors
const included = requestBodyInclusionSetting.get(key) || false
let commonExt = showCommonExtensions ? getCommonExtensions(schema) : null
const required = schemaForMediaType.get("required", List()).includes(key)
const type = schema.get("type")
const format = schema.get("format")
const description = schema.get("description")
const currentValue = requestBodyValue.getIn([key, "value"])
const currentErrors = requestBodyValue.getIn([key, "errors"]) || requestBodyErrors
const included = requestBodyInclusionSetting.get(key) || false

let initialValue = fn.getSampleSchema(schema, false, {
includeWriteOnly: true,
})

let initialValue = fn.getSampleSchema(schema, false, {
includeWriteOnly: true
})
if (initialValue === false) {
initialValue = "false"
}

if (initialValue === false) {
initialValue = "false"
}
if (initialValue === 0) {
initialValue = "0"
}

if (initialValue === 0) {
initialValue = "0"
}
if (typeof initialValue !== "string" && type === "object") {
initialValue = stringify(initialValue)
}

if (typeof initialValue !== "string" && type === "object") {
initialValue = stringify(initialValue)
}
if (typeof initialValue === "string" && type === "array") {
initialValue = JSON.parse(initialValue)
}

if (typeof initialValue === "string" && type === "array") {
initialValue = JSON.parse(initialValue)
}
const isFile = type === "string" && (format === "binary" || format === "base64")

const isFile = type === "string" && (format === "binary" || format === "base64")
const schemaPartForKey = mediaTypeValue
.get("schema")
.update("properties", (properties) => properties.filter((v, k) => k === key))

return <tr key={key} className="parameters" data-property-name={key}>
return <tr key={key} className="parameters" data-property-name={key}>
<td className="parameters-col_name">
<div className={required ? "parameter__name required" : "parameter__name"}>
{ key }
{ !required ? null : <span>&nbsp;*</span> }
{key}
{!required ? null : <span>&nbsp;*</span>}
</div>
<div className="parameter__type">
{ type }
{ format && <span className="prop-format">(${format})</span>}
{type}
{format && <span className="prop-format">(${format})</span>}
{!showCommonExtensions || !commonExt.size ? null : commonExt.entrySeq().map(([key, v]) => <ParameterExt key={`${key}-${v}`} xKey={key} xVal={v} />)}
</div>
<div className="parameter__deprecated">
{ schema.get("deprecated") ? "deprecated": null }
{schema.get("deprecated") ? "deprecated" : null}
</div>
</td>
<td className="parameters-col_description">
<Markdown source={ description }></Markdown>
<Markdown source={description}></Markdown>
{isExecute ? <div>
<JsonSchemaForm
fn={fn}
Expand All @@ -213,8 +217,8 @@ const RequestBody = ({
description={key}
getComponent={getComponent}
value={currentValue === undefined ? initialValue : currentValue}
required = { required }
errors = { currentErrors }
required={required}
errors={currentErrors}
onChange={(value) => {
onChange(value, [key])
}}
Expand All @@ -227,11 +231,28 @@ const RequestBody = ({
isDisabled={Array.isArray(currentValue) ? currentValue.length !== 0 : !isEmptyValue(currentValue)}
/>
)}
</div> : null }
</div> : null}
{!isExecute && isContentTypeMultipart && type === "object" ? (
<ModelExample
getComponent={getComponent}
getConfigs={getConfigs}
specSelectors={specSelectors}
expandDepth={1}
isExecute={false}
schema={schemaPartForKey}
specPath={specPath.push("content", contentType)}
example={
<HighlightCode className="body-param__example" language={"json"}>
{initialValue}
</HighlightCode>
}
includeWriteOnly={true}
/>
) : null}
</td>
</tr>
})
}
</tr>
})
}
</tbody>
</table>
</div>
Expand All @@ -250,22 +271,22 @@ const RequestBody = ({
}

return <div>
{ requestBodyDescription &&
{requestBodyDescription &&
<Markdown source={requestBodyDescription} />
}
{
sampleForMediaType ? (
<ExamplesSelectValueRetainer
userHasEditedBody={userHasEditedBody}
examples={sampleForMediaType}
currentKey={activeExamplesKey}
currentUserInputValue={requestBodyValue}
onSelect={handleExamplesSelect}
updateValue={onChange}
defaultToFirstExample={true}
getComponent={getComponent}
setRetainRequestBodyValueFlag={setRetainRequestBodyValueFlag}
/>
userHasEditedBody={userHasEditedBody}
examples={sampleForMediaType}
currentKey={activeExamplesKey}
currentUserInputValue={requestBodyValue}
onSelect={handleExamplesSelect}
updateValue={onChange}
defaultToFirstExample={true}
getComponent={getComponent}
setRetainRequestBodyValueFlag={setRetainRequestBodyValueFlag}
/>
) : null
}
{
Expand All @@ -281,9 +302,9 @@ const RequestBody = ({
</div>
) : (
<ModelExample
getComponent={ getComponent }
getConfigs={ getConfigs }
specSelectors={ specSelectors }
getComponent={getComponent}
getConfigs={getConfigs}
specSelectors={specSelectors}
expandDepth={1}
isExecute={isExecute}
schema={mediaTypeValue.get("schema")}
Expand Down Expand Up @@ -327,7 +348,7 @@ RequestBody.propTypes = {
activeExamplesKey: PropTypes.string,
updateActiveExamplesKey: PropTypes.func,
setRetainRequestBodyValueFlag: PropTypes.func,
oas3Actions: PropTypes.object.isRequired
oas3Actions: PropTypes.object.isRequired,
}

export default RequestBody
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,29 @@ describe("OAS3 default views", () => {
.get(".parameters-col_description textarea")
.should("contains.text", "\"stuff\": \"string\"")
})

it("should display calculated object string as example (#4581, #5169, #9756)", () => {
cy.visit(
"/?url=/documents/features/request-body/multipart/default-views.yaml",
)
.get("#operations-default-post_test")
.click()
// Show example
.get(".parameters-col_description code")
.should("contains.text", "\"stuff\": \"string\"")
// Switch to schema
.get(".parameters-col_description")
.contains("Schema")
.click()
.get(".parameters-col_description")
.should("contains.text", "parameters")
.should("not.contain.text", "file")
.should("contains.text", "TestBody")
// Expand Try It Out to hide example
.get(".try-out__btn")
.click()
.get(".parameters-col_description textarea")
.should("contains.text", "\"stuff\": \"string\"")
})
})
})