├── .gitignore ├── LICENSE ├── README.md └── src ├── Contract Test Environment.postman_environment.json └── Contract Test Generator.postman_collection.json /.gitignore: -------------------------------------------------------------------------------- 1 | .history 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Allen Helton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Postman Contract Test Generator 2 | When building APIs, a common need is to validate the shape of the requests and responses. You want to verify the implementation of the API matches the definition document. This is typically done through *contract tests*, which exercise required fields, optional fields, and things like query and path parameters. 3 | 4 | [Postman](https://www.postman.com/) has the ability to group requests together and associate them to your API as a contract test. The intention of this feature is to build a [collection](https://www.postman.com/collection/) manually to add coverage to your API. 5 | 6 | # Objective 7 | To build an automated way to provide an exhaustive set of tests providing close to 100% coverage for contract tests. The **contract test generator** should not need to be maintained, it should be able to dynamically create all tests with every run based on the Open API Specification (OAS) document defining the API. 8 | 9 | The generator tests the **implementation** of a given API -- not the definition. It should use the definition document as a ruleset and compare the responses of the generated requests to it. 10 | 11 | # What It Does 12 | 13 | ## Execution 14 | Every run, the generator will perform the following actions: 15 | 16 | * Load the OAS for the provided API/workspace 17 | * Validate the definition is in the proper format 18 | * All parameters, schemas, and responses have `description` and `example` provided for every field 19 | * `Servers` are defined 20 | * User-defined governance settings configured in the *enviroment* 21 | * Build schema test array 22 | * Loop through every test in the array and submit the request to the API 23 | * Validate status code and response body schema against OAS 24 | 25 | ## Schema Test Definition 26 | For every path defined in the Open API Spec, a `json` object will be created in the following format to define the schema test: 27 | 28 | ``` 29 | { 30 | "path": "", // Combines url from server and path 31 | "parameters": [ ], // All parameters defined at the path 32 | "method": "", // Path method 33 | "allowedRole": "", // If using role based apps, grabs the first allowable role 34 | "responses": [ // All expected responses for the path 35 | { 36 | "statusCode": 200, // Status code defined in responses array 37 | "$ref": "#/components/responses/Ok" // Grabs either referenced response or inline defined schema 38 | } 39 | ], 40 | "success": true, // Boolean for if this test is expected to be a success 41 | "description": "Has all required fields", // Generated description for what is being tested 42 | "body": { }, // Generated body in JSON 43 | "name": "" // Generated request name with method, path, description, and success 44 | } 45 | ``` 46 | 47 | ## Test Generation 48 | The generator will build request bodies for each endpoint using the **example** provided for every property. If there is no example provided for an individual property, it will not be included in the request. Examples will be used for all parameters and schema properties. 49 | 50 | For accurate tests, it is advised *to use real values from your test environment in your examples* in order to get valid responses back during test execution. This means using known, hardcoded or seeded values in the system to define your API in the OAS. 51 | 52 | The request body will be generated with the required fields only. The generator will take the request body with all required fields and create an array of mutations against it. For each required field in the request body, two mutations will be created: 53 | 54 | * Omit the required field 55 | * Leave the required field blank 56 | 57 | Once all the mutations have been created, the generator proceeds to execute the tests. 58 | 59 | ### Example 60 | Let's take an example API that maintains cars. The car schema as defined in the OAS would be: 61 | 62 | ```yaml 63 | components: 64 | schemas: 65 | Car: 66 | type: object 67 | required: 68 | - make 69 | - model 70 | - year 71 | properties: 72 | make: 73 | type: string 74 | example: Nissan 75 | model: 76 | type: string 77 | example: Pathfinder 78 | year: 79 | type: number 80 | example: 2015 81 | color: 82 | type: string 83 | example: red 84 | ``` 85 | 86 | The generator will create the following request body for the schema: 87 | 88 | ```json 89 | { 90 | "make": "Nissan", 91 | "model": "Pathfinder", 92 | "year": 2015 93 | } 94 | ``` 95 | 96 | It will also make mutations for every property: 97 | ```json 98 | { 99 | "model": "Pathfinder", 100 | "year": 2015 101 | } 102 | ``` 103 | and 104 | 105 | ```json 106 | { 107 | "make": "", 108 | "model": "Pathfinder", 109 | "year": 2015 110 | } 111 | ``` 112 | 113 | The generator is building these tests to verify the implementation of the API matches what is defined in the OAS. It will check to see if a valid http status code (400) is given if a missing required field is submitted. 114 | 115 | 116 | # What It Does NOT Do 117 | The generator will **not** build a collection to be run at a later date. It builds and executes the tests at runtime, allowing for the test to dynamically update as changes are made to the Open API Spec. 118 | 119 | # Requirements 120 | The Open API Spec tested by the contract test generator is required to be in a specific format. Below are the minimum requirements for the tests to execute: 121 | 122 | * The `Servers` object is defined and has at least one server with a description 123 | * Every schema property must have an example defined 124 | * The following fields in the provided environment are configured 125 | * `env-apiKey` - Integration API Key for Postman 126 | * `env-workspaceId` - Identifier for the workspace that contains the API to be tested 127 | * `env-requireParamExample` - Must be set to true (the collection will still run, but this will show you where any problems lie) 128 | * `env-server` - Matches the description of the server to be tested in the `Servers` object 129 | * Request bodies are defined in the `#/components/schemas` section of the OAS and are referred to by using `$ref` 130 | 131 | # Authentication 132 | Authentication is the only piece of the generator that needs to be handled by the consumer. It is set up assuming all requests that execute against your API use the same authentication method. 133 | 134 | ## Configuration 135 | If your API uses authentication, you will be required to configure it on the collection itself. 136 | 137 | 1. Right click the collection in your Postman workspace and select **Edit**. 138 | 2. Click on the **Authorization** tab and configure the type of Authentication your API requires. 139 | 3. If using OAuth2.0, you may [reference my blog post](https://www.readysetcloud.io/blog/allen.helton/how-to-automate-oauth2-token-renewal-in-postman-864420d381a0/) on how to automate the token renewal. 140 | 4. Click **Update** to save your changes 141 | 142 | **NOTE-** If your API uses a standard api key header like `x-api-key` this step is unnecessary. You may just add it in the `#/components/parameters` section and it will be included automatically in each request. 143 | ```yaml 144 | components: 145 | parameters: 146 | ApiKey: 147 | name: x-api-key 148 | in: header 149 | example: 982345jsdw0971ls09812354 150 | schema: 151 | type: string 152 | ``` 153 | 154 | ## Setup 155 | In this repo there are two files you need to import into your [Postman](https://postman.com/) workspace: 156 | * Contract Test Generator *collection* 157 | * Contract Test Generator *Environment* 158 | 159 | If you are unsure how to import these into Postman, please [refer to this guide](https://kb.datamotion.com/?ht_kb=postman-instructions-for-exporting-and-importing). 160 | 161 | # CI Pipeline / Automation 162 | To run this as part of a CI pipeline, [Newman](https://learning.postman.com/docs/running-collections/using-newman-cli/command-line-integration-with-newman) a command line interface from Postman can be used to execute the collection. 163 | 164 | The collection runs as a single command with overridden environment variables. As many of the [environment variables](#Environment) can be overridden as you'd like, just keep adding the `--env-var` flag to the command to override variables. 165 | 166 | Below is an example of a completely genericized Newman execution. This command will pull from variables in the CI environment (all variables with a `$` come from CI) and substitute them in the command. 167 | 168 | ``` 169 | newman run https://api.getpostman.com/collections/$POSTMAN_TEST_GENERATION_COLLECTION_ID?apikey=$POSTMAN_API_KEY --environment https://api.getpostman.com/environments/$POSTMAN_TEST_GENERATION_ENVIRONMENT_ID?apikey=$POSTMAN_API_KEY --env-var "env-workspaceId=$POSTMAN_WORKSPACE_ID" --env-var "env-server=$POSTMAN_TEST_ENVIRONMENT" 170 | ``` 171 | 172 | This command offers maximum portability, offering the user the ability to import the collection and environment into their Postman workspace one time and reuse it for all APIs they own by simply replacing environment variables. 173 | 174 | Newman will return the appropriate exit code if any assertions fail, which will automatically cause your build pipeline to fail. 175 | 176 | # Extensions 177 | If you wish to use extensions with your Open API Spec to assist with the test generation, see details below for supported extensions: 178 | 179 | ## x-amazon-apigateway-integration 180 | If you use AWS API Gateway and practice [API-first development](https://www.readysetcloud.io/blog/allen.helton/api-first-development-with-postman/), chances are you use the [x-amazon-apigateway-integration extension](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-integration.html). This extension allows you to identify how each endpoint proxies to an AWS service. For API-first development, you are able to to mock out the proxy and response for endpoints that are not implemented yet. 181 | 182 | When using this extension, you can add the following snippet to tell API Gateway to mock out the response. 183 | ```yaml 184 | x-amazon-apigateway-integration: 185 | responses: 186 | 200: 187 | statusCode: 200 188 | passthroughBehavior: when_no_match 189 | requestTemplates: 190 | application/json: | 191 | { 192 | 'statusCode': 200 193 | } 194 | type: mock 195 | ``` 196 | 197 | The generator will skip over any endpoint methods that have `type: mock` defined in this extension. No tests will be run for mocked endpoints. 198 | 199 | ## x-postman-variables 200 | Instead of relying completely on seeded, or known, data in the system for the generated tests, this extension allows you to use objects created in the generated tests in subsequent tests. For example, if you have an endpoint that creates a `book` object by doing a **POST** to `/books`, this extension will allow you to save the returned id as a collection variable and use it in other API calls, like doing a **GET** on `/books/{bookId}`. 201 | 202 | **Saving a variable** 203 | 204 | In the `responses` section of an endpoint method you can add the following snippet to save a collection variable. 205 | ```yaml 206 | responses: 207 | 201: 208 | $ref: `#/components/responses/Created` 209 | x-postman-variables: 210 | - type: save 211 | name: bookId 212 | path: .id 213 | ``` 214 | 215 | `type` - Must be *save* in order to save the value to a collection variable 216 | `name` - What to name the collection variable 217 | `path` - Json-path to the property you want. Currently does not support arrays. It must start with a '.' 218 | 219 | The extension is an array, so you can add as many saves as you'd like. 220 | 221 | **Consuming a variable** 222 | 223 | The extension must be added to a parameter for consumption. This could be a header, query param, or path param. It works with both inline and ref parameters. 224 | ```yaml 225 | parameters: 226 | bookId: 227 | name: bookId 228 | in: path 229 | description: Unique identifier for the book 230 | required: true 231 | schema: 232 | type: string 233 | example: 23SovYJfRZ5Wt7jpZEPHVo 234 | x-postman-variables: 235 | - type: load 236 | name: bookId 237 | ``` 238 | 239 | Whenever this parameter is used, the test generator will load the value from the collection variable. If the collection variable does not exist or has no value, it will fall back to the provided example. 240 | 241 | # Environment 242 | Below is a list of environment variables currently consumed by the generator: 243 | 244 | * `env-apiKey` - Your Postman API key used to access the Postman API 245 | * `env-minApiCount` - The minimum amount of APIs required in a provided workspace 246 | * `env-maxApiCount` - The maximum amount of APIs required in a provided workspace 247 | * `env-workspaceId` - The identifier of the workspace you wish to generate tests for 248 | * `env-requireParamDescription` - Flag denoting if assertions should be run that require a parameter description - **BOOLEAN** 249 | * `env-requireParamExample` - Flag denoting if assertions should be run that require a parameter example - **BOOLEAN** 250 | * `env-paramDescriptionMinLength` - The minimum length a description should be. This is only used if `env-requireParamDescription` is true 251 | * `env-paramDesciptionMaxLength` - The maximum length a description should be. This is only used if `env-requireParamDescription` is true 252 | * `env-securityExtensionName` - If using a role-based API, the name of the extension you use to denote allowed roles on an endpoint 253 | * `env-roleHeaderName` - If using a role-based API, the name of the header where you supply the user's assumed role 254 | * `env-server` - The description of which `server` element to use. This is for the base url of your API. [See OAS Documentation](https://swagger.io/docs/specification/api-host-and-base-path/) 255 | * `env-runComponentTests` - Flag denoting if assertions should be run validating schema adherence - **BOOLEAN** 256 | * `env-runContractTests` - Flag denoting if contract tests should be generated and run - **BOOLEAN** 257 | * `env-schemaPropertyExceptions` - The names of any properties that should not be validated in the component tests - **ARRAY OF STRINGS** 258 | * `env-jsonToYaml` - NPM Package that converts yaml to json. DO NOT EDIT! 259 | 260 | # Contact 261 | You may contact me by any of the social media channels below: 262 | 263 | [![Twitter][1.1]][1] [![GitHub][2.1]][2] [![LinkedIn][3.1]][3] [![Ready, Set, Cloud!][4.1]][4] 264 | 265 | [1.1]: http://i.imgur.com/tXSoThF.png 266 | [2.1]: http://i.imgur.com/0o48UoR.png 267 | [3.1]: http://i.imgur.com/lGwB1Hk.png 268 | [4.1]: https://readysetcloud.s3.amazonaws.com/logo.png 269 | 270 | [1]: http://www.twitter.com/allenheltondev 271 | [2]: http://www.github.com/allenheltondev 272 | [3]: https://www.linkedin.com/in/allen-helton-85aa9650/ 273 | [4]: https://readysetcloud.io 274 | -------------------------------------------------------------------------------- /src/Contract Test Environment.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "8e6bbbb2-87bd-40ac-a414-2fbf68e8736d", 3 | "name": "Contract Test Environment", 4 | "values": [ 5 | { 6 | "key": "env-apiKey", 7 | "value": "", 8 | "type": "secret", 9 | "enabled": true 10 | }, 11 | { 12 | "key": "env-minApiCount", 13 | "value": "1", 14 | "enabled": true 15 | }, 16 | { 17 | "key": "env-maxApiCount", 18 | "value": "1", 19 | "enabled": true 20 | }, 21 | { 22 | "key": "env-workspaceId", 23 | "value": "", 24 | "enabled": true 25 | }, 26 | { 27 | "key": "env-requireParamDescription", 28 | "value": "true", 29 | "enabled": true 30 | }, 31 | { 32 | "key": "env-requireParamExample", 33 | "value": "true", 34 | "enabled": true 35 | }, 36 | { 37 | "key": "env-paramDescriptionMinLength", 38 | "value": "10", 39 | "enabled": true 40 | }, 41 | { 42 | "key": "env-paramDesciptionMaxLength", 43 | "value": "100", 44 | "enabled": true 45 | }, 46 | { 47 | "key": "env-securityExtensionName", 48 | "value": "", 49 | "enabled": true 50 | }, 51 | { 52 | "key": "env-roleHeaderName", 53 | "value": "Role", 54 | "enabled": true 55 | }, 56 | { 57 | "key": "env-server", 58 | "value": "", 59 | "enabled": true 60 | }, 61 | { 62 | "key": "env-runComponentTests", 63 | "value": "true", 64 | "enabled": true 65 | }, 66 | { 67 | "key": "env-runContractTests", 68 | "value": "true", 69 | "enabled": true 70 | }, 71 | { 72 | "key": "env-schemaPropertyExceptions", 73 | "value": "[]", 74 | "enabled": true 75 | }, 76 | { 77 | "key": "env-jsonToYaml", 78 | "value": "/* js-yaml 3.13.1 https://github.com/nodeca/js-yaml */(function(f){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=f()}else if(typeof define===\"function\"&&define.amd){define([],f)}else{var g;if(typeof window!==\"undefined\"){g=window}else if(typeof global!==\"undefined\"){g=global}else if(typeof self!==\"undefined\"){g=self}else{g=this}g.jsyaml = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c=\"function\"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error(\"Cannot find module '\"+i+\"'\");throw a.code=\"MODULE_NOT_FOUND\",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u=\"function\"==typeof require&&require,i=0;i */\nvar CHAR_QUESTION = 0x3F; /* ? */\nvar CHAR_COMMERCIAL_AT = 0x40; /* @ */\nvar CHAR_LEFT_SQUARE_BRACKET = 0x5B; /* [ */\nvar CHAR_RIGHT_SQUARE_BRACKET = 0x5D; /* ] */\nvar CHAR_GRAVE_ACCENT = 0x60; /* ` */\nvar CHAR_LEFT_CURLY_BRACKET = 0x7B; /* { */\nvar CHAR_VERTICAL_LINE = 0x7C; /* | */\nvar CHAR_RIGHT_CURLY_BRACKET = 0x7D; /* } */\n\nvar ESCAPE_SEQUENCES = {};\n\nESCAPE_SEQUENCES[0x00] = '\\\\0';\nESCAPE_SEQUENCES[0x07] = '\\\\a';\nESCAPE_SEQUENCES[0x08] = '\\\\b';\nESCAPE_SEQUENCES[0x09] = '\\\\t';\nESCAPE_SEQUENCES[0x0A] = '\\\\n';\nESCAPE_SEQUENCES[0x0B] = '\\\\v';\nESCAPE_SEQUENCES[0x0C] = '\\\\f';\nESCAPE_SEQUENCES[0x0D] = '\\\\r';\nESCAPE_SEQUENCES[0x1B] = '\\\\e';\nESCAPE_SEQUENCES[0x22] = '\\\\\"';\nESCAPE_SEQUENCES[0x5C] = '\\\\\\\\';\nESCAPE_SEQUENCES[0x85] = '\\\\N';\nESCAPE_SEQUENCES[0xA0] = '\\\\_';\nESCAPE_SEQUENCES[0x2028] = '\\\\L';\nESCAPE_SEQUENCES[0x2029] = '\\\\P';\n\nvar DEPRECATED_BOOLEANS_SYNTAX = [\n 'y', 'Y', 'yes', 'Yes', 'YES', 'on', 'On', 'ON',\n 'n', 'N', 'no', 'No', 'NO', 'off', 'Off', 'OFF'\n];\n\nfunction compileStyleMap(schema, map) {\n var result, keys, index, length, tag, style, type;\n\n if (map === null) return {};\n\n result = {};\n keys = Object.keys(map);\n\n for (index = 0, length = keys.length; index < length; index += 1) {\n tag = keys[index];\n style = String(map[tag]);\n\n if (tag.slice(0, 2) === '!!') {\n tag = 'tag:yaml.org,2002:' + tag.slice(2);\n }\n type = schema.compiledTypeMap['fallback'][tag];\n\n if (type && _hasOwnProperty.call(type.styleAliases, style)) {\n style = type.styleAliases[style];\n }\n\n result[tag] = style;\n }\n\n return result;\n}\n\nfunction encodeHex(character) {\n var string, handle, length;\n\n string = character.toString(16).toUpperCase();\n\n if (character <= 0xFF) {\n handle = 'x';\n length = 2;\n } else if (character <= 0xFFFF) {\n handle = 'u';\n length = 4;\n } else if (character <= 0xFFFFFFFF) {\n handle = 'U';\n length = 8;\n } else {\n throw new YAMLException('code point within a string may not be greater than 0xFFFFFFFF');\n }\n\n return '\\\\' + handle + common.repeat('0', length - string.length) + string;\n}\n\nfunction State(options) {\n this.schema = options['schema'] || DEFAULT_FULL_SCHEMA;\n this.indent = Math.max(1, (options['indent'] || 2));\n this.noArrayIndent = options['noArrayIndent'] || false;\n this.skipInvalid = options['skipInvalid'] || false;\n this.flowLevel = (common.isNothing(options['flowLevel']) ? -1 : options['flowLevel']);\n this.styleMap = compileStyleMap(this.schema, options['styles'] || null);\n this.sortKeys = options['sortKeys'] || false;\n this.lineWidth = options['lineWidth'] || 80;\n this.noRefs = options['noRefs'] || false;\n this.noCompatMode = options['noCompatMode'] || false;\n this.condenseFlow = options['condenseFlow'] || false;\n\n this.implicitTypes = this.schema.compiledImplicit;\n this.explicitTypes = this.schema.compiledExplicit;\n\n this.tag = null;\n this.result = '';\n\n this.duplicates = [];\n this.usedDuplicates = null;\n}\n\n// Indents every line in a string. Empty lines (\\n only) are not indented.\nfunction indentString(string, spaces) {\n var ind = common.repeat(' ', spaces),\n position = 0,\n next = -1,\n result = '',\n line,\n length = string.length;\n\n while (position < length) {\n next = string.indexOf('\\n', position);\n if (next === -1) {\n line = string.slice(position);\n position = length;\n } else {\n line = string.slice(position, next + 1);\n position = next + 1;\n }\n\n if (line.length && line !== '\\n') result += ind;\n\n result += line;\n }\n\n return result;\n}\n\nfunction generateNextLine(state, level) {\n return '\\n' + common.repeat(' ', state.indent * level);\n}\n\nfunction testImplicitResolving(state, str) {\n var index, length, type;\n\n for (index = 0, length = state.implicitTypes.length; index < length; index += 1) {\n type = state.implicitTypes[index];\n\n if (type.resolve(str)) {\n return true;\n }\n }\n\n return false;\n}\n\n// [33] s-white ::= s-space | s-tab\nfunction isWhitespace(c) {\n return c === CHAR_SPACE || c === CHAR_TAB;\n}\n\n// Returns true if the character can be printed without escaping.\n// From YAML 1.2: \"any allowed characters known to be non-printable\n// should also be escaped. [However,] This isn’t mandatory\"\n// Derived from nb-char - \\t - #x85 - #xA0 - #x2028 - #x2029.\nfunction isPrintable(c) {\n return (0x00020 <= c && c <= 0x00007E)\n || ((0x000A1 <= c && c <= 0x00D7FF) && c !== 0x2028 && c !== 0x2029)\n || ((0x0E000 <= c && c <= 0x00FFFD) && c !== 0xFEFF /* BOM */)\n || (0x10000 <= c && c <= 0x10FFFF);\n}\n\n// Simplified test for values allowed after the first character in plain style.\nfunction isPlainSafe(c) {\n // Uses a subset of nb-char - c-flow-indicator - \":\" - \"#\"\n // where nb-char ::= c-printable - b-char - c-byte-order-mark.\n return isPrintable(c) && c !== 0xFEFF\n // - c-flow-indicator\n && c !== CHAR_COMMA\n && c !== CHAR_LEFT_SQUARE_BRACKET\n && c !== CHAR_RIGHT_SQUARE_BRACKET\n && c !== CHAR_LEFT_CURLY_BRACKET\n && c !== CHAR_RIGHT_CURLY_BRACKET\n // - \":\" - \"#\"\n && c !== CHAR_COLON\n && c !== CHAR_SHARP;\n}\n\n// Simplified test for values allowed as the first character in plain style.\nfunction isPlainSafeFirst(c) {\n // Uses a subset of ns-char - c-indicator\n // where ns-char = nb-char - s-white.\n return isPrintable(c) && c !== 0xFEFF\n && !isWhitespace(c) // - s-white\n // - (c-indicator ::=\n // “-” | “?” | “:” | “,” | “[” | “]” | “{” | “}”\n && c !== CHAR_MINUS\n && c !== CHAR_QUESTION\n && c !== CHAR_COLON\n && c !== CHAR_COMMA\n && c !== CHAR_LEFT_SQUARE_BRACKET\n && c !== CHAR_RIGHT_SQUARE_BRACKET\n && c !== CHAR_LEFT_CURLY_BRACKET\n && c !== CHAR_RIGHT_CURLY_BRACKET\n // | “#” | “&” | “*” | “!” | “|” | “>” | “'” | “\"”\n && c !== CHAR_SHARP\n && c !== CHAR_AMPERSAND\n && c !== CHAR_ASTERISK\n && c !== CHAR_EXCLAMATION\n && c !== CHAR_VERTICAL_LINE\n && c !== CHAR_GREATER_THAN\n && c !== CHAR_SINGLE_QUOTE\n && c !== CHAR_DOUBLE_QUOTE\n // | “%” | “@” | “`”)\n && c !== CHAR_PERCENT\n && c !== CHAR_COMMERCIAL_AT\n && c !== CHAR_GRAVE_ACCENT;\n}\n\n// Determines whether block indentation indicator is required.\nfunction needIndentIndicator(string) {\n var leadingSpaceRe = /^\\n* /;\n return leadingSpaceRe.test(string);\n}\n\nvar STYLE_PLAIN = 1,\n STYLE_SINGLE = 2,\n STYLE_LITERAL = 3,\n STYLE_FOLDED = 4,\n STYLE_DOUBLE = 5;\n\n// Determines which scalar styles are possible and returns the preferred style.\n// lineWidth = -1 => no limit.\n// Pre-conditions: str.length > 0.\n// Post-conditions:\n// STYLE_PLAIN or STYLE_SINGLE => no \\n are in the string.\n// STYLE_LITERAL => no lines are suitable for folding (or lineWidth is -1).\n// STYLE_FOLDED => a line > lineWidth and can be folded (and lineWidth != -1).\nfunction chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, testAmbiguousType) {\n var i;\n var char;\n var hasLineBreak = false;\n var hasFoldableLine = false; // only checked if shouldTrackWidth\n var shouldTrackWidth = lineWidth !== -1;\n var previousLineBreak = -1; // count the first line correctly\n var plain = isPlainSafeFirst(string.charCodeAt(0))\n && !isWhitespace(string.charCodeAt(string.length - 1));\n\n if (singleLineOnly) {\n // Case: no block styles.\n // Check for disallowed characters to rule out plain and single.\n for (i = 0; i < string.length; i++) {\n char = string.charCodeAt(i);\n if (!isPrintable(char)) {\n return STYLE_DOUBLE;\n }\n plain = plain && isPlainSafe(char);\n }\n } else {\n // Case: block styles permitted.\n for (i = 0; i < string.length; i++) {\n char = string.charCodeAt(i);\n if (char === CHAR_LINE_FEED) {\n hasLineBreak = true;\n // Check if any line can be folded.\n if (shouldTrackWidth) {\n hasFoldableLine = hasFoldableLine ||\n // Foldable line = too long, and not more-indented.\n (i - previousLineBreak - 1 > lineWidth &&\n string[previousLineBreak + 1] !== ' ');\n previousLineBreak = i;\n }\n } else if (!isPrintable(char)) {\n return STYLE_DOUBLE;\n }\n plain = plain && isPlainSafe(char);\n }\n // in case the end is missing a \\n\n hasFoldableLine = hasFoldableLine || (shouldTrackWidth &&\n (i - previousLineBreak - 1 > lineWidth &&\n string[previousLineBreak + 1] !== ' '));\n }\n // Although every style can represent \\n without escaping, prefer block styles\n // for multiline, since they're more readable and they don't add empty lines.\n // Also prefer folding a super-long line.\n if (!hasLineBreak && !hasFoldableLine) {\n // Strings interpretable as another type have to be quoted;\n // e.g. the string 'true' vs. the boolean true.\n return plain && !testAmbiguousType(string)\n ? STYLE_PLAIN : STYLE_SINGLE;\n }\n // Edge case: block indentation indicator can only have one digit.\n if (indentPerLevel > 9 && needIndentIndicator(string)) {\n return STYLE_DOUBLE;\n }\n // At this point we know block styles are valid.\n // Prefer literal style unless we want to fold.\n return hasFoldableLine ? STYLE_FOLDED : STYLE_LITERAL;\n}\n\n// Note: line breaking/folding is implemented for only the folded style.\n// NB. We drop the last trailing newline (if any) of a returned block scalar\n// since the dumper adds its own newline. This always works:\n// • No ending newline => unaffected; already using strip \"-\" chomping.\n// • Ending newline => removed then restored.\n// Importantly, this keeps the \"+\" chomp indicator from gaining an extra line.\nfunction writeScalar(state, string, level, iskey) {\n state.dump = (function () {\n if (string.length === 0) {\n return \"''\";\n }\n if (!state.noCompatMode &&\n DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1) {\n return \"'\" + string + \"'\";\n }\n\n var indent = state.indent * Math.max(1, level); // no 0-indent scalars\n // As indentation gets deeper, let the width decrease monotonically\n // to the lower bound min(state.lineWidth, 40).\n // Note that this implies\n // state.lineWidth ≤ 40 + state.indent: width is fixed at the lower bound.\n // state.lineWidth > 40 + state.indent: width decreases until the lower bound.\n // This behaves better than a constant minimum width which disallows narrower options,\n // or an indent threshold which causes the width to suddenly increase.\n var lineWidth = state.lineWidth === -1\n ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent);\n\n // Without knowing if keys are implicit/explicit, assume implicit for safety.\n var singleLineOnly = iskey\n // No block styles in flow mode.\n || (state.flowLevel > -1 && level >= state.flowLevel);\n function testAmbiguity(string) {\n return testImplicitResolving(state, string);\n }\n\n switch (chooseScalarStyle(string, singleLineOnly, state.indent, lineWidth, testAmbiguity)) {\n case STYLE_PLAIN:\n return string;\n case STYLE_SINGLE:\n return \"'\" + string.replace(/'/g, \"''\") + \"'\";\n case STYLE_LITERAL:\n return '|' + blockHeader(string, state.indent)\n + dropEndingNewline(indentString(string, indent));\n case STYLE_FOLDED:\n return '>' + blockHeader(string, state.indent)\n + dropEndingNewline(indentString(foldString(string, lineWidth), indent));\n case STYLE_DOUBLE:\n return '\"' + escapeString(string, lineWidth) + '\"';\n default:\n throw new YAMLException('impossible error: invalid scalar style');\n }\n }());\n}\n\n// Pre-conditions: string is valid for a block scalar, 1 <= indentPerLevel <= 9.\nfunction blockHeader(string, indentPerLevel) {\n var indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : '';\n\n // note the special case: the string '\\n' counts as a \"trailing\" empty line.\n var clip = string[string.length - 1] === '\\n';\n var keep = clip && (string[string.length - 2] === '\\n' || string === '\\n');\n var chomp = keep ? '+' : (clip ? '' : '-');\n\n return indentIndicator + chomp + '\\n';\n}\n\n// (See the note for writeScalar.)\nfunction dropEndingNewline(string) {\n return string[string.length - 1] === '\\n' ? string.slice(0, -1) : string;\n}\n\n// Note: a long line without a suitable break point will exceed the width limit.\n// Pre-conditions: every char in str isPrintable, str.length > 0, width > 0.\nfunction foldString(string, width) {\n // In folded style, $k$ consecutive newlines output as $k+1$ newlines—\n // unless they're before or after a more-indented line, or at the very\n // beginning or end, in which case $k$ maps to $k$.\n // Therefore, parse each chunk as newline(s) followed by a content line.\n var lineRe = /(\\n+)([^\\n]*)/g;\n\n // first line (possibly an empty line)\n var result = (function () {\n var nextLF = string.indexOf('\\n');\n nextLF = nextLF !== -1 ? nextLF : string.length;\n lineRe.lastIndex = nextLF;\n return foldLine(string.slice(0, nextLF), width);\n }());\n // If we haven't reached the first content line yet, don't add an extra \\n.\n var prevMoreIndented = string[0] === '\\n' || string[0] === ' ';\n var moreIndented;\n\n // rest of the lines\n var match;\n while ((match = lineRe.exec(string))) {\n var prefix = match[1], line = match[2];\n moreIndented = (line[0] === ' ');\n result += prefix\n + (!prevMoreIndented && !moreIndented && line !== ''\n ? '\\n' : '')\n + foldLine(line, width);\n prevMoreIndented = moreIndented;\n }\n\n return result;\n}\n\n// Greedy line breaking.\n// Picks the longest line under the limit each time,\n// otherwise settles for the shortest line over the limit.\n// NB. More-indented lines *cannot* be folded, as that would add an extra \\n.\nfunction foldLine(line, width) {\n if (line === '' || line[0] === ' ') return line;\n\n // Since a more-indented line adds a \\n, breaks can't be followed by a space.\n var breakRe = / [^ ]/g; // note: the match index will always be <= length-2.\n var match;\n // start is an inclusive index. end, curr, and next are exclusive.\n var start = 0, end, curr = 0, next = 0;\n var result = '';\n\n // Invariants: 0 <= start <= length-1.\n // 0 <= curr <= next <= max(0, length-2). curr - start <= width.\n // Inside the loop:\n // A match implies length >= 2, so curr and next are <= length-2.\n while ((match = breakRe.exec(line))) {\n next = match.index;\n // maintain invariant: curr - start <= width\n if (next - start > width) {\n end = (curr > start) ? curr : next; // derive end <= length-2\n result += '\\n' + line.slice(start, end);\n // skip the space that was output as \\n\n start = end + 1; // derive start <= length-1\n }\n curr = next;\n }\n\n // By the invariants, start <= length-1, so there is something left over.\n // It is either the whole string or a part starting from non-whitespace.\n result += '\\n';\n // Insert a break if the remainder is too long and there is a break available.\n if (line.length - start > width && curr > start) {\n result += line.slice(start, curr) + '\\n' + line.slice(curr + 1);\n } else {\n result += line.slice(start);\n }\n\n return result.slice(1); // drop extra \\n joiner\n}\n\n// Escapes a double-quoted string.\nfunction escapeString(string) {\n var result = '';\n var char, nextChar;\n var escapeSeq;\n\n for (var i = 0; i < string.length; i++) {\n char = string.charCodeAt(i);\n // Check for surrogate pairs (reference Unicode 3.0 section \"3.7 Surrogates\").\n if (char >= 0xD800 && char <= 0xDBFF/* high surrogate */) {\n nextChar = string.charCodeAt(i + 1);\n if (nextChar >= 0xDC00 && nextChar <= 0xDFFF/* low surrogate */) {\n // Combine the surrogate pair and store it escaped.\n result += encodeHex((char - 0xD800) * 0x400 + nextChar - 0xDC00 + 0x10000);\n // Advance index one extra since we already used that char here.\n i++; continue;\n }\n }\n escapeSeq = ESCAPE_SEQUENCES[char];\n result += !escapeSeq && isPrintable(char)\n ? string[i]\n : escapeSeq || encodeHex(char);\n }\n\n return result;\n}\n\nfunction writeFlowSequence(state, level, object) {\n var _result = '',\n _tag = state.tag,\n index,\n length;\n\n for (index = 0, length = object.length; index < length; index += 1) {\n // Write only valid elements.\n if (writeNode(state, level, object[index], false, false)) {\n if (index !== 0) _result += ',' + (!state.condenseFlow ? ' ' : '');\n _result += state.dump;\n }\n }\n\n state.tag = _tag;\n state.dump = '[' + _result + ']';\n}\n\nfunction writeBlockSequence(state, level, object, compact) {\n var _result = '',\n _tag = state.tag,\n index,\n length;\n\n for (index = 0, length = object.length; index < length; index += 1) {\n // Write only valid elements.\n if (writeNode(state, level + 1, object[index], true, true)) {\n if (!compact || index !== 0) {\n _result += generateNextLine(state, level);\n }\n\n if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {\n _result += '-';\n } else {\n _result += '- ';\n }\n\n _result += state.dump;\n }\n }\n\n state.tag = _tag;\n state.dump = _result || '[]'; // Empty sequence if no valid values.\n}\n\nfunction writeFlowMapping(state, level, object) {\n var _result = '',\n _tag = state.tag,\n objectKeyList = Object.keys(object),\n index,\n length,\n objectKey,\n objectValue,\n pairBuffer;\n\n for (index = 0, length = objectKeyList.length; index < length; index += 1) {\n pairBuffer = state.condenseFlow ? '\"' : '';\n\n if (index !== 0) pairBuffer += ', ';\n\n objectKey = objectKeyList[index];\n objectValue = object[objectKey];\n\n if (!writeNode(state, level, objectKey, false, false)) {\n continue; // Skip this pair because of invalid key;\n }\n\n if (state.dump.length > 1024) pairBuffer += '? ';\n\n pairBuffer += state.dump + (state.condenseFlow ? '\"' : '') + ':' + (state.condenseFlow ? '' : ' ');\n\n if (!writeNode(state, level, objectValue, false, false)) {\n continue; // Skip this pair because of invalid value.\n }\n\n pairBuffer += state.dump;\n\n // Both key and value are valid.\n _result += pairBuffer;\n }\n\n state.tag = _tag;\n state.dump = '{' + _result + '}';\n}\n\nfunction writeBlockMapping(state, level, object, compact) {\n var _result = '',\n _tag = state.tag,\n objectKeyList = Object.keys(object),\n index,\n length,\n objectKey,\n objectValue,\n explicitPair,\n pairBuffer;\n\n // Allow sorting keys so that the output file is deterministic\n if (state.sortKeys === true) {\n // Default sorting\n objectKeyList.sort();\n } else if (typeof state.sortKeys === 'function') {\n // Custom sort function\n objectKeyList.sort(state.sortKeys);\n } else if (state.sortKeys) {\n // Something is wrong\n throw new YAMLException('sortKeys must be a boolean or a function');\n }\n\n for (index = 0, length = objectKeyList.length; index < length; index += 1) {\n pairBuffer = '';\n\n if (!compact || index !== 0) {\n pairBuffer += generateNextLine(state, level);\n }\n\n objectKey = objectKeyList[index];\n objectValue = object[objectKey];\n\n if (!writeNode(state, level + 1, objectKey, true, true, true)) {\n continue; // Skip this pair because of invalid key.\n }\n\n explicitPair = (state.tag !== null && state.tag !== '?') ||\n (state.dump && state.dump.length > 1024);\n\n if (explicitPair) {\n if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {\n pairBuffer += '?';\n } else {\n pairBuffer += '? ';\n }\n }\n\n pairBuffer += state.dump;\n\n if (explicitPair) {\n pairBuffer += generateNextLine(state, level);\n }\n\n if (!writeNode(state, level + 1, objectValue, true, explicitPair)) {\n continue; // Skip this pair because of invalid value.\n }\n\n if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {\n pairBuffer += ':';\n } else {\n pairBuffer += ': ';\n }\n\n pairBuffer += state.dump;\n\n // Both key and value are valid.\n _result += pairBuffer;\n }\n\n state.tag = _tag;\n state.dump = _result || '{}'; // Empty mapping if no valid pairs.\n}\n\nfunction detectType(state, object, explicit) {\n var _result, typeList, index, length, type, style;\n\n typeList = explicit ? state.explicitTypes : state.implicitTypes;\n\n for (index = 0, length = typeList.length; index < length; index += 1) {\n type = typeList[index];\n\n if ((type.instanceOf || type.predicate) &&\n (!type.instanceOf || ((typeof object === 'object') && (object instanceof type.instanceOf))) &&\n (!type.predicate || type.predicate(object))) {\n\n state.tag = explicit ? type.tag : '?';\n\n if (type.represent) {\n style = state.styleMap[type.tag] || type.defaultStyle;\n\n if (_toString.call(type.represent) === '[object Function]') {\n _result = type.represent(object, style);\n } else if (_hasOwnProperty.call(type.represent, style)) {\n _result = type.represent[style](object, style);\n } else {\n throw new YAMLException('!<' + type.tag + '> tag resolver accepts not \"' + style + '\" style');\n }\n\n state.dump = _result;\n }\n\n return true;\n }\n }\n\n return false;\n}\n\n// Serializes `object` and writes it to global `result`.\n// Returns true on success, or false on invalid object.\n//\nfunction writeNode(state, level, object, block, compact, iskey) {\n state.tag = null;\n state.dump = object;\n\n if (!detectType(state, object, false)) {\n detectType(state, object, true);\n }\n\n var type = _toString.call(state.dump);\n\n if (block) {\n block = (state.flowLevel < 0 || state.flowLevel > level);\n }\n\n var objectOrArray = type === '[object Object]' || type === '[object Array]',\n duplicateIndex,\n duplicate;\n\n if (objectOrArray) {\n duplicateIndex = state.duplicates.indexOf(object);\n duplicate = duplicateIndex !== -1;\n }\n\n if ((state.tag !== null && state.tag !== '?') || duplicate || (state.indent !== 2 && level > 0)) {\n compact = false;\n }\n\n if (duplicate && state.usedDuplicates[duplicateIndex]) {\n state.dump = '*ref_' + duplicateIndex;\n } else {\n if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) {\n state.usedDuplicates[duplicateIndex] = true;\n }\n if (type === '[object Object]') {\n if (block && (Object.keys(state.dump).length !== 0)) {\n writeBlockMapping(state, level, state.dump, compact);\n if (duplicate) {\n state.dump = '&ref_' + duplicateIndex + state.dump;\n }\n } else {\n writeFlowMapping(state, level, state.dump);\n if (duplicate) {\n state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;\n }\n }\n } else if (type === '[object Array]') {\n var arrayLevel = (state.noArrayIndent && (level > 0)) ? level - 1 : level;\n if (block && (state.dump.length !== 0)) {\n writeBlockSequence(state, arrayLevel, state.dump, compact);\n if (duplicate) {\n state.dump = '&ref_' + duplicateIndex + state.dump;\n }\n } else {\n writeFlowSequence(state, arrayLevel, state.dump);\n if (duplicate) {\n state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;\n }\n }\n } else if (type === '[object String]') {\n if (state.tag !== '?') {\n writeScalar(state, state.dump, level, iskey);\n }\n } else {\n if (state.skipInvalid) return false;\n throw new YAMLException('unacceptable kind of an object to dump ' + type);\n }\n\n if (state.tag !== null && state.tag !== '?') {\n state.dump = '!<' + state.tag + '> ' + state.dump;\n }\n }\n\n return true;\n}\n\nfunction getDuplicateReferences(object, state) {\n var objects = [],\n duplicatesIndexes = [],\n index,\n length;\n\n inspectNode(object, objects, duplicatesIndexes);\n\n for (index = 0, length = duplicatesIndexes.length; index < length; index += 1) {\n state.duplicates.push(objects[duplicatesIndexes[index]]);\n }\n state.usedDuplicates = new Array(length);\n}\n\nfunction inspectNode(object, objects, duplicatesIndexes) {\n var objectKeyList,\n index,\n length;\n\n if (object !== null && typeof object === 'object') {\n index = objects.indexOf(object);\n if (index !== -1) {\n if (duplicatesIndexes.indexOf(index) === -1) {\n duplicatesIndexes.push(index);\n }\n } else {\n objects.push(object);\n\n if (Array.isArray(object)) {\n for (index = 0, length = object.length; index < length; index += 1) {\n inspectNode(object[index], objects, duplicatesIndexes);\n }\n } else {\n objectKeyList = Object.keys(object);\n\n for (index = 0, length = objectKeyList.length; index < length; index += 1) {\n inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes);\n }\n }\n }\n }\n}\n\nfunction dump(input, options) {\n options = options || {};\n\n var state = new State(options);\n\n if (!state.noRefs) getDuplicateReferences(input, state);\n\n if (writeNode(state, 0, input, true, true)) return state.dump + '\\n';\n\n return '';\n}\n\nfunction safeDump(input, options) {\n return dump(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));\n}\n\nmodule.exports.dump = dump;\nmodule.exports.safeDump = safeDump;\n\n},{\"./common\":2,\"./exception\":4,\"./schema/default_full\":9,\"./schema/default_safe\":10}],4:[function(require,module,exports){\n// YAML error class. http://stackoverflow.com/questions/8458984\n//\n'use strict';\n\nfunction YAMLException(reason, mark) {\n // Super constructor\n Error.call(this);\n\n this.name = 'YAMLException';\n this.reason = reason;\n this.mark = mark;\n this.message = (this.reason || '(unknown reason)') + (this.mark ? ' ' + this.mark.toString() : '');\n\n // Include stack trace in error object\n if (Error.captureStackTrace) {\n // Chrome and NodeJS\n Error.captureStackTrace(this, this.constructor);\n } else {\n // FF, IE 10+ and Safari 6+. Fallback for others\n this.stack = (new Error()).stack || '';\n }\n}\n\n\n// Inherit from Error\nYAMLException.prototype = Object.create(Error.prototype);\nYAMLException.prototype.constructor = YAMLException;\n\n\nYAMLException.prototype.toString = function toString(compact) {\n var result = this.name + ': ';\n\n result += this.reason || '(unknown reason)';\n\n if (!compact && this.mark) {\n result += ' ' + this.mark.toString();\n }\n\n return result;\n};\n\n\nmodule.exports = YAMLException;\n\n},{}],5:[function(require,module,exports){\n'use strict';\n\n/*eslint-disable max-len,no-use-before-define*/\n\nvar common = require('./common');\nvar YAMLException = require('./exception');\nvar Mark = require('./mark');\nvar DEFAULT_SAFE_SCHEMA = require('./schema/default_safe');\nvar DEFAULT_FULL_SCHEMA = require('./schema/default_full');\n\n\nvar _hasOwnProperty = Object.prototype.hasOwnProperty;\n\n\nvar CONTEXT_FLOW_IN = 1;\nvar CONTEXT_FLOW_OUT = 2;\nvar CONTEXT_BLOCK_IN = 3;\nvar CONTEXT_BLOCK_OUT = 4;\n\n\nvar CHOMPING_CLIP = 1;\nvar CHOMPING_STRIP = 2;\nvar CHOMPING_KEEP = 3;\n\n\nvar PATTERN_NON_PRINTABLE = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F-\\x84\\x86-\\x9F\\uFFFE\\uFFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]/;\nvar PATTERN_NON_ASCII_LINE_BREAKS = /[\\x85\\u2028\\u2029]/;\nvar PATTERN_FLOW_INDICATORS = /[,\\[\\]\\{\\}]/;\nvar PATTERN_TAG_HANDLE = /^(?:!|!!|![a-z\\-]+!)$/i;\nvar PATTERN_TAG_URI = /^(?:!|[^,\\[\\]\\{\\}])(?:%[0-9a-f]{2}|[0-9a-z\\-#;\\/\\?:@&=\\+\\$,_\\.!~\\*'\\(\\)\\[\\]])*$/i;\n\n\nfunction _class(obj) { return Object.prototype.toString.call(obj); }\n\nfunction is_EOL(c) {\n return (c === 0x0A/* LF */) || (c === 0x0D/* CR */);\n}\n\nfunction is_WHITE_SPACE(c) {\n return (c === 0x09/* Tab */) || (c === 0x20/* Space */);\n}\n\nfunction is_WS_OR_EOL(c) {\n return (c === 0x09/* Tab */) ||\n (c === 0x20/* Space */) ||\n (c === 0x0A/* LF */) ||\n (c === 0x0D/* CR */);\n}\n\nfunction is_FLOW_INDICATOR(c) {\n return c === 0x2C/* , */ ||\n c === 0x5B/* [ */ ||\n c === 0x5D/* ] */ ||\n c === 0x7B/* { */ ||\n c === 0x7D/* } */;\n}\n\nfunction fromHexCode(c) {\n var lc;\n\n if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) {\n return c - 0x30;\n }\n\n /*eslint-disable no-bitwise*/\n lc = c | 0x20;\n\n if ((0x61/* a */ <= lc) && (lc <= 0x66/* f */)) {\n return lc - 0x61 + 10;\n }\n\n return -1;\n}\n\nfunction escapedHexLen(c) {\n if (c === 0x78/* x */) { return 2; }\n if (c === 0x75/* u */) { return 4; }\n if (c === 0x55/* U */) { return 8; }\n return 0;\n}\n\nfunction fromDecimalCode(c) {\n if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) {\n return c - 0x30;\n }\n\n return -1;\n}\n\nfunction simpleEscapeSequence(c) {\n /* eslint-disable indent */\n return (c === 0x30/* 0 */) ? '\\x00' :\n (c === 0x61/* a */) ? '\\x07' :\n (c === 0x62/* b */) ? '\\x08' :\n (c === 0x74/* t */) ? '\\x09' :\n (c === 0x09/* Tab */) ? '\\x09' :\n (c === 0x6E/* n */) ? '\\x0A' :\n (c === 0x76/* v */) ? '\\x0B' :\n (c === 0x66/* f */) ? '\\x0C' :\n (c === 0x72/* r */) ? '\\x0D' :\n (c === 0x65/* e */) ? '\\x1B' :\n (c === 0x20/* Space */) ? ' ' :\n (c === 0x22/* \" */) ? '\\x22' :\n (c === 0x2F/* / */) ? '/' :\n (c === 0x5C/* \\ */) ? '\\x5C' :\n (c === 0x4E/* N */) ? '\\x85' :\n (c === 0x5F/* _ */) ? '\\xA0' :\n (c === 0x4C/* L */) ? '\\u2028' :\n (c === 0x50/* P */) ? '\\u2029' : '';\n}\n\nfunction charFromCodepoint(c) {\n if (c <= 0xFFFF) {\n return String.fromCharCode(c);\n }\n // Encode UTF-16 surrogate pair\n // https://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B010000_to_U.2B10FFFF\n return String.fromCharCode(\n ((c - 0x010000) >> 10) + 0xD800,\n ((c - 0x010000) & 0x03FF) + 0xDC00\n );\n}\n\nvar simpleEscapeCheck = new Array(256); // integer, for fast access\nvar simpleEscapeMap = new Array(256);\nfor (var i = 0; i < 256; i++) {\n simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0;\n simpleEscapeMap[i] = simpleEscapeSequence(i);\n}\n\n\nfunction State(input, options) {\n this.input = input;\n\n this.filename = options['filename'] || null;\n this.schema = options['schema'] || DEFAULT_FULL_SCHEMA;\n this.onWarning = options['onWarning'] || null;\n this.legacy = options['legacy'] || false;\n this.json = options['json'] || false;\n this.listener = options['listener'] || null;\n\n this.implicitTypes = this.schema.compiledImplicit;\n this.typeMap = this.schema.compiledTypeMap;\n\n this.length = input.length;\n this.position = 0;\n this.line = 0;\n this.lineStart = 0;\n this.lineIndent = 0;\n\n this.documents = [];\n\n /*\n this.version;\n this.checkLineBreaks;\n this.tagMap;\n this.anchorMap;\n this.tag;\n this.anchor;\n this.kind;\n this.result;*/\n\n}\n\n\nfunction generateError(state, message) {\n return new YAMLException(\n message,\n new Mark(state.filename, state.input, state.position, state.line, (state.position - state.lineStart)));\n}\n\nfunction throwError(state, message) {\n throw generateError(state, message);\n}\n\nfunction throwWarning(state, message) {\n if (state.onWarning) {\n state.onWarning.call(null, generateError(state, message));\n }\n}\n\n\nvar directiveHandlers = {\n\n YAML: function handleYamlDirective(state, name, args) {\n\n var match, major, minor;\n\n if (state.version !== null) {\n throwError(state, 'duplication of %YAML directive');\n }\n\n if (args.length !== 1) {\n throwError(state, 'YAML directive accepts exactly one argument');\n }\n\n match = /^([0-9]+)\\.([0-9]+)$/.exec(args[0]);\n\n if (match === null) {\n throwError(state, 'ill-formed argument of the YAML directive');\n }\n\n major = parseInt(match[1], 10);\n minor = parseInt(match[2], 10);\n\n if (major !== 1) {\n throwError(state, 'unacceptable YAML version of the document');\n }\n\n state.version = args[0];\n state.checkLineBreaks = (minor < 2);\n\n if (minor !== 1 && minor !== 2) {\n throwWarning(state, 'unsupported YAML version of the document');\n }\n },\n\n TAG: function handleTagDirective(state, name, args) {\n\n var handle, prefix;\n\n if (args.length !== 2) {\n throwError(state, 'TAG directive accepts exactly two arguments');\n }\n\n handle = args[0];\n prefix = args[1];\n\n if (!PATTERN_TAG_HANDLE.test(handle)) {\n throwError(state, 'ill-formed tag handle (first argument) of the TAG directive');\n }\n\n if (_hasOwnProperty.call(state.tagMap, handle)) {\n throwError(state, 'there is a previously declared suffix for \"' + handle + '\" tag handle');\n }\n\n if (!PATTERN_TAG_URI.test(prefix)) {\n throwError(state, 'ill-formed tag prefix (second argument) of the TAG directive');\n }\n\n state.tagMap[handle] = prefix;\n }\n};\n\n\nfunction captureSegment(state, start, end, checkJson) {\n var _position, _length, _character, _result;\n\n if (start < end) {\n _result = state.input.slice(start, end);\n\n if (checkJson) {\n for (_position = 0, _length = _result.length; _position < _length; _position += 1) {\n _character = _result.charCodeAt(_position);\n if (!(_character === 0x09 ||\n (0x20 <= _character && _character <= 0x10FFFF))) {\n throwError(state, 'expected valid JSON character');\n }\n }\n } else if (PATTERN_NON_PRINTABLE.test(_result)) {\n throwError(state, 'the stream contains non-printable characters');\n }\n\n state.result += _result;\n }\n}\n\nfunction mergeMappings(state, destination, source, overridableKeys) {\n var sourceKeys, key, index, quantity;\n\n if (!common.isObject(source)) {\n throwError(state, 'cannot merge mappings; the provided source object is unacceptable');\n }\n\n sourceKeys = Object.keys(source);\n\n for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {\n key = sourceKeys[index];\n\n if (!_hasOwnProperty.call(destination, key)) {\n destination[key] = source[key];\n overridableKeys[key] = true;\n }\n }\n}\n\nfunction storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, startLine, startPos) {\n var index, quantity;\n\n // The output is a plain object here, so keys can only be strings.\n // We need to convert keyNode to a string, but doing so can hang the process\n // (deeply nested arrays that explode exponentially using aliases).\n if (Array.isArray(keyNode)) {\n keyNode = Array.prototype.slice.call(keyNode);\n\n for (index = 0, quantity = keyNode.length; index < quantity; index += 1) {\n if (Array.isArray(keyNode[index])) {\n throwError(state, 'nested arrays are not supported inside keys');\n }\n\n if (typeof keyNode === 'object' && _class(keyNode[index]) === '[object Object]') {\n keyNode[index] = '[object Object]';\n }\n }\n }\n\n // Avoid code execution in load() via toString property\n // (still use its own toString for arrays, timestamps,\n // and whatever user schema extensions happen to have @@toStringTag)\n if (typeof keyNode === 'object' && _class(keyNode) === '[object Object]') {\n keyNode = '[object Object]';\n }\n\n\n keyNode = String(keyNode);\n\n if (_result === null) {\n _result = {};\n }\n\n if (keyTag === 'tag:yaml.org,2002:merge') {\n if (Array.isArray(valueNode)) {\n for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {\n mergeMappings(state, _result, valueNode[index], overridableKeys);\n }\n } else {\n mergeMappings(state, _result, valueNode, overridableKeys);\n }\n } else {\n if (!state.json &&\n !_hasOwnProperty.call(overridableKeys, keyNode) &&\n _hasOwnProperty.call(_result, keyNode)) {\n state.line = startLine || state.line;\n state.position = startPos || state.position;\n throwError(state, 'duplicated mapping key');\n }\n _result[keyNode] = valueNode;\n delete overridableKeys[keyNode];\n }\n\n return _result;\n}\n\nfunction readLineBreak(state) {\n var ch;\n\n ch = state.input.charCodeAt(state.position);\n\n if (ch === 0x0A/* LF */) {\n state.position++;\n } else if (ch === 0x0D/* CR */) {\n state.position++;\n if (state.input.charCodeAt(state.position) === 0x0A/* LF */) {\n state.position++;\n }\n } else {\n throwError(state, 'a line break is expected');\n }\n\n state.line += 1;\n state.lineStart = state.position;\n}\n\nfunction skipSeparationSpace(state, allowComments, checkIndent) {\n var lineBreaks = 0,\n ch = state.input.charCodeAt(state.position);\n\n while (ch !== 0) {\n while (is_WHITE_SPACE(ch)) {\n ch = state.input.charCodeAt(++state.position);\n }\n\n if (allowComments && ch === 0x23/* # */) {\n do {\n ch = state.input.charCodeAt(++state.position);\n } while (ch !== 0x0A/* LF */ && ch !== 0x0D/* CR */ && ch !== 0);\n }\n\n if (is_EOL(ch)) {\n readLineBreak(state);\n\n ch = state.input.charCodeAt(state.position);\n lineBreaks++;\n state.lineIndent = 0;\n\n while (ch === 0x20/* Space */) {\n state.lineIndent++;\n ch = state.input.charCodeAt(++state.position);\n }\n } else {\n break;\n }\n }\n\n if (checkIndent !== -1 && lineBreaks !== 0 && state.lineIndent < checkIndent) {\n throwWarning(state, 'deficient indentation');\n }\n\n return lineBreaks;\n}\n\nfunction testDocumentSeparator(state) {\n var _position = state.position,\n ch;\n\n ch = state.input.charCodeAt(_position);\n\n // Condition state.position === state.lineStart is tested\n // in parent on each call, for efficiency. No needs to test here again.\n if ((ch === 0x2D/* - */ || ch === 0x2E/* . */) &&\n ch === state.input.charCodeAt(_position + 1) &&\n ch === state.input.charCodeAt(_position + 2)) {\n\n _position += 3;\n\n ch = state.input.charCodeAt(_position);\n\n if (ch === 0 || is_WS_OR_EOL(ch)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction writeFoldedLines(state, count) {\n if (count === 1) {\n state.result += ' ';\n } else if (count > 1) {\n state.result += common.repeat('\\n', count - 1);\n }\n}\n\n\nfunction readPlainScalar(state, nodeIndent, withinFlowCollection) {\n var preceding,\n following,\n captureStart,\n captureEnd,\n hasPendingContent,\n _line,\n _lineStart,\n _lineIndent,\n _kind = state.kind,\n _result = state.result,\n ch;\n\n ch = state.input.charCodeAt(state.position);\n\n if (is_WS_OR_EOL(ch) ||\n is_FLOW_INDICATOR(ch) ||\n ch === 0x23/* # */ ||\n ch === 0x26/* & */ ||\n ch === 0x2A/* * */ ||\n ch === 0x21/* ! */ ||\n ch === 0x7C/* | */ ||\n ch === 0x3E/* > */ ||\n ch === 0x27/* ' */ ||\n ch === 0x22/* \" */ ||\n ch === 0x25/* % */ ||\n ch === 0x40/* @ */ ||\n ch === 0x60/* ` */) {\n return false;\n }\n\n if (ch === 0x3F/* ? */ || ch === 0x2D/* - */) {\n following = state.input.charCodeAt(state.position + 1);\n\n if (is_WS_OR_EOL(following) ||\n withinFlowCollection && is_FLOW_INDICATOR(following)) {\n return false;\n }\n }\n\n state.kind = 'scalar';\n state.result = '';\n captureStart = captureEnd = state.position;\n hasPendingContent = false;\n\n while (ch !== 0) {\n if (ch === 0x3A/* : */) {\n following = state.input.charCodeAt(state.position + 1);\n\n if (is_WS_OR_EOL(following) ||\n withinFlowCollection && is_FLOW_INDICATOR(following)) {\n break;\n }\n\n } else if (ch === 0x23/* # */) {\n preceding = state.input.charCodeAt(state.position - 1);\n\n if (is_WS_OR_EOL(preceding)) {\n break;\n }\n\n } else if ((state.position === state.lineStart && testDocumentSeparator(state)) ||\n withinFlowCollection && is_FLOW_INDICATOR(ch)) {\n break;\n\n } else if (is_EOL(ch)) {\n _line = state.line;\n _lineStart = state.lineStart;\n _lineIndent = state.lineIndent;\n skipSeparationSpace(state, false, -1);\n\n if (state.lineIndent >= nodeIndent) {\n hasPendingContent = true;\n ch = state.input.charCodeAt(state.position);\n continue;\n } else {\n state.position = captureEnd;\n state.line = _line;\n state.lineStart = _lineStart;\n state.lineIndent = _lineIndent;\n break;\n }\n }\n\n if (hasPendingContent) {\n captureSegment(state, captureStart, captureEnd, false);\n writeFoldedLines(state, state.line - _line);\n captureStart = captureEnd = state.position;\n hasPendingContent = false;\n }\n\n if (!is_WHITE_SPACE(ch)) {\n captureEnd = state.position + 1;\n }\n\n ch = state.input.charCodeAt(++state.position);\n }\n\n captureSegment(state, captureStart, captureEnd, false);\n\n if (state.result) {\n return true;\n }\n\n state.kind = _kind;\n state.result = _result;\n return false;\n}\n\nfunction readSingleQuotedScalar(state, nodeIndent) {\n var ch,\n captureStart, captureEnd;\n\n ch = state.input.charCodeAt(state.position);\n\n if (ch !== 0x27/* ' */) {\n return false;\n }\n\n state.kind = 'scalar';\n state.result = '';\n state.position++;\n captureStart = captureEnd = state.position;\n\n while ((ch = state.input.charCodeAt(state.position)) !== 0) {\n if (ch === 0x27/* ' */) {\n captureSegment(state, captureStart, state.position, true);\n ch = state.input.charCodeAt(++state.position);\n\n if (ch === 0x27/* ' */) {\n captureStart = state.position;\n state.position++;\n captureEnd = state.position;\n } else {\n return true;\n }\n\n } else if (is_EOL(ch)) {\n captureSegment(state, captureStart, captureEnd, true);\n writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent));\n captureStart = captureEnd = state.position;\n\n } else if (state.position === state.lineStart && testDocumentSeparator(state)) {\n throwError(state, 'unexpected end of the document within a single quoted scalar');\n\n } else {\n state.position++;\n captureEnd = state.position;\n }\n }\n\n throwError(state, 'unexpected end of the stream within a single quoted scalar');\n}\n\nfunction readDoubleQuotedScalar(state, nodeIndent) {\n var captureStart,\n captureEnd,\n hexLength,\n hexResult,\n tmp,\n ch;\n\n ch = state.input.charCodeAt(state.position);\n\n if (ch !== 0x22/* \" */) {\n return false;\n }\n\n state.kind = 'scalar';\n state.result = '';\n state.position++;\n captureStart = captureEnd = state.position;\n\n while ((ch = state.input.charCodeAt(state.position)) !== 0) {\n if (ch === 0x22/* \" */) {\n captureSegment(state, captureStart, state.position, true);\n state.position++;\n return true;\n\n } else if (ch === 0x5C/* \\ */) {\n captureSegment(state, captureStart, state.position, true);\n ch = state.input.charCodeAt(++state.position);\n\n if (is_EOL(ch)) {\n skipSeparationSpace(state, false, nodeIndent);\n\n // TODO: rework to inline fn with no type cast?\n } else if (ch < 256 && simpleEscapeCheck[ch]) {\n state.result += simpleEscapeMap[ch];\n state.position++;\n\n } else if ((tmp = escapedHexLen(ch)) > 0) {\n hexLength = tmp;\n hexResult = 0;\n\n for (; hexLength > 0; hexLength--) {\n ch = state.input.charCodeAt(++state.position);\n\n if ((tmp = fromHexCode(ch)) >= 0) {\n hexResult = (hexResult << 4) + tmp;\n\n } else {\n throwError(state, 'expected hexadecimal character');\n }\n }\n\n state.result += charFromCodepoint(hexResult);\n\n state.position++;\n\n } else {\n throwError(state, 'unknown escape sequence');\n }\n\n captureStart = captureEnd = state.position;\n\n } else if (is_EOL(ch)) {\n captureSegment(state, captureStart, captureEnd, true);\n writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent));\n captureStart = captureEnd = state.position;\n\n } else if (state.position === state.lineStart && testDocumentSeparator(state)) {\n throwError(state, 'unexpected end of the document within a double quoted scalar');\n\n } else {\n state.position++;\n captureEnd = state.position;\n }\n }\n\n throwError(state, 'unexpected end of the stream within a double quoted scalar');\n}\n\nfunction readFlowCollection(state, nodeIndent) {\n var readNext = true,\n _line,\n _tag = state.tag,\n _result,\n _anchor = state.anchor,\n following,\n terminator,\n isPair,\n isExplicitPair,\n isMapping,\n overridableKeys = {},\n keyNode,\n keyTag,\n valueNode,\n ch;\n\n ch = state.input.charCodeAt(state.position);\n\n if (ch === 0x5B/* [ */) {\n terminator = 0x5D;/* ] */\n isMapping = false;\n _result = [];\n } else if (ch === 0x7B/* { */) {\n terminator = 0x7D;/* } */\n isMapping = true;\n _result = {};\n } else {\n return false;\n }\n\n if (state.anchor !== null) {\n state.anchorMap[state.anchor] = _result;\n }\n\n ch = state.input.charCodeAt(++state.position);\n\n while (ch !== 0) {\n skipSeparationSpace(state, true, nodeIndent);\n\n ch = state.input.charCodeAt(state.position);\n\n if (ch === terminator) {\n state.position++;\n state.tag = _tag;\n state.anchor = _anchor;\n state.kind = isMapping ? 'mapping' : 'sequence';\n state.result = _result;\n return true;\n } else if (!readNext) {\n throwError(state, 'missed comma between flow collection entries');\n }\n\n keyTag = keyNode = valueNode = null;\n isPair = isExplicitPair = false;\n\n if (ch === 0x3F/* ? */) {\n following = state.input.charCodeAt(state.position + 1);\n\n if (is_WS_OR_EOL(following)) {\n isPair = isExplicitPair = true;\n state.position++;\n skipSeparationSpace(state, true, nodeIndent);\n }\n }\n\n _line = state.line;\n composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true);\n keyTag = state.tag;\n keyNode = state.result;\n skipSeparationSpace(state, true, nodeIndent);\n\n ch = state.input.charCodeAt(state.position);\n\n if ((isExplicitPair || state.line === _line) && ch === 0x3A/* : */) {\n isPair = true;\n ch = state.input.charCodeAt(++state.position);\n skipSeparationSpace(state, true, nodeIndent);\n composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true);\n valueNode = state.result;\n }\n\n if (isMapping) {\n storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode);\n } else if (isPair) {\n _result.push(storeMappingPair(state, null, overridableKeys, keyTag, keyNode, valueNode));\n } else {\n _result.push(keyNode);\n }\n\n skipSeparationSpace(state, true, nodeIndent);\n\n ch = state.input.charCodeAt(state.position);\n\n if (ch === 0x2C/* , */) {\n readNext = true;\n ch = state.input.charCodeAt(++state.position);\n } else {\n readNext = false;\n }\n }\n\n throwError(state, 'unexpected end of the stream within a flow collection');\n}\n\nfunction readBlockScalar(state, nodeIndent) {\n var captureStart,\n folding,\n chomping = CHOMPING_CLIP,\n didReadContent = false,\n detectedIndent = false,\n textIndent = nodeIndent,\n emptyLines = 0,\n atMoreIndented = false,\n tmp,\n ch;\n\n ch = state.input.charCodeAt(state.position);\n\n if (ch === 0x7C/* | */) {\n folding = false;\n } else if (ch === 0x3E/* > */) {\n folding = true;\n } else {\n return false;\n }\n\n state.kind = 'scalar';\n state.result = '';\n\n while (ch !== 0) {\n ch = state.input.charCodeAt(++state.position);\n\n if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {\n if (CHOMPING_CLIP === chomping) {\n chomping = (ch === 0x2B/* + */) ? CHOMPING_KEEP : CHOMPING_STRIP;\n } else {\n throwError(state, 'repeat of a chomping mode identifier');\n }\n\n } else if ((tmp = fromDecimalCode(ch)) >= 0) {\n if (tmp === 0) {\n throwError(state, 'bad explicit indentation width of a block scalar; it cannot be less than one');\n } else if (!detectedIndent) {\n textIndent = nodeIndent + tmp - 1;\n detectedIndent = true;\n } else {\n throwError(state, 'repeat of an indentation width identifier');\n }\n\n } else {\n break;\n }\n }\n\n if (is_WHITE_SPACE(ch)) {\n do { ch = state.input.charCodeAt(++state.position); }\n while (is_WHITE_SPACE(ch));\n\n if (ch === 0x23/* # */) {\n do { ch = state.input.charCodeAt(++state.position); }\n while (!is_EOL(ch) && (ch !== 0));\n }\n }\n\n while (ch !== 0) {\n readLineBreak(state);\n state.lineIndent = 0;\n\n ch = state.input.charCodeAt(state.position);\n\n while ((!detectedIndent || state.lineIndent < textIndent) &&\n (ch === 0x20/* Space */)) {\n state.lineIndent++;\n ch = state.input.charCodeAt(++state.position);\n }\n\n if (!detectedIndent && state.lineIndent > textIndent) {\n textIndent = state.lineIndent;\n }\n\n if (is_EOL(ch)) {\n emptyLines++;\n continue;\n }\n\n // End of the scalar.\n if (state.lineIndent < textIndent) {\n\n // Perform the chomping.\n if (chomping === CHOMPING_KEEP) {\n state.result += common.repeat('\\n', didReadContent ? 1 + emptyLines : emptyLines);\n } else if (chomping === CHOMPING_CLIP) {\n if (didReadContent) { // i.e. only if the scalar is not empty.\n state.result += '\\n';\n }\n }\n\n // Break this `while` cycle and go to the funciton's epilogue.\n break;\n }\n\n // Folded style: use fancy rules to handle line breaks.\n if (folding) {\n\n // Lines starting with white space characters (more-indented lines) are not folded.\n if (is_WHITE_SPACE(ch)) {\n atMoreIndented = true;\n // except for the first content line (cf. Example 8.1)\n state.result += common.repeat('\\n', didReadContent ? 1 + emptyLines : emptyLines);\n\n // End of more-indented block.\n } else if (atMoreIndented) {\n atMoreIndented = false;\n state.result += common.repeat('\\n', emptyLines + 1);\n\n // Just one line break - perceive as the same line.\n } else if (emptyLines === 0) {\n if (didReadContent) { // i.e. only if we have already read some scalar content.\n state.result += ' ';\n }\n\n // Several line breaks - perceive as different lines.\n } else {\n state.result += common.repeat('\\n', emptyLines);\n }\n\n // Literal style: just add exact number of line breaks between content lines.\n } else {\n // Keep all line breaks except the header line break.\n state.result += common.repeat('\\n', didReadContent ? 1 + emptyLines : emptyLines);\n }\n\n didReadContent = true;\n detectedIndent = true;\n emptyLines = 0;\n captureStart = state.position;\n\n while (!is_EOL(ch) && (ch !== 0)) {\n ch = state.input.charCodeAt(++state.position);\n }\n\n captureSegment(state, captureStart, state.position, false);\n }\n\n return true;\n}\n\nfunction readBlockSequence(state, nodeIndent) {\n var _line,\n _tag = state.tag,\n _anchor = state.anchor,\n _result = [],\n following,\n detected = false,\n ch;\n\n if (state.anchor !== null) {\n state.anchorMap[state.anchor] = _result;\n }\n\n ch = state.input.charCodeAt(state.position);\n\n while (ch !== 0) {\n\n if (ch !== 0x2D/* - */) {\n break;\n }\n\n following = state.input.charCodeAt(state.position + 1);\n\n if (!is_WS_OR_EOL(following)) {\n break;\n }\n\n detected = true;\n state.position++;\n\n if (skipSeparationSpace(state, true, -1)) {\n if (state.lineIndent <= nodeIndent) {\n _result.push(null);\n ch = state.input.charCodeAt(state.position);\n continue;\n }\n }\n\n _line = state.line;\n composeNode(state, nodeIndent, CONTEXT_BLOCK_IN, false, true);\n _result.push(state.result);\n skipSeparationSpace(state, true, -1);\n\n ch = state.input.charCodeAt(state.position);\n\n if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) {\n throwError(state, 'bad indentation of a sequence entry');\n } else if (state.lineIndent < nodeIndent) {\n break;\n }\n }\n\n if (detected) {\n state.tag = _tag;\n state.anchor = _anchor;\n state.kind = 'sequence';\n state.result = _result;\n return true;\n }\n return false;\n}\n\nfunction readBlockMapping(state, nodeIndent, flowIndent) {\n var following,\n allowCompact,\n _line,\n _pos,\n _tag = state.tag,\n _anchor = state.anchor,\n _result = {},\n overridableKeys = {},\n keyTag = null,\n keyNode = null,\n valueNode = null,\n atExplicitKey = false,\n detected = false,\n ch;\n\n if (state.anchor !== null) {\n state.anchorMap[state.anchor] = _result;\n }\n\n ch = state.input.charCodeAt(state.position);\n\n while (ch !== 0) {\n following = state.input.charCodeAt(state.position + 1);\n _line = state.line; // Save the current line.\n _pos = state.position;\n\n //\n // Explicit notation case. There are two separate blocks:\n // first for the key (denoted by \"?\") and second for the value (denoted by \":\")\n //\n if ((ch === 0x3F/* ? */ || ch === 0x3A/* : */) && is_WS_OR_EOL(following)) {\n\n if (ch === 0x3F/* ? */) {\n if (atExplicitKey) {\n storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null);\n keyTag = keyNode = valueNode = null;\n }\n\n detected = true;\n atExplicitKey = true;\n allowCompact = true;\n\n } else if (atExplicitKey) {\n // i.e. 0x3A/* : */ === character after the explicit key.\n atExplicitKey = false;\n allowCompact = true;\n\n } else {\n throwError(state, 'incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line');\n }\n\n state.position += 1;\n ch = following;\n\n //\n // Implicit notation case. Flow-style node as the key first, then \":\", and the value.\n //\n } else if (composeNode(state, flowIndent, CONTEXT_FLOW_OUT, false, true)) {\n\n if (state.line === _line) {\n ch = state.input.charCodeAt(state.position);\n\n while (is_WHITE_SPACE(ch)) {\n ch = state.input.charCodeAt(++state.position);\n }\n\n if (ch === 0x3A/* : */) {\n ch = state.input.charCodeAt(++state.position);\n\n if (!is_WS_OR_EOL(ch)) {\n throwError(state, 'a whitespace character is expected after the key-value separator within a block mapping');\n }\n\n if (atExplicitKey) {\n storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null);\n keyTag = keyNode = valueNode = null;\n }\n\n detected = true;\n atExplicitKey = false;\n allowCompact = false;\n keyTag = state.tag;\n keyNode = state.result;\n\n } else if (detected) {\n throwError(state, 'can not read an implicit mapping pair; a colon is missed');\n\n } else {\n state.tag = _tag;\n state.anchor = _anchor;\n return true; // Keep the result of `composeNode`.\n }\n\n } else if (detected) {\n throwError(state, 'can not read a block mapping entry; a multiline key may not be an implicit key');\n\n } else {\n state.tag = _tag;\n state.anchor = _anchor;\n return true; // Keep the result of `composeNode`.\n }\n\n } else {\n break; // Reading is done. Go to the epilogue.\n }\n\n //\n // Common reading code for both explicit and implicit notations.\n //\n if (state.line === _line || state.lineIndent > nodeIndent) {\n if (composeNode(state, nodeIndent, CONTEXT_BLOCK_OUT, true, allowCompact)) {\n if (atExplicitKey) {\n keyNode = state.result;\n } else {\n valueNode = state.result;\n }\n }\n\n if (!atExplicitKey) {\n storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _line, _pos);\n keyTag = keyNode = valueNode = null;\n }\n\n skipSeparationSpace(state, true, -1);\n ch = state.input.charCodeAt(state.position);\n }\n\n if (state.lineIndent > nodeIndent && (ch !== 0)) {\n throwError(state, 'bad indentation of a mapping entry');\n } else if (state.lineIndent < nodeIndent) {\n break;\n }\n }\n\n //\n // Epilogue.\n //\n\n // Special case: last mapping's node contains only the key in explicit notation.\n if (atExplicitKey) {\n storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null);\n }\n\n // Expose the resulting mapping.\n if (detected) {\n state.tag = _tag;\n state.anchor = _anchor;\n state.kind = 'mapping';\n state.result = _result;\n }\n\n return detected;\n}\n\nfunction readTagProperty(state) {\n var _position,\n isVerbatim = false,\n isNamed = false,\n tagHandle,\n tagName,\n ch;\n\n ch = state.input.charCodeAt(state.position);\n\n if (ch !== 0x21/* ! */) return false;\n\n if (state.tag !== null) {\n throwError(state, 'duplication of a tag property');\n }\n\n ch = state.input.charCodeAt(++state.position);\n\n if (ch === 0x3C/* < */) {\n isVerbatim = true;\n ch = state.input.charCodeAt(++state.position);\n\n } else if (ch === 0x21/* ! */) {\n isNamed = true;\n tagHandle = '!!';\n ch = state.input.charCodeAt(++state.position);\n\n } else {\n tagHandle = '!';\n }\n\n _position = state.position;\n\n if (isVerbatim) {\n do { ch = state.input.charCodeAt(++state.position); }\n while (ch !== 0 && ch !== 0x3E/* > */);\n\n if (state.position < state.length) {\n tagName = state.input.slice(_position, state.position);\n ch = state.input.charCodeAt(++state.position);\n } else {\n throwError(state, 'unexpected end of the stream within a verbatim tag');\n }\n } else {\n while (ch !== 0 && !is_WS_OR_EOL(ch)) {\n\n if (ch === 0x21/* ! */) {\n if (!isNamed) {\n tagHandle = state.input.slice(_position - 1, state.position + 1);\n\n if (!PATTERN_TAG_HANDLE.test(tagHandle)) {\n throwError(state, 'named tag handle cannot contain such characters');\n }\n\n isNamed = true;\n _position = state.position + 1;\n } else {\n throwError(state, 'tag suffix cannot contain exclamation marks');\n }\n }\n\n ch = state.input.charCodeAt(++state.position);\n }\n\n tagName = state.input.slice(_position, state.position);\n\n if (PATTERN_FLOW_INDICATORS.test(tagName)) {\n throwError(state, 'tag suffix cannot contain flow indicator characters');\n }\n }\n\n if (tagName && !PATTERN_TAG_URI.test(tagName)) {\n throwError(state, 'tag name cannot contain such characters: ' + tagName);\n }\n\n if (isVerbatim) {\n state.tag = tagName;\n\n } else if (_hasOwnProperty.call(state.tagMap, tagHandle)) {\n state.tag = state.tagMap[tagHandle] + tagName;\n\n } else if (tagHandle === '!') {\n state.tag = '!' + tagName;\n\n } else if (tagHandle === '!!') {\n state.tag = 'tag:yaml.org,2002:' + tagName;\n\n } else {\n throwError(state, 'undeclared tag handle \"' + tagHandle + '\"');\n }\n\n return true;\n}\n\nfunction readAnchorProperty(state) {\n var _position,\n ch;\n\n ch = state.input.charCodeAt(state.position);\n\n if (ch !== 0x26/* & */) return false;\n\n if (state.anchor !== null) {\n throwError(state, 'duplication of an anchor property');\n }\n\n ch = state.input.charCodeAt(++state.position);\n _position = state.position;\n\n while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) {\n ch = state.input.charCodeAt(++state.position);\n }\n\n if (state.position === _position) {\n throwError(state, 'name of an anchor node must contain at least one character');\n }\n\n state.anchor = state.input.slice(_position, state.position);\n return true;\n}\n\nfunction readAlias(state) {\n var _position, alias,\n ch;\n\n ch = state.input.charCodeAt(state.position);\n\n if (ch !== 0x2A/* * */) return false;\n\n ch = state.input.charCodeAt(++state.position);\n _position = state.position;\n\n while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) {\n ch = state.input.charCodeAt(++state.position);\n }\n\n if (state.position === _position) {\n throwError(state, 'name of an alias node must contain at least one character');\n }\n\n alias = state.input.slice(_position, state.position);\n\n if (!state.anchorMap.hasOwnProperty(alias)) {\n throwError(state, 'unidentified alias \"' + alias + '\"');\n }\n\n state.result = state.anchorMap[alias];\n skipSeparationSpace(state, true, -1);\n return true;\n}\n\nfunction composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact) {\n var allowBlockStyles,\n allowBlockScalars,\n allowBlockCollections,\n indentStatus = 1, // 1: this>parent, 0: this=parent, -1: this parentIndent) {\n indentStatus = 1;\n } else if (state.lineIndent === parentIndent) {\n indentStatus = 0;\n } else if (state.lineIndent < parentIndent) {\n indentStatus = -1;\n }\n }\n }\n\n if (indentStatus === 1) {\n while (readTagProperty(state) || readAnchorProperty(state)) {\n if (skipSeparationSpace(state, true, -1)) {\n atNewLine = true;\n allowBlockCollections = allowBlockStyles;\n\n if (state.lineIndent > parentIndent) {\n indentStatus = 1;\n } else if (state.lineIndent === parentIndent) {\n indentStatus = 0;\n } else if (state.lineIndent < parentIndent) {\n indentStatus = -1;\n }\n } else {\n allowBlockCollections = false;\n }\n }\n }\n\n if (allowBlockCollections) {\n allowBlockCollections = atNewLine || allowCompact;\n }\n\n if (indentStatus === 1 || CONTEXT_BLOCK_OUT === nodeContext) {\n if (CONTEXT_FLOW_IN === nodeContext || CONTEXT_FLOW_OUT === nodeContext) {\n flowIndent = parentIndent;\n } else {\n flowIndent = parentIndent + 1;\n }\n\n blockIndent = state.position - state.lineStart;\n\n if (indentStatus === 1) {\n if (allowBlockCollections &&\n (readBlockSequence(state, blockIndent) ||\n readBlockMapping(state, blockIndent, flowIndent)) ||\n readFlowCollection(state, flowIndent)) {\n hasContent = true;\n } else {\n if ((allowBlockScalars && readBlockScalar(state, flowIndent)) ||\n readSingleQuotedScalar(state, flowIndent) ||\n readDoubleQuotedScalar(state, flowIndent)) {\n hasContent = true;\n\n } else if (readAlias(state)) {\n hasContent = true;\n\n if (state.tag !== null || state.anchor !== null) {\n throwError(state, 'alias node should not have any properties');\n }\n\n } else if (readPlainScalar(state, flowIndent, CONTEXT_FLOW_IN === nodeContext)) {\n hasContent = true;\n\n if (state.tag === null) {\n state.tag = '?';\n }\n }\n\n if (state.anchor !== null) {\n state.anchorMap[state.anchor] = state.result;\n }\n }\n } else if (indentStatus === 0) {\n // Special case: block sequences are allowed to have same indentation level as the parent.\n // http://www.yaml.org/spec/1.2/spec.html#id2799784\n hasContent = allowBlockCollections && readBlockSequence(state, blockIndent);\n }\n }\n\n if (state.tag !== null && state.tag !== '!') {\n if (state.tag === '?') {\n for (typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex += 1) {\n type = state.implicitTypes[typeIndex];\n\n // Implicit resolving is not allowed for non-scalar types, and '?'\n // non-specific tag is only assigned to plain scalars. So, it isn't\n // needed to check for 'kind' conformity.\n\n if (type.resolve(state.result)) { // `state.result` updated in resolver if matched\n state.result = type.construct(state.result);\n state.tag = type.tag;\n if (state.anchor !== null) {\n state.anchorMap[state.anchor] = state.result;\n }\n break;\n }\n }\n } else if (_hasOwnProperty.call(state.typeMap[state.kind || 'fallback'], state.tag)) {\n type = state.typeMap[state.kind || 'fallback'][state.tag];\n\n if (state.result !== null && type.kind !== state.kind) {\n throwError(state, 'unacceptable node kind for !<' + state.tag + '> tag; it should be \"' + type.kind + '\", not \"' + state.kind + '\"');\n }\n\n if (!type.resolve(state.result)) { // `state.result` updated in resolver if matched\n throwError(state, 'cannot resolve a node with !<' + state.tag + '> explicit tag');\n } else {\n state.result = type.construct(state.result);\n if (state.anchor !== null) {\n state.anchorMap[state.anchor] = state.result;\n }\n }\n } else {\n throwError(state, 'unknown tag !<' + state.tag + '>');\n }\n }\n\n if (state.listener !== null) {\n state.listener('close', state);\n }\n return state.tag !== null || state.anchor !== null || hasContent;\n}\n\nfunction readDocument(state) {\n var documentStart = state.position,\n _position,\n directiveName,\n directiveArgs,\n hasDirectives = false,\n ch;\n\n state.version = null;\n state.checkLineBreaks = state.legacy;\n state.tagMap = {};\n state.anchorMap = {};\n\n while ((ch = state.input.charCodeAt(state.position)) !== 0) {\n skipSeparationSpace(state, true, -1);\n\n ch = state.input.charCodeAt(state.position);\n\n if (state.lineIndent > 0 || ch !== 0x25/* % */) {\n break;\n }\n\n hasDirectives = true;\n ch = state.input.charCodeAt(++state.position);\n _position = state.position;\n\n while (ch !== 0 && !is_WS_OR_EOL(ch)) {\n ch = state.input.charCodeAt(++state.position);\n }\n\n directiveName = state.input.slice(_position, state.position);\n directiveArgs = [];\n\n if (directiveName.length < 1) {\n throwError(state, 'directive name must not be less than one character in length');\n }\n\n while (ch !== 0) {\n while (is_WHITE_SPACE(ch)) {\n ch = state.input.charCodeAt(++state.position);\n }\n\n if (ch === 0x23/* # */) {\n do { ch = state.input.charCodeAt(++state.position); }\n while (ch !== 0 && !is_EOL(ch));\n break;\n }\n\n if (is_EOL(ch)) break;\n\n _position = state.position;\n\n while (ch !== 0 && !is_WS_OR_EOL(ch)) {\n ch = state.input.charCodeAt(++state.position);\n }\n\n directiveArgs.push(state.input.slice(_position, state.position));\n }\n\n if (ch !== 0) readLineBreak(state);\n\n if (_hasOwnProperty.call(directiveHandlers, directiveName)) {\n directiveHandlers[directiveName](state, directiveName, directiveArgs);\n } else {\n throwWarning(state, 'unknown document directive \"' + directiveName + '\"');\n }\n }\n\n skipSeparationSpace(state, true, -1);\n\n if (state.lineIndent === 0 &&\n state.input.charCodeAt(state.position) === 0x2D/* - */ &&\n state.input.charCodeAt(state.position + 1) === 0x2D/* - */ &&\n state.input.charCodeAt(state.position + 2) === 0x2D/* - */) {\n state.position += 3;\n skipSeparationSpace(state, true, -1);\n\n } else if (hasDirectives) {\n throwError(state, 'directives end mark is expected');\n }\n\n composeNode(state, state.lineIndent - 1, CONTEXT_BLOCK_OUT, false, true);\n skipSeparationSpace(state, true, -1);\n\n if (state.checkLineBreaks &&\n PATTERN_NON_ASCII_LINE_BREAKS.test(state.input.slice(documentStart, state.position))) {\n throwWarning(state, 'non-ASCII line breaks are interpreted as content');\n }\n\n state.documents.push(state.result);\n\n if (state.position === state.lineStart && testDocumentSeparator(state)) {\n\n if (state.input.charCodeAt(state.position) === 0x2E/* . */) {\n state.position += 3;\n skipSeparationSpace(state, true, -1);\n }\n return;\n }\n\n if (state.position < (state.length - 1)) {\n throwError(state, 'end of the stream or a document separator is expected');\n } else {\n return;\n }\n}\n\n\nfunction loadDocuments(input, options) {\n input = String(input);\n options = options || {};\n\n if (input.length !== 0) {\n\n // Add tailing `\\n` if not exists\n if (input.charCodeAt(input.length - 1) !== 0x0A/* LF */ &&\n input.charCodeAt(input.length - 1) !== 0x0D/* CR */) {\n input += '\\n';\n }\n\n // Strip BOM\n if (input.charCodeAt(0) === 0xFEFF) {\n input = input.slice(1);\n }\n }\n\n var state = new State(input, options);\n\n // Use 0 as string terminator. That significantly simplifies bounds check.\n state.input += '\\0';\n\n while (state.input.charCodeAt(state.position) === 0x20/* Space */) {\n state.lineIndent += 1;\n state.position += 1;\n }\n\n while (state.position < (state.length - 1)) {\n readDocument(state);\n }\n\n return state.documents;\n}\n\n\nfunction loadAll(input, iterator, options) {\n var documents = loadDocuments(input, options), index, length;\n\n if (typeof iterator !== 'function') {\n return documents;\n }\n\n for (index = 0, length = documents.length; index < length; index += 1) {\n iterator(documents[index]);\n }\n}\n\n\nfunction load(input, options) {\n var documents = loadDocuments(input, options);\n\n if (documents.length === 0) {\n /*eslint-disable no-undefined*/\n return undefined;\n } else if (documents.length === 1) {\n return documents[0];\n }\n throw new YAMLException('expected a single document in the stream, but found more');\n}\n\n\nfunction safeLoadAll(input, output, options) {\n if (typeof output === 'function') {\n loadAll(input, output, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));\n } else {\n return loadAll(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));\n }\n}\n\n\nfunction safeLoad(input, options) {\n return load(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));\n}\n\n\nmodule.exports.loadAll = loadAll;\nmodule.exports.load = load;\nmodule.exports.safeLoadAll = safeLoadAll;\nmodule.exports.safeLoad = safeLoad;\n\n},{\"./common\":2,\"./exception\":4,\"./mark\":6,\"./schema/default_full\":9,\"./schema/default_safe\":10}],6:[function(require,module,exports){\n'use strict';\n\n\nvar common = require('./common');\n\n\nfunction Mark(name, buffer, position, line, column) {\n this.name = name;\n this.buffer = buffer;\n this.position = position;\n this.line = line;\n this.column = column;\n}\n\n\nMark.prototype.getSnippet = function getSnippet(indent, maxLength) {\n var head, start, tail, end, snippet;\n\n if (!this.buffer) return null;\n\n indent = indent || 4;\n maxLength = maxLength || 75;\n\n head = '';\n start = this.position;\n\n while (start > 0 && '\\x00\\r\\n\\x85\\u2028\\u2029'.indexOf(this.buffer.charAt(start - 1)) === -1) {\n start -= 1;\n if (this.position - start > (maxLength / 2 - 1)) {\n head = ' ... ';\n start += 5;\n break;\n }\n }\n\n tail = '';\n end = this.position;\n\n while (end < this.buffer.length && '\\x00\\r\\n\\x85\\u2028\\u2029'.indexOf(this.buffer.charAt(end)) === -1) {\n end += 1;\n if (end - this.position > (maxLength / 2 - 1)) {\n tail = ' ... ';\n end -= 5;\n break;\n }\n }\n\n snippet = this.buffer.slice(start, end);\n\n return common.repeat(' ', indent) + head + snippet + tail + '\\n' +\n common.repeat(' ', indent + this.position - start + head.length) + '^';\n};\n\n\nMark.prototype.toString = function toString(compact) {\n var snippet, where = '';\n\n if (this.name) {\n where += 'in \"' + this.name + '\" ';\n }\n\n where += 'at line ' + (this.line + 1) + ', column ' + (this.column + 1);\n\n if (!compact) {\n snippet = this.getSnippet();\n\n if (snippet) {\n where += ':\\n' + snippet;\n }\n }\n\n return where;\n};\n\n\nmodule.exports = Mark;\n\n},{\"./common\":2}],7:[function(require,module,exports){\n'use strict';\n\n/*eslint-disable max-len*/\n\nvar common = require('./common');\nvar YAMLException = require('./exception');\nvar Type = require('./type');\n\n\nfunction compileList(schema, name, result) {\n var exclude = [];\n\n schema.include.forEach(function (includedSchema) {\n result = compileList(includedSchema, name, result);\n });\n\n schema[name].forEach(function (currentType) {\n result.forEach(function (previousType, previousIndex) {\n if (previousType.tag === currentType.tag && previousType.kind === currentType.kind) {\n exclude.push(previousIndex);\n }\n });\n\n result.push(currentType);\n });\n\n return result.filter(function (type, index) {\n return exclude.indexOf(index) === -1;\n });\n}\n\n\nfunction compileMap(/* lists... */) {\n var result = {\n scalar: {},\n sequence: {},\n mapping: {},\n fallback: {}\n }, index, length;\n\n function collectType(type) {\n result[type.kind][type.tag] = result['fallback'][type.tag] = type;\n }\n\n for (index = 0, length = arguments.length; index < length; index += 1) {\n arguments[index].forEach(collectType);\n }\n return result;\n}\n\n\nfunction Schema(definition) {\n this.include = definition.include || [];\n this.implicit = definition.implicit || [];\n this.explicit = definition.explicit || [];\n\n this.implicit.forEach(function (type) {\n if (type.loadKind && type.loadKind !== 'scalar') {\n throw new YAMLException('There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.');\n }\n });\n\n this.compiledImplicit = compileList(this, 'implicit', []);\n this.compiledExplicit = compileList(this, 'explicit', []);\n this.compiledTypeMap = compileMap(this.compiledImplicit, this.compiledExplicit);\n}\n\n\nSchema.DEFAULT = null;\n\n\nSchema.create = function createSchema() {\n var schemas, types;\n\n switch (arguments.length) {\n case 1:\n schemas = Schema.DEFAULT;\n types = arguments[0];\n break;\n\n case 2:\n schemas = arguments[0];\n types = arguments[1];\n break;\n\n default:\n throw new YAMLException('Wrong number of arguments for Schema.create function');\n }\n\n schemas = common.toArray(schemas);\n types = common.toArray(types);\n\n if (!schemas.every(function (schema) { return schema instanceof Schema; })) {\n throw new YAMLException('Specified list of super schemas (or a single Schema object) contains a non-Schema object.');\n }\n\n if (!types.every(function (type) { return type instanceof Type; })) {\n throw new YAMLException('Specified list of YAML types (or a single Type object) contains a non-Type object.');\n }\n\n return new Schema({\n include: schemas,\n explicit: types\n });\n};\n\n\nmodule.exports = Schema;\n\n},{\"./common\":2,\"./exception\":4,\"./type\":13}],8:[function(require,module,exports){\n// Standard YAML's Core schema.\n// http://www.yaml.org/spec/1.2/spec.html#id2804923\n//\n// NOTE: JS-YAML does not support schema-specific tag resolution restrictions.\n// So, Core schema has no distinctions from JSON schema is JS-YAML.\n\n\n'use strict';\n\n\nvar Schema = require('../schema');\n\n\nmodule.exports = new Schema({\n include: [\n require('./json')\n ]\n});\n\n},{\"../schema\":7,\"./json\":12}],9:[function(require,module,exports){\n// JS-YAML's default schema for `load` function.\n// It is not described in the YAML specification.\n//\n// This schema is based on JS-YAML's default safe schema and includes\n// JavaScript-specific types: !!js/undefined, !!js/regexp and !!js/function.\n//\n// Also this schema is used as default base schema at `Schema.create` function.\n\n\n'use strict';\n\n\nvar Schema = require('../schema');\n\n\nmodule.exports = Schema.DEFAULT = new Schema({\n include: [\n require('./default_safe')\n ],\n explicit: [\n require('../type/js/undefined'),\n require('../type/js/regexp'),\n require('../type/js/function')\n ]\n});\n\n},{\"../schema\":7,\"../type/js/function\":18,\"../type/js/regexp\":19,\"../type/js/undefined\":20,\"./default_safe\":10}],10:[function(require,module,exports){\n// JS-YAML's default schema for `safeLoad` function.\n// It is not described in the YAML specification.\n//\n// This schema is based on standard YAML's Core schema and includes most of\n// extra types described at YAML tag repository. (http://yaml.org/type/)\n\n\n'use strict';\n\n\nvar Schema = require('../schema');\n\n\nmodule.exports = new Schema({\n include: [\n require('./core')\n ],\n implicit: [\n require('../type/timestamp'),\n require('../type/merge')\n ],\n explicit: [\n require('../type/binary'),\n require('../type/omap'),\n require('../type/pairs'),\n require('../type/set')\n ]\n});\n\n},{\"../schema\":7,\"../type/binary\":14,\"../type/merge\":22,\"../type/omap\":24,\"../type/pairs\":25,\"../type/set\":27,\"../type/timestamp\":29,\"./core\":8}],11:[function(require,module,exports){\n// Standard YAML's Failsafe schema.\n// http://www.yaml.org/spec/1.2/spec.html#id2802346\n\n\n'use strict';\n\n\nvar Schema = require('../schema');\n\n\nmodule.exports = new Schema({\n explicit: [\n require('../type/str'),\n require('../type/seq'),\n require('../type/map')\n ]\n});\n\n},{\"../schema\":7,\"../type/map\":21,\"../type/seq\":26,\"../type/str\":28}],12:[function(require,module,exports){\n// Standard YAML's JSON schema.\n// http://www.yaml.org/spec/1.2/spec.html#id2803231\n//\n// NOTE: JS-YAML does not support schema-specific tag resolution restrictions.\n// So, this schema is not such strict as defined in the YAML specification.\n// It allows numbers in binary notaion, use `Null` and `NULL` as `null`, etc.\n\n\n'use strict';\n\n\nvar Schema = require('../schema');\n\n\nmodule.exports = new Schema({\n include: [\n require('./failsafe')\n ],\n implicit: [\n require('../type/null'),\n require('../type/bool'),\n require('../type/int'),\n require('../type/float')\n ]\n});\n\n},{\"../schema\":7,\"../type/bool\":15,\"../type/float\":16,\"../type/int\":17,\"../type/null\":23,\"./failsafe\":11}],13:[function(require,module,exports){\n'use strict';\n\nvar YAMLException = require('./exception');\n\nvar TYPE_CONSTRUCTOR_OPTIONS = [\n 'kind',\n 'resolve',\n 'construct',\n 'instanceOf',\n 'predicate',\n 'represent',\n 'defaultStyle',\n 'styleAliases'\n];\n\nvar YAML_NODE_KINDS = [\n 'scalar',\n 'sequence',\n 'mapping'\n];\n\nfunction compileStyleAliases(map) {\n var result = {};\n\n if (map !== null) {\n Object.keys(map).forEach(function (style) {\n map[style].forEach(function (alias) {\n result[String(alias)] = style;\n });\n });\n }\n\n return result;\n}\n\nfunction Type(tag, options) {\n options = options || {};\n\n Object.keys(options).forEach(function (name) {\n if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) {\n throw new YAMLException('Unknown option \"' + name + '\" is met in definition of \"' + tag + '\" YAML type.');\n }\n });\n\n // TODO: Add tag format check.\n this.tag = tag;\n this.kind = options['kind'] || null;\n this.resolve = options['resolve'] || function () { return true; };\n this.construct = options['construct'] || function (data) { return data; };\n this.instanceOf = options['instanceOf'] || null;\n this.predicate = options['predicate'] || null;\n this.represent = options['represent'] || null;\n this.defaultStyle = options['defaultStyle'] || null;\n this.styleAliases = compileStyleAliases(options['styleAliases'] || null);\n\n if (YAML_NODE_KINDS.indexOf(this.kind) === -1) {\n throw new YAMLException('Unknown kind \"' + this.kind + '\" is specified for \"' + tag + '\" YAML type.');\n }\n}\n\nmodule.exports = Type;\n\n},{\"./exception\":4}],14:[function(require,module,exports){\n'use strict';\n\n/*eslint-disable no-bitwise*/\n\nvar NodeBuffer;\n\ntry {\n // A trick for browserified version, to not include `Buffer` shim\n var _require = require;\n NodeBuffer = _require('buffer').Buffer;\n} catch (__) {}\n\nvar Type = require('../type');\n\n\n// [ 64, 65, 66 ] -> [ padding, CR, LF ]\nvar BASE64_MAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\\n\\r';\n\n\nfunction resolveYamlBinary(data) {\n if (data === null) return false;\n\n var code, idx, bitlen = 0, max = data.length, map = BASE64_MAP;\n\n // Convert one by one.\n for (idx = 0; idx < max; idx++) {\n code = map.indexOf(data.charAt(idx));\n\n // Skip CR/LF\n if (code > 64) continue;\n\n // Fail on illegal characters\n if (code < 0) return false;\n\n bitlen += 6;\n }\n\n // If there are any bits left, source was corrupted\n return (bitlen % 8) === 0;\n}\n\nfunction constructYamlBinary(data) {\n var idx, tailbits,\n input = data.replace(/[\\r\\n=]/g, ''), // remove CR/LF & padding to simplify scan\n max = input.length,\n map = BASE64_MAP,\n bits = 0,\n result = [];\n\n // Collect by 6*4 bits (3 bytes)\n\n for (idx = 0; idx < max; idx++) {\n if ((idx % 4 === 0) && idx) {\n result.push((bits >> 16) & 0xFF);\n result.push((bits >> 8) & 0xFF);\n result.push(bits & 0xFF);\n }\n\n bits = (bits << 6) | map.indexOf(input.charAt(idx));\n }\n\n // Dump tail\n\n tailbits = (max % 4) * 6;\n\n if (tailbits === 0) {\n result.push((bits >> 16) & 0xFF);\n result.push((bits >> 8) & 0xFF);\n result.push(bits & 0xFF);\n } else if (tailbits === 18) {\n result.push((bits >> 10) & 0xFF);\n result.push((bits >> 2) & 0xFF);\n } else if (tailbits === 12) {\n result.push((bits >> 4) & 0xFF);\n }\n\n // Wrap into Buffer for NodeJS and leave Array for browser\n if (NodeBuffer) {\n // Support node 6.+ Buffer API when available\n return NodeBuffer.from ? NodeBuffer.from(result) : new NodeBuffer(result);\n }\n\n return result;\n}\n\nfunction representYamlBinary(object /*, style*/) {\n var result = '', bits = 0, idx, tail,\n max = object.length,\n map = BASE64_MAP;\n\n // Convert every three bytes to 4 ASCII characters.\n\n for (idx = 0; idx < max; idx++) {\n if ((idx % 3 === 0) && idx) {\n result += map[(bits >> 18) & 0x3F];\n result += map[(bits >> 12) & 0x3F];\n result += map[(bits >> 6) & 0x3F];\n result += map[bits & 0x3F];\n }\n\n bits = (bits << 8) + object[idx];\n }\n\n // Dump tail\n\n tail = max % 3;\n\n if (tail === 0) {\n result += map[(bits >> 18) & 0x3F];\n result += map[(bits >> 12) & 0x3F];\n result += map[(bits >> 6) & 0x3F];\n result += map[bits & 0x3F];\n } else if (tail === 2) {\n result += map[(bits >> 10) & 0x3F];\n result += map[(bits >> 4) & 0x3F];\n result += map[(bits << 2) & 0x3F];\n result += map[64];\n } else if (tail === 1) {\n result += map[(bits >> 2) & 0x3F];\n result += map[(bits << 4) & 0x3F];\n result += map[64];\n result += map[64];\n }\n\n return result;\n}\n\nfunction isBinary(object) {\n return NodeBuffer && NodeBuffer.isBuffer(object);\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:binary', {\n kind: 'scalar',\n resolve: resolveYamlBinary,\n construct: constructYamlBinary,\n predicate: isBinary,\n represent: representYamlBinary\n});\n\n},{\"../type\":13}],15:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nfunction resolveYamlBoolean(data) {\n if (data === null) return false;\n\n var max = data.length;\n\n return (max === 4 && (data === 'true' || data === 'True' || data === 'TRUE')) ||\n (max === 5 && (data === 'false' || data === 'False' || data === 'FALSE'));\n}\n\nfunction constructYamlBoolean(data) {\n return data === 'true' ||\n data === 'True' ||\n data === 'TRUE';\n}\n\nfunction isBoolean(object) {\n return Object.prototype.toString.call(object) === '[object Boolean]';\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:bool', {\n kind: 'scalar',\n resolve: resolveYamlBoolean,\n construct: constructYamlBoolean,\n predicate: isBoolean,\n represent: {\n lowercase: function (object) { return object ? 'true' : 'false'; },\n uppercase: function (object) { return object ? 'TRUE' : 'FALSE'; },\n camelcase: function (object) { return object ? 'True' : 'False'; }\n },\n defaultStyle: 'lowercase'\n});\n\n},{\"../type\":13}],16:[function(require,module,exports){\n'use strict';\n\nvar common = require('../common');\nvar Type = require('../type');\n\nvar YAML_FLOAT_PATTERN = new RegExp(\n // 2.5e4, 2.5 and integers\n '^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?' +\n // .2e4, .2\n // special case, seems not from spec\n '|\\\\.[0-9_]+(?:[eE][-+]?[0-9]+)?' +\n // 20:59\n '|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\\\.[0-9_]*' +\n // .inf\n '|[-+]?\\\\.(?:inf|Inf|INF)' +\n // .nan\n '|\\\\.(?:nan|NaN|NAN))$');\n\nfunction resolveYamlFloat(data) {\n if (data === null) return false;\n\n if (!YAML_FLOAT_PATTERN.test(data) ||\n // Quick hack to not allow integers end with `_`\n // Probably should update regexp & check speed\n data[data.length - 1] === '_') {\n return false;\n }\n\n return true;\n}\n\nfunction constructYamlFloat(data) {\n var value, sign, base, digits;\n\n value = data.replace(/_/g, '').toLowerCase();\n sign = value[0] === '-' ? -1 : 1;\n digits = [];\n\n if ('+-'.indexOf(value[0]) >= 0) {\n value = value.slice(1);\n }\n\n if (value === '.inf') {\n return (sign === 1) ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;\n\n } else if (value === '.nan') {\n return NaN;\n\n } else if (value.indexOf(':') >= 0) {\n value.split(':').forEach(function (v) {\n digits.unshift(parseFloat(v, 10));\n });\n\n value = 0.0;\n base = 1;\n\n digits.forEach(function (d) {\n value += d * base;\n base *= 60;\n });\n\n return sign * value;\n\n }\n return sign * parseFloat(value, 10);\n}\n\n\nvar SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/;\n\nfunction representYamlFloat(object, style) {\n var res;\n\n if (isNaN(object)) {\n switch (style) {\n case 'lowercase': return '.nan';\n case 'uppercase': return '.NAN';\n case 'camelcase': return '.NaN';\n }\n } else if (Number.POSITIVE_INFINITY === object) {\n switch (style) {\n case 'lowercase': return '.inf';\n case 'uppercase': return '.INF';\n case 'camelcase': return '.Inf';\n }\n } else if (Number.NEGATIVE_INFINITY === object) {\n switch (style) {\n case 'lowercase': return '-.inf';\n case 'uppercase': return '-.INF';\n case 'camelcase': return '-.Inf';\n }\n } else if (common.isNegativeZero(object)) {\n return '-0.0';\n }\n\n res = object.toString(10);\n\n // JS stringifier can build scientific format without dots: 5e-100,\n // while YAML requres dot: 5.e-100. Fix it with simple hack\n\n return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace('e', '.e') : res;\n}\n\nfunction isFloat(object) {\n return (Object.prototype.toString.call(object) === '[object Number]') &&\n (object % 1 !== 0 || common.isNegativeZero(object));\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:float', {\n kind: 'scalar',\n resolve: resolveYamlFloat,\n construct: constructYamlFloat,\n predicate: isFloat,\n represent: representYamlFloat,\n defaultStyle: 'lowercase'\n});\n\n},{\"../common\":2,\"../type\":13}],17:[function(require,module,exports){\n'use strict';\n\nvar common = require('../common');\nvar Type = require('../type');\n\nfunction isHexCode(c) {\n return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) ||\n ((0x41/* A */ <= c) && (c <= 0x46/* F */)) ||\n ((0x61/* a */ <= c) && (c <= 0x66/* f */));\n}\n\nfunction isOctCode(c) {\n return ((0x30/* 0 */ <= c) && (c <= 0x37/* 7 */));\n}\n\nfunction isDecCode(c) {\n return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */));\n}\n\nfunction resolveYamlInteger(data) {\n if (data === null) return false;\n\n var max = data.length,\n index = 0,\n hasDigits = false,\n ch;\n\n if (!max) return false;\n\n ch = data[index];\n\n // sign\n if (ch === '-' || ch === '+') {\n ch = data[++index];\n }\n\n if (ch === '0') {\n // 0\n if (index + 1 === max) return true;\n ch = data[++index];\n\n // base 2, base 8, base 16\n\n if (ch === 'b') {\n // base 2\n index++;\n\n for (; index < max; index++) {\n ch = data[index];\n if (ch === '_') continue;\n if (ch !== '0' && ch !== '1') return false;\n hasDigits = true;\n }\n return hasDigits && ch !== '_';\n }\n\n\n if (ch === 'x') {\n // base 16\n index++;\n\n for (; index < max; index++) {\n ch = data[index];\n if (ch === '_') continue;\n if (!isHexCode(data.charCodeAt(index))) return false;\n hasDigits = true;\n }\n return hasDigits && ch !== '_';\n }\n\n // base 8\n for (; index < max; index++) {\n ch = data[index];\n if (ch === '_') continue;\n if (!isOctCode(data.charCodeAt(index))) return false;\n hasDigits = true;\n }\n return hasDigits && ch !== '_';\n }\n\n // base 10 (except 0) or base 60\n\n // value should not start with `_`;\n if (ch === '_') return false;\n\n for (; index < max; index++) {\n ch = data[index];\n if (ch === '_') continue;\n if (ch === ':') break;\n if (!isDecCode(data.charCodeAt(index))) {\n return false;\n }\n hasDigits = true;\n }\n\n // Should have digits and should not end with `_`\n if (!hasDigits || ch === '_') return false;\n\n // if !base60 - done;\n if (ch !== ':') return true;\n\n // base60 almost not used, no needs to optimize\n return /^(:[0-5]?[0-9])+$/.test(data.slice(index));\n}\n\nfunction constructYamlInteger(data) {\n var value = data, sign = 1, ch, base, digits = [];\n\n if (value.indexOf('_') !== -1) {\n value = value.replace(/_/g, '');\n }\n\n ch = value[0];\n\n if (ch === '-' || ch === '+') {\n if (ch === '-') sign = -1;\n value = value.slice(1);\n ch = value[0];\n }\n\n if (value === '0') return 0;\n\n if (ch === '0') {\n if (value[1] === 'b') return sign * parseInt(value.slice(2), 2);\n if (value[1] === 'x') return sign * parseInt(value, 16);\n return sign * parseInt(value, 8);\n }\n\n if (value.indexOf(':') !== -1) {\n value.split(':').forEach(function (v) {\n digits.unshift(parseInt(v, 10));\n });\n\n value = 0;\n base = 1;\n\n digits.forEach(function (d) {\n value += (d * base);\n base *= 60;\n });\n\n return sign * value;\n\n }\n\n return sign * parseInt(value, 10);\n}\n\nfunction isInteger(object) {\n return (Object.prototype.toString.call(object)) === '[object Number]' &&\n (object % 1 === 0 && !common.isNegativeZero(object));\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:int', {\n kind: 'scalar',\n resolve: resolveYamlInteger,\n construct: constructYamlInteger,\n predicate: isInteger,\n represent: {\n binary: function (obj) { return obj >= 0 ? '0b' + obj.toString(2) : '-0b' + obj.toString(2).slice(1); },\n octal: function (obj) { return obj >= 0 ? '0' + obj.toString(8) : '-0' + obj.toString(8).slice(1); },\n decimal: function (obj) { return obj.toString(10); },\n /* eslint-disable max-len */\n hexadecimal: function (obj) { return obj >= 0 ? '0x' + obj.toString(16).toUpperCase() : '-0x' + obj.toString(16).toUpperCase().slice(1); }\n },\n defaultStyle: 'decimal',\n styleAliases: {\n binary: [ 2, 'bin' ],\n octal: [ 8, 'oct' ],\n decimal: [ 10, 'dec' ],\n hexadecimal: [ 16, 'hex' ]\n }\n});\n\n},{\"../common\":2,\"../type\":13}],18:[function(require,module,exports){\n'use strict';\n\nvar esprima;\n\n// Browserified version does not have esprima\n//\n// 1. For node.js just require module as deps\n// 2. For browser try to require mudule via external AMD system.\n// If not found - try to fallback to window.esprima. If not\n// found too - then fail to parse.\n//\ntry {\n // workaround to exclude package from browserify list.\n var _require = require;\n esprima = _require('esprima');\n} catch (_) {\n /*global window */\n if (typeof window !== 'undefined') esprima = window.esprima;\n}\n\nvar Type = require('../../type');\n\nfunction resolveJavascriptFunction(data) {\n if (data === null) return false;\n\n try {\n var source = '(' + data + ')',\n ast = esprima.parse(source, { range: true });\n\n if (ast.type !== 'Program' ||\n ast.body.length !== 1 ||\n ast.body[0].type !== 'ExpressionStatement' ||\n (ast.body[0].expression.type !== 'ArrowFunctionExpression' &&\n ast.body[0].expression.type !== 'FunctionExpression')) {\n return false;\n }\n\n return true;\n } catch (err) {\n return false;\n }\n}\n\nfunction constructJavascriptFunction(data) {\n /*jslint evil:true*/\n\n var source = '(' + data + ')',\n ast = esprima.parse(source, { range: true }),\n params = [],\n body;\n\n if (ast.type !== 'Program' ||\n ast.body.length !== 1 ||\n ast.body[0].type !== 'ExpressionStatement' ||\n (ast.body[0].expression.type !== 'ArrowFunctionExpression' &&\n ast.body[0].expression.type !== 'FunctionExpression')) {\n throw new Error('Failed to resolve function');\n }\n\n ast.body[0].expression.params.forEach(function (param) {\n params.push(param.name);\n });\n\n body = ast.body[0].expression.body.range;\n\n // Esprima's ranges include the first '{' and the last '}' characters on\n // function expressions. So cut them out.\n if (ast.body[0].expression.body.type === 'BlockStatement') {\n /*eslint-disable no-new-func*/\n return new Function(params, source.slice(body[0] + 1, body[1] - 1));\n }\n // ES6 arrow functions can omit the BlockStatement. In that case, just return\n // the body.\n /*eslint-disable no-new-func*/\n return new Function(params, 'return ' + source.slice(body[0], body[1]));\n}\n\nfunction representJavascriptFunction(object /*, style*/) {\n return object.toString();\n}\n\nfunction isFunction(object) {\n return Object.prototype.toString.call(object) === '[object Function]';\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:js/function', {\n kind: 'scalar',\n resolve: resolveJavascriptFunction,\n construct: constructJavascriptFunction,\n predicate: isFunction,\n represent: representJavascriptFunction\n});\n\n},{\"../../type\":13}],19:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../../type');\n\nfunction resolveJavascriptRegExp(data) {\n if (data === null) return false;\n if (data.length === 0) return false;\n\n var regexp = data,\n tail = /\\/([gim]*)$/.exec(data),\n modifiers = '';\n\n // if regexp starts with '/' it can have modifiers and must be properly closed\n // `/foo/gim` - modifiers tail can be maximum 3 chars\n if (regexp[0] === '/') {\n if (tail) modifiers = tail[1];\n\n if (modifiers.length > 3) return false;\n // if expression starts with /, is should be properly terminated\n if (regexp[regexp.length - modifiers.length - 1] !== '/') return false;\n }\n\n return true;\n}\n\nfunction constructJavascriptRegExp(data) {\n var regexp = data,\n tail = /\\/([gim]*)$/.exec(data),\n modifiers = '';\n\n // `/foo/gim` - tail can be maximum 4 chars\n if (regexp[0] === '/') {\n if (tail) modifiers = tail[1];\n regexp = regexp.slice(1, regexp.length - modifiers.length - 1);\n }\n\n return new RegExp(regexp, modifiers);\n}\n\nfunction representJavascriptRegExp(object /*, style*/) {\n var result = '/' + object.source + '/';\n\n if (object.global) result += 'g';\n if (object.multiline) result += 'm';\n if (object.ignoreCase) result += 'i';\n\n return result;\n}\n\nfunction isRegExp(object) {\n return Object.prototype.toString.call(object) === '[object RegExp]';\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:js/regexp', {\n kind: 'scalar',\n resolve: resolveJavascriptRegExp,\n construct: constructJavascriptRegExp,\n predicate: isRegExp,\n represent: representJavascriptRegExp\n});\n\n},{\"../../type\":13}],20:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../../type');\n\nfunction resolveJavascriptUndefined() {\n return true;\n}\n\nfunction constructJavascriptUndefined() {\n /*eslint-disable no-undefined*/\n return undefined;\n}\n\nfunction representJavascriptUndefined() {\n return '';\n}\n\nfunction isUndefined(object) {\n return typeof object === 'undefined';\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:js/undefined', {\n kind: 'scalar',\n resolve: resolveJavascriptUndefined,\n construct: constructJavascriptUndefined,\n predicate: isUndefined,\n represent: representJavascriptUndefined\n});\n\n},{\"../../type\":13}],21:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nmodule.exports = new Type('tag:yaml.org,2002:map', {\n kind: 'mapping',\n construct: function (data) { return data !== null ? data : {}; }\n});\n\n},{\"../type\":13}],22:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nfunction resolveYamlMerge(data) {\n return data === '<<' || data === null;\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:merge', {\n kind: 'scalar',\n resolve: resolveYamlMerge\n});\n\n},{\"../type\":13}],23:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nfunction resolveYamlNull(data) {\n if (data === null) return true;\n\n var max = data.length;\n\n return (max === 1 && data === '~') ||\n (max === 4 && (data === 'null' || data === 'Null' || data === 'NULL'));\n}\n\nfunction constructYamlNull() {\n return null;\n}\n\nfunction isNull(object) {\n return object === null;\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:null', {\n kind: 'scalar',\n resolve: resolveYamlNull,\n construct: constructYamlNull,\n predicate: isNull,\n represent: {\n canonical: function () { return '~'; },\n lowercase: function () { return 'null'; },\n uppercase: function () { return 'NULL'; },\n camelcase: function () { return 'Null'; }\n },\n defaultStyle: 'lowercase'\n});\n\n},{\"../type\":13}],24:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nvar _hasOwnProperty = Object.prototype.hasOwnProperty;\nvar _toString = Object.prototype.toString;\n\nfunction resolveYamlOmap(data) {\n if (data === null) return true;\n\n var objectKeys = [], index, length, pair, pairKey, pairHasKey,\n object = data;\n\n for (index = 0, length = object.length; index < length; index += 1) {\n pair = object[index];\n pairHasKey = false;\n\n if (_toString.call(pair) !== '[object Object]') return false;\n\n for (pairKey in pair) {\n if (_hasOwnProperty.call(pair, pairKey)) {\n if (!pairHasKey) pairHasKey = true;\n else return false;\n }\n }\n\n if (!pairHasKey) return false;\n\n if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey);\n else return false;\n }\n\n return true;\n}\n\nfunction constructYamlOmap(data) {\n return data !== null ? data : [];\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:omap', {\n kind: 'sequence',\n resolve: resolveYamlOmap,\n construct: constructYamlOmap\n});\n\n},{\"../type\":13}],25:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nvar _toString = Object.prototype.toString;\n\nfunction resolveYamlPairs(data) {\n if (data === null) return true;\n\n var index, length, pair, keys, result,\n object = data;\n\n result = new Array(object.length);\n\n for (index = 0, length = object.length; index < length; index += 1) {\n pair = object[index];\n\n if (_toString.call(pair) !== '[object Object]') return false;\n\n keys = Object.keys(pair);\n\n if (keys.length !== 1) return false;\n\n result[index] = [ keys[0], pair[keys[0]] ];\n }\n\n return true;\n}\n\nfunction constructYamlPairs(data) {\n if (data === null) return [];\n\n var index, length, pair, keys, result,\n object = data;\n\n result = new Array(object.length);\n\n for (index = 0, length = object.length; index < length; index += 1) {\n pair = object[index];\n\n keys = Object.keys(pair);\n\n result[index] = [ keys[0], pair[keys[0]] ];\n }\n\n return result;\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:pairs', {\n kind: 'sequence',\n resolve: resolveYamlPairs,\n construct: constructYamlPairs\n});\n\n},{\"../type\":13}],26:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nmodule.exports = new Type('tag:yaml.org,2002:seq', {\n kind: 'sequence',\n construct: function (data) { return data !== null ? data : []; }\n});\n\n},{\"../type\":13}],27:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nvar _hasOwnProperty = Object.prototype.hasOwnProperty;\n\nfunction resolveYamlSet(data) {\n if (data === null) return true;\n\n var key, object = data;\n\n for (key in object) {\n if (_hasOwnProperty.call(object, key)) {\n if (object[key] !== null) return false;\n }\n }\n\n return true;\n}\n\nfunction constructYamlSet(data) {\n return data !== null ? data : {};\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:set', {\n kind: 'mapping',\n resolve: resolveYamlSet,\n construct: constructYamlSet\n});\n\n},{\"../type\":13}],28:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nmodule.exports = new Type('tag:yaml.org,2002:str', {\n kind: 'scalar',\n construct: function (data) { return data !== null ? data : ''; }\n});\n\n},{\"../type\":13}],29:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nvar YAML_DATE_REGEXP = new RegExp(\n '^([0-9][0-9][0-9][0-9])' + // [1] year\n '-([0-9][0-9])' + // [2] month\n '-([0-9][0-9])$'); // [3] day\n\nvar YAML_TIMESTAMP_REGEXP = new RegExp(\n '^([0-9][0-9][0-9][0-9])' + // [1] year\n '-([0-9][0-9]?)' + // [2] month\n '-([0-9][0-9]?)' + // [3] day\n '(?:[Tt]|[ \\\\t]+)' + // ...\n '([0-9][0-9]?)' + // [4] hour\n ':([0-9][0-9])' + // [5] minute\n ':([0-9][0-9])' + // [6] second\n '(?:\\\\.([0-9]*))?' + // [7] fraction\n '(?:[ \\\\t]*(Z|([-+])([0-9][0-9]?)' + // [8] tz [9] tz_sign [10] tz_hour\n '(?::([0-9][0-9]))?))?$'); // [11] tz_minute\n\nfunction resolveYamlTimestamp(data) {\n if (data === null) return false;\n if (YAML_DATE_REGEXP.exec(data) !== null) return true;\n if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true;\n return false;\n}\n\nfunction constructYamlTimestamp(data) {\n var match, year, month, day, hour, minute, second, fraction = 0,\n delta = null, tz_hour, tz_minute, date;\n\n match = YAML_DATE_REGEXP.exec(data);\n if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data);\n\n if (match === null) throw new Error('Date resolve error');\n\n // match: [1] year [2] month [3] day\n\n year = +(match[1]);\n month = +(match[2]) - 1; // JS month starts with 0\n day = +(match[3]);\n\n if (!match[4]) { // no hour\n return new Date(Date.UTC(year, month, day));\n }\n\n // match: [4] hour [5] minute [6] second [7] fraction\n\n hour = +(match[4]);\n minute = +(match[5]);\n second = +(match[6]);\n\n if (match[7]) {\n fraction = match[7].slice(0, 3);\n while (fraction.length < 3) { // milli-seconds\n fraction += '0';\n }\n fraction = +fraction;\n }\n\n // match: [8] tz [9] tz_sign [10] tz_hour [11] tz_minute\n\n if (match[9]) {\n tz_hour = +(match[10]);\n tz_minute = +(match[11] || 0);\n delta = (tz_hour * 60 + tz_minute) * 60000; // delta in mili-seconds\n if (match[9] === '-') delta = -delta;\n }\n\n date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction));\n\n if (delta) date.setTime(date.getTime() - delta);\n\n return date;\n}\n\nfunction representYamlTimestamp(object /*, style*/) {\n return object.toISOString();\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:timestamp', {\n kind: 'scalar',\n resolve: resolveYamlTimestamp,\n construct: constructYamlTimestamp,\n instanceOf: Date,\n represent: representYamlTimestamp\n});\n\n},{\"../type\":13}],\"/\":[function(require,module,exports){\n'use strict';\n\n\nvar yaml = require('./lib/js-yaml.js');\n\n\nmodule.exports = yaml;\n\n},{\"./lib/js-yaml.js\":1}]},{},[])(\"/\")\n});", 79 | "enabled": true 80 | }, 81 | { 82 | "key": "env-openapi-json-url", 83 | "value": "", 84 | "type": "default", 85 | "enabled": true 86 | } 87 | ], 88 | "_postman_variable_scope": "environment", 89 | "_postman_exported_at": "2023-08-28T03:17:55.182Z", 90 | "_postman_exported_using": "Postman/10.17.2" 91 | } 92 | -------------------------------------------------------------------------------- /src/Contract Test Generator.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "03c5c4b6-9071-4ab4-b5a1-4bda97a3b3aa", 4 | "name": "Contract Test Generator", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 6 | "_exporter_id": "28411126", 7 | "_collection_link": "https://www.postman.com/auto-gpt/workspace/agent-protocol-public/collection/28411126-03c5c4b6-9071-4ab4-b5a1-4bda97a3b3aa?action=share&source=collection_link&creator=28411126" 8 | }, 9 | "item": [ 10 | { 11 | "name": "API Validation", 12 | "item": [ 13 | { 14 | "name": "Cleanup Previous Run", 15 | "event": [ 16 | { 17 | "listen": "prerequest", 18 | "script": { 19 | "exec": [ 20 | "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", 21 | "// for more details on what we're doing here. \r", 22 | "\r", 23 | "cleanupCollectionVariables();\r", 24 | "\r", 25 | "function cleanupCollectionVariables() {\r", 26 | " const clean = _.keys(pm.collectionVariables.toObject());\r", 27 | "\r", 28 | " _.each(clean, (arrItem) => {\r", 29 | " pm.collectionVariables.unset(arrItem);\r", 30 | " });\r", 31 | "}" 32 | ], 33 | "type": "text/javascript" 34 | } 35 | }, 36 | { 37 | "listen": "test", 38 | "script": { 39 | "exec": [ 40 | "" 41 | ], 42 | "type": "text/javascript" 43 | } 44 | } 45 | ], 46 | "request": { 47 | "method": "GET", 48 | "header": [], 49 | "url": { 50 | "raw": "https://postman-echo.com/delay/0", 51 | "protocol": "https", 52 | "host": [ 53 | "postman-echo", 54 | "com" 55 | ], 56 | "path": [ 57 | "delay", 58 | "0" 59 | ] 60 | } 61 | }, 62 | "response": [] 63 | }, 64 | { 65 | "name": "Initialize", 66 | "event": [ 67 | { 68 | "listen": "test", 69 | "script": { 70 | "exec": [ 71 | "var envSchema = null\r", 72 | "if (pm.environment.get(\"env-openapi-json-url\")){\r", 73 | " envSchema = JSON.stringify(pm.response.json());\r", 74 | "}\r", 75 | "\r", 76 | "const providedSchema = pm.environment.get('env-schema') || envSchema;\r", 77 | "if(providedSchema){\r", 78 | " let success = true;\r", 79 | " try{\r", 80 | " const yaml = pm.environment.get('env-jsonToYaml');\r", 81 | " (new Function(yaml))();\r", 82 | "\r", 83 | " const schema = jsyaml.load(providedSchema);\r", 84 | " pm.collectionVariables.set('coll-schema', JSON.stringify(schema));\r", 85 | " postman.setNextRequest('Get API Base Url');\r", 86 | " }\r", 87 | " catch(err){\r", 88 | " console.log(err);\r", 89 | " success = false;\r", 90 | " postman.setNextRequest(null);\r", 91 | " }\r", 92 | "\r", 93 | " pm.test('Successfully converted provided schema', function(){\r", 94 | " pm.expect(success).to.be.true;\r", 95 | " }); \r", 96 | "}" 97 | ], 98 | "type": "text/javascript" 99 | } 100 | }, 101 | { 102 | "listen": "prerequest", 103 | "script": { 104 | "exec": [ 105 | "if (pm.environment.get(\"env-openapi-json-url\")){", 106 | " pm.request.url = pm.environment.get(\"env-openapi-json-url\");", 107 | "}" 108 | ], 109 | "type": "text/javascript" 110 | } 111 | } 112 | ], 113 | "request": { 114 | "method": "GET", 115 | "header": [], 116 | "url": { 117 | "raw": "https://postman-echo.com/delay/0", 118 | "protocol": "https", 119 | "host": [ 120 | "postman-echo", 121 | "com" 122 | ], 123 | "path": [ 124 | "delay", 125 | "0" 126 | ] 127 | } 128 | }, 129 | "response": [] 130 | }, 131 | { 132 | "name": "Validate API In Workspace", 133 | "event": [ 134 | { 135 | "listen": "test", 136 | "script": { 137 | "exec": [ 138 | "const minApiCount = Number(pm.environment.get('env-minApiCount'));\r", 139 | "const maxApiCount = Number(pm.environment.get('env-maxApiCount'));\r", 140 | "const jsonData = pm.response.json();\r", 141 | "\r", 142 | "pm.test(`Workspace API count is between ${minApiCount} and ${maxApiCount}. (Count: ${jsonData.apis.length})`, function () { \r", 143 | " pm.expect(jsonData.apis.length).to.be.at.least(minApiCount); \r", 144 | " pm.expect(jsonData.apis.length).to.be.at.most(maxApiCount);\r", 145 | "});\r", 146 | "\r", 147 | "let apiIds = [];\r", 148 | "_.forEach(jsonData.apis, function(api){\r", 149 | " apiIds.push(api.id);\r", 150 | "});\r", 151 | "\r", 152 | "pm.collectionVariables.set('coll-apiIds', JSON.stringify(apiIds));" 153 | ], 154 | "type": "text/javascript" 155 | } 156 | } 157 | ], 158 | "request": { 159 | "auth": { 160 | "type": "noauth" 161 | }, 162 | "method": "GET", 163 | "header": [ 164 | { 165 | "key": "X-Api-Key", 166 | "value": "{{env-apiKey}}", 167 | "type": "text" 168 | } 169 | ], 170 | "url": { 171 | "raw": "https://api.getpostman.com/apis?workspace={{env-workspaceId}}", 172 | "protocol": "https", 173 | "host": [ 174 | "api", 175 | "getpostman", 176 | "com" 177 | ], 178 | "path": [ 179 | "apis" 180 | ], 181 | "query": [ 182 | { 183 | "key": "workspace", 184 | "value": "{{env-workspaceId}}" 185 | } 186 | ] 187 | } 188 | }, 189 | "response": [] 190 | }, 191 | { 192 | "name": "Get Current API Version", 193 | "event": [ 194 | { 195 | "listen": "test", 196 | "script": { 197 | "exec": [ 198 | "const jsonData = pm.response.json();\r", 199 | "\r", 200 | "pm.test('API has one or more versions', function(){\r", 201 | " pm.expect(jsonData).to.have.property('versions').and.to.be.an('array');\r", 202 | " pm.expect(jsonData.versions.length).to.be.above(0);\r", 203 | "});\r", 204 | "\r", 205 | "const version = jsonData.versions[0];\r", 206 | "pm.collectionVariables.set('coll-versionId', version.id);" 207 | ], 208 | "type": "text/javascript" 209 | } 210 | }, 211 | { 212 | "listen": "prerequest", 213 | "script": { 214 | "exec": [ 215 | "let apiIds = pm.collectionVariables.get('coll-apiIds');\r", 216 | "if(apiIds){\r", 217 | " apiIds = JSON.parse(apiIds);\r", 218 | " const apiId = apiIds.pop();\r", 219 | "\r", 220 | " pm.collectionVariables.set('coll-apiId', apiId);\r", 221 | " pm.collectionVariables.set('coll-apiIds', JSON.stringify(apiIds));\r", 222 | "}\r", 223 | "else {\r", 224 | " pm.request.url = 'https://postman-echo.com/delay/0'\r", 225 | " pm.request.name = 'No APIs found in the workspace. Skipping execution';\r", 226 | " postman.setNextRequest(null);\r", 227 | "}" 228 | ], 229 | "type": "text/javascript" 230 | } 231 | } 232 | ], 233 | "request": { 234 | "auth": { 235 | "type": "noauth" 236 | }, 237 | "method": "GET", 238 | "header": [ 239 | { 240 | "key": "X-Api-Key", 241 | "value": "{{env-apiKey}}", 242 | "type": "text" 243 | } 244 | ], 245 | "url": { 246 | "raw": "https://api.getpostman.com/apis/:apiId/versions", 247 | "protocol": "https", 248 | "host": [ 249 | "api", 250 | "getpostman", 251 | "com" 252 | ], 253 | "path": [ 254 | "apis", 255 | ":apiId", 256 | "versions" 257 | ], 258 | "query": [ 259 | { 260 | "key": null, 261 | "value": "", 262 | "disabled": true 263 | } 264 | ], 265 | "variable": [ 266 | { 267 | "key": "apiId", 268 | "value": "{{coll-apiId}}" 269 | } 270 | ] 271 | } 272 | }, 273 | "response": [] 274 | }, 275 | { 276 | "name": "Get Current API Schema", 277 | "event": [ 278 | { 279 | "listen": "test", 280 | "script": { 281 | "exec": [ 282 | "const jsonData = pm.response.json();\r", 283 | "\r", 284 | "pm.test('Has schema for current version', function(){\r", 285 | " pm.expect(jsonData).to.have.property('version');\r", 286 | " pm.expect(jsonData.version).to.have.property('schema').and.to.be.an('array');\r", 287 | " pm.expect(jsonData.version.schema.length).to.be.above(0);\r", 288 | "\r", 289 | " pm.collectionVariables.set('coll-schemaId', jsonData.version.schema[0]);\r", 290 | "});" 291 | ], 292 | "type": "text/javascript" 293 | } 294 | }, 295 | { 296 | "listen": "prerequest", 297 | "script": { 298 | "exec": [ 299 | "" 300 | ], 301 | "type": "text/javascript" 302 | } 303 | } 304 | ], 305 | "request": { 306 | "auth": { 307 | "type": "noauth" 308 | }, 309 | "method": "GET", 310 | "header": [ 311 | { 312 | "key": "X-Api-Key", 313 | "type": "text", 314 | "value": "{{env-apiKey}}" 315 | } 316 | ], 317 | "url": { 318 | "raw": "https://api.getpostman.com/apis/:apiId/versions/:versionId", 319 | "protocol": "https", 320 | "host": [ 321 | "api", 322 | "getpostman", 323 | "com" 324 | ], 325 | "path": [ 326 | "apis", 327 | ":apiId", 328 | "versions", 329 | ":versionId" 330 | ], 331 | "query": [ 332 | { 333 | "key": null, 334 | "value": "", 335 | "disabled": true 336 | } 337 | ], 338 | "variable": [ 339 | { 340 | "key": "apiId", 341 | "value": "{{coll-apiId}}" 342 | }, 343 | { 344 | "key": "versionId", 345 | "value": "{{coll-versionId}}" 346 | } 347 | ] 348 | } 349 | }, 350 | "response": [] 351 | }, 352 | { 353 | "name": "Get API Schema", 354 | "event": [ 355 | { 356 | "listen": "test", 357 | "script": { 358 | "exec": [ 359 | "try {\r", 360 | " const jsonData = pm.response.json();\r", 361 | " if(jsonData.schema.language.toLowerCase() == 'json'){\r", 362 | " pm.test('Schema is JSON', function(){\r", 363 | " pm.expect(1).to.equal(1);\r", 364 | " pm.collectionVariables.set('coll-schema', jsonData.schema.schema);\r", 365 | " });\r", 366 | " } else {\r", 367 | " pm.test('Schema translates to JSON', function(){\r", 368 | " try{\r", 369 | " const yaml = pm.environment.get('env-jsonToYaml');\r", 370 | " (new Function(yaml))();\r", 371 | "\r", 372 | " const schema = jsyaml.load(jsonData.schema.schema);\r", 373 | " pm.collectionVariables.set('coll-schema', JSON.stringify(schema));\r", 374 | " pm.expect(1).to.equal(1);\r", 375 | " }\r", 376 | " catch(err){\r", 377 | " pm.expect(`${err.name} - ${err.message}`).to.equal(undefined);\r", 378 | " } \r", 379 | " });\r", 380 | " }\r", 381 | "}\r", 382 | "catch(err) {\r", 383 | " console.log(err);\r", 384 | " pm.test('Unable to load schema', function(){\r", 385 | " pm.expect(0).to.equal(1);\r", 386 | " postman.setNextRequest(null);\r", 387 | " })\r", 388 | "}" 389 | ], 390 | "type": "text/javascript" 391 | } 392 | }, 393 | { 394 | "listen": "prerequest", 395 | "script": { 396 | "exec": [ 397 | "if (pm.environment.get(\"env-openapi-json-url\")){", 398 | " pm.request.url = pm.environment.get(\"env-openapi-json-url\");", 399 | "}", 400 | "" 401 | ], 402 | "type": "text/javascript" 403 | } 404 | } 405 | ], 406 | "request": { 407 | "auth": { 408 | "type": "noauth" 409 | }, 410 | "method": "GET", 411 | "header": [ 412 | { 413 | "key": "X-Api-Key", 414 | "value": "{{env-apiKey}}", 415 | "type": "text" 416 | } 417 | ], 418 | "url": { 419 | "raw": "https://api.getpostman.com/apis/:apiId/versions/:apiVersionId/schemas/:schemaId", 420 | "protocol": "https", 421 | "host": [ 422 | "api", 423 | "getpostman", 424 | "com" 425 | ], 426 | "path": [ 427 | "apis", 428 | ":apiId", 429 | "versions", 430 | ":apiVersionId", 431 | "schemas", 432 | ":schemaId" 433 | ], 434 | "variable": [ 435 | { 436 | "key": "apiId", 437 | "value": "{{coll-apiId}}" 438 | }, 439 | { 440 | "key": "apiVersionId", 441 | "value": "{{coll-versionId}}" 442 | }, 443 | { 444 | "key": "schemaId", 445 | "value": "{{coll-schemaId}}" 446 | } 447 | ] 448 | } 449 | }, 450 | "response": [] 451 | }, 452 | { 453 | "name": "Get API Base Url", 454 | "event": [ 455 | { 456 | "listen": "test", 457 | "script": { 458 | "exec": [ 459 | "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", 460 | "const server = pm.environment.get('env-server');\r", 461 | "\r", 462 | "pm.test('Environment has test server defined', function () {\r", 463 | " pm.expect(server).to.not.be.undefined;\r", 464 | "});\r", 465 | "\r", 466 | "pm.test('Schema has server/baseUrl defined', function () {\r", 467 | " const servers = schema.servers;\r", 468 | " pm.expect(servers).to.not.be.undefined;\r", 469 | " const serverToTest = servers.find(s => s.description.toLowerCase() == server.toLowerCase());\r", 470 | " pm.expect(serverToTest).to.not.be.undefined;\r", 471 | "\r", 472 | " pm.expect(serverToTest).to.have.property('url');\r", 473 | " pm.collectionVariables.set('coll-baseUrl', serverToTest.url);\r", 474 | "});\r", 475 | "\r", 476 | "const runComponentTests = pm.environment.get('env-runComponentTests') == 'true';\r", 477 | "if(!runComponentTests){ \r", 478 | " const runContractTests = pm.environment.get('env-runContractTests') == 'true';\r", 479 | " if(runContractTests){\r", 480 | " postman.setNextRequest('Build Schema Tests');\r", 481 | " } else {\r", 482 | " postman.setNextRequest('More APIs to Process?');\r", 483 | " } \r", 484 | "}" 485 | ], 486 | "type": "text/javascript" 487 | } 488 | } 489 | ], 490 | "request": { 491 | "auth": { 492 | "type": "noauth" 493 | }, 494 | "method": "GET", 495 | "header": [], 496 | "url": { 497 | "raw": "https://postman-echo.com/delay/0", 498 | "protocol": "https", 499 | "host": [ 500 | "postman-echo", 501 | "com" 502 | ], 503 | "path": [ 504 | "delay", 505 | "0" 506 | ] 507 | } 508 | }, 509 | "response": [] 510 | } 511 | ] 512 | }, 513 | { 514 | "name": "Components", 515 | "item": [ 516 | { 517 | "name": "Verify Component Adherence", 518 | "event": [ 519 | { 520 | "listen": "test", 521 | "script": { 522 | "exec": [ 523 | "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", 524 | "\r", 525 | "const requireParamDescription = Boolean(pm.environment.get('env-requireParamDescription'));\r", 526 | "const requireParamExample = Boolean(pm.environment.get('env-requireParamExample'));\r", 527 | "\r", 528 | "let paramDescriptionMinLength = pm.environment.get('env-paramDescriptionMinLength');\r", 529 | "if (paramDescriptionMinLength) {\r", 530 | " paramDescriptionMinLength = Number(paramDescriptionMinLength);\r", 531 | "}\r", 532 | "\r", 533 | "let paramDescriptionMaxLength = pm.environment.get('env-paramDesciptionMaxLength');\r", 534 | "if (paramDescriptionMaxLength) {\r", 535 | " paramDescriptionMaxLength = Number(paramDescriptionMaxLength);\r", 536 | "}\r", 537 | "\r", 538 | "var testedSchemaRefs = [];\r", 539 | "\r", 540 | "if (schema.components.parameters) {\r", 541 | " for (let prop in schema.components.parameters) {\r", 542 | " let parameter = schema.components.parameters[prop];\r", 543 | "\r", 544 | " pm.test(`Parameter '${prop}' starts with a lowercase letter`, function () {\r", 545 | " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toLowerCase());\r", 546 | " });\r", 547 | "\r", 548 | " if (requireParamDescription) {\r", 549 | " pm.test(`Parameter '${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", 550 | " pm.expect(parameter).to.have.property('description').and.to.be.a('string');\r", 551 | " pm.expect(parameter.description.length).to.be.at.least(paramDescriptionMinLength);\r", 552 | " pm.expect(parameter.description.length).to.be.at.most(paramDescriptionMaxLength);\r", 553 | " });\r", 554 | " }\r", 555 | "\r", 556 | " if (requireParamExample) {\r", 557 | " pm.test(`Parameter '${prop}' has an example`, function () {\r", 558 | " pm.expect(parameter).to.have.property('schema');\r", 559 | " pm.expect(parameter.schema).to.have.property('example');\r", 560 | " });\r", 561 | " }\r", 562 | " }\r", 563 | "}\r", 564 | "\r", 565 | "if (schema.components.schemas) {\r", 566 | " for (let prop in schema.components.schemas) {\r", 567 | " pm.test(`Schema '${prop}' begins with an uppercase letter`, function () {\r", 568 | " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toUpperCase());\r", 569 | " });\r", 570 | "\r", 571 | " const testedSchema = testedSchemaRefs.find(tsr => tsr == prop);\r", 572 | " if (!testedSchema) {\r", 573 | " const schemaObject = schema.components.schemas[prop];\r", 574 | " testSchemaObject(schema, schemaObject, prop);\r", 575 | " testedSchemaRefs.push(prop);\r", 576 | " }\r", 577 | " }\r", 578 | "}\r", 579 | "\r", 580 | "if (schema.components.responses) {\r", 581 | " for (let prop in schema.components.responses) {\r", 582 | " pm.test(`Response '${prop}' begins with an uppercase letter`, function () {\r", 583 | " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toUpperCase());\r", 584 | " });\r", 585 | "\r", 586 | " if (requireParamDescription) {\r", 587 | " const response = schema.components.responses[prop];\r", 588 | " pm.test(`Response '${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", 589 | " pm.expect(response).to.have.property('description').and.to.be.a('string');\r", 590 | " pm.expect(response.description.length).to.be.at.least(paramDescriptionMinLength);\r", 591 | " pm.expect(response.description.length).to.be.at.most(paramDescriptionMaxLength);\r", 592 | " });\r", 593 | " }\r", 594 | " }\r", 595 | "}\r", 596 | "\r", 597 | "const runContractTests = pm.environment.get('env-runContractTests') == 'true';\r", 598 | "if (runContractTests) {\r", 599 | " postman.setNextRequest('Build Schema Tests');\r", 600 | "} else {\r", 601 | " postman.setNextRequest('More APIs to Process?');\r", 602 | "}\r", 603 | "\r", 604 | "\r", 605 | "function testSchemaObject(schema, object, objectName) {\r", 606 | " if (object.type && object.type.toLowerCase() == 'object') {\r", 607 | " if (object.required) {\r", 608 | " for (let i = 0; i < object.required.length; i++) {\r", 609 | " const requiredProp = object.required[i];\r", 610 | " pm.test(`Schema '${objectName}' has required property '${requiredProp}' defined`, function () {\r", 611 | " pm.expect(object.properties).to.have.property(requiredProp);\r", 612 | " });\r", 613 | " }\r", 614 | " }\r", 615 | "\r", 616 | " let schemaPropertyExceptions = [];\r", 617 | " if (pm.environment.has('env-schemaPropertyExceptions')) {\r", 618 | " schemaPropertyExceptions = JSON.parse(pm.environment.get('env-schemaPropertyExceptions'));\r", 619 | " }\r", 620 | "\r", 621 | " for (let prop in object.properties) {\r", 622 | " const property = object.properties[prop];\r", 623 | "\r", 624 | " if (!schemaPropertyExceptions.some(pe => pe === prop)) {\r", 625 | " pm.test(`Schema property '${objectName}.${prop}' is lowercase`, function () {\r", 626 | " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toLowerCase());\r", 627 | " });\r", 628 | " }\r", 629 | "\r", 630 | " if (property.type && property.type.toLowerCase() == 'object') {\r", 631 | " testSchemaObject(schema, property, `${objectName}.${prop}`);\r", 632 | " }\r", 633 | " else if (property.type && property.type.toLowerCase() == 'array') {\r", 634 | " testSchemaObject(schema, property, `${objectName}.${prop}(list)`);\r", 635 | " }\r", 636 | " else if (property.oneOf) {\r", 637 | " _.forEach(property.oneOf, (oneOf, i) => {\r", 638 | " testSchemaObject(schema, oneOf, `${objectName}.${prop}(oneOf).${i}`)\r", 639 | " });\r", 640 | " }\r", 641 | " else if (property.allOf) {\r", 642 | " _.forEach(property.allOf, (allOf, i) => {\r", 643 | " testSchemaObject(schema, allOf, `${objectName}.${prop}(allOf).${i}`)\r", 644 | " });\r", 645 | " }\r", 646 | " else if (property.anyOf) {\r", 647 | " _.forEach(property.anyOf, (anyOf, i) => {\r", 648 | " testSchemaObject(schema, anyOf, `${objectName}.${prop}(anyOf).${i}`)\r", 649 | " });\r", 650 | " }\r", 651 | " else {\r", 652 | " if (requireParamDescription && !property.$ref) {\r", 653 | " pm.test(`Schema property '${objectName}.${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", 654 | " pm.expect(property).to.have.property('description').and.to.be.a('string');\r", 655 | " pm.expect(property.description.length).to.be.at.least(paramDescriptionMinLength);\r", 656 | " pm.expect(property.description.length).to.be.at.most(paramDescriptionMaxLength);\r", 657 | " });\r", 658 | "\r", 659 | " if (property.description) {\r", 660 | " pm.test(`Schema property '${objectName}.${prop}' description is not just the name`, function () {\r", 661 | " pm.expect(prop.toLowerCase()).to.not.equal(property.description.toLowerCase());\r", 662 | " });\r", 663 | " }\r", 664 | " }\r", 665 | "\r", 666 | " if (requireParamExample && !property.$ref) {\r", 667 | " pm.test(`Schema property '${objectName}.${prop}' has an example`, function () {\r", 668 | " pm.expect(property).to.have.property('example');\r", 669 | " });\r", 670 | " }\r", 671 | " }\r", 672 | " }\r", 673 | " }\r", 674 | " else if (object.type && object.type.toLowerCase() == 'array') {\r", 675 | " pm.test(`Schema '${objectName}' has items defined`, function () {\r", 676 | " pm.expect(object).to.have.property('items');\r", 677 | " });\r", 678 | "\r", 679 | " testSchemaObject(schema, object.items, `${objectName}.list`);\r", 680 | " }\r", 681 | " else if (object.oneOf) {\r", 682 | " handleSchemaArray(schema, object, objectName, 'oneOf');\r", 683 | " } else if (object.allOf) {\r", 684 | " handleSchemaArray(schema, object, objectName, 'allOf');\r", 685 | " }\r", 686 | " else if (object.anyOf) {\r", 687 | " handleSchemaArray(schema, object, objectName, 'anyOf');\r", 688 | " }\r", 689 | " else if (object.$ref) {\r", 690 | " const name = getName(object.$ref);\r", 691 | " const testedRef = testedSchemaRefs.find(tsr => tsr == name);\r", 692 | " if (!testedRef) {\r", 693 | " testSchemaObject(schema, schema.components.schemas[name], objectName);\r", 694 | " testedSchemaRefs.push(name);\r", 695 | " }\r", 696 | " }\r", 697 | " else {\r", 698 | " pm.test(`Schema '${objectName}' has a declared type`, function () {\r", 699 | " pm.expect(object).to.have.property('type');\r", 700 | " });\r", 701 | " }\r", 702 | "}\r", 703 | "\r", 704 | "function handleSchemaArray(schema, object, objectName, arrayType) {\r", 705 | " for (let i = 0; i < object[arrayType].length; i++) {\r", 706 | " const arraySchema = object[arrayType][i];\r", 707 | " if (arraySchema.$ref) {\r", 708 | " const name = getName(arraySchema.$ref);\r", 709 | " const testedRef = testedSchemaRefs.find(tsr => tsr == name);\r", 710 | " if (!testedRef) {\r", 711 | " testSchemaObject(schema, schema.components.schemas[name], `${objectName}[${i}](ref ${name})`);\r", 712 | " testedSchemaRefs.push(name);\r", 713 | " }\r", 714 | " }\r", 715 | " else {\r", 716 | " testSchemaObject(schema, arraySchema, `${objectName}[${i}]`);\r", 717 | " }\r", 718 | " }\r", 719 | "}\r", 720 | "\r", 721 | "function getName(ref) {\r", 722 | " let pieces = ref.split('/');\r", 723 | " return pieces[pieces.length - 1];\r", 724 | "}\r", 725 | "" 726 | ], 727 | "type": "text/javascript" 728 | } 729 | } 730 | ], 731 | "request": { 732 | "auth": { 733 | "type": "noauth" 734 | }, 735 | "method": "GET", 736 | "header": [], 737 | "url": { 738 | "raw": "https://postman-echo.com/delay/0", 739 | "protocol": "https", 740 | "host": [ 741 | "postman-echo", 742 | "com" 743 | ], 744 | "path": [ 745 | "delay", 746 | "0" 747 | ] 748 | } 749 | }, 750 | "response": [] 751 | } 752 | ] 753 | }, 754 | { 755 | "name": "Contract Tests", 756 | "item": [ 757 | { 758 | "name": "Build Schema Tests", 759 | "event": [ 760 | { 761 | "listen": "prerequest", 762 | "script": { 763 | "exec": [ 764 | "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", 765 | "\r", 766 | "let schemaTests = [];\r", 767 | "for (let prop in schema.paths) {\r", 768 | " const pathName = prop;\r", 769 | " let path = {\r", 770 | " path: `${pm.collectionVariables.get('coll-baseUrl')}${pathName}`,\r", 771 | " parameters: schema.paths[prop].parameters,\r", 772 | " };\r", 773 | "\r", 774 | " for (let method in schema.paths[prop]) {\r", 775 | " if (method.toLowerCase() == 'parameters' || isMockEndpoint(schema.paths[prop][method])) {\r", 776 | " continue;\r", 777 | " }\r", 778 | "\r", 779 | " let currentPath = _.cloneDeep(path);\r", 780 | " currentPath.method = method.toUpperCase();\r", 781 | " let pathMethod = schema.paths[prop][method];\r", 782 | " currentPath.parameters = combineParameters(currentPath.parameters, pathMethod.parameters);\r", 783 | " let securityExtension = pm.environment.get('env-securityExtensionName');\r", 784 | " if (securityExtension && pathMethod[securityExtension] && pathMethod[securityExtension].length > 0) {\r", 785 | " currentPath.allowedRole = pathMethod[securityExtension][0];\r", 786 | " }\r", 787 | "\r", 788 | " const expectedResponses = getExpectedResponses(pathMethod);\r", 789 | " currentPath.responses = expectedResponses;\r", 790 | "\r", 791 | " if (pathMethod.requestBody) {\r", 792 | " let bodyModel;\r", 793 | " if (pathMethod.requestBody.content['application/json']?.schema?.$ref) {\r", 794 | " bodyModel = getSchemaReference(schema, pathMethod.requestBody.content['application/json'].schema.$ref);\r", 795 | " }\r", 796 | " else if (pathMethod.requestBody.content['application/json']?.schema) {\r", 797 | " bodyModel = pathMethod.requestBody.content['application/json'].schema;\r", 798 | " }\r", 799 | " else {\r", 800 | " continue;\r", 801 | " }\r", 802 | "\r", 803 | " const models = buildModels(schema, bodyModel);\r", 804 | " const mutations = buildModelMutations(models);\r", 805 | "\r", 806 | " mutations.forEach((mutation) => {\r", 807 | " let schemaTest = _.cloneDeep(currentPath);\r", 808 | " Object.assign(schemaTest, mutation);\r", 809 | " schemaTest.name = `${schemaTest.method} - ${pathName} - ${schemaTest.description} - SUCCESS: ${schemaTest.success}`;\r", 810 | " schemaTests.push(schemaTest);\r", 811 | " });\r", 812 | " }\r", 813 | " else {\r", 814 | " currentPath.name = `${currentPath.method} - ${pathName} - No Request Body - SUCCESS: true`;\r", 815 | " currentPath.success = true;\r", 816 | " schemaTests.push(currentPath);\r", 817 | " }\r", 818 | " }\r", 819 | "}\r", 820 | "schemaTests = moveDeleteEndpointsToEnd(schemaTests);\r", 821 | "pm.collectionVariables.set('coll-schemaTests', JSON.stringify(schemaTests));\r", 822 | "\r", 823 | "// \r", 824 | "// Move delete endpoints to the end for cleanup\r", 825 | "//\r", 826 | "function moveDeleteEndpointsToEnd(schemaTests) {\r", 827 | " let sortedTests = [...schemaTests];\r", 828 | " try {\r", 829 | " let successfulDeletes = sortedTests.filter(schemaTest => schemaTest.method == 'DELETE' && schemaTest.success);\r", 830 | "\r", 831 | " if (successfulDeletes) {\r", 832 | " // order deletes from the deepest entity to highest level entity based on path\r", 833 | " successfulDeletes.sort((a, b) => b.path.split('/').length - a.path.split('/').length);\r", 834 | " sortedTests = sortedTests.filter(schemaTest => !successfulDeletes.find(sd => sd == schemaTest));\r", 835 | " sortedTests = sortedTests.concat(successfulDeletes);\r", 836 | " }\r", 837 | " }\r", 838 | " catch (err) {\r", 839 | " console.log('An error occurred when sorting delete tests', err);\r", 840 | " }\r", 841 | "\r", 842 | " return sortedTests;\r", 843 | "}\r", 844 | "\r", 845 | "//\r", 846 | "// Supporting Methods Below\r", 847 | "//\r", 848 | "function buildModels(schema, object) {\r", 849 | " let models = [];\r", 850 | "\r", 851 | " if (object['$ref']) {\r", 852 | " object = getSchemaReference(schema, object['$ref']);\r", 853 | " }\r", 854 | "\r", 855 | " if (object.type && object.type.toLowerCase() == 'object') {\r", 856 | " if (object.required && object.required.length > 0) {\r", 857 | " models.push({});\r", 858 | " _.forEach(object.required, function (param) {\r", 859 | " const property = object.properties[param];\r", 860 | "\r", 861 | " if (property.type && ['string', 'number', 'integer', 'boolean'].includes(property.type.toLowerCase())) {\r", 862 | " for (let modelIndex = 0; modelIndex < models.length; modelIndex++) {\r", 863 | " let model = models[modelIndex];\r", 864 | " model[param] = property.example;\r", 865 | " }\r", 866 | " }\r", 867 | " else {\r", 868 | " const nestedObjects = buildModels(schema, property);\r", 869 | " models = addToModels(models, nestedObjects, param);\r", 870 | " }\r", 871 | " });\r", 872 | " }\r", 873 | "\r", 874 | " if (object.minProperties) {\r", 875 | " _.forEach(models, function (model) {\r", 876 | " if (Object.keys(model).length < object.minProperties) {\r", 877 | " for (let i = Object.keys(model).length; i < object.minProperties; i++) {\r", 878 | " for (const [key, value] of Object.entries(object.properties)) {\r", 879 | " if (['string', 'number', 'integer', 'boolean'].includes(value.type.toLowerCase()) && model[key] == undefined) {\r", 880 | " model[key] = value.example;\r", 881 | " break;\r", 882 | " }\r", 883 | " }\r", 884 | " }\r", 885 | " }\r", 886 | " })\r", 887 | " }\r", 888 | " }\r", 889 | " else if (object.type && object.type.toLowerCase() == 'array') {\r", 890 | " let items = buildModels(schema, object.items);\r", 891 | " if (Array.isArray(items)) {\r", 892 | " for (let i = 0; i < items.length; i++) {\r", 893 | " models.push([items[i]]);\r", 894 | " }\r", 895 | " }\r", 896 | " else {\r", 897 | " models.push([items]);\r", 898 | " }\r", 899 | " }\r", 900 | " else if (object.oneOf) {\r", 901 | " _.forEach(object.oneOf, function (component) {\r", 902 | " let items = buildModels(schema, component);\r", 903 | " models = models.concat(items);\r", 904 | " });\r", 905 | " }\r", 906 | " else if (object.allOf) {\r", 907 | " let pieces = [{}];\r", 908 | " _.forEach(object.allOf, function (component) {\r", 909 | " let componentModels = buildModels(schema, component);\r", 910 | " pieces = addToModels(pieces, componentModels);\r", 911 | " });\r", 912 | "\r", 913 | " models = pieces;\r", 914 | " }\r", 915 | " else if (object.anyOf) {\r", 916 | " let pieces = [];\r", 917 | " let combinedPieces = [{}];\r", 918 | " _.forEach(object.anyOf, function (component) {\r", 919 | " let componentModels = buildModels(schema, component);\r", 920 | " combinedPieces = addToModels(combinedPieces, componentModels);\r", 921 | " pieces = pieces.concat(componentModels);\r", 922 | " });\r", 923 | "\r", 924 | " models = pieces.concat(combinedPieces);\r", 925 | " }\r", 926 | " else {\r", 927 | " // All other options are primitive values\r", 928 | " return object.example;\r", 929 | " }\r", 930 | " return models;\r", 931 | "}\r", 932 | "\r", 933 | "function getSchemaReference(schema, referenceName) {\r", 934 | " const refPieces = referenceName.split('/');\r", 935 | " let reference = schema;\r", 936 | " for (let i = 1; i < refPieces.length; i++) {\r", 937 | " reference = reference[refPieces[i]];\r", 938 | " }\r", 939 | "\r", 940 | " return reference;\r", 941 | "}\r", 942 | "\r", 943 | "function addToModels(models, newPieces, name) {\r", 944 | " let newModels = [];\r", 945 | " _.forEach(models, function (model) {\r", 946 | " _.forEach(newPieces, function (newPiece) {\r", 947 | " let newModel = _.cloneDeep(model);\r", 948 | " if (name) {\r", 949 | " newModel[name] = newPiece;\r", 950 | " }\r", 951 | " else {\r", 952 | " Object.assign(newModel, newPiece);\r", 953 | " }\r", 954 | " newModels.push(newModel);\r", 955 | " });\r", 956 | " });\r", 957 | "\r", 958 | " return newModels;\r", 959 | "}\r", 960 | "\r", 961 | "function buildModelMutations(models) {\r", 962 | " let modelMutations = [];\r", 963 | " _.forEach(models, function (model) {\r", 964 | " addMutation(true, 'Has all required fields', model, modelMutations);\r", 965 | " let mutations = buildMutation(model);\r", 966 | " modelMutations = modelMutations.concat(mutations);\r", 967 | " });\r", 968 | "\r", 969 | " return modelMutations;\r", 970 | "}\r", 971 | "\r", 972 | "function buildMutation(model) {\r", 973 | " let mutations = [];\r", 974 | "\r", 975 | " for (const [key, value] of Object.entries(model)) {\r", 976 | " if (typeof value == 'object') {\r", 977 | " let nestedMutations = buildMutation(value);\r", 978 | " nestedMutations.forEach((nestedMutation) => {\r", 979 | " let mutation = _.cloneDeep(model);\r", 980 | " mutation[key] = nestedMutation.body;\r", 981 | " addMutation(false, `${nestedMutation.description} in ${key} object`, mutation, mutations);\r", 982 | " });\r", 983 | "\r", 984 | " let mutation = _.cloneDeep(model);\r", 985 | " delete mutation[key];\r", 986 | " addMutation(false, `Missing ${key} object`, mutation, mutations);\r", 987 | "\r", 988 | " let emptyMutation = _.cloneDeep(model);\r", 989 | " emptyMutation[key] = {};\r", 990 | " addMutation(false, `Empty ${key} object`, emptyMutation, mutations);\r", 991 | " }\r", 992 | " else {\r", 993 | " if (Array.isArray(value)) {\r", 994 | " console.log('probably an error');\r", 995 | " }\r", 996 | " let mutation = _.cloneDeep(model);\r", 997 | " delete mutation[key];\r", 998 | " addMutation(false, `Missing ${key} property`, mutation, mutations);\r", 999 | "\r", 1000 | " let blankMutation = _.cloneDeep(model);\r", 1001 | " blankMutation[key] = '';\r", 1002 | " addMutation(false, `Blank ${key} property`, blankMutation, mutations);\r", 1003 | " }\r", 1004 | " }\r", 1005 | "\r", 1006 | " return mutations;\r", 1007 | "}\r", 1008 | "\r", 1009 | "function addMutation(isSuccess, description, mutation, mutations) {\r", 1010 | " mutations.push({\r", 1011 | " success: isSuccess,\r", 1012 | " description: description,\r", 1013 | " body: mutation\r", 1014 | " });\r", 1015 | "}\r", 1016 | "\r", 1017 | "function getExpectedResponses(pathMethod) {\r", 1018 | " const responses = [];\r", 1019 | " for (const [statusCode, value] of Object.entries(pathMethod.responses)) {\r", 1020 | " let response = {\r", 1021 | " statusCode: Number(statusCode)\r", 1022 | " };\r", 1023 | "\r", 1024 | " if (value['x-postman-variables'] && Array.isArray(value['x-postman-variables'])) {\r", 1025 | " response.variables = value['x-postman-variables'].filter(variable => variable.type.toLowerCase() === 'save');\r", 1026 | " }\r", 1027 | "\r", 1028 | " if (value.$ref) {\r", 1029 | " response.$ref = value.$ref;\r", 1030 | " }\r", 1031 | " else {\r", 1032 | " if (value.content?.['application/json']?.schema) {\r", 1033 | " if (value.content['application/json'].schema.$ref) {\r", 1034 | " response.$ref = value.content['application/json'].schema.$ref;\r", 1035 | " }\r", 1036 | " else {\r", 1037 | " response.schema = value.content['application/json'].schema;\r", 1038 | " }\r", 1039 | " }\r", 1040 | " }\r", 1041 | "\r", 1042 | " responses.push(response);\r", 1043 | " }\r", 1044 | " return responses;\r", 1045 | "}\r", 1046 | "\r", 1047 | "function isMockEndpoint(pathMethod) {\r", 1048 | " let isMock = false;\r", 1049 | " if (pathMethod && pathMethod['x-amazon-apigateway-integration'] && pathMethod['x-amazon-apigateway-integration'].type\r", 1050 | " && pathMethod['x-amazon-apigateway-integration'].type.toLowerCase() == 'mock') {\r", 1051 | " isMock = true;\r", 1052 | " }\r", 1053 | "\r", 1054 | " return isMock;\r", 1055 | "}\r", 1056 | "\r", 1057 | "function combineParameters(endpointParameters, methodParameters) {\r", 1058 | " if (!endpointParameters && !methodParameters) {\r", 1059 | " return;\r", 1060 | " }\r", 1061 | " let parameters = [];\r", 1062 | " if (endpointParameters && endpointParameters.length) {\r", 1063 | " parameters = [...endpointParameters];\r", 1064 | " }\r", 1065 | "\r", 1066 | " if (methodParameters && methodParameters.length) {\r", 1067 | " parameters = [...parameters, ...methodParameters];\r", 1068 | " }\r", 1069 | "\r", 1070 | " return parameters;\r", 1071 | "}" 1072 | ], 1073 | "type": "text/javascript" 1074 | } 1075 | }, 1076 | { 1077 | "listen": "test", 1078 | "script": { 1079 | "exec": [ 1080 | "let schemaTests = pm.collectionVariables.get('coll-schemaTests');\r", 1081 | "if(schemaTests){\r", 1082 | " schemaTests = JSON.parse(schemaTests);\r", 1083 | " if(!schemaTests || !schemaTests.length){\r", 1084 | " postman.setNextRequest('More APIs to Process?');\r", 1085 | " }\r", 1086 | "}" 1087 | ], 1088 | "type": "text/javascript" 1089 | } 1090 | } 1091 | ], 1092 | "request": { 1093 | "auth": { 1094 | "type": "noauth" 1095 | }, 1096 | "method": "GET", 1097 | "header": [], 1098 | "url": { 1099 | "raw": "https://postman-echo.com/delay/0", 1100 | "protocol": "https", 1101 | "host": [ 1102 | "postman-echo", 1103 | "com" 1104 | ], 1105 | "path": [ 1106 | "delay", 1107 | "0" 1108 | ] 1109 | } 1110 | }, 1111 | "response": [] 1112 | }, 1113 | { 1114 | "name": "Test Request", 1115 | "event": [ 1116 | { 1117 | "listen": "prerequest", 1118 | "script": { 1119 | "exec": [ 1120 | "const url = require('url');\r", 1121 | "\r", 1122 | "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", 1123 | "let schemaTests = JSON.parse(pm.collectionVariables.get('coll-schemaTests'));\r", 1124 | "\r", 1125 | "const schemaTest = schemaTests.shift();\r", 1126 | "pm.collectionVariables.set('coll-schemaTests', JSON.stringify(schemaTests));\r", 1127 | "pm.variables.set('currentSchemaTest', JSON.stringify(schemaTest));\r", 1128 | "\r", 1129 | "const path = replacePathParameters(schema, schemaTest.path, schemaTest.parameters);\r", 1130 | "pm.request.url.update(path);\r", 1131 | "delete pm.request.url.auth;\r", 1132 | "delete pm.request.url.port;\r", 1133 | "delete pm.request.url.hash;\r", 1134 | "if (pm.request.url.protocol) {\r", 1135 | " pm.request.url.protocol = pm.request.url.protocol.replace(/\\:$/, '');\r", 1136 | "} else {\r", 1137 | " pm.request.url.protocol = 'https';\r", 1138 | "}\r", 1139 | "pm.request.method = schemaTest.method;\r", 1140 | "pm.request.name = schemaTest.name;\r", 1141 | "\r", 1142 | "pm.variables.set('requestName', schemaTest.name);\r", 1143 | "pm.variables.set('body', JSON.stringify(schemaTest.body));\r", 1144 | "\r", 1145 | "// Add top level parameters from the path\r", 1146 | "const roleHeaderName = pm.environment.get('env-roleHeaderName');\r", 1147 | "\r", 1148 | "if (schemaTest.parameters) {\r", 1149 | " for (let i = 0; i < schemaTest.parameters.length; i++) {\r", 1150 | " let param = schemaTest.parameters[i];\r", 1151 | "\r", 1152 | " if (param.$ref) {\r", 1153 | " let pieces = param.$ref.split('/');\r", 1154 | " const name = pieces[pieces.length - 1];\r", 1155 | " const schemaParam = schema.components.parameters[name];\r", 1156 | " const paramType = schemaParam.in.toLowerCase();\r", 1157 | " const paramValue = loadParameterValue(schemaParam);\r", 1158 | " if (paramType == 'header' && schemaParam.required == true) {\r", 1159 | " if (roleHeaderName && schemaParam.name.toLowerCase() == roleHeaderName.toLowerCase()) {\r", 1160 | " pm.request.headers.upsert({ key: schemaParam.name, value: schemaTest.allowedRole });\r", 1161 | " }\r", 1162 | " else {\r", 1163 | " pm.request.headers.upsert({ key: schemaParam.name, value: paramValue });\r", 1164 | " }\r", 1165 | " } else if (paramType == 'query' && schemaParam.required == true) {\r", 1166 | " pm.request.url.query.upsert({ key: schemaParam.name, value: paramValue });\r", 1167 | " }\r", 1168 | " } else {\r", 1169 | " const paramType = param.in.toLowerCase();\r", 1170 | " const paramValue = loadParameterValue(param);\r", 1171 | " if (paramType == 'header') {\r", 1172 | " pm.request.headers.upsert({ key: param.name, value: paramValue });\r", 1173 | " } else if (paramType == 'query' && param.required == true) {\r", 1174 | " pm.request.url.query.upsert({ key: param.name, value: paramValue });\r", 1175 | " }\r", 1176 | " }\r", 1177 | " }\r", 1178 | "}\r", 1179 | "\r", 1180 | "function loadParameterValue(parameter) {\r", 1181 | " let parameterValue;\r", 1182 | " if (parameter['x-postman-variables']) {\r", 1183 | " let variable = parameter['x-postman-variables'].find(v => v.type.toLowerCase() === 'load');\r", 1184 | " if (variable && pm.collectionVariables.has(variable.name)) {\r", 1185 | " parameterValue = pm.collectionVariables.get(variable.name);\r", 1186 | " }\r", 1187 | " else {\r", 1188 | " parameterValue = resolveParameterExample(parameter);\r", 1189 | " }\r", 1190 | " }\r", 1191 | " else {\r", 1192 | " parameterValue = resolveParameterExample(parameter);\r", 1193 | " }\r", 1194 | "\r", 1195 | " return parameterValue;\r", 1196 | "}\r", 1197 | "\r", 1198 | "function resolveParameterExample(parameter) {\r", 1199 | " let paramValue = (parameter.schema.example != undefined) ? parameter.schema.example : parameter.example;\r", 1200 | " let value = paramValue;\r", 1201 | " if (typeof paramValue !== 'number' && typeof paramValue !== 'boolean') {\r", 1202 | " let pathVariableRegex = /^{{\\$.*}}$/;\r", 1203 | " let matches = paramValue.match(pathVariableRegex);\r", 1204 | "\r", 1205 | " if (matches && matches.length) {\r", 1206 | " value = pm.variables.replaceIn(paramValue);\r", 1207 | " }\r", 1208 | " }\r", 1209 | "\r", 1210 | " return encodeURIComponent(value);\r", 1211 | "}\r", 1212 | "\r", 1213 | "function replacePathParameters(schema, pathName, parameters) {\r", 1214 | " let replacedPathName = pathName;\r", 1215 | " let pathVariableRegex = /{([^}]*)}/g;\r", 1216 | " let matches = pathName.match(pathVariableRegex);\r", 1217 | " _.forEach(matches, function (match) {\r", 1218 | " let paramName = match.substring(1, match.length - 1);\r", 1219 | " _.forEach(parameters, function (param) {\r", 1220 | " if (param.$ref) {\r", 1221 | " let parameter = getSchemaReference(schema, param.$ref);\r", 1222 | " if (parameter.in && parameter.in.toLowerCase() == 'path' && parameter.name && parameter.name == paramName) {\r", 1223 | " let parameterValue = loadParameterValue(parameter);\r", 1224 | " replacedPathName = replacedPathName.replace(match, parameterValue);\r", 1225 | " return false;\r", 1226 | " }\r", 1227 | " } else {\r", 1228 | " if (param.in && param.in.toLowerCase() == 'path' && param.name && param.name == paramName) {\r", 1229 | " let parameterValue = loadParameterValue(param);\r", 1230 | " replacedPathName = replacedPathName.replace(match, parameterValue);\r", 1231 | " return false;\r", 1232 | " }\r", 1233 | " }\r", 1234 | " });\r", 1235 | " });\r", 1236 | "\r", 1237 | " return url.parse(replacedPathName);\r", 1238 | "}\r", 1239 | "\r", 1240 | "function getSchemaReference(schema, referenceName) {\r", 1241 | " const refPieces = referenceName.split('/');\r", 1242 | " let reference = schema;\r", 1243 | " for (let i = 1; i < refPieces.length; i++) {\r", 1244 | " reference = reference[refPieces[i]];\r", 1245 | " }\r", 1246 | "\r", 1247 | " return reference;\r", 1248 | "}" 1249 | ], 1250 | "type": "text/javascript" 1251 | } 1252 | }, 1253 | { 1254 | "listen": "test", 1255 | "script": { 1256 | "exec": [ 1257 | "const schemaTests = JSON.parse(pm.collectionVariables.get('coll-schemaTests'));\r", 1258 | "if(schemaTests.length > 0){\r", 1259 | " postman.setNextRequest('Test Request');\r", 1260 | "}\r", 1261 | "\r", 1262 | "const schemaTest = JSON.parse(pm.variables.get('currentSchemaTest'));\r", 1263 | "console.log(schemaTest.name);\r", 1264 | "\r", 1265 | "pm.test(`${schemaTest.name} - Has expected status code`, function () {\r", 1266 | " // const errorOn500 = pm.environment.get('env-errorOn500');\r", 1267 | " // if(errorOn500){\r", 1268 | " // pm.response.to.not.have.status(500);\r", 1269 | " // }\r", 1270 | "\r", 1271 | " if(schemaTest.success){\r", 1272 | " try{\r", 1273 | " if(pm.response.code >= 400) {\r", 1274 | " const jsonData = pm.response.json();\r", 1275 | " if(pm.response.code == 401) {\r", 1276 | " pm.expect(pm.request.headers.get('Role')).to.equal('role');\r", 1277 | " }\r", 1278 | " pm.expect('').to.equal(jsonData.message); \r", 1279 | " }\r", 1280 | " \r", 1281 | " pm.expect(pm.response.code).to.not.equal(400);\r", 1282 | " }\r", 1283 | " catch(err) {\r", 1284 | " console.log(err);\r", 1285 | " pm.expect(pm.response.code).to.not.equal(400);\r", 1286 | " } \r", 1287 | " }\r", 1288 | " else {\r", 1289 | " const statusCode = pm.response.code\r", 1290 | " pm.expect(statusCode === 400 || statusCode === 422).to.be.true;\r", 1291 | " } \r", 1292 | "});\r", 1293 | "\r", 1294 | "const expectedResponse = schemaTest.responses.find(r => r.statusCode == pm.response.code);\r", 1295 | "pm.test(`${schemaTest.name} - Status code (${pm.response.code}) is allowed`, function(){\r", 1296 | " pm.expect(expectedResponse).to.exist;\r", 1297 | "});\r", 1298 | "\r", 1299 | "if(expectedResponse){\r", 1300 | " pm.test(`${schemaTest.name} - Has expected response body schema`, function(){\r", 1301 | " const Ajv = require('ajv');\r", 1302 | " const ajv = new Ajv({allErrors: true,format: false,nullable: true});\r", 1303 | " \r", 1304 | " if(pm.response.code == 204 || shouldResponseBeEmpty(expectedResponse)){\r", 1305 | " checkForEmptyResponse();\r", 1306 | " }\r", 1307 | " else if(expectedResponse.$ref){ \r", 1308 | " const jsonData = pm.response.json();\r", 1309 | " const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", 1310 | " ajv.addSchema(schema, 'OAS');\r", 1311 | " const valid = ajv.validate({$ref: `OAS${expectedResponse.$ref}`}, jsonData);\r", 1312 | " const errors = ajv.errorsText(valid.errors);\r", 1313 | " pm.expect(errors).to.equal('No errors');\r", 1314 | " if(errors !== 'No errors'){\r", 1315 | " console.log(errors);\r", 1316 | " }\r", 1317 | " }\r", 1318 | " else if(expectedResponse.schema){\r", 1319 | " const jsonData = pm.response.json();\r", 1320 | " const validate = ajv.compile(expectedResponse.schema);\r", 1321 | " const valid = validate(jsonData);\r", 1322 | " const errors = ajv.errorsText(valid.errors);\r", 1323 | " pm.expect(errors).to.equal('No errors');\r", 1324 | " if(errors !== 'No errors'){\r", 1325 | " console.log(errors);\r", 1326 | " }\r", 1327 | " }\r", 1328 | " else {\r", 1329 | " checkForEmptyResponse();\r", 1330 | " }\r", 1331 | "\r", 1332 | " if(expectedResponse.variables){\r", 1333 | " const jsonData = pm.response.json();\r", 1334 | " _.forEach(expectedResponse.variables, function(variable){\r", 1335 | " let pathPieces = variable.path.split('.').filter(piece => piece);\r", 1336 | " let data = jsonData;\r", 1337 | " let found = true;\r", 1338 | " _.forEach(pathPieces, function(piece){\r", 1339 | " if(data[piece]){\r", 1340 | " data = data[piece];\r", 1341 | " }\r", 1342 | " else {\r", 1343 | " found = false;\r", 1344 | " }\r", 1345 | " });\r", 1346 | "\r", 1347 | " if(found){\r", 1348 | " pm.collectionVariables.set(variable.name, data);\r", 1349 | " }\r", 1350 | " else {\r", 1351 | " pm.test(`Unable to save dynamic variable ${variable.name} at the provided path.`, function() {\r", 1352 | " pm.expect(true).to.equal(variable.path);\r", 1353 | " });\r", 1354 | " }\r", 1355 | " });\r", 1356 | " }\r", 1357 | " });\r", 1358 | "}\r", 1359 | "\r", 1360 | "function checkForEmptyResponse() {\r", 1361 | " let emptyBody = true;\r", 1362 | " if(pm.response.text()){\r", 1363 | " emptyBody = false; \r", 1364 | " }\r", 1365 | "\r", 1366 | " pm.expect(emptyBody).to.be.true;\r", 1367 | "}\r", 1368 | "\r", 1369 | "function shouldResponseBeEmpty(expectedResponse){\r", 1370 | " let responseSchema = expectedResponse.schema;\r", 1371 | " if(expectedResponse.$ref){\r", 1372 | " let schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", 1373 | " responseSchema = getSchemaReference(schema, expectedResponse.$ref);\r", 1374 | " if(expectedResponse.$ref.startsWith('#/components/responses')){\r", 1375 | " return (!responseSchema || !responseSchema.content || !responseSchema.content['application/json'] \r", 1376 | " || !responseSchema.content['application/json'].schema || Object.keys(responseSchema.content['application/json'].schema).length == 0);\r", 1377 | " } else {\r", 1378 | " return false;\r", 1379 | " }\r", 1380 | " }\r", 1381 | " else {\r", 1382 | " return (Object.keys(responseSchema).length == 0);\r", 1383 | " }\r", 1384 | "}\r", 1385 | "\r", 1386 | "function getSchemaReference(schema, referenceName){\r", 1387 | " const refPieces = referenceName.split('/');\r", 1388 | " let reference = schema;\r", 1389 | " for(let i = 1; i < refPieces.length; i++){\r", 1390 | " reference = reference[refPieces[i]];\r", 1391 | " }\r", 1392 | "\r", 1393 | " return reference;\r", 1394 | "}" 1395 | ], 1396 | "type": "text/javascript" 1397 | } 1398 | } 1399 | ], 1400 | "protocolProfileBehavior": { 1401 | "disableBodyPruning": true 1402 | }, 1403 | "request": { 1404 | "method": "GET", 1405 | "header": [], 1406 | "body": { 1407 | "mode": "raw", 1408 | "raw": "{{body}}", 1409 | "options": { 1410 | "raw": { 1411 | "language": "json" 1412 | } 1413 | } 1414 | }, 1415 | "url": { 1416 | "raw": "https://postman-echo.com/get", 1417 | "protocol": "https", 1418 | "host": [ 1419 | "postman-echo", 1420 | "com" 1421 | ], 1422 | "path": [ 1423 | "get" 1424 | ] 1425 | } 1426 | }, 1427 | "response": [] 1428 | } 1429 | ] 1430 | }, 1431 | { 1432 | "name": "Finalize", 1433 | "item": [ 1434 | { 1435 | "name": "More APIs to Process?", 1436 | "event": [ 1437 | { 1438 | "listen": "test", 1439 | "script": { 1440 | "exec": [ 1441 | "let apis = pm.collectionVariables.get('coll-apiIds');\r", 1442 | "if(apis){\r", 1443 | " try{\r", 1444 | " apis = JSON.parse(apis);\r", 1445 | " if(apis.length > 0){\r", 1446 | " postman.setNextRequest('Get Current API Version');\r", 1447 | " }\r", 1448 | " }\r", 1449 | " catch(err){} \r", 1450 | "}" 1451 | ], 1452 | "type": "text/javascript" 1453 | } 1454 | } 1455 | ], 1456 | "request": { 1457 | "auth": { 1458 | "type": "noauth" 1459 | }, 1460 | "method": "GET", 1461 | "header": [], 1462 | "url": { 1463 | "raw": "https://postman-echo.com/delay/0", 1464 | "protocol": "https", 1465 | "host": [ 1466 | "postman-echo", 1467 | "com" 1468 | ], 1469 | "path": [ 1470 | "delay", 1471 | "0" 1472 | ] 1473 | } 1474 | }, 1475 | "response": [] 1476 | }, 1477 | { 1478 | "name": "Remove Test Variables", 1479 | "event": [ 1480 | { 1481 | "listen": "prerequest", 1482 | "script": { 1483 | "exec": [ 1484 | "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", 1485 | "// for more details on what we're doing here. \r", 1486 | "\r", 1487 | "cleanupCollectionVariables();\r", 1488 | "\r", 1489 | "function cleanupCollectionVariables() {\r", 1490 | " const clean = _.keys(pm.collectionVariables.toObject());\r", 1491 | "\r", 1492 | " _.each(clean, (arrItem) => {\r", 1493 | " pm.collectionVariables.unset(arrItem);\r", 1494 | " });\r", 1495 | "}" 1496 | ], 1497 | "type": "text/javascript" 1498 | } 1499 | }, 1500 | { 1501 | "listen": "test", 1502 | "script": { 1503 | "exec": [ 1504 | "" 1505 | ], 1506 | "type": "text/javascript" 1507 | } 1508 | } 1509 | ], 1510 | "request": { 1511 | "method": "GET", 1512 | "header": [], 1513 | "url": { 1514 | "raw": "https://postman-echo.com/delay/0", 1515 | "protocol": "https", 1516 | "host": [ 1517 | "postman-echo", 1518 | "com" 1519 | ], 1520 | "path": [ 1521 | "delay", 1522 | "0" 1523 | ] 1524 | } 1525 | }, 1526 | "response": [] 1527 | } 1528 | ] 1529 | } 1530 | ], 1531 | "event": [ 1532 | { 1533 | "listen": "prerequest", 1534 | "script": { 1535 | "type": "text/javascript", 1536 | "exec": [ 1537 | "" 1538 | ] 1539 | } 1540 | }, 1541 | { 1542 | "listen": "test", 1543 | "script": { 1544 | "type": "text/javascript", 1545 | "exec": [ 1546 | "" 1547 | ] 1548 | } 1549 | } 1550 | ] 1551 | } 1552 | --------------------------------------------------------------------------------