in with the new

This commit is contained in:
Ron
2017-03-17 21:17:53 -07:00
parent bd8344c808
commit f22a628934
157 changed files with 12952 additions and 0 deletions

17
src/core/plugins/all.js Normal file
View 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
})

View 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
View 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))
})
}
}

View File

@@ -0,0 +1,9 @@
import * as AST from "./ast"
import JumpToPath from "./jump-to-path"
export default function() {
return {
fn: { AST },
components: { JumpToPath }
}
}

View 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
}
}

View 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 ) })
}

View 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
}
}
}
}

View 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)
}
}

View 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
}

View 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 })
}

View 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 }
}
}
}

View 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
}
}

View 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
})
}
```

View 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("")
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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"), "")
}

View 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
}
}
}
}

View 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
})
}
}
}

View 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()
)

View 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}
// }
// }

View 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
}
}
}
}

View 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 || "") + "")
}
}

View 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")
)

View 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 } }
}

View 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)

View File

@@ -0,0 +1,5 @@
import * as fn from "./fn"
export default function () {
return { fn }
}

View 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 }
}
}

View 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
}
}
}
}

View 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)
}
}
}

View 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()
}

View 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)
}

View 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
})

View File

@@ -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>
)
}
}

View File

@@ -0,0 +1,5 @@
.swagger-ui {
.Resizer.vertical.disabled {
display: none;
}
}

View File

@@ -0,0 +1,14 @@
import * as components from "./components"
export default function SplitPaneModePlugin() {
return {
// statePlugins: {
// layout: {
// actions,
// selectors,
// }
// },
components,
}
}

View 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
}
}
}

View File

@@ -0,0 +1,8 @@
import { shallowEqualKeys } from "core/utils"
import { transformPathToArray } from "core/path-translator"
export default function() {
return {
fn: { shallowEqualKeys, transformPathToArray }
}
}

View 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),
}
}
}

View 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)
}