diff --git a/src/core/components/layouts/base.jsx b/src/core/components/layouts/base.jsx
index 6cd909bb..1f5a0788 100644
--- a/src/core/components/layouts/base.jsx
+++ b/src/core/components/layouts/base.jsx
@@ -94,8 +94,9 @@ export default class BaseLayout extends React.Component {
) : null }
{ servers && servers.size ? (
-
+
+
Server
+ { !tryItOutEnabled ? null :
+
+ }
+
{!tryItOutEnabled || !allowTryItOut ? null : schemes && schemes.size ?
{
+ const { path, method } = this.props
+ // FIXME: we should be keeping up with this in props/state upstream of us
+ // instead of cheating™ with `forceUpdate`
+ this.forceUpdate()
+ return this.props.setSelectedServer(server, `${path}:${method}`)
+ }
+
+ setServerVariableValue = (obj) => {
+ const { path, method } = this.props
+ // FIXME: we should be keeping up with this in props/state upstream of us
+ // instead of cheating™ with `forceUpdate`
+ this.forceUpdate()
+ return this.props.setServerVariableValue({
+ ...obj,
+ namespace: `${path}:${method}`
+ })
+ }
+
+ getSelectedServer = () => {
+ const { path, method } = this.props
+ return this.props.getSelectedServer(`${path}:${method}`)
+ }
+
+ getServerVariable = (server, key) => {
+ const { path, method } = this.props
+ return this.props.getServerVariable({
+ namespace: `${path}:${method}`,
+ server
+ }, key)
+ }
+
+ getEffectiveServerValue = (server) => {
+ const { path, method } = this.props
+ return this.props.getEffectiveServerValue({
+ server,
+ namespace: `${path}:${method}`
+ })
+ }
+
+ render() {
+ const {
+ // for self
+ operationServers,
+ pathServers,
+
+ // util
+ getComponent
+ } = this.props
+
+ if(!operationServers && !pathServers) {
+ return null
+ }
+
+ const Servers = getComponent("Servers")
+
+ const serversToDisplay = operationServers || pathServers
+ const displaying = operationServers ? "operation" : "path"
+
+ return
+
+
+
+ These {displaying}-level options override the global server options.
+
+
+
+
+ }
+}
diff --git a/src/core/plugins/oas3/components/servers.jsx b/src/core/plugins/oas3/components/servers.jsx
index 75b528f5..d6ed4a63 100644
--- a/src/core/plugins/oas3/components/servers.jsx
+++ b/src/core/plugins/oas3/components/servers.jsx
@@ -15,7 +15,11 @@ export default class Servers extends React.Component {
}
componentDidMount() {
- let { servers } = this.props
+ let { servers, currentServer } = this.props
+
+ if(currentServer) {
+ return
+ }
//fire 'change' event to set default 'value' of select
this.setServer(servers.first().get("url"))
@@ -93,9 +97,8 @@ export default class Servers extends React.Component {
let shouldShowVariableUI = currentServerVariableDefs.size !== 0
return (
-
+
{ shouldShowVariableUI ?
-
Server variables
+
Computed URL:
{getEffectiveServerValue(currentServer)}
+
Server variables
{
diff --git a/src/core/plugins/oas3/reducers.js b/src/core/plugins/oas3/reducers.js
index 8590284d..871aff89 100644
--- a/src/core/plugins/oas3/reducers.js
+++ b/src/core/plugins/oas3/reducers.js
@@ -7,8 +7,9 @@ import {
} from "./actions"
export default {
- [UPDATE_SELECTED_SERVER]: (state, { payload: selectedServerUrl } ) =>{
- return state.setIn( [ "selectedServer" ], selectedServerUrl)
+ [UPDATE_SELECTED_SERVER]: (state, { payload: { selectedServerUrl, namespace } } ) =>{
+ const path = namespace ? [ namespace, "selectedServer"] : [ "selectedServer"]
+ return state.setIn( path, selectedServerUrl)
},
[UPDATE_REQUEST_BODY_VALUE]: (state, { payload: { value, pathMethod } } ) =>{
let [path, method] = pathMethod
@@ -21,7 +22,8 @@ export default {
[UPDATE_RESPONSE_CONTENT_TYPE]: (state, { payload: { value, path, method } } ) =>{
return state.setIn( [ "requestData", path, method, "responseContentType" ], value)
},
- [UPDATE_SERVER_VARIABLE_VALUE]: (state, { payload: { server, key, val } } ) =>{
- return state.setIn( [ "serverVariableValues", server, key ], val)
+ [UPDATE_SERVER_VARIABLE_VALUE]: (state, { payload: { server, namespace, key, val } } ) =>{
+ const path = namespace ? [ namespace, "serverVariableValues", server, key ] : [ "serverVariableValues", server, key ]
+ return state.setIn(path, val)
},
}
diff --git a/src/core/plugins/oas3/selectors.js b/src/core/plugins/oas3/selectors.js
index 86fc617f..c65a6882 100644
--- a/src/core/plugins/oas3/selectors.js
+++ b/src/core/plugins/oas3/selectors.js
@@ -15,8 +15,9 @@ function onlyOAS3(selector) {
}
}
-export const selectedServer = onlyOAS3(state => {
- return state.getIn(["selectedServer"]) || ""
+export const selectedServer = onlyOAS3((state, namespace) => {
+ const path = namespace ? [namespace, "selectedServer"] : ["selectedServer"]
+ return state.getIn(path) || ""
}
)
@@ -35,19 +36,68 @@ export const responseContentType = onlyOAS3((state, path, method) => {
}
)
-export const serverVariableValue = onlyOAS3((state, server, key) => {
- return state.getIn(["serverVariableValues", server, key]) || null
+export const serverVariableValue = onlyOAS3((state, locationData, key) => {
+ let path
+
+ // locationData may take one of two forms, for backwards compatibility
+ // Object: ({server, namespace?}) or String:(server)
+ if(typeof locationData !== "string") {
+ const { server, namespace } = locationData
+ if(namespace) {
+ path = [namespace, "serverVariableValues", server, key]
+ } else {
+ path = ["serverVariableValues", server, key]
+ }
+ } else {
+ const server = locationData
+ path = ["serverVariableValues", server, key]
+ }
+
+ return state.getIn(path) || null
}
)
-export const serverVariables = onlyOAS3((state, server) => {
- return state.getIn(["serverVariableValues", server]) || OrderedMap()
+export const serverVariables = onlyOAS3((state, locationData) => {
+ let path
+
+ // locationData may take one of two forms, for backwards compatibility
+ // Object: ({server, namespace?}) or String:(server)
+ if(typeof locationData !== "string") {
+ const { server, namespace } = locationData
+ if(namespace) {
+ path = [namespace, "serverVariableValues", server]
+ } else {
+ path = ["serverVariableValues", server]
+ }
+ } else {
+ const server = locationData
+ path = ["serverVariableValues", server]
+ }
+
+ return state.getIn(path) || OrderedMap()
}
)
-export const serverEffectiveValue = onlyOAS3((state, server) => {
- let varValues = state.getIn(["serverVariableValues", server]) || OrderedMap()
- let str = server
+export const serverEffectiveValue = onlyOAS3((state, locationData) => {
+ var varValues, serverValue
+
+ // locationData may take one of two forms, for backwards compatibility
+ // Object: ({server, namespace?}) or String:(server)
+ if(typeof locationData !== "string") {
+ const { server, namespace } = locationData
+ serverValue = server
+ if(namespace) {
+ varValues = state.getIn([namespace, "serverVariableValues", serverValue])
+ } else {
+ varValues = state.getIn(["serverVariableValues", serverValue])
+ }
+ } else {
+ serverValue = locationData
+ varValues = state.getIn(["serverVariableValues", serverValue])
+ }
+
+ varValues = varValues || OrderedMap()
+ let str = serverValue
varValues.map((val, key) => {
str = str.replace(new RegExp(`{${key}}`, "g"), val)
diff --git a/src/core/plugins/spec/actions.js b/src/core/plugins/spec/actions.js
index 75c2a6b4..5e6f51fc 100644
--- a/src/core/plugins/spec/actions.js
+++ b/src/core/plugins/spec/actions.js
@@ -228,9 +228,18 @@ export const executeRequest = (req) =>
}
if(specSelectors.isOAS3()) {
- // OAS3 request feature support
- req.server = oas3Selectors.selectedServer()
- req.serverVariables = oas3Selectors.serverVariables(req.server).toJS()
+ const namespace = `${pathName}:${method}`
+
+ req.server = oas3Selectors.selectedServer(namespace) || oas3Selectors.selectedServer()
+
+ const namespaceVariables = oas3Selectors.serverVariables({
+ server: req.server,
+ namespace
+ }).toJS()
+ const globalVariables = oas3Selectors.serverVariables({ server: req.server }).toJS()
+
+ req.serverVariables = Object.keys(namespaceVariables).length ? namespaceVariables : globalVariables
+
req.requestContentType = oas3Selectors.requestContentType(pathName, method)
req.responseContentType = oas3Selectors.responseContentType(pathName, method) || "*/*"
const requestBody = oas3Selectors.requestBodyValue(pathName, method)
diff --git a/src/style/_layout.scss b/src/style/_layout.scss
index 0427b276..1d399f76 100644
--- a/src/style/_layout.scss
+++ b/src/style/_layout.scss
@@ -650,78 +650,6 @@
}
}
-.server-container
-{
- margin: 0 0 20px 0;
- padding: 30px 0;
-
- background: $server-container-background-color;
- box-shadow: 0 1px 2px 0 rgba($server-container-box-shadow-color,.15);
-
- .computed-url {
- margin: 2em 0;
-
- code {
- color: $server-container-computed-url-code-font-color;
- display: inline-block;
- padding: 4px;
- font-size: 16px;
- margin: 0 1em;
- font-style: italic;
- }
- }
-
- .servers
- {
- display: flex;
- align-items: center;
-
- .servers-title {
- margin-right: 1em;
- }
-
- > label
- {
- font-size: 12px;
-
- display: flex;
- flex-direction: column;
-
- margin: -20px 15px 0 0;
-
- @include text_headline();
-
- select
- {
- min-width: 130px;
- }
- }
-
- table {
- tr {
- width: 30em;
- }
- td {
- display: inline-block;
- max-width: 15em;
- vertical-align: middle;
- padding-top: 10px;
- padding-bottom: 10px;
-
- &:first-of-type {
- padding-right: 2em;
- }
-
- input {
- width: 100%;
- height: 100%;
- }
- }
- }
- }
-}
-
-
.loading-container
{
padding: 40px 0 60px;
diff --git a/src/style/_servers.scss b/src/style/_servers.scss
new file mode 100644
index 00000000..bf3ae39a
--- /dev/null
+++ b/src/style/_servers.scss
@@ -0,0 +1,74 @@
+.servers
+{
+ > label
+ {
+ font-size: 12px;
+
+ margin: -20px 15px 0 0;
+
+ @include text_headline();
+
+ select
+ {
+ min-width: 130px;
+ }
+ }
+
+ h4.message {
+ padding-bottom: 2em;
+ }
+
+ table {
+ tr {
+ width: 30em;
+ }
+ td {
+ display: inline-block;
+ max-width: 15em;
+ vertical-align: middle;
+ padding-top: 10px;
+ padding-bottom: 10px;
+
+ &:first-of-type {
+ padding-right: 2em;
+ }
+
+ input {
+ width: 100%;
+ height: 100%;
+ }
+ }
+ }
+
+ .computed-url {
+ margin: 2em 0;
+
+ code {
+ display: inline-block;
+ padding: 4px;
+ font-size: 16px;
+ margin: 0 1em;
+ }
+ }
+}
+
+
+.global-server-container
+{
+ margin: 0 0 20px 0;
+ padding: 30px 0;
+
+ background: $server-container-background-color;
+ box-shadow: 0 1px 2px 0 rgba($server-container-box-shadow-color,.15);
+
+ .servers-title {
+ line-height: 2em;
+ font-weight: bold;
+ }
+}
+
+.operation-servers {
+ h4.message {
+ margin-bottom: 2em;
+ }
+}
diff --git a/src/style/main.scss b/src/style/main.scss
index edc7e4f3..23bf2585 100644
--- a/src/style/main.scss
+++ b/src/style/main.scss
@@ -9,6 +9,7 @@
@import 'form';
@import 'modal';
@import 'models';
+ @import 'servers';
@import 'table';
@import 'topbar';
@import 'information';
diff --git a/test/core/plugins/oas3/state-integration.js b/test/core/plugins/oas3/state-integration.js
new file mode 100644
index 00000000..6ce69da5
--- /dev/null
+++ b/test/core/plugins/oas3/state-integration.js
@@ -0,0 +1,359 @@
+import expect from "expect"
+import { fromJS, OrderedMap } from "immutable"
+
+import {
+ selectedServer,
+ serverVariableValue,
+ serverVariables,
+ serverEffectiveValue
+} from "corePlugins/oas3/selectors"
+
+import reducers from "corePlugins/oas3/reducers"
+
+import {
+ setSelectedServer,
+ setServerVariableValue,
+} from "corePlugins/oas3/actions"
+
+describe("OAS3 plugin - state", function() {
+ describe("action + reducer + selector integration", function() {
+ describe("selectedServer", function() {
+ it("should set and get a global selectedServer", function() {
+ const state = new OrderedMap()
+ const system = {
+ // needed to handle `onlyOAS3` wrapper
+ getSystem() {
+ return {
+ specSelectors: {
+ specJson: () => {
+ return fromJS({ openapi: "3.0.0" })
+ }
+ }
+ }
+ }
+ }
+
+ // Create the action
+ const action = setSelectedServer("http://google.com")
+
+ // Collect the new state
+ const newState = reducers["oas3_set_servers"](state, action)
+
+ // Get the value with the selector
+ const res = selectedServer(newState)(system)
+
+ expect(res).toEqual("http://google.com")
+ })
+
+ it("should set and get a namespaced selectedServer", function() {
+ const state = fromJS({
+ selectedServer: "http://yahoo.com"
+ })
+ const system = {
+ // needed to handle `onlyOAS3` wrapper
+ getSystem() {
+ return {
+ specSelectors: {
+ specJson: () => {
+ return fromJS({ openapi: "3.0.0" })
+ }
+ }
+ }
+ }
+ }
+
+ // Create the action
+ const action = setSelectedServer("http://google.com", "myOperation")
+
+ // Collect the new state
+ const newState = reducers["oas3_set_servers"](state, action)
+
+ // Get the value with the selector
+ const res = selectedServer(newState, "myOperation")(system)
+
+ // Get the global selected server
+ const globalRes = selectedServer(newState)(system)
+
+ expect(res).toEqual("http://google.com")
+ expect(globalRes).toEqual("http://yahoo.com")
+ })
+ })
+
+ describe("serverVariableValue", function() {
+ it("should set and get a global serverVariableValue", function() {
+ const state = new OrderedMap()
+ const system = {
+ // needed to handle `onlyOAS3` wrapper
+ getSystem() {
+ return {
+ specSelectors: {
+ specJson: () => {
+ return fromJS({ openapi: "3.0.0" })
+ }
+ }
+ }
+ }
+ }
+
+ // Create the action
+ const action = setServerVariableValue({
+ server: "google.com",
+ key: "foo",
+ val: "bar"
+ })
+
+ // Collect the new state
+ const newState = reducers["oas3_set_server_variable_value"](state, action)
+
+ // Get the value with the selector
+ const res = serverVariableValue(newState, "google.com", "foo")(system)
+
+ expect(res).toEqual("bar")
+ })
+ it("should set and get a namespaced serverVariableValue", function() {
+ const state = fromJS({
+ serverVariableValues: {
+ "google.com": {
+ foo: "123"
+ }
+ }
+ })
+ const system = {
+ // needed to handle `onlyOAS3` wrapper
+ getSystem() {
+ return {
+ specSelectors: {
+ specJson: () => {
+ return fromJS({ openapi: "3.0.0" })
+ }
+ }
+ }
+ }
+ }
+
+ // Create the action
+ const action = setServerVariableValue({
+ namespace: "myOperation",
+ server: "google.com",
+ key: "foo",
+ val: "bar"
+ })
+
+ // Collect the new state
+ const newState = reducers["oas3_set_server_variable_value"](state, action)
+
+ // Get the value with the selector
+ const res = serverVariableValue(newState, {
+ namespace: "myOperation",
+ server: "google.com"
+ }, "foo")(system)
+
+ // Get the global value, to cross-check
+ const globalRes = serverVariableValue(newState, {
+ server: "google.com"
+ }, "foo")(system)
+
+ expect(res).toEqual("bar")
+ expect(globalRes).toEqual("123")
+ })
+ })
+
+ describe("serverVariables", function() {
+ it("should set and get global serverVariables", function() {
+ const state = new OrderedMap()
+ const system = {
+ // needed to handle `onlyOAS3` wrapper
+ getSystem() {
+ return {
+ specSelectors: {
+ specJson: () => {
+ return fromJS({ openapi: "3.0.0" })
+ }
+ }
+ }
+ }
+ }
+
+ // Create the action
+ const action = setServerVariableValue({
+ server: "google.com",
+ key: "foo",
+ val: "bar"
+ })
+
+ // Collect the new state
+ const newState = reducers["oas3_set_server_variable_value"](state, action)
+
+ // Get the value with the selector
+ const res = serverVariables(newState, "google.com", "foo")(system)
+
+ expect(res.toJS()).toEqual({
+ foo: "bar"
+ })
+ })
+
+ it("should set and get namespaced serverVariables", function() {
+ const state = fromJS({
+ serverVariableValues: {
+ "google.com": {
+ foo: "123"
+ }
+ }
+ })
+
+ const system = {
+ // needed to handle `onlyOAS3` wrapper
+ getSystem() {
+ return {
+ specSelectors: {
+ specJson: () => {
+ return fromJS({ openapi: "3.0.0" })
+ }
+ }
+ }
+ }
+ }
+
+ // Create the action
+ const action = setServerVariableValue({
+ namespace: "myOperation",
+ server: "google.com",
+ key: "foo",
+ val: "bar"
+ })
+
+ // Collect the new state
+ const newState = reducers["oas3_set_server_variable_value"](state, action)
+
+ // Get the value with the selector
+ const res = serverVariables(newState, {
+ namespace: "myOperation",
+ server: "google.com"
+ }, "foo")(system)
+
+ // Get the global value, to cross-check
+ const globalRes = serverVariables(newState, {
+ server: "google.com"
+ }, "foo")(system)
+
+ expect(res.toJS()).toEqual({
+ foo: "bar"
+ })
+
+ expect(globalRes.toJS()).toEqual({
+ foo: "123"
+ })
+ })
+ })
+ describe("serverEffectiveValue", function() {
+ it("should set variable values and compute a URL for a namespaced server", function() {
+ const state = fromJS({
+ serverVariableValues: {
+ "google.com/{foo}": {
+ foo: "123"
+ }
+ }
+ })
+
+ const system = {
+ // needed to handle `onlyOAS3` wrapper
+ getSystem() {
+ return {
+ specSelectors: {
+ specJson: () => {
+ return fromJS({ openapi: "3.0.0" })
+ }
+ }
+ }
+ }
+ }
+
+ // Create the action
+ const action = setServerVariableValue({
+ namespace: "myOperation",
+ server: "google.com/{foo}",
+ key: "foo",
+ val: "bar"
+ })
+
+ // Collect the new state
+ const newState = reducers["oas3_set_server_variable_value"](state, action)
+
+ // Get the value with the selector
+ const res = serverEffectiveValue(newState, {
+ namespace: "myOperation",
+ server: "google.com/{foo}"
+ })(system)
+
+ // Get the global value, to cross-check
+ const globalRes = serverEffectiveValue(newState, {
+ server: "google.com/{foo}"
+ })(system)
+
+ expect(res).toEqual("google.com/bar")
+
+ expect(globalRes).toEqual("google.com/123")
+ })
+ })
+
+ })
+ describe("selectors", function() {
+ describe("serverEffectiveValue", function() {
+ it("should compute global serverEffectiveValues", function() {
+ const state = fromJS({
+ serverVariableValues: {
+ "google.com/{foo}/{bar}": {
+ foo: "123",
+ bar: "456"
+ }
+ }
+ })
+ const system = {
+ // needed to handle `onlyOAS3` wrapper
+ getSystem() {
+ return {
+ specSelectors: {
+ specJson: () => {
+ return fromJS({ openapi: "3.0.0" })
+ }
+ }
+ }
+ }
+ }
+
+ // Get the value with the selector
+ const res = serverEffectiveValue(state, "google.com/{foo}/{bar}")(system)
+
+ expect(res).toEqual("google.com/123/456")
+ })
+
+ it("should handle multiple variable instances", function() {
+ const state = fromJS({
+ serverVariableValues: {
+ "google.com/{foo}/{foo}/{bar}": {
+ foo: "123",
+ bar: "456"
+ }
+ }
+ })
+ const system = {
+ // needed to handle `onlyOAS3` wrapper
+ getSystem() {
+ return {
+ specSelectors: {
+ specJson: () => {
+ return fromJS({ openapi: "3.0.0" })
+ }
+ }
+ }
+ }
+ }
+
+ // Get the value with the selector
+ const res = serverEffectiveValue(state, "google.com/{foo}/{foo}/{bar}")(system)
+
+ expect(res).toEqual("google.com/123/123/456")
+ })
+ })
+ })
+})