Skip to content

Commit d9f5691

Browse files
geraldglynntim-lai
andauthored
fix(OAS3): relative urls (#5341)
* Added tooling for appending OAS3 relative URLs to selected Server Info * Terms of service URL * Contact URL * License URL * External Docs URL Tag * Tag External Docs URL Operation * Operation External Docs ** Operation Tag Co-authored-by: Tim Lai <[email protected]>
1 parent 225a915 commit d9f5691

File tree

11 files changed

+174
-31
lines changed

11 files changed

+174
-31
lines changed

src/core/components/info.jsx

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React from "react"
22
import PropTypes from "prop-types"
3-
import { fromJS } from "immutable"
43
import ImPropTypes from "react-immutable-proptypes"
54
import { sanitizeUrl } from "core/utils"
5+
import { buildUrl } from "core/utils/url"
66

77

88
export class InfoBasePath extends React.Component {
@@ -26,13 +26,16 @@ export class InfoBasePath extends React.Component {
2626
class Contact extends React.Component {
2727
static propTypes = {
2828
data: PropTypes.object,
29-
getComponent: PropTypes.func.isRequired
29+
getComponent: PropTypes.func.isRequired,
30+
specSelectors: PropTypes.object.isRequired,
31+
selectedServer: PropTypes.string,
32+
url: PropTypes.string.isRequired,
3033
}
3134

3235
render(){
33-
let { data, getComponent } = this.props
36+
let { data, getComponent, selectedServer, url: specUrl} = this.props
3437
let name = data.get("name") || "the developer"
35-
let url = data.get("url")
38+
let url = buildUrl(data.get("url"), specUrl, {selectedServer})
3639
let email = data.get("email")
3740

3841
const Link = getComponent("Link")
@@ -53,17 +56,18 @@ class Contact extends React.Component {
5356
class License extends React.Component {
5457
static propTypes = {
5558
license: PropTypes.object,
56-
getComponent: PropTypes.func.isRequired
57-
59+
getComponent: PropTypes.func.isRequired,
60+
specSelectors: PropTypes.object.isRequired,
61+
selectedServer: PropTypes.string,
62+
url: PropTypes.string.isRequired,
5863
}
5964

6065
render(){
61-
let { license, getComponent } = this.props
66+
let { license, getComponent, selectedServer, url: specUrl } = this.props
6267

6368
const Link = getComponent("Link")
64-
65-
let name = license.get("name") || "License"
66-
let url = license.get("url")
69+
let name = license.get("name") || "License"
70+
let url = buildUrl(license.get("url"), specUrl, {selectedServer})
6771

6872
return (
6973
<div className="info__license">
@@ -88,7 +92,7 @@ export class InfoUrl extends React.PureComponent {
8892

8993
const Link = getComponent("Link")
9094

91-
return <Link target="_blank" href={ sanitizeUrl(url) }><span className="url"> { url } </span></Link>
95+
return <Link target="_blank" href={ sanitizeUrl(url) }><span className="url"> { url }</span></Link>
9296
}
9397
}
9498

@@ -100,17 +104,21 @@ export default class Info extends React.Component {
100104
basePath: PropTypes.string,
101105
externalDocs: ImPropTypes.map,
102106
getComponent: PropTypes.func.isRequired,
107+
oas3selectors: PropTypes.func,
108+
selectedServer: PropTypes.string,
103109
}
104110

105111
render() {
106-
let { info, url, host, basePath, getComponent, externalDocs } = this.props
112+
let { info, url, host, basePath, getComponent, externalDocs, selectedServer, url: specUrl } = this.props
107113
let version = info.get("version")
108114
let description = info.get("description")
109115
let title = info.get("title")
110-
let termsOfService = info.get("termsOfService")
116+
let termsOfServiceUrl = buildUrl(info.get("termsOfService"), specUrl, {selectedServer})
111117
let contact = info.get("contact")
112118
let license = info.get("license")
113-
const { url:externalDocsUrl, description:externalDocsDescription } = (externalDocs || fromJS({})).toJS()
119+
let rawExternalDocsUrl = externalDocs && externalDocs.get("url")
120+
let externalDocsUrl = buildUrl(rawExternalDocsUrl, specUrl, {selectedServer})
121+
let externalDocsDescription = externalDocs && externalDocs.get("description")
114122

115123
const Markdown = getComponent("Markdown", true)
116124
const Link = getComponent("Link")
@@ -133,14 +141,14 @@ export default class Info extends React.Component {
133141
</div>
134142

135143
{
136-
termsOfService && <div className="info__tos">
137-
<Link target="_blank" href={ sanitizeUrl(termsOfService) }>Terms of service</Link>
144+
termsOfServiceUrl && <div className="info__tos">
145+
<Link target="_blank" href={ sanitizeUrl(termsOfServiceUrl) }>Terms of service</Link>
138146
</div>
139147
}
140148

141-
{contact && contact.size ? <Contact getComponent={getComponent} data={ contact } /> : null }
142-
{license && license.size ? <License getComponent={getComponent} license={ license } /> : null }
143-
{ externalDocsUrl ?
149+
{contact && contact.size ? <Contact getComponent={getComponent} data={ contact } selectedServer={selectedServer} url={url} /> : null }
150+
{license && license.size ? <License getComponent={getComponent} license={ license } selectedServer={selectedServer} url={url}/> : null }
151+
{ externalDocs ?
144152
<Link className="info__extdocs" target="_blank" href={sanitizeUrl(externalDocsUrl)}>{externalDocsDescription || externalDocsUrl}</Link>
145153
: null }
146154

src/core/components/operation-tag.jsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import PropTypes from "prop-types"
33
import ImPropTypes from "react-immutable-proptypes"
44
import Im from "immutable"
55
import { createDeepLinkPath, escapeDeepLinkPath, sanitizeUrl } from "core/utils"
6+
import { buildUrl } from "core/utils/url"
67

78
export default class OperationTag extends React.Component {
89

@@ -15,12 +16,15 @@ export default class OperationTag extends React.Component {
1516
tagObj: ImPropTypes.map.isRequired,
1617
tag: PropTypes.string.isRequired,
1718

19+
oas3Selectors: PropTypes.func.isRequired,
1820
layoutSelectors: PropTypes.object.isRequired,
1921
layoutActions: PropTypes.object.isRequired,
2022

2123
getConfigs: PropTypes.func.isRequired,
2224
getComponent: PropTypes.func.isRequired,
2325

26+
specUrl: PropTypes.string.isRequired,
27+
2428
children: PropTypes.element,
2529
}
2630

@@ -29,11 +33,12 @@ export default class OperationTag extends React.Component {
2933
tagObj,
3034
tag,
3135
children,
32-
36+
oas3Selectors,
3337
layoutSelectors,
3438
layoutActions,
3539
getConfigs,
3640
getComponent,
41+
specUrl,
3742
} = this.props
3843

3944
let {
@@ -50,7 +55,8 @@ export default class OperationTag extends React.Component {
5055

5156
let tagDescription = tagObj.getIn(["tagDetails", "description"], null)
5257
let tagExternalDocsDescription = tagObj.getIn(["tagDetails", "externalDocs", "description"])
53-
let tagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"])
58+
let rawTagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"])
59+
let tagExternalDocsUrl = buildUrl( rawTagExternalDocsUrl, specUrl, {selectedServer: oas3Selectors.selectedServer()} )
5460

5561
let isShownKey = ["operations-tag", tag]
5662
let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list")

src/core/components/operation.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { PureComponent } from "react"
22
import PropTypes from "prop-types"
33
import { getList } from "core/utils"
44
import { getExtensions, sanitizeUrl, escapeDeepLinkPath } from "core/utils"
5+
import { buildUrl } from "core/utils/url"
56
import { Iterable, List } from "immutable"
67
import ImPropTypes from "react-immutable-proptypes"
78

@@ -81,6 +82,7 @@ export default class Operation extends PureComponent {
8182
schemes
8283
} = op
8384

85+
const externalDocsUrl = externalDocs ? buildUrl(externalDocs.url, specSelectors.url(), { selectedServer: oas3Selectors.selectedServer() }) : ""
8486
let operation = operationProps.getIn(["op"])
8587
let responses = operation.get("responses")
8688
let parameters = getList(operation, ["parameters"])
@@ -127,14 +129,14 @@ export default class Operation extends PureComponent {
127129
</div>
128130
}
129131
{
130-
externalDocs && externalDocs.url ?
132+
externalDocsUrl ?
131133
<div className="opblock-external-docs-wrapper">
132134
<h4 className="opblock-title_normal">Find more details</h4>
133135
<div className="opblock-external-docs">
134136
<span className="opblock-external-docs__description">
135137
<Markdown source={ externalDocs.description } />
136138
</span>
137-
<Link target="_blank" className="opblock-external-docs__link" href={sanitizeUrl(externalDocs.url)}>{externalDocs.url}</Link>
139+
<Link target="_blank" className="opblock-external-docs__link" href={sanitizeUrl(externalDocsUrl)}>{externalDocsUrl}</Link>
138140
</div>
139141
</div> : null
140142
}

src/core/components/operations.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export default class Operations extends React.Component {
1616
specActions: PropTypes.object.isRequired,
1717
oas3Actions: PropTypes.object.isRequired,
1818
getComponent: PropTypes.func.isRequired,
19+
oas3Selectors: PropTypes.func.isRequired,
1920
layoutSelectors: PropTypes.object.isRequired,
2021
layoutActions: PropTypes.object.isRequired,
2122
authActions: PropTypes.object.isRequired,
@@ -28,6 +29,7 @@ export default class Operations extends React.Component {
2829
let {
2930
specSelectors,
3031
getComponent,
32+
oas3Selectors,
3133
layoutSelectors,
3234
layoutActions,
3335
getConfigs,
@@ -65,10 +67,12 @@ export default class Operations extends React.Component {
6567
key={"operation-" + tag}
6668
tagObj={tagObj}
6769
tag={tag}
70+
oas3Selectors={oas3Selectors}
6871
layoutSelectors={layoutSelectors}
6972
layoutActions={layoutActions}
7073
getConfigs={getConfigs}
71-
getComponent={getComponent}>
74+
getComponent={getComponent}
75+
specUrl={specSelectors.url()}>
7276
{
7377
operations.map( op => {
7478
const path = op.get("path")

src/core/containers/info.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,26 @@ export default class InfoContainer extends React.Component {
77
specActions: PropTypes.object.isRequired,
88
specSelectors: PropTypes.object.isRequired,
99
getComponent: PropTypes.func.isRequired,
10+
oas3Selectors: PropTypes.func.isRequired,
1011
}
1112

1213
render () {
13-
const {specSelectors, getComponent} = this.props
14+
const {specSelectors, getComponent, oas3Selectors} = this.props
1415

1516
const info = specSelectors.info()
1617
const url = specSelectors.url()
1718
const basePath = specSelectors.basePath()
1819
const host = specSelectors.host()
1920
const externalDocs = specSelectors.externalDocs()
21+
const selectedServer = oas3Selectors.selectedServer()
2022

2123
const Info = getComponent("info")
2224

2325
return (
2426
<div>
2527
{info && info.count() ? (
2628
<Info info={info} url={url} host={host} basePath={basePath} externalDocs={externalDocs}
27-
getComponent={getComponent}/>
29+
getComponent={getComponent} selectedServer={selectedServer} />
2830
) : null}
2931
</div>
3032
)

src/core/utils.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ export const validatePattern = (val, rxPattern) => {
399399

400400
// validation of parameters before execute
401401
export const validateParam = (param, value, { isOAS3 = false, bypassRequiredCheck = false } = {}) => {
402-
402+
403403
let errors = []
404404

405405
let paramRequired = param.get("required")
@@ -436,7 +436,7 @@ export const validateParam = (param, value, { isOAS3 = false, bypassRequiredChec
436436
let objectStringCheck = type === "object" && typeof value === "string" && value
437437

438438
const allChecks = [
439-
stringCheck, arrayCheck, arrayListCheck, arrayStringCheck, fileCheck,
439+
stringCheck, arrayCheck, arrayListCheck, arrayStringCheck, fileCheck,
440440
booleanCheck, numberCheck, integerCheck, objectCheck, objectStringCheck,
441441
]
442442

@@ -640,7 +640,6 @@ export function sanitizeUrl(url) {
640640
return braintreeSanitizeUrl(url)
641641
}
642642

643-
644643
export function requiresValidationURL(uri) {
645644
if (!uri || uri.indexOf("localhost") >= 0 || uri.indexOf("127.0.0.1") >= 0 || uri === "none") {
646645
return false

src/core/utils/url.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export function isAbsoluteUrl(url) {
2+
return url.match(/^(?:[a-z]+:)?\/\//i) // Matches http://, HTTP://, https://, ftp://, //example.com,
3+
}
4+
5+
export function addProtocol(url) {
6+
if(!url.match(/^\/\//i)) return url // Checks if protocol is missing e.g. //example.com
7+
return `${window.location.protocol}${url}`
8+
}
9+
10+
export function buildBaseUrl(selectedServer, specUrl) {
11+
if(!selectedServer) return specUrl
12+
if(isAbsoluteUrl(selectedServer)) return addProtocol(selectedServer)
13+
14+
return new URL(selectedServer, specUrl).href
15+
}
16+
17+
export function buildUrl(url, specUrl, { selectedServer="" } = {}) {
18+
if(!url) return
19+
if(isAbsoluteUrl(url)) return url
20+
21+
const baseUrl = buildBaseUrl(selectedServer, specUrl)
22+
return new URL(url, baseUrl).href
23+
}

test/mocha/components/info-wrapper.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ describe("<InfoContainer/>", function () {
1717
url () {},
1818
basePath () {},
1919
host () {},
20-
externalDocs () {}
20+
externalDocs () {},
21+
},
22+
oas3Selectors: {
23+
selectedServer () {},
2124
},
2225
getComponent: c => components[c]
2326
}

test/mocha/components/operations.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe("<Operations/>", function(){
2929
},
3030
specSelectors: {
3131
isOAS3() { return false },
32+
url() { return "https://petstore.swagger.io/v2/swagger.json" },
3233
taggedOperations() {
3334
return fromJS({
3435
"default": {
@@ -83,6 +84,7 @@ describe("<Operations/>", function(){
8384
},
8485
specSelectors: {
8586
isOAS3() { return true },
87+
url() { return "https://petstore.swagger.io/v2/swagger.json" },
8688
taggedOperations() {
8789
return fromJS({
8890
"default": {

0 commit comments

Comments
 (0)