refactor(oas31): concentrate OpenAPI 3.1.0 code to separate plugin (#8475)

Refs #8474
This commit is contained in:
Vladimír Gorej
2023-03-16 12:05:19 +01:00
committed by GitHub
parent ceccb218d3
commit 8b274414ab
35 changed files with 929 additions and 699 deletions

View File

@@ -0,0 +1,45 @@
/**
* @prettier
*/
import React from "react"
import PropTypes from "prop-types"
import { safeBuildUrl } from "core/utils/url"
import { sanitizeUrl } from "core/utils"
class Contact extends React.Component {
static propTypes = {
data: PropTypes.object,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
selectedServer: PropTypes.string,
url: PropTypes.string.isRequired,
}
render() {
const { data, getComponent, selectedServer, url: specUrl } = this.props
const name = data.get("name", "the developer")
const url = safeBuildUrl(data.get("url"), specUrl, { selectedServer })
const email = data.get("email")
const Link = getComponent("Link")
return (
<div className="info__contact">
{url && (
<div>
<Link href={sanitizeUrl(url)} target="_blank">
{name} - Website
</Link>
</div>
)}
{email && (
<Link href={sanitizeUrl(`mailto:${email}`)}>
{url ? `Send email to ${name}` : `Contact ${name}`}
</Link>
)}
</div>
)
}
}
export default Contact

View File

@@ -1,102 +1,53 @@
/**
* @prettier
*/
import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { sanitizeUrl } from "core/utils"
import { safeBuildUrl } from "core/utils/url"
export class InfoBasePath extends React.Component {
static propTypes = {
host: PropTypes.string,
basePath: PropTypes.string
basePath: PropTypes.string,
}
render() {
let { host, basePath } = this.props
const { host, basePath } = this.props
return (
<pre className="base-url">
[ Base URL: {host}{basePath} ]
[ Base URL: {host}
{basePath} ]
</pre>
)
}
}
export class Contact extends React.Component {
static propTypes = {
data: PropTypes.object,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
selectedServer: PropTypes.string,
url: PropTypes.string.isRequired,
}
render(){
let { data, getComponent, selectedServer, url: specUrl} = this.props
let name = data.get("name") || "the developer"
let url = safeBuildUrl(data.get("url"), specUrl, {selectedServer})
let email = data.get("email")
const Link = getComponent("Link")
return (
<div className="info__contact">
{ url && <div><Link href={ sanitizeUrl(url) } target="_blank">{ name } - Website</Link></div> }
{ email &&
<Link href={sanitizeUrl(`mailto:${email}`)}>
{ url ? `Send email to ${name}` : `Contact ${name}`}
</Link>
}
</div>
)
}
}
export class License extends React.Component {
static propTypes = {
license: PropTypes.object,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
selectedServer: PropTypes.string,
url: PropTypes.string.isRequired,
}
render(){
let { license, getComponent, selectedServer, url: specUrl } = this.props
const Link = getComponent("Link")
let name = license.get("name") || "License"
let url = safeBuildUrl(license.get("url"), specUrl, {selectedServer})
return (
<div className="info__license">
{
url ? <Link target="_blank" href={ sanitizeUrl(url) }>{ name }</Link>
: <span>{ name }</span>
}
</div>
)
}
}
export class InfoUrl extends React.PureComponent {
static propTypes = {
url: PropTypes.string.isRequired,
getComponent: PropTypes.func.isRequired
getComponent: PropTypes.func.isRequired,
}
render() {
const { url, getComponent } = this.props
const Link = getComponent("Link")
return <Link target="_blank" href={ sanitizeUrl(url) }><span className="url"> { url }</span></Link>
return (
<Link target="_blank" href={sanitizeUrl(url)}>
<span className="url"> {url}</span>
</Link>
)
}
}
export default class Info extends React.Component {
class Info extends React.Component {
static propTypes = {
title: PropTypes.any,
description: PropTypes.any,
version: PropTypes.any,
info: PropTypes.object,
url: PropTypes.string,
host: PropTypes.string,
@@ -108,16 +59,32 @@ export default class Info extends React.Component {
}
render() {
let { info, url, host, basePath, getComponent, externalDocs, selectedServer, url: specUrl } = this.props
let version = info.get("version")
let description = info.get("description")
let title = info.get("title")
let termsOfServiceUrl = safeBuildUrl(info.get("termsOfService"), specUrl, {selectedServer})
let contact = info.get("contact")
let license = info.get("license")
let rawExternalDocsUrl = externalDocs && externalDocs.get("url")
let externalDocsUrl = safeBuildUrl(rawExternalDocsUrl, specUrl, {selectedServer})
let externalDocsDescription = externalDocs && externalDocs.get("description")
const {
info,
url,
host,
basePath,
getComponent,
externalDocs,
selectedServer,
url: specUrl,
} = this.props
const version = info.get("version")
const description = info.get("description")
const title = info.get("title")
const termsOfServiceUrl = safeBuildUrl(
info.get("termsOfService"),
specUrl,
{ selectedServer }
)
const contactData = info.get("contact")
const licenseData = info.get("license")
const rawExternalDocsUrl = externalDocs && externalDocs.get("url")
const externalDocsUrl = safeBuildUrl(rawExternalDocsUrl, specUrl, {
selectedServer,
})
const externalDocsDescription =
externalDocs && externalDocs.get("description")
const Markdown = getComponent("Markdown", true)
const Link = getComponent("Link")
@@ -125,42 +92,61 @@ export default class Info extends React.Component {
const InfoUrl = getComponent("InfoUrl")
const InfoBasePath = getComponent("InfoBasePath")
const License = getComponent("License")
const Contact = getComponent("Contact")
return (
<div className="info">
<hgroup className="main">
<h2 className="title" >{ title }
{ version && <VersionStamp version={version}></VersionStamp> }
<h2 className="title">
{title}
{version && <VersionStamp version={version}></VersionStamp>}
</h2>
{ host || basePath ? <InfoBasePath host={ host } basePath={ basePath } /> : null }
{ url && <InfoUrl getComponent={getComponent} url={url} /> }
{host || basePath ? (
<InfoBasePath host={host} basePath={basePath} />
) : null}
{url && <InfoUrl getComponent={getComponent} url={url} />}
</hgroup>
<div className="description">
<Markdown source={ description } />
<Markdown source={description} />
</div>
{
termsOfServiceUrl && <div className="info__tos">
<Link target="_blank" href={ sanitizeUrl(termsOfServiceUrl) }>Terms of service</Link>
{termsOfServiceUrl && (
<div className="info__tos">
<Link target="_blank" href={sanitizeUrl(termsOfServiceUrl)}>
Terms of service
</Link>
</div>
}
{contact && contact.size ? <Contact getComponent={getComponent} data={ contact } selectedServer={selectedServer} url={url} /> : null }
{license && license.size ? <License getComponent={getComponent} license={ license } selectedServer={selectedServer} url={url}/> : null }
{ externalDocsUrl ?
<Link className="info__extdocs" target="_blank" href={sanitizeUrl(externalDocsUrl)}>{externalDocsDescription || externalDocsUrl}</Link>
: null }
)}
{contactData?.size > 0 && (
<Contact
getComponent={getComponent}
data={contactData}
selectedServer={selectedServer}
url={url}
/>
)}
{licenseData?.size > 0 && (
<License
getComponent={getComponent}
license={licenseData}
selectedServer={selectedServer}
url={url}
/>
)}
{externalDocsUrl ? (
<Link
className="info__extdocs"
target="_blank"
href={sanitizeUrl(externalDocsUrl)}
>
{externalDocsDescription || externalDocsUrl}
</Link>
) : null}
</div>
)
}
}
Info.propTypes = {
title: PropTypes.any,
description: PropTypes.any,
version: PropTypes.any,
url: PropTypes.string
}
export default Info

View File

@@ -1,37 +1,39 @@
/**
* @prettier
*/
import React from "react"
import PropTypes from "prop-types"
export default class BaseLayout extends React.Component {
static propTypes = {
errSelectors: PropTypes.object.isRequired,
errActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
oas3Selectors: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired
getComponent: PropTypes.func.isRequired,
}
render() {
let {errSelectors, specSelectors, getComponent} = this.props
const { errSelectors, specSelectors, getComponent } = this.props
let SvgAssets = getComponent("SvgAssets")
let InfoContainer = getComponent("InfoContainer", true)
let VersionPragmaFilter = getComponent("VersionPragmaFilter")
let Operations = getComponent("operations", true)
let Models = getComponent("Models", true)
let Webhooks = getComponent("Webhooks", true)
let Row = getComponent("Row")
let Col = getComponent("Col")
let Errors = getComponent("errors", true)
const SvgAssets = getComponent("SvgAssets")
const InfoContainer = getComponent("InfoContainer", true)
const VersionPragmaFilter = getComponent("VersionPragmaFilter")
const Operations = getComponent("operations", true)
const Models = getComponent("Models", true)
const Webhooks = getComponent("Webhooks", true)
const Row = getComponent("Row")
const Col = getComponent("Col")
const Errors = getComponent("errors", true)
const ServersContainer = getComponent("ServersContainer", true)
const SchemesContainer = getComponent("SchemesContainer", true)
const AuthorizeBtnContainer = getComponent("AuthorizeBtnContainer", true)
const FilterContainer = getComponent("FilterContainer", true)
let isSwagger2 = specSelectors.isSwagger2()
let isOAS3 = specSelectors.isOAS3()
const isOpenAPI31 = specSelectors.selectIsOpenAPI31()
const isSwagger2 = specSelectors.isSwagger2()
const isOAS3 = specSelectors.isOAS3()
const isOAS31 = specSelectors.isOAS31()
const isSpecEmpty = !specSelectors.specStr()
@@ -39,44 +41,50 @@ export default class BaseLayout extends React.Component {
let loadingMessage = null
if(loadingStatus === "loading") {
loadingMessage = <div className="info">
<div className="loading-container">
<div className="loading"></div>
if (loadingStatus === "loading") {
loadingMessage = (
<div className="info">
<div className="loading-container">
<div className="loading"></div>
</div>
</div>
</div>
)
}
if(loadingStatus === "failed") {
loadingMessage = <div className="info">
<div className="loading-container">
<h4 className="title">Failed to load API definition.</h4>
<Errors />
if (loadingStatus === "failed") {
loadingMessage = (
<div className="info">
<div className="loading-container">
<h4 className="title">Failed to load API definition.</h4>
<Errors />
</div>
</div>
</div>
)
}
if (loadingStatus === "failedConfig") {
const lastErr = errSelectors.lastError()
const lastErrMsg = lastErr ? lastErr.get("message") : ""
loadingMessage = <div className="info failed-config">
<div className="loading-container">
<h4 className="title">Failed to load remote configuration.</h4>
<p>{lastErrMsg}</p>
loadingMessage = (
<div className="info failed-config">
<div className="loading-container">
<h4 className="title">Failed to load remote configuration.</h4>
<p>{lastErrMsg}</p>
</div>
</div>
</div>
)
}
if(!loadingMessage && isSpecEmpty) {
if (!loadingMessage && isSpecEmpty) {
loadingMessage = <h4>No API definition provided.</h4>
}
if(loadingMessage) {
return <div className="swagger-ui">
<div className="loading-container">
{loadingMessage}
if (loadingMessage) {
return (
<div className="swagger-ui">
<div className="loading-container">{loadingMessage}</div>
</div>
</div>
)
}
const servers = specSelectors.servers()
@@ -87,43 +95,47 @@ export default class BaseLayout extends React.Component {
const hasSecurityDefinitions = !!specSelectors.securityDefinitions()
return (
<div className='swagger-ui'>
<div className="swagger-ui">
<SvgAssets />
<VersionPragmaFilter isSwagger2={isSwagger2} isOAS3={isOAS3} alsoShow={<Errors/>}>
<Errors/>
<VersionPragmaFilter
isSwagger2={isSwagger2}
isOAS3={isOAS3}
alsoShow={<Errors />}
>
<Errors />
<Row className="information-container">
<Col mobile={12}>
<InfoContainer/>
<InfoContainer />
</Col>
</Row>
{hasServers || hasSchemes || hasSecurityDefinitions ? (
<div className="scheme-container">
<Col className="schemes wrapper" mobile={12}>
{hasServers ? (<ServersContainer />) : null}
{hasSchemes ? (<SchemesContainer />) : null}
{hasSecurityDefinitions ? (<AuthorizeBtnContainer />) : null}
{hasServers ? <ServersContainer /> : null}
{hasSchemes ? <SchemesContainer /> : null}
{hasSecurityDefinitions ? <AuthorizeBtnContainer /> : null}
</Col>
</div>
) : null}
<FilterContainer/>
<FilterContainer />
<Row>
<Col mobile={12} desktop={12} >
<Operations/>
<Col mobile={12} desktop={12}>
<Operations />
</Col>
</Row>
{ isOpenAPI31 &&
{isOAS31 && (
<Row className="webhooks-container">
<Col mobile={12} desktop={12} >
<Col mobile={12} desktop={12}>
<Webhooks />
</Col>
</Row>
}
)}
<Row>
<Col mobile={12} desktop={12} >
<Models/>
<Col mobile={12} desktop={12}>
<Models />
</Col>
</Row>
</VersionPragmaFilter>

View File

@@ -0,0 +1,41 @@
/**
* @prettier
*/
import React from "react"
import PropTypes from "prop-types"
import { safeBuildUrl } from "core/utils/url"
import { sanitizeUrl } from "core/utils"
class License extends React.Component {
static propTypes = {
license: PropTypes.object,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
selectedServer: PropTypes.string,
url: PropTypes.string.isRequired,
}
render() {
const { license, getComponent, selectedServer, url: specUrl } = this.props
const name = license.get("name", "License")
const url = safeBuildUrl(license.get("url"), specUrl, { selectedServer })
const Link = getComponent("Link")
return (
<div className="info__license">
{url ? (
<div className="info__license__url">
<Link target="_blank" href={sanitizeUrl(url)}>
{name}
</Link>
</div>
) : (
<span>{name}</span>
)}
</div>
)
}
}
export default License

View File

@@ -1,6 +1,5 @@
import { createSelector } from "reselect"
import { List, Map, fromJS } from "immutable"
import { isOAS3 as isOAS3Helper } from "../helpers"
// Helpers
@@ -9,8 +8,7 @@ const state = state => state
function onlyOAS3(selector) {
return (ori, system) => (...args) => {
const spec = system.getSystem().specSelectors.specJson()
if(isOAS3Helper(spec)) {
if(system.getSystem().specSelectors.isOAS3()) {
// Pass the spec plugin state to Reselect to trigger on securityDefinitions update
let resolvedSchemes = system.getState().getIn(["spec", "resolvedSubtrees",
"components", "securitySchemes"])

View File

@@ -6,7 +6,6 @@ import ServersContainer from "./servers-container"
import RequestBodyEditor from "./request-body-editor"
import HttpAuth from "./http-auth"
import OperationServers from "./operation-servers"
import Webhooks from "./webhooks"
export default {
Callbacks,
@@ -17,5 +16,4 @@ export default {
RequestBodyEditor,
OperationServers,
operationLink: OperationLink,
Webhooks
}

View File

@@ -1,44 +1,27 @@
/**
* @prettier
*/
import React from "react"
export function isOpenAPI30(jsSpec) {
export function isOAS30(jsSpec) {
const oasVersion = jsSpec.get("openapi")
if (typeof oasVersion !== "string") {
return false
}
return oasVersion.startsWith("3.0.") && oasVersion.length > 4
}
export function isOpenAPI31(jsSpec) {
const oasVersion = jsSpec.get("openapi")
if (typeof oasVersion !== "string") {
return false
}
return oasVersion.startsWith("3.1.") && oasVersion.length > 4
}
export function isOAS3(jsSpec) {
const oasVersion = jsSpec.get("openapi")
if(typeof oasVersion !== "string") {
return false
}
return isOpenAPI30(jsSpec) || isOpenAPI31(jsSpec)
return (
typeof oasVersion === "string" &&
/^3\.0\.([0123])(?:-rc[012])?$/.test(oasVersion)
)
}
export function isSwagger2(jsSpec) {
const swaggerVersion = jsSpec.get("swagger")
if(typeof swaggerVersion !== "string") {
return false
}
return swaggerVersion.startsWith("2.0")
return typeof swaggerVersion === "string" && swaggerVersion === "2.0"
}
export function OAS3ComponentWrapFactory(Component) {
return (Ori, system) => (props) => {
if(system && system.specSelectors && system.specSelectors.specJson) {
const spec = system.specSelectors.specJson()
if(isOAS3(spec)) {
if (typeof system.specSelectors?.isOAS3 === "function") {
if (system.specSelectors.isOAS3()) {
return <Component {...props} {...system} Ori={Ori}></Component>
} else {
return <Ori {...props}></Ori>

View File

@@ -1,31 +1,32 @@
// import reducers from "./reducers"
// import * as actions from "./actions"
/**
* @prettier
*/
import * as specWrapSelectors from "./spec-extensions/wrap-selectors"
import * as authWrapSelectors from "./auth-extensions/wrap-selectors"
import * as specSelectors from "./spec-extensions/selectors"
import components from "./components"
import wrapComponents from "./wrap-components"
import * as oas3Actions from "./actions"
import * as oas3Selectors from "./selectors"
import oas3Reducers from "./reducers"
import * as actions from "./actions"
import * as selectors from "./selectors"
import reducers from "./reducers"
export default function() {
export default function () {
return {
components,
wrapComponents,
statePlugins: {
spec: {
wrapSelectors: specWrapSelectors,
selectors: specSelectors
selectors: specSelectors,
},
auth: {
wrapSelectors: authWrapSelectors
wrapSelectors: authWrapSelectors,
},
oas3: {
actions: oas3Actions,
reducers: oas3Reducers,
selectors: oas3Selectors,
}
}
actions,
reducers,
selectors,
},
},
}
}

View File

@@ -1,41 +1,53 @@
/**
* @prettier
*/
import { OrderedMap, Map, List } from "immutable"
import { isOAS3 as isOAS3Helper } from "./helpers"
import { getDefaultRequestBodyValue } from "./components/request-body"
import { stringify } from "../../utils"
// Helpers
function onlyOAS3(selector) {
return (...args) => (system) => {
const spec = system.getSystem().specSelectors.specJson()
if(isOAS3Helper(spec)) {
return selector(...args)
} else {
return null
return (...args) =>
(system) => {
if (system.getSystem().specSelectors.isOAS3()) {
return selector(...args)
} else {
return null
}
}
}
}
function validateRequestBodyIsRequired(selector) {
return (...args) => (system) => {
const specJson = system.getSystem().specSelectors.specJson()
const argsList = [...args]
// expect argsList[0] = state
let pathMethod = argsList[1] || []
let isOas3RequestBodyRequired = specJson.getIn(["paths", ...pathMethod, "requestBody", "required"])
return (...args) =>
(system) => {
const specJson = system.getSystem().specSelectors.specJson()
const argsList = [...args]
// expect argsList[0] = state
let pathMethod = argsList[1] || []
let isOas3RequestBodyRequired = specJson.getIn([
"paths",
...pathMethod,
"requestBody",
"required",
])
if (isOas3RequestBodyRequired) {
return selector(...args)
} else {
// validation pass b/c not required
return true
if (isOas3RequestBodyRequired) {
return selector(...args)
} else {
// validation pass b/c not required
return true
}
}
}
}
const validateRequestBodyValueExists = (state, pathMethod) => {
pathMethod = pathMethod || []
let oas3RequestBodyValue = state.getIn(["requestData", ...pathMethod, "bodyValue"])
let oas3RequestBodyValue = state.getIn([
"requestData",
...pathMethod,
"bodyValue",
])
// context: bodyValue can be a String, or a Map
if (!oas3RequestBodyValue) {
return false
@@ -44,68 +56,85 @@ const validateRequestBodyValueExists = (state, pathMethod) => {
return true
}
export const selectedServer = onlyOAS3((state, namespace) => {
const path = namespace ? [namespace, "selectedServer"] : ["selectedServer"]
return state.getIn(path) || ""
}
)
const path = namespace ? [namespace, "selectedServer"] : ["selectedServer"]
return state.getIn(path) || ""
})
export const requestBodyValue = onlyOAS3((state, path, method) => {
return state.getIn(["requestData", path, method, "bodyValue"]) || null
}
)
return state.getIn(["requestData", path, method, "bodyValue"]) || null
})
export const shouldRetainRequestBodyValue = onlyOAS3((state, path, method) => {
return state.getIn(["requestData", path, method, "retainBodyValue"]) || false
}
)
return state.getIn(["requestData", path, method, "retainBodyValue"]) || false
})
export const selectDefaultRequestBodyValue = (state, path, method) => (system) => {
const {oas3Selectors, specSelectors} = system.getSystem()
const spec = specSelectors.specJson()
if(isOAS3Helper(spec)) {
const currentMediaType = oas3Selectors.requestContentType(path, method)
if (currentMediaType) {
return getDefaultRequestBodyValue(
specSelectors.specResolvedSubtree(["paths", path, method, "requestBody"]),
currentMediaType,
oas3Selectors.activeExamplesMember(
path, method,
"requestBody",
"requestBody",
export const selectDefaultRequestBodyValue =
(state, path, method) => (system) => {
const { oas3Selectors, specSelectors } = system.getSystem()
if (specSelectors.isOAS3()) {
const currentMediaType = oas3Selectors.requestContentType(path, method)
if (currentMediaType) {
return getDefaultRequestBodyValue(
specSelectors.specResolvedSubtree([
"paths",
path,
method,
"requestBody",
]),
currentMediaType,
oas3Selectors.activeExamplesMember(
path,
method,
"requestBody",
"requestBody"
)
)
)
}
}
return null
}
return null
}
export const hasUserEditedBody = (state, path, method) => (system) => {
const {oas3Selectors, specSelectors} = system.getSystem()
const spec = specSelectors.specJson()
if(isOAS3Helper(spec)) {
const { oas3Selectors, specSelectors } = system.getSystem()
if (specSelectors.isOAS3()) {
let userHasEditedBody = false
const currentMediaType = oas3Selectors.requestContentType(path, method)
let userEditedRequestBody = oas3Selectors.requestBodyValue(path, method)
if (Map.isMap(userEditedRequestBody)) {
// context is not application/json media-type
userEditedRequestBody = stringify(userEditedRequestBody.mapEntries((kv) => Map.isMap(kv[1]) ? [kv[0], kv[1].get("value")] : kv).toJS())
userEditedRequestBody = stringify(
userEditedRequestBody
.mapEntries((kv) =>
Map.isMap(kv[1]) ? [kv[0], kv[1].get("value")] : kv
)
.toJS()
)
}
if(List.isList(userEditedRequestBody)) {
if (List.isList(userEditedRequestBody)) {
userEditedRequestBody = stringify(userEditedRequestBody)
}
if (currentMediaType) {
const currentMediaTypeDefaultBodyValue = getDefaultRequestBodyValue(
specSelectors.specResolvedSubtree(["paths", path, method, "requestBody"]),
specSelectors.specResolvedSubtree([
"paths",
path,
method,
"requestBody",
]),
currentMediaType,
oas3Selectors.activeExamplesMember(
path, method,
"requestBody",
path,
method,
"requestBody",
"requestBody"
)
)
userHasEditedBody = !!userEditedRequestBody && userEditedRequestBody !== currentMediaTypeDefaultBodyValue
userHasEditedBody =
!!userEditedRequestBody &&
userEditedRequestBody !== currentMediaTypeDefaultBodyValue
}
return userHasEditedBody
} else {
@@ -114,106 +143,114 @@ export const hasUserEditedBody = (state, path, method) => (system) => {
}
export const requestBodyInclusionSetting = onlyOAS3((state, path, method) => {
return state.getIn(["requestData", path, method, "bodyInclusion"]) || Map()
}
)
return state.getIn(["requestData", path, method, "bodyInclusion"]) || Map()
})
export const requestBodyErrors = onlyOAS3((state, path, method) => {
return state.getIn(["requestData", path, method, "errors"]) || null
}
)
return state.getIn(["requestData", path, method, "errors"]) || null
})
export const activeExamplesMember = onlyOAS3((state, path, method, type, name) => {
return state.getIn(["examples", path, method, type, name, "activeExample"]) || null
export const activeExamplesMember = onlyOAS3(
(state, path, method, type, name) => {
return (
state.getIn(["examples", path, method, type, name, "activeExample"]) ||
null
)
}
)
export const requestContentType = onlyOAS3((state, path, method) => {
return state.getIn(["requestData", path, method, "requestContentType"]) || null
}
)
return (
state.getIn(["requestData", path, method, "requestContentType"]) || null
)
})
export const responseContentType = onlyOAS3((state, path, method) => {
return state.getIn(["requestData", path, method, "responseContentType"]) || null
}
)
return (
state.getIn(["requestData", path, method, "responseContentType"]) || null
)
})
export const serverVariableValue = onlyOAS3((state, locationData, key) => {
let path
let path
// locationData may take one of two forms, for backwards compatibility
// Object: ({server, namespace?}) or String:(server)
if(typeof locationData !== "string") {
const { server, namespace } = locationData
if(namespace) {
path = [namespace, "serverVariableValues", server, key]
} else {
path = ["serverVariableValues", server, key]
}
// locationData may take one of two forms, for backwards compatibility
// Object: ({server, namespace?}) or String:(server)
if (typeof locationData !== "string") {
const { server, namespace } = locationData
if (namespace) {
path = [namespace, "serverVariableValues", server, key]
} else {
const server = locationData
path = ["serverVariableValues", server, key]
}
return state.getIn(path) || null
} else {
const server = locationData
path = ["serverVariableValues", server, key]
}
)
return state.getIn(path) || null
})
export const serverVariables = onlyOAS3((state, locationData) => {
let path
let path
// locationData may take one of two forms, for backwards compatibility
// Object: ({server, namespace?}) or String:(server)
if(typeof locationData !== "string") {
const { server, namespace } = locationData
if(namespace) {
path = [namespace, "serverVariableValues", server]
} else {
path = ["serverVariableValues", server]
}
// locationData may take one of two forms, for backwards compatibility
// Object: ({server, namespace?}) or String:(server)
if (typeof locationData !== "string") {
const { server, namespace } = locationData
if (namespace) {
path = [namespace, "serverVariableValues", server]
} else {
const server = locationData
path = ["serverVariableValues", server]
}
return state.getIn(path) || OrderedMap()
} else {
const server = locationData
path = ["serverVariableValues", server]
}
)
return state.getIn(path) || OrderedMap()
})
export const serverEffectiveValue = onlyOAS3((state, locationData) => {
var varValues, serverValue
var varValues, serverValue
// locationData may take one of two forms, for backwards compatibility
// Object: ({server, namespace?}) or String:(server)
if(typeof locationData !== "string") {
const { server, namespace } = locationData
serverValue = server
if(namespace) {
varValues = state.getIn([namespace, "serverVariableValues", serverValue])
} else {
varValues = state.getIn(["serverVariableValues", serverValue])
}
// locationData may take one of two forms, for backwards compatibility
// Object: ({server, namespace?}) or String:(server)
if (typeof locationData !== "string") {
const { server, namespace } = locationData
serverValue = server
if (namespace) {
varValues = state.getIn([namespace, "serverVariableValues", serverValue])
} else {
serverValue = locationData
varValues = state.getIn(["serverVariableValues", serverValue])
}
varValues = varValues || OrderedMap()
let str = serverValue
varValues.map((val, key) => {
str = str.replace(new RegExp(`{${key}}`, "g"), val)
})
return str
} else {
serverValue = locationData
varValues = state.getIn(["serverVariableValues", serverValue])
}
)
varValues = varValues || OrderedMap()
let str = serverValue
varValues.map((val, key) => {
str = str.replace(new RegExp(`{${key}}`, "g"), val)
})
return str
})
export const validateBeforeExecute = validateRequestBodyIsRequired(
(state, pathMethod) => validateRequestBodyValueExists(state, pathMethod)
)
export const validateShallowRequired = (state, { oas3RequiredRequestBodyContentType, oas3RequestContentType, oas3RequestBodyValue} ) => {
export const validateShallowRequired = (
state,
{
oas3RequiredRequestBodyContentType,
oas3RequestContentType,
oas3RequestBodyValue,
}
) => {
let missingRequiredKeys = []
// context: json => String; urlencoded, form-data => Map
if (!Map.isMap(oas3RequestBodyValue)) {
@@ -221,16 +258,19 @@ export const validateShallowRequired = (state, { oas3RequiredRequestBodyContentT
}
let requiredKeys = []
// Cycle through list of possible contentTypes for matching contentType and defined requiredKeys
Object.keys(oas3RequiredRequestBodyContentType.requestContentType).forEach((contentType) => {
if (contentType === oas3RequestContentType) {
let contentTypeVal = oas3RequiredRequestBodyContentType.requestContentType[contentType]
contentTypeVal.forEach((requiredKey) => {
if (requiredKeys.indexOf(requiredKey) < 0 ) {
requiredKeys.push(requiredKey)
}
})
Object.keys(oas3RequiredRequestBodyContentType.requestContentType).forEach(
(contentType) => {
if (contentType === oas3RequestContentType) {
let contentTypeVal =
oas3RequiredRequestBodyContentType.requestContentType[contentType]
contentTypeVal.forEach((requiredKey) => {
if (requiredKeys.indexOf(requiredKey) < 0) {
requiredKeys.push(requiredKey)
}
})
}
}
})
)
requiredKeys.forEach((key) => {
let requiredKeyValue = oas3RequestBodyValue.getIn([key, "value"])
if (!requiredKeyValue) {

View File

@@ -1,72 +1,39 @@
import { createSelector } from "reselect"
import { Map } from "immutable"
import { isOAS3 as isOAS3Helper, isOpenAPI31 as isOpenAPI31Helper, isSwagger2 as isSwagger2Helper } from "../helpers"
import { isSwagger2 as isSwagger2Helper, isOAS30 as isOAS30Helper } from "../helpers"
// Helpers
/**
* Helpers
*/
// 1/2023: as of now, more accurately, isAnyOAS3
function onlyOAS3(selector) {
return () => (system, ...args) => {
const spec = system.getSystem().specSelectors.specJson()
if(isOAS3Helper(spec)) {
return selector(...args)
} else {
return null
}
}
}
function isOpenAPI31(selector) {
return () => (system, ...args) => {
const spec = system.getSystem().specSelectors.specJson()
if (isOpenAPI31Helper(spec)) {
return selector(...args)
} else {
return null
}
}
}
const state = state => {
return state || Map()
}
const specJson = createSelector(
state,
spec => spec.get("json", Map())
)
const specResolved = createSelector(
state,
spec => spec.get("resolved", Map())
)
const spec = state => {
let res = specResolved(state)
if(res.count() < 1)
res = specJson(state)
return res
}
// New selectors
export const servers = onlyOAS3(createSelector(
spec,
spec => spec.getIn(["servers"]) || Map()
))
export const isSwagger2 = (ori, system) => () => {
export const isSwagger2 = () => (system) => {
const spec = system.getSystem().specSelectors.specJson()
return isSwagger2Helper(spec)
}
export const selectIsOpenAPI31 = (ori, system) => () => {
export const isOAS30 = () => (system) => {
const spec = system.getSystem().specSelectors.specJson()
return isOpenAPI31Helper(spec)
return isOAS30Helper(spec)
}
export const selectWebhooks = isOpenAPI31(createSelector(
spec,
spec => spec.getIn(["webhooks"]) || Map()
))
export const isOAS3 = () => (system) => {
return system.getSystem().specSelectors.isOAS30()
}
function onlyOAS3(selector) {
return () => (system, ...args) => {
const spec = system.getSystem().specSelectors.specJson()
if(system.specSelectors.isOAS3(spec)) {
const result = selector(...args)
return typeof result === "function" ? result(system, ...args) : result
} else {
return null
}
}
}
export const servers = onlyOAS3(() => (system) => {
const spec = system.specSelectors.specJson()
return spec.get("servers", servers.mapConst)
})
servers.mapConst = Map()

View File

@@ -1,107 +1,54 @@
/**
* @prettier
*/
import { createSelector } from "reselect"
import { specJsonWithResolvedSubtrees } from "../../spec/selectors"
import { Map } from "immutable"
import { isOAS3 as isOAS3Helper, isOpenAPI31 as isOpenAPI31Helper, isSwagger2 as isSwagger2Helper } from "../helpers"
// Helpers
// 1/2023: as of now, more accurately, isAnyOAS3
/**
* Helpers
*/
function onlyOAS3(selector) {
return (ori, system) => (...args) => {
const spec = system.getSystem().specSelectors.specJson()
if(isOAS3Helper(spec)) {
return selector(...args)
} else {
return ori(...args)
return (ori, system) =>
(...args) => {
if (system.getSystem().specSelectors.isOAS3()) {
const result = selector(...args)
return typeof result === "function" ? result(system, ...args) : result
} else {
return ori(...args)
}
}
}
}
function isOpenAPI31(selector) {
return (ori, system) => (...args) => {
const spec = system.getSystem().specSelectors.specJson()
if (isOpenAPI31Helper(spec)) {
return selector(...args)
} else {
return null
}
}
}
const state = state => {
return state || Map()
}
const nullSelector = createSelector(() => null)
const OAS3NullSelector = onlyOAS3(nullSelector)
const specJson = createSelector(
state,
spec => spec.get("json", Map())
)
/**
* Wrappers
*/
const specResolved = createSelector(
state,
spec => spec.get("resolved", Map())
)
export const definitions = onlyOAS3(() => (system) => {
const spec = system.getSystem().specSelectors.specJson()
const schemas = spec.getIn(["components", "schemas"])
return Map.isMap(schemas) ? schemas : definitions.mapConst
})
definitions.mapConst = Map()
const spec = state => {
let res = specResolved(state)
if(res.count() < 1)
res = specJson(state)
return res
}
// Wrappers
export const definitions = onlyOAS3(createSelector(
spec,
spec => {
const res = spec.getIn(["components", "schemas"])
return Map.isMap(res) ? res : Map()
}
))
export const hasHost = onlyOAS3((state) => {
return spec(state).hasIn(["servers", 0])
export const hasHost = onlyOAS3(() => (system) => {
const spec = system.getSystem().specSelectors.specJson()
return spec.hasIn(["servers", 0])
})
export const securityDefinitions = onlyOAS3(createSelector(
specJsonWithResolvedSubtrees,
spec => spec.getIn(["components", "securitySchemes"]) || null
))
export const securityDefinitions = onlyOAS3(
createSelector(
specJsonWithResolvedSubtrees,
(spec) => spec.getIn(["components", "securitySchemes"]) || null
)
)
export const host = OAS3NullSelector
export const basePath = OAS3NullSelector
export const consumes = OAS3NullSelector
export const produces = OAS3NullSelector
export const schemes = OAS3NullSelector
// New selectors
export const servers = onlyOAS3(createSelector(
spec,
spec => spec.getIn(["servers"]) || Map()
))
export const isOAS3 = (ori, system) => () => {
const spec = system.getSystem().specSelectors.specJson()
return isOAS3Helper(Map.isMap(spec) ? spec : Map())
}
export const isSwagger2 = (ori, system) => () => {
const spec = system.getSystem().specSelectors.specJson()
return isSwagger2Helper(Map.isMap(spec) ? spec : Map())
}
export const selectIsOpenAPI31 = (ori, system) => () => {
const spec = system.getSystem().specSelectors.specJson()
return isOpenAPI31Helper(Map.isMap(spec) ? spec : Map())
}
export const selectWebhooks = isOpenAPI31(createSelector(
spec,
spec => spec.getIn(["webhooks"]) || Map()
))

View File

@@ -4,8 +4,6 @@ import VersionStamp from "./version-stamp"
import OnlineValidatorBadge from "./online-validator-badge"
import Model from "./model"
import JsonSchema_string from "./json-schema-string"
import License from "./license"
import info from "./info"
export default {
Markdown,
@@ -14,6 +12,4 @@ export default {
VersionStamp,
model: Model,
onlineValidatorBadge: OnlineValidatorBadge,
License,
info,
}

View File

@@ -1,78 +0,0 @@
import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { sanitizeUrl } from "core/utils"
import { safeBuildUrl } from "core/utils/url"
import { OAS3ComponentWrapFactory } from "../helpers"
const Info = (props) => {
const { info, url, host, basePath, getComponent, specSelectors, externalDocs, selectedServer, url: specUrl } = props
const isOpenAPI31 = specSelectors.selectIsOpenAPI31()
const version = info.get("version")
const description = info.get("description")
const title = info.get("title")
const termsOfServiceUrl = safeBuildUrl(info.get("termsOfService"), specUrl, { selectedServer })
const contact = info.get("contact")
const license = info.get("license")
// note that ux may want to move summary to a sub-heading, as summary is a string that does not need to be Markdown
const summary = info.get("summary") // OAS3.1 field
const rawExternalDocsUrl = externalDocs && externalDocs.get("url")
const externalDocsUrl = safeBuildUrl(rawExternalDocsUrl, specUrl, { selectedServer })
const externalDocsDescription = externalDocs && externalDocs.get("description")
const Markdown = getComponent("Markdown", true)
const Link = getComponent("Link")
const VersionStamp = getComponent("VersionStamp")
const InfoUrl = getComponent("InfoUrl")
const InfoBasePath = getComponent("InfoBasePath")
const License = getComponent("License")
const Contact = getComponent("Contact")
return (
<div className="info">
<hgroup className="main">
<h2 className="title" >{title}
{version && <VersionStamp version={version}></VersionStamp>}
</h2>
{host || basePath ? <InfoBasePath host={host} basePath={basePath} /> : null}
{url && <InfoUrl getComponent={getComponent} url={url} />}
</hgroup>
{
isOpenAPI31 && summary && <div className="info__summary">
<Markdown source={summary} />
</div>
}
<div className="description">
<Markdown source={description} />
</div>
{
termsOfServiceUrl && <div className="info__tos">
<Link target="_blank" href={sanitizeUrl(termsOfServiceUrl)}>Terms of service</Link>
</div>
}
{contact && contact.size ? <Contact getComponent={getComponent} data={contact} selectedServer={selectedServer} url={url} /> : null}
{license && license.size ? <License getComponent={getComponent} license={license} selectedServer={selectedServer} url={url} /> : null}
{externalDocsUrl ?
<Link className="info__extdocs" target="_blank" href={sanitizeUrl(externalDocsUrl)}>{externalDocsDescription || externalDocsUrl}</Link>
: null}
</div>
)
}
Info.propTypes = {
info: PropTypes.object,
url: PropTypes.string,
host: PropTypes.string,
basePath: PropTypes.string,
externalDocs: ImPropTypes.map,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
oas3selectors: PropTypes.func,
selectedServer: PropTypes.string,
}
export default OAS3ComponentWrapFactory(Info)

View File

@@ -1,49 +0,0 @@
import React from "react"
import PropTypes from "prop-types"
import { sanitizeUrl } from "core/utils"
import { safeBuildUrl } from "core/utils/url"
import { OAS3ComponentWrapFactory } from "../helpers"
const baseSPDXurl = "https://spdx.org/licenses"
const createSPDXurl = (identifier) => {
return `${baseSPDXurl}/${identifier}.html`
}
const License = (props) => {
const { license, getComponent, selectedServer, url: specUrl, specSelectors } = props
const Link = getComponent("Link")
const name = license.get("name") || "License"
const url = safeBuildUrl(license.get("url"), specUrl, { selectedServer })
const identifier = license.get("identifier") || "" // OAS3.1 field
const identifierUrl = createSPDXurl(identifier)
const isOpenAPI31 = specSelectors.selectIsOpenAPI31()
return (
<div className="info__license">
{
!isOpenAPI31 && url && <div className="info__license__url"><Link target="_blank" href={sanitizeUrl(url)}>{name}</Link></div>
}
{
isOpenAPI31 && url && !identifier && <div className="info__license__url"><Link target="_blank" href={sanitizeUrl(url)}>{name}</Link></div>
}
{
isOpenAPI31 && identifier && !url && <div className="info__license__identifier"><Link target="_blank" href={sanitizeUrl(baseSPDXurl)}>SPDX License</Link>: <Link target="_blank" href={sanitizeUrl(identifierUrl)}>{identifier}</Link></div>
}
{/* {
isOpenAPI31 && identifier && url && <div className="info__license_error">Render Error: License.url and License.identifier are mutually exclusive fields</div>
} */}
</div>
)
}
License.propTypes = {
license: PropTypes.shape({
get: PropTypes.func,
}),
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
selectedServer: PropTypes.string,
url: PropTypes.string.isRequired,
}
export default OAS3ComponentWrapFactory(License)

View File

@@ -0,0 +1,155 @@
/**
* @prettier
*/
import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { sanitizeUrl } from "core/utils"
import { safeBuildUrl } from "core/utils/url"
export class InfoBasePath extends React.Component {
static propTypes = {
host: PropTypes.string,
basePath: PropTypes.string,
}
render() {
const { host, basePath } = this.props
return (
<pre className="base-url">
[ Base URL: {host}
{basePath} ]
</pre>
)
}
}
export class InfoUrl extends React.PureComponent {
static propTypes = {
url: PropTypes.string.isRequired,
getComponent: PropTypes.func.isRequired,
}
render() {
const { url, getComponent } = this.props
const Link = getComponent("Link")
return (
<Link target="_blank" href={sanitizeUrl(url)}>
<span className="url"> {url}</span>
</Link>
)
}
}
class Info extends React.Component {
static propTypes = {
title: PropTypes.any,
description: PropTypes.any,
version: PropTypes.any,
info: PropTypes.object,
url: PropTypes.string,
host: PropTypes.string,
basePath: PropTypes.string,
externalDocs: ImPropTypes.map,
getComponent: PropTypes.func.isRequired,
oas3selectors: PropTypes.func,
selectedServer: PropTypes.string,
}
render() {
const {
info,
url,
host,
basePath,
getComponent,
externalDocs,
selectedServer,
url: specUrl,
} = this.props
const version = info.get("version")
const summary = info.get("summary")
const description = info.get("description")
const title = info.get("title")
const termsOfServiceUrl = safeBuildUrl(
info.get("termsOfService"),
specUrl,
{ selectedServer }
)
const contactData = info.get("contact")
const licenseData = info.get("license")
const rawExternalDocsUrl = externalDocs && externalDocs.get("url")
const externalDocsUrl = safeBuildUrl(rawExternalDocsUrl, specUrl, {
selectedServer,
})
const externalDocsDescription =
externalDocs && externalDocs.get("description")
const Markdown = getComponent("Markdown", true)
const Link = getComponent("Link")
const VersionStamp = getComponent("VersionStamp")
const InfoUrl = getComponent("InfoUrl")
const InfoBasePath = getComponent("InfoBasePath")
const License = getComponent("License")
const Contact = getComponent("Contact")
return (
<div className="info">
<hgroup className="main">
<h2 className="title">
{title}
{version && <VersionStamp version={version}></VersionStamp>}
</h2>
{host || basePath ? (
<InfoBasePath host={host} basePath={basePath} />
) : null}
{url && <InfoUrl getComponent={getComponent} url={url} />}
</hgroup>
{summary && (
<div className="info__summary">
<Markdown source={summary} />
</div>
)}
<div className="description">
<Markdown source={description} />
</div>
{termsOfServiceUrl && (
<div className="info__tos">
<Link target="_blank" href={sanitizeUrl(termsOfServiceUrl)}>
Terms of service
</Link>
</div>
)}
{contactData?.size > 0 && (
<Contact
getComponent={getComponent}
data={contactData}
selectedServer={selectedServer}
url={url}
/>
)}
{licenseData?.size > 0 && (
<License
getComponent={getComponent}
license={licenseData}
selectedServer={selectedServer}
url={url}
/>
)}
{externalDocsUrl ? (
<Link
className="info__extdocs"
target="_blank"
href={sanitizeUrl(externalDocsUrl)}
>
{externalDocsDescription || externalDocsUrl}
</Link>
) : null}
</div>
)
}
}
export default Info

View File

@@ -0,0 +1,52 @@
/**
* @prettier
*/
import React from "react"
import PropTypes from "prop-types"
import { safeBuildUrl } from "core/utils/url"
import { sanitizeUrl } from "core/utils"
class License extends React.Component {
static propTypes = {
license: PropTypes.object,
getComponent: PropTypes.func.isRequired,
selectedServer: PropTypes.string,
url: PropTypes.string.isRequired,
}
render() {
const { license, getComponent, selectedServer, url: specUrl } = this.props
const name = license.get("name", "License")
const url = sanitizeUrl(
safeBuildUrl(license.get("url"), specUrl, { selectedServer })
)
const identifier = license.get("identifier", "")
const spdxURL = sanitizeUrl(`https://spdx.org/licenses/${identifier}.html`)
const Link = getComponent("Link")
return (
<div className="info__license">
{identifier && (
<div className="info__license__url">
<Link target="_blank" href={spdxURL}>
{name}
</Link>
</div>
)}
{url && !identifier && (
<div className="info__license__url">
<Link target="_blank" href={url}>
{name}
</Link>
</div>
)}
{!url && !identifier && <span>{name}</span>}
</div>
)
}
}
export default License

View File

@@ -1,4 +1,3 @@
// OpenAPI 3.1 feature
import React from "react"
import PropTypes from "prop-types"
import { fromJS } from "immutable"
@@ -9,7 +8,7 @@ import ImPropTypes from "react-immutable-proptypes"
const Webhooks = (props) => {
const { specSelectors, getComponent, specPath } = props
const webhooksPathItems = specSelectors.selectWebhooks() // OrderedMap
const webhooksPathItems = specSelectors.webhooks()
if (!webhooksPathItems || webhooksPathItems?.size < 1) {
return null
}

View File

@@ -0,0 +1,10 @@
/**
* @prettier
*/
export const isOAS31 = (jsSpec) => {
const oasVersion = jsSpec.get("openapi")
return (
typeof oasVersion === "string" && /^3\.1\.(?:[1-9]\d*|0)$/.test(oasVersion)
)
}

View File

@@ -0,0 +1,37 @@
/**
* @prettier
*/
import Webhooks from "./components/webhooks"
import License from "./components/license"
import Info from "./components/info"
import LicenseWrapper from "./wrap-components/license"
import InfoWrapper from "./wrap-components/info"
import { isOAS31, webhooks } from "./spec-extensions/selectors"
import { isOAS3 } from "./spec-extensions/wrap-selectors"
const OAS31Plugin = () => {
return {
components: {
Webhooks,
OAS31Info: Info,
OAS31License: License,
},
wrapComponents: {
License: LicenseWrapper,
info: InfoWrapper,
},
statePlugins: {
spec: {
selectors: {
isOAS31,
webhooks,
},
wrapSelectors: {
isOAS3,
},
},
},
}
}
export default OAS31Plugin

View File

@@ -0,0 +1,29 @@
/**
* @prettier
*/
import { Map } from "immutable"
import { isOAS31 as isOAS31Helper } from "../helpers"
export const isOAS31 = () => (system) => {
const spec = system.specSelectors.specJson()
return isOAS31Helper(spec)
}
const onlyOAS31 =
(selector) =>
() =>
(system, ...args) => {
if (system.getSystem().specSelectors.isOAS31()) {
const result = selector(...args)
return typeof result === "function" ? result(system, ...args) : result
} else {
return null
}
}
export const webhooks = onlyOAS31(() => (system) => {
const spec = system.specSelectors.specJson()
return spec.get("webhooks", webhooks.mapConst)
})
webhooks.mapConst = Map()

View File

@@ -0,0 +1,4 @@
export const isOAS3 = (oriSelector, system) => (state, ...args) => {
const isOAS31 = system.specSelectors.isOAS31()
return isOAS31 || oriSelector(...args)
}

View File

@@ -0,0 +1,16 @@
/**
* @prettier
*/
import React from "react"
const InfoWrapper = (Original, system) => (props) => {
if (system.specSelectors.isOAS31()) {
const OAS31Info = system.getComponent("OAS31Info")
return <OAS31Info {...props} />
}
return <Original {...props} />
}
export default InfoWrapper

View File

@@ -0,0 +1,16 @@
/**
* @prettier
*/
import React from "react"
const LicenseWrapper = (Original, system) => (props) => {
if (system.specSelectors.isOAS31()) {
const OAS31License = system.getComponent("OAS31License")
return <OAS31License {...props} />
}
return <Original {...props} />
}
export default LicenseWrapper

View File

@@ -1,12 +1,10 @@
/**
* @prettier
*/
import BasePreset from "./base"
import OAS3Plugin from "../plugins/oas3"
// Just the base, for now.
import OAS31Plugin from "../plugins/oas31"
export default function PresetApis() {
return [
BasePreset,
OAS3Plugin
]
return [BasePreset, OAS3Plugin, OAS31Plugin]
}

View File

@@ -1,3 +1,6 @@
/**
* @prettier
*/
import err from "core/plugins/err"
import layout from "core/plugins/layout"
import spec from "core/plugins/spec"
@@ -57,13 +60,10 @@ import Errors from "core/components/errors"
import ContentType from "core/components/content-type"
import Overview from "core/components/overview"
import InitializedInput from "core/components/initialized-input"
import Info, {
InfoUrl,
InfoBasePath,
License,
Contact,
} from "core/components/info"
import Info, { InfoUrl, InfoBasePath } from "core/components/info"
import InfoContainer from "core/containers/info"
import Contact from "core/components/contact"
import License from "core/components/license"
import JumpToPath from "core/components/jump-to-path"
import CopyToClipboardBtn from "core/components/copy-to-clipboard-btn"
import Footer from "core/components/footer"
@@ -87,16 +87,12 @@ import VersionPragmaFilter from "core/components/version-pragma-filter"
import VersionStamp from "core/components/version-stamp"
import DeepLink from "core/components/deep-link"
import SvgAssets from "core/components/svg-assets"
import Markdown from "core/components/providers/markdown"
import BaseLayout from "core/components/layouts/base"
import * as LayoutUtils from "core/components/layout-utils"
import * as JsonSchemaComponents from "core/json-schema-components"
export default function() {
export default function () {
let coreComponents = {
components: {
App,
@@ -115,6 +111,10 @@ export default function() {
InitializedInput,
info: Info,
InfoContainer,
InfoUrl,
InfoBasePath,
Contact,
License,
JumpToPath,
CopyToClipboardBtn,
onlineValidatorBadge: OnlineValidatorBadge,
@@ -163,23 +163,19 @@ export default function() {
OperationTag,
OperationContainer,
DeepLink,
InfoUrl,
InfoBasePath,
License,
Contact,
SvgAssets,
Example,
ExamplesSelect,
ExamplesSelectValueRetainer,
}
},
}
let formComponents = {
components: LayoutUtils
components: LayoutUtils,
}
let jsonSchemaComponents = {
components: JsonSchemaComponents
components: JsonSchemaComponents,
}
return [

View File

@@ -1,4 +1,5 @@
swagger: 2.0.0
swagger: "2.0"
info:
title: OpenAPI 2.0 Info Object
version: 1.0.0

View File

@@ -1,4 +1,4 @@
swagger: 2.0.0
swagger: "2.0"
info:
title: OpenAPI 2.0 License with only url present
version: 1.0.0

View File

@@ -5,5 +5,4 @@ info:
description: This is a sample server for a pet store.
license:
name: Apache 2.0
# url: https://www.apache.org/licenses/LICENSE-2.0.html
identifier: Apache-2.0 # mutually exclusive of url; separately, for json_schema, consider const, prefix, array items can be of a different type (current assumption is all array items are the same)
identifier: Apache-2.0

View File

@@ -1,11 +1,13 @@
describe("Render Info Component", () => {
describe("OpenAPI 2.x", () => {
describe("OpenAPI 2.0", () => {
const baseUrl = "/?url=/documents/features/info-openAPI2.yaml"
it("should render Info Description", () => {
cy.visit(baseUrl)
.get(".info .description")
.should("contains.text", "This is a sample")
})
it("should render Info Main anchor target xss link with safe `rel` attributes", () => {
cy.visit(baseUrl)
.get(".info .main > a")
@@ -16,19 +18,23 @@ describe("Render Info Component", () => {
.should("have.attr", "target")
.and("equal", "_blank")
})
it("should not render Info Summary (an OpenAPI 3.1 field)", () => {
cy.visit(baseUrl)
.get(".info__summary")
.should("not.exist")
})
})
describe("OpenAPI 3.0.x", () => {
const baseUrl = "/?url=/documents/features/info-openAPI30.yaml"
it("should render Info Description", () => {
cy.visit(baseUrl)
.get(".info .description")
.should("contains.text", "This is a sample")
})
it("should render Info Main anchor target xss link with safe `rel` attributes", () => {
cy.visit(baseUrl)
.get(".info .main > a")
@@ -39,19 +45,23 @@ describe("Render Info Component", () => {
.should("have.attr", "target")
.and("equal", "_blank")
})
it("should not render Info Summary (an OpenAPI 3.1 field)", () => {
cy.visit(baseUrl)
.get(".info__summary")
.should("not.exist")
})
})
describe("OpenAPI 3.1.x", () => {
const baseUrl = "/?url=/documents/features/info-openAPI31.yaml"
it("should render Info Description", () => {
cy.visit(baseUrl)
.get(".info .description")
.should("contains.text", "This is a sample")
})
it("should render Info Main anchor target xss link with safe `rel` attributes", () => {
cy.visit(baseUrl)
.get(".info .main > a")

View File

@@ -1,18 +1,20 @@
/**
* @prettier
*/
describe("Render License Component", () => {
describe("OpenAPI 2", () =>{
describe("OpenAPI 2.0", () => {
const baseUrl = "/?url=/documents/features/license-openAPI2.yaml"
it("should render License URL", () => {
cy.visit(baseUrl)
.get(".info__license")
.should("exist")
.should("contains.text", "Apache 2.0")
.should("not.contains.text", "SPDX License")
.get(".info__license__identifier")
.should("not.exist")
})
it("should render License URL anchor target xss link with safe `rel` attributes ", () => {
it("should render License URL anchor target xss link with safe `rel` attributes", () => {
cy.visit(baseUrl)
.get(".info__license > .link")
.get(".info__license__url > .link")
.should("have.attr", "rel")
.and("include", "noopener")
.and("include", "noreferrer")
@@ -21,18 +23,18 @@ describe("Render License Component", () => {
.and("equal", "_blank")
})
})
describe("OpenAPI 3.0.x", () => {
const baseUrl = "/?url=/documents/features/license-openAPI30.yaml"
it("should render License URL", () => {
cy.visit(baseUrl)
.get(".info__license__url")
.should("exist")
.should("contains.text", "Apache 2.0")
.should("not.contains.text", "SPDX License")
.get(".info__license__identifier")
.should("not.exist")
})
it("should render URL anchor target xss link with safe `rel` attributes ", () => {
})
it("should render URL anchor target xss link with safe `rel` attributes", () => {
cy.visit(baseUrl)
.get(".info__license__url > a")
.should("have.attr", "rel")
@@ -43,19 +45,22 @@ describe("Render License Component", () => {
.and("equal", "_blank")
})
})
describe("OpenAPI 3.1.x", () => {
describe("only URL", () => {
describe("given URL field", () => {
const baseUrl = "/?url=/documents/features/license-openAPI31-url.yaml"
it("should render URL", () => {
cy.visit(baseUrl)
.get(".info__license__url")
.should("exist")
.should("contains.text", "Apache 2.0")
.should("not.contains.text", "SPDX License")
.get(".info__license__identifier")
.should("not.exist")
})
it("should render URL anchor target xss link with safe `rel` attributes ", () => {
.get(".info__license__url > a")
.should("have.attr", "href")
.and("equal", "https://www.apache.org/licenses/LICENSE-2.0.html")
})
it("should render URL anchor target xss link with safe `rel` attributes", () => {
cy.visit(baseUrl)
.get(".info__license__url > a")
.should("have.attr", "rel")
@@ -66,40 +71,43 @@ describe("Render License Component", () => {
.and("equal", "_blank")
})
})
describe("only SPDX Identifier", () => {
const baseUrl = "/?url=/documents/features/license-openAPI31-identifier.yaml"
it("should render SPDX Identifier", () => {
describe("given identifier field", () => {
const baseUrl =
"/?url=/documents/features/license-openAPI31-identifier.yaml"
it("should render URL using identifier", () => {
cy.visit(baseUrl)
.get(".info__license__identifier")
.should("exist")
.should("contains.text", "Apache-2.0")
.should("contains.text", "SPDX License")
.get(".info__license__url")
.should("not.exist")
})
it("should render SPDX and Identifier anchor target xss links with safe `rel` attributes ", () => {
.should("exist")
.should("contains.text", "Apache 2.0")
.get(".info__license__url > a")
.should("have.attr", "href")
.and("equal", "https://spdx.org/licenses/Apache-2.0.html")
})
it("should render URL anchor target xss links with safe `rel` attributes", () => {
cy.visit(baseUrl)
.get(".info__license__identifier > a")
.each(($el) => {
cy.get($el)
.should("have.attr", "rel")
.and("include", "noopener")
.and("include", "noreferrer")
cy.get($el)
.should("have.attr", "target")
.and("equal", "_blank")
})
.get(".info__license__url > a")
.should("have.attr", "rel")
.and("include", "noopener")
.and("include", "noreferrer")
.get(".info .main > a")
.should("have.attr", "target")
.and("equal", "_blank")
})
})
describe("URL and SPX are mutually exclusive", () => {
it("should render nothing if both URL & SPDX exists", () => {
const baseUrl = "/?url=/documents/features/license-openAPI31-error-both-identifier-and-url.yaml"
const baseUrl =
"/?url=/documents/features/license-openAPI31-error-both-identifier-and-url.yaml"
cy.visit(baseUrl)
.get(".info__license__identifier")
.should("not.exist")
.get(".info__license__url")
.should("not.exist")
})
})
})
})
})

View File

@@ -1,7 +1,7 @@
import { fromJS } from "immutable"
import { isOAS3, isSwagger2 } from "corePlugins/oas3/helpers"
import { isOAS30, isSwagger2 } from "corePlugins/oas3/helpers"
const isOAS3Shorthand = (version) => isOAS3(fromJS({
const isOAS3Shorthand = (version) => isOAS30(fromJS({
openapi: version
}))
@@ -13,7 +13,7 @@ describe("isOAS3", function () {
it("should recognize valid OAS3 version values", function () {
expect(isOAS3Shorthand("3.0.0")).toEqual(true)
expect(isOAS3Shorthand("3.0.1")).toEqual(true)
expect(isOAS3Shorthand("3.0.11111")).toEqual(true)
expect(isOAS3Shorthand("3.0.11111")).toEqual(false)
expect(isOAS3Shorthand("3.0.0-rc0")).toEqual(true)
})
@@ -31,7 +31,7 @@ describe("isOAS3", function () {
})
it("should gracefully fail when `openapi` field is missing", function () {
expect(isOAS3(fromJS({
expect(isOAS30(fromJS({
openApi: "3.0.0"
}))).toEqual(false)
expect(isOAS3Shorthand(null)).toEqual(false)
@@ -41,7 +41,7 @@ describe("isOAS3", function () {
describe("isSwagger2", function () {
it("should recognize valid Swagger 2.0 version values", function () {
expect(isSwagger2Shorthand("2.0")).toEqual(true)
expect(isSwagger2Shorthand("2.0-rc0")).toEqual(true)
expect(isSwagger2Shorthand("2.0-rc0")).toEqual(false)
})
it("should fail for invalid Swagger 2.0 version values", function () {

View File

@@ -26,7 +26,8 @@ describe("OAS3 plugin - state", function() {
specSelectors: {
specJson: () => {
return fromJS({ openapi: "3.0.0" })
}
},
isOAS3: () => true,
}
}
}
@@ -55,7 +56,8 @@ describe("OAS3 plugin - state", function() {
specSelectors: {
specJson: () => {
return fromJS({ openapi: "3.0.0" })
}
},
isOAS3: () => true,
}
}
}
@@ -88,7 +90,8 @@ describe("OAS3 plugin - state", function() {
specSelectors: {
specJson: () => {
return fromJS({ openapi: "3.0.0" })
}
},
isOAS3: () => true,
}
}
}
@@ -124,7 +127,8 @@ describe("OAS3 plugin - state", function() {
specSelectors: {
specJson: () => {
return fromJS({ openapi: "3.0.0" })
}
},
isOAS3: () => true,
}
}
}
@@ -167,7 +171,8 @@ describe("OAS3 plugin - state", function() {
specSelectors: {
specJson: () => {
return fromJS({ openapi: "3.0.0" })
}
},
isOAS3: () => true,
}
}
}
@@ -207,7 +212,8 @@ describe("OAS3 plugin - state", function() {
specSelectors: {
specJson: () => {
return fromJS({ openapi: "3.0.0" })
}
},
isOAS3: () => true,
}
}
}
@@ -261,7 +267,8 @@ describe("OAS3 plugin - state", function() {
specSelectors: {
specJson: () => {
return fromJS({ openapi: "3.0.0" })
}
},
isOAS3: () => true,
}
}
}
@@ -314,7 +321,8 @@ describe("OAS3 plugin - state", function() {
specSelectors: {
specJson: () => {
return fromJS({ openapi: "3.0.0" })
}
},
isOAS3: () => true,
}
}
}
@@ -342,7 +350,8 @@ describe("OAS3 plugin - state", function() {
specSelectors: {
specJson: () => {
return fromJS({ openapi: "3.0.0" })
}
},
isOAS3: () => true,
}
}
}

View File

@@ -17,6 +17,7 @@ describe("oas3 plugin - auth extensions - wrapSelectors", function(){
specJson: () => fromJS({
openapi: "3.0.0"
}),
isOAS3: () => true,
securityDefinitions: () => {
return fromJS({
"oauth2AuthorizationCode": {

View File

@@ -1,8 +1,5 @@
import { fromJS } from "immutable"
import {
definitions
} from "corePlugins/oas3/spec-extensions/wrap-selectors"
import { definitions } from "corePlugins/oas3/spec-extensions/wrap-selectors"
describe("oas3 plugin - spec extensions - wrapSelectors", function(){
@@ -28,6 +25,7 @@ describe("oas3 plugin - spec extensions - wrapSelectors", function(){
getSystem: () => system,
specSelectors: {
specJson: () => spec,
isOAS3: () => true,
}
}
@@ -57,6 +55,7 @@ describe("oas3 plugin - spec extensions - wrapSelectors", function(){
getSystem: () => system,
specSelectors: {
specJson: () => spec,
isOAS3: () => true,
}
}
@@ -82,7 +81,8 @@ describe("oas3 plugin - spec extensions - wrapSelectors", function(){
getSystem: () => system,
specSelectors: {
specJson: () => spec,
}
isOAS3: () => true,
},
}
// When

View File

@@ -1,7 +1,9 @@
import React from "react"
import { render } from "enzyme"
import { fromJS } from "immutable"
import Info, { InfoUrl, License } from "components/info"
import Info, { InfoUrl } from "components/info"
import Contact from "components/contact"
import License from "components/license"
import { Link } from "components/layout-utils"
import Markdown from "components/providers/markdown"
@@ -11,7 +13,8 @@ describe("<Info/> Anchor Target Safety", function(){
Markdown,
InfoUrl,
License,
Link
Contact,
Link,
}
const baseProps = {
getComponent: c => components[c] || dummyComponent,