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

View File

@@ -0,0 +1,6 @@
/* global ace */
ace.define("ace/snippets/yaml",
["require","exports","module"], function(e,t,n){ // eslint-disable-line no-unused-vars
t.snippetText=undefined
t.scope="yaml"
})

View File

@@ -0,0 +1,27 @@
import React, { PropTypes } from "react"
export default class App extends React.Component {
getLayout() {
let { getComponent, layoutSelectors } = this.props
const layoutName = layoutSelectors.current()
const Component = getComponent(layoutName, true)
return Component ? Component : ()=> <h1> No layout defined for "{layoutName}" </h1>
}
render() {
const Layout = this.getLayout()
return (
<Layout/>
)
}
}
App.propTypes = {
getComponent: PropTypes.func.isRequired,
layoutSelectors: PropTypes.object.isRequired,
}
App.defaultProps = {
}

View File

@@ -0,0 +1,82 @@
import React, { PropTypes } from "react"
export default class ApiKeyAuth extends React.Component {
static propTypes = {
authorized: PropTypes.object,
getComponent: PropTypes.func.isRequired,
errSelectors: PropTypes.object.isRequired,
schema: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func
}
constructor(props, context) {
super(props, context)
let { name, schema } = this.props
let value = this.getValue()
this.state = {
name: name,
schema: schema,
value: value
}
}
getValue () {
let { name, authorized } = this.props
return authorized && authorized.getIn([name, "value"])
}
onChange =(e) => {
let { onChange } = this.props
let value = e.target.value
let newState = Object.assign({}, this.state, { value: value })
this.setState(newState)
onChange(newState)
}
render() {
let { schema, getComponent, errSelectors, name } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
const AuthError = getComponent("authError")
const Markdown = getComponent( "Markdown" )
const JumpToPath = getComponent("JumpToPath", true)
let value = this.getValue()
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
return (
<div>
<h4>Api key authorization<JumpToPath path={[ "securityDefinitions", name ]} /></h4>
{ value && <h6>Authorized</h6>}
<Row>
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
source={ schema.get("description") } />
</Row>
<Row>
<p>Name: <code>{ schema.get("name") }</code></p>
</Row>
<Row>
<p>In: <code>{ schema.get("in") }</code></p>
</Row>
<Row>
<label>Value:</label>
<Col>
{
value || <Input type="text" onChange={ this.onChange }/>
}
</Col>
</Row>
{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
</div>
)
}
}

View File

@@ -0,0 +1,59 @@
import React, { PropTypes } from "react"
export default class AuthorizationPopup extends React.Component {
close =() => {
let { authActions } = this.props
authActions.showDefinitions(false)
}
render() {
let { authSelectors, authActions, getComponent, errSelectors, specSelectors, fn: { AST } } = this.props
let definitions = authSelectors.shownDefinitions()
const Auths = getComponent("auths")
return (
<div className="dialog-ux">
<div className="backdrop-ux"></div>
<div className="modal-ux">
<div className="modal-dialog-ux">
<div className="modal-ux-inner">
<div className="modal-ux-header">
<h3>Available authorizations</h3>
<button type="button" className="close-modal" onClick={ this.close }>
<svg width="20" height="20">
<use xlinkHref="#close" />
</svg>
</button>
</div>
<div className="modal-ux-content">
{
definitions.valueSeq().map(( definition, key ) => {
return <Auths key={ key }
AST={AST}
definitions={ definition }
getComponent={ getComponent }
errSelectors={ errSelectors }
authSelectors={ authSelectors }
authActions={ authActions }
specSelectors={ specSelectors }/>
})
}
</div>
</div>
</div>
</div>
</div>
)
}
static propTypes = {
fn: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
errSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
}
}

View File

@@ -0,0 +1,42 @@
import React, { PropTypes } from "react"
export default class AuthorizeBtn extends React.Component {
static propTypes = {
className: PropTypes.string
}
onClick =() => {
let { authActions, authSelectors, errActions} = this.props
let definitions = authSelectors.definitionsToAuthorize()
authActions.showDefinitions(definitions)
}
render() {
let { authSelectors, getComponent } = this.props
//must be moved out of button component
const AuthorizationPopup = getComponent("authorizationPopup", true)
let showPopup = !!authSelectors.shownDefinitions()
let isAuthorized = !!authSelectors.authorized().size
return (
<div className="auth-wrapper">
<button className={isAuthorized ? "btn authorize locked" : "btn authorize unlocked"} onClick={ this.onClick }>
<span>Authorize</span>
<svg width="20" height="20">
<use xlinkHref={ isAuthorized ? "#locked" : "#unlocked" } />
</svg>
</button>
{ showPopup && <AuthorizationPopup /> }
</div>
)
}
static propTypes = {
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
errActions: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
}
}

View File

@@ -0,0 +1,34 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
export default class AuthorizeOperationBtn extends React.Component {
onClick =(e) => {
e.stopPropagation()
let { security, authActions, authSelectors } = this.props
let definitions = authSelectors.getDefinitionsByNames(security)
authActions.showDefinitions(definitions)
}
render() {
let { security, authSelectors } = this.props
let isAuthorized = authSelectors.isAuthorized(security)
return (
<button className={isAuthorized ? "authorization__btn locked" : "authorization__btn unlocked"} onClick={ this.onClick }>
<svg width="20" height="20">
<use xlinkHref={ isAuthorized ? "#locked" : "#unlocked" } />
</svg>
</button>
)
}
static propTypes = {
authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
security: ImPropTypes.iterable.isRequired
}
}

View File

@@ -0,0 +1,138 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
export default class Auths extends React.Component {
static propTypes = {
definitions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired
}
constructor(props, context) {
super(props, context)
this.state = {}
}
onAuthChange =(auth) => {
let { name } = auth
this.setState({ [name]: auth })
}
submitAuth =(e) => {
e.preventDefault()
let { authActions } = this.props
authActions.authorize(this.state)
}
logoutClick =(e) => {
e.preventDefault()
let { authActions, definitions } = this.props
let auths = definitions.map( (val, key) => {
return key
}).toArray()
authActions.logout(auths)
}
render() {
let { definitions, getComponent, authSelectors, errSelectors, specSelectors } = this.props
const ApiKeyAuth = getComponent("apiKeyAuth")
const BasicAuth = getComponent("basicAuth")
const Oauth2 = getComponent("oauth2", true)
const Button = getComponent("Button")
const JumpToPath = getComponent("JumpToPath", true)
let specStr = specSelectors.specStr()
let authorized = authSelectors.authorized()
let authorizedAuth = definitions.filter( (definition, key) => {
return !!authorized.get(key)
})
let nonOauthDefinitions = definitions.filter( schema => schema.get("type") !== "oauth2")
let oauthDefinitions = definitions.filter( schema => schema.get("type") === "oauth2")
return (
<div className="auth-container">
{
!!nonOauthDefinitions.size && <form onSubmit={ this.submitAuth }>
{
nonOauthDefinitions.map( (schema, name) => {
let type = schema.get("type")
let authEl
switch(type) {
case "apiKey": authEl = <ApiKeyAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ this.onAuthChange } />
break
case "basic": authEl = <BasicAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ this.onAuthChange } />
break
default: authEl = <div key={ name }>Unknown security definition type { type }</div>
}
return (<div key={`${name}-jump`}>
{ authEl }
</div>)
}).toArray()
}
<div className="auth-btn-wrapper">
{
nonOauthDefinitions.size === authorizedAuth.size ? <Button className="btn modal-btn auth" onClick={ this.logoutClick }>Logout</Button>
: <Button type="submit" className="btn modal-btn auth authorize">Authorize</Button>
}
</div>
</form>
}
{
oauthDefinitions && oauthDefinitions.size ? <div>
<div className="scope-def">
<p>Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.</p>
<p>API requires the following scopes. Select which ones you want to grant to Swagger UI.</p>
</div>
{
definitions.filter( schema => schema.get("type") === "oauth2")
.map( (schema, name) =>{
return (<div key={ name }>
<Oauth2 authorized={ authorized }
schema={ schema }
name={ name } />
</div>)
}
).toArray()
}
</div> : null
}
</div>
)
}
static propTypes = {
errSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
definitions: ImPropTypes.iterable.isRequired
}
}

View File

@@ -0,0 +1,97 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
export default class BasicAuth extends React.Component {
static propTypes = {
authorized: PropTypes.object,
getComponent: PropTypes.func.isRequired,
schema: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired
}
constructor(props, context) {
super(props, context)
let { schema, name } = this.props
let value = this.getValue()
let username = value.username
this.state = {
name: name,
schema: schema,
value: !username ? {} : {
username: username
}
}
}
getValue () {
let { authorized, name } = this.props
return authorized && authorized.getIn([name, "value"]) || {}
}
onChange =(e) => {
let { onChange } = this.props
let { value, name } = e.target
let newValue = this.state.value
newValue[name] = value
this.setState({ value: newValue })
onChange(this.state)
}
render() {
let { schema, getComponent, name, errSelectors } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
const AuthError = getComponent("authError")
const JumpToPath = getComponent("JumpToPath", true)
const Markdown = getComponent( "Markdown" )
let username = this.getValue().username
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
return (
<div>
<h4>Basic authorization<JumpToPath path={[ "securityDefinitions", name ]} /></h4>
{ username && <h6>Authorized</h6> }
<Row>
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
source={ schema.get("description") } />
</Row>
<Row>
<Col tablet={2} desktop={2}>username:</Col>
<Col tablet={10} desktop={10}>
{
username || <Input type="text" required="required" name="username" onChange={ this.onChange }/>
}
</Col>
</Row>
{
!username && <Row>
<Col tablet={2} desktop={2}>password:</Col>
<Col tablet={10} desktop={10}><Input required="required" autoComplete="new-password" name="password" type="password" onChange={ this.onChange }/></Col>
</Row>
}
{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
</div>
)
}
static propTypes = {
name: PropTypes.string.isRequired,
errSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
onChange: PropTypes.func,
schema: ImPropTypes.map,
authorized: ImPropTypes.map
}
}

View File

@@ -0,0 +1,23 @@
import React, { PropTypes } from "react"
export default class AuthError extends React.Component {
static propTypes = {
error: PropTypes.object.isRequired
}
render() {
let { error } = this.props
let level = error.get("level")
let message = error.get("message")
let source = error.get("source")
return (
<div className="errors" style={{ backgroundColor: "#ffeeee", color: "red", margin: "1em" }}>
<b style={{ textTransform: "capitalize", marginRight: "1em"}} >{ source } { level }</b>
<span>{ message }</span>
</div>
)
}
}

View File

@@ -0,0 +1,218 @@
import React, { PropTypes } from "react"
import oauth2Authorize from "core/oauth2-authorize"
const IMPLICIT = "implicit"
const ACCESS_CODE = "accessCode"
const PASSWORD = "password"
const APPLICATION = "application"
export default class Oauth2 extends React.Component {
static propTypes = {
name: PropTypes.string,
authorized: PropTypes.object,
configs: PropTypes.object,
getComponent: PropTypes.func.isRequired,
schema: PropTypes.object.isRequired,
authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
errSelectors: PropTypes.object.isRequired,
errActions: PropTypes.object.isRequired
}
constructor(props, context) {
super(props, context)
let { name, schema, authorized } = this.props
let auth = authorized && authorized.get(name)
let username = auth && auth.get("username") || ""
let clientId = auth && auth.get("clientId") || ""
let clientSecret = auth && auth.get("clientSecret") || ""
let passwordType = auth && auth.get("passwordType") || "none"
this.state = {
name: name,
schema: schema,
scopes: [],
clientId: clientId,
clientSecret: clientSecret,
username: username,
password: "",
passwordType: passwordType
}
}
authorize =() => {
let { authActions, errActions, getConfigs } = this.props
let configs = getConfigs()
errActions.clear({authId: name,type: "auth", source: "auth"})
oauth2Authorize(this.state, authActions, errActions, configs)
}
onScopeChange =(e) => {
let { target } = e
let { checked } = target
let scope = target.dataset.value
if ( checked && this.state.scopes.indexOf(scope) === -1 ) {
let newScopes = this.state.scopes.concat([scope])
this.setState({ scopes: newScopes })
} else if ( !checked && this.state.scopes.indexOf(scope) > -1) {
this.setState({ scopes: this.state.scopes.filter((val) => val !== scope) })
}
}
onInputChange =(e) => {
let { target : { dataset : { name }, value } } = e
let state = {
[name]: value
}
this.setState(state)
}
logout =(e) => {
e.preventDefault()
let { authActions, errActions, name } = this.props
errActions.clear({authId: name, type: "auth", source: "auth"})
authActions.logout([ name ])
}
render() {
let { schema, getComponent, authSelectors, errSelectors, name } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
const Button = getComponent("Button")
const AuthError = getComponent("authError")
const JumpToPath = getComponent("JumpToPath", true)
const Markdown = getComponent( "Markdown" )
let flow = schema.get("flow")
let scopes = schema.get("allowedScopes") || schema.get("scopes")
let authorizedAuth = authSelectors.authorized().get(name)
let isAuthorized = !!authorizedAuth
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
let isValid = !errors.filter( err => err.get("source") === "validation").size
return (
<div>
<h4>OAuth2.0 <JumpToPath path={[ "securityDefinitions", name ]} /></h4>
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
source={ schema.get("description") } />
{ isAuthorized && <h6>Authorized</h6> }
{ ( flow === IMPLICIT || flow === ACCESS_CODE ) && <p>Authorization URL: <code>{ schema.get("authorizationUrl") }</code></p> }
{ ( flow === PASSWORD || flow === ACCESS_CODE || flow === APPLICATION ) && <p>Token URL:<code> { schema.get("tokenUrl") }</code></p> }
<p className="flow">Flow: <code>{ schema.get("flow") }</code></p>
{
flow === PASSWORD && ( !isAuthorized || isAuthorized && this.state.username) && <Row>
<Col tablet={2} desktop={2}>username:</Col>
<Col tablet={10} desktop={10}>
{
isAuthorized ? <span>{ this.state.username }</span>
: <input type="text" data-name="username" onChange={ this.onInputChange }/>
}
</Col>
</Row>
}
{
flow === PASSWORD && !isAuthorized && <Row>
<Col tablet={2} desktop={2}>password:</Col>
<Col tablet={10} desktop={10}>
<input type="password" data-name="password" onChange={ this.onInputChange }/>
</Col>
</Row>
}
{
flow === PASSWORD && <Row>
<Col tablet={2} desktop={2}>type:</Col>
<Col tablet={10} desktop={10}>
{
isAuthorized ? <span>{ this.state.passwordType }</span>
: <select data-name="passwordType" onChange={ this.onInputChange }>
<option value="none">None or other</option>
<option value="basic">Basic auth</option>
<option value="request">Request body</option>
</select>
}
</Col>
</Row>
}
{
( flow === IMPLICIT || flow === ACCESS_CODE || ( flow === PASSWORD && this.state.passwordType!== "none") ) &&
( !isAuthorized || isAuthorized && this.state.clientId) && <Row>
<label htmlFor="client_id">client_id:</label>
<Col tablet={10} desktop={10}>
{
isAuthorized ? <span>{ this.state.clientId }</span>
: <input id="client_id" type="text" required={ flow === PASSWORD } data-name="clientId"
onChange={ this.onInputChange }/>
}
</Col>
</Row>
}
{
( flow === ACCESS_CODE || ( flow === PASSWORD && this.state.passwordType!== "none") ) && <Row>
<label htmlFor="client_secret">client_secret:</label>
<Col tablet={10} desktop={10}>
{
isAuthorized ? <span>{ this.state.clientSecret }</span>
: <input id="client_secret" type="text" data-name="clientSecret"
onChange={ this.onInputChange }/>
}
</Col>
</Row>
}
{
!isAuthorized && flow !== PASSWORD && scopes && scopes.size ? <div className="scopes">
<h2>Scopes:</h2>
{ scopes.map((description, name) => {
return (
<Row key={ name }>
<div className="checkbox">
<Input data-value={ name }
id={`${name}-checkbox`}
disabled={ isAuthorized }
type="checkbox"
onChange={ this.onScopeChange }/>
<label htmlFor={`${name}-checkbox`}>
<span className="item"></span>
<div className="text">
<p className="name">{name}</p>
<p className="description">{description}</p>
</div>
</label>
</div>
</Row>
)
}).toArray()
}
</div> : null
}
{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
<div className="auth-btn-wrapper">
{ isValid && flow !== APPLICATION &&
( isAuthorized ? <Button className="btn modal-btn auth authorize" onClick={ this.logout }>Logout</Button>
: <Button className="btn modal-btn auth authorize" onClick={ this.authorize }>Authorize</Button>
)
}
</div>
</div>
)
}
}

View File

@@ -0,0 +1,24 @@
import React, { Component, PropTypes } from "react"
export default class Clear extends Component {
onClick =() => {
let { specActions, path, method } = this.props
specActions.clearResponse( path, method )
specActions.clearRequest( path, method )
}
render(){
return (
<button className="btn btn-clear opblock-control__btn" onClick={ this.onClick }>
Clear
</button>
)
}
static propTypes = {
specActions: PropTypes.object.isRequired,
path: PropTypes.string.isRequired,
method: PropTypes.string.isRequired,
}
}

View File

@@ -0,0 +1,45 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
import { fromJS } from 'immutable'
const noop = ()=>{}
export default class ContentType extends React.Component {
static propTypes = {
contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set]),
value: PropTypes.string,
onChange: PropTypes.func,
className: PropTypes.string
}
static defaultProps = {
onChange: noop,
value: null,
contentTypes: fromJS(["application/json"]),
}
componentDidMount() {
// Needed to populate the form, initially
this.props.onChange(this.props.contentTypes.first())
}
onChangeWrapper = e => this.props.onChange(e.target.value)
render() {
let { contentTypes, className, value } = this.props
if ( !contentTypes || !contentTypes.size )
return null
return (
<div className={ "content-type-wrapper " + ( className || "" ) }>
<select className="content-type" value={value} onChange={this.onChangeWrapper} >
{ contentTypes.map( (val) => {
return <option key={ val } value={ val }>{ val }</option>
}).toArray()}
</select>
</div>
)
}
}

View File

@@ -0,0 +1,28 @@
import React, { PropTypes } from "react"
import curlify from "core/curlify"
export default class Curl extends React.Component {
static propTypes = {
request: PropTypes.object.isRequired
}
handleFocus(e) {
e.target.select()
document.execCommand("copy")
}
render() {
let { request } = this.props
let curl = curlify(request)
return (
<div>
<h4>Curl</h4>
<div className="copy-paste">
<textarea onFocus={this.handleFocus} className="curl" style={{ whiteSpace: "normal" }} defaultValue={curl}></textarea>
</div>
</div>
)
}
}

View File

@@ -0,0 +1,51 @@
import React, { PropTypes } from "react"
import Collapse from "react-collapse"
import { presets } from "react-motion"
import ObjectInspector from "react-object-inspector"
import Perf from "react-addons-perf"
export default class Debug extends React.Component {
constructor() {
super()
this.state = {
jsonDumpOpen: false
}
this.toggleJsonDump = (e) => {
e.preventDefault()
this.setState({jsonDumpOpen: !this.state.jsonDumpOpen})
}
window.Perf = Perf
}
plusOrMinus(bool) {
return bool ? "-" : "+"
}
render() {
let { getState } = this.props
window.props = this.props
return (
<div className="info">
<h3><a onClick={this.toggleJsonDump}> {this.plusOrMinus(this.state.jsonDumpOpen)} App </a></h3>
<Collapse isOpened={this.state.jsonDumpOpen} springConfig={presets.noWobble}>
<ObjectInspector data={getState().toJS() || {}} name="state" initialExpandedPaths={["state"]}/>
</Collapse>
</div>
)
}
}
Debug.propTypes = {
getState: PropTypes.func.isRequired
}

View File

@@ -0,0 +1,113 @@
import React, { PropTypes } from "react"
import Im, { List } from "immutable"
import Collapse from "react-collapse"
import sortBy from "lodash/sortBy"
export default class Errors extends React.Component {
static propTypes = {
jumpToLine: PropTypes.func,
errSelectors: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired
}
render() {
let { jumpToLine, errSelectors, layoutSelectors, layoutActions } = this.props
let errors = errSelectors.allErrors()
// all thrown errors, plus error-level everything else
let allErrorsToDisplay = errors.filter(err => err.get("type") === "thrown" ? true :err.get("level") === "error")
if(!allErrorsToDisplay || allErrorsToDisplay.count() < 1) {
return null
}
let isVisible = layoutSelectors.isShown(["errorPane"], true)
let toggleVisibility = () => layoutActions.show(["errorPane"], !isVisible)
let sortedJSErrors = allErrorsToDisplay.sortBy(err => err.get("line"))
return (
<pre className="errors-wrapper">
<hgroup className="error">
<h4 className="errors__title">Errors</h4>
<button className="btn errors__clear-btn" onClick={ toggleVisibility }>{ isVisible ? "Hide" : "Show" }</button>
</hgroup>
<Collapse isOpened={ isVisible } animated >
<div className="errors">
{ sortedJSErrors.map((err, i) => {
if(err.get("type") === "thrown") {
return <ThrownErrorItem key={ i } error={ err.get("error") || err } jumpToLine={jumpToLine} />
}
if(err.get("type") === "spec") {
return <SpecErrorItem key={ i } error={ err } jumpToLine={jumpToLine} />
}
}) }
</div>
</Collapse>
</pre>
)
}
}
const ThrownErrorItem = ( { error, jumpToLine } ) => {
if(!error) {
return null
}
let errorLine = error.get("line")
return (
<div className="error-wrapper">
{ !error ? null :
<div>
<h4>{ (error.get("source") && error.get("level")) ?
toTitleCase(error.get("source")) + " " + error.get("level") : "" }
{ error.get("path") ? <small> at {error.get("path")}</small>: null }</h4>
<span style={{ whiteSpace: "pre-line", "maxWidth": "100%" }}>
{ error.get("message") }
</span>
<div>
{ errorLine ? <a onClick={jumpToLine.bind(null, errorLine)}>Jump to line { errorLine }</a> : null }
</div>
</div>
}
</div>
)
}
const SpecErrorItem = ( { error, jumpToLine } ) => {
return (
<div className="error-wrapper">
{ !error ? null :
<div>
<h4>{ toTitleCase(error.get("source")) + " " + error.get("level") }{ error.get("path") ? <small> at {List.isList(error.get("path")) ? error.get("path").join(".") : error.get("path")}</small>: null }</h4>
<span style={{ whiteSpace: "pre-line"}}>{ error.get("message") }</span>
<div>
{ jumpToLine ? (
<a onClick={jumpToLine.bind(null, error.get("line"))}>Jump to line { error.get("line") }</a>
) : null }
</div>
</div>
}
</div>
)
}
function toTitleCase(str) {
return str
.split(" ")
.map(substr => substr[0].toUpperCase() + substr.slice(1))
.join(" ")
}
ThrownErrorItem.propTypes = {
error: PropTypes.object.isRequired,
jumpToLine: PropTypes.func
}
SpecErrorItem.propTypes = {
error: PropTypes.object.isRequired,
jumpToLine: PropTypes.func
}

View File

@@ -0,0 +1,41 @@
import React, { Component, PropTypes } from "react"
import { fromJS } from "immutable"
export default class Execute extends Component {
static propTypes = {
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
operation: PropTypes.object.isRequired,
path: PropTypes.string.isRequired,
getComponent: PropTypes.func.isRequired,
method: PropTypes.string.isRequired,
onExecute: PropTypes.func
}
onClick=()=>{
let { specSelectors, specActions, operation, path, method } = this.props
specActions.validateParams( [path, method] )
if ( specSelectors.validateBeforeExecute([path, method]) ) {
if(this.props.onExecute) {
this.props.onExecute()
}
specActions.execute( { operation, path, method } )
}
}
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val)
render(){
let { getComponent, operation, specActions, path, method } = this.props
const ContentType = getComponent( "contentType" )
return (
<button className="btn execute opblock-control__btn" onClick={ this.onClick }>
Execute
</button>
)
}
}

View File

@@ -0,0 +1,9 @@
import React from "react"
export default class Footer extends React.Component {
render() {
return (
<div className="footer"></div>
)
}
}

View File

@@ -0,0 +1,46 @@
import React, { PropTypes } from "react"
import Im from "immutable"
export default class Headers extends React.Component {
static propTypes = {
headers: PropTypes.object.isRequired
};
render() {
let { headers } = this.props
if ( !headers || !headers.size )
return null
return (
<div className="headers-wrapper">
<h4 className="headers__title">Headers:</h4>
<table className="headers">
<thead>
<tr className="header-row">
<th className="header-col">Name</th>
<th className="header-col">Description</th>
<th className="header-col">Type</th>
</tr>
</thead>
<tbody>
{
headers.entrySeq().map( ([ key, header ]) => {
if(!Im.Map.isMap(header)) {
return null
}
return (<tr key={ key }>
<td className="header-col">{ key }</td>
<td className="header-col">{ header.get( "description" ) }</td>
<td className="header-col">{ header.get( "type" ) }</td>
</tr>)
}).toArray()
}
</tbody>
</table>
</div>
)
}
}

View File

@@ -0,0 +1,24 @@
import React, { Component, PropTypes } from "react"
import { highlight } from "core/utils"
export default class HighlightCode extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
className: PropTypes.string
}
componentDidMount() {
highlight(this.refs.el)
}
componentDidUpdate() {
highlight(this.refs.el)
}
render () {
let { value, className } = this.props
className = className || ""
return <pre ref="el" className={className + " microlight"}>{ value }</pre>
}
}

View File

@@ -0,0 +1,128 @@
import React, { PropTypes } from "react"
import { fromJS } from "immutable"
import ImPropTypes from "react-immutable-proptypes"
class Path extends React.Component {
static propTypes = {
host: PropTypes.string,
basePath: PropTypes.string
}
render() {
let { host, basePath } = this.props
return (
<pre className="base-url">
[ Base url: {host}{basePath}]
</pre>
)
}
}
class Contact extends React.Component {
static propTypes = {
data: PropTypes.object
}
render(){
let { data } = this.props
let name = data.get("name") || "the developer"
let url = data.get("url")
let email = data.get("email")
return (
<div>
{ url && <div><a href={ url } target="_blank">{ name } - Website</a></div> }
{ email &&
<a href={`mailto:${email}`}>
{ url ? `Send email to ${name}` : `Contact ${name}`}
</a>
}
</div>
)
}
}
class License extends React.Component {
static propTypes = {
license: PropTypes.object
}
render(){
let { license } = this.props
let name = license.get("name") || "License"
let url = license.get("url")
return (
<div>
{
url ? <a href={ url }>{ name }</a>
: <span>{ name }</span>
}
</div>
)
}
}
export default class Info extends React.Component {
static propTypes = {
info: PropTypes.object,
url: PropTypes.string,
host: PropTypes.string,
basePath: PropTypes.string,
externalDocs: ImPropTypes.map,
getComponent: PropTypes.func.isRequired,
}
render() {
let { info, url, host, basePath, getComponent, externalDocs } = this.props
let version = info.get("version")
let description = info.get("description")
let title = info.get("title")
let termsOfService = info.get("termsOfService")
let contact = info.get("contact")
let license = info.get("license")
const { url:externalDocsUrl, description:externalDocsDescription } = (externalDocs || fromJS({})).toJS()
const Markdown = getComponent("Markdown")
return (
<div className="info">
<hgroup className="main">
<h2 className="title" >{ title }
{ version && <small><pre className="version"> { version } </pre></small> }
</h2>
{ host || basePath ? <Path host={ host } basePath={ basePath } /> : null }
{ url && <a href={ url }><span className="url"> { url } </span></a> }
</hgroup>
<div className="description">
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} source={ description } />
</div>
{
termsOfService && <div>
<a href={ termsOfService }>Terms of service</a>
</div>
}
{ contact && contact.size ? <Contact data={ contact } /> : null }
{ license && license.size ? <License license={ license } /> : null }
{ externalDocsUrl ?
<a target="_blank" href={externalDocsUrl}>{externalDocsDescription || externalDocsUrl}</a>
: null }
</div>
)
}
}
Info.propTypes = {
title: PropTypes.any,
description: PropTypes.any,
version: PropTypes.any,
url: PropTypes.string
}

View File

@@ -0,0 +1,252 @@
import React, { PropTypes } from "react"
import OriCollapse from "react-collapse"
import _Markdown from "react-remarkable"
const noop = () => {}
function xclass(...args) {
return args.filter(a => !!a).join(" ").trim()
}
export const Markdown = _Markdown
export class Container extends React.Component {
render() {
let { fullscreen, full, ...rest } = this.props
// Normal element
if(fullscreen)
return <section {...rest}/>
let containerClass = "container" + (full ? "-full" : "")
return (
<section {...rest} className={xclass(rest.className, containerClass)}/>
)
}
}
Container.propTypes = {
fullscreen: PropTypes.bool,
full: PropTypes.bool,
className: PropTypes.string
}
const DEVICES = {
"mobile": "",
"tablet": "-tablet",
"desktop": "-desktop",
"large": "-hd"
}
export class Col extends React.Component {
render() {
const {
hide,
keepContents,
mobile, /* we don't want these in the final component, since React now complains. So we extract them */
tablet,
desktop,
large,
...rest
} = this.props
if(hide && !keepContents)
return <span/>
let classesAr = []
for (let device in DEVICES) {
let deviceClass = DEVICES[device]
if(device in this.props) {
let val = this.props[device]
if(val < 1) {
classesAr.push("none" + deviceClass)
continue
}
classesAr.push("block" + deviceClass)
classesAr.push("col-" + val + deviceClass)
}
}
let classes = xclass(rest.className, "clear", ...classesAr)
return (
<section {...rest} style={{display: hide ? "none": null}} className={classes}/>
)
}
}
Col.propTypes = {
hide: PropTypes.bool,
keepContents: PropTypes.bool,
mobile: PropTypes.number,
tablet: PropTypes.number,
desktop: PropTypes.number,
large: PropTypes.number,
className: PropTypes.string
}
export class Row extends React.Component {
render() {
return <div {...this.props} className={xclass(this.props.className, "wrapper")} />
}
}
Row.propTypes = {
className: PropTypes.string
}
export class Button extends React.Component {
static propTypes = {
className: PropTypes.string
}
static defaultProps = {
className: ""
}
render() {
return <button {...this.props} className={xclass(this.props.className, "button")} />
}
}
export const TextArea = (props) => <textarea {...props} />
export const Input = (props) => <input {...props} />
export class Select extends React.Component {
static propTypes = {
allowedValues: PropTypes.object,
value: PropTypes.any,
onChange: PropTypes.func,
multiple: PropTypes.bool,
allowEmptyValue: PropTypes.bool
}
static defaultProps = {
multiple: false,
allowEmptyValue: true
}
constructor(props, context) {
super(props, context)
let value
if (props.value !== undefined) {
value = props.value
} else {
value = props.multiple ? [""] : ""
}
this.state = { value: value }
}
onChange = (e) => {
let { onChange, multiple } = this.props
let options = [].slice.call(e.target.options)
let value
if (multiple) {
value = options.filter(function (option) {
return option.selected
})
.map(function (option){
return option.value
})
} else {
value = e.target.value
}
this.setState({value: value})
onChange && onChange(value)
}
render(){
let { allowedValues, multiple, allowEmptyValue } = this.props
let value = this.state.value.toJS ? this.state.value.toJS() : this.state.value
return (
<select multiple={ multiple } value={ value } onChange={ this.onChange } >
{ allowEmptyValue ? <option value="">--</option> : null }
{
allowedValues.map(function (item, key) {
return <option key={ key } value={ String(item) }>{ item }</option>
})
}
</select>
)
}
}
export class Link extends React.Component {
render() {
return <a {...this.props} className={xclass(this.props.className, "link")}/>
}
}
Link.propTypes = {
className: PropTypes.string
}
const NoMargin = ({children}) => <div style={{height: "auto", border: "none", margin: 0, padding: 0}}> {children} </div>
NoMargin.propTypes = {
children: PropTypes.node
}
export class Collapse extends React.Component {
static propTypes = {
isOpened: PropTypes.bool,
children: PropTypes.node.isRequired,
animated: PropTypes.bool
}
static defaultProps = {
isOpened: false,
animated: false
}
renderNotAnimated() {
if(!this.props.isOpened)
return <noscript/>
return (
<NoMargin>
{this.props.children}
</NoMargin>
)
}
render() {
let { animated, isOpened, children } = this.props
if(!animated)
return this.renderNotAnimated()
children = isOpened ? children : null
return (
<OriCollapse isOpened={isOpened}>
<NoMargin>
{children}
</NoMargin>
</OriCollapse>
)
}
}

View File

@@ -0,0 +1,72 @@
import React, { PropTypes } from "react"
export default class XPane extends React.Component {
render() {
let { getComponent, specSelectors, specActions, layoutSelectors, layoutActions } = this.props
let info = specSelectors.info()
let url = specSelectors.url()
let showEditor = layoutSelectors.isShown("editor")
let Info = getComponent("info")
let Operations = getComponent("operations", true)
let Overview = getComponent("overview", true)
let Editor = getComponent("editor", true)
let Footer = getComponent("footer", true)
let Header = getComponent("header", true)
let Container = getComponent("Container")
let Row = getComponent("Row")
let Col = getComponent("Col")
let Button = getComponent("Button")
let showEditorAction = ()=> layoutActions.show("editor", !showEditor)
return (
<Container fullscreen>
<Header/>
{
info && info.size ? <Info version={info.get("version")}
description={info.get("description")}
title={info.get("title")}
url={url}/>
: null
}
<Button onClick={showEditorAction}>{showEditor ? "Hide" : "Show"} Editor</Button>
<Button onClick={specActions.formatIntoYaml}>Format contents</Button>
<Row>
<Col desktop={3} >
<Overview/>
</Col>
<Col hide={!showEditor} keepContents={true} desktop={5} >
<Editor/>
</Col>
<Col desktop={showEditor ? 4 : 9} >
<Operations/>
</Col>
</Row>
<Footer></Footer>
</Container>
)
}
}
XPane.propTypes = {
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired
}

View File

@@ -0,0 +1,91 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
const Headers = ( { headers } )=>{
return (
<div>
<h5>Response headers</h5>
<pre>{headers}</pre>
</div>)
}
Headers.propTypes = {
headers: PropTypes.array.isRequired
}
export default class LiveResponse extends React.Component {
static propTypes = {
response: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired
}
render() {
let { request, response, getComponent } = this.props
const Curl = getComponent("curl")
let body = response.get("text")
let status = response.get("status")
let url = response.get("url")
let originalHeaders = response.get("headers")
let headers = originalHeaders && originalHeaders.toJS()
let headersKeys = Object.keys(headers)
let returnObject = headersKeys.map(key => {
return <span className="headerline" key={key}> {key}: {headers[key]} </span>
})
let notDocumented = response.get("notDocumented")
let ResponseBody = getComponent("responseBody")
let contentType = headers && headers["content-type"]
let isError = response.get("error")
return (
<div>
{ request && <Curl request={ request }/> }
<h4>Server response</h4>
<table className="responses-table">
<thead>
<tr className="responses-header">
<td className="col col_header response-col_status">Code</td>
<td className="col col_header response-col_description">Details</td>
</tr>
</thead>
<tbody>
<tr className="response">
<td className="col response-col_status">
{ status }
{
!notDocumented ? null :
<div className="response-undocumented">
<i> Undocumented </i>
</div>
}
</td>
<td className="col response-col_description">
{
!isError ? null : <span>
{`${response.get("name")}: ${response.get("message")}`}
</span>
}
{
!body || isError ? null
: <ResponseBody content={ body }
contentType={ contentType }
url={ url }
headers={ headers }
getComponent={ getComponent }/>
}
{
!headers ? null : <Headers headers={ returnObject }/>
}
</td>
</tr>
</tbody>
</table>
</div>
)
}
static propTypes = {
getComponent: PropTypes.func.isRequired,
request: ImPropTypes.map,
response: ImPropTypes.map
}
}

View File

@@ -0,0 +1,58 @@
import React, { PropTypes } from "react"
export default class ModelExample extends React.Component {
static propTypes = {
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
schema: PropTypes.object.isRequired,
example: PropTypes.any.isRequired,
isExecute: PropTypes.bool
}
constructor(props, context) {
super(props, context)
this.state = {
activeTab: "example"
}
}
activeTab =( e ) => {
let { target : { dataset : { name } } } = e
this.setState({
activeTab: name
})
}
render() {
let { getComponent, specSelectors, schema, example, isExecute } = this.props
const Model = getComponent("model")
return <div>
<ul className="tab">
<li className={ "tabitem" + ( isExecute || this.state.activeTab === "example" ? " active" : "") }>
<a className="tablinks" data-name="example" onClick={ this.activeTab }>Example Value</a>
</li>
<li className={ "tabitem" + ( !isExecute && this.state.activeTab === "model" ? " active" : "") }>
<a className={ "tablinks" + ( isExecute ? " inactive" : "" )} data-name="model" onClick={ this.activeTab }>Model</a>
</li>
</ul>
<div>
{
(isExecute || this.state.activeTab === "example") && example
}
{
!isExecute && this.state.activeTab === "model" && <Model schema={ schema }
getComponent={ getComponent }
specSelectors={ specSelectors }
expandDepth={ 1 } />
}
</div>
</div>
}
}

View File

@@ -0,0 +1,300 @@
import React, { Component, PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
import isObject from "lodash/isObject"
import { List } from "immutable"
const braceOpen = "{"
const braceClose = "}"
const EnumModel = ({ value }) => {
let collapsedContent = <span>Array [ { value.count() } ]</span>
return <span className="prop-enum">
Enum:<br />
<Collapse collapsedContent={ collapsedContent }>
[ { value.join(", ") } ]
</Collapse>
</span>
}
EnumModel.propTypes = {
value: ImPropTypes.iterable
}
class ObjectModel extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
isRef: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number
}
render(){
let { schema, name, isRef, getComponent, depth, ...props } = this.props
let { expandDepth } = this.props
const JumpToPath = getComponent("JumpToPath", true)
let description = schema.get("description")
let properties = schema.get("properties")
let additionalProperties = schema.get("additionalProperties")
let title = schema.get("title") || name
let required = schema.get("required")
const JumpToPathSection = ({ name }) => <span className="model-jump-to-path"><JumpToPath path={`definitions.${name}`} /></span>
let collapsedContent = (<span>
<span>{ braceOpen }</span>...<span>{ braceClose }</span>
{
isRef ? <JumpToPathSection name={ name }/> : ""
}
</span>)
return <span className="model">
{
title && <span className="model-title">
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> }
<span className="model-title__text">{ title }</span>
</span>
}
<Collapse collapsed={ depth > expandDepth } collapsedContent={ collapsedContent }>
<span className="brace-open object">{ braceOpen }</span>
{
!isRef ? null : <JumpToPathSection name={ name }/>
}
<span className="inner-object">
{
<table className="model" style={{ marginLeft: "2em" }}><tbody>
{
!description ? null : <tr style={{ color: "#999", fontStyle: "italic" }}>
<td>description:</td>
<td>{ description }</td>
</tr>
}
{
!(properties && properties.size) ? null : properties.entrySeq().map(
([key, value]) => {
let isRequired = List.isList(required) && required.contains(key)
let propertyStyle = { verticalAlign: "top", paddingRight: "0.2em" }
if ( isRequired ) {
propertyStyle.fontWeight = "bold"
}
return (<tr key={key}>
<td style={ propertyStyle }>{ key }:</td>
<td style={{ verticalAlign: "top" }}>
<Model key={ `object-${name}-${key}_${value}` } { ...props }
required={ isRequired }
getComponent={ getComponent }
schema={ value }
depth={ depth + 1 } />
</td>
</tr>)
}).toArray()
}
{
!additionalProperties || !additionalProperties.size ? null
: <tr>
<td>{ "< * >:" }</td>
<td>
<Model { ...props } required={ false }
getComponent={ getComponent }
schema={ additionalProperties }
depth={ depth + 1 } />
</td>
</tr>
}
</tbody></table>
}
</span>
<span className="brace-close">{ braceClose }</span>
</Collapse>
</span>
}
}
class Primitive extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
required: PropTypes.bool
}
render(){
let { schema, required } = this.props
if(!schema || !schema.get) {
// don't render if schema isn't correctly formed
return <div></div>
}
let type = schema.get("type")
let format = schema.get("format")
let xml = schema.get("xml")
let enumArray = schema.get("enum")
let description = schema.get("description")
let properties = schema.filter( ( v, key) => ["enum", "type", "format", "$$ref"].indexOf(key) === -1 )
let style = required ? { fontWeight: "bold" } : {}
let propStyle = { color: "#999", fontStyle: "italic" }
return <span className="prop">
<span className="prop-type" style={ style }>{ type }</span> { required && <span style={{ color: "red" }}>*</span>}
{ format && <span className="prop-format">(${format})</span>}
{
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }>
<br />{ key !== "description" && key + ": " }{ String(v) }</span>)
: null
}
{
xml && xml.size ? (<span><br /><span style={ propStyle }>xml:</span>
{
xml.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }><br/>&nbsp;&nbsp;&nbsp;{key}: { String(v) }</span>).toArray()
}
</span>): null
}
{
enumArray && <EnumModel value={ enumArray } />
}
</span>
}
}
class ArrayModel extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
required: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number
}
render(){
let { required, schema, depth, expandDepth } = this.props
let items = schema.get("items")
return <span>
<Collapse collapsed={ depth > expandDepth } collapsedContent="[...]">
[
<span><Model { ...this.props } schema={ items } required={ false }/></span>
]
</Collapse>
{ required && <span style={{ color: "red" }}>*</span>}
</span>
}
}
class Model extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
isRef: PropTypes.bool,
required: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number
}
getModelName =( ref )=> {
if ( ref.indexOf("#/definitions/") !== -1 ) {
return ref.replace(/^.*#\/definitions\//, "")
}
}
getRefSchema =( model )=> {
let { specSelectors } = this.props
return specSelectors.findDefinition(model)
}
render () {
let { schema, required, name, isRef } = this.props
let $$ref = schema && schema.get("$$ref")
let modelName = $$ref && this.getModelName( $$ref )
let modelSchema, type
if ( schema && (schema.get("type") || schema.get("properties")) ) {
modelSchema = schema
} else if ( $$ref ) {
modelSchema = this.getRefSchema( modelName )
}
type = modelSchema && modelSchema.get("type")
if ( !type && modelSchema && modelSchema.get("properties") ) {
type = "object"
}
switch(type) {
case "object":
return <ObjectModel className="object" { ...this.props } schema={ modelSchema }
name={ modelName || name }
isRef={ isRef!== undefined ? isRef : !!$$ref }/>
case "array":
return <ArrayModel className="array" { ...this.props } schema={ modelSchema } required={ required } />
case "string":
case "number":
case "integer":
case "boolean":
default:
return <Primitive schema={ modelSchema } required={ required }/>
}
}
}
export default class ModelComponent extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
name: PropTypes.string,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
expandDepth: PropTypes.number
}
render(){
let { name, schema } = this.props
let title = schema.get("title") || name
return <div className="model-box">
<Model { ...this.props } depth={ 1 } expandDepth={ this.props.expandDepth || 0 }/>
</div>
}
}
class Collapse extends Component {
static propTypes = {
collapsedContent: PropTypes.any,
collapsed: PropTypes.bool,
children: PropTypes.any
}
static defaultProps = {
collapsedContent: "{...}",
collapsed: true,
}
constructor(props, context) {
super(props, context)
let { collapsed, collapsedContent } = this.props
this.state = {
collapsed: collapsed !== undefined ? collapsed : Collapse.defaultProps.collapsed,
collapsedContent: collapsedContent || Collapse.defaultProps.collapsedContent
}
}
toggleCollapsed=()=>{
this.setState({
collapsed: !this.state.collapsed
})
}
render () {
return (<span>
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}>
<span className={ "model-toggle" + ( this.state.collapsed ? " collapsed" : "" ) }></span>
</span>
{ this.state.collapsed ? this.state.collapsedContent : this.props.children }
</span>)
}
}

View File

@@ -0,0 +1,42 @@
import React, { Component, PropTypes } from "react"
export default class Models extends Component {
static propTypes = {
getComponent: PropTypes.func,
specSelectors: PropTypes.object
}
render(){
let { specSelectors, getComponent, layoutSelectors, layoutActions } = this.props
let definitions = specSelectors.definitions()
let showModels = layoutSelectors.isShown('models', true)
const Model = getComponent("model")
const Collapse = getComponent("Collapse")
if (!definitions.size) return null
return <section className={ showModels ? "models is-open" : "models"}>
<h4 onClick={() => layoutActions.show('models', !showModels)}>
<span>Models</span>
<svg width="20" height="20">
<use xlinkHref="#large-arrow" />
</svg>
</h4>
<Collapse isOpened={showModels} animated>
{
definitions.entrySeq().map( ( [ name, model ])=>{
return <div className="model-container" key={ `models-section-${name}` }>
<Model name={ name }
schema={ model }
isRef={ true }
getComponent={ getComponent }
specSelectors={ specSelectors }/>
</div>
}).toArray()
}
</Collapse>
</section>
}
}

View File

@@ -0,0 +1,40 @@
import React from "react"
export default class OnlineValidatorBadge extends React.Component {
constructor(props, context) {
super(props, context)
let { specSelectors, getConfigs } = props
let { validatorUrl } = getConfigs()
this.state = {
url: specSelectors.url(),
validatorUrl: validatorUrl
}
}
componentWillReceiveProps(nextProps) {
let { specSelectors, getConfigs } = nextProps
let { validatorUrl } = getConfigs()
this.setState({
url: specSelectors.url(),
validatorUrl: validatorUrl
})
}
render() {
let { getConfigs } = this.props
let { spec } = getConfigs()
if ( typeof spec === "object" && Object.keys(spec).length) return null
if (!this.state.url) {
return null
}
return (<span style={{ float: "right"}}>
<a target="_blank" href={`${ this.state.validatorUrl }/debug?url=${ this.state.url }`}>
<img alt="Online validator badge" src={`${ this.state.validatorUrl }?url=${ this.state.url }`} />
</a>
</span>)
}
}

View File

@@ -0,0 +1,258 @@
import React, { PropTypes } from "react"
import { Map, fromJS } from "immutable"
import shallowCompare from "react-addons-shallow-compare"
import { getList } from "core/utils"
import * as CustomPropTypes from "core/proptypes"
//import "less/opblock"
export default class Operation extends React.Component {
static propTypes = {
path: PropTypes.string.isRequired,
method: PropTypes.string.isRequired,
operation: PropTypes.object.isRequired,
showSummary: PropTypes.bool,
isShownKey: CustomPropTypes.arrayOrString.isRequired,
jumpToKey: CustomPropTypes.arrayOrString.isRequired,
allowTryItOut: PropTypes.bool,
response: PropTypes.object,
request: PropTypes.object,
getComponent: PropTypes.func.isRequired,
authActions: PropTypes.object,
authSelectors: PropTypes.object,
specActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired
}
static defaultProps = {
showSummary: true,
response: null,
allowTryItOut: true,
}
constructor(props, context) {
super(props, context)
this.state = {
tryItOutEnabled: false
}
}
componentWillReceiveProps(nextProps) {
const defaultContentType = "application/json"
let { specActions, path, method, operation } = nextProps
let producesValue = operation.get("produces_value")
let produces = operation.get("produces")
let consumes = operation.get("consumes")
let consumesValue = operation.get("consumes_value")
if(nextProps.response !== this.props.response) {
this.setState({ executeInProgress: false })
}
if (producesValue === undefined) {
producesValue = produces && produces.size ? produces.first() : defaultContentType
specActions.changeProducesValue([path, method], producesValue)
}
if (consumesValue === undefined) {
consumesValue = consumes && consumes.size ? consumes.first() : defaultContentType
specActions.changeConsumesValue([path, method], consumesValue)
}
}
shouldComponentUpdate(props, state) {
return shallowCompare(this, props, state)
}
toggleShown =() => {
let { layoutActions, isShownKey } = this.props
layoutActions.show(isShownKey, !this.isShown())
}
isShown =() => {
let { layoutSelectors, isShownKey } = this.props
return layoutSelectors.isShown(isShownKey, false ) // Here is where we set the default
}
onTryoutClick =() => {
this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})
}
onCancelClick =() => {
let { specActions, path, method } = this.props
this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})
specActions.clearValidateParams([path, method])
}
onExecute = () => {
this.setState({ executeInProgress: true })
}
render() {
let {
isShownKey,
jumpToKey,
path,
method,
operation,
showSummary,
response,
request,
allowTryItOut,
fn,
getComponent,
specActions,
specSelectors,
authActions,
authSelectors,
layoutSelectors,
layoutActions,
} = this.props
let summary = operation.get("summary")
let description = operation.get("description")
let deprecated = operation.get("deprecated")
let externalDocs = operation.get("externalDocs")
let responses = operation.get("responses")
let security = operation.get("security") || specSelectors.security()
let produces = operation.get("produces")
let schemes = operation.get("schemes")
let parameters = getList(operation, ["parameters"])
const Responses = getComponent("responses")
const Parameters = getComponent( "parameters" )
const Execute = getComponent( "execute" )
const Clear = getComponent( "clear" )
const AuthorizeOperationBtn = getComponent( "authorizeOperationBtn" )
const JumpToPath = getComponent("JumpToPath", true)
const Collapse = getComponent( "Collapse" )
const Markdown = getComponent( "Markdown" )
const Schemes = getComponent( "schemes" )
// Merge in Live Response
if(response && response.size > 0) {
let notDocumented = !responses.get(String(response.get("status")))
response = response.set("notDocumented", notDocumented)
}
let { tryItOutEnabled } = this.state
let shown = this.isShown()
let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method )
return (
<div className={deprecated ? "opblock opblock-deprecated" : shown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey} >
<div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} >
<span className="opblock-summary-method">{method.toUpperCase()}</span>
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } >
<span>{path}</span>
<JumpToPath path={jumpToKey} />
</span>
{ !showSummary ? null :
<div className="opblock-summary-description">
{ summary }
</div>
}
{
(!security || !security.count()) ? null :
<AuthorizeOperationBtn authActions={ authActions }
security={ security }
authSelectors={ authSelectors }/>
}
</div>
<Collapse isOpened={shown} animated>
<div className="opblock-body">
{ deprecated && <h4 className="opblock-title_normal"> Warning: Deprecated</h4>}
{ description &&
<div className="opblock-description-wrapper">
<div className="opblock-description">
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} source={ description } />
</div>
</div>
}
{
externalDocs && externalDocs.get("url") ?
<div className="opblock-external-docs-wrapper">
<h4 className="opblock-title_normal">Find more details</h4>
<div className="opblock-external-docs">
<span className="opblock-external-docs__description">{ externalDocs.get("description") }</span>
<a className="opblock-external-docs__link" href={ externalDocs.get("url") }>{ externalDocs.get("url") }</a>
</div>
</div> : null
}
<Parameters
parameters={parameters}
onChangeKey={onChangeKey}
onTryoutClick = { this.onTryoutClick }
onCancelClick = { this.onCancelClick }
tryItOutEnabled = { tryItOutEnabled }
allowTryItOut={allowTryItOut}
fn={fn}
getComponent={ getComponent }
specActions={ specActions }
specSelectors={ specSelectors }
pathMethod={ [path, method] }
/>
{!tryItOutEnabled || !allowTryItOut ? null : schemes && schemes.size ? <Schemes schemes={ schemes }
path={ path }
method={ method }
specActions={ specActions }/>
: null
}
<div className={(!tryItOutEnabled || !response || !allowTryItOut) ? "execute-wrapper" : "btn-group"}>
{ !tryItOutEnabled || !allowTryItOut ? null :
<Execute
getComponent={getComponent}
operation={ operation }
specActions={ specActions }
specSelectors={ specSelectors }
path={ path }
method={ method }
onExecute={ this.onExecute } />
}
{ (!tryItOutEnabled || !response || !allowTryItOut) ? null :
<Clear
onClick={ this.onClearClick }
specActions={ specActions }
path={ path }
method={ method }/>
}
</div>
{this.state.executeInProgress ? <div className="loading-container"><div className="loading"></div></div> : null}
{ !responses ? null :
<Responses
responses={ responses }
request={ request }
tryItOutResponse={ response }
getComponent={ getComponent }
specSelectors={ specSelectors }
specActions={ specActions }
produces={ produces }
producesValue={ operation.get("produces_value") }
pathMethod={ [path, method] }
fn={fn} />
}
</div>
</Collapse>
</div>
)
}
}

View File

@@ -0,0 +1,133 @@
import React, { PropTypes } from "react"
import {presets} from "react-motion"
export default class Operations extends React.Component {
static propTypes = {
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
authSelectors: PropTypes.object.isRequired,
};
static defaultProps = {
};
render() {
let {
specSelectors,
specActions,
getComponent,
layoutSelectors,
layoutActions,
authActions,
authSelectors,
fn
} = this.props
let taggedOps = specSelectors.taggedOperations()
const Operation = getComponent("operation")
const Collapse = getComponent("Collapse")
const Schemes = getComponent("schemes")
let showSummary = layoutSelectors.showSummary()
return (
<div>
{
taggedOps.map( (tagObj, tag) => {
let operations = tagObj.get("operations")
let tagDescription = tagObj.getIn(["tagDetails", "description"], null)
let isShownKey = ["operations-tag", tag]
let showTag = layoutSelectors.isShown(isShownKey, true)
return (
<div className={showTag ? "opblock-tag-section is-open" : "opblock-tag-section"} key={"operation-" + tag}>
<h4 className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }>
<span onClick={() => layoutActions.show(isShownKey, !showTag)}>{tag}</span>
{ !tagDescription ? null :
<small onClick={() => layoutActions.show(isShownKey, !showTag)} >
{ tagDescription }
</small>
}
<button className="expand-methods" title="Expand all methods">
<svg className="expand" width="20" height="20">
<use xlinkHref="#expand" />
</svg>
</button>
<button className="expand-operation" title="Expand operation" onClick={() => layoutActions.show(isShownKey, !showTag)}>
<svg className="arrow" width="20" height="20">
<use xlinkHref={showTag ? "#large-arrow-down" : "#large-arrow"} />
</svg>
</button>
</h4>
<Collapse isOpened={showTag}>
{
operations.map( op => {
const isShownKey = ["operations", op.get("id"), tag]
const path = op.get("path", "")
const method = op.get("method", "")
const jumpToKey = `paths.${path}.${method}`
const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method"))
const response = specSelectors.responseFor(op.get("path"), op.get("method"))
const request = specSelectors.requestFor(op.get("path"), op.get("method"))
return <Operation
{...op.toObject()}
isShownKey={isShownKey}
jumpToKey={jumpToKey}
showSummary={showSummary}
key={isShownKey}
response={ response }
request={ request }
allowTryItOut={allowTryItOut}
specActions={ specActions }
specSelectors={ specSelectors }
layoutActions={ layoutActions }
layoutSelectors={ layoutSelectors }
authActions={ authActions }
authSelectors={ authSelectors }
getComponent={ getComponent }
fn={fn}
/>
}).toArray()
}
</Collapse>
</div>
)
}).toArray()
}
{ taggedOps.size < 1 ? <h3> No operations defined in spec! </h3> : null }
</div>
)
}
}
Operations.propTypes = {
layoutActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
fn: PropTypes.object.isRequired
}

View File

@@ -0,0 +1,119 @@
import React, { PropTypes } from "react"
import { Link } from "core/components/layout-utils"
export default class Overview extends React.Component {
constructor(...args) {
super(...args)
this.setTagShown = this._setTagShown.bind(this)
}
_setTagShown(showTagId, shown) {
this.props.layoutActions.show(showTagId, shown)
}
showOp(key, shown) {
let { layoutActions } = this.props
layoutActions.show(key, shown)
}
render() {
let { specSelectors, layoutSelectors, layoutActions, getComponent } = this.props
let taggedOps = specSelectors.taggedOperations()
const Collapse = getComponent("Collapse")
return (
<div>
<h4 className="overview-title">Overview</h4>
{
taggedOps.map( (tagObj, tag) => {
let operations = tagObj.get("operations")
let tagDetails = tagObj.get("tagDetails")
let showTagId = ["overview-tags", tag]
let showTag = layoutSelectors.isShown(showTagId, true)
let toggleShow = ()=> layoutActions.show(showTagId, !showTag)
return (
<div key={"overview-"+tag}>
<h4 onClick={toggleShow} className="link overview-tag"> {showTag ? "-" : "+"}{tag}</h4>
<Collapse isOpened={showTag} animated>
{
operations.map( op => {
let { path, method, operation, id } = op.toObject() // toObject is shallow
let showOpIdPrefix = "operations"
let showOpId = id
let shown = layoutSelectors.isShown([showOpIdPrefix, showOpId])
return <OperationLink key={id}
path={path}
method={method}
id={path + "-" + method}
shown={shown}
showOpId={showOpId}
showOpIdPrefix={showOpIdPrefix}
href={`#operation-${showOpId}`}
onClick={layoutActions.show} />
}).toArray()
}
</Collapse>
</div>
)
}).toArray()
}
{ taggedOps.size < 1 && <h3> No operations defined in spec! </h3> }
</div>
)
}
}
Overview.propTypes = {
layoutSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired
}
export class OperationLink extends React.Component {
constructor(props) {
super(props)
this.onClick = this._onClick.bind(this)
}
_onClick() {
let { showOpId, showOpIdPrefix, onClick, shown } = this.props
onClick([showOpIdPrefix, showOpId], !shown)
}
render() {
let { id, method, shown, href } = this.props
return (
<Link href={ href } style={{fontWeight: shown ? "bold" : "normal"}} onClick={this.onClick} className="block opblock-link">
<div>
<small className={`bold-label-${method}`}>{method.toUpperCase()}</small>
<span className="bold-label" >{id}</span>
</div>
</Link>
)
}
}
OperationLink.propTypes = {
href: PropTypes.string,
onClick: PropTypes.func,
id: PropTypes.string.isRequired,
method: PropTypes.string.isRequired,
shown: PropTypes.bool.isRequired,
showOpId: PropTypes.string.isRequired,
showOpIdPrefix: PropTypes.string.isRequired
}

View File

@@ -0,0 +1,141 @@
import React, { Component, PropTypes } from "react"
import shallowCompare from "react-addons-shallow-compare"
import { Set, fromJS, List } from "immutable"
import { getSampleSchema } from "core/utils"
const NOOP = Function.prototype
export default class ParamBody extends Component {
static propTypes = {
param: PropTypes.object,
onChange: PropTypes.func,
onChangeConsumes: PropTypes.func,
consumes: PropTypes.object,
consumesValue: PropTypes.string,
fn: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
isExecute: PropTypes.bool,
specSelectors: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired
};
static defaultProp = {
consumes: fromJS(["application/json"]),
param: fromJS({}),
onChange: NOOP,
onChangeConsumes: NOOP,
};
constructor(props, context) {
super(props, context)
this.state = {
isEditBox: false,
value: ""
}
}
componentDidMount() {
this.updateValues.call(this, this.props)
}
shouldComponentUpdate(props, state) {
return shallowCompare(this, props, state)
}
componentWillReceiveProps(nextProps) {
this.updateValues.call(this, nextProps)
}
updateValues = (props) => {
let { specSelectors, pathMethod, param, isExecute, consumesValue="", onChangeConsumes } = props
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : {}
let isXml = /xml/i.test(consumesValue)
let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value")
if ( paramValue ) {
this.setState({ value: paramValue })
this.onChange(paramValue, {isXml: isXml, isEditBox: isExecute})
} else {
if (isXml) {
this.onChange(this.sample("xml"), {isXml: isXml, isEditBox: isExecute})
} else {
this.onChange(this.sample(), {isEditBox: isExecute})
}
}
}
sample = (xml) => {
let { param, fn:{inferSchema} } = this.props
let schema = inferSchema(param.toJS())
return getSampleSchema(schema, xml)
}
onChange = (value, { isEditBox, isXml }) => {
this.setState({value, isEditBox})
this._onChange(value, isXml)
}
_onChange = (val, isXml) => { (this.props.onChange || NOOP)(this.props.param, val, isXml) }
handleOnChange = e => {
let {consumesValue} = this.props
this.onChange(e.target.value.trim(), {isXml: /xml/i.test(consumesValue)})
}
toggleIsEditBox = () => this.setState( state => ({isEditBox: !state.isEditBox}))
render() {
let {
onChangeConsumes,
param,
isExecute,
specSelectors,
pathMethod,
getComponent,
} = this.props
const Button = getComponent("Button")
const TextArea = getComponent("TextArea")
const HighlightCode = getComponent("highlightCode")
const ContentType = getComponent("contentType")
// for domains where specSelectors not passed
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : param
let errors = parameter.get("errors", List())
let consumesValue = specSelectors.contentTypeValues(pathMethod).get("requestContentType")
let consumes = this.props.consumes && this.props.consumes.size ? this.props.consumes : ParamBody.defaultProp.consumes
let { value, isEditBox } = this.state
return (
<div className="body-param">
{
isEditBox && isExecute
? <TextArea className={ "body-param__text" + ( errors.count() ? " invalid" : "")} value={value} onChange={ this.handleOnChange }/>
: (value && <HighlightCode className="body-param__example"
value={ value }/>)
}
<div className="body-param-options">
{
!isExecute ? null
: <div className="body-param-edit">
<Button className={isEditBox ? "btn cancel body-param__example-edit" : "btn edit body-param__example-edit"}
onClick={this.toggleIsEditBox}>{ isEditBox ? "Cancel" : "Edit"}
</Button>
</div>
}
<label htmlFor="">
<span>Parameter content type</span>
<ContentType value={ consumesValue } contentTypes={ consumes } onChange={onChangeConsumes} className="body-param-content-type" />
</label>
</div>
</div>
)
}
}

View File

@@ -0,0 +1,119 @@
import React, { Component, PropTypes } from "react"
import win from "core/window"
export default class ParameterRow extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
param: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
fn: PropTypes.object.isRequired,
isExecute: PropTypes.bool,
onChangeConsumes: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired
}
constructor(props, context) {
super(props, context)
let { specSelectors, pathMethod, param } = props
let defaultValue = param.get("default")
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
let value = parameter ? parameter.get("value") : ""
if ( defaultValue !== undefined && value === undefined ) {
this.onChangeWrapper(defaultValue)
}
}
componentWillReceiveProps(props) {
let { specSelectors, pathMethod, param } = props
let defaultValue = param.get("default")
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
let value = parameter ? parameter.get("value") : ""
if ( defaultValue !== undefined && value === undefined ) {
this.onChangeWrapper(defaultValue)
}
}
onChangeWrapper = (value) => {
let { onChange, param } = this.props
return onChange(param, value)
}
render() {
let {param, onChange, getComponent, isExecute, fn, onChangeConsumes, specSelectors, pathMethod} = this.props
// const onChangeWrapper = (value) => onChange(param, value)
const JsonSchemaForm = getComponent("JsonSchemaForm")
const ParamBody = getComponent("ParamBody")
let inType = param.get("in")
let bodyParam = inType !== "body" ? null
: <ParamBody getComponent={getComponent}
fn={fn}
param={param}
consumes={ specSelectors.operationConsumes(pathMethod) }
consumesValue={ specSelectors.contentTypeValues(pathMethod).get("requestContentType") }
onChange={onChange}
onChangeConsumes={onChangeConsumes}
isExecute={ isExecute }
specSelectors={ specSelectors }
pathMethod={ pathMethod }
/>
const ModelExample = getComponent("modelExample")
const Markdown = getComponent("Markdown")
let schema = param.get("schema")
let isFormData = inType === "formData"
let isFormDataSupported = "FormData" in win
let required = param.get("required")
let itemType = param.getIn(["items", "type"])
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
let value = parameter ? parameter.get("value") : ""
return (
<tr>
<td className="col parameters-col_name">
<div className={required ? "parameter__name required" : "parameter__name"}>
{ param.get("name") }
{ !required ? null : <span style={{color: "red"}}>&nbsp;*</span> }
</div>
<div className="parаmeter__type">{ param.get("type") } { itemType && `[${itemType}]` }</div>
<div className="parameter__in">({ param.get("in") })</div>
</td>
<td className="col parameters-col_description">
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
source={ param.get("description") }/>
{(isFormData && !isFormDataSupported) && <div>Error: your browser does not support FormData</div>}
{ bodyParam || !isExecute ? null
: <JsonSchemaForm fn={fn}
getComponent={getComponent}
value={ value }
required={ required }
description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`}
onChange={ this.onChangeWrapper }
schema={ param }/>
}
{
bodyParam && schema ? <ModelExample getComponent={ getComponent }
isExecute={ isExecute }
specSelectors={ specSelectors }
schema={ schema }
example={ bodyParam }/>
: null
}
</td>
</tr>
)
}
}

View File

@@ -0,0 +1,109 @@
import React, { Component, PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
import Im, { fromJS } from "immutable"
// More readable, just iterate over maps, only
const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn)
export default class Parameters extends Component {
static propTypes = {
parameters: ImPropTypes.list.isRequired,
specActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired,
tryItOutEnabled: PropTypes.bool,
allowTryItOut: PropTypes.bool,
onTryoutClick: PropTypes.func,
onCancelClick: PropTypes.func,
onChangeKey: PropTypes.array,
pathMethod: PropTypes.array.isRequired
}
static defaultProps = {
onTryoutClick: Function.prototype,
onCancelClick: Function.prototype,
tryItOutEnabled: false,
allowTryItOut: true,
onChangeKey: [],
}
onChange = ( param, value, isXml ) => {
let {
specActions: { changeParam },
onChangeKey,
} = this.props
changeParam( onChangeKey, param.get("name"), value, isXml)
}
onChangeConsumesWrapper = ( val ) => {
let {
specActions: { changeConsumesValue },
onChangeKey
} = this.props
changeConsumesValue(onChangeKey, val)
}
render(){
let {
onTryoutClick,
onCancelClick,
parameters,
allowTryItOut,
tryItOutEnabled,
fn,
getComponent,
specSelectors,
pathMethod
} = this.props
const ParameterRow = getComponent("parameterRow")
const TryItOutButton = getComponent("TryItOutButton")
const isExecute = tryItOutEnabled && allowTryItOut
return (
<div className="opblock-section">
<div className="opblock-section-header">
<h4 className="opblock-title">Parameters</h4>
{ allowTryItOut ? (
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } />
) : null }
</div>
{ !parameters.count() ? <div className="opblock-description-wrapper"><p>No parameters</p></div> :
<div className="table-container">
<table className="parameters">
<thead>
<tr>
<th className="col col_header parameters-col_name">Name</th>
<th className="col col_header parameters-col_description">Description</th>
</tr>
</thead>
<tbody>
{
eachMap(parameters, (parameter, k) => (
<ParameterRow fn={ fn }
getComponent={ getComponent }
param={ parameter }
key={ parameter.get( "name" ) }
onChange={ this.onChange }
onChangeConsumes={this.onChangeConsumesWrapper}
specSelectors={ specSelectors }
pathMethod={ pathMethod }
isExecute={ isExecute }/>
)).toArray()
}
</tbody>
</table>
</div>
}
</div>
)
}
}

View File

@@ -0,0 +1,95 @@
import React, { PropTypes } from "react"
import { formatXml } from "core/utils"
import lowerCase from "lodash/lowerCase"
export default class ResponseBody extends React.Component {
static propTypes = {
content: PropTypes.any.isRequired,
contentType: PropTypes.string.isRequired,
getComponent: PropTypes.func.isRequired,
headers: PropTypes.object,
url: PropTypes.string
}
render() {
let { content, contentType, url, headers={}, getComponent } = this.props
const HighlightCode = getComponent("highlightCode")
let body, bodyEl
url = url || ""
// JSON
if (/json/i.test(contentType)) {
try {
body = JSON.stringify(JSON.parse(content), null, " ")
} catch (error) {
body = "can't parse JSON. Raw result:\n\n" + content
}
bodyEl = <HighlightCode value={ body } />
// XML
} else if (/xml/i.test(contentType)) {
body = formatXml(content)
bodyEl = <HighlightCode value={ body } />
// HTML or Plain Text
} else if (lowerCase(contentType) === "text/html" || /text\/plain/.test(contentType)) {
bodyEl = <HighlightCode value={ content } />
// Image
} else if (/^image\//i.test(contentType)) {
bodyEl = <img src={ url } />
// Audio
} else if (/^audio\//i.test(contentType)) {
bodyEl = <pre><audio controls><source src={ url } type={ contentType } /></audio></pre>
// Download
} else if (
/^application\/octet-stream/i.test(contentType) ||
headers["Content-Disposition"] && (/attachment/i).test(headers["Content-Disposition"]) ||
headers["content-disposition"] && (/attachment/i).test(headers["content-disposition"]) ||
headers["Content-Description"] && (/File Transfer/i).test(headers["Content-Description"]) ||
headers["content-description"] && (/File Transfer/i).test(headers["content-description"])) {
let contentLength = headers["content-length"] || headers["Content-Length"]
if ( !(+contentLength) ) return null
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
if (!isSafari && "Blob" in window) {
let type = contentType || "text/html"
let blob = (content instanceof Blob) ? content : new Blob([content], {type: type})
let href = window.URL.createObjectURL(blob)
let fileName = url.substr(url.lastIndexOf("/") + 1)
let download = [type, fileName, href].join(":")
// Use filename from response header
let disposition = headers["content-disposition"] || headers["Content-Disposition"]
if (typeof disposition !== "undefined") {
let responseFilename = /filename=([^;]*);?/i.exec(disposition)
if (responseFilename !== null && responseFilename.length > 1) {
download = responseFilename[1]
}
}
bodyEl = <div><a href={ href } download={ download }>{ "Download file" }</a></div>
} else {
bodyEl = <pre>Download headers detected but your browser does not support downloading binary via XHR (Blob).</pre>
}
// Anything else (CORS)
} else if (typeof content === "string") {
bodyEl = <HighlightCode value={ content } />
} else {
bodyEl = <div>Unknown response type</div>
}
return ( !bodyEl ? null : <div>
<h5>Response body</h5>
{ bodyEl }
</div>
)
}
}

View File

@@ -0,0 +1,91 @@
import React, { PropTypes } from "react"
import { fromJS } from 'immutable'
import { getSampleSchema } from "core/utils"
const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
if ( examples && examples.size ) {
return examples.entrySeq().map( ([ key, example ]) => {
return (<div key={ key }>
<h5>{ key }</h5>
<HighlightCode className="example" value={ example } />
</div>)
}).toArray()
}
if ( sampleResponse ) { return <div>
<HighlightCode className="example" value={ sampleResponse } />
</div>
}
return null
}
export default class Response extends React.Component {
static propTypes = {
code: PropTypes.string.isRequired,
response: PropTypes.object,
className: PropTypes.string,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired,
contentType: PropTypes.string
}
static defaultProps = {
response: fromJS({}),
};
render() {
let {
code,
response,
className,
fn,
getComponent,
specSelectors,
contentType
} = this.props
let { inferSchema } = fn
let schema = inferSchema(response.toJS())
let headers = response.get("headers")
let examples = response.get("examples")
const Headers = getComponent("headers")
const HighlightCode = getComponent("highlightCode")
const ModelExample = getComponent("modelExample")
const Markdown = getComponent( "Markdown" )
let sampleResponse = schema ? getSampleSchema(schema, contentType, { includeReadOnly: true }) : null
let example = getExampleComponent( sampleResponse, examples, HighlightCode )
return (
<tr className={ "response " + ( className || "") }>
<td className="col response-col_status">
{ code }
</td>
<td className="col response-col_description">
<div className="response-col_description__inner">
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} source={ response.get( "description" ) } />
</div>
{ example ? (
<ModelExample
getComponent={ getComponent }
specSelectors={ specSelectors }
schema={ fromJS(schema) }
example={ example }/>
) : null}
{ headers ? (
<Headers headers={ headers }/>
) : null}
</td>
</tr>
)
}
}

View File

@@ -0,0 +1,93 @@
import React, { PropTypes } from "react"
import { fromJS } from "immutable"
import { defaultStatusCode } from "core/utils"
export default class Responses extends React.Component {
static propTypes = {
request: PropTypes.object,
tryItOutResponse: PropTypes.object,
responses: PropTypes.object.isRequired,
produces: PropTypes.object,
producesValue: PropTypes.any,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired,
fn: PropTypes.object.isRequired
}
static defaultProps = {
request: null,
tryItOutResponse: null,
produces: fromJS(["application/json"])
}
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue(this.props.pathMethod, val)
render() {
let { responses, request, tryItOutResponse, getComponent, specSelectors, fn, producesValue } = this.props
let defaultCode = defaultStatusCode( responses )
const ContentType = getComponent( "contentType" )
const LiveResponse = getComponent( "liveResponse" )
const Response = getComponent( "response" )
let produces = this.props.produces && this.props.produces.size ? this.props.produces : Responses.defaultProps.produces
return (
<div className="responses-wrapper">
<div className="opblock-section-header">
<h4>Responses</h4>
<label>
<span>Response content type</span>
<ContentType value={producesValue}
onChange={this.onChangeProducesWrapper}
contentTypes={produces}
className="execute-content-type"/>
</label>
</div>
<div className="responses-inner">
{
!tryItOutResponse ? null
: <div>
<LiveResponse request={ request }
response={ tryItOutResponse }
getComponent={ getComponent } />
<h4>Responses</h4>
</div>
}
<table className="responses-table">
<thead>
<tr className="responses-header">
<td className="col col_header response-col_status">Code</td>
<td className="col col_header response-col_description">Description</td>
</tr>
</thead>
<tbody>
{
responses.entrySeq().map( ([code, response]) => {
let className = tryItOutResponse && tryItOutResponse.get("status") == code ? "response_current" : ""
return (
<Response key={ code }
isDefault={defaultCode === code}
fn={fn}
className={ className }
code={ code }
response={ response }
specSelectors={ specSelectors }
contentType={ producesValue }
getComponent={ getComponent }/>
)
}).toArray()
}
</tbody>
</table>
</div>
</div>
)
}
}

View File

@@ -0,0 +1,45 @@
import React, { PropTypes } from "react"
export default class Schemes extends React.Component {
static propTypes = {
specActions: PropTypes.object.isRequired,
schemes: PropTypes.object.isRequired,
path: PropTypes.string,
method: PropTypes.string
}
componentWillMount() {
let { schemes } = this.props
//fire 'change' event to set default 'value' of select
this.setScheme(schemes.first())
}
onChange =( e ) => {
let { path, method, specActions } = this.props
this.setScheme( e.target.value )
}
setScheme =( value ) => {
let { path, method, specActions } = this.props
specActions.setScheme( value, path, method )
}
render() {
let { schemes } = this.props
return (
<label htmlFor="schemes">
<span>Schemes</span>
<select onChange={ this.onChange }>
{ schemes.valueSeq().map(
( scheme ) => <option value={ scheme } key={ scheme }>{ scheme }</option>
).toArray()}
</select>
</label>
)
}
}

View File

View File

@@ -0,0 +1,27 @@
import React, { PropTypes } from "react"
export default class TryItOutButton extends React.Component {
static propTypes = {
onTryoutClick: PropTypes.func,
enabled: PropTypes.bool, // Try it out is enabled, ie: the user has access to the form
};
static defaultProps = {
onTryoutClick: Function.prototype,
enabled: false,
};
render() {
const { onTryoutClick, onCancelClick, enabled } = this.props
return (
<div className="try-out">
{
enabled ? <button className="btn try-out__btn cancel" onClick={ onTryoutClick }>Cancel</button>
: <button className="btn try-out__btn" onClick={ onCancelClick }>Try it out </button>
}
</div>
)
}
}

34
src/core/curlify.js Normal file
View File

@@ -0,0 +1,34 @@
export default function curl( request ){
let curlified = []
let type = ""
let headers = request.get("headers")
curlified.push( "curl" )
curlified.push( "-X", request.get("method") )
curlified.push( request.get("url") )
if ( headers && headers.size ) {
for( let p of request.get("headers").entries() ){
let [ h,v ] = p
type = v
curlified.push( "-H " )
curlified.push( `"${h}: ${v}"` )
}
}
if ( request.get("body") ){
if(type === "multipart/form-data" && request.get("method") === "POST") {
let formDataBody = request.get("body").split("&")
for(var data in formDataBody) {
curlified.push( "-F" )
curlified.push(formDataBody[data])
}
} else {
curlified.push( "-d" )
curlified.push( JSON.stringify( request.get("body") ).replace(/\\n/g, "") )
}
}
return curlified.join( " " )
}

119
src/core/index.js Normal file
View File

@@ -0,0 +1,119 @@
import deepExtend from "deep-extend"
import System from "core/system"
import ApisPreset from "core/presets/apis"
import * as AllPlugins from "core/plugins/all"
import { filterConfigs } from "plugins/configs"
module.exports = function SwaggerUI(opts) {
const defaults = {
// Some general settings, that we floated to the top
dom_id: null,
spec: {},
url: "",
layout: "Layout",
configs: {
validatorUrl: "https://online.swagger.io/validator"
},
// Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance.
// Instead, we can compile the first plugin ( it can be a collection of plugins ), then batch the rest.
presets: [
],
// Plugins; ( loaded after presets )
plugins: [
],
// Inline Plugin
fn: { },
components: { },
state: { },
// Override some core configs... at your own risk
store: { },
}
const config = deepExtend({}, defaults, opts)
const storeConfigs = deepExtend({}, config.store, {
system: {
configs: config.configs
},
plugins: config.presets,
state: {
layout: {
layout: config.layout
},
spec: {
spec: "",
url: config.url
}
}
})
let inlinePlugin = ()=> {
return {
fn: config.fn,
components: config.components,
state: config.state,
}
}
var store = new System(storeConfigs)
store.register([config.plugins, inlinePlugin])
var system = store.getSystem()
const downloadSpec = (configs) => {
if(typeof config !== "object") {
return system
}
let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {}
let mergedConfig = deepExtend({}, config, configs, localConfig)
store.setConfigs(filterConfigs(mergedConfig))
if(typeof mergedConfig.spec === "object" && Object.keys(mergedConfig.spec).length) {
system.specActions.updateUrl("")
system.specActions.updateLoadingStatus("success");
system.specActions.updateSpec(JSON.stringify(mergedConfig.spec))
} else if(mergedConfig.url) {
system.specActions.updateUrl(mergedConfig.url)
system.specActions.download(mergedConfig.url)
}
if(mergedConfig.dom_id)
system.render(mergedConfig.dom_id, "App")
return system
}
if (system.specActions.getConfigByUrl && !system.specActions.getConfigByUrl(downloadSpec)) {
return downloadSpec(config)
}
if (system.specActions.download && config.url) {
system.specActions.download(config.url)
}
if(config.spec && typeof config.spec === "string")
system.specActions.updateSpec(config.spec)
if(config.dom_id) {
system.render(config.dom_id, "App")
} else {
console.error("Skipped rendering: no `dom_id` was specified")
}
return system
}
// Add presets
module.exports.presets = {
apis: ApisPreset,
}
// All Plugins
module.exports.plugins = AllPlugins

View File

@@ -0,0 +1,187 @@
import React, { PropTypes, Component } from "react"
import { arrayify } from "core/utils"
import shallowCompare from "react-addons-shallow-compare"
import { List, fromJS } from "immutable"
import assign from "object-assign"
//import "less/json-schema-form"
const noop = ()=> {}
const JsonSchemaPropShape = {
getComponent: PropTypes.func.isRequired,
value: PropTypes.any,
onChange: PropTypes.func,
keyName: PropTypes.any,
fn: PropTypes.object.isRequired,
schema: PropTypes.object,
required: PropTypes.bool,
description: PropTypes.any
}
const JsonSchemaDefaultProps = {
value: "",
onChange: noop,
schema: {},
keyName: "",
required: false
}
export class JsonSchemaForm extends Component {
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps
render() {
let { schema, value, onChange, getComponent, fn } = this.props
if(schema.toJS)
schema = schema.toJS()
let { type, format="" } = schema
let Comp = getComponent(`JsonSchema_${type}_${format}`) || getComponent(`JsonSchema_${type}`) || getComponent("JsonSchema_string")
return <Comp { ...this.props } fn={fn} getComponent={getComponent} value={value} onChange={onChange} schema={schema}/>
}
}
export class JsonSchema_string extends Component {
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps
onChange = (e) => {
const value = this.props.schema["type"] === "file" ? e.target.files[0] : e.target.value
this.props.onChange(value, this.props.keyName)
}
onEnumChange = (val) => this.props.onChange(val)
render() {
let { getComponent, value, schema, fn, required, description } = this.props
let enumValue = schema["enum"]
let errors = schema.errors || []
if ( enumValue ) {
const Select = getComponent("Select")
return (<Select allowedValues={ enumValue }
value={ value }
allowEmptyValue={ !required }
onChange={ this.onEnumChange }/>)
}
const isDisabled = schema["in"] === "formData" && !("FormData" in window)
const Input = getComponent("Input")
if (schema["type"] === "file") {
return <Input type="file" className={ errors.length ? "invalid" : ""} onChange={ this.onChange } disabled={isDisabled}/>
}
else {
return <Input type="text" className={ errors.length ? "invalid" : ""} value={value} placeholder={description} onChange={ this.onChange } disabled={isDisabled}/>
}
}
}
export class JsonSchema_array extends Component {
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps
constructor(props, context) {
super(props, context)
this.state = {value: props.value}
}
componentWillReceiveProps(props) {
if(props.value !== this.state.value)
this.setState({value: props.value})
}
shouldComponentUpdate(props, state) {
return shallowCompare(this, props, state)
}
onChange = () => this.props.onChange(this.state.value)
onItemChange = (itemVal, i) => {
this.setState(state => ({
value: state.value.set(i, itemVal)
}), this.onChange)
}
removeItem = (i) => {
this.setState(state => ({
value: state.value.remove(i)
}), this.onChange)
}
addItem = () => {
this.setState(state => {
state.value = state.value || List()
return {
value: state.value.push("")
}
}, this.onChange)
}
onEnumChange = (value) => {
this.setState(state => ({
value: value
}), this.onChange)
}
render() {
let { getComponent, onChange, required, schema, fn } = this.props
let itemSchema = fn.inferSchema(schema.items)
const JsonSchemaForm = getComponent("JsonSchemaForm")
const Button = getComponent("Button")
let enumValue = itemSchema["enum"]
let value = this.state.value
if ( enumValue ) {
const Select = getComponent("Select")
return (<Select multiple={ true }
value={ value }
allowedValues={ enumValue }
allowEmptyValue={ !required }
onChange={ this.onEnumChange }/>)
}
let errors = schema.errors || []
return (
<div>
{ !value || value.count() < 1 ?
(errors.length ? <span style={{ color: "red", fortWeight: "bold" }}>{ errors[0] }</span> : null) :
value.map( (item,i) => {
let schema = Object.assign({}, itemSchema)
let err = errors.filter((err) => err.index === i)
if ( err.length ) {
schema.errors = [ err[0].error + i ]
}
return (
<div key={i} className="json-schema-form-item">
<JsonSchemaForm fn={fn} getComponent={getComponent} value={item} onChange={(val) => this.onItemChange(val, i)} schema={schema} />
<Button className="json-schema-form-item-remove" onClick={()=> this.removeItem(i)} > - </Button>
</div>
)
}).toArray()
}
<Button className="json-schema-form-item-add" onClick={this.addItem}> Add item </Button>
</div>
)
}
}
export class JsonSchema_boolean extends Component {
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps
onEnumChange = (val) => this.props.onChange(val)
render() {
let { getComponent, required, value } = this.props
const Select = getComponent("Select")
return (<Select value={ String(value) }
allowedValues={ fromJS(["true", "false"]) }
allowEmptyValue={ !required }
onChange={ this.onEnumChange }/>)
}
}

View File

@@ -0,0 +1,47 @@
import win from "core/window"
export default function authorize ( auth, authActions, errActions, configs ) {
let { schema, scopes, name, clientId } = auth
let redirectUrl = configs.oauth2RedirectUrl
let scopeSeparator = " "
let state = name
let flow = schema.get("flow")
let url
if (flow === "password") {
authActions.authorizePassword(auth)
return
}
// todo move to parser
if ( !redirectUrl ) {
errActions.newAuthErr( {
authId: name,
source: "validation",
level: "error",
message: "oauth2RedirectUri configuration is not passed. Oauth2 authorization cannot be performed."
})
return
}
if (flow === "implicit" || flow === "accessCode") {
url = schema.get("authorizationUrl") + "?response_type=" + (flow === "implicit" ? "token" : "code")
}
url += "&redirect_uri=" + encodeURIComponent(redirectUrl)
+ "&scope=" + encodeURIComponent(scopes.join(scopeSeparator))
+ "&state=" + encodeURIComponent(state)
+ "&client_id=" + encodeURIComponent(clientId)
// pass action authorizeOauth2 and authentication data through window
// to authorize with oauth2
win.swaggerUIRedirectOauth2 = {
auth: auth,
state: state,
callback: authActions.preAuthorizeOauth2,
errCb: errActions.newAuthErr
}
win.open(url)
}

View File

@@ -0,0 +1,67 @@
import get from "lodash/get"
export function transformPathToArray(property, jsSpec) {
if(property.slice(0,9) === "instance.") {
var str = property.slice(9)
} else { // eslint-disable-next-line no-redeclare
var str = property
}
var pathArr = []
str
.split(".")
.map(item => {
// "key[0]" becomes ["key", "0"]
if(item.includes("[")) {
let index = parseInt(item.match(/\[(.*)\]/)[1])
let keyName = item.slice(0, item.indexOf("["))
return [keyName, index.toString()]
} else {
return item
}
})
.reduce(function(a, b) {
// flatten!
return a.concat(b)
}, [])
.concat([""]) // add an empty item into the array, so we don't get stuck with something in our buffer below
.reduce((buffer, curr, i, arr) => {
let obj = pathArr.length ? get(jsSpec, pathArr) : jsSpec
if(get(obj, makeAccessArray(buffer, curr))) {
if(buffer.length) {
pathArr.push(buffer)
}
if(curr.length) {
pathArr.push(curr)
}
return ""
} else {
// attach key to buffer
return `${buffer}${buffer.length ? "." : ""}${curr}`
}
}, "")
if(typeof get(jsSpec, pathArr) !== "undefined") {
return pathArr
} else {
// if our path is not correct (there is no value at the path),
// return null
return null
}
}
function makeAccessArray(buffer, curr) {
let arr = []
if(buffer.length) {
arr.push(buffer)
}
if(curr.length) {
arr.push(curr)
}
return arr
}

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

11
src/core/presets/apis.js Normal file
View File

@@ -0,0 +1,11 @@
import BasePreset from "./base"
import allowTryItOutIfHost from "core/plugins/allow-try-it-out-if-host"
export default function PresetApis() {
return [
BasePreset,
allowTryItOutIfHost,
]
}

118
src/core/presets/base.js Normal file
View File

@@ -0,0 +1,118 @@
import err from "core/plugins/err"
import layout from "core/plugins/layout"
import spec from "core/plugins/spec"
import view from "core/plugins/view"
import samples from "core/plugins/samples"
import logs from "core/plugins/logs"
import ast from "core/plugins/ast"
import swaggerJs from "core/plugins/swagger-js"
import auth from "core/plugins/auth"
import util from "core/plugins/util"
import SplitPaneModePlugin from "core/plugins/split-pane-mode"
import downloadUrlPlugin from "core/plugins/download-url"
import App from "core/components/app"
import AuthorizationPopup from "core/components/auth/authorization-popup"
import AuthorizeBtn from "core/components/auth/authorize-btn"
import AuthorizeOperationBtn from "core/components/auth/authorize-operation-btn"
import Auths from "core/components/auth/auths"
import AuthError from "core/components/auth/error"
import ApiKeyAuth from "core/components/auth/api-key-auth"
import BasicAuth from "core/components/auth/basic-auth"
import Oauth2 from "core/components/auth/oauth2"
import Clear from "core/components/clear"
import LiveResponse from "core/components/live-response"
import OnlineValidatorBadge from "core/components/online-validator-badge"
import Operations from "core/components/operations"
import Operation from "core/components/operation"
import HighlightCode from "core/components/highlight-code"
import Responses from "core/components/responses"
import Response from "core/components/response"
import ResponseBody from "core/components/response-body"
import Parameters from "core/components/parameters"
import ParameterRow from "core/components/parameter-row"
import Execute from "core/components/execute"
import Headers from "core/components/headers"
import Errors from "core/components/errors"
import ContentType from "core/components/content-type"
import Overview from "core/components/overview"
import Info from "core/components/info"
import Footer from "core/components/footer"
import ParamBody from "core/components/param-body"
import Curl from "core/components/curl"
import Schemes from "core/components/schemes"
import ModelExample from "core/components/model-example"
import Model from "core/components/model"
import Models from "core/components/models"
import TryItOutButton from "core/components/try-it-out-button"
import * as LayoutUtils from "core/components/layout-utils"
import * as JsonSchemaComponents from "core/json-schema-components"
export default function() {
let coreComponents = {
components: {
App,
authorizationPopup: AuthorizationPopup,
authorizeBtn: AuthorizeBtn,
authorizeOperationBtn: AuthorizeOperationBtn,
auths: Auths,
authError: AuthError,
oauth2: Oauth2,
apiKeyAuth: ApiKeyAuth,
basicAuth: BasicAuth,
clear: Clear,
liveResponse: LiveResponse,
info: Info,
onlineValidatorBadge: OnlineValidatorBadge,
operations: Operations,
operation: Operation,
highlightCode: HighlightCode,
responses: Responses,
response: Response,
responseBody: ResponseBody,
parameters: Parameters,
parameterRow: ParameterRow,
execute: Execute,
headers: Headers,
errors: Errors,
contentType: ContentType,
overview: Overview,
footer: Footer,
ParamBody: ParamBody,
curl: Curl,
schemes: Schemes,
modelExample: ModelExample,
model: Model,
models: Models,
TryItOutButton,
}
}
let formComponents = {
components: LayoutUtils
}
let jsonSchemaComponents = {
components: JsonSchemaComponents
}
return [
util,
logs,
view,
spec,
err,
layout,
samples,
coreComponents,
formComponents,
swaggerJs,
jsonSchemaComponents,
auth,
ast,
SplitPaneModePlugin,
downloadUrlPlugin
]
}

16
src/core/proptypes.js Normal file
View File

@@ -0,0 +1,16 @@
import { PropTypes } from "react"
// Takes a list and proptype, and returns a PropType.shape({ [item]: propType })
const mapListToPropTypeShape = (list, propType) => PropTypes.shape(
list.reduce((shape, propName) => {
shape[propName] = propType
return shape
}, {}))
export const arrayOrString = PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.string),
PropTypes.string,
])
export const objectWithFuncs = list => mapListToPropTypeShape(list, PropTypes.func.isRequired)

372
src/core/system.js Normal file
View File

@@ -0,0 +1,372 @@
import { createStore, applyMiddleware, bindActionCreators, compose } from "redux"
import Im, { fromJS, Map } from "immutable"
import deepExtend from "deep-extend"
import createLogger from "redux-logger"
import { combineReducers } from "redux-immutable"
import assign from "object-assign"
import serializeError from "serialize-error"
import { NEW_THROWN_ERR } from "corePlugins/err/actions"
import win from "core/window"
import { systemThunkMiddleware, isFn, objMap, objReduce, isObject, isArray, isFunc } from "core/utils"
const idFn = a => a
// Apply middleware that gets sandwitched between `dispatch` and the reducer function(s)
function createStoreWithMiddleware(rootReducer, initialState, getSystem) {
let middlwares = [
// createLogger( {
// stateTransformer: state => state && state.toJS()
// } ),
// errorLog(getSystem), Need to properly handle errors that occur during a render. Ie: let them be...
systemThunkMiddleware( getSystem )
]
const composeEnhancers = win.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
return createStore(rootReducer, initialState, composeEnhancers(
applyMiddleware( ...middlwares )
))
}
export default class Store {
constructor(opts={}) {
deepExtend(this, {
state: {},
plugins: [],
system: {
configs: {},
fn: {},
components: {},
rootInjects: {},
statePlugins: {}
},
boundSystem: {},
toolbox: {}
}, opts)
this.getSystem = this._getSystem.bind(this)
// Bare system (nothing in it, besides the state)
this.store = configureStore(idFn, fromJS(this.state), this.getSystem )
// will be the system + Im, we can add more tools when we need to
this.buildSystem(false)
// Bootstrap plugins
this.register(this.plugins)
}
getStore() {
return this.store
}
register(plugins, rebuild=true) {
var pluginSystem = combinePlugins(plugins, this.getSystem())
systemExtend(this.system, pluginSystem)
if(rebuild) {
this.buildSystem()
}
}
buildSystem(buildReducer=true) {
let dispatch = this.getStore().dispatch
let getState = this.getStore().getState
this.boundSystem = assign({},
this.getRootInjects(),
this.getWrappedAndBoundActions(dispatch),
this.getBoundSelectors(getState, this.getSystem),
this.getStateThunks(getState),
this.getFn(),
this.getConfigs()
)
if(buildReducer)
this.rebuildReducer()
}
_getSystem() {
return this.boundSystem
}
getRootInjects() {
return assign({
getSystem: this.getSystem,
getStore: this.getStore.bind(this),
getComponents: this.getComponents.bind(this),
getState: this.getStore().getState,
getConfigs: this._getConfigs.bind(this),
Im
}, this.system.rootInjects || {})
}
_getConfigs(){
return this.system.configs
}
getConfigs() {
return {
configs: this.system.configs
}
}
setConfigs(configs) {
this.system.configs = configs
}
rebuildReducer() {
this.store.replaceReducer(buildReducer(this.system.statePlugins))
}
/**
* Generic getter from system.statePlugins
*
*/
getType(name) {
let upName = name[0].toUpperCase() + name.slice(1)
return objReduce(this.system.statePlugins, (val, namespace) => {
let thing = val[name]
if(thing)
return {[namespace+upName]: thing}
})
}
getSelectors() {
return this.getType("selectors")
}
getActions() {
let actionHolders = this.getType("actions")
return objMap(actionHolders, (actions) => {
return objReduce(actions, (action, actionName) => {
if(isFn(action))
return {[actionName]: action}
})
})
}
getWrappedAndBoundActions(dispatch) {
let actionGroups = this.getBoundActions(dispatch)
return objMap(actionGroups, (actions, actionGroupName) => {
let wrappers = this.system.statePlugins[actionGroupName.slice(0,-7)].wrapActions
if(wrappers) {
return objMap(actions, (action, actionName) => {
let wrap = wrappers[actionName]
if(!wrap) {
return action
}
if(!Array.isArray(wrap)) {
wrap = [wrap]
}
return wrap.reduce((acc, fn) => {
let newAction = (...args) => {
return fn(acc, this.getSystem())(...args)
}
if(!isFn(newAction)) {
throw new TypeError("wrapActions needs to return a function that returns a new function (ie the wrapped action)")
}
return newAction
}, action || Function.prototype)
})
}
return actions
})
}
getStates(state) {
return Object.keys(this.system.statePlugins).reduce((obj, key) => {
obj[key] = state.get(key)
return obj
}, {})
}
getStateThunks(getState) {
return Object.keys(this.system.statePlugins).reduce((obj, key) => {
obj[key] = ()=> getState().get(key)
return obj
}, {})
}
getFn() {
return {
fn: this.system.fn
}
}
getComponents(component) {
if(typeof component !== "undefined")
return this.system.components[component]
return this.system.components
}
getBoundSelectors(getState, getSystem) {
return objMap(this.getSelectors(), (obj, key) => {
let stateName = [key.slice(0, -9)] // selectors = 9 chars
const getNestedState = ()=> getState().getIn(stateName)
return objMap(obj, (fn) => {
return (...args) => {
let res = fn.apply(null, [getNestedState(), ...args])
// If a selector returns a function, give it the system - for advanced usage
if(typeof(res) === "function")
res = res(getSystem())
return res
}
})
})
}
getBoundActions(dispatch) {
dispatch = dispatch || this.getStore().dispatch
const process = creator =>{
if( typeof( creator ) !== "function" ) {
return objMap(creator, prop => process(prop))
}
return ( ...args )=>{
var action = null
try{
action = creator( ...args )
}
catch( e ){
action = {type: NEW_THROWN_ERR, error: true, payload: serializeError(e) }
}
finally{
return action
}
}
}
return objMap(this.getActions(), actionCreator => bindActionCreators( process( actionCreator ), dispatch ) )
}
getMapStateToProps() {
return () => {
let obj = assign({}, this.getSystem())
return obj
}
}
getMapDispatchToProps(extras) {
return (dispatch) => {
return deepExtend({}, this.getWrappedAndBoundActions(dispatch), this.getFn(), extras)
}
}
}
function combinePlugins(plugins, toolbox) {
if(isObject(plugins) && !isArray(plugins))
return plugins
if(isFunc(plugins))
return combinePlugins(plugins(toolbox), toolbox)
if(isArray(plugins)) {
return plugins
.map(plugin => combinePlugins(plugin, toolbox))
.reduce(systemExtend, {})
}
return {}
}
// Wraps deepExtend, to account for certain fields, being wrappers.
// Ie: we need to convert some fields into arrays, and append to them.
// Rather than overwrite
function systemExtend(dest={}, src={}) {
if(!isObject(dest)) {
return {}
}
if(!isObject(src)) {
return dest
}
// Account for wrapActions, make it an array and append to it
// Modifies `src`
// 80% of this code is just safe traversal. We need to address that ( ie: use a lib )
const { statePlugins } = dest
if(isObject(statePlugins)) {
for(let namespace in statePlugins) {
const namespaceObj = statePlugins[namespace]
if(!isObject(namespaceObj) || !isObject(namespaceObj.wrapActions)) {
continue
}
const { wrapActions } = namespaceObj
for(let actionName in wrapActions) {
let action = wrapActions[actionName]
// This should only happen if dest is the first plugin, since invocations after that will ensure its an array
if(!Array.isArray(action)) {
action = [action]
wrapActions[actionName] = action // Put the value inside an array
}
if(src && src.statePlugins && src.statePlugins[namespace] && src.statePlugins[namespace].wrapActions && src.statePlugins[namespace].wrapActions[actionName]) {
src.statePlugins[namespace].wrapActions[actionName] = wrapActions[actionName].concat(src.statePlugins[namespace].wrapActions[actionName])
}
}
}
}
return deepExtend(dest, src)
}
function buildReducer(states) {
let reducerObj = objMap(states, (val) => {
return val.reducers
})
return allReducers(reducerObj)
}
function allReducers(reducerSystem) {
let reducers = Object.keys(reducerSystem).reduce((obj, key) => {
obj[key] = makeReducer(reducerSystem[key])
return obj
},{})
if(!Object.keys(reducers).length) {
return idFn
}
return combineReducers(reducers)
}
function makeReducer(reducerObj) {
return (state = new Map(), action) => {
if(!reducerObj)
return state
let redFn = reducerObj[action.type]
if(redFn) {
return redFn(state, action)
}
return state
}
}
function configureStore(rootReducer, initialState, getSystem) {
const store = createStoreWithMiddleware(rootReducer, initialState, getSystem)
// if (module.hot) {
// // Enable Webpack hot module replacement for reducers
// module.hot.accept("reducers/index", () => {
// const nextRootReducer = require("reducers/index")
// store.replaceReducer(nextRootReducer)
// })
// }
return store
}

551
src/core/utils.js Normal file
View File

@@ -0,0 +1,551 @@
import Im from "immutable"
import assign from "object-assign"
import shallowEqual from "shallowequal"
import camelCase from "lodash/camelCase"
import upperFirst from "lodash/upperFirst"
import _memoize from "lodash/memoize"
import some from "lodash/some"
import eq from "lodash/eq"
import { memoizedSampleFromSchema, memoizedCreateXMLExample } from "core/plugins/samples/fn"
const DEFAULT_REPONSE_KEY = "default"
export const isImmutable = (maybe) => Im.Iterable.isIterable(maybe)
export function objectify (thing) {
if(!isObject(thing))
return {}
if(isImmutable(thing))
return thing.toObject()
return thing
}
export function arrayify (thing) {
if(!thing)
return []
if(thing.toArray)
return thing.toArray()
return normalizeArray(thing)
}
export function fromJSOrdered (js) {
if(isImmutable(js))
return js // Can't do much here
return !isObject(js) ? js :
Array.isArray(js) ?
Im.Seq(js).map(fromJSOrdered).toList() :
Im.Seq(js).map(fromJSOrdered).toOrderedMap()
}
export function bindToState(obj, state) {
var newObj = {}
Object.keys(obj)
.filter(key => typeof obj[key] === "function")
.forEach(key => newObj[key] = obj[key].bind(null, state))
return newObj
}
export function normalizeArray(arr) {
if(Array.isArray(arr))
return arr
return [arr]
}
export function isFn(fn) {
return typeof fn === "function"
}
export function isObject(obj) {
return !!obj && typeof obj === "object"
}
export function isFunc(thing) {
return typeof(thing) === "function"
}
export function isArray(thing) {
return Array.isArray(thing)
}
// I've changed memoize libs more than once, so I'm using this a way to make that simpler
export const memoize = _memoize
export function objMap(obj, fn) {
return Object.keys(obj).reduce((newObj, key) => {
newObj[key] = fn(obj[key], key)
return newObj
}, {})
}
export function objReduce(obj, fn) {
return Object.keys(obj).reduce((newObj, key) => {
let res = fn(obj[key], key)
if(res && typeof res === "object")
assign(newObj, res)
return newObj
}, {})
}
// Redux middleware that exposes the system to async actions (like redux-thunk, but with out system instead of (dispatch, getState)
export function systemThunkMiddleware(getSystem) {
return ({ dispatch, getState }) => { // eslint-disable-line no-unused-vars
return next => action => {
if (typeof action === "function") {
return action(getSystem())
}
return next(action)
}
}
}
export const errorLog = getSystem => () => next => action => {
try{
next( action )
}
catch( e ) {
getSystem().errActions.newThrownErr( e, action )
}
}
export function defaultStatusCode ( responses ) {
let codes = responses.keySeq()
return codes.contains(DEFAULT_REPONSE_KEY) ? DEFAULT_REPONSE_KEY : codes.filter( key => (key+"")[0] === "2").sort().first()
}
/**
* Returns an Immutable List, safely
* @param {Immutable.Iterable} iterable the iterable to get the key from
* @param {String|[String]} key either an array of keys, or a single key
* @returns {Immutable.List} either iterable.get(keys) or an empty Immutable.List
*/
export function getList(iterable, keys) {
if(!Im.Iterable.isIterable(iterable)) {
return Im.List()
}
let val = iterable.getIn(Array.isArray(keys) ? keys : [keys])
return Im.List.isList(val) ? val : Im.List()
}
// Adapted from http://stackoverflow.com/a/2893259/454004
// Note: directly ported from CoffeeScript
export function formatXml (xml) {
var contexp, fn, formatted, indent, l, lastType, len, lines, ln, pad, reg, transitions, wsexp
reg = /(>)(<)(\/*)/g
wsexp = /[ ]*(.*)[ ]+\n/g
contexp = /(<.+>)(.+\n)/g
xml = xml.replace(/\r\n/g, "\n").replace(reg, "$1\n$2$3").replace(wsexp, "$1\n").replace(contexp, "$1\n$2")
pad = 0
formatted = ""
lines = xml.split("\n")
indent = 0
lastType = "other"
transitions = {
"single->single": 0,
"single->closing": -1,
"single->opening": 0,
"single->other": 0,
"closing->single": 0,
"closing->closing": -1,
"closing->opening": 0,
"closing->other": 0,
"opening->single": 1,
"opening->closing": 0,
"opening->opening": 1,
"opening->other": 1,
"other->single": 0,
"other->closing": -1,
"other->opening": 0,
"other->other": 0
}
fn = function(ln) {
var fromTo, j, key, padding, type, types, value
types = {
single: Boolean(ln.match(/<.+\/>/)),
closing: Boolean(ln.match(/<\/.+>/)),
opening: Boolean(ln.match(/<[^!?].*>/))
}
type = ((function() {
var results
results = []
for (key in types) {
value = types[key]
if (value) {
results.push(key)
}
}
return results
})())[0]
type = type === void 0 ? "other" : type
fromTo = lastType + "->" + type
lastType = type
padding = ""
indent += transitions[fromTo]
padding = ((function() {
var m, ref1, results
results = []
for (j = m = 0, ref1 = indent; 0 <= ref1 ? m < ref1 : m > ref1; j = 0 <= ref1 ? ++m : --m) {
results.push(" ")
}
return results
})()).join("")
if (fromTo === "opening->closing") {
formatted = formatted.substr(0, formatted.length - 1) + ln + "\n"
} else {
formatted += padding + ln + "\n"
}
}
for (l = 0, len = lines.length; l < len; l++) {
ln = lines[l]
fn(ln)
}
return formatted
}
/**
* Adapted from http://github.com/asvd/microlight
* @copyright 2016 asvd <heliosframework@gmail.com>
*/
export function highlight (el) {
const MAX_LENGTH = 5000
var
_window = window,
_document = document,
appendChild = "appendChild",
test = "test",
// style and color templates
textShadow = ";text-shadow:",
opacity = "opacity:.",
_0px_0px = " 0px 0px ",
_3px_0px_5 = "3px 0px 5",
brace = ")",
i,
microlighted
if (!el) return ""
if (el.textContent.length > MAX_LENGTH) { return el.textContent }
var reset = function(el) {
var text = el.textContent,
pos = 0, // current position
next1 = text[0], // next character
chr = 1, // current character
prev1, // previous character
prev2, // the one before the previous
token = // current token content
el.innerHTML = "", // (and cleaning the node)
// current token type:
// 0: anything else (whitespaces / newlines)
// 1: operator or brace
// 2: closing braces (after which '/' is division not regex)
// 3: (key)word
// 4: regex
// 5: string starting with "
// 6: string starting with '
// 7: xml comment <!-- -->
// 8: multiline comment /* */
// 9: single-line comment starting with two slashes //
// 10: single-line comment starting with hash #
tokenType = 0,
// kept to determine between regex and division
lastTokenType,
// flag determining if token is multi-character
multichar,
node,
// calculating the colors for the style templates
colorArr = /(\d*\, \d*\, \d*)(, ([.\d]*))?/g.exec(
_window.getComputedStyle(el).color
),
pxColor = "px rgba("+colorArr[1]+",",
alpha = colorArr[3]||1
// running through characters and highlighting
while (prev2 = prev1,
// escaping if needed (with except for comments)
// pervious character will not be therefore
// recognized as a token finalize condition
prev1 = tokenType < 7 && prev1 == "\\" ? 1 : chr
) {
chr = next1
next1=text[++pos]
multichar = token.length > 1
// checking if current token should be finalized
if (!chr || // end of content
// types 9-10 (single-line comments) end with a
// newline
(tokenType > 8 && chr == "\n") ||
[ // finalize conditions for other token types
// 0: whitespaces
/\S/[test](chr), // merged together
// 1: operators
1, // consist of a single character
// 2: braces
1, // consist of a single character
// 3: (key)word
!/[$\w]/[test](chr),
// 4: regex
(prev1 == "/" || prev1 == "\n") && multichar,
// 5: string with "
prev1 == "\"" && multichar,
// 6: string with '
prev1 == "'" && multichar,
// 7: xml comment
text[pos-4]+prev2+prev1 == "-->",
// 8: multiline comment
prev2+prev1 == "*/"
][tokenType]
) {
// appending the token to the result
if (token) {
// remapping token type into style
// (some types are highlighted similarly)
el[appendChild](
node = _document.createElement("span")
).setAttribute("style", [
// 0: not formatted
"color: #555; font-weight: bold;",
// 1: keywords
"",
// 2: punctuation
"",
// 3: strings and regexps
"color: #555;",
// 4: comments
""
][
// not formatted
!tokenType ? 0 :
// punctuation
tokenType < 3 ? 2 :
// comments
tokenType > 6 ? 4 :
// regex and strings
tokenType > 3 ? 3 :
// otherwise tokenType == 3, (key)word
// (1 if regexp matches, 0 otherwise)
+ /^(a(bstract|lias|nd|rguments|rray|s(m|sert)?|uto)|b(ase|egin|ool(ean)?|reak|yte)|c(ase|atch|har|hecked|lass|lone|ompl|onst|ontinue)|de(bugger|cimal|clare|f(ault|er)?|init|l(egate|ete)?)|do|double|e(cho|ls?if|lse(if)?|nd|nsure|num|vent|x(cept|ec|p(licit|ort)|te(nds|nsion|rn)))|f(allthrough|alse|inal(ly)?|ixed|loat|or(each)?|riend|rom|unc(tion)?)|global|goto|guard|i(f|mp(lements|licit|ort)|n(it|clude(_once)?|line|out|stanceof|t(erface|ernal)?)?|s)|l(ambda|et|ock|ong)|m(icrolight|odule|utable)|NaN|n(amespace|ative|ext|ew|il|ot|ull)|o(bject|perator|r|ut|verride)|p(ackage|arams|rivate|rotected|rotocol|ublic)|r(aise|e(adonly|do|f|gister|peat|quire(_once)?|scue|strict|try|turn))|s(byte|ealed|elf|hort|igned|izeof|tatic|tring|truct|ubscript|uper|ynchronized|witch)|t(emplate|hen|his|hrows?|ransient|rue|ry|ype(alias|def|id|name|of))|u(n(checked|def(ined)?|ion|less|signed|til)|se|sing)|v(ar|irtual|oid|olatile)|w(char_t|hen|here|hile|ith)|xor|yield)$/[test](token)
])
node[appendChild](_document.createTextNode(token))
}
// saving the previous token type
// (skipping whitespaces and comments)
lastTokenType =
(tokenType && tokenType < 7) ?
tokenType : lastTokenType
// initializing a new token
token = ""
// determining the new token type (going up the
// list until matching a token type start
// condition)
tokenType = 11
while (![
1, // 0: whitespace
// 1: operator or braces
/[\/{}[(\-+*=<>:;|\\.,?!&@~]/[test](chr),
/[\])]/[test](chr), // 2: closing brace
/[$\w]/[test](chr), // 3: (key)word
chr == "/" && // 4: regex
// previous token was an
// opening brace or an
// operator (otherwise
// division, not a regex)
(lastTokenType < 2) &&
// workaround for xml
// closing tags
prev1 != "<",
chr == "\"", // 5: string with "
chr == "'", // 6: string with '
// 7: xml comment
chr+next1+text[pos+1]+text[pos+2] == "<!--",
chr+next1 == "/*", // 8: multiline comment
chr+next1 == "//", // 9: single-line comment
chr == "#" // 10: hash-style comment
][--tokenType]);
}
token += chr
}
}
return reset(el)
}
/**
* Take an immutable map, and convert to a list.
* Where the keys are merged with the value objects
* @param {Immutable.Map} map, the map to convert
* @param {String} key the key to use, when merging the `key`
* @returns {Immutable.List}
*/
export function mapToList(map, keyNames="key", collectedKeys=Im.Map()) {
if(!Im.Map.isMap(map) || !map.size) {
return Im.List()
}
if(!Array.isArray(keyNames)) {
keyNames = [ keyNames ]
}
if(keyNames.length < 1) {
return map.merge(collectedKeys)
}
// I need to avoid `flatMap` from merging in the Maps, as well as the lists
let list = Im.List()
let keyName = keyNames[0]
for(let entry of map.entries()) {
let [key, val] = entry
let nextList = mapToList(val, keyNames.slice(1), collectedKeys.set(keyName, key))
if(Im.List.isList(nextList)) {
list = list.concat(nextList)
} else {
list = list.push(nextList)
}
}
return list
}
// PascalCase, aka UpperCamelCase
export function pascalCase(str) {
return upperFirst(camelCase(str))
}
// Remove the ext of a filename, and pascalCase it
export function pascalCaseFilename(filename) {
return pascalCase(filename.replace(/\.[^./]*$/, ""))
}
// Only compare a set of props
export function shallowEqualKeys(a,b, keys) {
return !!keys.find(key => !shallowEqual(a[key], b[key]))
}
// Check if ...
// - new props
// - If immutable, use .is()
// - if in explicit objectList, then compare using _.eq
// - else use ===
export const propChecker = (props, nextProps, objectList=[], ignoreList=[]) => {
if(Object.keys(props).length !== Object.keys(nextProps).length) {
return true
}
return (
some(props, (a, name) => {
if(ignoreList.includes(name)) {
return false
}
let b = nextProps[name]
if(Im.Iterable.isIterable(a)) {
return !Im.is(a,b)
}
// Not going to compare objects
if(typeof a === "object" && typeof b === "object") {
return false
}
return a !== b
})
|| objectList.some( objectPropName => !eq(props[objectPropName], nextProps[objectPropName])))
}
// validation of parameters before execute
export const validateParam = (param, isXml) => {
let errors = []
let value = isXml && param.get("in") === "body" ? param.get("value_xml") : param.get("value")
let required = param.get("required")
let type = param.get("type")
if ( required && (!value || (type==="array" && Array.isArray(value) && !value.length ))) {
errors.push("Required field is not provided")
return errors
}
if ( !value ) return errors
if ( type === "number" ) {
let err = validateNumber(value)
if (!err) return errors
errors.push(err)
} else if ( type === "integer" ) {
let err = validateInteger(value)
if (!err) return errors
errors.push(err)
} else if ( type === "array" ) {
let itemType
if ( !value.count() ) { return errors }
itemType = param.getIn(["items", "type"])
value.forEach((item, index) => {
let err
if (itemType === "number") {
err = validateNumber(item)
} else if (itemType === "integer") {
err = validateInteger(item)
}
if ( err ) {
errors.push({ index: index, error: err})
}
})
}
return errors
}
const validateNumber = ( val ) => {
if ( !/^\d+(.?\d+)?$/.test(val)) {
return "Value must be a number"
}
}
const validateInteger = ( val ) => {
if ( !/^\d+$/.test(val)) {
return "Value must be integer"
}
}
export const getSampleSchema = (schema, contentType="", config={}) => {
if (/xml/.test(contentType)) {
if (!schema.xml || !schema.xml.name) {
let name
schema.xml = schema.xml || {}
if (schema.$$ref) {
let match = schema.$$ref.match(/\S*\/(\S+)$/)
schema.xml.name = match[1]
} else if (schema.type || schema.items || schema.properties || schema.additionalProperties) {
return '<?xml version="1.0" encoding="UTF-8"?>\n<!-- XML example cannot be generated -->'
} else {
return null
}
}
return memoizedCreateXMLExample(schema, config)
}
return JSON.stringify(memoizedSampleFromSchema(schema, config), null, 2)
}

20
src/core/window.js Normal file
View File

@@ -0,0 +1,20 @@
var win = {
location: {},
history: {},
open: () => {},
close: () => {}
}
try {
win = window
var props = ["File", "Blob", "FormData"]
for (var prop of props) {
if (prop in window) {
win[prop] = window[prop]
}
}
} catch( e ) {
console.error(e)
}
export default win