mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 22:30:15 +00:00
XML and XPath support
This commit is contained in:
parent
b51fb24e53
commit
46d56804e6
@ -20,6 +20,8 @@
|
|||||||
"raven": "^0.12.1",
|
"raven": "^0.12.1",
|
||||||
"request": "^2.71.0",
|
"request": "^2.71.0",
|
||||||
"tough-cookie": "^2.3.1",
|
"tough-cookie": "^2.3.1",
|
||||||
"traverse": "^0.6.6"
|
"traverse": "^0.6.6",
|
||||||
|
"xml2js": "^0.4.17",
|
||||||
|
"xml2js-xpath": "^0.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import React, {Component, PropTypes} from 'react';
|
import React, {Component, PropTypes} from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import Dropdown from './base/Dropdown';
|
import Dropdown from './base/Dropdown';
|
||||||
import MethodTag from './tags/MethodTag';
|
import MethodTag from './tags/MethodTag';
|
||||||
import {METHODS} from '../lib/constants';
|
import {METHODS, DEBOUNCE_MILLIS} from '../lib/constants';
|
||||||
import Mousetrap from '../lib/mousetrap';
|
import Mousetrap from '../lib/mousetrap';
|
||||||
import {trackEvent} from '../lib/analytics';
|
import {trackEvent} from '../lib/analytics';
|
||||||
import {DEBOUNCE_MILLIS} from '../lib/constants';
|
|
||||||
|
|
||||||
|
|
||||||
class RequestUrlBar extends Component {
|
class RequestUrlBar extends Component {
|
||||||
@ -23,7 +21,10 @@ class RequestUrlBar extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
Mousetrap.bindGlobal('mod+l', () => {this.input.focus(); this.input.select()});
|
Mousetrap.bindGlobal('mod+l', () => {
|
||||||
|
this.input.focus();
|
||||||
|
this.input.select()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -33,9 +34,10 @@ class RequestUrlBar extends Component {
|
|||||||
const hasError = !url;
|
const hasError = !url;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames({'urlbar': true, 'urlbar--error': hasError})}>
|
<form className={classnames({'urlbar': true, 'urlbar--error': hasError})}
|
||||||
|
onSubmit={this._handleFormSubmit.bind(this)}>
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
<button>
|
<button type="button">
|
||||||
<div className="tall">
|
<div className="tall">
|
||||||
<span>{method}</span>
|
<span>{method}</span>
|
||||||
<i className="fa fa-caret-down"/>
|
<i className="fa fa-caret-down"/>
|
||||||
@ -54,7 +56,6 @@ class RequestUrlBar extends Component {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<form onSubmit={this._handleFormSubmit.bind(this)}>
|
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<input
|
<input
|
||||||
ref={n => this.input = n}
|
ref={n => this.input = n}
|
||||||
@ -63,9 +64,8 @@ class RequestUrlBar extends Component {
|
|||||||
defaultValue={url}
|
defaultValue={url}
|
||||||
onChange={e => this._handleUrlChange(e.target.value)}/>
|
onChange={e => this._handleUrlChange(e.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
<button>Send</button>
|
<button type="submit" className="urlbar__send-button">Send</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ import {getDOMNode} from 'react-dom';
|
|||||||
import CodeMirror from 'codemirror';
|
import CodeMirror from 'codemirror';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import JSONPath from 'jsonpath-plus';
|
import JSONPath from 'jsonpath-plus';
|
||||||
|
import xml2js from 'xml2js';
|
||||||
|
import xpath from 'xml2js-xpath';
|
||||||
import {DEBOUNCE_MILLIS} from '../../lib/constants';
|
import {DEBOUNCE_MILLIS} from '../../lib/constants';
|
||||||
import 'codemirror/mode/css/css';
|
import 'codemirror/mode/css/css';
|
||||||
import 'codemirror/mode/htmlmixed/htmlmixed';
|
import 'codemirror/mode/htmlmixed/htmlmixed';
|
||||||
@ -145,6 +147,55 @@ class Editor extends Component {
|
|||||||
return mode.indexOf('json') !== -1
|
return mode.indexOf('json') !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_isXML (mode) {
|
||||||
|
if (!mode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mode.indexOf('xml') !== -1
|
||||||
|
}
|
||||||
|
|
||||||
|
_formatJSON (code) {
|
||||||
|
try {
|
||||||
|
let obj = JSON.parse(code);
|
||||||
|
|
||||||
|
if (this.props.updateFilter && this.state.filter) {
|
||||||
|
obj = JSONPath({json: obj, path: this.state.filter});
|
||||||
|
}
|
||||||
|
|
||||||
|
code = JSON.stringify(obj, null, '\t');
|
||||||
|
} catch (e) {
|
||||||
|
// That's Ok, just leave it
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
_formatXML (code) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
xml2js.parseString(code, (err, obj) => {
|
||||||
|
if (err) {
|
||||||
|
resolve(code);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.updateFilter && this.state.filter) {
|
||||||
|
obj = xpath.find(obj, this.state.filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
const builder = new xml2js.Builder({
|
||||||
|
renderOpts: {
|
||||||
|
pretty: true,
|
||||||
|
indent: '\t'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const xml = builder.buildObject(obj);
|
||||||
|
|
||||||
|
resolve(xml);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets options on the CodeMirror editor while also sanitizing them
|
* Sets options on the CodeMirror editor while also sanitizing them
|
||||||
*/
|
*/
|
||||||
@ -202,22 +253,18 @@ class Editor extends Component {
|
|||||||
this._originalCode = code;
|
this._originalCode = code;
|
||||||
this._ignoreNextChange = true;
|
this._ignoreNextChange = true;
|
||||||
|
|
||||||
|
let promise;
|
||||||
if (this.props.prettify) {
|
if (this.props.prettify) {
|
||||||
try {
|
if (this._isXML(this.props.mode)) {
|
||||||
let obj = JSON.parse(code);
|
promise = this._formatXML(code);
|
||||||
|
} else {
|
||||||
if (this.props.updateFilter && this.state.filter) {
|
promise = this._formatJSON(code);
|
||||||
obj = JSONPath({json: obj, path: this.state.filter});
|
}
|
||||||
|
} else {
|
||||||
|
promise = Promise.resolve(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
code = JSON.stringify(obj, null, '\t');
|
promise.then(code => this.codeMirror.setValue(code));
|
||||||
} catch (e) {
|
|
||||||
// That's Ok, just leave it
|
|
||||||
// TODO: support more than just JSON prettifying
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.codeMirror.setValue(code);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleFilterChange (filter) {
|
_handleFilterChange (filter) {
|
||||||
@ -232,33 +279,51 @@ class Editor extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_showFilterHelp () {
|
_showFilterHelp () {
|
||||||
|
const json = this._isJSON(this.props.mode);
|
||||||
|
const link = json ? (
|
||||||
|
<Link href="http://goessner.net/articles/JsonPath/">
|
||||||
|
JSONPath
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
href="https://www.w3.org/TR/xpath/">
|
||||||
|
XPath
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
|
||||||
getModal(AlertModal).show({
|
getModal(AlertModal).show({
|
||||||
headerName: 'Response Filtering Help',
|
headerName: 'Response Filtering Help',
|
||||||
message: (
|
message: (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
Use <Link href="http://schier.co">JSONPath</Link> to filter the
|
Use {link} to filter the response body. Here are some examples that
|
||||||
response body. Here are some examples that you might use on a
|
you might use on a book store API.
|
||||||
book store API.
|
|
||||||
</p>
|
</p>
|
||||||
<table className="pad-top-sm">
|
<table className="pad-top-sm">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code className="selectable">$.store.books[*].title</code>
|
<td><code className="selectable">
|
||||||
|
{json ? '$.store.books[*].title' : '/store/books/title'}
|
||||||
|
</code>
|
||||||
</td>
|
</td>
|
||||||
<td>Get titles of all books in the store</td>
|
<td>Get titles of all books in the store</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code className="selectable">$.store.books[?(@.price <
|
<td><code className="selectable">
|
||||||
10)].title</code></td>
|
{json ? '$.store.books[?(@.price < 10)].title' : '/store/books[price < 10]'}
|
||||||
|
</code></td>
|
||||||
<td>Get books costing more than $10</td>
|
<td>Get books costing more than $10</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code className="selectable">$.store.books[-1:]</code></td>
|
<td><code className="selectable">
|
||||||
|
{json ? '$.store.books[-1:]' : '/store/books[last()]'}
|
||||||
|
</code></td>
|
||||||
<td>Get the last book in the store</td>
|
<td>Get the last book in the store</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code className="selectable">$.store.books.length</code></td>
|
<td><code className="selectable">
|
||||||
|
{json ? '$.store.books.length' : 'count(/store/books)'}
|
||||||
|
</code></td>
|
||||||
<td>Get the number of books in the store</td>
|
<td>Get the number of books in the store</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -286,14 +351,14 @@ class Editor extends Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let filterElement = null;
|
let filterElement = null;
|
||||||
if (this.props.updateFilter && this._isJSON(mode)) {
|
if (this.props.updateFilter && (this._isJSON(mode) || this._isXML(mode))) {
|
||||||
filterElement = (
|
filterElement = (
|
||||||
<div className="editor__filter">
|
<div className="editor__filter">
|
||||||
<div className="form-control form-control--outlined">
|
<div className="form-control form-control--outlined">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
defaultValue={filter || ''}
|
defaultValue={filter || ''}
|
||||||
placeholder="$.store.book[*].author"
|
placeholder={this._isJSON(mode) ? '$.store.books[*].author' : '/store/books/author'}
|
||||||
onChange={e => this._handleFilterChange(e.target.value)}
|
onChange={e => this._handleFilterChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,9 +3,8 @@
|
|||||||
|
|
||||||
.urlbar {
|
.urlbar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: auto 1fr;
|
flex-direction: row;
|
||||||
grid-template-rows: 1fr;
|
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
|
|
||||||
@ -51,21 +50,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
input {
|
||||||
display: grid;
|
min-width: 0;
|
||||||
grid-template-columns: 1fr auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
form button {
|
.urlbar__send-button {
|
||||||
padding-right: @padding-md;
|
padding-right: @padding-md;
|
||||||
padding-left: @padding-md;
|
padding-left: @padding-md;
|
||||||
}
|
}
|
||||||
|
|
||||||
form, .form-control {
|
.form-control {
|
||||||
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
form button,
|
.urlbar__send-button,
|
||||||
& > .dropdown {
|
& > .dropdown {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
@sidebar-width: 19rem;
|
@sidebar-width: 19rem;
|
||||||
|
|
||||||
/* Scrollbars */
|
/* Scrollbars */
|
||||||
@scrollbar-width: 0.8rem;
|
@scrollbar-width: 0.75rem;
|
||||||
|
|
||||||
/* Borders */
|
/* Borders */
|
||||||
@radius-sm: 0.15rem;
|
@radius-sm: 0.15rem;
|
||||||
|
@ -80,7 +80,9 @@
|
|||||||
"redux-thunk": "^2.0.1",
|
"redux-thunk": "^2.0.1",
|
||||||
"request": "^2.74.0",
|
"request": "^2.74.0",
|
||||||
"tough-cookie": "^2.3.1",
|
"tough-cookie": "^2.3.1",
|
||||||
"traverse": "^0.6.6"
|
"traverse": "^0.6.6",
|
||||||
|
"xml2js": "^0.4.17",
|
||||||
|
"xml2js-xpath": "^0.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "^6.11.4",
|
"babel-cli": "^6.11.4",
|
||||||
|
Loading…
Reference in New Issue
Block a user