Compare commits

...
8 Commits
Author SHA1 Message Date
kyle 2c4327bfd8 v3.13.2 (#4393)
* v3.13.2

* rebuild dist
2018-03-30 21:55:58 -07:00
kyle fd8274b353 Filter $$ref from examples (#4392)
* fix(dev-server): don't open localhost in a browser
* tests: refactor model-example enzyme tests to be more isolated
* tests: add failing sampleFromSchema tests for $$ref keys
* tests: add additional test for user-created $$ref values
* fix: create deeplyStripKey; use it to filter $$refs out of examples
* tests: add cases for deeplyStripKey
2018-03-30 18:02:32 -07:00
Helder Sepulveda 762a32b59b fix: header access for Edge browser (#4383) 2018-03-29 16:24:46 -07:00
kyle 8189fd2473 fix: path item $ref rendering (#4381)
* WIP: trigger resolution of Path Item $ref on spec load

* fix(dev-server): don't open localhost in a browser

* preserve key order when merging specJson with specResolvedSubtrees

* remove stray `debugger`
2018-03-27 17:04:31 -07:00
Jake Reed 71d39af443 fix(docker): MAINTAINER deprecated. Use LABEL. (#4370) 2018-03-27 12:02:22 -07:00
kyle 9c8955edd4 v3.13.1 (#4367)
* fix: don't assign example if an example does not exist

fixes #4319

* v3.13.1

* rebuild dist
2018-03-24 14:20:35 -07:00
kyle 003a2e7584 fix: don't assign example if an example does not exist (#4365)
fixes #4319
2018-03-24 02:04:01 -07:00
kyle 1f9fb0e68d fix: request resolution via OperationContainer on mount (#4358) 2018-03-21 21:06:18 -07:00
20 changed files with 1407 additions and 87 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
FROM alpine:3.5
MAINTAINER fehguy
LABEL maintainer="fehguy"
ENV VERSION "v2.2.10"
ENV FOLDER "swagger-ui-2.2.10"
+1 -1
View File
@@ -22,7 +22,7 @@ The OpenAPI Specification has undergone 5 revisions since initial creation in 20
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
------------------ | ------------ | -------------------------- | -----
3.13.0 | 2018-03-16 | 2.0, 3.0 | [tag v3.13.0](https://github.com/swagger-api/swagger-ui/tree/v3.13.0)
3.13.2 | 2018-03-23 | 2.0, 3.0 | [tag v3.13.2](https://github.com/swagger-api/swagger-ui/tree/v3.13.2)
3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21)
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10)
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5)
+14 -14
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
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
+3 -4
View File
@@ -1,6 +1,6 @@
{
"name": "swagger-ui",
"version": "3.13.0",
"version": "3.13.2",
"main": "dist/swagger-ui.js",
"repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [
@@ -19,9 +19,8 @@
"build-core": "webpack --config webpack-dist.config.js --colors",
"build-standalone": "webpack --config webpack-dist-standalone.config.js --colors",
"predev": "npm install",
"dev": "npm-run-all --parallel hot-server open-localhost",
"dev": "npm-run-all --parallel hot-server",
"watch": "webpack --config webpack-watch.config.js --watch --progress",
"open-localhost": "node -e 'require(\"open\")(\"http://localhost:3200\")'",
"hot-server": "webpack-dev-server --host 0.0.0.0 --config webpack-hot-dev-server.config.js --inline --hot --progress --content-base dev-helpers/",
"deps-license": "license-checker --production --csv --out $npm_package_config_deps_check_dir/licenses.csv && license-checker --development --csv --out $npm_package_config_deps_check_dir/licenses-dev.csv",
"deps-size": "webpack -p --config webpack.check.js --json | webpack-bundle-size-analyzer >| $npm_package_config_deps_check_dir/sizes.txt",
@@ -84,7 +83,7 @@
"scroll-to-element": "^2.0.0",
"serialize-error": "2.0.0",
"shallowequal": "0.2.2",
"swagger-client": "^3.5.1",
"swagger-client": "^3.5.2",
"url-parse": "^1.1.8",
"whatwg-fetch": "0.11.1",
"worker-loader": "^0.7.1",
+1 -1
View File
@@ -60,7 +60,7 @@ export default class LiveResponse extends React.Component {
const body = response.get("text")
const duration = response.get("duration")
const headersKeys = Object.keys(headers)
const contentType = headers["content-type"]
const contentType = headers["content-type"] || headers["Content-Type"]
const Curl = getComponent("curl")
const ResponseBody = getComponent("responseBody")
+11 -1
View File
@@ -81,6 +81,15 @@ export default class OperationContainer extends PureComponent {
}
}
componentDidMount() {
const { isShown } = this.props
const resolvedSubtree = this.getResolvedSubtree()
if(isShown && resolvedSubtree === undefined) {
this.requestResolvedSubtree()
}
}
componentWillReceiveProps(nextProps) {
const { response, isShown } = nextProps
const resolvedSubtree = this.getResolvedSubtree()
@@ -96,7 +105,8 @@ export default class OperationContainer extends PureComponent {
toggleShown =() => {
let { layoutActions, tag, operationId, isShown } = this.props
if(!isShown) {
const resolvedSubtree = this.getResolvedSubtree()
if(!isShown && resolvedSubtree === undefined) {
// transitioning from collapsed to expanded
this.requestResolvedSubtree()
}
+17 -14
View File
@@ -1,4 +1,4 @@
import { objectify, isFunc, normalizeArray } from "core/utils"
import { objectify, isFunc, normalizeArray, deeplyStripKey } from "core/utils"
import XML from "xml"
import memoizee from "memoizee"
@@ -29,13 +29,14 @@ export const sampleFromSchema = (schema, config={}) => {
let { type, example, properties, additionalProperties, items } = objectify(schema)
let { includeReadOnly, includeWriteOnly } = config
if(example && example.$$ref) {
delete example.$$ref
if(example !== undefined) {
return deeplyStripKey(example, "$$ref", (val) => {
// do a couple of quick sanity tests to ensure the value
// looks like a $$ref that swagger-client generates.
return typeof val === "string" && val.indexOf("#") > -1
})
}
if(example !== undefined)
return example
if(!type) {
if(properties) {
type = "object"
@@ -73,13 +74,13 @@ export const sampleFromSchema = (schema, config={}) => {
}
if(type === "array") {
if(Array.isArray(items.anyOf)) {
return items.anyOf.map(i => sampleFromSchema(i, config))
}
if(Array.isArray(items.oneOf)) {
return items.oneOf.map(i => sampleFromSchema(i, config))
}
if(Array.isArray(items.anyOf)) {
return items.anyOf.map(i => sampleFromSchema(i, config))
}
if(Array.isArray(items.oneOf)) {
return items.oneOf.map(i => sampleFromSchema(i, config))
}
return [ sampleFromSchema(items, config) ]
}
@@ -214,7 +215,9 @@ export const sampleXmlFromSchema = (schema, config={}) => {
|| enumAttrVal || primitive(props[propName])
} else {
props[propName].xml.name = props[propName].xml.name || propName
props[propName].example = props[propName].example !== undefined ? props[propName].example : example[propName]
if(props[propName].example === undefined && example[propName] !== undefined) {
props[propName].example = example[propName]
}
let t = sampleXmlFromSchema(props[propName])
if (Array.isArray(t)) {
res[displayName] = res[displayName].concat(t)
+3 -3
View File
@@ -54,7 +54,7 @@ const mergerFn = (oldVal, newVal) => {
return newVal
}
return Map().mergeWith(
return OrderedMap().mergeWith(
mergerFn,
oldVal,
newVal
@@ -66,7 +66,7 @@ const mergerFn = (oldVal, newVal) => {
export const specJsonWithResolvedSubtrees = createSelector(
state,
spec => Map().mergeWith(
spec => OrderedMap().mergeWith(
mergerFn,
spec.get("json"),
spec.get("resolvedSubtrees")
@@ -109,7 +109,7 @@ export const semver = createSelector(
)
export const paths = createSelector(
spec,
specJsonWithResolvedSubtrees,
spec => spec.get("paths")
)
+16
View File
@@ -1,3 +1,5 @@
import get from "lodash/get"
export const updateSpec = (ori, {specActions}) => (...args) => {
ori(...args)
specActions.parseToJson(...args)
@@ -5,7 +7,21 @@ export const updateSpec = (ori, {specActions}) => (...args) => {
export const updateJsonSpec = (ori, {specActions}) => (...args) => {
ori(...args)
specActions.invalidateResolvedSubtreeCache()
// Trigger resolution of any path-level $refs.
const [json] = args
const pathItems = get(json, ["paths"])
const pathItemKeys = Object.keys(pathItems)
pathItemKeys.forEach(k => {
const val = get(pathItems, [k])
if(val.$ref) {
specActions.requestResolvedSubtree(["paths", k])
}
})
}
// Log the request ( just for debugging, shouldn't affect prod )
+22
View File
@@ -712,3 +712,25 @@ 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))
// Deeply strips a specific key from an object.
//
// `predicate` can be used to discriminate the stripping further,
// by preserving the key's place in the object based on its value.
export function deeplyStripKey(input, keyToStrip, predicate = () => true) {
if(typeof input !== "object" || Array.isArray(input) || !keyToStrip) {
return input
}
const obj = Object.assign({}, input)
Object.keys(obj).forEach(k => {
if(k === keyToStrip && predicate(obj[k], k)) {
delete obj[k]
return
}
obj[k] = deeplyStripKey(obj[k], keyToStrip, predicate)
})
return obj
}
+23 -17
View File
@@ -6,23 +6,8 @@ import ModelExample from "components/model-example"
import ModelComponent from "components/model-wrapper"
describe("<ModelExample/>", function(){
// Given
let components = {
ModelWrapper: ModelComponent
}
let props = {
getComponent: (c) => {
return components[c]
},
specSelectors: {},
schema: {},
example: "{\"example\": \"value\"}",
isExecute: false,
getConfigs: () => ({
defaultModelRendering: "model",
defaultModelExpandDepth: 1
})
}
let components, props
let exampleSelectedTestInputs = [
{ defaultModelRendering: "model", isExecute: true },
{ defaultModelRendering: "example", isExecute: true },
@@ -30,10 +15,31 @@ describe("<ModelExample/>", function(){
{ defaultModelRendering: "othervalue", isExecute: true },
{ defaultModelRendering: "othervalue", isExecute: false }
]
let modelSelectedTestInputs = [
{ defaultModelRendering: "model", isExecute: false }
]
beforeEach(() => {
components = {
ModelWrapper: ModelComponent
}
props = {
getComponent: (c) => {
return components[c]
},
specSelectors: {},
schema: {},
example: "{\"example\": \"value\"}",
isExecute: false,
getConfigs: () => ({
defaultModelRendering: "model",
defaultModelExpandDepth: 1
})
}
})
it("renders model and example tabs", function(){
// When
+108 -22
View File
@@ -100,6 +100,92 @@ describe("sampleFromSchema", function() {
expect(sampleFromSchema(definition, { includeWriteOnly: true })).toEqual(expected)
})
it("returns object without any $$ref fields at the root schema level", function () {
var definition = {
type: "object",
properties: {
message: {
type: "string"
}
},
example: {
value: {
message: "Hello, World!"
},
$$ref: "#/components/examples/WelcomeExample"
},
$$ref: "#/components/schemas/Welcome"
}
var expected = {
"value": {
"message": "Hello, World!"
}
}
expect(sampleFromSchema(definition, { includeWriteOnly: true })).toEqual(expected)
})
it("returns object without any $$ref fields at nested schema levels", function () {
var definition = {
type: "object",
properties: {
message: {
type: "string"
}
},
example: {
a: {
value: {
message: "Hello, World!"
},
$$ref: "#/components/examples/WelcomeExample"
}
},
$$ref: "#/components/schemas/Welcome"
}
var expected = {
a: {
"value": {
"message": "Hello, World!"
}
}
}
expect(sampleFromSchema(definition, { includeWriteOnly: true })).toEqual(expected)
})
it("returns object with any $$ref fields that appear to be user-created", function () {
var definition = {
type: "object",
properties: {
message: {
type: "string"
}
},
example: {
$$ref: {
value: {
message: "Hello, World!"
},
$$ref: "#/components/examples/WelcomeExample"
}
},
$$ref: "#/components/schemas/Welcome"
}
var expected = {
$$ref: {
"value": {
"message": "Hello, World!"
}
}
}
expect(sampleFromSchema(definition, { includeWriteOnly: true })).toEqual(expected)
})
describe("for array type", function() {
it("returns array with sample of array type", function() {
var definition = {
@@ -108,12 +194,12 @@ describe("sampleFromSchema", function() {
type: "integer"
}
}
var expected = [ 0 ]
expect(sampleFromSchema(definition)).toEqual(expected)
})
it("returns array of examples for array that has example", function() {
var definition = {
type: "array",
@@ -122,9 +208,9 @@ describe("sampleFromSchema", function() {
},
example: "dog"
}
var expected = [ "dog" ]
expect(sampleFromSchema(definition)).toEqual(expected)
})
@@ -132,16 +218,16 @@ describe("sampleFromSchema", function() {
var definition = {
type: "array",
items: {
type: "string",
type: "string",
},
example: [ "dog", "cat" ]
}
var expected = [ "dog", "cat" ]
expect(sampleFromSchema(definition)).toEqual(expected)
})
it("returns array of samples for oneOf type", function() {
var definition = {
type: "array",
@@ -154,9 +240,9 @@ describe("sampleFromSchema", function() {
]
}
}
var expected = [ 0 ]
expect(sampleFromSchema(definition)).toEqual(expected)
})
@@ -175,9 +261,9 @@ describe("sampleFromSchema", function() {
]
}
}
var expected = [ "string", 0 ]
expect(sampleFromSchema(definition)).toEqual(expected)
})
@@ -198,12 +284,12 @@ describe("sampleFromSchema", function() {
]
}
}
var expected = [ "dog", 1 ]
expect(sampleFromSchema(definition)).toEqual(expected)
})
it("returns array of samples for anyOf type", function() {
var definition = {
type: "array",
@@ -216,9 +302,9 @@ describe("sampleFromSchema", function() {
]
}
}
var expected = [ 0 ]
expect(sampleFromSchema(definition)).toEqual(expected)
})
@@ -237,9 +323,9 @@ describe("sampleFromSchema", function() {
]
}
}
var expected = [ "string", 0 ]
expect(sampleFromSchema(definition)).toEqual(expected)
})
@@ -260,9 +346,9 @@ describe("sampleFromSchema", function() {
]
}
}
var expected = [ "dog", 1 ]
expect(sampleFromSchema(definition)).toEqual(expected)
})
})
File diff suppressed because it is too large Load Diff
+91 -1
View File
@@ -1,6 +1,7 @@
/* eslint-env mocha */
import expect from "expect"
import { fromJS } from "immutable"
import { fromJSOrdered } from "core/utils"
import {
parameterValues,
contentTypeValues,
@@ -9,6 +10,8 @@ import {
operationConsumes
} from "corePlugins/spec/selectors"
import Petstore from "./assets/petstore.json"
describe("spec plugin - selectors", function(){
describe("parameterValue", function(){
@@ -311,7 +314,6 @@ describe("spec plugin - selectors", function(){
})
describe("specJsonWithResolvedSubtrees", function(){
it("should return a correctly merged tree", function(){
@@ -360,5 +362,93 @@ describe("spec plugin - selectors", function(){
}
})
})
it("should preserve initial map key ordering", function(){
// Given
let state = fromJSOrdered({
json: Petstore,
resolvedSubtrees: {
paths: {
"/pet/{petId}": {
post: {
tags: [
"pet"
],
summary: "Updates a pet in the store with form data",
description: "",
operationId: "updatePetWithForm",
consumes: [
"application/x-www-form-urlencoded"
],
produces: [
"application/xml",
"application/json"
],
parameters: [
{
name: "petId",
"in": "path",
description: "ID of pet that needs to be updated",
required: true,
type: "integer",
format: "int64"
},
{
name: "name",
"in": "formData",
description: "Updated name of the pet",
required: false,
type: "string"
},
{
name: "status",
"in": "formData",
description: "Updated status of the pet",
required: false,
type: "string"
}
],
responses: {
"405": {
description: "Invalid input"
}
},
security: [
{
petstore_auth: [
"write:pets",
"read:pets"
]
}
],
__originalOperationId: "updatePetWithForm"
}
}
}
}
})
// When
let result = specJsonWithResolvedSubtrees(state)
// Then
const correctOrder = [
"/pet",
"/pet/findByStatus",
"/pet/findByTags",
"/pet/{petId}",
"/pet/{petId}/uploadImage",
"/store/inventory",
"/store/order",
"/store/order/{orderId}",
"/user",
"/user/createWithArray",
"/user/createWithList",
"/user/login",
"/user/logout",
"/user/{username}"
]
expect(state.getIn(["json", "paths"]).keySeq().toJS()).toEqual(correctOrder)
expect(result.getIn(["paths"]).keySeq().toJS()).toEqual(correctOrder)
})
})
})
+54 -1
View File
@@ -21,7 +21,8 @@ import {
createDeepLinkPath,
escapeDeepLinkPath,
sanitizeUrl,
extractFileNameFromContentDispositionHeader
extractFileNameFromContentDispositionHeader,
deeplyStripKey
} from "core/utils"
import win from "core/window"
@@ -942,6 +943,58 @@ describe("utils", function() {
})
})
describe("deeplyStripKey", function() {
it("should filter out a specified key", function() {
const input = {
$$ref: "#/this/is/my/ref",
a: {
$$ref: "#/this/is/my/other/ref",
value: 12345
}
}
const result = deeplyStripKey(input, "$$ref")
expect(result).toEqual({
a: {
value: 12345
}
})
})
it("should filter out a specified key by predicate", function() {
const input = {
$$ref: "#/this/is/my/ref",
a: {
$$ref: "#/keep/this/one",
value: 12345
}
}
const result = deeplyStripKey(input, "$$ref", (v) => v !== "#/keep/this/one")
expect(result).toEqual({
a: {
value: 12345,
$$ref: "#/keep/this/one"
}
})
})
it("should only call the predicate when the key matches", function() {
const input = {
$$ref: "#/this/is/my/ref",
a: {
$$ref: "#/this/is/my/other/ref",
value: 12345
}
}
let count = 0
const result = deeplyStripKey(input, "$$ref", () => {
count++
return true
})
expect(count).toEqual(2)
})
})
describe("parse and serialize search", function() {
afterEach(function() {
win.location.search = ""