Skip to content

Commit c29e712

Browse files
authored
fix: align OpenAPI 3.x.y file uploads with specification (#10409)
Refs #9278
1 parent 22adad3 commit c29e712

File tree

19 files changed

+812
-200
lines changed

19 files changed

+812
-200
lines changed

src/core/config/defaults.js

+7
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ const defaultOptions = Object.freeze({
8787
onComplete: null,
8888
modelPropertyMacro: null,
8989
parameterMacro: null,
90+
91+
fileUploadMediaTypes: [
92+
"application/octet-stream",
93+
"image/",
94+
"audio/",
95+
"video/",
96+
],
9097
})
9198

9299
export default defaultOptions

src/core/config/type-cast/mappings.js

+4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ const mappings = {
4545
docExpansion: { typeCaster: stringTypeCaster },
4646
dom_id: { typeCaster: nullableStringTypeCaster },
4747
domNode: { typeCaster: domNodeTypeCaster },
48+
fileUploadMediaTypes: {
49+
typeCaster: arrayTypeCaster,
50+
defaultValue: defaultOptions.fileUploadMediaTypes,
51+
},
4852
filter: { typeCaster: filterTypeCaster },
4953
fn: { typeCaster: objectTypeCaster },
5054
initialState: { typeCaster: objectTypeCaster },

src/core/plugins/json-schema-2020-12/fn.js

+21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/**
22
* @prettier
33
*/
4+
import { List, Map } from "immutable"
5+
46
export const upperFirst = (value) => {
57
if (typeof value === "string") {
68
return `${value.charAt(0).toUpperCase()}${value.slice(1)}`
@@ -504,3 +506,22 @@ export const makeGetExtensionKeywords = (fnAccessor) => {
504506

505507
return getExtensionKeywords
506508
}
509+
510+
export const hasSchemaType = (schema, type) => {
511+
const isSchemaImmutable = Map.isMap(schema)
512+
513+
if (!isSchemaImmutable && !isPlainObject(schema)) {
514+
return false
515+
}
516+
517+
const hasType = (schemaType) =>
518+
type === schemaType || (Array.isArray(type) && type.includes(schemaType))
519+
520+
const schemaType = isSchemaImmutable ? schema.get("type") : schema.type
521+
522+
if (List.isList(schemaType) || Array.isArray(schemaType)) {
523+
return schemaType.some((t) => hasType(t))
524+
}
525+
526+
return hasType(schemaType)
527+
}

src/core/plugins/json-schema-2020-12/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
isBooleanJSONSchema,
5656
getSchemaKeywords,
5757
makeGetExtensionKeywords,
58+
hasSchemaType,
5859
} from "./fn"
5960
import { JSONSchemaPathContext, JSONSchemaLevelContext } from "./context"
6061
import {
@@ -143,6 +144,7 @@ const JSONSchema202012Plugin = ({ getSystem, fn }) => {
143144
useLevel,
144145
getSchemaKeywords,
145146
getExtensionKeywords: makeGetExtensionKeywords(fnAccessor),
147+
hasSchemaType,
146148
},
147149
},
148150
}

src/core/plugins/json-schema-5/components/json-schema-components.jsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,15 @@ export class JsonSchemaForm extends Component {
5151
const format = schema && schema.get ? schema.get("format") : null
5252
const type = schema && schema.get ? schema.get("type") : null
5353
const foldedType = fn.jsonSchema202012.foldType(immutableToJS(type))
54+
const isFileUploadIntended = fn.isFileUploadIntended(schema)
5455

5556
let getComponentSilently = (name) => getComponent(name, false, { failSilently: true })
5657
let Comp = type ? format ?
5758
getComponentSilently(`JsonSchema_${type}_${format}`) :
5859
getComponentSilently(`JsonSchema_${type}`) :
5960
getComponent("JsonSchema_string")
6061

61-
if (List.isList(type) && (foldedType === "array" || foldedType === "object")) {
62+
if (!isFileUploadIntended && List.isList(type) && (foldedType === "array" || foldedType === "object")) {
6263
Comp = getComponent("JsonSchema_object")
6364
}
6465

src/core/plugins/json-schema-5/fn.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @prettier
3+
*/
4+
import { Map } from "immutable"
5+
import isPlainObject from "lodash/isPlainObject"
6+
7+
export const hasSchemaType = (schema, type) => {
8+
const isSchemaImmutable = Map.isMap(schema)
9+
10+
if (!isSchemaImmutable && !isPlainObject(schema)) {
11+
return false
12+
}
13+
14+
const schemaType = isSchemaImmutable ? schema.get("type") : schema.type
15+
16+
return (
17+
type === schemaType || (Array.isArray(type) && type.includes(schemaType))
18+
)
19+
}

src/core/plugins/json-schema-5/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Schemes from "./components/schemes"
1414
import SchemesContainer from "./containers/schemes"
1515
import * as JSONSchemaComponents from "./components/json-schema-components"
1616
import { ModelExtensions } from "./components/model-extensions"
17+
import { hasSchemaType } from "./fn"
1718

1819
const JSONSchema5Plugin = () => ({
1920
components: {
@@ -31,6 +32,9 @@ const JSONSchema5Plugin = () => ({
3132
SchemesContainer,
3233
...JSONSchemaComponents,
3334
},
35+
fn: {
36+
hasSchemaType,
37+
},
3438
})
3539

3640
export default JSONSchema5Plugin

src/core/plugins/oas3/components/request-body.jsx

+14-15
Original file line numberDiff line numberDiff line change
@@ -105,21 +105,13 @@ const RequestBody = ({
105105
}
106106
requestBodyErrors = List.isList(requestBodyErrors) ? requestBodyErrors : List()
107107

108-
if(!mediaTypeValue.size) {
109-
return null
110-
}
111-
112-
const isObjectContent = mediaTypeValue.getIn(["schema", "type"]) === "object"
113-
const isBinaryFormat = mediaTypeValue.getIn(["schema", "format"]) === "binary"
114-
const isBase64Format = mediaTypeValue.getIn(["schema", "format"]) === "base64"
108+
const isFileUploadIntended = fn.isFileUploadIntended(
109+
mediaTypeValue?.get("schema"),
110+
contentType
111+
)
115112

116113
if(
117-
contentType === "application/octet-stream"
118-
|| contentType.indexOf("image/") === 0
119-
|| contentType.indexOf("audio/") === 0
120-
|| contentType.indexOf("video/") === 0
121-
|| isBinaryFormat
122-
|| isBase64Format
114+
isFileUploadIntended
123115
) {
124116
const Input = getComponent("Input")
125117

@@ -132,6 +124,13 @@ const RequestBody = ({
132124
return <Input type={"file"} onChange={handleFile} />
133125
}
134126

127+
128+
if (!mediaTypeValue.size) {
129+
return null
130+
}
131+
132+
const isObjectContent = fn.hasSchemaType(mediaTypeValue.get("schema"), "object")
133+
135134
if (
136135
isObjectContent &&
137136
(
@@ -190,11 +189,11 @@ const RequestBody = ({
190189
initialValue = JSON.parse(initialValue)
191190
}
192191

193-
const isFile = type === "string" && (format === "binary" || format === "base64")
192+
const isFileUploadIntended = fn.isFileUploadIntended(schema)
194193

195194
const jsonSchemaForm = <JsonSchemaForm
196195
fn={fn}
197-
dispatchInitialValue={!isFile}
196+
dispatchInitialValue={!isFileUploadIntended}
198197
schema={schema}
199198
description={key}
200199
getComponent={getComponent}

src/core/plugins/oas3/fn.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* @prettier
3+
*/
4+
import { Map } from "immutable"
5+
import isPlainObject from "lodash/isPlainObject"
6+
7+
export const makeIsFileUploadIntended = (getSystem) => {
8+
const isFileUploadIntended = (schema, mediaType = null) => {
9+
const { getConfigs, fn } = getSystem()
10+
const { fileUploadMediaTypes } = getConfigs()
11+
const isFileUploadMediaType =
12+
typeof mediaType === "string" &&
13+
fileUploadMediaTypes.some((fileUploadMediaType) =>
14+
mediaType.startsWith(fileUploadMediaType)
15+
)
16+
17+
if (isFileUploadMediaType) {
18+
return true
19+
}
20+
21+
const isSchemaImmutable = Map.isMap(schema)
22+
23+
if (!isSchemaImmutable && !isPlainObject(schema)) {
24+
return false
25+
}
26+
27+
const format = isSchemaImmutable ? schema.get("format") : schema.format
28+
29+
return (
30+
fn.hasSchemaType(schema, "string") && ["binary", "byte"].includes(format)
31+
)
32+
}
33+
34+
return isFileUploadIntended
35+
}

src/core/plugins/oas3/index.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import wrapComponents from "./wrap-components"
99
import * as actions from "./actions"
1010
import * as selectors from "./selectors"
1111
import reducers from "./reducers"
12+
import { makeIsFileUploadIntended } from "./fn"
13+
14+
export default function ({ getSystem }) {
15+
const isFileUploadIntended = makeIsFileUploadIntended(getSystem)
1216

13-
export default function () {
1417
return {
1518
components,
1619
wrapComponents,
@@ -28,5 +31,9 @@ export default function () {
2831
selectors: { ...selectors },
2932
},
3033
},
34+
fn: {
35+
isFileUploadIntended,
36+
isFileUploadIntendedOAS30: isFileUploadIntended,
37+
},
3138
}
3239
}

src/core/plugins/oas3/wrap-components/json-schema-string.jsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ export default OAS3ComponentWrapFactory(({ Ori, ...props }) => {
66
schema,
77
getComponent,
88
errors,
9-
onChange
9+
onChange,
10+
fn
1011
} = props
1112

12-
const format = schema && schema.get ? schema.get("format") : null
13-
const type = schema && schema.get ? schema.get("type") : null
13+
const isFileUploadIntended = fn.isFileUploadIntended(schema)
1414
const Input = getComponent("Input")
1515

16-
if(type && type === "string" && (format && (format === "binary" || format === "base64"))) {
16+
if (isFileUploadIntended) {
1717
return <Input type="file"
1818
className={ errors.length ? "invalid" : ""}
1919
title={ errors.length ? errors : ""}

src/core/plugins/oas31/after-load.js

+24
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getProperties,
77
} from "./json-schema-2020-12-extensions/fn"
88
import { wrapOAS31Fn } from "./fn"
9+
import { makeIsFileUploadIntended } from "./oas3-extensions/fn"
910

1011
function afterLoad({ fn, getSystem }) {
1112
// overrides for fn.jsonSchema202012
@@ -38,6 +39,29 @@ function afterLoad({ fn, getSystem }) {
3839

3940
Object.assign(this.fn, wrappedFns)
4041
}
42+
43+
// overrides behavior in OpenAPI 3.1.x, recognizes more intentions
44+
const isFileUploadIntended = makeIsFileUploadIntended(getSystem)
45+
const { isFileUploadIntended: isFileUploadIntendedWrap } = wrapOAS31Fn(
46+
{
47+
isFileUploadIntended,
48+
},
49+
getSystem()
50+
)
51+
52+
this.fn.isFileUploadIntended = isFileUploadIntendedWrap
53+
this.fn.isFileUploadIntendedOAS31 = isFileUploadIntended
54+
55+
if (fn.jsonSchema202012) {
56+
const { hasSchemaType } = wrapOAS31Fn(
57+
{
58+
hasSchemaType: fn.jsonSchema202012.hasSchemaType,
59+
},
60+
getSystem()
61+
)
62+
63+
this.fn.hasSchemaType = hasSchemaType
64+
}
4165
}
4266

4367
export default afterLoad
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* @prettier
3+
*/
4+
5+
export const makeIsFileUploadIntended = (getSystem) => {
6+
const isFileUploadIntended = (schema, mediaType = null) => {
7+
const { fn } = getSystem()
8+
9+
return fn.isFileUploadIntendedOAS30(schema, mediaType)
10+
}
11+
12+
return isFileUploadIntended
13+
}

0 commit comments

Comments
 (0)