Skip to content

Commit

Permalink
Add support to "examples" as per OpenAPI 3.0.0 spec
Browse files Browse the repository at this point in the history
Merged from swagger-api#3616, then
collected and re-applied the diff to have a nice git history
  • Loading branch information
ffissore committed Jan 9, 2018
1 parent 0818882 commit 9570c82
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 24 deletions.
40 changes: 39 additions & 1 deletion src/core/components/model-example.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ export default class ModelExample extends React.Component {
specSelectors: PropTypes.object.isRequired,
schema: PropTypes.object.isRequired,
example: PropTypes.any.isRequired,
examples: ImPropTypes.orderedMap,
isExecute: PropTypes.bool,
getConfigs: PropTypes.func.isRequired,
specPath: ImPropTypes.list.isRequired,
}

constructor(props, context) {
super(props, context)

let { getConfigs } = this.props
let { defaultModelRendering } = getConfigs()
if (defaultModelRendering !== "example" && defaultModelRendering !== "model") {
Expand All @@ -33,16 +35,36 @@ export default class ModelExample extends React.Component {
})
}

formatValue = (value) => {
if(typeof(value) === "string") {
return value
} else {
return JSON.stringify(value, null, 2)
}
}

render() {
let { getComponent, specSelectors, schema, example, isExecute, getConfigs, specPath } = this.props
let { getComponent, specSelectors, schema, example, examples, isExecute, getConfigs, specPath } = this.props
let { defaultModelExpandDepth } = getConfigs()
const ModelWrapper = getComponent("ModelWrapper")
const HighlightCode = getComponent("highlightCode")
const Markdown = getComponent("Markdown")
const ExternalValue = getComponent("ExternalValue")

// TODO fetch externalValue and display it on demand
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>

{ examples && examples.map( (item, key) => {
return ( !isExecute && <li key={"examples_key_" + key} className={"tabitem" + ( isExecute || this.state.activeTab === "example_" + key ? " active" : "") }>
<a className="tablinks" data-name={"example_" + key} onClick={ this.activeTab }>Example: {key}</a>
</li> )
} ).toArray()
}

{ schema ? <li className={ "tabitem" + ( !isExecute && this.state.activeTab === "model" ? " active" : "") }>
<a className={ "tablinks" + ( isExecute ? " inactive" : "" )} data-name="model" onClick={ this.activeTab }>Model</a>
</li> : null }
Expand All @@ -51,6 +73,22 @@ export default class ModelExample extends React.Component {
{
(isExecute || this.state.activeTab === "example") && example
}
{
examples && examples.map( (item, key) => {
return ((!isExecute && this.state.activeTab === "example_" + key) && (
<div key={"example_div_key_" + key} className="example-wrapper">
{ item.has("summary") && <h6 className="example-summary">{ item.get("summary") }</h6> }
{
item.has("description") && <div className="example-description">
<Markdown source={ item.get("description") } />
</div>
}
{ item.has("value") && <HighlightCode value={ this.formatValue(item.get("value")) } /> }
{ item.has("externalValue") && <ExternalValue location={ item.get("externalValue") } getComponent={ getComponent } /> }
</div>
))
} ).toArray()
}
{
!isExecute && this.state.activeTab === "model" && <ModelWrapper schema={ schema }
getComponent={ getComponent }
Expand Down
38 changes: 34 additions & 4 deletions src/core/components/parameter-row.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Map } from "immutable"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import win from "core/window"
import HighlightCode from "./highlight-code"
import { formatParamValue } from "core/plugins/oas3/utils"
import { getExtensions } from "core/utils"

export default class ParameterRow extends Component {
Expand Down Expand Up @@ -36,6 +38,7 @@ export default class ParameterRow extends Component {
let { isOAS3 } = specSelectors

let example = param.get("example")
let examples = param.get("examples")
let defaultValue = param.get("default")
let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
let enumValue
Expand All @@ -54,6 +57,8 @@ export default class ParameterRow extends Component {
value = paramValue
} else if ( example !== undefined ) {
value = example
} else if ( examples !== undefined && examples.size > 0 ) {
value = examples.first().value
} else if ( defaultValue !== undefined) {
value = defaultValue
} else if ( param.get("required") && enumValue && enumValue.size ) {
Expand Down Expand Up @@ -127,9 +132,20 @@ export default class ParameterRow extends Component {
// Default and Example Value for readonly doc
let paramDefaultValue // undefined
let paramExample // undefined
let paramExamples // undefined
if ( param !== undefined ) {
paramDefaultValue = param.get("default")
paramExample = param.get("example")
paramExamples = isOAS3 && isOAS3() && param.get("examples")

if(isOAS3 && isOAS3() && !paramExamples){
paramExample = param.get("example")

if(paramExample) {
paramExample = <HighlightCode value={ formatParamValue(paramExample, parameter) }/>
} else {
paramExample = <HighlightCode value={ formatParamValue(value, parameter) }/>
}
}
}

if (isDisplayParamItemsEnum) { // if we have an array, default value is in "items"
Expand Down Expand Up @@ -158,7 +174,7 @@ export default class ParameterRow extends Component {
<Markdown source={
"<i>Available values</i> : " + paramItemsEnum.map(function(item) {
return item
}).toArray()}/>
}).toArray()}/>
: null
}

Expand All @@ -177,7 +193,7 @@ export default class ParameterRow extends Component {
{ bodyParam || !isExecute ? null
: <JsonSchemaForm fn={fn}
getComponent={getComponent}
value={ value }
value={ formatParamValue(value, parameter) }
required={ required }
description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`}
onChange={ this.onChangeWrapper }
Expand All @@ -192,10 +208,24 @@ export default class ParameterRow extends Component {
isExecute={ isExecute }
specSelectors={ specSelectors }
schema={ schema }
example={ bodyParam }/>
example={ bodyParam }
examples={ paramExamples }/>
: null
}

{/* for non-body params with example(s) */}
{
(!bodyParam && paramExamples) && <ModelExample
getComponent={ getComponent }
example={ example }
examples={ paramExamples }
getConfigs={ getConfigs }
isExecute={ isExecute }
specSelectors={ specSelectors }
schema={ schema } />
}


</td>

</tr>
Expand Down
17 changes: 15 additions & 2 deletions src/core/components/response.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import cx from "classnames"
import { fromJS, Seq, Iterable, List } from "immutable"
import { fromJS, OrderedMap, Seq, Iterable, List } from "immutable"
import { getSampleSchema, fromJSOrdered } from "core/utils"

const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
Expand Down Expand Up @@ -106,6 +106,17 @@ export default class Response extends React.Component {
includeReadOnly: true
}) : null
schema = oas3SchemaForContentType ? inferSchema(oas3SchemaForContentType.toJS()) : null
examples = response.getIn(["content", this.state.responseContentType, "examples"])

if(!examples) {
// The example object is mutually exclusive of the examples object. Prefer `examples` here.
let example = response.getIn(["content", this.state.responseContentType, "example"])

if(example) {
examples = OrderedMap({"Example": example})
}
}

specPathWithPossibleSchema = oas3SchemaForContentType ? schemaPath : specPath
} else {
schema = inferSchema(response.toJS()) // TODO: don't convert back and forth. Lets just stick with immutable for inferSchema
Expand Down Expand Up @@ -156,7 +167,9 @@ export default class Response extends React.Component {
getConfigs={ getConfigs }
specSelectors={ specSelectors }
schema={ fromJSOrdered(schema) }
example={ example }/>
example={ example }
examples={ examples }
/>
) : null}

{ headers ? (
Expand Down
33 changes: 33 additions & 0 deletions src/core/plugins/oas3/components/external-value.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react"
import PropTypes from "prop-types"

/**
* ExternalValue is a <HighlightCode> with content set to an external location.
* usage: <ExternalValue location="http://example.com/path/to/file" />
*/
export default class ExternalValue extends React.Component {
static propTypes = {
getComponent: PropTypes.func.isRequired,
location: PropTypes.string.isRequired
}

constructor(props, context) {
super(props, context)
}

render() {
let { location } = this.props
// const Markdown = getComponent("Markdown")

// TODO should externalValue be fetched and displayed inline?

return (
<div>
<a href={location} target="_blank" title={location} >Open ExternalValue</a>
</div>
)
}
}



4 changes: 4 additions & 0 deletions src/core/plugins/oas3/components/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

import Callbacks from "./callbacks"
import RequestBody from "./request-body"
import OperationLink from "./operation-link.jsx"
import ExternalValue from "./external-value.jsx"
import Servers from "./servers"
import RequestBodyEditor from "./request-body-editor"
import HttpAuth from "./http-auth"
Expand All @@ -10,6 +12,8 @@ export default {
Callbacks,
HttpAuth,
RequestBody,
operationLink: OperationLink,
ExternalValue,
Servers,
RequestBodyEditor,
OperationServers,
Expand Down
4 changes: 4 additions & 0 deletions src/core/plugins/oas3/components/request-body.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { OrderedMap } from "immutable"
import { memoizedGetExamples } from "core/plugins/oas3/utils"

const RequestBody = ({
requestBody,
Expand All @@ -23,6 +24,8 @@ const RequestBody = ({

const mediaTypeValue = requestBodyContent.get(contentType)

const examples = memoizedGetExamples(mediaTypeValue.get("examples"))

if(!mediaTypeValue) {
return null
}
Expand All @@ -47,6 +50,7 @@ const RequestBody = ({
isExecute={isExecute}
specSelectors={specSelectors}
/>}
examples={examples}
/>
</div>
}
Expand Down
27 changes: 27 additions & 0 deletions src/core/plugins/oas3/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import memoizee from "memoizee"
import { fromJS } from "immutable"

/**
* Convert OAS3 "Examples" (in requestBody) section and convert to Map
* @param examplesInSpec {object} "examples" in OAS3 spec
* @returns {Map} Map {name: {summary, description, value, externalValue}}
*/
export const getExamples = (examplesInSpec) => {
return fromJS(examplesInSpec)
}

export const memoizedGetExamples = memoizee(getExamples)

/**
* Convert 'value' to serialized format, so can be handled correctly in <input>
* @param value value
* @param parameter {OrderedMap} parameter in spec
*/
export const formatParamValue = (value, parameter) => {
if (typeof ( value ) === "string") {
return value
} else {
// TODO apply 'style', 'explode', 'allowReserved' in OAS3
return JSON.stringify(value, null, 2)
}
}
44 changes: 27 additions & 17 deletions src/style/_layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -395,33 +395,29 @@
{
font-size: 12px;

min-width: 100px;
min-width: 90px;
padding: 0;

cursor: pointer;

@include text_headline();

&:first-of-type
{
position: relative;
position: relative;

padding-left: 0;
padding-left: 0;
padding-right: 5px;
margin-right: 5px;

&:after
{
position: absolute;
top: 0;
right: 6px;

width: 1px;
height: 100%;
&:nth-last-child(n+2):after
{
position: absolute;
top: 0;
right: 0;

content: '';
width: 1px;
height: 100%;

background: rgba($tab-list-item-first-background-color,.2);
}
content: '';
background: rgba($tab-list-item-first-background-color,.2);
}

&.active
Expand Down Expand Up @@ -758,3 +754,17 @@ a.nostyle {
cursor: pointer;
}
}

/* OAS3 responseBody examples start */
.example-description {
margin: 0 0 5px 0;
padding: 5px 0;
}

/* .example-summary is the summary line of parameter examples */
.example-summary {
margin: 0;
/* use the same font as .opblock-summary-description */
@include text_body();
}
/* OAS3 responseBody examples end */

0 comments on commit 9570c82

Please sign in to comment.