in with the new
This commit is contained in:
17
src/core/plugins/all.js
Normal file
17
src/core/plugins/all.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { pascalCaseFilename } from "core/utils"
|
||||
|
||||
const request = require.context(".", true, /\.jsx?$/)
|
||||
|
||||
request.keys().forEach( function( key ){
|
||||
if( key === "./index.js" ) {
|
||||
return
|
||||
}
|
||||
|
||||
// if( key.slice(2).indexOf("/") > -1) {
|
||||
// // skip files in subdirs
|
||||
// return
|
||||
// }
|
||||
|
||||
let mod = request(key)
|
||||
module.exports[pascalCaseFilename(key)] = mod.default ? mod.default : mod
|
||||
})
|
||||
36
src/core/plugins/allow-try-it-out-if-host.js
Normal file
36
src/core/plugins/allow-try-it-out-if-host.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React, { PropTypes } from "react"
|
||||
|
||||
export default function (system) {
|
||||
return {
|
||||
components: {
|
||||
NoHostWarning,
|
||||
},
|
||||
statePlugins: {
|
||||
spec: {
|
||||
selectors: {
|
||||
allowTryItOutFor,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a quick style. How do we improve this?
|
||||
const style = {
|
||||
backgroundColor: "#e7f0f7",
|
||||
padding: "1rem",
|
||||
borderRadius: "3px",
|
||||
}
|
||||
|
||||
function NoHostWarning() {
|
||||
return (
|
||||
<div style={style}>Note: The interactive forms are disabled, as no `host` property was found in the specification. Please see: <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object" target="_blank">OAI 2.0/#swagger-object</a></div>
|
||||
)
|
||||
}
|
||||
|
||||
// Only allow if, there is a host field
|
||||
function allowTryItOutFor(state) {
|
||||
return ({specSelectors}) => {
|
||||
return specSelectors.hasHost(state)
|
||||
}
|
||||
}
|
||||
284
src/core/plugins/ast/ast.js
Normal file
284
src/core/plugins/ast/ast.js
Normal file
@@ -0,0 +1,284 @@
|
||||
import YAML from "yaml-js"
|
||||
import isArray from "lodash/isArray"
|
||||
import lodashFind from "lodash/find"
|
||||
import { memoize } from "core/utils"
|
||||
|
||||
let cachedCompose = memoize(YAML.compose) // TODO: build a custom cache based on content
|
||||
|
||||
var MAP_TAG = "tag:yaml.org,2002:map"
|
||||
var SEQ_TAG = "tag:yaml.org,2002:seq"
|
||||
|
||||
export function getLineNumberForPath(yaml, path) {
|
||||
|
||||
// Type check
|
||||
if (typeof yaml !== "string") {
|
||||
throw new TypeError("yaml should be a string")
|
||||
}
|
||||
if (!isArray(path)) {
|
||||
throw new TypeError("path should be an array of strings")
|
||||
}
|
||||
|
||||
var i = 0
|
||||
|
||||
let ast = cachedCompose(yaml)
|
||||
|
||||
// simply walks the tree using current path recursively to the point that
|
||||
// path is empty
|
||||
|
||||
return find(ast, path)
|
||||
|
||||
function find(current, path, last) {
|
||||
if(!current) {
|
||||
// something has gone quite wrong
|
||||
// return the last start_mark as a best-effort
|
||||
if(last && last.start_mark)
|
||||
return last.start_mark.line
|
||||
return 0
|
||||
}
|
||||
|
||||
if (path.length && current.tag === MAP_TAG) {
|
||||
for (i = 0; i < current.value.length; i++) {
|
||||
var pair = current.value[i]
|
||||
var key = pair[0]
|
||||
var value = pair[1]
|
||||
|
||||
if (key.value === path[0]) {
|
||||
return find(value, path.slice(1), current)
|
||||
}
|
||||
|
||||
if (key.value === path[0].replace(/\[.*/, "")) {
|
||||
// access the array at the index in the path (example: grab the 2 in "tags[2]")
|
||||
var index = parseInt(path[0].match(/\[(.*)\]/)[1])
|
||||
if(value.value.length === 1 && index !== 0 && !!index) {
|
||||
var nextVal = lodashFind(value.value[0], { value: index.toString() })
|
||||
} else { // eslint-disable-next-line no-redeclare
|
||||
var nextVal = value.value[index]
|
||||
}
|
||||
return find(nextVal, path.slice(1), value.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (path.length && current.tag === SEQ_TAG) {
|
||||
var item = current.value[path[0]]
|
||||
|
||||
if (item && item.tag) {
|
||||
return find(item, path.slice(1), current.value)
|
||||
}
|
||||
}
|
||||
|
||||
if (current.tag === MAP_TAG && !Array.isArray(last)) {
|
||||
return current.start_mark.line
|
||||
} else {
|
||||
return current.start_mark.line + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a position object with given
|
||||
* @param {string} yaml
|
||||
* YAML or JSON string
|
||||
* @param {array} path
|
||||
* an array of stings that constructs a
|
||||
* JSON Path similiar to JSON Pointers(RFC 6901). The difference is, each
|
||||
* component of path is an item of the array intead of beinf seperated with
|
||||
* slash(/) in a string
|
||||
*/
|
||||
export function positionRangeForPath(yaml, path) {
|
||||
|
||||
// Type check
|
||||
if (typeof yaml !== "string") {
|
||||
throw new TypeError("yaml should be a string")
|
||||
}
|
||||
if (!isArray(path)) {
|
||||
throw new TypeError("path should be an array of strings")
|
||||
}
|
||||
|
||||
var invalidRange = {
|
||||
start: {line: -1, column: -1},
|
||||
end: {line: -1, column: -1}
|
||||
}
|
||||
var i = 0
|
||||
|
||||
let ast = cachedCompose(yaml)
|
||||
|
||||
// simply walks the tree using current path recursively to the point that
|
||||
// path is empty.
|
||||
return find(ast)
|
||||
|
||||
function find(current) {
|
||||
if (current.tag === MAP_TAG) {
|
||||
for (i = 0; i < current.value.length; i++) {
|
||||
var pair = current.value[i]
|
||||
var key = pair[0]
|
||||
var value = pair[1]
|
||||
|
||||
if (key.value === path[0]) {
|
||||
path.shift()
|
||||
return find(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (current.tag === SEQ_TAG) {
|
||||
var item = current.value[path[0]]
|
||||
|
||||
if (item && item.tag) {
|
||||
path.shift()
|
||||
return find(item)
|
||||
}
|
||||
}
|
||||
|
||||
// if path is still not empty we were not able to find the node
|
||||
if (path.length) {
|
||||
return invalidRange
|
||||
}
|
||||
|
||||
return {
|
||||
/* jshint camelcase: false */
|
||||
start: {
|
||||
line: current.start_mark.line,
|
||||
column: current.start_mark.column
|
||||
},
|
||||
end: {
|
||||
line: current.end_mark.line,
|
||||
column: current.end_mark.column
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON Path for position object in the spec
|
||||
* @param {string} yaml
|
||||
* YAML or JSON string
|
||||
* @param {object} position
|
||||
* position in the YAML or JSON string with `line` and `column` properties.
|
||||
* `line` and `column` number are zero indexed
|
||||
*/
|
||||
export function pathForPosition(yaml, position) {
|
||||
|
||||
// Type check
|
||||
if (typeof yaml !== "string") {
|
||||
throw new TypeError("yaml should be a string")
|
||||
}
|
||||
if (typeof position !== "object" || typeof position.line !== "number" ||
|
||||
typeof position.column !== "number") {
|
||||
throw new TypeError("position should be an object with line and column" +
|
||||
" properties")
|
||||
}
|
||||
|
||||
try {
|
||||
var ast = cachedCompose(yaml)
|
||||
} catch (e) {
|
||||
console.error("Error composing AST", e)
|
||||
console.error(`Problem area:\n`, yaml.split("\n").slice(position.line - 5, position.line + 5).join("\n"))
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
var path = []
|
||||
|
||||
return find(ast)
|
||||
|
||||
/**
|
||||
* recursive find function that finds the node matching the position
|
||||
* @param {object} current - AST object to serach into
|
||||
*/
|
||||
function find(current) {
|
||||
|
||||
// algorythm:
|
||||
// is current a promitive?
|
||||
// // finish recursion without modifying the path
|
||||
// is current a hash?
|
||||
// // find a key or value that position is in their range
|
||||
// // if key is in range, terminate recursion with exisiting path
|
||||
// // if a value is in range push the corresponding key to the path
|
||||
// // andcontinue recursion
|
||||
// is current an array
|
||||
// // find the item that position is in it"s range and push the index
|
||||
// // of the item to the path and continue recursion with that item.
|
||||
|
||||
var i = 0
|
||||
|
||||
if (!current || [MAP_TAG, SEQ_TAG].indexOf(current.tag) === -1) {
|
||||
return path
|
||||
}
|
||||
|
||||
if (current.tag === MAP_TAG) {
|
||||
for (i = 0; i < current.value.length; i++) {
|
||||
var pair = current.value[i]
|
||||
var key = pair[0]
|
||||
var value = pair[1]
|
||||
|
||||
if (isInRange(key)) {
|
||||
return path
|
||||
} else if (isInRange(value)) {
|
||||
path.push(key.value)
|
||||
return find(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (current.tag === SEQ_TAG) {
|
||||
for (i = 0; i < current.value.length; i++) {
|
||||
var item = current.value[i]
|
||||
|
||||
if (isInRange(item)) {
|
||||
path.push(i.toString())
|
||||
return find(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return path
|
||||
|
||||
/**
|
||||
* Determines if position is in node"s range
|
||||
* @param {object} node - AST node
|
||||
* @return {Boolean} true if position is in node"s range
|
||||
*/
|
||||
function isInRange(node) {
|
||||
/* jshint camelcase: false */
|
||||
|
||||
// if node is in a single line
|
||||
if (node.start_mark.line === node.end_mark.line) {
|
||||
|
||||
return (position.line === node.start_mark.line) &&
|
||||
(node.start_mark.column <= position.column) &&
|
||||
(node.end_mark.column >= position.column)
|
||||
}
|
||||
|
||||
// if position is in the same line as start_mark
|
||||
if (position.line === node.start_mark.line) {
|
||||
return position.column >= node.start_mark.column
|
||||
}
|
||||
|
||||
// if position is in the same line as end_mark
|
||||
if (position.line === node.end_mark.line) {
|
||||
return position.column <= node.end_mark.column
|
||||
}
|
||||
|
||||
// if position is between start and end lines return true, otherwise
|
||||
// return false.
|
||||
return (node.start_mark.line < position.line) &&
|
||||
(node.end_mark.line > position.line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// utility fns
|
||||
|
||||
|
||||
export let pathForPositionAsync = promisifySyncFn(pathForPosition)
|
||||
export let positionRangeForPathAsync = promisifySyncFn(positionRangeForPath)
|
||||
export let getLineNumberForPathAsync = promisifySyncFn(getLineNumberForPath)
|
||||
|
||||
function promisifySyncFn(fn) {
|
||||
return function(...args) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
resolve(fn(...args))
|
||||
})
|
||||
}
|
||||
}
|
||||
9
src/core/plugins/ast/index.js
Normal file
9
src/core/plugins/ast/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as AST from "./ast"
|
||||
import JumpToPath from "./jump-to-path"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
fn: { AST },
|
||||
components: { JumpToPath }
|
||||
}
|
||||
}
|
||||
9
src/core/plugins/ast/jump-to-path.jsx
Normal file
9
src/core/plugins/ast/jump-to-path.jsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from "react"
|
||||
|
||||
// Nothing by default- component can be overriden by another plugin.
|
||||
|
||||
export default class JumpToPath extends React.Component {
|
||||
render() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
118
src/core/plugins/auth/actions.js
Normal file
118
src/core/plugins/auth/actions.js
Normal file
@@ -0,0 +1,118 @@
|
||||
import win from "core/window"
|
||||
import btoa from "btoa"
|
||||
|
||||
export const SHOW_AUTH_POPUP = "show_popup"
|
||||
export const AUTHORIZE = "authorize"
|
||||
export const LOGOUT = "logout"
|
||||
export const PRE_AUTHORIZE_OAUTH2 = "pre_authorize_oauth2"
|
||||
export const AUTHORIZE_OAUTH2 = "authorize_oauth2"
|
||||
export const VALIDATE = "validate"
|
||||
|
||||
export function showDefinitions(payload) {
|
||||
return {
|
||||
type: SHOW_AUTH_POPUP,
|
||||
payload: payload
|
||||
}
|
||||
}
|
||||
|
||||
export function authorize(payload) {
|
||||
return {
|
||||
type: AUTHORIZE,
|
||||
payload: payload
|
||||
}
|
||||
}
|
||||
|
||||
export function logout(payload) {
|
||||
return {
|
||||
type: LOGOUT,
|
||||
payload: payload
|
||||
}
|
||||
}
|
||||
|
||||
export const preAuthorizeOauth2 = (payload) => ( { authActions, errActions } ) => {
|
||||
let { auth , token, isValid } = payload
|
||||
let { schema, name } = auth
|
||||
let flow = schema.get("flow")
|
||||
|
||||
// remove oauth2 property from window after redirect from authentication
|
||||
delete win.swaggerUIRedirectOauth2
|
||||
|
||||
if ( flow !== "accessCode" && !isValid ) {
|
||||
errActions.newAuthErr( {
|
||||
authId: name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
})
|
||||
}
|
||||
|
||||
if ( token.error ) {
|
||||
errActions.newAuthErr({
|
||||
authId: name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: JSON.stringify(token)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
authActions.authorizeOauth2({ auth, token })
|
||||
}
|
||||
|
||||
export function authorizeOauth2(payload) {
|
||||
return {
|
||||
type: AUTHORIZE_OAUTH2,
|
||||
payload: payload
|
||||
}
|
||||
}
|
||||
|
||||
export const authorizePassword = ( auth ) => ( { fn, authActions, errActions } ) => {
|
||||
let { schema, name, username, password, passwordType, clientId, clientSecret } = auth
|
||||
let req = {
|
||||
url: schema.get("tokenUrl"),
|
||||
method: "post",
|
||||
headers: {
|
||||
"content-type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
query: {
|
||||
grant_type: "password",
|
||||
username,
|
||||
password
|
||||
}
|
||||
}
|
||||
|
||||
if ( passwordType === "basic") {
|
||||
req.headers.authorization = "Basic " + btoa(clientId + ":" + clientSecret)
|
||||
} else if ( passwordType === "request") {
|
||||
req.query = Object.assign(req.query, { client_id: clientId, client_secret: clientSecret })
|
||||
}
|
||||
return fn.fetch(req)
|
||||
.then(( response ) => {
|
||||
let token = JSON.parse(response.data)
|
||||
let error = token && ( token.error || "" )
|
||||
let parseError = token && ( token.parseError || "" )
|
||||
|
||||
if ( !response.ok ) {
|
||||
errActions.newAuthErr( {
|
||||
authId: name,
|
||||
level: "error",
|
||||
source: "auth",
|
||||
message: response.statusText
|
||||
} )
|
||||
return
|
||||
}
|
||||
|
||||
if ( error || parseError ) {
|
||||
errActions.newAuthErr({
|
||||
authId: name,
|
||||
level: "error",
|
||||
source: "auth",
|
||||
message: JSON.stringify(token)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
authActions.authorizeOauth2({ auth, token })
|
||||
})
|
||||
.catch(err => { errActions.newAuthErr( err ) })
|
||||
}
|
||||
19
src/core/plugins/auth/index.js
Normal file
19
src/core/plugins/auth/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import reducers from "./reducers"
|
||||
import * as actions from "./actions"
|
||||
import * as selectors from "./selectors"
|
||||
import * as specWrapActionReplacements from "./spec-wrap-actions"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
statePlugins: {
|
||||
auth: {
|
||||
reducers,
|
||||
actions,
|
||||
selectors
|
||||
},
|
||||
spec: {
|
||||
wrapActions: specWrapActionReplacements
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/core/plugins/auth/reducers.js
Normal file
63
src/core/plugins/auth/reducers.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { fromJS, Map } from "immutable"
|
||||
import btoa from "btoa"
|
||||
|
||||
import {
|
||||
SHOW_AUTH_POPUP,
|
||||
AUTHORIZE,
|
||||
PRE_AUTHORIZE_OAUTH2,
|
||||
AUTHORIZE_OAUTH2,
|
||||
LOGOUT
|
||||
} from "./actions"
|
||||
|
||||
export default {
|
||||
[SHOW_AUTH_POPUP]: (state, { payload } ) =>{
|
||||
return state.set( "showDefinitions", payload )
|
||||
},
|
||||
|
||||
[AUTHORIZE]: (state, { payload } ) =>{
|
||||
let securities = fromJS(payload)
|
||||
let map = state.get("authorized") || Map()
|
||||
|
||||
// refactor withMutations
|
||||
securities.entrySeq().forEach( ([ key, security ]) => {
|
||||
let type = security.getIn(["schema", "type"])
|
||||
let name = security.get("name")
|
||||
|
||||
if ( type === "apiKey" ) {
|
||||
map = map.set(key, security)
|
||||
} else if ( type === "basic" ) {
|
||||
let username = security.getIn(["value", "username"])
|
||||
let password = security.getIn(["value", "password"])
|
||||
|
||||
map = map.setIn([key, "value"], {
|
||||
username: username,
|
||||
header: "Basic " + btoa(username + ":" + password)
|
||||
})
|
||||
|
||||
map = map.setIn([key, "schema"], security.get("schema"))
|
||||
}
|
||||
})
|
||||
|
||||
return state.set( "authorized", map )
|
||||
},
|
||||
|
||||
[AUTHORIZE_OAUTH2]: (state, { payload } ) =>{
|
||||
let { auth, token } = payload
|
||||
let parsedAuth
|
||||
|
||||
auth.token = token
|
||||
parsedAuth = fromJS(auth)
|
||||
|
||||
return state.setIn( [ "authorized", parsedAuth.get("name") ], parsedAuth )
|
||||
},
|
||||
|
||||
[LOGOUT]: (state, { payload } ) =>{
|
||||
let result = state.get("authorized").withMutations((authorized) => {
|
||||
payload.forEach((auth) => {
|
||||
authorized.delete(auth)
|
||||
})
|
||||
})
|
||||
|
||||
return state.set("authorized", result)
|
||||
}
|
||||
}
|
||||
78
src/core/plugins/auth/selectors.js
Normal file
78
src/core/plugins/auth/selectors.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import { createSelector } from "reselect"
|
||||
import { List, Map } from "immutable"
|
||||
|
||||
const state = state => state
|
||||
|
||||
export const shownDefinitions = createSelector(
|
||||
state,
|
||||
auth => auth.get( "showDefinitions" )
|
||||
)
|
||||
|
||||
export const definitionsToAuthorize = createSelector(
|
||||
state,
|
||||
auth =>( { specSelectors } ) => {
|
||||
let definitions = specSelectors.securityDefinitions()
|
||||
let list = List()
|
||||
|
||||
//todo refactor
|
||||
definitions.entrySeq().forEach( ([ key, val ]) => {
|
||||
let map = Map()
|
||||
|
||||
map = map.set(key, val)
|
||||
list = list.push(map)
|
||||
})
|
||||
|
||||
return list
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
export const getDefinitionsByNames = ( state, securities ) =>( { specSelectors } ) => {
|
||||
let securityDefinitions = specSelectors.securityDefinitions()
|
||||
let result = List()
|
||||
|
||||
securities.valueSeq().forEach( (names) => {
|
||||
let map = Map()
|
||||
names.entrySeq().forEach( ([name, scopes]) => {
|
||||
let definition = securityDefinitions.get(name)
|
||||
let allowedScopes
|
||||
|
||||
if ( definition.get("type") === "oauth2" && scopes.size ) {
|
||||
allowedScopes = definition.get("scopes")
|
||||
|
||||
allowedScopes.keySeq().forEach( (key) => {
|
||||
if ( !scopes.contains(key) ) {
|
||||
allowedScopes = allowedScopes.delete(key)
|
||||
}
|
||||
})
|
||||
|
||||
definition = definition.set("allowedScopes", allowedScopes)
|
||||
}
|
||||
|
||||
map = map.set(name, definition)
|
||||
})
|
||||
|
||||
result = result.push(map)
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export const authorized = createSelector(
|
||||
state,
|
||||
auth => auth.get("authorized") || Map()
|
||||
)
|
||||
|
||||
|
||||
export const isAuthorized = ( state, securities ) =>( { authSelectors } ) => {
|
||||
let authorized = authSelectors.authorized()
|
||||
let isAuth = false
|
||||
|
||||
return !!securities.toJS().filter( ( security ) => {
|
||||
let isAuthorized = true
|
||||
|
||||
return Object.keys(security).map((key) => {
|
||||
return !isAuthorized || !!authorized.get(key)
|
||||
}).indexOf(false) === -1
|
||||
}).length
|
||||
}
|
||||
13
src/core/plugins/auth/spec-wrap-actions.js
Normal file
13
src/core/plugins/auth/spec-wrap-actions.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Map } from "immutable"
|
||||
|
||||
// Add security to the final `execute` call ( via `extras` )
|
||||
export const execute = ( oriAction, { authSelectors, specSelectors }) => ({ path, method, operation, extras }) => {
|
||||
let securities = {
|
||||
authorized: authSelectors.authorized() && authSelectors.authorized().toJS(),
|
||||
definitions: specSelectors.securityDefinitions() && specSelectors.securityDefinitions().toJS(),
|
||||
specSecurity: specSelectors.security() && specSelectors.security().toJS()
|
||||
}
|
||||
|
||||
return oriAction({ path, method, operation, securities, ...extras })
|
||||
}
|
||||
|
||||
67
src/core/plugins/download-url.js
Normal file
67
src/core/plugins/download-url.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/* global Promise */
|
||||
|
||||
import { createSelector } from "reselect"
|
||||
import { Map } from "immutable"
|
||||
|
||||
export default function downloadUrlPlugin (toolbox) {
|
||||
let { fn, Im } = toolbox
|
||||
|
||||
const actions = {
|
||||
download: (url)=> ({ errActions, specSelectors, specActions }) => {
|
||||
let { fetch } = fn
|
||||
url = url || specSelectors.url()
|
||||
specActions.updateLoadingStatus("loading")
|
||||
fetch(url, {
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
}).then(next,next)
|
||||
|
||||
function next(res) {
|
||||
if(res instanceof Error || res.status >= 400) {
|
||||
specActions.updateLoadingStatus("failed")
|
||||
return errActions.newThrownErr( new Error(res.statusText + " " + url) )
|
||||
}
|
||||
specActions.updateLoadingStatus("success")
|
||||
specActions.updateSpec(res.text)
|
||||
specActions.updateUrl(url)
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
updateLoadingStatus: (status) => {
|
||||
let enums = [null, "loading", "failed", "success"]
|
||||
if(enums.indexOf(status) === -1) {
|
||||
console.error(`Error: ${status} is not one of ${JSON.stringify(enums)}`)
|
||||
}
|
||||
|
||||
return {
|
||||
type: "spec_update_loading_status",
|
||||
payload: status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let reducers = {
|
||||
"spec_update_loading_status": (state, action) => {
|
||||
return (typeof action.payload === "string")
|
||||
? state.set("loadingStatus", action.payload)
|
||||
: state
|
||||
}
|
||||
}
|
||||
|
||||
let selectors = {
|
||||
loadingStatus: createSelector(
|
||||
state => {
|
||||
return state || Map()
|
||||
},
|
||||
spec => spec.get("loadingStatus") || null
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
statePlugins: {
|
||||
spec: { actions, reducers, selectors }
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/core/plugins/err/actions.js
Normal file
43
src/core/plugins/err/actions.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import serializeError from "serialize-error"
|
||||
|
||||
export const NEW_THROWN_ERR = "err_new_thrown_err"
|
||||
export const NEW_THROWN_ERR_BATCH = "err_new_thrown_err_batch"
|
||||
export const NEW_SPEC_ERR = "err_new_spec_err"
|
||||
export const NEW_AUTH_ERR = "err_new_auth_err"
|
||||
export const CLEAR = "err_clear"
|
||||
|
||||
export function newThrownErr(err, action) {
|
||||
return {
|
||||
type: NEW_THROWN_ERR,
|
||||
payload: { action, error: serializeError(err) }
|
||||
}
|
||||
}
|
||||
|
||||
export function newThrownErrBatch(errors) {
|
||||
return {
|
||||
type: NEW_THROWN_ERR_BATCH,
|
||||
payload: errors
|
||||
}
|
||||
}
|
||||
|
||||
export function newSpecErr(err, action) {
|
||||
return {
|
||||
type: NEW_SPEC_ERR,
|
||||
payload: err
|
||||
}
|
||||
}
|
||||
|
||||
export function newAuthErr(err, action) {
|
||||
return {
|
||||
type: NEW_AUTH_ERR,
|
||||
payload: err
|
||||
}
|
||||
}
|
||||
|
||||
export function clear(filter = {}) {
|
||||
// filter looks like: {type: 'spec'}, {source: 'parser'}
|
||||
return {
|
||||
type: CLEAR,
|
||||
payload: filter
|
||||
}
|
||||
}
|
||||
31
src/core/plugins/err/error-transformers/README.md
Normal file
31
src/core/plugins/err/error-transformers/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Error transformers
|
||||
|
||||
Error transformers provide a standard interface for making generated error messages more useful to end users.
|
||||
|
||||
### Inputs & outputs
|
||||
|
||||
Each transformer's `transform` function is given an Immutable List of Immutable Maps as its first argument.
|
||||
|
||||
It is expected that each `transform` function returns a List of similarly-formed Maps.
|
||||
|
||||
These errors originate from the Redux error actions that add errors to state. Errors are transformed before being passed into the reducer.
|
||||
|
||||
It's important that all the keys present in each error (specifically, `line`, `level`, `message`, `source`, and `type`) are present when the transformer is finished.
|
||||
|
||||
##### Deleting an error
|
||||
|
||||
If you want to delete an error completely, you can overwrite it with `null`. The null value will be filtered out of the transformed error array before the errors are returned.
|
||||
å
|
||||
|
||||
### Example transformer
|
||||
|
||||
This transformer will increase all your line numbers by 10.
|
||||
|
||||
```
|
||||
export function transform(errors) {
|
||||
return errors.map(err => {
|
||||
err.line += 10
|
||||
return err
|
||||
})
|
||||
}
|
||||
```
|
||||
57
src/core/plugins/err/error-transformers/hook.js
Normal file
57
src/core/plugins/err/error-transformers/hook.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import concat from "lodash/concat"
|
||||
import reduce from "lodash/reduce"
|
||||
let request = require.context("./transformers/", true, /\.js$/)
|
||||
let errorTransformers = []
|
||||
|
||||
request.keys().forEach( function( key ){
|
||||
if( key === "./hook.js" ) {
|
||||
return
|
||||
}
|
||||
|
||||
if( !key.match(/js$/) ) {
|
||||
return
|
||||
}
|
||||
|
||||
if( key.slice(2).indexOf("/") > -1) {
|
||||
// skip files in subdirs
|
||||
return
|
||||
}
|
||||
|
||||
errorTransformers.push({
|
||||
name: toTitleCase(key).replace(".js", "").replace("./", ""),
|
||||
transform: request(key).transform
|
||||
})
|
||||
})
|
||||
|
||||
export default function transformErrors (errors, system) {
|
||||
let inputs = {
|
||||
jsSpec: system.specSelectors.specJson().toJS()
|
||||
}
|
||||
|
||||
let transformedErrors = reduce(errorTransformers, (result, transformer) => {
|
||||
try {
|
||||
let newlyTransformedErrors = transformer.transform(result, inputs)
|
||||
return newlyTransformedErrors.filter(err => !!err) // filter removed errors
|
||||
} catch(e) {
|
||||
console.error("Transformer error:", e)
|
||||
return result
|
||||
}
|
||||
}, errors)
|
||||
|
||||
return transformedErrors
|
||||
.filter(err => !!err) // filter removed errors
|
||||
.map(err => {
|
||||
if(!err.get("line") && err.get("path")) {
|
||||
// TODO: re-resolve line number if we've transformed it away
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function toTitleCase(str) {
|
||||
return str
|
||||
.split("-")
|
||||
.map(substr => substr[0].toUpperCase() + substr.slice(1))
|
||||
.join("")
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export function transform(errors) {
|
||||
// JSONSchema refers to the current object being validated
|
||||
// as 'instance'. This isn't helpful to users, so we remove it.
|
||||
return errors
|
||||
.map(err => {
|
||||
let seekStr = "is not of a type(s)"
|
||||
let i = err.get("message").indexOf(seekStr)
|
||||
if(i > -1) {
|
||||
let types = err.get("message").slice(i + seekStr.length).split(",")
|
||||
return err.set("message", err.get("message").slice(0, i) + makeNewMessage(types))
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function makeNewMessage(types) {
|
||||
return types.reduce((p, c, i, arr) => {
|
||||
if(i === arr.length - 1 && arr.length > 1) {
|
||||
return p + "or " + c
|
||||
} else if(arr[i+1] && arr.length > 2) {
|
||||
return p + c + ", "
|
||||
} else if(arr[i+1]) {
|
||||
return p + c + " "
|
||||
} else {
|
||||
return p + c
|
||||
}
|
||||
}, "should be a")
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import get from "lodash/get"
|
||||
import last from "lodash/get"
|
||||
import { fromJS, List } from "immutable"
|
||||
|
||||
export function transform(errors, { jsSpec }) {
|
||||
// LOOK HERE THIS TRANSFORMER IS CURRENTLY DISABLED 😃
|
||||
// TODO: finish implementing, fix flattening problem
|
||||
/* eslint-disable no-unreachable */
|
||||
return errors
|
||||
|
||||
|
||||
// JSONSchema gives us very little to go on
|
||||
let searchStr = "is not exactly one from <#/definitions/parameter>,<#/definitions/jsonReference>"
|
||||
return errors
|
||||
.map(err => {
|
||||
let message = err.get("message")
|
||||
let isParameterOneOfError = message.indexOf(searchStr) > -1
|
||||
if(isParameterOneOfError) {
|
||||
// try to find what's wrong
|
||||
return createTailoredParameterError(err, jsSpec)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
})
|
||||
.flatten(true) // shallow Immutable flatten
|
||||
}
|
||||
|
||||
const VALID_IN_VALUES = ["path", "query", "header", "body", "formData"]
|
||||
const VALID_COLLECTIONFORMAT_VALUES = ["csv", "ssv", "tsv", "pipes", "multi"]
|
||||
|
||||
function createTailoredParameterError(err, jsSpec) {
|
||||
let newErrs = []
|
||||
let parameter = get(jsSpec, err.get("path"))
|
||||
|
||||
// find addressable cases
|
||||
if(parameter.in && VALID_IN_VALUES.indexOf(parameter.in) === -1) {
|
||||
let message = `Wrong value for the "in" keyword. Expected one of: ${VALID_IN_VALUES.join(", ")}.`
|
||||
newErrs.push({
|
||||
message,
|
||||
path: err.get("path") + ".in",
|
||||
type: "spec",
|
||||
source: "schema",
|
||||
level: "error"
|
||||
})
|
||||
}
|
||||
|
||||
if(parameter.collectionFormat && VALID_COLLECTIONFORMAT_VALUES.indexOf(parameter.collectionFormat) === -1) {
|
||||
let message = `Wrong value for the "collectionFormat" keyword. Expected one of: ${VALID_COLLECTIONFORMAT_VALUES.join(", ")}.`
|
||||
newErrs.push({
|
||||
message,
|
||||
path: err.get("path") + ".collectionFormat",
|
||||
type: "spec",
|
||||
source: "schema",
|
||||
level: "error"
|
||||
})
|
||||
}
|
||||
|
||||
return newErrs.length ? fromJS(newErrs) : err // fall back to making no changes
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export function transform(errors) {
|
||||
return errors
|
||||
.map(err => {
|
||||
return err.set("message", removeSubstring(err.get("message"), "instance."))
|
||||
})
|
||||
}
|
||||
|
||||
function removeSubstring(str, substr) {
|
||||
return str.replace(new RegExp(substr, "g"), "")
|
||||
}
|
||||
15
src/core/plugins/err/index.js
Normal file
15
src/core/plugins/err/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import makeReducers from "./reducers"
|
||||
import * as actions from "./actions"
|
||||
import * as selectors from "./selectors"
|
||||
|
||||
export default function(system) {
|
||||
return {
|
||||
statePlugins: {
|
||||
err: {
|
||||
reducers: makeReducers(system),
|
||||
actions,
|
||||
selectors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
src/core/plugins/err/reducers.js
Normal file
68
src/core/plugins/err/reducers.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
NEW_THROWN_ERR,
|
||||
NEW_THROWN_ERR_BATCH,
|
||||
NEW_SPEC_ERR,
|
||||
NEW_AUTH_ERR,
|
||||
CLEAR
|
||||
} from "./actions"
|
||||
|
||||
import reject from "lodash/reject"
|
||||
|
||||
import Im, { fromJS, List } from "immutable"
|
||||
|
||||
import transformErrors from "./error-transformers/hook"
|
||||
|
||||
let DEFAULT_ERROR_STRUCTURE = {
|
||||
// defaults
|
||||
line: 0,
|
||||
level: "error",
|
||||
message: "Unknown error"
|
||||
}
|
||||
|
||||
export default function(system) {
|
||||
return {
|
||||
[NEW_THROWN_ERR]: (state, { payload }) => {
|
||||
let error = Object.assign(DEFAULT_ERROR_STRUCTURE, payload, {type: "thrown"})
|
||||
return state
|
||||
.update("errors", errors => (errors || List()).push( fromJS( error )) )
|
||||
.update("errors", errors => transformErrors(errors, system.getSystem()))
|
||||
},
|
||||
|
||||
[NEW_THROWN_ERR_BATCH]: (state, { payload }) => {
|
||||
payload = payload.map(err => {
|
||||
return fromJS(Object.assign(DEFAULT_ERROR_STRUCTURE, err, { type: "thrown" }))
|
||||
})
|
||||
return state
|
||||
.update("errors", errors => (errors || List()).concat( fromJS( payload )) )
|
||||
.update("errors", errors => transformErrors(errors, system.getSystem()))
|
||||
},
|
||||
|
||||
[NEW_SPEC_ERR]: (state, { payload }) => {
|
||||
let error = fromJS(payload)
|
||||
error = error.set("type", "spec")
|
||||
return state
|
||||
.update("errors", errors => (errors || List()).push( fromJS(error)).sortBy(err => err.get("line")) )
|
||||
.update("errors", errors => transformErrors(errors, system.getSystem()))
|
||||
},
|
||||
|
||||
[NEW_AUTH_ERR]: (state, { payload }) => {
|
||||
let error = fromJS(Object.assign({}, payload))
|
||||
|
||||
error = error.set("type", "auth")
|
||||
return state
|
||||
.update("errors", errors => (errors || List()).push( fromJS(error)) )
|
||||
.update("errors", errors => transformErrors(errors, system.getSystem()))
|
||||
},
|
||||
|
||||
[CLEAR]: (state, { payload }) => {
|
||||
if(!payload) {
|
||||
return
|
||||
}
|
||||
// TODO: Rework, to use immutable only, no need for lodash
|
||||
let newErrors = Im.fromJS(reject((state.get("errors") || List()).toJS(), payload))
|
||||
return state.merge({
|
||||
errors: newErrors
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/core/plugins/err/selectors.js
Normal file
15
src/core/plugins/err/selectors.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { List } from "immutable"
|
||||
import { createSelector } from "reselect"
|
||||
|
||||
const state = state => state
|
||||
|
||||
export const allErrors = createSelector(
|
||||
state,
|
||||
err => err.get("errors", List())
|
||||
)
|
||||
|
||||
export const lastError = createSelector(
|
||||
allErrors,
|
||||
all => all.last()
|
||||
)
|
||||
|
||||
45
src/core/plugins/layout/actions.js
Normal file
45
src/core/plugins/layout/actions.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { normalizeArray } from "core/utils"
|
||||
|
||||
export const UPDATE_LAYOUT = "layout_update_layout"
|
||||
export const UPDATE_MODE = "layout_update_mode"
|
||||
export const SHOW = "layout_show"
|
||||
|
||||
// export const ONLY_SHOW = "layout_only_show"
|
||||
|
||||
export function updateLayout(layout) {
|
||||
return {
|
||||
type: UPDATE_LAYOUT,
|
||||
payload: layout
|
||||
}
|
||||
}
|
||||
|
||||
export function show(thing, shown=true) {
|
||||
thing = normalizeArray(thing)
|
||||
return {
|
||||
type: SHOW,
|
||||
payload: {thing, shown}
|
||||
}
|
||||
}
|
||||
|
||||
// Simple string key-store, used for
|
||||
export function changeMode(thing, mode="") {
|
||||
thing = normalizeArray(thing)
|
||||
return {
|
||||
type: UPDATE_MODE,
|
||||
payload: {thing, mode}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// export function onlyShow(thing, shown=true) {
|
||||
// thing = normalizeArray(thing)
|
||||
// if(thing.length < 2)
|
||||
// throw new Error("layoutActions.onlyShow only works, when `thing` is an array with length > 1")
|
||||
// return {
|
||||
// type: ONLY_SHOW,
|
||||
// payload: {thing, shown}
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
15
src/core/plugins/layout/index.js
Normal file
15
src/core/plugins/layout/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import reducers from "./reducers"
|
||||
import * as actions from "./actions"
|
||||
import * as selectors from "./selectors"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
statePlugins: {
|
||||
layout: {
|
||||
reducers,
|
||||
actions,
|
||||
selectors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/core/plugins/layout/reducers.js
Normal file
24
src/core/plugins/layout/reducers.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
UPDATE_LAYOUT,
|
||||
UPDATE_MODE,
|
||||
SHOW
|
||||
} from "./actions"
|
||||
|
||||
export default {
|
||||
|
||||
[UPDATE_LAYOUT]: (state, action) => state.set("layout", action.payload),
|
||||
|
||||
[SHOW]: (state, action) => {
|
||||
let thing = action.payload.thing
|
||||
let shown = action.payload.shown
|
||||
return state.setIn(["shown"].concat(thing), shown)
|
||||
},
|
||||
|
||||
[UPDATE_MODE]: (state, action) => {
|
||||
let thing = action.payload.thing
|
||||
let mode = action.payload.mode
|
||||
return state.setIn(["modes"].concat(thing), (mode || "") + "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
22
src/core/plugins/layout/selectors.js
Normal file
22
src/core/plugins/layout/selectors.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { createSelector } from "reselect"
|
||||
import { normalizeArray } from "core/utils"
|
||||
|
||||
const state = state => state
|
||||
|
||||
export const current = state => state.get("layout")
|
||||
|
||||
export const isShown = (state, thing, def) => {
|
||||
thing = normalizeArray(thing)
|
||||
return Boolean(state.getIn(["shown", ...thing], def))
|
||||
}
|
||||
|
||||
export const whatMode = (state, thing, def="") => {
|
||||
thing = normalizeArray(thing)
|
||||
return state.getIn(["modes", ...thing], def)
|
||||
}
|
||||
|
||||
export const showSummary = createSelector(
|
||||
state,
|
||||
state => !isShown(state, "editor")
|
||||
)
|
||||
|
||||
28
src/core/plugins/logs/index.js
Normal file
28
src/core/plugins/logs/index.js
Normal file
@@ -0,0 +1,28 @@
|
||||
export default function ({configs}) {
|
||||
|
||||
const levels = {
|
||||
"debug": 0,
|
||||
"info": 1,
|
||||
"log": 2,
|
||||
"warn": 3,
|
||||
"error": 4
|
||||
}
|
||||
|
||||
const getLevel = (level) => levels[level] || -1
|
||||
|
||||
let { logLevel } = configs
|
||||
let logLevelInt = getLevel(logLevel)
|
||||
|
||||
function log(level, ...args) {
|
||||
if(getLevel(level) >= logLevelInt)
|
||||
// eslint-disable-next-line no-console
|
||||
console[level](...args)
|
||||
}
|
||||
|
||||
log.warn = log.bind(null, "warn")
|
||||
log.error = log.bind(null, "error")
|
||||
log.info = log.bind(null, "info")
|
||||
log.debug = log.bind(null, "debug")
|
||||
|
||||
return { rootInjects: { log } }
|
||||
}
|
||||
223
src/core/plugins/samples/fn.js
Normal file
223
src/core/plugins/samples/fn.js
Normal file
@@ -0,0 +1,223 @@
|
||||
import { objectify, isFunc, normalizeArray } from "core/utils"
|
||||
import XML from "xml"
|
||||
import memoizee from "memoizee"
|
||||
|
||||
const primitives = {
|
||||
"string": () => "string",
|
||||
"string_email": () => "user@example.com",
|
||||
"string_date-time": () => new Date().toISOString(),
|
||||
"number": () => 0,
|
||||
"number_float": () => 0.0,
|
||||
"integer": () => 0,
|
||||
"boolean": () => true
|
||||
}
|
||||
|
||||
const primitive = (schema) => {
|
||||
schema = objectify(schema)
|
||||
let { type, format } = schema
|
||||
|
||||
let fn = primitives[`${type}_${format}`] || primitives[type]
|
||||
|
||||
if(isFunc(fn))
|
||||
return fn(schema)
|
||||
|
||||
return "Unknown Type: " + schema.type
|
||||
}
|
||||
|
||||
|
||||
export const sampleFromSchema = (schema, config={}) => {
|
||||
let { type, example, properties, additionalProperties, items } = objectify(schema)
|
||||
let { includeReadOnly } = config
|
||||
|
||||
if(example !== undefined)
|
||||
return example
|
||||
|
||||
if(!type) {
|
||||
if(properties) {
|
||||
type = "object"
|
||||
} else if(items) {
|
||||
type = "array"
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if(type === "object") {
|
||||
let props = objectify(properties)
|
||||
let obj = {}
|
||||
for (var name in props) {
|
||||
if ( !props[name].readOnly || includeReadOnly ) {
|
||||
obj[name] = sampleFromSchema(props[name])
|
||||
}
|
||||
}
|
||||
|
||||
if ( additionalProperties === true ) {
|
||||
obj.additionalProp1 = {}
|
||||
} else if ( additionalProperties ) {
|
||||
let additionalProps = objectify(additionalProperties)
|
||||
let additionalPropVal = sampleFromSchema(additionalProps)
|
||||
|
||||
for (let i = 1; i < 4; i++) {
|
||||
obj["additionalProp" + i] = additionalPropVal
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
if(type === "array") {
|
||||
return [ sampleFromSchema(items) ]
|
||||
}
|
||||
|
||||
if(schema["enum"]) {
|
||||
if(schema["default"])
|
||||
return schema["default"]
|
||||
return normalizeArray(schema["enum"])[0]
|
||||
}
|
||||
|
||||
return primitive(schema)
|
||||
}
|
||||
|
||||
export const inferSchema = (thing) => {
|
||||
if(thing.schema)
|
||||
thing = thing.schema
|
||||
|
||||
if(thing.properties) {
|
||||
thing.type = "object"
|
||||
}
|
||||
|
||||
return thing // Hopefully this will have something schema like in it... `type` for example
|
||||
}
|
||||
|
||||
|
||||
export const sampleXmlFromSchema = (schema, config={}) => {
|
||||
let objectifySchema = objectify(schema)
|
||||
let { type, properties, additionalProperties, items, example } = objectifySchema
|
||||
let { includeReadOnly } = config
|
||||
let defaultValue = objectifySchema.default
|
||||
let res = {}
|
||||
let _attr = {}
|
||||
let { xml } = schema
|
||||
let { name, prefix, namespace } = xml
|
||||
let enumValue = objectifySchema.enum
|
||||
let displayName, value
|
||||
|
||||
if(!type) {
|
||||
if(properties || additionalProperties) {
|
||||
type = "object"
|
||||
} else if(items) {
|
||||
type = "array"
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
name = name || "notagname"
|
||||
// add prefix to name if exists
|
||||
displayName = (prefix ? prefix + ":" : "") + name
|
||||
if ( namespace ) {
|
||||
//add prefix to namespace if exists
|
||||
let namespacePrefix = prefix ? ( "xmlns:" + prefix ) : "xmlns"
|
||||
_attr[namespacePrefix] = namespace
|
||||
}
|
||||
|
||||
if (type === "array") {
|
||||
if (items) {
|
||||
items.xml = items.xml || xml || {}
|
||||
items.xml.name = items.xml.name || xml.name
|
||||
|
||||
if (xml.wrapped) {
|
||||
res[displayName] = []
|
||||
if (Array.isArray(defaultValue)) {
|
||||
|
||||
defaultValue.forEach((v)=>{
|
||||
items.default = v
|
||||
res[displayName].push(sampleXmlFromSchema(items, config))
|
||||
})
|
||||
} else {
|
||||
res[displayName] = [sampleXmlFromSchema(items, config)]
|
||||
}
|
||||
|
||||
if (_attr) {
|
||||
res[displayName].push({_attr: _attr})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
let _res = []
|
||||
|
||||
if (Array.isArray(defaultValue)) {
|
||||
defaultValue.forEach((v)=>{
|
||||
items.default = v
|
||||
_res.push(sampleXmlFromSchema(items, config))
|
||||
})
|
||||
return _res
|
||||
|
||||
}
|
||||
return sampleXmlFromSchema(items, config)
|
||||
}
|
||||
}
|
||||
|
||||
if (type === "object") {
|
||||
let props = objectify(properties)
|
||||
res[displayName] = []
|
||||
example = example || {}
|
||||
|
||||
for (let propName in props) {
|
||||
if ( !props[propName].readOnly || includeReadOnly ) {
|
||||
props[propName].xml = props[propName].xml || {}
|
||||
|
||||
if (props[propName].xml.attribute) {
|
||||
let enumAttrVal = Array.isArray(props[propName].enum) && props[propName].enum[0]
|
||||
let attrExample = props[propName].example
|
||||
let attrDefault = props[propName].default
|
||||
_attr[props[propName].xml.name || propName] = attrExample!== undefined && attrExample
|
||||
|| example[propName] !== undefined && example[propName] || attrDefault !== undefined && attrDefault
|
||||
|| 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]
|
||||
res[displayName].push(sampleXmlFromSchema(props[propName]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalProperties === true) {
|
||||
res[displayName].push({additionalProp: "Anything can be here"})
|
||||
} else if (additionalProperties) {
|
||||
res[displayName].push({additionalProp: primitive(additionalProperties)})
|
||||
}
|
||||
|
||||
if (_attr) {
|
||||
res[displayName].push({_attr: _attr})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
if (example !== undefined) {
|
||||
value = example
|
||||
} else if (defaultValue !== undefined) {
|
||||
//display example if exists
|
||||
value = defaultValue
|
||||
} else if (Array.isArray(enumValue)) {
|
||||
//display enum first value
|
||||
value = enumValue[0]
|
||||
} else {
|
||||
//set default value
|
||||
value = primitive(schema)
|
||||
}
|
||||
|
||||
res[displayName] = _attr ? [{_attr: _attr}, value] : value
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
export function createXMLExample(schema, config) {
|
||||
let json = sampleXmlFromSchema(schema, config)
|
||||
if (!json) { return }
|
||||
|
||||
return XML(json, { declaration: true, indent: "\t" })
|
||||
}
|
||||
|
||||
export const memoizedCreateXMLExample = memoizee(createXMLExample)
|
||||
|
||||
export const memoizedSampleFromSchema = memoizee(sampleFromSchema)
|
||||
5
src/core/plugins/samples/index.js
Normal file
5
src/core/plugins/samples/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as fn from "./fn"
|
||||
|
||||
export default function () {
|
||||
return { fn }
|
||||
}
|
||||
234
src/core/plugins/spec/actions.js
Normal file
234
src/core/plugins/spec/actions.js
Normal file
@@ -0,0 +1,234 @@
|
||||
import YAML from "js-yaml"
|
||||
import serializeError from "serialize-error"
|
||||
|
||||
// Actions conform to FSA (flux-standard-actions)
|
||||
// {type: string,payload: Any|Error, meta: obj, error: bool}
|
||||
|
||||
export const UPDATE_SPEC = "spec_update_spec"
|
||||
export const UPDATE_URL = "spec_update_url"
|
||||
export const UPDATE_JSON = "spec_update_json"
|
||||
export const UPDATE_PARAM = "spec_update_param"
|
||||
export const VALIDATE_PARAMS = "spec_validate_param"
|
||||
export const SET_RESPONSE = "spec_set_response"
|
||||
export const SET_REQUEST = "spec_set_request"
|
||||
export const LOG_REQUEST = "spec_log_request"
|
||||
export const CLEAR_RESPONSE = "spec_clear_response"
|
||||
export const CLEAR_REQUEST = "spec_clear_request"
|
||||
export const ClEAR_VALIDATE_PARAMS = "spec_clear_validate_param"
|
||||
export const UPDATE_OPERATION_VALUE = "spec_update_operation_value"
|
||||
export const UPDATE_RESOLVED = "spec_update_resolved"
|
||||
export const SET_SCHEME = "set_scheme"
|
||||
|
||||
export function updateSpec(spec) {
|
||||
if(spec instanceof Error) {
|
||||
return {type: UPDATE_SPEC, error: true, payload: spec}
|
||||
}
|
||||
|
||||
if(typeof spec === "string") {
|
||||
return {
|
||||
type: UPDATE_SPEC,
|
||||
payload: spec.replace(/\t/g, " ") || ""
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: UPDATE_SPEC,
|
||||
payload: ""
|
||||
}
|
||||
}
|
||||
|
||||
export function updateResolved(spec) {
|
||||
return {
|
||||
type: UPDATE_RESOLVED,
|
||||
payload: spec
|
||||
}
|
||||
}
|
||||
|
||||
export function updateUrl(url) {
|
||||
return {type: UPDATE_URL, payload: url}
|
||||
}
|
||||
|
||||
export function updateJsonSpec(json) {
|
||||
if(!json || typeof json !== "object") {
|
||||
throw new Error("updateJson must only accept a simple JSON object")
|
||||
}
|
||||
return {type: UPDATE_JSON, payload: json}
|
||||
}
|
||||
|
||||
export const parseToJson = (str) => ({specActions, specSelectors, errActions}) => {
|
||||
let { specStr } = specSelectors
|
||||
|
||||
let json = null
|
||||
try {
|
||||
str = str || specStr()
|
||||
errActions.clear({ source: "parser" })
|
||||
json = YAML.safeLoad(str)
|
||||
} catch(e) {
|
||||
// TODO: push error to state
|
||||
console.error(e)
|
||||
return errActions.newSpecErr({
|
||||
source: "parser",
|
||||
level: "error",
|
||||
message: e.reason,
|
||||
line: e.mark && e.mark.line ? e.mark.line + 1 : undefined
|
||||
})
|
||||
}
|
||||
return specActions.updateJsonSpec(json)
|
||||
}
|
||||
|
||||
export const resolveSpec = (json, url) => ({specActions, specSelectors, errActions, fn: { fetch, resolve, AST }}) => {
|
||||
if(typeof(json) === "undefined") {
|
||||
json = specSelectors.specJson()
|
||||
}
|
||||
if(typeof(url) === "undefined") {
|
||||
url = specSelectors.url()
|
||||
}
|
||||
|
||||
let { getLineNumberForPath } = AST
|
||||
|
||||
let specStr = specSelectors.specStr()
|
||||
|
||||
return resolve({fetch, spec: json, baseDoc: url})
|
||||
.then( ({spec, errors}) => {
|
||||
errActions.clear({
|
||||
type: "thrown"
|
||||
})
|
||||
|
||||
if(errors.length > 0) {
|
||||
let preparedErrors = errors
|
||||
.map(err => {
|
||||
console.error(err)
|
||||
err.line = err.fullPath ? getLineNumberForPath(specStr, err.fullPath) : null
|
||||
err.path = err.fullPath ? err.fullPath.join(".") : null
|
||||
err.level = "error"
|
||||
err.type = "thrown"
|
||||
err.source = "resolver"
|
||||
Object.defineProperty(err, "message", { enumerable: true, value: err.message })
|
||||
return err
|
||||
})
|
||||
errActions.newThrownErrBatch(preparedErrors)
|
||||
}
|
||||
|
||||
return specActions.updateResolved(spec)
|
||||
})
|
||||
}
|
||||
|
||||
export const formatIntoYaml = () => ({specActions, specSelectors}) => {
|
||||
let { specStr } = specSelectors
|
||||
let { updateSpec } = specActions
|
||||
|
||||
try {
|
||||
let yaml = YAML.safeDump(YAML.safeLoad(specStr()), {indent: 2})
|
||||
updateSpec(yaml)
|
||||
} catch(e) {
|
||||
updateSpec(e)
|
||||
}
|
||||
}
|
||||
|
||||
export function changeParam( path, paramName, value, isXml ){
|
||||
return {
|
||||
type: UPDATE_PARAM,
|
||||
payload:{ path, value, paramName, isXml }
|
||||
}
|
||||
}
|
||||
|
||||
export function validateParams( payload ){
|
||||
return {
|
||||
type: VALIDATE_PARAMS,
|
||||
payload:{ pathMethod: payload }
|
||||
}
|
||||
}
|
||||
|
||||
export function clearValidateParams( payload ){
|
||||
return {
|
||||
type: ClEAR_VALIDATE_PARAMS,
|
||||
payload:{ pathMethod: payload }
|
||||
}
|
||||
}
|
||||
|
||||
export function changeConsumesValue(path, value) {
|
||||
return {
|
||||
type: UPDATE_OPERATION_VALUE,
|
||||
payload:{ path, value, key: "consumes_value" }
|
||||
}
|
||||
}
|
||||
|
||||
export function changeProducesValue(path, value) {
|
||||
return {
|
||||
type: UPDATE_OPERATION_VALUE,
|
||||
payload:{ path, value, key: "produces_value" }
|
||||
}
|
||||
}
|
||||
|
||||
export const setResponse = ( path, method, res ) => {
|
||||
return {
|
||||
payload: { path, method, res },
|
||||
type: SET_RESPONSE
|
||||
}
|
||||
}
|
||||
|
||||
export const setRequest = ( path, method, req ) => {
|
||||
return {
|
||||
payload: { path, method, req },
|
||||
type: SET_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
// This is for debugging, remove this comment if you depend on this action
|
||||
export const logRequest = (req) => {
|
||||
return {
|
||||
payload: req,
|
||||
type: LOG_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
// Actually fire the request via fn.execute
|
||||
// (For debugging) and ease of testing
|
||||
export const executeRequest = (req) => ({fn, specActions, errActions}) => {
|
||||
let { pathName, method } = req
|
||||
let parsedRequest = Object.assign({}, req)
|
||||
if ( pathName && method ) {
|
||||
parsedRequest.operationId = method.toLowerCase() + "-" + pathName
|
||||
}
|
||||
parsedRequest = fn.buildRequest(parsedRequest)
|
||||
specActions.setRequest(req.pathName, req.method, parsedRequest)
|
||||
|
||||
return fn.execute(req)
|
||||
.then( fn.serializeRes )
|
||||
.then( res => specActions.setResponse(req.pathName, req.method, res))
|
||||
.catch( err => specActions.setResponse(req.pathName, req.method, { error: true, err: serializeError(err) } ) )
|
||||
}
|
||||
|
||||
|
||||
// I'm using extras as a way to inject properties into the final, `execute` method - It's not great. Anyone have a better idea? @ponelat
|
||||
export const execute = ( { path, method, ...extras }={} ) => (system) => {
|
||||
let { fn:{fetch}, specSelectors, specActions } = system
|
||||
let spec = specSelectors.spec().toJS()
|
||||
let scheme = specSelectors.operationScheme(path, method)
|
||||
let { requestContentType, responseContentType } = specSelectors.contentTypeValues([path, method]).toJS()
|
||||
let isXml = /xml/i.test(requestContentType)
|
||||
let parameters = specSelectors.parameterValues([path, method], isXml).toJS()
|
||||
|
||||
return specActions.executeRequest({fetch, spec, pathName: path, method, parameters, requestContentType, scheme, responseContentType, ...extras })
|
||||
}
|
||||
|
||||
export function clearResponse (path, method) {
|
||||
return {
|
||||
type: CLEAR_RESPONSE,
|
||||
payload:{ path, method }
|
||||
}
|
||||
}
|
||||
|
||||
export function clearRequest (path, method) {
|
||||
return {
|
||||
type: CLEAR_REQUEST,
|
||||
payload:{ path, method }
|
||||
}
|
||||
}
|
||||
|
||||
export function setScheme (scheme, path, method) {
|
||||
return {
|
||||
type: SET_SCHEME,
|
||||
payload: { scheme, path, method }
|
||||
}
|
||||
}
|
||||
17
src/core/plugins/spec/index.js
Normal file
17
src/core/plugins/spec/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import reducers from "./reducers"
|
||||
import * as actions from "./actions"
|
||||
import * as selectors from "./selectors"
|
||||
import * as wrapActions from "./wrap-actions"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
statePlugins: {
|
||||
spec: {
|
||||
wrapActions,
|
||||
reducers,
|
||||
actions,
|
||||
selectors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
123
src/core/plugins/spec/reducers.js
Normal file
123
src/core/plugins/spec/reducers.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import { fromJS } from "immutable"
|
||||
import { fromJSOrdered, validateParam } from "core/utils"
|
||||
import win from "../../window"
|
||||
|
||||
import {
|
||||
UPDATE_SPEC,
|
||||
UPDATE_URL,
|
||||
UPDATE_JSON,
|
||||
UPDATE_PARAM,
|
||||
VALIDATE_PARAMS,
|
||||
SET_RESPONSE,
|
||||
SET_REQUEST,
|
||||
UPDATE_RESOLVED,
|
||||
UPDATE_OPERATION_VALUE,
|
||||
CLEAR_RESPONSE,
|
||||
CLEAR_REQUEST,
|
||||
ClEAR_VALIDATE_PARAMS,
|
||||
SET_SCHEME
|
||||
} from "./actions"
|
||||
|
||||
export default {
|
||||
|
||||
[UPDATE_SPEC]: (state, action) => {
|
||||
return (typeof action.payload === "string")
|
||||
? state.set("spec", action.payload)
|
||||
: state
|
||||
},
|
||||
|
||||
[UPDATE_URL]: (state, action) => {
|
||||
return state.set("url", action.payload+"")
|
||||
},
|
||||
|
||||
[UPDATE_JSON]: (state, action) => {
|
||||
return state.set("json", fromJSOrdered(action.payload))
|
||||
},
|
||||
|
||||
[UPDATE_RESOLVED]: (state, action) => {
|
||||
return state.setIn(["resolved"], fromJSOrdered(action.payload))
|
||||
},
|
||||
|
||||
[UPDATE_PARAM]: ( state, {payload} ) => {
|
||||
let { path, paramName, value, isXml } = payload
|
||||
return state.updateIn( [ "resolved", "paths", ...path, "parameters" ], fromJS([]), parameters => {
|
||||
let index = parameters.findIndex( p => p.get( "name" ) === paramName )
|
||||
if (!(value instanceof win.File)) {
|
||||
value = fromJSOrdered( value )
|
||||
}
|
||||
return parameters.setIn( [ index, isXml ? "value_xml" : "value" ], value)
|
||||
})
|
||||
},
|
||||
|
||||
[VALIDATE_PARAMS]: ( state, { payload: { pathMethod } } ) => {
|
||||
let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] )
|
||||
let parameters = operation.get("parameters")
|
||||
let isXml = /xml/i.test(operation.get("consumes_value"))
|
||||
|
||||
return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => {
|
||||
return parameters.withMutations( parameters => {
|
||||
for ( let i = 0, len = parameters.count(); i < len; i++ ) {
|
||||
let errors = validateParam(parameters.get(i), isXml)
|
||||
parameters.setIn([i, "errors"], fromJS(errors))
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
[ClEAR_VALIDATE_PARAMS]: ( state, { payload: { pathMethod } } ) => {
|
||||
let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] )
|
||||
let parameters = operation.get("parameters")
|
||||
|
||||
return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => {
|
||||
return parameters.withMutations( parameters => {
|
||||
for ( let i = 0, len = parameters.count(); i < len; i++ ) {
|
||||
parameters.setIn([i, "errors"], fromJS({}))
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
[SET_RESPONSE]: (state, { payload: { res, path, method } } ) =>{
|
||||
let result
|
||||
if ( res.error ) {
|
||||
result = Object.assign({error: true}, res.err)
|
||||
} else {
|
||||
result = res
|
||||
}
|
||||
|
||||
let newState = state.setIn( [ "responses", path, method ], fromJSOrdered(result) )
|
||||
|
||||
// ImmutableJS messes up Blob. Needs to reset its value.
|
||||
if (res.data instanceof win.Blob) {
|
||||
newState = newState.setIn( [ "responses", path, method, "text" ], res.data)
|
||||
}
|
||||
return newState
|
||||
},
|
||||
|
||||
[SET_REQUEST]: (state, { payload: { req, path, method } } ) =>{
|
||||
return state.setIn( [ "requests", path, method ], fromJSOrdered(req))
|
||||
},
|
||||
|
||||
[UPDATE_OPERATION_VALUE]: (state, { payload: { path, value, key } }) => {
|
||||
return state.setIn(["resolved", "paths", ...path, key], fromJS(value))
|
||||
},
|
||||
|
||||
[CLEAR_RESPONSE]: (state, { payload: { path, method } } ) =>{
|
||||
return state.deleteIn( [ "responses", path, method ])
|
||||
},
|
||||
|
||||
[CLEAR_REQUEST]: (state, { payload: { path, method } } ) =>{
|
||||
return state.deleteIn( [ "requests", path, method ])
|
||||
},
|
||||
|
||||
[SET_SCHEME]: (state, { payload: { scheme, path, method } } ) =>{
|
||||
if ( path && method ) {
|
||||
return state.setIn( [ "scheme", path, method ], scheme)
|
||||
}
|
||||
|
||||
if (!path && !method) {
|
||||
return state.setIn( [ "scheme", "_defaultScheme" ], scheme)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
318
src/core/plugins/spec/selectors.js
Normal file
318
src/core/plugins/spec/selectors.js
Normal file
@@ -0,0 +1,318 @@
|
||||
import { createSelector } from "reselect"
|
||||
import { fromJS, Set, Map, List } from "immutable"
|
||||
|
||||
const DEFAULT_TAG = "default"
|
||||
|
||||
const OPERATION_METHODS = ["get", "put", "post", "delete", "options", "head", "patch"]
|
||||
|
||||
const state = state => {
|
||||
return state || Map()
|
||||
}
|
||||
|
||||
export const lastError = createSelector(
|
||||
state,
|
||||
spec => spec.get("lastError")
|
||||
)
|
||||
|
||||
export const url = createSelector(
|
||||
state,
|
||||
spec => spec.get("url")
|
||||
)
|
||||
|
||||
export const specStr = createSelector(
|
||||
state,
|
||||
spec => spec.get("spec") || ""
|
||||
)
|
||||
|
||||
export const specSource = createSelector(
|
||||
state,
|
||||
spec => spec.get("specSource") || "not-editor"
|
||||
)
|
||||
|
||||
export const specJson = createSelector(
|
||||
state,
|
||||
spec => spec.get("json", Map())
|
||||
)
|
||||
|
||||
export const specResolved = createSelector(
|
||||
state,
|
||||
spec => spec.get("resolved", Map())
|
||||
)
|
||||
|
||||
// Default Spec ( as an object )
|
||||
export const spec = state => {
|
||||
let res = specResolved(state)
|
||||
if(res.count() < 1)
|
||||
res = specJson(state)
|
||||
return res
|
||||
}
|
||||
|
||||
export const info = createSelector(
|
||||
spec,
|
||||
spec => returnSelfOrNewMap(spec && spec.get("info"))
|
||||
)
|
||||
|
||||
export const externalDocs = createSelector(
|
||||
spec,
|
||||
spec => returnSelfOrNewMap(spec && spec.get("externalDocs"))
|
||||
)
|
||||
|
||||
export const version = createSelector(
|
||||
info,
|
||||
info => info && info.get("version")
|
||||
)
|
||||
|
||||
export const semver = createSelector(
|
||||
version,
|
||||
version => /v?([0-9]*)\.([0-9]*)\.([0-9]*)/i.exec(version).slice(1)
|
||||
)
|
||||
|
||||
export const paths = createSelector(
|
||||
spec,
|
||||
spec => spec.get("paths")
|
||||
)
|
||||
|
||||
export const operations = createSelector(
|
||||
paths,
|
||||
paths => {
|
||||
if(!paths || paths.size < 1)
|
||||
return List()
|
||||
|
||||
let list = List()
|
||||
|
||||
if(!paths || !paths.forEach) {
|
||||
return List()
|
||||
}
|
||||
|
||||
paths.forEach((path, pathName) => {
|
||||
if(!path || !path.forEach) {
|
||||
return {}
|
||||
}
|
||||
path.forEach((operation, method) => {
|
||||
if(OPERATION_METHODS.indexOf(method) === -1) {
|
||||
return
|
||||
}
|
||||
list = list.push(fromJS({
|
||||
path: pathName,
|
||||
method,
|
||||
operation,
|
||||
id: `${method}-${pathName}`
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
return list
|
||||
}
|
||||
)
|
||||
|
||||
export const consumes = createSelector(
|
||||
spec,
|
||||
spec => Set(spec.get("consumes"))
|
||||
)
|
||||
|
||||
export const produces = createSelector(
|
||||
spec,
|
||||
spec => Set(spec.get("produces"))
|
||||
)
|
||||
|
||||
export const security = createSelector(
|
||||
spec,
|
||||
spec => spec.get("security", List())
|
||||
)
|
||||
|
||||
export const securityDefinitions = createSelector(
|
||||
spec,
|
||||
spec => spec.get("securityDefinitions")
|
||||
)
|
||||
|
||||
|
||||
export const findDefinition = ( state, name ) => {
|
||||
return specResolved(state).getIn(["definitions", name], null)
|
||||
}
|
||||
|
||||
export const definitions = createSelector(
|
||||
spec,
|
||||
spec => spec.get("definitions") || Map()
|
||||
)
|
||||
|
||||
export const basePath = createSelector(
|
||||
spec,
|
||||
spec => spec.get("basePath")
|
||||
)
|
||||
|
||||
export const host = createSelector(
|
||||
spec,
|
||||
spec => spec.get("host")
|
||||
)
|
||||
|
||||
export const schemes = createSelector(
|
||||
spec,
|
||||
spec => spec.get("schemes", Map())
|
||||
)
|
||||
|
||||
export const operationsWithRootInherited = createSelector(
|
||||
operations,
|
||||
consumes,
|
||||
produces,
|
||||
(operations, consumes, produces) => {
|
||||
return operations.map( ops => ops.update("operation", op => {
|
||||
if(op) {
|
||||
if(!Map.isMap(op)) { return }
|
||||
return op.withMutations( op => {
|
||||
if ( !op.get("consumes") ) {
|
||||
op.update("consumes", a => Set(a).merge(consumes))
|
||||
}
|
||||
if ( !op.get("produces") ) {
|
||||
op.update("produces", a => Set(a).merge(produces))
|
||||
}
|
||||
return op
|
||||
})
|
||||
} else {
|
||||
// return something with Immutable methods
|
||||
return Map()
|
||||
}
|
||||
|
||||
}))
|
||||
}
|
||||
)
|
||||
|
||||
export const tags = createSelector(
|
||||
spec,
|
||||
json => json.get("tags", List())
|
||||
)
|
||||
|
||||
export const tagDetails = (state, tag) => {
|
||||
let currentTags = tags(state) || List()
|
||||
return currentTags.filter(Map.isMap).find(t => t.get("name") === tag, Map())
|
||||
}
|
||||
|
||||
export const operationsWithTags = createSelector(
|
||||
operationsWithRootInherited,
|
||||
operations => {
|
||||
return operations.reduce( (taggedMap, op) => {
|
||||
let tags = Set(op.getIn(["operation","tags"]))
|
||||
if(tags.count() < 1)
|
||||
return taggedMap.update(DEFAULT_TAG, List(), ar => ar.push(op))
|
||||
return tags.reduce( (res, tag) => res.update(tag, List(), (ar) => ar.push(op)), taggedMap )
|
||||
}, Map())
|
||||
}
|
||||
)
|
||||
|
||||
export const taggedOperations = createSelector(
|
||||
state,
|
||||
operationsWithTags,
|
||||
(state, tagMap) => {
|
||||
return tagMap.map((ops, tag) => Map({tagDetails: tagDetails(state, tag), operations: ops}))
|
||||
}
|
||||
)
|
||||
|
||||
export const responses = createSelector(
|
||||
state,
|
||||
state => state.get( "responses", Map() )
|
||||
)
|
||||
|
||||
export const requests = createSelector(
|
||||
state,
|
||||
state => state.get( "requests", Map() )
|
||||
)
|
||||
|
||||
export const responseFor = (state, path, method) => {
|
||||
return responses(state).getIn([path, method], null)
|
||||
}
|
||||
|
||||
export const requestFor = (state, path, method) => {
|
||||
return requests(state).getIn([path, method], null)
|
||||
}
|
||||
|
||||
export const allowTryItOutFor = (state, path, method ) => {
|
||||
// This is just a hook for now.
|
||||
return true
|
||||
}
|
||||
|
||||
// Get the parameter value by parameter name
|
||||
export function getParameter(state, pathMethod, name) {
|
||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
return params.filter( (p) => {
|
||||
return Map.isMap(p) && p.get("name") === name
|
||||
}).first()
|
||||
}
|
||||
|
||||
export const hasHost = createSelector(
|
||||
spec,
|
||||
spec => {
|
||||
const host = spec.get("host")
|
||||
return typeof host === "string" && host.length > 0 && host[0] !== "/"
|
||||
}
|
||||
)
|
||||
|
||||
// Get the parameter values, that the user filled out
|
||||
export function parameterValues(state, pathMethod, isXml) {
|
||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
return params.reduce( (hash, p) => {
|
||||
let value = isXml && p.get("in") === "body" ? p.get("value_xml") : p.get("value")
|
||||
return hash.set(p.get("name"), value)
|
||||
}, fromJS({}))
|
||||
}
|
||||
|
||||
// True if any parameter includes `in: ?`
|
||||
export function parametersIncludeIn(parameters, inValue="") {
|
||||
if(List.isList(parameters)) {
|
||||
return parameters.some( p => Map.isMap(p) && p.get("in") === inValue )
|
||||
}
|
||||
}
|
||||
|
||||
// True if any parameter includes `type: ?`
|
||||
export function parametersIncludeType(parameters, typeValue="") {
|
||||
if(List.isList(parameters)) {
|
||||
return parameters.some( p => Map.isMap(p) && p.get("type") === typeValue )
|
||||
}
|
||||
}
|
||||
|
||||
// Get the consumes/produces value that the user selected
|
||||
export function contentTypeValues(state, pathMethod) {
|
||||
let op = spec(state).getIn(["paths", ...pathMethod], fromJS({}))
|
||||
const parameters = op.get("parameters") || new List()
|
||||
const requestContentType = (
|
||||
parametersIncludeType(parameters, "file") ? "multipart/form-data"
|
||||
: parametersIncludeIn(parameters, "formData") ? "application/x-www-form-urlencoded"
|
||||
: op.get("consumes_value")
|
||||
)
|
||||
|
||||
|
||||
return fromJS({
|
||||
requestContentType,
|
||||
responseContentType: op.get("produces_value")
|
||||
})
|
||||
}
|
||||
|
||||
// Get the consumes/produces by path
|
||||
export function operationConsumes(state, pathMethod) {
|
||||
return spec(state).getIn(["paths", ...pathMethod, "consumes"], fromJS({}))
|
||||
}
|
||||
|
||||
export const operationScheme = ( state, path, method ) => {
|
||||
return state.getIn(["scheme", path, method]) || state.getIn(["scheme", "_defaultScheme"]) || "http"
|
||||
}
|
||||
|
||||
export const canExecuteScheme = ( state, path, method ) => {
|
||||
return ["http", "https"].indexOf(operationScheme(state, path, method)) > -1
|
||||
}
|
||||
|
||||
export const validateBeforeExecute = ( state, pathMethod ) => {
|
||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
let isValid = true
|
||||
|
||||
params.forEach( (p) => {
|
||||
let errors = p.get("errors")
|
||||
if ( errors && errors.count() ) {
|
||||
isValid = false
|
||||
}
|
||||
})
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
function returnSelfOrNewMap(obj) {
|
||||
// returns obj if obj is an Immutable map, else returns a new Map
|
||||
return Map.isMap(obj) ? obj : new Map()
|
||||
}
|
||||
15
src/core/plugins/spec/wrap-actions.js
Normal file
15
src/core/plugins/spec/wrap-actions.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export const updateSpec = (ori, {specActions}) => (...args) => {
|
||||
ori(...args)
|
||||
specActions.parseToJson(...args)
|
||||
}
|
||||
|
||||
export const updateJsonSpec = (ori, {specActions}) => (...args) => {
|
||||
ori(...args)
|
||||
specActions.resolveSpec(...args)
|
||||
}
|
||||
|
||||
// Log the request ( just for debugging, shouldn't affect prod )
|
||||
export const executeRequest = (ori, { specActions }) => (req) => {
|
||||
specActions.logRequest(req)
|
||||
return ori(req)
|
||||
}
|
||||
17
src/core/plugins/split-pane-mode/components/index.js
Normal file
17
src/core/plugins/split-pane-mode/components/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { pascalCaseFilename } from "core/utils"
|
||||
|
||||
const request = require.context(".", true, /\.jsx?$/)
|
||||
|
||||
request.keys().forEach( function( key ){
|
||||
if( key === "./index.js" ) {
|
||||
return
|
||||
}
|
||||
|
||||
// if( key.slice(2).indexOf("/") > -1) {
|
||||
// // skip files in subdirs
|
||||
// return
|
||||
// }
|
||||
|
||||
let mod = request(key)
|
||||
module.exports[pascalCaseFilename(key)] = mod.default ? mod.default : mod
|
||||
})
|
||||
@@ -0,0 +1,81 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import SplitPane from "react-split-pane"
|
||||
import "./split-pane-mode.less"
|
||||
|
||||
const MODE_KEY = ["split-pane-mode"]
|
||||
const MODE_LEFT = "left"
|
||||
const MODE_RIGHT = "right"
|
||||
const MODE_BOTH = "both" // or anything other than left/right
|
||||
|
||||
export default class SplitPaneMode extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
threshold: PropTypes.number,
|
||||
|
||||
children: PropTypes.array,
|
||||
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
layoutActions: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
threshold: 100, // in pixels
|
||||
children: [],
|
||||
};
|
||||
|
||||
onDragFinished = () => {
|
||||
let { threshold, layoutActions } = this.props
|
||||
let { position, draggedSize } = this.refs.splitPane.state
|
||||
this.draggedSize = draggedSize
|
||||
|
||||
let nearLeftEdge = position <= threshold
|
||||
let nearRightEdge = draggedSize <= threshold
|
||||
|
||||
layoutActions
|
||||
.changeMode(MODE_KEY, (
|
||||
nearLeftEdge
|
||||
? MODE_RIGHT : nearRightEdge
|
||||
? MODE_LEFT : MODE_BOTH
|
||||
))
|
||||
}
|
||||
|
||||
sizeFromMode = (mode, defaultSize) => {
|
||||
if(mode === MODE_LEFT) {
|
||||
this.draggedSize = null
|
||||
return "0px"
|
||||
} else if (mode === MODE_RIGHT) {
|
||||
this.draggedSize = null
|
||||
return "100%"
|
||||
}
|
||||
// mode === "both"
|
||||
return this.draggedSize || defaultSize
|
||||
}
|
||||
|
||||
render() {
|
||||
let { children, layoutSelectors } = this.props
|
||||
|
||||
const mode = layoutSelectors.whatMode(MODE_KEY)
|
||||
const left = mode === MODE_RIGHT ? <noscript/> : children[0]
|
||||
const right = mode === MODE_LEFT ? <noscript/> : children[1]
|
||||
const size = this.sizeFromMode(mode, '50%')
|
||||
|
||||
return (
|
||||
<SplitPane
|
||||
disabledClass={''}
|
||||
ref={'splitPane'}
|
||||
split='vertical'
|
||||
defaultSize={'50%'}
|
||||
primary="second"
|
||||
minSize={0}
|
||||
size={size}
|
||||
onDragFinished={this.onDragFinished}
|
||||
allowResize={mode !== MODE_LEFT && mode !== MODE_RIGHT }
|
||||
resizerStyle={{"flex": "0 0 auto", "position": "relative"}}
|
||||
>
|
||||
{ left }
|
||||
{ right }
|
||||
</SplitPane>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.swagger-ui {
|
||||
.Resizer.vertical.disabled {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
14
src/core/plugins/split-pane-mode/index.js
Normal file
14
src/core/plugins/split-pane-mode/index.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import * as components from "./components"
|
||||
|
||||
export default function SplitPaneModePlugin() {
|
||||
return {
|
||||
// statePlugins: {
|
||||
// layout: {
|
||||
// actions,
|
||||
// selectors,
|
||||
// }
|
||||
// },
|
||||
|
||||
components,
|
||||
}
|
||||
}
|
||||
13
src/core/plugins/swagger-js/index.js
Normal file
13
src/core/plugins/swagger-js/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import Swagger from "swagger-client"
|
||||
|
||||
module.exports = function({ configs }) {
|
||||
return {
|
||||
fn: {
|
||||
fetch: Swagger.makeHttp(configs.preFetch, configs.postFetch),
|
||||
buildRequest: Swagger.buildRequest,
|
||||
execute: Swagger.execute,
|
||||
resolve: Swagger.resolve,
|
||||
serializeRes: Swagger.serializeRes
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/core/plugins/util/index.js
Normal file
8
src/core/plugins/util/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { shallowEqualKeys } from "core/utils"
|
||||
import { transformPathToArray } from "core/path-translator"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
fn: { shallowEqualKeys, transformPathToArray }
|
||||
}
|
||||
}
|
||||
19
src/core/plugins/view/index.js
Normal file
19
src/core/plugins/view/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as rootInjects from "./root-injects"
|
||||
import { memoize } from "core/utils"
|
||||
|
||||
export default function({getComponents, getStore, getSystem}) {
|
||||
|
||||
let { getComponent, render, makeMappedContainer } = rootInjects
|
||||
|
||||
// getComponent should be passed into makeMappedContainer, _already_ memoized... otherwise we have a big performance hit ( think, really big )
|
||||
const memGetComponent = memoize(getComponent.bind(null, getSystem, getStore, getComponents))
|
||||
const memMakeMappedContainer = memoize(makeMappedContainer.bind(null, getSystem, getStore, memGetComponent, getComponents))
|
||||
|
||||
return {
|
||||
rootInjects: {
|
||||
getComponent: memGetComponent,
|
||||
makeMappedContainer: memMakeMappedContainer,
|
||||
render: render.bind(null, getSystem, getStore, getComponent, getComponents),
|
||||
}
|
||||
}
|
||||
}
|
||||
123
src/core/plugins/view/root-injects.js
Normal file
123
src/core/plugins/view/root-injects.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import React, { Component } from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import { connect, Provider } from "react-redux"
|
||||
import omit from "lodash/omit"
|
||||
|
||||
|
||||
const NotFoundComponent = name => ()=> <span style={{color: "red"}}> "{name}" component not found </span>
|
||||
|
||||
|
||||
const SystemWrapper = (getSystem, ComponentToWrap ) => class extends Component {
|
||||
render() {
|
||||
return <ComponentToWrap {...getSystem() } {...this.props} {...this.context} />
|
||||
}
|
||||
}
|
||||
|
||||
const RootWrapper = (reduxStore, ComponentToWrap) => class extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Provider store={reduxStore}>
|
||||
<ComponentToWrap {...this.props} {...this.context} />
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const makeContainer = (getSystem, component, reduxStore) => {
|
||||
let wrappedWithSystem = SystemWrapper(getSystem, component, reduxStore)
|
||||
let connected = connect(state => ({state}))(wrappedWithSystem)
|
||||
if(reduxStore)
|
||||
return RootWrapper(reduxStore, connected)
|
||||
return connected
|
||||
}
|
||||
|
||||
const handleProps = (getSystem, mapping, props, oldProps) => {
|
||||
for (let prop in mapping) {
|
||||
let fn = mapping[prop]
|
||||
if(typeof fn === "function")
|
||||
fn(props[prop], oldProps[prop], getSystem())
|
||||
}
|
||||
}
|
||||
|
||||
export const makeMappedContainer = (getSystem, getStore, memGetComponent, getComponents, componentName, mapping) => {
|
||||
|
||||
return class extends Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
handleProps(getSystem, mapping, props, {})
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
handleProps(getSystem, mapping, nextProps, this.props)
|
||||
}
|
||||
|
||||
render() {
|
||||
let cleanProps = omit(this.props, mapping ? Object.keys(mapping) : [])
|
||||
let Comp = memGetComponent(componentName, "root")
|
||||
return <Comp {...cleanProps}/>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const render = (getSystem, getStore, getComponent, getComponents, dom) => {
|
||||
let domNode = document.querySelector(dom)
|
||||
let App = (getComponent(getSystem, getStore, getComponents, "App", "root"))
|
||||
ReactDOM.render(( <App/> ), domNode)
|
||||
}
|
||||
|
||||
// Render try/catch wrapper
|
||||
const createClass = component => React.createClass({
|
||||
render() {
|
||||
return component(this.props)
|
||||
}
|
||||
})
|
||||
|
||||
const Fallback = ({ error, name }) => <div style={{ // eslint-disable-line react/prop-types
|
||||
padding: "1em",
|
||||
"color": "#aaa"
|
||||
}}>😱 <i>Could not render { name ? name : "this component" }, see console.</i></div>
|
||||
|
||||
const wrapRender = (component) => {
|
||||
const isStateless = component => !(component.prototype && component.prototype.isReactComponent)
|
||||
|
||||
const target = isStateless(component) ? createClass(component) : component
|
||||
|
||||
const ori = target.prototype.render
|
||||
|
||||
target.prototype.render = function render(...args) {
|
||||
try {
|
||||
return ori.apply(this, args)
|
||||
} catch (error) {
|
||||
console.error(error) // eslint-disable-line no-console
|
||||
return <Fallback error={error} name={target.name} />
|
||||
}
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
|
||||
export const getComponent = (getSystem, getStore, getComponents, componentName, container) => {
|
||||
|
||||
if(typeof componentName !== "string")
|
||||
throw new TypeError("Need a string, to fetch a component. Was given a " + typeof componentName)
|
||||
|
||||
let component = getComponents(componentName)
|
||||
|
||||
if(!component) {
|
||||
getSystem().log.warn("Could not find component", componentName)
|
||||
return null
|
||||
}
|
||||
|
||||
if(!container)
|
||||
return wrapRender(component)
|
||||
|
||||
if(container === "root")
|
||||
return makeContainer(getSystem, component, getStore())
|
||||
|
||||
// container == truthy
|
||||
return makeContainer(getSystem, component)
|
||||
}
|
||||
Reference in New Issue
Block a user