Compare commits

...
5 Commits
Author SHA1 Message Date
Kyle Shockey f784472857 WIP 2018-04-26 22:23:00 -07:00
Kyle Shockey c888daf9b4 fix: path-item $ref operation metadata storage 2018-04-27 10:25:26 -07:00
Helder Sepulveda cbff0251ae feat: option to show common query parameters (#4245)
* extend getExtensions

Add optional param to getExtensions that can retrieve more stuff

* Add getCommonExtensions

* Trim trailing spaces

* Remove unused parameter

* Move the format inline with the param type

* correction to UnitTest
2018-04-26 21:18:45 -07:00
kyle 62354568a9 feat: request/response interceptors for remote config fetch (#4484) 2018-04-26 21:04:55 -07:00
kyle d981f0f26e v3.13.6 (#4472) 2018-04-24 00:18:38 -07:00
22 changed files with 326 additions and 62 deletions
+81
View File
@@ -0,0 +1,81 @@
openapi: "3.0.0"
info:
version: 1.0.0
title: Brentertainment OAuth2 Test Server
description: >
The server does not support CORS (yet?), so you need to take the strange step of disabling Same-Origin Policy in your browser.
[Here](https://www.thepolyglotdeveloper.com/2014/08/bypass-cors-errors-testing-apis-locally/) are some instructions.
license:
name: MIT
servers:
- url: http://brentertainment.com/oauth2/lockdin/
paths:
/resource:
get:
description: Protected resource
security:
- oauth2AuthorizationCode: ["resource"]
- oauth2Implicit: ["resource"]
- oauth2Password: ["resource"]
- oauth2ClientCredentials: ["resource"]
- oauth2Multiflow: ["resource"]
responses:
200:
description: the only response
content:
application/json:
schema:
type: array
items:
type: string
components:
securitySchemes:
oauth2AuthorizationCode:
type: oauth2
flows:
authorizationCode:
authorizationUrl: "http://brentertainment.com/oauth2/lockdin/authorize"
tokenUrl: "http://brentertainment.com/oauth2/lockdin/token"
scopes:
resource: our only scope
oauth2Implicit:
type: oauth2
flows:
implicit:
authorizationUrl: "http://brentertainment.com/oauth2/lockdin/authorize"
scopes:
resource: our only scope
oauth2Password:
type: oauth2
flows:
password:
tokenUrl: "http://brentertainment.com/oauth2/lockdin/token"
scopes:
resource: our only scope
oauth2ClientCredentials:
type: oauth2
flows:
clientCredentials:
tokenUrl: "http://brentertainment.com/oauth2/lockdin/token"
scopes:
resource: our only scope
oauth2Multiflow:
type: oauth2
flows:
clientCredentials:
tokenUrl: "http://brentertainment.com/oauth2/lockdin/token"
scopes:
resource: our only scope
password:
tokenUrl: "http://brentertainment.com/oauth2/lockdin/token"
scopes:
resource: our only scope
implicit:
authorizationUrl: "http://brentertainment.com/oauth2/lockdin/authorize"
scopes:
resource: our only scope
authorizationCode:
authorizationUrl: "http://brentertainment.com/oauth2/lockdin/authorize"
tokenUrl: "http://brentertainment.com/oauth2/lockdin/token"
scopes:
resource: our only scope
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -58,6 +58,7 @@ Parameter Name | Description
`maxDisplayedTags` | `Number`. If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations.
`operationsSorter` | `Function=(a => a)`. Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). Default is the order returned by the server unchanged.
`showExtensions` | `Boolean=false`. Controls the display of vendor extension (`x-`) fields and values for Operations, Parameters, and Schema.
`showCommonExtensions` | `Boolean=false`. Controls the display of extensions (`pattern`, `maxLength`, `minLength`, `maximum`, `minimum`) fields and values for Parameters.
`tagsSorter` | `Function=(a => a)`. Apply a sort to the tag list of each API. It can be 'alpha' (sort by paths alphanumerically) or a function (see [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) to learn how to write a sort function). Two tag name strings are passed to the sorter for each pass. Default is the order determined by Swagger-UI.
`onComplete` | `Function=NOOP`. Provides a mechanism to be notified when Swagger-UI has finished rendering a newly provided definition.
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "swagger-ui",
"version": "3.13.5",
"version": "3.13.6",
"main": "dist/swagger-ui.js",
"repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [
@@ -84,7 +84,7 @@
"scroll-to-element": "^2.0.0",
"serialize-error": "2.0.0",
"shallowequal": "0.2.2",
"swagger-client": "^3.7.1",
"swagger-client": "^3.7.2",
"url-parse": "^1.1.8",
"whatwg-fetch": "0.11.1",
"worker-loader": "^0.7.1",
+5 -2
View File
@@ -99,10 +99,13 @@ export default class Auths extends React.Component {
{
definitions.filter( schema => schema.get("type") === "oauth2")
.map( (schema, name) =>{
return (<div key={ name }>
const { flow } = schema
const key = [name, flow].join("__").slice(0, -2)
return (<div key={ key }>
<Oauth2 authorized={ authorized }
schema={ schema }
name={ name } />
authId={ key }
name={ key || schema.get("name") || name } />
</div>)
}
).toArray()
+8 -8
View File
@@ -47,11 +47,11 @@ export default class Oauth2 extends React.Component {
}
authorize =() => {
let { authActions, errActions, getConfigs, authSelectors } = this.props
let { authActions, errActions, getConfigs, authSelectors, authId } = this.props
let configs = getConfigs()
let authConfigs = authSelectors.getConfigs()
errActions.clear({authId: name,type: "auth", source: "auth"})
errActions.clear({authId: authId ,type: "auth", source: "auth"})
oauth2Authorize({auth: this.state, authActions, errActions, configs, authConfigs })
}
@@ -79,15 +79,15 @@ export default class Oauth2 extends React.Component {
logout =(e) => {
e.preventDefault()
let { authActions, errActions, name } = this.props
let { authActions, errActions, authId } = this.props
errActions.clear({authId: name, type: "auth", source: "auth"})
authActions.logout([ name ])
errActions.clear({authId: authId, type: "auth", source: "auth"})
authActions.logout([ authId ])
}
render() {
let {
schema, getComponent, authSelectors, errSelectors, name, specSelectors
schema, getComponent, authSelectors, errSelectors, name, authId, specSelectors
} = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
@@ -107,9 +107,9 @@ export default class Oauth2 extends React.Component {
let flow = schema.get("flow")
let scopes = schema.get("allowedScopes") || schema.get("scopes")
let authorizedAuth = authSelectors.authorized().get(name)
let authorizedAuth = authSelectors.authorized().get(authId)
let isAuthorized = !!authorizedAuth
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
let errors = errSelectors.allErrors().filter( err => err.get("authId") === authId)
let isValid = !errors.filter( err => err.get("source") === "validation").size
let description = schema.get("description")
+12 -5
View File
@@ -3,7 +3,7 @@ import { Map } from "immutable"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import win from "core/window"
import { getExtensions } from "core/utils"
import { getExtensions, getCommonExtensions } from "core/utils"
export default class ParameterRow extends Component {
static propTypes = {
@@ -82,7 +82,7 @@ export default class ParameterRow extends Component {
let { isOAS3 } = specSelectors
const { showExtensions } = getConfigs()
const { showExtensions, showCommonExtensions } = getConfigs()
// const onChangeWrapper = (value) => onChange(param, value)
const JsonSchemaForm = getComponent("JsonSchemaForm")
@@ -106,15 +106,17 @@ export default class ParameterRow extends Component {
const ParameterExt = getComponent("ParameterExt")
let paramWithMeta = specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in"))
let format = param.get("format")
let schema = isOAS3 && isOAS3() ? param.get("schema") : param
let type = schema.get("type")
let isFormData = inType === "formData"
let isFormDataSupported = "FormData" in win
let required = param.get("required")
let itemType = schema.getIn(["items", "type"])
let value = paramWithMeta ? paramWithMeta.get("value") : ""
let extensions = getExtensions(param)
let commonExt = showCommonExtensions ? getCommonExtensions(param) : null
let extensions = showExtensions ? getExtensions(param) : null
let paramItems // undefined
let paramEnum // undefined
@@ -153,11 +155,16 @@ export default class ParameterRow extends Component {
{ param.get("name") }
{ !required ? null : <span style={{color: "red"}}>&nbsp;*</span> }
</div>
<div className="parameter__type">{ type } { itemType && `[${itemType}]` }</div>
<div className="parameter__type">
{ type }
{ itemType && `[${itemType}]` }
{ format && <span className="prop-format">(${format})</span>}
</div>
<div className="parameter__deprecated">
{ isOAS3 && isOAS3() && param.get("deprecated") ? "deprecated": null }
</div>
<div className="parameter__in">({ param.get("in") })</div>
{ !showCommonExtensions || !commonExt.size ? null : commonExt.map((v, key) => <ParameterExt key={`${key}-${v}`} xKey={key} xVal={v} /> )}
{ !showExtensions || !extensions.size ? null : extensions.map((v, key) => <ParameterExt key={`${key}-${v}`} xKey={key} xVal={v} /> )}
</td>
+1
View File
@@ -49,6 +49,7 @@ module.exports = function SwaggerUI(opts) {
defaultModelExpandDepth: 1,
defaultModelsExpandDepth: 1,
showExtensions: false,
showCommonExtensions: false,
supportedSubmitMethods: [
"get",
"put",
+12
View File
@@ -0,0 +1,12 @@
import YAML from "js-yaml"
export const parseYamlConfig = (yaml, system) => {
try {
return YAML.safeLoad(yaml)
} catch(e) {
if (system) {
system.errActions.newThrownErr( new Error(e) )
}
return {}
}
}
+2 -38
View File
@@ -1,46 +1,10 @@
import YAML from "js-yaml"
import yamlConfig from "root/swagger-config.yaml"
import { parseYamlConfig } from "./helpers"
import * as actions from "./actions"
import * as specActions from "./spec-actions"
import * as selectors from "./selectors"
import reducers from "./reducers"
const parseYamlConfig = (yaml, system) => {
try {
return YAML.safeLoad(yaml)
} catch(e) {
if (system) {
system.errActions.newThrownErr( new Error(e) )
}
return {}
}
}
const specActions = {
downloadConfig: (url) => ({fn}) => {
let {fetch} = fn
return fetch(url)
},
getConfigByUrl: (configUrl, cb)=> ({ specActions }) => {
if (configUrl) {
return specActions.downloadConfig(configUrl).then(next, next)
}
function next(res) {
if (res instanceof Error || res.status >= 400) {
specActions.updateLoadingStatus("failedConfig")
specActions.updateLoadingStatus("failedConfig")
specActions.updateUrl("")
console.error(res.statusText + " " + configUrl)
cb(null)
} else {
cb(parseYamlConfig(res.text))
}
}
}
}
const specSelectors = {
getLocalConfig: () => {
return parseYamlConfig(yamlConfig)
+34
View File
@@ -0,0 +1,34 @@
import { parseYamlConfig } from "./helpers"
export const downloadConfig = (url) => ({fn: { fetch }, getConfigs}) => {
const { requestInterceptor, responseInterceptor } = getConfigs()
let req = { url }
if(requestInterceptor) {
req = requestInterceptor(req)
}
return fetch(req)
.then(res => {
if(res) {
return responseInterceptor(res)
}
return res
})
}
export const getConfigByUrl = (configUrl, cb)=> ({ specActions }) => {
if (configUrl) {
return specActions.downloadConfig(configUrl).then(next, next)
}
function next(res) {
if (res instanceof Error || res.status >= 400) {
specActions.updateLoadingStatus("failedConfig")
specActions.updateLoadingStatus("failedConfig")
specActions.updateUrl("")
console.error(res.statusText + " " + configUrl)
cb(null)
} else {
cb(parseYamlConfig(res.text))
}
}
}
@@ -33,6 +33,7 @@ export const definitionsToAuthorize = onlyOAS3(createSelector(
definition.get("flows").entrySeq().forEach(([flowKey, flowVal]) => {
let translatedDef = fromJS({
flow: flowKey,
name: defName,
authorizationUrl: flowVal.get("authorizationUrl"),
tokenUrl: flowVal.get("tokenUrl"),
scopes: flowVal.get("scopes"),
@@ -40,7 +41,7 @@ export const definitionsToAuthorize = onlyOAS3(createSelector(
})
list = list.push(new Map({
[defName]: translatedDef.filter((v) => {
[`${defName}__${flowKey}`]: translatedDef.filter((v) => {
// filter out unset values, sometimes `authorizationUrl`
// and `tokenUrl` come out as `undefined` in the data
return v !== undefined
+5 -1
View File
@@ -118,7 +118,11 @@ export default {
let operationPath = ["paths", ...path]
let metaPath = ["meta", "paths", ...path]
if(!state.getIn(["json", ...operationPath]) && !state.getIn(["resolved", ...operationPath])) {
if(
!state.getIn(["json", ...operationPath])
&& !state.getIn(["resolved", ...operationPath])
&& !state.getIn(["resolvedSubtrees", ...operationPath])
) {
// do nothing if the operation does not exist
return state
}
+1
View File
@@ -712,6 +712,7 @@ export const createDeepLinkPath = (str) => typeof str == "string" || str instanc
export const escapeDeepLinkPath = (str) => cssEscape( createDeepLinkPath(str) )
export const getExtensions = (defObj) => defObj.filter((v, k) => /^x-/.test(k))
export const getCommonExtensions = (defObj) => defObj.filter((v, k) => /^pattern|maxLength|minLength|maximum|minimum/.test(k))
// Deeply strips a specific key from an object.
//
+84
View File
@@ -0,0 +1,84 @@
/* eslint-env mocha */
import expect, { createSpy } from "expect"
import { downloadConfig } from "corePlugins/configs/spec-actions"
describe("configs plugin - actions", () => {
describe("downloadConfig", () => {
it("should call the system fetch helper with a provided url", () => {
const fetchSpy = createSpy(async () => {}).andCallThrough()
const system = {
fn: {
fetch: fetchSpy
},
getConfigs() {
return {}
}
}
const url = "http://swagger.io/one"
downloadConfig(url)(system)
expect(fetchSpy).toHaveBeenCalledWith({
url: url
})
})
it("should allow the globally configured requestInterceptor to modify the request", () => {
const fetchSpy = createSpy(async () => {}).andCallThrough()
const requestInterceptorSpy = createSpy((req) => {
req.url = "http://swagger.io/two"
return req
}).andCallThrough()
const system = {
fn: {
fetch: fetchSpy
},
getConfigs() {
return {
requestInterceptor: requestInterceptorSpy
}
}
}
const url = "http://swagger.io/one"
downloadConfig(url)(system)
expect(fetchSpy).toHaveBeenCalledWith({
url: "http://swagger.io/two"
})
})
it("should allow the globally configured responseInterceptor to modify the response", async () => {
const fetchSpy = createSpy(async (req) => {
return {
url: req.url,
ok: true
}
}).andCallThrough()
const responseInterceptorSpy = createSpy((res) => {
res.url = "http://swagger.io/two"
return res
}).andCallThrough()
const system = {
fn: {
fetch: fetchSpy
},
getConfigs() {
return {
responseInterceptor: responseInterceptorSpy
}
}
}
const url = "http://swagger.io/one"
const res = await downloadConfig(url)(system)
expect(res).toEqual({
url: "http://swagger.io/two",
ok: true
})
})
})
})
+21 -1
View File
@@ -1,6 +1,6 @@
/* eslint-env mocha */
import expect from "expect"
import { fromJS, OrderedMap } from "immutable"
import { Map, fromJS, OrderedMap } from "immutable"
import {
mapToList,
parseSearch,
@@ -20,6 +20,8 @@ import {
getAcceptControllingResponse,
createDeepLinkPath,
escapeDeepLinkPath,
getExtensions,
getCommonExtensions,
sanitizeUrl,
extractFileNameFromContentDispositionHeader,
deeplyStripKey
@@ -943,6 +945,24 @@ describe("utils", function() {
})
})
describe("getExtensions", function() {
const objTest = Map([[ "x-test", "a"], ["minimum", "b"]])
it("does not error on empty array", function() {
const result1 = getExtensions([])
expect(result1).toEqual([])
const result2 = getCommonExtensions([])
expect(result2).toEqual([])
})
it("gets only the x- keys", function() {
const result = getExtensions(objTest)
expect(result).toEqual(Map([[ "x-test", "a"]]))
})
it("gets the common keys", function() {
const result = getCommonExtensions(objTest, true)
expect(result).toEqual(Map([[ "minimum", "b"]]))
})
})
describe("deeplyStripKey", function() {
it("should filter out a specified key", function() {
const input = {
+31
View File
@@ -0,0 +1,31 @@
describe("bug #4485: operation metadata storage when referenced via path item $ref", function () {
let mainPage
beforeEach(function (client, done) {
mainPage = client
.url("localhost:3230")
.page.main()
client.waitForElementVisible(".download-url-input", 5000)
.pause(2000)
.clearValue(".download-url-input")
.setValue(".download-url-input", "http://localhost:3230/test-specs/bugs/4485/main.yaml")
.click("button.download-url-button")
.pause(1000)
done()
})
afterEach(function (client, done) {
done()
})
it("sets a consumes value for a body parameter correctly", function (client) {
client.waitForElementVisible(".opblock-tag-section", 10000)
.click(".opblock")
.waitForElementVisible(".opblock-body", 5000)
.click("button.btn.try-out__btn")
.click("select.content-type [value=\"application/xml\"]")
.pause(500)
.assert.value("select.content-type", "application/xml")
client.end()
})
})
+15
View File
@@ -0,0 +1,15 @@
---
post:
description: Book
operationId: buy
summary: Buy a book
tags:
- Book
consumes:
- application/json
- application/xml
parameters:
- name: requestBody
in: body
description: Buy a Book
required: true
+5
View File
@@ -0,0 +1,5 @@
---
swagger: '2.0'
paths:
"/v1/book":
"$ref": "./book.yaml"