diff --git a/src/core/config/defaults.js b/src/core/config/defaults.js index b7daa8fd624..12ce768b11e 100644 --- a/src/core/config/defaults.js +++ b/src/core/config/defaults.js @@ -87,6 +87,13 @@ const defaultOptions = Object.freeze({ onComplete: null, modelPropertyMacro: null, parameterMacro: null, + + fileUploadMediaTypes: [ + "application/octet-stream", + "image/", + "audio/", + "video/", + ], }) export default defaultOptions diff --git a/src/core/config/type-cast/mappings.js b/src/core/config/type-cast/mappings.js index b025fb1295e..559f35025c2 100644 --- a/src/core/config/type-cast/mappings.js +++ b/src/core/config/type-cast/mappings.js @@ -45,6 +45,10 @@ const mappings = { docExpansion: { typeCaster: stringTypeCaster }, dom_id: { typeCaster: nullableStringTypeCaster }, domNode: { typeCaster: domNodeTypeCaster }, + fileUploadMediaTypes: { + typeCaster: arrayTypeCaster, + defaultValue: defaultOptions.fileUploadMediaTypes, + }, filter: { typeCaster: filterTypeCaster }, fn: { typeCaster: objectTypeCaster }, initialState: { typeCaster: objectTypeCaster }, diff --git a/src/core/plugins/json-schema-2020-12/fn.js b/src/core/plugins/json-schema-2020-12/fn.js index 1e6626e4c56..d07f7acd4e0 100644 --- a/src/core/plugins/json-schema-2020-12/fn.js +++ b/src/core/plugins/json-schema-2020-12/fn.js @@ -1,6 +1,8 @@ /** * @prettier */ +import { List, Map } from "immutable" + export const upperFirst = (value) => { if (typeof value === "string") { return `${value.charAt(0).toUpperCase()}${value.slice(1)}` @@ -504,3 +506,22 @@ export const makeGetExtensionKeywords = (fnAccessor) => { return getExtensionKeywords } + +export const hasSchemaType = (schema, type) => { + const isSchemaImmutable = Map.isMap(schema) + + if (!isSchemaImmutable && !isPlainObject(schema)) { + return false + } + + const hasType = (schemaType) => + type === schemaType || (Array.isArray(type) && type.includes(schemaType)) + + const schemaType = isSchemaImmutable ? schema.get("type") : schema.type + + if (List.isList(schemaType) || Array.isArray(schemaType)) { + return schemaType.some((t) => hasType(t)) + } + + return hasType(schemaType) +} diff --git a/src/core/plugins/json-schema-2020-12/index.js b/src/core/plugins/json-schema-2020-12/index.js index e6c929d9fd9..c78d89dc7d2 100644 --- a/src/core/plugins/json-schema-2020-12/index.js +++ b/src/core/plugins/json-schema-2020-12/index.js @@ -55,6 +55,7 @@ import { isBooleanJSONSchema, getSchemaKeywords, makeGetExtensionKeywords, + hasSchemaType, } from "./fn" import { JSONSchemaPathContext, JSONSchemaLevelContext } from "./context" import { @@ -143,6 +144,7 @@ const JSONSchema202012Plugin = ({ getSystem, fn }) => { useLevel, getSchemaKeywords, getExtensionKeywords: makeGetExtensionKeywords(fnAccessor), + hasSchemaType, }, }, } diff --git a/src/core/plugins/json-schema-5/components/json-schema-components.jsx b/src/core/plugins/json-schema-5/components/json-schema-components.jsx index 1899ffcdf68..6575a067dcd 100644 --- a/src/core/plugins/json-schema-5/components/json-schema-components.jsx +++ b/src/core/plugins/json-schema-5/components/json-schema-components.jsx @@ -51,6 +51,7 @@ export class JsonSchemaForm extends Component { const format = schema && schema.get ? schema.get("format") : null const type = schema && schema.get ? schema.get("type") : null const foldedType = fn.jsonSchema202012.foldType(immutableToJS(type)) + const isFileUploadIntended = fn.isFileUploadIntended(schema) let getComponentSilently = (name) => getComponent(name, false, { failSilently: true }) let Comp = type ? format ? @@ -58,7 +59,7 @@ export class JsonSchemaForm extends Component { getComponentSilently(`JsonSchema_${type}`) : getComponent("JsonSchema_string") - if (List.isList(type) && (foldedType === "array" || foldedType === "object")) { + if (!isFileUploadIntended && List.isList(type) && (foldedType === "array" || foldedType === "object")) { Comp = getComponent("JsonSchema_object") } diff --git a/src/core/plugins/json-schema-5/fn.js b/src/core/plugins/json-schema-5/fn.js new file mode 100644 index 00000000000..c4844b4bcba --- /dev/null +++ b/src/core/plugins/json-schema-5/fn.js @@ -0,0 +1,19 @@ +/** + * @prettier + */ +import { Map } from "immutable" +import isPlainObject from "lodash/isPlainObject" + +export const hasSchemaType = (schema, type) => { + const isSchemaImmutable = Map.isMap(schema) + + if (!isSchemaImmutable && !isPlainObject(schema)) { + return false + } + + const schemaType = isSchemaImmutable ? schema.get("type") : schema.type + + return ( + type === schemaType || (Array.isArray(type) && type.includes(schemaType)) + ) +} diff --git a/src/core/plugins/json-schema-5/index.js b/src/core/plugins/json-schema-5/index.js index 3aa3e375d78..f3e394a1699 100644 --- a/src/core/plugins/json-schema-5/index.js +++ b/src/core/plugins/json-schema-5/index.js @@ -14,6 +14,7 @@ import Schemes from "./components/schemes" import SchemesContainer from "./containers/schemes" import * as JSONSchemaComponents from "./components/json-schema-components" import { ModelExtensions } from "./components/model-extensions" +import { hasSchemaType } from "./fn" const JSONSchema5Plugin = () => ({ components: { @@ -31,6 +32,9 @@ const JSONSchema5Plugin = () => ({ SchemesContainer, ...JSONSchemaComponents, }, + fn: { + hasSchemaType, + }, }) export default JSONSchema5Plugin diff --git a/src/core/plugins/oas3/components/request-body.jsx b/src/core/plugins/oas3/components/request-body.jsx index 7c80040be50..d7065ee04d0 100644 --- a/src/core/plugins/oas3/components/request-body.jsx +++ b/src/core/plugins/oas3/components/request-body.jsx @@ -105,21 +105,13 @@ const RequestBody = ({ } requestBodyErrors = List.isList(requestBodyErrors) ? requestBodyErrors : List() - 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" + const isFileUploadIntended = fn.isFileUploadIntended( + mediaTypeValue?.get("schema"), + contentType + ) if( - contentType === "application/octet-stream" - || contentType.indexOf("image/") === 0 - || contentType.indexOf("audio/") === 0 - || contentType.indexOf("video/") === 0 - || isBinaryFormat - || isBase64Format + isFileUploadIntended ) { const Input = getComponent("Input") @@ -132,6 +124,13 @@ const RequestBody = ({ return } + + if (!mediaTypeValue.size) { + return null + } + + const isObjectContent = fn.hasSchemaType(mediaTypeValue.get("schema"), "object") + if ( isObjectContent && ( @@ -190,11 +189,11 @@ const RequestBody = ({ initialValue = JSON.parse(initialValue) } - const isFile = type === "string" && (format === "binary" || format === "base64") + const isFileUploadIntended = fn.isFileUploadIntended(schema) const jsonSchemaForm = { + const isFileUploadIntended = (schema, mediaType = null) => { + const { getConfigs, fn } = getSystem() + const { fileUploadMediaTypes } = getConfigs() + const isFileUploadMediaType = + typeof mediaType === "string" && + fileUploadMediaTypes.some((fileUploadMediaType) => + mediaType.startsWith(fileUploadMediaType) + ) + + if (isFileUploadMediaType) { + return true + } + + const isSchemaImmutable = Map.isMap(schema) + + if (!isSchemaImmutable && !isPlainObject(schema)) { + return false + } + + const format = isSchemaImmutable ? schema.get("format") : schema.format + + return ( + fn.hasSchemaType(schema, "string") && ["binary", "byte"].includes(format) + ) + } + + return isFileUploadIntended +} diff --git a/src/core/plugins/oas3/index.js b/src/core/plugins/oas3/index.js index 7d63ce0e6a0..9a42d9e2ae1 100644 --- a/src/core/plugins/oas3/index.js +++ b/src/core/plugins/oas3/index.js @@ -9,8 +9,11 @@ import wrapComponents from "./wrap-components" import * as actions from "./actions" import * as selectors from "./selectors" import reducers from "./reducers" +import { makeIsFileUploadIntended } from "./fn" + +export default function ({ getSystem }) { + const isFileUploadIntended = makeIsFileUploadIntended(getSystem) -export default function () { return { components, wrapComponents, @@ -28,5 +31,9 @@ export default function () { selectors: { ...selectors }, }, }, + fn: { + isFileUploadIntended, + isFileUploadIntendedOAS30: isFileUploadIntended, + }, } } diff --git a/src/core/plugins/oas3/wrap-components/json-schema-string.jsx b/src/core/plugins/oas3/wrap-components/json-schema-string.jsx index 96534b0f20b..86469396e83 100644 --- a/src/core/plugins/oas3/wrap-components/json-schema-string.jsx +++ b/src/core/plugins/oas3/wrap-components/json-schema-string.jsx @@ -6,14 +6,14 @@ export default OAS3ComponentWrapFactory(({ Ori, ...props }) => { schema, getComponent, errors, - onChange + onChange, + fn } = props - const format = schema && schema.get ? schema.get("format") : null - const type = schema && schema.get ? schema.get("type") : null + const isFileUploadIntended = fn.isFileUploadIntended(schema) const Input = getComponent("Input") - if(type && type === "string" && (format && (format === "binary" || format === "base64"))) { + if (isFileUploadIntended) { return { + const isFileUploadIntended = (schema, mediaType = null) => { + const { fn } = getSystem() + + return fn.isFileUploadIntendedOAS30(schema, mediaType) + } + + return isFileUploadIntended +} diff --git a/test/e2e-cypress/e2e/features/plugins/oas3/request-body-upload-file.cy.js b/test/e2e-cypress/e2e/features/plugins/oas3/request-body-upload-file.cy.js new file mode 100644 index 00000000000..c5827b59432 --- /dev/null +++ b/test/e2e-cypress/e2e/features/plugins/oas3/request-body-upload-file.cy.js @@ -0,0 +1,189 @@ +/** + * @prettier + */ + +describe("OpenAPI 3.0 Request Body upload file button", () => { + beforeEach(() => { + cy.visit("/?url=/documents/features/oas3-request-body-upload-file.yaml") + }) + + describe("application/octet-stream", () => { + beforeEach(() => { + cy.get("#operations-default-uploadApplicationOctetStream").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for application/octet-stream media types." + ) + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("image/png", () => { + beforeEach(() => { + cy.get("#operations-default-uploadImagePng").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for image/png media types." + ) + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("audio/wav", () => { + beforeEach(() => { + cy.get("#operations-default-uploadAudioWav").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for audio/wav media types." + ) + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("video/mpeg", () => { + beforeEach(() => { + cy.get("#operations-default-uploadVideoMpeg").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for video/mpeg media types." + ) + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("application/octet-stream with empty Media Type Object", () => { + beforeEach(() => { + cy.get("#operations-default-uploadApplicationOctetStreamEmpty").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for application/octet-stream media types." + ) + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("schema type string and format binary", () => { + beforeEach(() => { + cy.get("#operations-default-uploadSchemaFormatBinary").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for application/x-custom media types." + ) + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("schema type string and format byte", () => { + beforeEach(() => { + cy.get("#operations-default-uploadSchemaFormatByte").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for application/x-custom media types." + ) + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("multipart/form-data object property with schema type string and format binary", () => { + beforeEach(() => { + cy.get("#operations-default-uploadPropertySchemaFormatBinary").click() + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("multipart/form-data object property with schema type string and format byte", () => { + beforeEach(() => { + cy.get("#operations-default-uploadPropertySchemaFormatByte").click() + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) +}) diff --git a/test/e2e-cypress/e2e/features/plugins/oas31/oas31-request-body-upload-file.cy.js b/test/e2e-cypress/e2e/features/plugins/oas31/oas31-request-body-upload-file.cy.js new file mode 100644 index 00000000000..4d1f9a254aa --- /dev/null +++ b/test/e2e-cypress/e2e/features/plugins/oas31/oas31-request-body-upload-file.cy.js @@ -0,0 +1,257 @@ +/** + * @prettier + */ + +describe("OpenAPI 3.1 Request Body upload file button", () => { + beforeEach(() => { + cy.visit("/?url=/documents/features/oas31-request-body-upload-file.yaml") + }) + + describe("application/octet-stream", () => { + beforeEach(() => { + cy.get("#operations-default-uploadApplicationOctetStream").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for application/octet-stream media types." + ) + }) + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("image/png", () => { + beforeEach(() => { + cy.get("#operations-default-uploadImagePng").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for image/png media types." + ) + }) + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("audio/wav", () => { + beforeEach(() => { + cy.get("#operations-default-uploadAudioWav").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for audio/wav media types." + ) + }) + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("video/mpeg", () => { + beforeEach(() => { + cy.get("#operations-default-uploadVideoMpeg").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for video/mpeg media types." + ) + }) + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("application/octet-stream with empty Media Type Object", () => { + beforeEach(() => { + cy.get("#operations-default-uploadApplicationOctetStreamEmpty").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for application/octet-stream media types." + ) + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("schema type string and format binary", () => { + beforeEach(() => { + cy.get("#operations-default-uploadSchemaTypeFormatBinary").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for application/x-custom media types." + ) + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("schema type string and format byte", () => { + beforeEach(() => { + cy.get("#operations-default-uploadSchemaTypeFormatByte").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for application/x-custom media types." + ) + }) + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("schema union type includes string and format binary", () => { + beforeEach(() => { + cy.get("#operations-default-uploadSchemaUnionTypeFormatBinary").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for application/x-custom media types." + ) + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("schema union type includes string and format byte", () => { + beforeEach(() => { + cy.get("#operations-default-uploadSchemaUnionTypeFormatByte").click() + }) + + it("should display description with the correct content type", () => { + cy.get( + ".opblock-section-request-body .opblock-description-wrapper i" + ).should( + "have.text", + "Example values are not available for application/x-custom media types." + ) + }) + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("multipart/form-data object property with schema type string and format binary", () => { + beforeEach(() => { + cy.get("#operations-default-uploadPropertySchemaFormatBinary").click() + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("multipart/form-data object property with schema type string and format byte", () => { + beforeEach(() => { + cy.get("#operations-default-uploadPropertySchemaFormatByte").click() + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("multipart/form-data object property with schema union type including string and format binary", () => { + beforeEach(() => { + cy.get( + "#operations-default-uploadPropertySchemaUnionTypeFormatBinary" + ).click() + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) + + describe("multipart/form-data object property with schema union type including string and format byte", () => { + beforeEach(() => { + cy.get( + "#operations-default-uploadPropertySchemaUnionTypeFormatByte" + ).click() + }) + + it("should display a file upload button", () => { + cy.get(".try-out__btn").click() + cy.get( + ".opblock-section-request-body .opblock-description-wrapper input" + ).should("have.prop", "type", "file") + }) + }) +}) diff --git a/test/e2e-cypress/e2e/features/request-body-upload-file.cy.js b/test/e2e-cypress/e2e/features/request-body-upload-file.cy.js deleted file mode 100644 index bb0819f92dc..00000000000 --- a/test/e2e-cypress/e2e/features/request-body-upload-file.cy.js +++ /dev/null @@ -1,132 +0,0 @@ -/** - * @prettier - */ - -describe("OpenAPI 3.0 Request Body upload file button", () => { - describe("application/octet-stream", () => { - it("should display description with the correct content type", () => { - cy.visit("/?url=/documents/features/request-body-upload-file.yaml") - .get("#operations-default-uploadApplicationOctetStream") - .click() - .get(".opblock-section-request-body .opblock-description-wrapper i") - .should( - "have.text", - "Example values are not available for application/octet-stream media types." - ) - }) - it("should display a file upload button", () => { - cy.visit("/?url=/documents/features/request-body-upload-file.yaml") - .get("#operations-default-uploadApplicationOctetStream") - .click() - .get(".try-out__btn") - .click() - .get(".opblock-section-request-body .opblock-description-wrapper input") - .should("have.prop", "type", "file") - }) - }) - describe("image/png", () => { - it("should display description with the correct content type", () => { - cy.visit("/?url=/documents/features/request-body-upload-file.yaml") - .get("#operations-default-uploadImagePng") - .click() - .get(".opblock-section-request-body .opblock-description-wrapper i") - .should( - "have.text", - "Example values are not available for image/png media types." - ) - }) - it("should display a file upload button", () => { - cy.visit("/?url=/documents/features/request-body-upload-file.yaml") - .get("#operations-default-uploadApplicationOctetStream") - .click() - .get(".try-out__btn") - .click() - .get(".opblock-section-request-body .opblock-description-wrapper input") - .should("have.prop", "type", "file") - }) - }) - describe("audio/wav", () => { - it("should display description with the correct content type", () => { - cy.visit("/?url=/documents/features/request-body-upload-file.yaml") - .get("#operations-default-uploadAudioWav") - .click() - .get(".opblock-section-request-body .opblock-description-wrapper i") - .should( - "have.text", - "Example values are not available for audio/wav media types." - ) - }) - it("should display a file upload button", () => { - cy.visit("/?url=/documents/features/request-body-upload-file.yaml") - .get("#operations-default-uploadApplicationOctetStream") - .click() - .get(".try-out__btn") - .click() - .get(".opblock-section-request-body .opblock-description-wrapper input") - .should("have.prop", "type", "file") - }) - }) - describe("video/mpeg", () => { - it("should display description with the correct content type", () => { - cy.visit("/?url=/documents/features/request-body-upload-file.yaml") - .get("#operations-default-uploadVideoMpeg") - .click() - .get(".opblock-section-request-body .opblock-description-wrapper i") - .should( - "have.text", - "Example values are not available for video/mpeg media types." - ) - }) - it("should display a file upload button", () => { - cy.visit("/?url=/documents/features/request-body-upload-file.yaml") - .get("#operations-default-uploadApplicationOctetStream") - .click() - .get(".try-out__btn") - .click() - .get(".opblock-section-request-body .opblock-description-wrapper input") - .should("have.prop", "type", "file") - }) - }) - describe("schema format binary", () => { - it("should display description with the correct content type", () => { - cy.visit("/?url=/documents/features/request-body-upload-file.yaml") - .get("#operations-default-uploadSchemaFormatBinary") - .click() - .get(".opblock-section-request-body .opblock-description-wrapper i") - .should( - "have.text", - "Example values are not available for application/x-custom media types." - ) - }) - it("should display a file upload button", () => { - cy.visit("/?url=/documents/features/request-body-upload-file.yaml") - .get("#operations-default-uploadSchemaFormatBinary") - .click() - .get(".try-out__btn") - .click() - .get(".opblock-section-request-body .opblock-description-wrapper input") - .should("have.prop", "type", "file") - }) - }) - describe("schema format base64", () => { - it("should display description with the correct content type", () => { - cy.visit("/?url=/documents/features/request-body-upload-file.yaml") - .get("#operations-default-uploadSchemaFormatBase64") - .click() - .get(".opblock-section-request-body .opblock-description-wrapper i") - .should( - "have.text", - "Example values are not available for application/x-custom media types." - ) - }) - it("should display a file upload button", () => { - cy.visit("/?url=/documents/features/request-body-upload-file.yaml") - .get("#operations-default-uploadSchemaFormatBinary") - .click() - .get(".try-out__btn") - .click() - .get(".opblock-section-request-body .opblock-description-wrapper input") - .should("have.prop", "type", "file") - }) - }) -}) diff --git a/test/e2e-cypress/static/documents/features/request-body-upload-file.yaml b/test/e2e-cypress/static/documents/features/oas3-request-body-upload-file.yaml similarity index 53% rename from test/e2e-cypress/static/documents/features/request-body-upload-file.yaml rename to test/e2e-cypress/static/documents/features/oas3-request-body-upload-file.yaml index 2c487da298c..619a5095a14 100644 --- a/test/e2e-cypress/static/documents/features/request-body-upload-file.yaml +++ b/test/e2e-cypress/static/documents/features/oas3-request-body-upload-file.yaml @@ -7,8 +7,11 @@ info: * `audio/*` content type (no matter what schema format) * `image/*` content type (no matter what schema format) * `video/*` content type (no matter what schema format) - * schema format is `base64` (no matter what content type) - * schema format is `binary` (no matter what content type) + * `application/octect-stream` content type with empty Media Type Object + * schema type is `string` and format is `byte` (no matter what content type) + * schema type is `string` and format is `binary` (no matter what content type) + * multipart/form-data object property schema type is `string` and format is `byte` + * multipart/form-data object property schema type is `string` and format is `binary` version: "1.0.0" paths: /upload-application-octet-stream: @@ -19,13 +22,6 @@ paths: application/octet-stream: schema: type: string - responses: - '200': - description: successful operation - content: - text/plain: - schema: - type: string /upload-image-png: post: operationId: uploadImagePng @@ -34,13 +30,6 @@ paths: image/png: schema: type: string - responses: - '200': - description: successful operation - content: - text/plain: - schema: - type: string /upload-audio-wav: post: operationId: uploadAudioWav @@ -49,13 +38,6 @@ paths: audio/wav: schema: type: string - responses: - '200': - description: successful operation - content: - text/plain: - schema: - type: string /upload-video-mpeg: post: operationId: uploadVideoMpeg @@ -64,13 +46,12 @@ paths: video/mpeg: schema: type: string - responses: - '200': - description: successful operation - content: - text/plain: - schema: - type: string + /upload-application-octet-stream-empty: + post: + operationId: uploadApplicationOctetStreamEmpty + requestBody: + content: + application/octet-stream: {} /upload-schema-format-binary: post: operationId: uploadSchemaFormatBinary @@ -80,26 +61,36 @@ paths: schema: type: string format: binary - responses: - '200': - description: successful operation - content: - text/plain: - schema: - type: string - /upload-schema-format-base64: + /upload-schema-format-byte: post: - operationId: uploadSchemaFormatBase64 + operationId: uploadSchemaFormatByte requestBody: content: application/x-custom: schema: type: string - format: base64 - responses: - '200': - description: successful operation - content: - text/plain: - schema: - type: string + format: byte + /upload-property-schema-format-binary: + post: + operationId: uploadPropertySchemaFormatBinary + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + /upload-property-schema-format-byte: + post: + operationId: uploadPropertySchemaFormatByte + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: byte diff --git a/test/e2e-cypress/static/documents/features/oas31-request-body-upload-file.yaml b/test/e2e-cypress/static/documents/features/oas31-request-body-upload-file.yaml new file mode 100644 index 00000000000..85c04608eef --- /dev/null +++ b/test/e2e-cypress/static/documents/features/oas31-request-body-upload-file.yaml @@ -0,0 +1,150 @@ +openapi: 3.1.0 +info: + title: "Request body file upload" + description: |- + This document has examples for examining the `schema` or content type for request bodies requiring a file upload + * `application/octect-stream` content type (no matter what schema format) + * `audio/*` content type (no matter what schema format) + * `image/*` content type (no matter what schema format) + * `video/*` content type (no matter what schema format) + * `application/octect-stream` content type with empty Media Type Object + * schema type is `string` and format is `byte` (no matter what content type) + * schema type is `string` and format is `binary` (no matter what content type) + * schema union type includes `string` and format is `byte` (no matter what content type) + * schema union type includes `string` and format is `binary` (no matter what content type) + * multipart/form-data object property schema type is `string` and format is `byte` + * multipart/form-data object property schema type is `string` and format is `binary` + * multipart/form-data object property schema union type includes `string` and format is `byte` + * multipart/form-data object property schema union type includes `string` and format is `binary` + version: "1.0.0" +paths: + /upload-application-octet-stream: + post: + operationId: uploadApplicationOctetStream + requestBody: + content: + application/octet-stream: + schema: + type: string + /upload-image-png: + post: + operationId: uploadImagePng + requestBody: + content: + image/png: + schema: + type: string + /upload-audio-wav: + post: + operationId: uploadAudioWav + requestBody: + content: + audio/wav: + schema: + type: string + /upload-video-mpeg: + post: + operationId: uploadVideoMpeg + requestBody: + content: + video/mpeg: + schema: + type: string + /upload-application-octet-stream-empty: + post: + operationId: uploadApplicationOctetStreamEmpty + requestBody: + content: + application/octet-stream: {} + /upload-schema-type-format-binary: + post: + operationId: uploadSchemaTypeFormatBinary + requestBody: + content: + application/x-custom: + schema: + type: string + format: binary + /upload-schema-type-format-byte: + post: + operationId: uploadSchemaTypeFormatByte + requestBody: + content: + application/x-custom: + schema: + type: string + format: byte + /upload-schema-union-type-format-binary: + post: + operationId: uploadSchemaUnionTypeFormatBinary + requestBody: + content: + application/x-custom: + schema: + type: + - object + - string + format: binary + /upload-schema-union-type-format-byte: + post: + operationId: uploadSchemaUnionTypeFormatByte + requestBody: + content: + application/x-custom: + schema: + type: + - object + - string + format: byte + /upload-property-schema-format-binary: + post: + operationId: uploadPropertySchemaFormatBinary + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + /upload-property-schema-format-byte: + post: + operationId: uploadPropertySchemaFormatByte + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + /upload-property-schema-union-type-format-binary: + post: + operationId: uploadPropertySchemaUnionTypeFormatBinary + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: + - object + - string + format: binary + /upload-property-schema-union-type-format-byte: + post: + operationId: uploadPropertySchemaUnionTypeFormatByte + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: + - object + - string + format: byte diff --git a/test/unit/core/plugins/json-schema-5/components/json-schema-form.jsx b/test/unit/core/plugins/json-schema-5/components/json-schema-form.jsx index 73d0d2292a3..4600beb0784 100644 --- a/test/unit/core/plugins/json-schema-5/components/json-schema-form.jsx +++ b/test/unit/core/plugins/json-schema-5/components/json-schema-form.jsx @@ -4,6 +4,7 @@ import { Select, Input, TextArea } from "core/components/layout-utils" import { mount, render } from "enzyme" import * as JsonSchemaComponents from "core/plugins/json-schema-5/components/json-schema-components" import { foldType } from "core/plugins/json-schema-2020-12-samples/fn/index" +import { makeIsFileUploadIntended } from "core/plugins/oas3/fn" const components = {...JsonSchemaComponents, Select, Input, TextArea} @@ -13,6 +14,16 @@ const getComponentStub = (name) => { return null } +const getSystemStub = () => ({ + getConfigs: () => ({ + fileUploadMediaTypes: [], + }), + fn: { + hasSchemaType: () => {}, + isFileUploadIntendedOAS30: () => {}, + }, +}) + describe("", function(){ describe("strings", function() { it("should render the correct options for a string enum parameter", function(){ @@ -26,6 +37,7 @@ describe("", function(){ jsonSchema202012: { foldType, }, + isFileUploadIntended: makeIsFileUploadIntended(getSystemStub) }, schema: Immutable.fromJS({ type: "string", @@ -53,6 +65,7 @@ describe("", function(){ jsonSchema202012: { foldType, }, + isFileUploadIntended: makeIsFileUploadIntended(getSystemStub) }, schema: Immutable.fromJS({ type: "string", @@ -78,6 +91,7 @@ describe("", function(){ jsonSchema202012: { foldType, }, + isFileUploadIntended: makeIsFileUploadIntended(getSystemStub) }, required: true, schema: Immutable.fromJS({ @@ -106,6 +120,7 @@ describe("", function(){ jsonSchema202012: { foldType, }, + isFileUploadIntended: makeIsFileUploadIntended(getSystemStub) }, schema: Immutable.fromJS({ type: "boolean" @@ -133,6 +148,7 @@ describe("", function(){ jsonSchema202012: { foldType, }, + isFileUploadIntended: makeIsFileUploadIntended(getSystemStub) }, schema: Immutable.fromJS({ type: "boolean", @@ -160,6 +176,7 @@ describe("", function(){ jsonSchema202012: { foldType, }, + isFileUploadIntended: makeIsFileUploadIntended(getSystemStub) }, schema: Immutable.fromJS({ type: "boolean", @@ -188,6 +205,7 @@ describe("", function(){ jsonSchema202012: { foldType, }, + isFileUploadIntended: makeIsFileUploadIntended(getSystemStub) }, required: true, schema: Immutable.fromJS({ @@ -219,6 +237,7 @@ describe("", function(){ jsonSchema202012: { foldType, }, + isFileUploadIntended: makeIsFileUploadIntended(getSystemStub) }, errors: List(), schema: Immutable.fromJS({ @@ -252,6 +271,7 @@ describe("", function(){ jsonSchema202012: { foldType, }, + isFileUploadIntended: makeIsFileUploadIntended(getSystemStub) }, schema: Immutable.fromJS({ type: "NotARealType" @@ -278,6 +298,7 @@ describe("", function(){ jsonSchema202012: { foldType, }, + isFileUploadIntended: makeIsFileUploadIntended(getSystemStub) }, schema: Immutable.fromJS({ type: "NotARealType",