added ajv validator

This commit is contained in:
Harjeet Singh 2018-04-23 16:36:06 -07:00
parent 2a7ce2b64e
commit e3b9afad92
10 changed files with 1115 additions and 13 deletions

993
demo/petstore.json Normal file
View File

@ -0,0 +1,993 @@
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.",
"version": "1.0.0",
"title": "Swagger Petstore",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"email": "apiteam@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"tags": [{
"name": "pet",
"description": "Everything about your Pets",
"externalDocs": {
"description": "Find out more",
"url": "http://swagger.io"
}
},
{
"name": "store",
"description": "Access to Petstore orders"
},
{
"name": "user",
"description": "Operations about user",
"externalDocs": {
"description": "Find out more about our store",
"url": "http://swagger.io"
}
}
],
"schemes": [
"http"
],
"paths": {
"/pet": {
"post": {
"tags": [
"pet"
],
"summary": "Add a new pet to the store",
"description": "",
"operationId": "addPet",
"consumes": [
"application/json",
"application/xml"
],
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"in": "body",
"name": "body",
"description": "Pet object that needs to be added to the store",
"required": true,
"schema": {
"$ref": "#/definitions/Pet"
}
}],
"responses": {
"405": {
"description": "Invalid input"
}
},
"security": [{
"petstore_auth": [
"write:pets",
"read:pets"
]
}]
},
"put": {
"tags": [
"pet"
],
"summary": "Update an existing pet",
"description": "",
"operationId": "updatePet",
"consumes": [
"application/json",
"application/xml"
],
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"in": "body",
"name": "body",
"description": "Pet object that needs to be added to the store",
"required": true,
"schema": {
"$ref": "#/definitions/Pet"
}
}],
"responses": {
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Pet not found"
},
"405": {
"description": "Validation exception"
}
},
"security": [{
"petstore_auth": [
"write:pets",
"read:pets"
]
}]
}
},
"/pet/findByStatus": {
"get": {
"tags": [
"pet"
],
"summary": "Finds Pets by status",
"description": "Multiple status values can be provided with comma separated strings",
"operationId": "findPetsByStatus",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"name": "status",
"in": "query",
"description": "Status values that need to be considered for filter",
"required": true,
"type": "array",
"items": {
"type": "string",
"enum": [
"available",
"pending",
"sold"
],
"default": "available"
},
"collectionFormat": "multi"
}],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Pet"
}
}
},
"400": {
"description": "Invalid status value"
}
},
"security": [{
"petstore_auth": [
"write:pets",
"read:pets"
]
}]
}
},
"/pet/findByTags": {
"get": {
"tags": [
"pet"
],
"summary": "Finds Pets by tags",
"description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.",
"operationId": "findPetsByTags",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"name": "tags",
"in": "query",
"description": "Tags to filter by",
"required": true,
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi"
}],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Pet"
}
}
},
"400": {
"description": "Invalid tag value"
}
},
"security": [{
"petstore_auth": [
"write:pets",
"read:pets"
]
}],
"deprecated": true
}
},
"/pet/{petId}": {
"get": {
"tags": [
"pet"
],
"summary": "Find pet by ID",
"description": "Returns a single pet",
"operationId": "getPetById",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"name": "petId",
"in": "path",
"description": "ID of pet to return",
"required": true,
"type": "integer",
"format": "int64"
}],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"$ref": "#/definitions/Pet"
}
},
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Pet not found"
}
},
"security": [{
"api_key": [
]
}]
},
"post": {
"tags": [
"pet"
],
"summary": "Updates a pet in the store with form data",
"description": "",
"operationId": "updatePetWithForm",
"consumes": [
"application/x-www-form-urlencoded"
],
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"name": "petId",
"in": "path",
"description": "ID of pet that needs to be updated",
"required": true,
"type": "integer",
"format": "int64"
},
{
"name": "name",
"in": "formData",
"description": "Updated name of the pet",
"required": false,
"type": "string"
},
{
"name": "status",
"in": "formData",
"description": "Updated status of the pet",
"required": false,
"type": "string"
}
],
"responses": {
"405": {
"description": "Invalid input"
}
},
"security": [{
"petstore_auth": [
"write:pets",
"read:pets"
]
}]
},
"delete": {
"tags": [
"pet"
],
"summary": "Deletes a pet",
"description": "",
"operationId": "deletePet",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"name": "api_key",
"in": "header",
"required": false,
"type": "string"
},
{
"name": "petId",
"in": "path",
"description": "Pet id to delete",
"required": true,
"type": "integer",
"format": "int64"
}
],
"responses": {
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Pet not found"
}
},
"security": [{
"petstore_auth": [
"write:pets",
"read:pets"
]
}]
}
},
"/pet/{petId}/uploadImage": {
"post": {
"tags": [
"pet"
],
"summary": "uploads an image",
"description": "",
"operationId": "uploadFile",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"parameters": [{
"name": "petId",
"in": "path",
"description": "ID of pet to update",
"required": true,
"type": "integer",
"format": "int64"
},
{
"name": "additionalMetadata",
"in": "formData",
"description": "Additional data to pass to server",
"required": false,
"type": "string"
},
{
"name": "file",
"in": "formData",
"description": "file to upload",
"required": false,
"type": "file"
}
],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"$ref": "#/definitions/ApiResponse"
}
}
},
"security": [{
"petstore_auth": [
"write:pets",
"read:pets"
]
}]
}
},
"/store/inventory": {
"get": {
"tags": [
"store"
],
"summary": "Returns pet inventories by status",
"description": "Returns a map of status codes to quantities",
"operationId": "getInventory",
"produces": [
"application/json"
],
"parameters": [
],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"type": "object",
"additionalProperties": {
"type": "integer",
"format": "int32"
}
}
}
},
"security": [{
"api_key": [
]
}]
}
},
"/store/order": {
"post": {
"tags": [
"store"
],
"summary": "Place an order for a pet",
"description": "",
"operationId": "placeOrder",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"in": "body",
"name": "body",
"description": "order placed for purchasing the pet",
"required": true,
"schema": {
"$ref": "#/definitions/Order"
}
}],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"$ref": "#/definitions/Order"
}
},
"400": {
"description": "Invalid Order"
}
}
}
},
"/store/order/{orderId}": {
"get": {
"tags": [
"store"
],
"summary": "Find purchase order by ID",
"description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions",
"operationId": "getOrderById",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"name": "orderId",
"in": "path",
"description": "ID of pet that needs to be fetched",
"required": true,
"type": "integer",
"maximum": 10.0,
"minimum": 1.0,
"format": "int64"
}],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"$ref": "#/definitions/Order"
}
},
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Order not found"
}
}
},
"delete": {
"tags": [
"store"
],
"summary": "Delete purchase order by ID",
"description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors",
"operationId": "deleteOrder",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"name": "orderId",
"in": "path",
"description": "ID of the order that needs to be deleted",
"required": true,
"type": "integer",
"minimum": 1.0,
"format": "int64"
}],
"responses": {
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Order not found"
}
}
}
},
"/user": {
"post": {
"tags": [
"user"
],
"summary": "Create user",
"description": "This can only be done by the logged in user.",
"operationId": "createUser",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"in": "body",
"name": "body",
"description": "Created user object",
"required": true,
"schema": {
"$ref": "#/definitions/User"
}
}],
"responses": {
"default": {
"description": "successful operation"
}
}
}
},
"/user/createWithArray": {
"post": {
"tags": [
"user"
],
"summary": "Creates list of users with given input array",
"description": "",
"operationId": "createUsersWithArrayInput",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"in": "body",
"name": "body",
"description": "List of user object",
"required": true,
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/User"
}
}
}],
"responses": {
"default": {
"description": "successful operation"
}
}
}
},
"/user/createWithList": {
"post": {
"tags": [
"user"
],
"summary": "Creates list of users with given input array",
"description": "",
"operationId": "createUsersWithListInput",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"in": "body",
"name": "body",
"description": "List of user object",
"required": true,
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/User"
}
}
}],
"responses": {
"default": {
"description": "successful operation"
}
}
}
},
"/user/login": {
"get": {
"tags": [
"user"
],
"summary": "Logs user into the system",
"description": "",
"operationId": "loginUser",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"name": "username",
"in": "query",
"description": "The user name for login",
"required": true,
"type": "string"
},
{
"name": "password",
"in": "query",
"description": "The password for login in clear text",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"type": "string"
},
"headers": {
"X-Rate-Limit": {
"type": "integer",
"format": "int32",
"description": "calls per hour allowed by the user"
},
"X-Expires-After": {
"type": "string",
"format": "date-time",
"description": "date in UTC when token expires"
}
}
},
"400": {
"description": "Invalid username/password supplied"
}
}
}
},
"/user/logout": {
"get": {
"tags": [
"user"
],
"summary": "Logs out current logged in user session",
"description": "",
"operationId": "logoutUser",
"produces": [
"application/xml",
"application/json"
],
"parameters": [
],
"responses": {
"default": {
"description": "successful operation"
}
}
}
},
"/user/{username}": {
"get": {
"tags": [
"user"
],
"summary": "Get user by user name",
"description": "",
"operationId": "getUserByName",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"name": "username",
"in": "path",
"description": "The name that needs to be fetched. Use user1 for testing. ",
"required": true,
"type": "string"
}],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"$ref": "#/definitions/User"
}
},
"400": {
"description": "Invalid username supplied"
},
"404": {
"description": "User not found"
}
}
},
"put": {
"tags": [
"user"
],
"summary": "Updated user",
"description": "This can only be done by the logged in user.",
"operationId": "updateUser",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"name": "username",
"in": "path",
"description": "name that need to be updated",
"required": true,
"type": "string"
},
{
"in": "body",
"name": "body",
"description": "Updated user object",
"required": true,
"schema": {
"$ref": "#/definitions/User"
}
}
],
"responses": {
"400": {
"description": "Invalid user supplied"
},
"404": {
"description": "User not found"
}
}
},
"delete": {
"tags": [
"user"
],
"summary": "Delete user",
"description": "This can only be done by the logged in user.",
"operationId": "deleteUser",
"produces": [
"application/xml",
"application/json"
],
"parameters": [{
"name": "username",
"in": "path",
"description": "The name that needs to be deleted",
"required": true,
"type": "string"
}],
"responses": {
"400": {
"description": "Invalid username supplied"
},
"404": {
"description": "User not found"
}
}
}
}
},
"securityDefinitions": {
"petstore_auth": {
"type": "oauth2",
"authorizationUrl": "http://petstore.swagger.io/oauth/dialog",
"flow": "implicit",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "header"
}
},
"definitions": {
"Order": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"petId": {
"type": "integer",
"format": "int64"
},
"quantity": {
"type": "integer",
"format": "int32"
},
"shipDate": {
"type": "string",
"format": "date-time"
},
"status": {
"type": "string",
"description": "Order Status",
"enum": [
"placed",
"approved",
"delivered"
]
},
"complete": {
"type": "boolean",
"default": false
}
},
"xml": {
"name": "Order"
}
},
"User": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"username": {
"type": "string"
},
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"email": {
"type": "string"
},
"password": {
"type": "string"
},
"phone": {
"type": "string"
},
"userStatus": {
"type": "integer",
"format": "int32",
"description": "User Status"
}
},
"xml": {
"name": "User"
}
},
"Category": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
}
},
"xml": {
"name": "Category"
}
},
"Tag": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
}
},
"xml": {
"name": "Tag"
}
},
"Pet": {
"type": "object",
"required": [
"name",
"photoUrls"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"category": {
"$ref": "#/definitions/Category"
},
"name": {
"type": "string",
"example": "doggie"
},
"photoUrls": {
"type": "array",
"xml": {
"name": "photoUrl",
"wrapped": true
},
"items": {
"type": "string"
}
},
"tags": {
"type": "array",
"xml": {
"name": "tag",
"wrapped": true
},
"items": {
"$ref": "#/definitions/Tag"
}
},
"status": {
"type": "string",
"description": "pet status in the store",
"enum": [
"available",
"pending",
"sold"
]
}
},
"xml": {
"name": "Pet"
}
},
"ApiResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"type": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
},
"externalDocs": {
"description": "Find out more about Swagger",
"url": "http://swagger.io"
}
}

View File

@ -8,6 +8,8 @@ import { AppStore } from '../../src/services/AppStore';
import { RedocRawOptions } from '../../src/services/RedocNormalizedOptions';
import { loadAndBundleSpec } from '../../src/utils/loadAndBundleSpec';
const Ajv = require('ajv');
const renderRoot = (props: RedocProps) =>
render(
<AppContainer>
@ -22,6 +24,7 @@ const swagger = window.location.search.indexOf('swagger') > -1; // compatibility
let specUrl = swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml';
specUrl = 'intent.json';
//specUrl = 'swagger.yaml';
//specUrl = 'petstore.json';
let store;
const options: RedocRawOptions = { nativeScrollbars: false };
@ -29,6 +32,15 @@ const options: RedocRawOptions = { nativeScrollbars: false };
async function init() {
const spec = await loadAndBundleSpec(specUrl);
store = new AppStore(spec, specUrl, options);
const ajv = new Ajv({ allErrors: true, unknownFormats: ['int32', 'UUID', 'int64'] });
ajv.addSchema(spec, 'specs.json')
const ajvError = require('ajv-errors')(ajv);
window.ajv = ajv;
window.ajvError = ajvError;
renderRoot({ store });
}

View File

@ -71,6 +71,8 @@
"@types/webpack": "^4.1.2",
"@types/webpack-env": "^1.13.0",
"@types/yargs": "^11.0.0",
"ajv": "^6.4.0",
"ajv-errors": "^1.0.0",
"babel-loader": "8.0.0-beta.2",
"babel-plugin-styled-components": "^1.5.1",
"beautify-benchmark": "^0.2.4",

View File

@ -7,6 +7,7 @@ export const Button = styled.button`
border: none;
color: white;
font-size: 0.929em;
padding: 5px;
`;
export const SendButton = Button.extend`

View File

@ -44,3 +44,8 @@ export const FlexLayout = styled.div`
display: flex;
width: 100%;
`;
export const ConsoleActionsRow = FlexLayout.extend`
padding: 5px 0px;
`;

View File

@ -23,6 +23,24 @@ export interface ConsoleEditorProps {
@observer
export class ConsoleEditor extends React.Component<ConsoleEditorProps> {
public editor: any;
/*
get aceEditor(): AceEditor {
return this._aceEditor;
}
set aceEditor(aceEditor: AceEditor) {
if (aceEditor) {
this.aceEditor = this.aceEditor
}
else {
console.log("Error: Undefined ace editor!");
}
}
*/
render() {
const { mediaTypes } = this.props;
@ -59,6 +77,7 @@ export class ConsoleEditor extends React.Component<ConsoleEditorProps> {
name="request-builder-editor"
editorProps={{ $blockScrolling: true }}
value={JSON.stringify(sample, null, 2)}
ref={(ace: AceEditor) => (this.editor = ace)}
width="100%"
height="400px"
/>

View File

@ -4,7 +4,7 @@ import { OperationModel } from '../../services/models';
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
import { SendButton } from '../../common-elements/buttons';
import { FlexLayout } from '../../common-elements/panels';
import { ConsoleActionsRow } from '../../common-elements/panels';
import { ConsoleEditor } from './ConsoleEditor';
export interface ConsoleViewerProps {
@ -13,9 +13,32 @@ export interface ConsoleViewerProps {
@observer
export class ConsoleViewer extends React.Component<ConsoleViewerProps> {
private consoleEditor: ConsoleEditor;
operation: OperationModel;
visited = new Set();
onClickSend = () => {
const ace = this.consoleEditor && this.consoleEditor.editor;
//const value = ace && ace.editor &&
const schema = this.getSchema();
//console.log('Schema: ' + JSON.stringify(schema, null, 2));
const value = ace && ace.editor.getValue();
const ref = schema && schema['_$ref'];
var valid = window && window.ajv.validate({ $ref: `specs.json${ref}` }, value);
console.log(JSON.stringify(window.ajv.errors));
if (!valid) {
console.warn('INVALID REQUEST!');
}
console.log('Value: ' + value);
};
render() {
const { operation } = this.props;
const requestBodyContent = operation.requestBody && operation.requestBody.content && operation.requestBody.content;
@ -27,15 +50,43 @@ export class ConsoleViewer extends React.Component<ConsoleViewerProps> {
<div>
<h3> Console </h3>
{hasBodySample && (
<ConsoleEditor mediaTypes={mediaTypes} />
<ConsoleEditor
mediaTypes={mediaTypes}
ref={(editor: ConsoleEditor) => (this.consoleEditor = editor)} />
)}
{samples.map(sample => (
{false && samples.map(sample => (
<SourceCodeWithCopy lang={sample.lang} source={sample.source} />
))}
<FlexLayout>
<SendButton>Send Request</SendButton>
</FlexLayout>
<ConsoleActionsRow>
<SendButton onClick={this.onClickSend} >Send Request</SendButton>
</ConsoleActionsRow>
</div>
);
}
getSchema() {
const { operation } = this.props;
const requestBodyContent = operation.requestBody && operation.requestBody.content && operation.requestBody.content;
const mediaTypes = (requestBodyContent && requestBodyContent.mediaTypes) ? requestBodyContent.mediaTypes : [];
if (!mediaTypes.length) {
return null;
}
let schema = {};
for (let mediaType of mediaTypes) {
if (mediaType.name.indexOf('json') > -1) {
if (mediaType.schema) {
//schema = mediaType.schema;
schema = mediaType.schema && mediaType.schema.rawSchema;
console.log('rawSchema : ' + JSON.stringify(schema));
console.log('schema : ' + JSON.stringify(mediaType.schema.schema));
schema['_$ref'] = mediaType.schema && mediaType.schema['_$ref']
}
break;
}
}
return schema;
}
}

View File

@ -54,12 +54,12 @@ export class Operation extends React.Component<OperationProps, OperationState> {
};
}
onTry(e) {
onTry = (e) => {
this.setState({
executeMode: e.target.checked
});
console.log(e.target.checked + ' ' + this.props.operation);
}
};
/*
activate = (item: IMenuItem) => {
@ -86,7 +86,7 @@ export class Operation extends React.Component<OperationProps, OperationState> {
<ShareLink href={'#' + operation.id} />
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
</H2>
<Toggle type="checkbox" onChange={this.onTry.bind(this)} />
<Toggle type="checkbox" onChange={this.onTry} />
<span>Try it out!</span>
{options.pathInMiddlePanel && <Endpoint operation={operation} inverted={true} />}
{description !== undefined && <Markdown source={description} />}

View File

@ -16,11 +16,11 @@ export async function loadAndBundleSpec(specUrlOrObject: object | string): Promi
// we can derefrence the schema here for future use.
// const derefrencedSpec = await parser.dereference(cloneDeep(spec));
const derefed = await parser.dereference(v2Specs, {
resolve: { http: { withCredentials: false } },
} as object);
//const derefed = await parser.dereference(v2Specs, {
// resolve: { http: { withCredentials: false } },
//} as object);
return derefed;
return v2Specs;
}

View File

@ -391,6 +391,10 @@ agent-base@^4.1.0:
dependencies:
es6-promisify "^5.0.0"
ajv-errors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59"
ajv-keywords@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
@ -423,6 +427,15 @@ ajv@^6.1.0:
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
ajv@^6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.4.0.tgz#d3aff78e9277549771daf0164cff48482b754fc6"
dependencies:
fast-deep-equal "^1.0.0"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
uri-js "^3.0.2"
align-text@^0.1.1, align-text@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
@ -9480,6 +9493,12 @@ upper-case@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
uri-js@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-3.0.2.tgz#f90b858507f81dea4dcfbb3c4c3dbfa2b557faaa"
dependencies:
punycode "^2.1.0"
urix@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"