├── .gitattributes ├── .github └── workflows │ └── main.yaml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── LICENSE ├── README.md ├── appveyor.yml ├── binding.gyp ├── package.json ├── src ├── options.cc ├── options.h ├── options_parser.cc ├── parse_async.cc ├── parse_sync.cc ├── protagonist.cc ├── protagonist.h ├── refractToV8.cc ├── refractToV8.h ├── validate_async.cc └── validate_sync.cc ├── test ├── await-test.js ├── callback-test.js ├── fixtures-test.js ├── fixtures │ ├── error.apib │ ├── error.json │ ├── generate-body-schema.parse.json │ ├── generate-body-schema.parse.sourcemap.json │ ├── generate-body.parse.json │ ├── generate-body.parse.sourcemap.json │ ├── require-name.json │ ├── valid.apib │ ├── valid.parse.json │ ├── valid.parse.sourcemap.json │ ├── warning.apib │ ├── warning.parse.json │ └── warning.validate.json ├── helpers │ ├── await.js │ ├── callback.js │ ├── options.js │ ├── params.js │ ├── promise.js │ ├── protagonist.js │ └── sync.js ├── options-test.js ├── params-test.js ├── performance │ ├── fixtures │ │ ├── fixture-1.apib │ │ ├── fixture-1.json │ │ ├── fixture-2.apib │ │ └── fixture-2.json │ └── perf-protagonist.js ├── promise-test.js ├── protagonist-options-crash-test.js ├── sync-test.js └── timeline-test.js └── tools └── protagonist.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Blueprints line endings MUST be LF-only (unix) 2 | *.apib text eol=lf 3 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | on: push 2 | jobs: 3 | test: 4 | strategy: 5 | matrix: 6 | platform: [ubuntu-latest, macos-latest] 7 | node: ['18', '16', '14', '12'] 8 | name: Node ${{ matrix.node }} on ${{ matrix.platform }} 9 | runs-on: ${{ matrix.platform }} 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | submodules: recursive 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: ${{ matrix.node }} 17 | - run: npm install 18 | - run: npm test 19 | 20 | smoke-test: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2 24 | with: 25 | submodules: recursive 26 | path: protagonist 27 | - uses: actions/setup-node@v1 28 | with: 29 | node-version: 18 30 | - run: npm pack 31 | working-directory: protagonist 32 | - run: npm install ./protagonist/protagonist-*.tgz 33 | - run: node -e "assert(require('protagonist').parseSync('# My API').element === 'parseResult')" 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | 16 | build/ 17 | node_modules/ 18 | 19 | .cproject 20 | .project 21 | .settings 22 | 23 | # OS X and Xcode specific 24 | .DS_Store 25 | build/ 26 | *.pbxuser 27 | !default.pbxuser 28 | *.mode1v3 29 | !default.mode1v3 30 | *.mode2v3 31 | !default.mode2v3 32 | *.perspectivev3 33 | !default.perspectivev3 34 | *.xcodeproj/*.xcworkspace 35 | xcuserdata 36 | xcshareddata 37 | profile 38 | *.moved-aside 39 | DerivedData 40 | .idea/ 41 | 42 | # Generated files 43 | config.mk 44 | config.gypi 45 | bin/ 46 | vendor/ 47 | .vagrant/ 48 | tmp/ 49 | 50 | # Ruby 51 | .bundle/ 52 | features/support/env-win.rb 53 | 54 | # Eclipse specific 55 | .cproject 56 | .project 57 | .settings 58 | 59 | THIRD_PARTY_LICENSES.txt 60 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "drafter"] 2 | path = drafter 3 | url = https://github.com/apiaryio/drafter.git 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Protagonist Changelog 2 | 3 | ## 2.3.0 (2023-05-18) 4 | 5 | This update now uses Drafter 5.1.0. Please see [Drafter 6 | 5.1.0](https://github.com/apiaryio/drafter/releases/tag/v5.1.0) for 7 | the list of changes. 8 | 9 | ### Breaking 10 | 11 | - Drop support for node.js versions < 12. 12 | 13 | ### Enhancements 14 | 15 | * Added support for building Protagonist with Node 16, and 18. 16 | 17 | ## 2.2.1 (2020-06-10) 18 | 19 | ### Enhancements 20 | 21 | * Added support for building Protagonist with Node 13/14. 22 | 23 | ## 2.2.0 (2020-04-20) 24 | 25 | ### Enhancements 26 | 27 | * Drafter contains two new options for disabling messageBody and 28 | messageBodySchema generation from MSON. `generateMessageBody` and 29 | `generatedMessageBodySchema` respectively. 30 | 31 | ## 2.1.0 (2020-03-17) 32 | 33 | This update now uses Drafter 5.0.0-rc.1. Please see [Drafter 34 | 5.0.0-rc.1](https://github.com/apiaryio/drafter/releases/tag/v-5.0.0-rc.1) for 35 | the list of changes. 36 | 37 | ## 2.0.2 (2019-10-29) 38 | 39 | This update now uses Drafter 4.0.2. Please see [Drafter 40 | 4.0.2](https://github.com/apiaryio/drafter/releases/tag/v4.0.2) for the list of 41 | changes. 42 | 43 | ## 2.0.1 (2019-09-17) 44 | 45 | This update now uses Drafter 4.0.1. Please see [Drafter 46 | 4.0.1](https://github.com/apiaryio/drafter/releases/tag/v4.0.1) for the list of 47 | changes. 48 | 49 | ### Enhancements 50 | 51 | - The Protagonist NPM package now contains a `THIRD_PARTY_LICENSES.txt` file 52 | which contains the licenses of the vendored C++ dependencies of the library. 53 | 54 | ## 2.0.0 (2019-07-02) 55 | 56 | This update now uses Drafter 4.0.0-pre.8. Please see [Drafter 57 | 4.0.0-pre.8](https://github.com/apiaryio/drafter/releases/tag/v4.0.0-pre.8) for 58 | the list of changes. 59 | 60 | ## 2.0.0-pre.10 (2019-05-31) 61 | 62 | This update now uses Drafter 4.0.0-pre.7. Please see [Drafter 63 | 4.0.0-pre.7](https://github.com/apiaryio/drafter/releases/tag/v4.0.0-pre.7) for 64 | the list of changes. 65 | 66 | ## 2.0.0-pre.9 (2019-05-20) 67 | 68 | This update now uses Drafter 4.0.0-pre.6. Please see [Drafter 69 | 4.0.0-pre.6](https://github.com/apiaryio/drafter/releases/tag/v4.0.0-pre.6) for 70 | the list of changes. 71 | 72 | ### Enhancements 73 | 74 | - Added support for Node 12. 75 | 76 | ## 2.0.0-pre.8 (2019-05-09) 77 | 78 | This is a re-release of Protagonist 2.0.0-pre.7 due to problems with NPM 79 | preventing the release of 2.0.0-pre.7. 80 | 81 | ## 2.0.0-pre.7 (2019-05-07) 82 | 83 | This update now uses Drafter 4.0.0-pre.5. Please see [Drafter 84 | 4.0.0-pre.5](https://github.com/apiaryio/drafter/releases/tag/v4.0.0-pre.5) for 85 | the list of changes. 86 | 87 | ## 2.0.0-pre.6 (2019-04-26) 88 | 89 | This update now uses Drafter 4.0.0-pre.4. Please see [Drafter 90 | 4.0.0-pre.4](https://github.com/apiaryio/drafter/releases/tag/v4.0.0-pre.4) for 91 | the list of changes. 92 | 93 | ## 2.0.0-pre.5 (2019-04-08) 94 | 95 | This update now uses Drafter 4.0.0-pre.3. Please see [Drafter 96 | 4.0.0-pre.3](https://github.com/apiaryio/drafter/releases/tag/v4.0.0-pre.3) for 97 | the list of changes. 98 | 99 | ## 2.0.0-pre.4 100 | 101 | ### Enhancements 102 | 103 | * Added support for node 11. 104 | 105 | ## 2.0.0-pre.3 106 | 107 | ### Bug Fixes 108 | 109 | * Fixed a segfault while handling invalid options that contains unsupported 110 | properties with unsupported types. 111 | 112 | ## 2.0.0-pre.2 113 | 114 | This update now uses Drafter 4.0.0-pre.2. Please see [Drafter 115 | 4.0.0-pre.2](https://github.com/apiaryio/drafter/releases/tag/v4.0.0-pre.2) for 116 | the list of changes. 117 | 118 | ## 2.0.0-pre.1 119 | 120 | This update now uses Drafter 4.0.0-pre.1. Please see [Drafter 121 | 4.0.0-pre.1](https://github.com/apiaryio/drafter/releases/tag/v4.0.0-pre.1) for 122 | the list of changes. 123 | 124 | ## 2.0.0-pre.0 125 | 126 | This update now uses Drafter 4.0.0-pre.0. Please see [Drafter 127 | 4.0.0-pre.0](https://github.com/apiaryio/drafter/releases/tag/v4.0.0-pre.0) for 128 | the list of changes. 129 | 130 | - updated Async call to use Nan::AsyncQueue instead of node v8 functionality directly 131 | 132 | ### Breaking 133 | 134 | - Removed the option to select AST Type. The ouput will be only refract 135 | 136 | - Drop support for node.js versions 0.10 and 0.12 137 | 138 | ## 1.6.8 139 | 140 | This update now uses Drafter 3.2.7. Please see [Drafter 141 | 3.2.7](https://github.com/apiaryio/drafter/releases/tag/v3.2.7) for 142 | the list of changes. 143 | 144 | ### Bug Fixes 145 | 146 | * Fixed a bug where an option when set to false overrides the previous options. 147 | 148 | ## 1.6.7 149 | 150 | This update now uses Drafter 3.2.6. Please see [Drafter 151 | 3.2.6](https://github.com/apiaryio/drafter/releases/tag/v3.2.6) for 152 | the list of changes. 153 | 154 | ## 1.6.6 155 | 156 | This update now uses Drafter 3.2.5. Please see [Drafter 157 | 3.2.5](https://github.com/apiaryio/drafter/releases/tag/v3.2.5) for 158 | the list of changes. 159 | 160 | ## 1.6.5 161 | 162 | This update now uses Drafter 3.2.4. Please see [Drafter 163 | 3.2.4](https://github.com/apiaryio/drafter/releases/tag/v3.2.4) for 164 | the list of changes. 165 | 166 | ## 1.6.4 167 | 168 | This update now uses Drafter 3.2.3. Please see [Drafter 169 | 3.2.3](https://github.com/apiaryio/drafter/releases/tag/v3.2.3) for 170 | the list of changes. 171 | 172 | ## 1.6.3 173 | 174 | This update now uses Drafter 3.2.2. Please see [Drafter 175 | 3.2.2](https://github.com/apiaryio/drafter/releases/tag/v3.2.2) for 176 | the list of changes. 177 | 178 | ## 1.6.2 179 | 180 | This update now uses Drafter 3.2.1. Please see [Drafter 181 | 3.2.1](https://github.com/apiaryio/drafter/releases/tag/v3.2.1) for 182 | the list of changes. 183 | 184 | ## 1.6.1 185 | 186 | This update now uses Drafter 3.2.0. Please see [Drafter 187 | 3.2.0](https://github.com/apiaryio/drafter/releases/tag/v3.2.0) for 188 | the list of changes. 189 | 190 | ## 1.6.0 191 | 192 | ### Enhancements 193 | 194 | * Added `validate` and `validateSync` to just return the warnings and errors 195 | after parsing a blueprint. 196 | 197 | ## 1.5.2 198 | 199 | This update now uses Drafter 3.1.3. Please see [Drafter 200 | 3.1.3](https://github.com/apiaryio/drafter/releases/tag/v3.1.3) for 201 | the list of changes. 202 | 203 | ## 1.5.1 204 | 205 | This update now uses Drafter 3.1.2. Please see [Drafter 206 | 3.1.2](https://github.com/apiaryio/drafter/releases/tag/v3.1.2) for 207 | the list of changes. 208 | 209 | ## 1.5.0 210 | 211 | This update now uses Drafter 3.1.1. Please see [Drafter 212 | 3.1.1](https://github.com/apiaryio/drafter/releases/tag/v3.1.1) for 213 | the list of changes. 214 | 215 | ## 1.5.0-pre.0 216 | 217 | This update now uses Drafter 3.1.0-pre.0. Please see [Drafter 218 | 3.1.0-pre.0](https://github.com/apiaryio/drafter/releases/tag/v3.1.0-pre.0) for 219 | the list of changes. 220 | 221 | ## 1.4.1 222 | 223 | ### Bug Fixes 224 | 225 | - Fixes a problem when installing Protagonist on macOS. 226 | 227 | 228 | ## 1.4.0 229 | 230 | This update now uses Drafter 3.0.0 Please see [Drafter 231 | 3.0.0](https://github.com/apiaryio/drafter/releases/tag/v3.0.0) for the list of 232 | changes. 233 | 234 | ### Breaking 235 | 236 | * Protagonist now uses C++11. 237 | 238 | The following compiler versions are supported: 239 | 240 | * Microsoft Visual C++ 2013 or higher 241 | * GCC 4.8 or higher 242 | * Clang 3.5 or higher 243 | 244 | 245 | ## 1.3.3 246 | 247 | This update now uses Drafter 2.3.1 Please see [Drafter 248 | 2.3.1](https://github.com/apiaryio/drafter/releases/tag/v2.3.1) for the list of 249 | changes. 250 | 251 | ## 1.3.2 252 | 253 | This update adds support for node.js 5 and 6. 254 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014 Apiary Inc. . 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.github.com/apiaryio/api-blueprint/master/assets/logo_apiblueprint.png) 2 | 3 | # Protagonist - API Blueprint Parser for Node.js 4 | 5 | [![AppVeyor](https://ci.appveyor.com/api/projects/status/uaa6ivk97urmoucr/branch/master?svg=true)](https://ci.appveyor.com/project/Apiary/protagonist) 6 | 7 | Protagonist is a Node.js wrapper for the 8 | [Drafter](https://github.com/apiaryio/drafter), an API Blueprint parser. [API 9 | Blueprint](https://apiblueprint.org) is a language for describing web APIs. 10 | 11 | ## Install 12 | 13 | **NOTE:** *For general use we recommend that you use the [Drafter 14 | NPM](https://github.com/apiaryio/drafter-npm) package instead of Protagonist 15 | directly as Protagonist needs to be compiled which may not be possible in every 16 | situation.* 17 | 18 | Protagonist can be installed via the [Protagonist npm 19 | package](https://npmjs.org/package/protagonist) by 20 | [npm](https://docs.npmjs.com/cli/npm) or [yarn](https://yarnpkg.com/en/). 21 | 22 | ```sh 23 | $ npm install protagonist 24 | # or 25 | $ yarn install protagonist 26 | ``` 27 | 28 | Protagonist uses the 29 | [node-gyp](https://github.com/nodejs/node-gyp#installation) build tool which 30 | requires Python 2.7 (3.x is not supported) along with a compiler and other 31 | build tools. Take a look at their installation steps for 32 | [Linux](https://github.com/nodejs/node-gyp#on-unix), 33 | [macOS](https://github.com/nodejs/node-gyp#on-macos), and 34 | [Windows](https://github.com/nodejs/node-gyp#on-windows). 35 | 36 | ## Usage 37 | 38 | Protagonist offers the ability to both validate, and parse an API Blueprint. 39 | It offers the following APIs: 40 | 41 | - [`validate(source, options)`](#validate) 42 | - [`validateSync(source, options)`](#validate-sync) 43 | - [`parse(source, options)`](#parse) 44 | - [`parseSync(source, options)`](#parse-sync) 45 | 46 | **NOTE:** *It is not recommended to use the synchronous APIs as they can block 47 | the Node.JS event loop.* 48 | 49 | 50 | ### Validating an API Blueprint 51 | 52 | You can validate an API Blueprint to determine if the source is a valid 53 | document. The parse result will contain any errors or warnings that the 54 | document would emit during parsing. 55 | 56 | ```js 57 | const protagonist = require('protagonist'); 58 | 59 | const parseResult = await protagonist.parse('# My API'); 60 | console.log(JSON.stringify(parseResult, null, 2)); 61 | ``` 62 | 63 | or by using Promises: 64 | 65 | ```js 66 | const protagonist = require('protagonist'); 67 | 68 | protagonist.validate('# My API') 69 | .then((parseResult) => { 70 | console.log(JSON.stringify(parseResult, null, 2)); 71 | }) 72 | .catch((error) => { 73 | console.error(error); 74 | }); 75 | ``` 76 | 77 | See the [parse result](#parse-result) section below for more information about 78 | the structure of the parse result. 79 | 80 | 81 | #### Synchronous API 82 | 83 | ```js 84 | const protagonist = require('protagonist'); 85 | const parseResult = protagonist.validateSync('# My API'); 86 | ``` 87 | 88 | #### Validation Options 89 | 90 | Options can be passed to the parser as an optional second argument to both the asynchronous and synchronous interfaces: 91 | 92 | ```js 93 | const protagonist = require('protagonist'); 94 | 95 | const options = { 96 | requireBlueprintName: true, 97 | }; 98 | const parseResult = await protagonist.validate('# My API', options); 99 | ``` 100 | 101 | The available options are: 102 | 103 | Name | Description 104 | ---------------------- | ---------------------------------------------------------- 105 | `requireBlueprintName` | Require parsed blueprints have a title (default: false) 106 | 107 | 108 | ### Parsing an API Blueprint 109 | 110 | You can parse an API Blueprint with async/await: 111 | 112 | ```js 113 | const protagonist = require('protagonist'); 114 | 115 | const parseResult = await protagonist.parse('# My API'); 116 | console.log(JSON.stringify(parseResult, null, 2)); 117 | ``` 118 | 119 | or by using Promises: 120 | 121 | ```js 122 | const protagonist = require('protagonist'); 123 | 124 | protagonist.parse('# My API') 125 | .then((parseResult) => { 126 | console.log(JSON.stringify(parseResult, null, 2)); 127 | }) 128 | .catch((error) => { 129 | console.error(error); 130 | }); 131 | ``` 132 | 133 | See the [parse result](#parse-result) section below for more information about 134 | the structure of the parse result. 135 | 136 | 137 | #### Synchronous API 138 | 139 | ```js 140 | const parseResult = protagonist.parseSync('# My API'); 141 | ``` 142 | 143 | #### Parsing Options 144 | 145 | Options can be passed to the parser as an optional second argument to both the asynchronous and synchronous interfaces: 146 | 147 | ```js 148 | const options = { 149 | generateSourceMap: true, 150 | generateMessageBody: true, 151 | generateMessageBodySchema: true, 152 | }; 153 | const parseResult = await protagonist.parse('# My API', options); 154 | ``` 155 | 156 | The available options are: 157 | 158 | Name | Description 159 | --------------------------- | ---------------------------------------------------------- 160 | `requireBlueprintName` | Require parsed blueprints have a title (default: false) 161 | `generateSourceMap` | Enable sourcemap generation (default: false) 162 | `generateMessageBody` | Enable generation of messageBody from MSON (default: true) 163 | `generateMessageBodySchema` | Enable generation of messageBodySchema from MSON (default: true) 164 | 165 | 166 | ### Parse Result 167 | 168 | The format of the parse result is an [API Elements](https://apielements.org/) 169 | structure, there is also [API Elements: 170 | JS](https://github.com/apiaryio/api-elements.js) which contains tooling to 171 | handle this format in JavaScript. It is recommended to use the provided API 172 | Elements tooling to [prevent any tight 173 | coupling](http://apielements.org/en/latest/overview.html#consuming-documents) 174 | between your tool and a parse result. 175 | 176 | As an example, parsing the following API Blueprint: 177 | 178 | ```apib 179 | # GET / 180 | + Response 204 181 | ``` 182 | 183 | Would result in the following [API Elements Parse 184 | Result](http://apielements.org/en/latest/element-definitions.html#parse-result-element-types): 185 | 186 | ```json 187 | { 188 | "element": "parseResult", 189 | "content": [ 190 | { 191 | "element": "category", 192 | "meta": { 193 | "classes": { 194 | "element": "array", 195 | "content": [ 196 | { 197 | "element": "string", 198 | "content": "api" 199 | } 200 | ] 201 | }, 202 | "title": { 203 | "element": "string", 204 | "content": "" 205 | } 206 | }, 207 | "content": [ 208 | { 209 | "element": "resource", 210 | "meta": { 211 | "title": { 212 | "element": "string", 213 | "content": "" 214 | } 215 | }, 216 | "attributes": { 217 | "href": { 218 | "element": "string", 219 | "content": "/" 220 | } 221 | }, 222 | "content": [ 223 | { 224 | "element": "transition", 225 | "meta": { 226 | "title": { 227 | "element": "string", 228 | "content": "" 229 | } 230 | }, 231 | "content": [ 232 | { 233 | "element": "httpTransaction", 234 | "content": [ 235 | { 236 | "element": "httpRequest", 237 | "attributes": { 238 | "method": { 239 | "element": "string", 240 | "content": "GET" 241 | } 242 | }, 243 | "content": [] 244 | }, 245 | { 246 | "element": "httpResponse", 247 | "attributes": { 248 | "statusCode": { 249 | "element": "string", 250 | "content": "204" 251 | } 252 | }, 253 | "content": [] 254 | } 255 | ] 256 | } 257 | ] 258 | } 259 | ] 260 | } 261 | ] 262 | } 263 | ] 264 | } 265 | ``` 266 | 267 | ## Developing Protagonist 268 | 269 | You can use the following steps to build and test Protagonist. 270 | 271 | ```sh 272 | $ git clone --recursive https://github.com/apiaryio/protagonist.git 273 | $ cd protagonist 274 | $ npm install 275 | ``` 276 | 277 | While iterating on the package, you can use `npm run build` to compile 278 | Protagonist: 279 | 280 | ```sh 281 | $ npm run build 282 | $ npm test 283 | ``` 284 | 285 | Protagonist is built using [node-gyp](https://github.com/nodejs/node-gyp), you 286 | can consult their documentation for further information on the build system. 287 | 288 | ## License 289 | 290 | MIT License. See the [LICENSE](https://github.com/apiaryio/protagonist/blob/master/LICENSE) file. 291 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against this version of Node.js 2 | os: Visual Studio 2015 CTP 3 | 4 | environment: 5 | matrix: 6 | - nodejs_version: "4" 7 | - nodejs_version: "6" 8 | - nodejs_version: "8" 9 | - nodejs_version: "10" 10 | 11 | # Install scripts. (runs after repo cloning) 12 | install: 13 | # Fetch project submodules 14 | - git submodule update --init --recursive 15 | # Get the latest stable version of Node.js or io.js 16 | - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) 17 | # install modules 18 | - npm install 19 | 20 | # Post-install test scripts. 21 | test_script: 22 | # Output useful info for debugging. 23 | - node --version 24 | - npm --version 25 | # run tests 26 | - npm test 27 | 28 | # Don't actually build. 29 | build: off 30 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "includes": [ 3 | "drafter/common.gypi" 4 | ], 5 | "targets": [ 6 | { 7 | "target_name": "protagonist", 8 | "include_dirs": [ 9 | "drafter/src", 10 | "", 6 | "main": "./build/Release/protagonist", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/apiaryio/protagonist.git" 10 | }, 11 | "engines": { 12 | "node": ">=12" 13 | }, 14 | "dependencies": { 15 | "nan": "^2.17.0" 16 | }, 17 | "devDependencies": { 18 | "async": "^2.6.1", 19 | "chai": "^4.3.7", 20 | "glob": "^7.1.2", 21 | "mocha": "^10.2.0", 22 | "node-gyp": "^9.3.0" 23 | }, 24 | "scripts": { 25 | "build": "node-gyp build", 26 | "prepack": "cp drafter/THIRD_PARTY_LICENSES.txt THIRD_PARTY_LICENSES.txt", 27 | "test:exclude-await": "mocha --exclude test/await-test.js", 28 | "test:await": "mocha", 29 | "test": "npm run test:await" 30 | }, 31 | "files": [ 32 | "THIRD_PARTY_LICENSES.txt", 33 | "binding.gyp", 34 | "src/*.cc", 35 | "src/*.h", 36 | "drafter/common.gypi", 37 | "drafter/drafter.gyp", 38 | "drafter/packages/drafter/src/*.cc", 39 | "drafter/packages/drafter/src/*.h", 40 | "drafter/packages/drafter/src/backend/*.cc", 41 | "drafter/packages/drafter/src/backend/*.h", 42 | "drafter/packages/drafter/src/parser/*.cc", 43 | "drafter/packages/drafter/src/parser/*.h", 44 | "drafter/packages/drafter/src/utils/*.cc", 45 | "drafter/packages/drafter/src/utils/*.h", 46 | "drafter/packages/drafter/src/utils/so/*.cc", 47 | "drafter/packages/drafter/src/utils/so/*.h", 48 | "drafter/packages/drafter/src/utils/log/*.cc", 49 | "drafter/packages/drafter/src/utils/log/*.h", 50 | "drafter/packages/drafter/src/refract/*.cc", 51 | "drafter/packages/drafter/src/refract/*.h", 52 | "drafter/packages/drafter/src/refract/dsd/*.cc", 53 | "drafter/packages/drafter/src/refract/dsd/*.h", 54 | "drafter/packages/boost/boost", 55 | "drafter/packages/PEGTL/include/tao/*.hpp", 56 | "drafter/packages/PEGTL/include/tao/pegtl/*.hpp", 57 | "drafter/packages/PEGTL/include/tao/pegtl/analysis/*.hpp", 58 | "drafter/packages/PEGTL/include/tao/pegtl/contrib/*.hpp", 59 | "drafter/packages/PEGTL/include/tao/pegtl/internal/*.hpp", 60 | "drafter/packages/apib/include/apib/syntax/*.h", 61 | "drafter/packages/apib/src/MediaType.cc", 62 | "drafter/packages/apib-parser/src/*.cc", 63 | "drafter/packages/apib-parser/include/apib/parser/*.h", 64 | "drafter/packages/apib-parser/src/grammar/*.cc", 65 | "drafter/packages/apib-parser/src/grammar/*.h", 66 | "drafter/packages/apib-parser/src/markdown-parser/*.cc", 67 | "drafter/packages/apib-parser/src/markdown-parser/*.h", 68 | "drafter/packages/apib-parser/src/posix/RegexMatch.cc", 69 | "drafter/packages/apib-parser/src/win/RegexMatch.cc", 70 | "drafter/packages/apib-parser/src/snowcrash/*.h", 71 | "drafter/packages/apib-parser/src/snowcrash/*.cc", 72 | "drafter/packages/apib-parser/src/snowcrash/posix/RegexMatch.cc", 73 | "drafter/packages/apib-parser/src/snowcrash/win/RegexMatch.cc", 74 | "drafter/packages/sundown/src/*.c", 75 | "drafter/packages/sundown/src/*.h", 76 | "drafter/packages/sundown/html/*.c", 77 | "drafter/packages/sundown/html/*.h", 78 | "drafter/packages/variant/include/mpark/*.hpp" 79 | ], 80 | "license": "MIT" 81 | } 82 | -------------------------------------------------------------------------------- /src/options.cc: -------------------------------------------------------------------------------- 1 | #include "options.h" 2 | 3 | #include 4 | 5 | #include "drafter.h" 6 | 7 | using namespace protagonist; 8 | 9 | namespace 10 | { 11 | void ensure_parse_options(parse_options_ptr& opts) noexcept 12 | { 13 | if (!opts) { 14 | opts.reset(drafter_init_parse_options()); 15 | } 16 | } 17 | } // namespace 18 | 19 | void parse_options_deleter::operator()(drafter_parse_options* obj) noexcept 20 | { 21 | drafter_free_parse_options(obj); 22 | } 23 | 24 | Options::Options() noexcept 25 | : parse_options_{nullptr}, serialize_sourcemaps_{false} 26 | { 27 | } 28 | 29 | const drafter_parse_options* Options::parseOptions() const noexcept 30 | { 31 | return parse_options_.get(); 32 | } 33 | 34 | parse_options_ptr Options::claimParseOptions() noexcept 35 | { 36 | return std::move(parse_options_); 37 | } 38 | 39 | void Options::setSerializeSourcemaps() noexcept 40 | { 41 | serialize_sourcemaps_ = true; 42 | } 43 | 44 | bool Options::serializeSourcemaps() const noexcept 45 | { 46 | return serialize_sourcemaps_; 47 | } 48 | 49 | void Options::setNameRequired() noexcept 50 | { 51 | ensure_parse_options(parse_options_); 52 | drafter_set_name_required(parse_options_.get()); 53 | } 54 | 55 | void Options::setSkipGenBodies() noexcept 56 | { 57 | ensure_parse_options(parse_options_); 58 | drafter_set_skip_gen_bodies(parse_options_.get()); 59 | } 60 | 61 | void Options::setSkipGenBodySchemas() noexcept 62 | { 63 | ensure_parse_options(parse_options_); 64 | drafter_set_skip_gen_body_schemas(parse_options_.get()); 65 | } 66 | -------------------------------------------------------------------------------- /src/options.h: -------------------------------------------------------------------------------- 1 | #ifndef OPTIONS_H 2 | #define OPTIONS_H 3 | 4 | #include 5 | 6 | struct drafter_parse_options; 7 | 8 | namespace protagonist 9 | { 10 | struct parse_options_deleter { 11 | void operator()(drafter_parse_options* obj) noexcept; 12 | }; 13 | 14 | using parse_options_ptr = 15 | std::unique_ptr; 16 | 17 | class Options 18 | { 19 | parse_options_ptr parse_options_; 20 | bool serialize_sourcemaps_; 21 | 22 | public: 23 | Options() noexcept; 24 | 25 | const drafter_parse_options* parseOptions() const noexcept; 26 | void setNameRequired() noexcept; 27 | void setSkipGenBodies() noexcept; 28 | void setSkipGenBodySchemas() noexcept; 29 | 30 | bool serializeSourcemaps() const noexcept; 31 | void setSerializeSourcemaps() noexcept; 32 | 33 | parse_options_ptr claimParseOptions() noexcept; 34 | }; 35 | } // namespace protagonist 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/options_parser.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "protagonist.h" 4 | #include "options.h" 5 | 6 | using namespace v8; 7 | using namespace protagonist; 8 | 9 | //static const std::string RenderDescriptionsOptionKey = "renderDescriptions"; 10 | static const std::string RequireBlueprintNameOptionKey = "requireBlueprintName"; 11 | static const std::string ExportSourcemapOptionKey = "exportSourcemap"; 12 | static const std::string GenerateSourceMapOptionKey = "generateSourceMap"; 13 | static const std::string GenerateMessageBodyOptionKey = "generateMessageBody"; 14 | static const std::string GenerateMessageBodySchemaOptionKey = "generateMessageBodySchema"; 15 | 16 | static ErrorMessage ErrorMessageForUnrecognisedOption(const Nan::Utf8String& key, const bool forValidate) 17 | { 18 | std::stringstream ss; 19 | ss << "unrecognized option '" << *key << "', expected: "; 20 | ss << "'" << RequireBlueprintNameOptionKey << "'"; 21 | ss << ", '" << GenerateMessageBodyOptionKey<< "'"; 22 | ss << ", '" << GenerateMessageBodySchemaOptionKey<< "'"; 23 | 24 | if (!forValidate) { 25 | ss << ", '" << GenerateSourceMapOptionKey << "'"; 26 | } 27 | 28 | return ss.str(); 29 | } 30 | 31 | ErrorMessage protagonist::ParseOptionsObject(Options& options, Local optionsObject, bool forValidate) { 32 | Local context = Nan::GetCurrentContext(); 33 | Isolate *isolate = context->GetIsolate(); 34 | 35 | const Local properties = optionsObject->GetPropertyNames(context).ToLocalChecked(); 36 | const uint32_t length = properties->Length(); 37 | 38 | for (uint32_t i = 0 ; i < length ; ++i) { 39 | Local key = properties->Get(context, i).ToLocalChecked(); 40 | const Nan::Utf8String strKey(key); 41 | 42 | v8::MaybeLocal maybeValue = optionsObject->Get(context, key); 43 | 44 | // all options are boolean w/ false value default 45 | const Local value = maybeValue.FromMaybe(Local(False(isolate))); 46 | 47 | if (RequireBlueprintNameOptionKey == *strKey) { 48 | if (value->IsTrue()) options.setNameRequired(); 49 | } else if (GenerateMessageBodyOptionKey == *strKey) { 50 | if (value->IsFalse()) options.setSkipGenBodies(); 51 | } else if (GenerateMessageBodySchemaOptionKey == *strKey) { 52 | if (value->IsFalse()) options.setSkipGenBodySchemas(); 53 | } else if (!forValidate) { 54 | if (ExportSourcemapOptionKey == *strKey || 55 | GenerateSourceMapOptionKey == *strKey) { 56 | if (value->IsTrue()) options.setSerializeSourcemaps(); 57 | } else { 58 | return ErrorMessageForUnrecognisedOption(strKey, forValidate); 59 | } 60 | } else { 61 | return ErrorMessageForUnrecognisedOption(strKey, forValidate); 62 | } 63 | } 64 | return {}; 65 | } 66 | -------------------------------------------------------------------------------- /src/parse_async.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "drafter.h" 3 | #include "protagonist.h" 4 | #include "refractToV8.h" 5 | #include "options.h" 6 | 7 | #include 8 | 9 | using std::string; 10 | using namespace v8; 11 | using namespace protagonist; 12 | 13 | using Nan::AsyncQueueWorker; 14 | using Nan::AsyncWorker; 15 | using Nan::Callback; 16 | using Nan::HandleScope; 17 | 18 | namespace 19 | { 20 | class ParseWorker : public AsyncWorker 21 | { 22 | Options options; 23 | Nan::Utf8String* sourceData; 24 | 25 | Nan::Persistent* persistent; 26 | 27 | // Output 28 | drafter_result* result; 29 | int parse_err_code; 30 | 31 | public: 32 | ParseWorker(Callback* callback, 33 | Nan::Persistent* persistent, 34 | Options opts, 35 | Nan::Utf8String* sourceData) 36 | : AsyncWorker(callback) 37 | , options(std::move(opts)) 38 | , sourceData(sourceData) 39 | , persistent(persistent) 40 | , result(nullptr) 41 | , parse_err_code(-1) 42 | { 43 | } 44 | 45 | void Execute() 46 | { 47 | parse_err_code = drafter_parse_blueprint(*(*sourceData), &result, options.parseOptions()); 48 | } 49 | 50 | void HandleOKCallback() 51 | { 52 | Nan::HandleScope scope; 53 | Local error = Nan::Null(); 54 | Local v8refract = Nan::Null(); 55 | 56 | switch (parse_err_code) { 57 | case DRAFTER_EUNKNOWN: 58 | error = Nan::Error("Parser: Unknown Error"); 59 | break; 60 | case DRAFTER_EINVALID_INPUT: 61 | error = Nan::Error("Parser: Invalid Input"); 62 | break; 63 | case DRAFTER_EINVALID_OUTPUT: 64 | error = Nan::Error("Parser: Invalid Output"); 65 | break; 66 | default: 67 | // do nothing 68 | ; 69 | } 70 | 71 | if (result) { 72 | v8refract = refract2v8(result, options.serializeSourcemaps()); 73 | } 74 | 75 | if (persistent) { 76 | auto resolver = Nan::New(*persistent); 77 | 78 | if (parse_err_code >= 0) { 79 | resolver->Resolve(Nan::GetCurrentContext(), v8refract); 80 | } else { 81 | resolver->Reject(Nan::GetCurrentContext(), error); 82 | } 83 | 84 | #if NODE_MODULE_VERSION >= NODE_14_0_MODULE_VERSION 85 | v8::Isolate::GetCurrent()->PerformMicrotaskCheckpoint(); 86 | #else 87 | v8::Isolate::GetCurrent()->RunMicrotasks(); 88 | #endif 89 | 90 | return; 91 | } else if (callback) { 92 | Local argv[] = { Nan::Null(), Nan::Null() }; 93 | 94 | if (parse_err_code >= 0) { 95 | argv[1] = v8refract; 96 | } else { 97 | argv[0] = error; 98 | } 99 | 100 | callback->Call(2, argv, async_resource); 101 | return; 102 | } 103 | 104 | // this should never happen unless sent nullptr to `callback` and `persitent` from calling function 105 | assert(0); 106 | } 107 | 108 | virtual ~ParseWorker() 109 | { 110 | if (result) { 111 | drafter_free_result(result); 112 | result = nullptr; 113 | } 114 | 115 | if (sourceData) { 116 | delete sourceData; 117 | sourceData = nullptr; 118 | } 119 | } 120 | }; 121 | } 122 | 123 | /** 124 | * posible args variant 125 | * (source) -> promise 126 | * (source, options) -> promise 127 | * (source, callback) -> callback 128 | * (source, option, callback) -> callback 129 | */ 130 | 131 | static bool isLastParamCallback(const Nan::FunctionCallbackInfo& info) 132 | { 133 | return info[info.Length() - 1]->IsFunction(); 134 | } 135 | 136 | NAN_METHOD(protagonist::Parse) 137 | { 138 | Nan::HandleScope scope; 139 | Nan::Persistent* persistent = nullptr; 140 | Callback* callback = nullptr; 141 | v8::Local resolver; 142 | 143 | // Check arguments 144 | 145 | size_t optionIndex = 0; 146 | size_t callbackIndex = 0; 147 | 148 | if (info.Length() < 1 || info.Length() > 3) { 149 | Nan::ThrowTypeError("wrong number of arguments, `parse(string, options, callback)` expected"); 150 | return; 151 | } 152 | 153 | if (!info[0]->IsString()) { 154 | Nan::ThrowTypeError("wrong 1st argument - string expected, `parse(string, options, [callback])`"); 155 | return; 156 | } 157 | 158 | if (isLastParamCallback(info)) { // last arg is callback funtion 159 | if (info.Length() == 3) { 160 | optionIndex = 1; 161 | } 162 | 163 | callbackIndex = info.Length() - 1; 164 | } else { // is Promise 165 | if (info.Length() == 2) { 166 | optionIndex = 1; 167 | } else if (info.Length() > 2) { // promise shoud not have more than 2 params 168 | Nan::ThrowTypeError("wrong number of arguments, `parse(string, options)` expected"); 169 | return; 170 | } 171 | } 172 | 173 | if (optionIndex && !info[optionIndex]->IsObject()) { 174 | Nan::ThrowTypeError("wrong 2nd argument - object expected, `parse(string, options, [callback])`"); 175 | return; 176 | } 177 | 178 | // Prepare options 179 | Options options{}; 180 | 181 | if (optionIndex) { 182 | ErrorMessage msg = ParseOptionsObject(options, Local::Cast(info[optionIndex]), false); 183 | 184 | if (!msg.empty()) { 185 | Nan::ThrowTypeError(msg.c_str()); 186 | return; 187 | } 188 | } 189 | 190 | if (callbackIndex) { 191 | callback = new Callback(info[callbackIndex].As()); 192 | } else { 193 | resolver = v8::Promise::Resolver::New(Nan::GetCurrentContext()).ToLocalChecked(); 194 | persistent = new Nan::Persistent(resolver); 195 | } 196 | 197 | AsyncQueueWorker( 198 | new ParseWorker(callback, persistent, std::move(options), new Nan::Utf8String(info[0]))); 199 | 200 | if (!callbackIndex) { 201 | info.GetReturnValue().Set(resolver->GetPromise()); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/parse_sync.cc: -------------------------------------------------------------------------------- 1 | #include "protagonist.h" 2 | 3 | #include "drafter.h" 4 | #include "refractToV8.h" 5 | #include "options.h" 6 | 7 | using namespace v8; 8 | using namespace protagonist; 9 | 10 | NAN_METHOD(protagonist::ParseSync) { 11 | Nan::HandleScope scope; 12 | Local context = Nan::GetCurrentContext(); 13 | 14 | // Check arguments 15 | if (info.Length() != 1 && info.Length() != 2) { 16 | Nan::ThrowTypeError("wrong number of arguments, `parseSync(string, options)` expected"); 17 | return; 18 | } 19 | 20 | if (!info[0]->IsString()) { 21 | Nan::ThrowTypeError("wrong 1st argument - string expected, `parseSync(string, options)`"); 22 | return; 23 | } 24 | 25 | if (info.Length() == 2 && !info[1]->IsObject()) { 26 | Nan::ThrowTypeError("wrong 2nd argument - object expected, `parseSync(string, options)`"); 27 | return; 28 | } 29 | 30 | // Get source data 31 | Nan::Utf8String sourceData(info[0]->ToString(context).ToLocalChecked()); 32 | 33 | // Prepare options 34 | Options options; 35 | 36 | if (info.Length() == 2) { 37 | ErrorMessage err = ParseOptionsObject(options, info[1]->ToObject(context).ToLocalChecked(), false); 38 | 39 | if (!err.empty()) { 40 | Nan::ThrowTypeError(err.c_str()); 41 | } 42 | } 43 | 44 | // Parse the source data 45 | drafter_result *result; 46 | int err_code = drafter_parse_blueprint(*sourceData, 47 | &result, 48 | options.parseOptions()); 49 | switch (err_code) { 50 | case DRAFTER_EUNKNOWN: 51 | Nan::ThrowError("Parser: Unknown Error"); 52 | break; 53 | case DRAFTER_EINVALID_INPUT: 54 | Nan::ThrowError("Parser: Invalid Input"); 55 | break; 56 | case DRAFTER_EINVALID_OUTPUT: 57 | Nan::ThrowError("Parser: Invalid Output"); 58 | break; 59 | default: 60 | break; 61 | } 62 | 63 | Local v8result = refract2v8(result, options.serializeSourcemaps()); 64 | drafter_free_result(result); 65 | info.GetReturnValue().Set(v8result); 66 | } 67 | -------------------------------------------------------------------------------- /src/protagonist.cc: -------------------------------------------------------------------------------- 1 | #include "protagonist.h" 2 | 3 | using namespace v8; 4 | using namespace protagonist; 5 | 6 | NAN_MODULE_INIT(InitAll) { 7 | Nan::Set(target, Nan::New("parse").ToLocalChecked(), 8 | Nan::GetFunction(Nan::New(Parse)).ToLocalChecked()); 9 | 10 | Nan::Set(target, Nan::New("parseSync").ToLocalChecked(), 11 | Nan::GetFunction(Nan::New(ParseSync)).ToLocalChecked()); 12 | 13 | Nan::Set(target, Nan::New("validate").ToLocalChecked(), 14 | Nan::GetFunction(Nan::New(Validate)).ToLocalChecked()); 15 | 16 | Nan::Set(target, Nan::New("validateSync").ToLocalChecked(), 17 | Nan::GetFunction(Nan::New(ValidateSync)).ToLocalChecked()); 18 | } 19 | 20 | NODE_MODULE(protagonist, InitAll) 21 | -------------------------------------------------------------------------------- /src/protagonist.h: -------------------------------------------------------------------------------- 1 | #ifndef PROTAGONIST_H 2 | #define PROTAGONIST_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include "nan.h" 9 | 10 | namespace protagonist { 11 | 12 | using ErrorMessage = std::string; 13 | class Options; 14 | 15 | ErrorMessage ParseOptionsObject(Options&, v8::Local, bool); 16 | 17 | // 18 | // Parse functions 19 | // 20 | extern NAN_METHOD(Parse); 21 | extern NAN_METHOD(ParseSync); 22 | 23 | // 24 | // Validate functions 25 | // 26 | extern NAN_METHOD(Validate); 27 | extern NAN_METHOD(ValidateSync); 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/refractToV8.cc: -------------------------------------------------------------------------------- 1 | #include "refractToV8.h" 2 | #include 3 | #include "Serialize.h" // Included because of serialize key for parseResult 4 | #include "drafter.h" 5 | #include "nan.h" 6 | #include "refract/Element.h" 7 | #include "refract/Exception.h" 8 | #include "refract/FilterVisitor.h" // Filtering just annotations 9 | #include "refract/Iterate.h" 10 | #include "refract/VisitorUtils.h" // TypeQuery and GetValue 11 | 12 | using namespace v8; 13 | using namespace refract; 14 | 15 | struct v8Wrapper { 16 | bool sourcemap; 17 | Local v8_value; 18 | 19 | v8Wrapper() : sourcemap(false){}; 20 | 21 | v8Wrapper(bool sourcemap) : sourcemap(sourcemap){}; 22 | 23 | void operator()(const NullElement& e); 24 | void operator()(const StringElement& e); 25 | void operator()(const NumberElement& e); 26 | void operator()(const BooleanElement& e); 27 | void operator()(const MemberElement& e); 28 | void operator()(const ArrayElement& e); 29 | void operator()(const EnumElement& e); 30 | void operator()(const ExtendElement& e); 31 | void operator()(const OptionElement& e); 32 | void operator()(const SelectElement& e); 33 | void operator()(const ObjectElement& e); 34 | void operator()(const IElement& e){}; 35 | void operator()(const RefElement& e); 36 | void operator()(const HolderElement& e); 37 | 38 | template 39 | void operator()(const T& e) 40 | { 41 | static_assert(sizeof(T) == 0, "Unknown Element in v8Wrapper"); 42 | } 43 | }; 44 | 45 | const std::set basic_elements = { 46 | "null", "string", "number", "boolean", "array", "object", "member"}; 47 | 48 | Local v8_string(const std::string& value) 49 | { 50 | return Nan::New(value.c_str()).ToLocalChecked(); 51 | } 52 | 53 | Local ElementToObject(const IElement* e, bool sourcemap) 54 | { 55 | v8Wrapper f(sourcemap); 56 | Visitor v(f); 57 | e->content(v); 58 | return f.v8_value; 59 | } 60 | 61 | Local v8ElementCollection(const InfoElements& collection, 62 | bool sourcemap) 63 | { 64 | Local result = Nan::New(); 65 | 66 | for (const auto& el : collection) { 67 | Local key; 68 | Local value; 69 | 70 | if (!sourcemap) { 71 | if (el.first == "sourceMap") { 72 | continue; 73 | } 74 | } 75 | 76 | key = v8_string(el.first); 77 | 78 | if (el.second) { 79 | value = ElementToObject(el.second.get(), sourcemap); 80 | } 81 | 82 | Nan::Set(result, key, value); 83 | } 84 | 85 | return result; 86 | } 87 | 88 | Local v8Element(const IElement& e, bool sourcemap) 89 | { 90 | Local res = Nan::New(); 91 | Nan::Set(res, v8_string("element"), v8_string(e.element())); 92 | if (e.meta().size() > 0) { 93 | Nan::Set(res, v8_string("meta"), v8ElementCollection(e.meta(), sourcemap)); 94 | } 95 | 96 | if (e.element() == "annotation") { 97 | sourcemap = true; 98 | } 99 | 100 | if (e.attributes().size() > 0) { 101 | Local attrs = v8ElementCollection(e.attributes(), sourcemap); 102 | MaybeLocal maybeProps = Nan::GetOwnPropertyNames(attrs); 103 | if (!maybeProps.IsEmpty()) { 104 | Local props = maybeProps.ToLocalChecked(); 105 | if (props->Length() > 0) { 106 | Nan::Set(res, v8_string("attributes"), attrs); 107 | } 108 | } 109 | } 110 | return res; 111 | } 112 | 113 | template 114 | Local v8ValueList(const T& e, bool sourcemap) 115 | { 116 | Local obj = v8Element(e, sourcemap); 117 | 118 | if (!e.empty()) { 119 | size_t i = 0; 120 | Local array = Nan::New(); 121 | 122 | for (const auto& el : e.get()) { 123 | Nan::Set(array, i, ElementToObject(el.get(), sourcemap)); 124 | ++i; 125 | } 126 | 127 | Nan::Set(obj, v8_string("content"), array); 128 | } 129 | 130 | return obj; 131 | } 132 | 133 | Local v8RefElement(const RefElement& e, bool sourcemap) 134 | { 135 | Local obj = v8Element(e, sourcemap); 136 | 137 | Nan::Set(obj, v8_string("content"), v8_string(e.get().symbol())); 138 | 139 | return obj; 140 | } 141 | 142 | void v8Wrapper::operator()(const NullElement& e) 143 | { 144 | Local obj = v8Element(e, sourcemap); 145 | Nan::Set(obj, v8_string("content"), Nan::Null()); 146 | v8_value = obj; 147 | } 148 | 149 | void v8Wrapper::operator()(const StringElement& e) 150 | { 151 | Local obj = v8Element(e, sourcemap); 152 | if (!e.empty()) { 153 | Nan::Set(obj, v8_string("content"), v8_string(e.get().get())); 154 | } 155 | v8_value = obj; 156 | } 157 | 158 | void v8Wrapper::operator()(const NumberElement& e) 159 | { 160 | Local obj = v8Element(e, sourcemap); 161 | if (!e.empty()) { 162 | Nan::JSON NanJSON; 163 | Nan::Set(obj, v8_string("content"), 164 | NanJSON.Parse(v8_string(e.get().get())).ToLocalChecked()); 165 | } 166 | v8_value = obj; 167 | } 168 | 169 | void v8Wrapper::operator()(const BooleanElement& e) 170 | { 171 | Local obj = v8Element(e, sourcemap); 172 | if (!e.empty()) { 173 | Nan::Set(obj, v8_string("content"), Nan::New(e.get())); 174 | } 175 | v8_value = obj; 176 | } 177 | 178 | void v8Wrapper::operator()(const MemberElement& e) 179 | { 180 | Local content = Nan::New(); 181 | Local key; 182 | Local value; 183 | 184 | if (e.get().key()) { 185 | key = ElementToObject(e.get().key(), sourcemap); 186 | } 187 | 188 | if (e.get().value()) { 189 | value = ElementToObject(e.get().value(), sourcemap); 190 | } 191 | 192 | Local obj = v8Element(e, sourcemap); 193 | Nan::Set(content, v8_string("key"), key); 194 | Nan::Set(content, v8_string("value"), value); 195 | Nan::Set(obj, v8_string("content"), content); 196 | v8_value = obj; 197 | } 198 | 199 | void v8Wrapper::operator()(const ArrayElement& e) 200 | { 201 | // XXX is GetValue appropriate here? shouldn't we just wrap drafter output? 202 | const auto* val = GetValue{}(e); 203 | 204 | Local array = Nan::New(); 205 | 206 | if (val && !val->empty()) { 207 | size_t i = 0; 208 | for (const auto& el : val->get()) { 209 | if (el) { 210 | Nan::Set(array, i, ElementToObject(el.get(), sourcemap)); 211 | } 212 | ++i; 213 | } 214 | } 215 | 216 | Local res = v8Element(e, sourcemap); 217 | if (!e.empty()) { 218 | Nan::Set(res, v8_string("content"), array); 219 | } 220 | v8_value = res; 221 | } 222 | 223 | void v8Wrapper::operator()(const EnumElement& e) 224 | { 225 | Local obj = v8Element(e, sourcemap); 226 | 227 | if (!e.empty()) { 228 | Nan::Set(obj, v8_string("content"), 229 | ElementToObject(e.get().value(), sourcemap)); 230 | } 231 | 232 | v8_value = obj; 233 | } 234 | 235 | void v8Wrapper::operator()(const ExtendElement& e) 236 | { 237 | throw NotImplemented("ExtendElement serialization Not Implemented"); 238 | } 239 | 240 | void v8Wrapper::operator()(const OptionElement& e) 241 | { 242 | v8_value = v8ValueList(e, sourcemap); 243 | } 244 | 245 | void v8Wrapper::operator()(const SelectElement& e) 246 | { 247 | v8_value = v8ValueList(e, sourcemap); 248 | } 249 | 250 | void v8Wrapper::operator()(const RefElement& e) 251 | { 252 | v8_value = v8RefElement(e, sourcemap); 253 | } 254 | 255 | void v8Wrapper::operator()(const HolderElement& e) 256 | { 257 | Local obj = v8Element(e, sourcemap); 258 | 259 | if (!e.empty()) { 260 | Nan::Set(obj, v8_string("content"), 261 | ElementToObject(e.get().data(), sourcemap)); 262 | } 263 | 264 | v8_value = obj; 265 | } 266 | 267 | void v8Wrapper::operator()(const ObjectElement& e) 268 | { 269 | Local obj = v8Element(e, sourcemap); 270 | 271 | if (!e.empty() && !e.get().empty()) { 272 | Local array = Nan::New(); 273 | size_t i = 0; 274 | 275 | for (const auto& el : e.get()) { 276 | Nan::Set(array, i, ElementToObject(el.get(), sourcemap)); 277 | ++i; 278 | } 279 | 280 | Nan::Set(obj, v8_string("content"), array); 281 | } 282 | 283 | v8_value = obj; 284 | } 285 | 286 | Local refract2v8(const IElement* res, 287 | bool sourceMaps) 288 | { 289 | assert(res); 290 | 291 | v8Wrapper f(sourceMaps); 292 | Visitor v(f); 293 | res->content(v); 294 | 295 | return f.v8_value; 296 | } 297 | 298 | Local annotations2v8(const IElement* res) 299 | { 300 | FilterVisitor filter(query::Element("annotation")); 301 | Iterate iterate(filter); 302 | 303 | iterate(*res); 304 | 305 | if (!filter.empty()) { 306 | std::vector elements = filter.elements(); 307 | Local array = Nan::New(); 308 | size_t i = 0; 309 | 310 | for (std::vector::const_iterator it = elements.begin(); 311 | it != elements.end(); ++i, ++it) { 312 | if (*it) { 313 | Nan::Set(array, i, ElementToObject(*it, true)); 314 | } 315 | } 316 | 317 | Local annotations = Nan::New(); 318 | Nan::Set(annotations, v8_string("element"), 319 | v8_string(drafter::SerializeKey::ParseResult)); 320 | Nan::Set(annotations, v8_string("content"), array); 321 | return annotations; 322 | } 323 | 324 | return Nan::Null(); 325 | } 326 | -------------------------------------------------------------------------------- /src/refractToV8.h: -------------------------------------------------------------------------------- 1 | #ifndef REFRACT2V8_H 2 | #define REFRACT2V8_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | v8::Local refract2v8(const refract::IElement* res, bool sourceMaps); 10 | v8::Local annotations2v8(const refract::IElement* res); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/validate_async.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "protagonist.h" 3 | #include "refractToV8.h" 4 | #include "options.h" 5 | 6 | #include 7 | 8 | using std::string; 9 | using namespace v8; 10 | using namespace protagonist; 11 | 12 | using Nan::AsyncQueueWorker; 13 | using Nan::AsyncWorker; 14 | using Nan::Callback; 15 | using Nan::HandleScope; 16 | 17 | namespace 18 | { 19 | 20 | class ValidateWorker : public AsyncWorker 21 | { 22 | parse_options_ptr parseOptions; 23 | Nan::Utf8String* sourceData; 24 | Nan::Persistent* persistent; 25 | 26 | // Output 27 | drafter_result* result; 28 | int parse_err_code; 29 | 30 | public: 31 | ValidateWorker(Callback* callback, 32 | Nan::Persistent* persistent, 33 | parse_options_ptr parseOptions, 34 | Nan::Utf8String* sourceData) 35 | : AsyncWorker(callback) 36 | , parseOptions(std::move(parseOptions)) 37 | , sourceData(sourceData) 38 | , persistent(persistent) 39 | , result(nullptr) 40 | , parse_err_code(-1) 41 | { 42 | } 43 | 44 | void Execute() 45 | { 46 | parse_err_code = drafter_check_blueprint(*(*sourceData), &result, parseOptions.get()); 47 | } 48 | 49 | void HandleOKCallback() 50 | { 51 | Nan::HandleScope scope; 52 | Local error = Nan::Null(); 53 | Local v8refract = Nan::Null(); 54 | 55 | switch (parse_err_code) { 56 | case DRAFTER_EUNKNOWN: 57 | error = Nan::Error("Parser: Unknown Error"); 58 | break; 59 | case DRAFTER_EINVALID_INPUT: 60 | error = Nan::Error("Parser: Invalid Input"); 61 | break; 62 | case DRAFTER_EINVALID_OUTPUT: 63 | error = Nan::Error("Parser: Invalid Output"); 64 | break; 65 | default: 66 | // do nothing 67 | ; 68 | } 69 | 70 | if (result) { 71 | v8refract = refract2v8(result, true); 72 | } 73 | 74 | if (persistent) { 75 | auto resolver = Nan::New(*persistent); 76 | 77 | if (parse_err_code >= 0) { 78 | resolver->Resolve(Nan::GetCurrentContext(), v8refract); 79 | } else { 80 | resolver->Reject(Nan::GetCurrentContext(), error); 81 | } 82 | 83 | #if NODE_MODULE_VERSION >= NODE_14_0_MODULE_VERSION 84 | v8::Isolate::GetCurrent()->PerformMicrotaskCheckpoint(); 85 | #else 86 | v8::Isolate::GetCurrent()->RunMicrotasks(); 87 | #endif 88 | 89 | return; 90 | } else if (callback) { 91 | Local argv[] = { Nan::Null(), Nan::Null() }; 92 | 93 | if (parse_err_code >= 0) { 94 | argv[1] = v8refract; 95 | } else { 96 | argv[0] = error; 97 | } 98 | 99 | callback->Call(2, argv, async_resource); 100 | return; 101 | } 102 | 103 | // this should never happen unless sent nullptr to `callback` and `persitent` from calling function 104 | assert(0); 105 | } 106 | 107 | virtual ~ValidateWorker() 108 | { 109 | if (result) { 110 | drafter_free_result(result); 111 | result = nullptr; 112 | } 113 | 114 | if (sourceData) { 115 | delete sourceData; 116 | sourceData = nullptr; 117 | } 118 | } 119 | }; 120 | 121 | } // namespace 122 | 123 | static bool isLastParamCallback(const Nan::FunctionCallbackInfo& info) 124 | { 125 | return info[info.Length() - 1]->IsFunction(); 126 | } 127 | 128 | NAN_METHOD(protagonist::Validate) 129 | { 130 | Nan::HandleScope scope; 131 | 132 | Nan::Persistent* persistent = nullptr; 133 | Callback* callback = nullptr; 134 | v8::Local resolver; 135 | 136 | size_t optionIndex = 0; 137 | size_t callbackIndex = 0; 138 | 139 | if (info.Length() < 1 || info.Length() > 3) { 140 | Nan::ThrowTypeError("wrong number of arguments, `validate(string, options, callback)` expected"); 141 | return; 142 | } 143 | 144 | if (!info[0]->IsString()) { 145 | Nan::ThrowTypeError("wrong 1st argument - string expected, `validate(string, options, [callback])`"); 146 | return; 147 | } 148 | 149 | if (isLastParamCallback(info)) { // last arg is callback funtion 150 | if (info.Length() == 3) { 151 | optionIndex = 1; 152 | } 153 | callbackIndex = info.Length() - 1; 154 | } else { // is Promise 155 | if (info.Length() == 2) { 156 | optionIndex = 1; 157 | } else if (info.Length() > 2) { // promise shoud not have more than 2 params 158 | Nan::ThrowTypeError("wrong number of arguments, `validate(string, options)` expected"); 159 | return; 160 | } 161 | } 162 | 163 | if (optionIndex && !info[optionIndex]->IsObject()) { 164 | Nan::ThrowTypeError("wrong 2nd argument - object expected, `validate(string, options, [callback])`"); 165 | return; 166 | } 167 | 168 | Options parseOptions; 169 | 170 | if (optionIndex) { 171 | ErrorMessage err = ParseOptionsObject( parseOptions, Local::Cast(info[optionIndex]), true); 172 | 173 | if (!err.empty()) { 174 | Nan::ThrowTypeError(err.c_str()); 175 | return; 176 | } 177 | } 178 | 179 | if (callbackIndex) { 180 | callback = new Callback(info[callbackIndex].As()); 181 | } else { 182 | resolver = v8::Promise::Resolver::New(Nan::GetCurrentContext()).ToLocalChecked(); 183 | persistent = new Nan::Persistent(resolver); 184 | } 185 | 186 | AsyncQueueWorker(new ValidateWorker(callback, persistent, parseOptions.claimParseOptions(), new Nan::Utf8String(info[0]))); 187 | 188 | if (!callbackIndex) { 189 | info.GetReturnValue().Set(resolver->GetPromise()); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/validate_sync.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "protagonist.h" 3 | #include "drafter.h" 4 | #include "options.h" 5 | #include "refractToV8.h" 6 | 7 | using std::string; 8 | using namespace v8; 9 | using namespace protagonist; 10 | 11 | NAN_METHOD(protagonist::ValidateSync) { 12 | Nan::HandleScope scope; 13 | Local context = Nan::GetCurrentContext(); 14 | 15 | // Check arguments 16 | if (info.Length() != 1 && info.Length() != 2) { 17 | Nan::ThrowTypeError("wrong number of arguments, `validateSync(string, options)` expected"); 18 | return; 19 | } 20 | 21 | if (!info[0]->IsString()) { 22 | Nan::ThrowTypeError("wrong 1st argument - string expected, `validateSync(string, options)`"); 23 | return; 24 | } 25 | 26 | if (info.Length() == 2 && !info[1]->IsObject()) { 27 | Nan::ThrowTypeError("wrong 2nd argument - object expected, `validateSync(string, options)`"); 28 | return; 29 | } 30 | 31 | // Get source data 32 | Nan::Utf8String sourceData(info[0]->ToString(context).ToLocalChecked()); 33 | 34 | // Prepare options 35 | Options options; 36 | 37 | if (info.Length() == 2) { 38 | ErrorMessage err = ParseOptionsObject(options, Local::Cast(info[1]), true); 39 | 40 | if(!err.empty()) { 41 | Nan::ThrowTypeError(err.c_str()); 42 | } 43 | } 44 | 45 | // Parse the source data 46 | drafter_result *result; 47 | int err_code = drafter_check_blueprint(*sourceData, 48 | &result, 49 | options.parseOptions()); 50 | if (err_code < 0) { 51 | Nan::ThrowError("Parsing failed"); 52 | return; 53 | } 54 | 55 | if (result) { 56 | Local v8result = refract2v8(result, true); 57 | drafter_free_result(result); 58 | info.GetReturnValue().Set(v8result); 59 | } 60 | else { 61 | info.GetReturnValue().Set(Nan::Null()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/await-test.js: -------------------------------------------------------------------------------- 1 | const awaitHelper = require('./helpers/await'); 2 | const protagonist = require('./helpers/protagonist'); 3 | 4 | awaitHelper(protagonist); 5 | -------------------------------------------------------------------------------- /test/callback-test.js: -------------------------------------------------------------------------------- 1 | const callbackHelper = require('./helpers/callback'); 2 | const protagonist = require('./helpers/protagonist'); 3 | 4 | callbackHelper(protagonist); 5 | -------------------------------------------------------------------------------- /test/fixtures-test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const expect = require('chai').expect; 3 | const glob = require('glob'); 4 | 5 | const protagonist = require('./helpers/protagonist'); 6 | 7 | describe('Parsing Drafter Test Fixtures', () => { 8 | const fixtures = glob.sync('drafter/test/fixtures/*/*.apib'); 9 | 10 | fixtures.forEach((fixture) => { 11 | if (fixture.match(/\/mson\//) !== null) { 12 | return; 13 | } 14 | 15 | it(`parsing ${fixture}`, () => { 16 | // Find the parse result fixture(s). 17 | // Some test fixtures are with source maps and others are not. 18 | const results = glob.sync(fixture.replace('.apib', '{.sourcemap,}.json')); 19 | const sourceMapResult = results.find((results) => results.includes('.sourcemap.json')) 20 | const result = results.find((results) => !results.includes('.sourcemap.json')) 21 | 22 | const source = fs.readFileSync(fixture, 'utf-8'); 23 | 24 | if (result) { 25 | const parseResult = protagonist.parseSync(source); 26 | const expected = JSON.parse(fs.readFileSync(result, 'utf-8')); 27 | expect(parseResult).to.deep.equal(expected); 28 | } 29 | 30 | if (sourceMapResult) { 31 | const sourceMapParseResult = protagonist.parseSync(source, {generateSourceMap: true}); 32 | const sourceMapExpected = JSON.parse(fs.readFileSync(sourceMapResult, 'utf-8')); 33 | expect(sourceMapParseResult).to.deep.equal(sourceMapExpected); 34 | } 35 | 36 | if (!result && !sourceMapResult) { 37 | throw Error('No parse result fixture found'); 38 | } 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/fixtures/error.apib: -------------------------------------------------------------------------------- 1 | # Data Structures 2 | ## A (A) 3 | -------------------------------------------------------------------------------- /test/fixtures/error.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "annotation", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "error" 13 | } 14 | ] 15 | } 16 | }, 17 | "attributes": { 18 | "code": { 19 | "element": "number", 20 | "content": 4 21 | }, 22 | "sourceMap": { 23 | "element": "array", 24 | "content": [ 25 | { 26 | "element": "sourceMap", 27 | "content": [ 28 | { 29 | "element": "array", 30 | "content": [ 31 | { 32 | "element": "number", 33 | "attributes": { 34 | "column": { 35 | "element": "number", 36 | "content": 1 37 | }, 38 | "line": { 39 | "element": "number", 40 | "content": 2 41 | } 42 | }, 43 | "content": 18 44 | }, 45 | { 46 | "element": "number", 47 | "attributes": { 48 | "column": { 49 | "element": "number", 50 | "content": 9 51 | }, 52 | "line": { 53 | "element": "number", 54 | "content": 2 55 | } 56 | }, 57 | "content": 9 58 | } 59 | ] 60 | } 61 | ] 62 | } 63 | ] 64 | } 65 | }, 66 | "content": "base type 'A' circularly referencing itself" 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /test/fixtures/generate-body-schema.parse.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "" 19 | } 20 | }, 21 | "attributes": { 22 | "metadata": { 23 | "element": "array", 24 | "content": [ 25 | { 26 | "element": "member", 27 | "meta": { 28 | "classes": { 29 | "element": "array", 30 | "content": [ 31 | { 32 | "element": "string", 33 | "content": "user" 34 | } 35 | ] 36 | } 37 | }, 38 | "content": { 39 | "key": { 40 | "element": "string", 41 | "content": "FORMAT" 42 | }, 43 | "value": { 44 | "element": "string", 45 | "content": "1A" 46 | } 47 | } 48 | } 49 | ] 50 | } 51 | }, 52 | "content": [ 53 | { 54 | "element": "category", 55 | "meta": { 56 | "classes": { 57 | "element": "array", 58 | "content": [ 59 | { 60 | "element": "string", 61 | "content": "resourceGroup" 62 | } 63 | ] 64 | }, 65 | "title": { 66 | "element": "string", 67 | "content": "Messages" 68 | } 69 | }, 70 | "content": [ 71 | { 72 | "element": "resource", 73 | "meta": { 74 | "title": { 75 | "element": "string", 76 | "content": "Message" 77 | } 78 | }, 79 | "attributes": { 80 | "href": { 81 | "element": "string", 82 | "content": "/messages/{id}" 83 | }, 84 | "hrefVariables": { 85 | "element": "hrefVariables", 86 | "content": [ 87 | { 88 | "element": "member", 89 | "meta": { 90 | "description": { 91 | "element": "string", 92 | "content": "Id of a message" 93 | } 94 | }, 95 | "attributes": { 96 | "typeAttributes": { 97 | "element": "array", 98 | "content": [ 99 | { 100 | "element": "string", 101 | "content": "required" 102 | } 103 | ] 104 | } 105 | }, 106 | "content": { 107 | "key": { 108 | "element": "string", 109 | "content": "id" 110 | }, 111 | "value": { 112 | "element": "enum", 113 | "attributes": { 114 | "enumerations": { 115 | "element": "array", 116 | "content": [ 117 | { 118 | "element": "string", 119 | "content": "1" 120 | }, 121 | { 122 | "element": "string", 123 | "content": "2" 124 | }, 125 | { 126 | "element": "string", 127 | "content": "3" 128 | } 129 | ] 130 | } 131 | } 132 | } 133 | } 134 | } 135 | ] 136 | } 137 | }, 138 | "content": [ 139 | { 140 | "element": "dataStructure", 141 | "content": { 142 | "element": "object", 143 | "meta": { 144 | "id": { 145 | "element": "string", 146 | "content": "Message" 147 | } 148 | }, 149 | "content": [ 150 | { 151 | "element": "member", 152 | "content": { 153 | "key": { 154 | "element": "string", 155 | "content": "id" 156 | }, 157 | "value": { 158 | "element": "number" 159 | } 160 | } 161 | }, 162 | { 163 | "element": "member", 164 | "meta": { 165 | "description": { 166 | "element": "string", 167 | "content": "The Message" 168 | } 169 | }, 170 | "content": { 171 | "key": { 172 | "element": "string", 173 | "content": "message" 174 | }, 175 | "value": { 176 | "element": "string", 177 | "content": "Hello World!" 178 | } 179 | } 180 | } 181 | ] 182 | } 183 | }, 184 | { 185 | "element": "transition", 186 | "meta": { 187 | "title": { 188 | "element": "string", 189 | "content": "Retrieve Message" 190 | } 191 | }, 192 | "content": [ 193 | { 194 | "element": "httpTransaction", 195 | "content": [ 196 | { 197 | "element": "httpRequest", 198 | "attributes": { 199 | "method": { 200 | "element": "string", 201 | "content": "GET" 202 | } 203 | }, 204 | "content": [] 205 | }, 206 | { 207 | "element": "httpResponse", 208 | "attributes": { 209 | "statusCode": { 210 | "element": "string", 211 | "content": "200" 212 | }, 213 | "headers": { 214 | "element": "httpHeaders", 215 | "content": [ 216 | { 217 | "element": "member", 218 | "content": { 219 | "key": { 220 | "element": "string", 221 | "content": "Content-Type" 222 | }, 223 | "value": { 224 | "element": "string", 225 | "content": "application/json" 226 | } 227 | } 228 | } 229 | ] 230 | } 231 | }, 232 | "content": [ 233 | { 234 | "element": "dataStructure", 235 | "content": { 236 | "element": "Message" 237 | } 238 | }, 239 | { 240 | "element": "asset", 241 | "meta": { 242 | "classes": { 243 | "element": "array", 244 | "content": [ 245 | { 246 | "element": "string", 247 | "content": "messageBody" 248 | } 249 | ] 250 | } 251 | }, 252 | "attributes": { 253 | "contentType": { 254 | "element": "string", 255 | "content": "application/json" 256 | } 257 | }, 258 | "content": "{\n \"id\": 0,\n \"message\": \"Hello World!\"\n}" 259 | } 260 | ] 261 | } 262 | ] 263 | } 264 | ] 265 | }, 266 | { 267 | "element": "transition", 268 | "meta": { 269 | "title": { 270 | "element": "string", 271 | "content": "Delete Message" 272 | } 273 | }, 274 | "content": [ 275 | { 276 | "element": "httpTransaction", 277 | "content": [ 278 | { 279 | "element": "httpRequest", 280 | "attributes": { 281 | "method": { 282 | "element": "string", 283 | "content": "DELETE" 284 | } 285 | }, 286 | "content": [] 287 | }, 288 | { 289 | "element": "httpResponse", 290 | "attributes": { 291 | "statusCode": { 292 | "element": "string", 293 | "content": "204" 294 | } 295 | }, 296 | "content": [] 297 | } 298 | ] 299 | } 300 | ] 301 | } 302 | ] 303 | } 304 | ] 305 | } 306 | ] 307 | } 308 | ] 309 | } 310 | 311 | -------------------------------------------------------------------------------- /test/fixtures/generate-body-schema.parse.sourcemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "" 19 | } 20 | }, 21 | "attributes": { 22 | "metadata": { 23 | "element": "array", 24 | "content": [ 25 | { 26 | "element": "member", 27 | "meta": { 28 | "classes": { 29 | "element": "array", 30 | "content": [ 31 | { 32 | "element": "string", 33 | "content": "user" 34 | } 35 | ] 36 | } 37 | }, 38 | "attributes": { 39 | "sourceMap": { 40 | "element": "array", 41 | "content": [ 42 | { 43 | "element": "sourceMap", 44 | "content": [ 45 | { 46 | "element": "array", 47 | "content": [ 48 | { 49 | "element": "number", 50 | "content": 0 51 | }, 52 | { 53 | "element": "number", 54 | "content": 12 55 | } 56 | ] 57 | } 58 | ] 59 | } 60 | ] 61 | } 62 | }, 63 | "content": { 64 | "key": { 65 | "element": "string", 66 | "content": "FORMAT" 67 | }, 68 | "value": { 69 | "element": "string", 70 | "content": "1A" 71 | } 72 | } 73 | } 74 | ] 75 | } 76 | }, 77 | "content": [ 78 | { 79 | "element": "category", 80 | "meta": { 81 | "classes": { 82 | "element": "array", 83 | "content": [ 84 | { 85 | "element": "string", 86 | "content": "resourceGroup" 87 | } 88 | ] 89 | }, 90 | "title": { 91 | "element": "string", 92 | "attributes": { 93 | "sourceMap": { 94 | "element": "array", 95 | "content": [ 96 | { 97 | "element": "sourceMap", 98 | "content": [ 99 | { 100 | "element": "array", 101 | "content": [ 102 | { 103 | "element": "number", 104 | "content": 12 105 | }, 106 | { 107 | "element": "number", 108 | "content": 18 109 | } 110 | ] 111 | } 112 | ] 113 | } 114 | ] 115 | } 116 | }, 117 | "content": "Messages" 118 | } 119 | }, 120 | "content": [ 121 | { 122 | "element": "resource", 123 | "meta": { 124 | "title": { 125 | "element": "string", 126 | "attributes": { 127 | "sourceMap": { 128 | "element": "array", 129 | "content": [ 130 | { 131 | "element": "sourceMap", 132 | "content": [ 133 | { 134 | "element": "array", 135 | "content": [ 136 | { 137 | "element": "number", 138 | "content": 30 139 | }, 140 | { 141 | "element": "number", 142 | "content": 28 143 | } 144 | ] 145 | } 146 | ] 147 | } 148 | ] 149 | } 150 | }, 151 | "content": "Message" 152 | } 153 | }, 154 | "attributes": { 155 | "href": { 156 | "element": "string", 157 | "attributes": { 158 | "sourceMap": { 159 | "element": "array", 160 | "content": [ 161 | { 162 | "element": "sourceMap", 163 | "content": [ 164 | { 165 | "element": "array", 166 | "content": [ 167 | { 168 | "element": "number", 169 | "content": 30 170 | }, 171 | { 172 | "element": "number", 173 | "content": 28 174 | } 175 | ] 176 | } 177 | ] 178 | } 179 | ] 180 | } 181 | }, 182 | "content": "/messages/{id}" 183 | }, 184 | "hrefVariables": { 185 | "element": "hrefVariables", 186 | "content": [ 187 | { 188 | "element": "member", 189 | "meta": { 190 | "description": { 191 | "element": "string", 192 | "attributes": { 193 | "sourceMap": { 194 | "element": "array", 195 | "content": [ 196 | { 197 | "element": "sourceMap", 198 | "content": [ 199 | { 200 | "element": "array", 201 | "content": [ 202 | { 203 | "element": "number", 204 | "content": 77 205 | }, 206 | { 207 | "element": "number", 208 | "content": 23 209 | } 210 | ] 211 | } 212 | ] 213 | } 214 | ] 215 | } 216 | }, 217 | "content": "Id of a message" 218 | } 219 | }, 220 | "attributes": { 221 | "typeAttributes": { 222 | "element": "array", 223 | "content": [ 224 | { 225 | "element": "string", 226 | "content": "required" 227 | } 228 | ] 229 | } 230 | }, 231 | "content": { 232 | "key": { 233 | "element": "string", 234 | "attributes": { 235 | "sourceMap": { 236 | "element": "array", 237 | "content": [ 238 | { 239 | "element": "sourceMap", 240 | "content": [ 241 | { 242 | "element": "array", 243 | "content": [ 244 | { 245 | "element": "number", 246 | "content": 77 247 | }, 248 | { 249 | "element": "number", 250 | "content": 23 251 | } 252 | ] 253 | } 254 | ] 255 | } 256 | ] 257 | } 258 | }, 259 | "content": "id" 260 | }, 261 | "value": { 262 | "element": "enum", 263 | "attributes": { 264 | "enumerations": { 265 | "element": "array", 266 | "content": [ 267 | { 268 | "element": "string", 269 | "attributes": { 270 | "sourceMap": { 271 | "element": "array", 272 | "content": [ 273 | { 274 | "element": "sourceMap", 275 | "content": [ 276 | { 277 | "element": "array", 278 | "content": [ 279 | { 280 | "element": "number", 281 | "content": 129 282 | }, 283 | { 284 | "element": "number", 285 | "content": 6 286 | } 287 | ] 288 | } 289 | ] 290 | } 291 | ] 292 | } 293 | }, 294 | "content": "1" 295 | }, 296 | { 297 | "element": "string", 298 | "attributes": { 299 | "sourceMap": { 300 | "element": "array", 301 | "content": [ 302 | { 303 | "element": "sourceMap", 304 | "content": [ 305 | { 306 | "element": "array", 307 | "content": [ 308 | { 309 | "element": "number", 310 | "content": 147 311 | }, 312 | { 313 | "element": "number", 314 | "content": 6 315 | } 316 | ] 317 | } 318 | ] 319 | } 320 | ] 321 | } 322 | }, 323 | "content": "2" 324 | }, 325 | { 326 | "element": "string", 327 | "attributes": { 328 | "sourceMap": { 329 | "element": "array", 330 | "content": [ 331 | { 332 | "element": "sourceMap", 333 | "content": [ 334 | { 335 | "element": "array", 336 | "content": [ 337 | { 338 | "element": "number", 339 | "content": 165 340 | }, 341 | { 342 | "element": "number", 343 | "content": 6 344 | } 345 | ] 346 | } 347 | ] 348 | } 349 | ] 350 | } 351 | }, 352 | "content": "3" 353 | } 354 | ] 355 | } 356 | } 357 | } 358 | } 359 | } 360 | ] 361 | } 362 | }, 363 | "content": [ 364 | { 365 | "element": "dataStructure", 366 | "content": { 367 | "element": "object", 368 | "meta": { 369 | "id": { 370 | "element": "string", 371 | "attributes": { 372 | "sourceMap": { 373 | "element": "array", 374 | "content": [ 375 | { 376 | "element": "sourceMap", 377 | "content": [ 378 | { 379 | "element": "array", 380 | "content": [ 381 | { 382 | "element": "number", 383 | "content": 30 384 | }, 385 | { 386 | "element": "number", 387 | "content": 28 388 | } 389 | ] 390 | } 391 | ] 392 | } 393 | ] 394 | } 395 | }, 396 | "content": "Message" 397 | } 398 | }, 399 | "attributes": { 400 | "sourceMap": { 401 | "element": "array", 402 | "content": [ 403 | { 404 | "element": "sourceMap", 405 | "content": [ 406 | { 407 | "element": "array", 408 | "content": [ 409 | { 410 | "element": "number", 411 | "content": 174 412 | }, 413 | { 414 | "element": "number", 415 | "content": 11 416 | } 417 | ] 418 | } 419 | ] 420 | } 421 | ] 422 | } 423 | }, 424 | "content": [ 425 | { 426 | "element": "member", 427 | "content": { 428 | "key": { 429 | "element": "string", 430 | "attributes": { 431 | "sourceMap": { 432 | "element": "array", 433 | "content": [ 434 | { 435 | "element": "sourceMap", 436 | "content": [ 437 | { 438 | "element": "array", 439 | "content": [ 440 | { 441 | "element": "number", 442 | "content": 191 443 | }, 444 | { 445 | "element": "number", 446 | "content": 12 447 | } 448 | ] 449 | } 450 | ] 451 | } 452 | ] 453 | } 454 | }, 455 | "content": "id" 456 | }, 457 | "value": { 458 | "element": "number", 459 | "attributes": { 460 | "sourceMap": { 461 | "element": "array", 462 | "content": [ 463 | { 464 | "element": "sourceMap", 465 | "content": [ 466 | { 467 | "element": "array", 468 | "content": [ 469 | { 470 | "element": "number", 471 | "content": 191 472 | }, 473 | { 474 | "element": "number", 475 | "content": 12 476 | } 477 | ] 478 | } 479 | ] 480 | } 481 | ] 482 | } 483 | } 484 | } 485 | } 486 | }, 487 | { 488 | "element": "member", 489 | "meta": { 490 | "description": { 491 | "element": "string", 492 | "attributes": { 493 | "sourceMap": { 494 | "element": "array", 495 | "content": [ 496 | { 497 | "element": "sourceMap", 498 | "content": [ 499 | { 500 | "element": "array", 501 | "content": [ 502 | { 503 | "element": "number", 504 | "content": 209 505 | }, 506 | { 507 | "element": "number", 508 | "content": 45 509 | } 510 | ] 511 | } 512 | ] 513 | } 514 | ] 515 | } 516 | }, 517 | "content": "The Message" 518 | } 519 | }, 520 | "content": { 521 | "key": { 522 | "element": "string", 523 | "attributes": { 524 | "sourceMap": { 525 | "element": "array", 526 | "content": [ 527 | { 528 | "element": "sourceMap", 529 | "content": [ 530 | { 531 | "element": "array", 532 | "content": [ 533 | { 534 | "element": "number", 535 | "content": 209 536 | }, 537 | { 538 | "element": "number", 539 | "content": 45 540 | } 541 | ] 542 | } 543 | ] 544 | } 545 | ] 546 | } 547 | }, 548 | "content": "message" 549 | }, 550 | "value": { 551 | "element": "string", 552 | "attributes": { 553 | "sourceMap": { 554 | "element": "array", 555 | "content": [ 556 | { 557 | "element": "sourceMap", 558 | "content": [ 559 | { 560 | "element": "array", 561 | "content": [ 562 | { 563 | "element": "number", 564 | "content": 209 565 | }, 566 | { 567 | "element": "number", 568 | "content": 45 569 | } 570 | ] 571 | } 572 | ] 573 | } 574 | ] 575 | } 576 | }, 577 | "content": "Hello World!" 578 | } 579 | } 580 | } 581 | ] 582 | } 583 | }, 584 | { 585 | "element": "transition", 586 | "meta": { 587 | "title": { 588 | "element": "string", 589 | "attributes": { 590 | "sourceMap": { 591 | "element": "array", 592 | "content": [ 593 | { 594 | "element": "sourceMap", 595 | "content": [ 596 | { 597 | "element": "array", 598 | "content": [ 599 | { 600 | "element": "number", 601 | "content": 255 602 | }, 603 | { 604 | "element": "number", 605 | "content": 26 606 | } 607 | ] 608 | } 609 | ] 610 | } 611 | ] 612 | } 613 | }, 614 | "content": "Retrieve Message" 615 | } 616 | }, 617 | "content": [ 618 | { 619 | "element": "httpTransaction", 620 | "content": [ 621 | { 622 | "element": "httpRequest", 623 | "attributes": { 624 | "method": { 625 | "element": "string", 626 | "attributes": { 627 | "sourceMap": { 628 | "element": "array", 629 | "content": [ 630 | { 631 | "element": "sourceMap", 632 | "content": [ 633 | { 634 | "element": "array", 635 | "content": [ 636 | { 637 | "element": "number", 638 | "content": 255 639 | }, 640 | { 641 | "element": "number", 642 | "content": 26 643 | } 644 | ] 645 | } 646 | ] 647 | } 648 | ] 649 | } 650 | }, 651 | "content": "GET" 652 | } 653 | }, 654 | "content": [] 655 | }, 656 | { 657 | "element": "httpResponse", 658 | "attributes": { 659 | "statusCode": { 660 | "element": "string", 661 | "attributes": { 662 | "sourceMap": { 663 | "element": "array", 664 | "content": [ 665 | { 666 | "element": "sourceMap", 667 | "content": [ 668 | { 669 | "element": "array", 670 | "content": [ 671 | { 672 | "element": "number", 673 | "content": 283 674 | }, 675 | { 676 | "element": "number", 677 | "content": 32 678 | } 679 | ] 680 | } 681 | ] 682 | } 683 | ] 684 | } 685 | }, 686 | "content": "200" 687 | }, 688 | "sourceMap": { 689 | "element": "array", 690 | "content": [ 691 | { 692 | "element": "sourceMap", 693 | "content": [ 694 | { 695 | "element": "array", 696 | "content": [ 697 | { 698 | "element": "number", 699 | "content": 283 700 | }, 701 | { 702 | "element": "number", 703 | "content": 32 704 | } 705 | ] 706 | } 707 | ] 708 | } 709 | ] 710 | }, 711 | "headers": { 712 | "element": "httpHeaders", 713 | "content": [ 714 | { 715 | "element": "member", 716 | "attributes": { 717 | "sourceMap": { 718 | "element": "array", 719 | "content": [ 720 | { 721 | "element": "sourceMap", 722 | "content": [ 723 | { 724 | "element": "array", 725 | "content": [ 726 | { 727 | "element": "number", 728 | "content": 283 729 | }, 730 | { 731 | "element": "number", 732 | "content": 32 733 | } 734 | ] 735 | } 736 | ] 737 | } 738 | ] 739 | } 740 | }, 741 | "content": { 742 | "key": { 743 | "element": "string", 744 | "content": "Content-Type" 745 | }, 746 | "value": { 747 | "element": "string", 748 | "content": "application/json" 749 | } 750 | } 751 | } 752 | ] 753 | } 754 | }, 755 | "content": [ 756 | { 757 | "element": "dataStructure", 758 | "content": { 759 | "element": "Message", 760 | "attributes": { 761 | "sourceMap": { 762 | "element": "array", 763 | "content": [ 764 | { 765 | "element": "sourceMap", 766 | "content": [ 767 | { 768 | "element": "array", 769 | "content": [ 770 | { 771 | "element": "number", 772 | "content": 321 773 | }, 774 | { 775 | "element": "number", 776 | "content": 21 777 | } 778 | ] 779 | } 780 | ] 781 | } 782 | ] 783 | } 784 | } 785 | } 786 | }, 787 | { 788 | "element": "asset", 789 | "meta": { 790 | "classes": { 791 | "element": "array", 792 | "content": [ 793 | { 794 | "element": "string", 795 | "content": "messageBody" 796 | } 797 | ] 798 | } 799 | }, 800 | "attributes": { 801 | "contentType": { 802 | "element": "string", 803 | "content": "application/json" 804 | } 805 | }, 806 | "content": "{\n \"id\": 0,\n \"message\": \"Hello World!\"\n}" 807 | } 808 | ] 809 | } 810 | ] 811 | } 812 | ] 813 | }, 814 | { 815 | "element": "transition", 816 | "meta": { 817 | "title": { 818 | "element": "string", 819 | "attributes": { 820 | "sourceMap": { 821 | "element": "array", 822 | "content": [ 823 | { 824 | "element": "sourceMap", 825 | "content": [ 826 | { 827 | "element": "array", 828 | "content": [ 829 | { 830 | "element": "number", 831 | "content": 343 832 | }, 833 | { 834 | "element": "number", 835 | "content": 27 836 | } 837 | ] 838 | } 839 | ] 840 | } 841 | ] 842 | } 843 | }, 844 | "content": "Delete Message" 845 | } 846 | }, 847 | "content": [ 848 | { 849 | "element": "httpTransaction", 850 | "content": [ 851 | { 852 | "element": "httpRequest", 853 | "attributes": { 854 | "method": { 855 | "element": "string", 856 | "attributes": { 857 | "sourceMap": { 858 | "element": "array", 859 | "content": [ 860 | { 861 | "element": "sourceMap", 862 | "content": [ 863 | { 864 | "element": "array", 865 | "content": [ 866 | { 867 | "element": "number", 868 | "content": 343 869 | }, 870 | { 871 | "element": "number", 872 | "content": 27 873 | } 874 | ] 875 | } 876 | ] 877 | } 878 | ] 879 | } 880 | }, 881 | "content": "DELETE" 882 | } 883 | }, 884 | "content": [] 885 | }, 886 | { 887 | "element": "httpResponse", 888 | "attributes": { 889 | "statusCode": { 890 | "element": "string", 891 | "attributes": { 892 | "sourceMap": { 893 | "element": "array", 894 | "content": [ 895 | { 896 | "element": "sourceMap", 897 | "content": [ 898 | { 899 | "element": "array", 900 | "content": [ 901 | { 902 | "element": "number", 903 | "content": 372 904 | }, 905 | { 906 | "element": "number", 907 | "content": 13 908 | } 909 | ] 910 | } 911 | ] 912 | } 913 | ] 914 | } 915 | }, 916 | "content": "204" 917 | }, 918 | "sourceMap": { 919 | "element": "array", 920 | "content": [ 921 | { 922 | "element": "sourceMap", 923 | "content": [ 924 | { 925 | "element": "array", 926 | "content": [ 927 | { 928 | "element": "number", 929 | "content": 372 930 | }, 931 | { 932 | "element": "number", 933 | "content": 13 934 | } 935 | ] 936 | } 937 | ] 938 | } 939 | ] 940 | } 941 | }, 942 | "content": [] 943 | } 944 | ] 945 | } 946 | ] 947 | } 948 | ] 949 | } 950 | ] 951 | } 952 | ] 953 | } 954 | ] 955 | } 956 | 957 | -------------------------------------------------------------------------------- /test/fixtures/generate-body.parse.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "" 19 | } 20 | }, 21 | "attributes": { 22 | "metadata": { 23 | "element": "array", 24 | "content": [ 25 | { 26 | "element": "member", 27 | "meta": { 28 | "classes": { 29 | "element": "array", 30 | "content": [ 31 | { 32 | "element": "string", 33 | "content": "user" 34 | } 35 | ] 36 | } 37 | }, 38 | "content": { 39 | "key": { 40 | "element": "string", 41 | "content": "FORMAT" 42 | }, 43 | "value": { 44 | "element": "string", 45 | "content": "1A" 46 | } 47 | } 48 | } 49 | ] 50 | } 51 | }, 52 | "content": [ 53 | { 54 | "element": "category", 55 | "meta": { 56 | "classes": { 57 | "element": "array", 58 | "content": [ 59 | { 60 | "element": "string", 61 | "content": "resourceGroup" 62 | } 63 | ] 64 | }, 65 | "title": { 66 | "element": "string", 67 | "content": "Messages" 68 | } 69 | }, 70 | "content": [ 71 | { 72 | "element": "resource", 73 | "meta": { 74 | "title": { 75 | "element": "string", 76 | "content": "Message" 77 | } 78 | }, 79 | "attributes": { 80 | "href": { 81 | "element": "string", 82 | "content": "/messages/{id}" 83 | }, 84 | "hrefVariables": { 85 | "element": "hrefVariables", 86 | "content": [ 87 | { 88 | "element": "member", 89 | "meta": { 90 | "description": { 91 | "element": "string", 92 | "content": "Id of a message" 93 | } 94 | }, 95 | "attributes": { 96 | "typeAttributes": { 97 | "element": "array", 98 | "content": [ 99 | { 100 | "element": "string", 101 | "content": "required" 102 | } 103 | ] 104 | } 105 | }, 106 | "content": { 107 | "key": { 108 | "element": "string", 109 | "content": "id" 110 | }, 111 | "value": { 112 | "element": "enum", 113 | "attributes": { 114 | "enumerations": { 115 | "element": "array", 116 | "content": [ 117 | { 118 | "element": "string", 119 | "content": "1" 120 | }, 121 | { 122 | "element": "string", 123 | "content": "2" 124 | }, 125 | { 126 | "element": "string", 127 | "content": "3" 128 | } 129 | ] 130 | } 131 | } 132 | } 133 | } 134 | } 135 | ] 136 | } 137 | }, 138 | "content": [ 139 | { 140 | "element": "dataStructure", 141 | "content": { 142 | "element": "object", 143 | "meta": { 144 | "id": { 145 | "element": "string", 146 | "content": "Message" 147 | } 148 | }, 149 | "content": [ 150 | { 151 | "element": "member", 152 | "content": { 153 | "key": { 154 | "element": "string", 155 | "content": "id" 156 | }, 157 | "value": { 158 | "element": "number" 159 | } 160 | } 161 | }, 162 | { 163 | "element": "member", 164 | "meta": { 165 | "description": { 166 | "element": "string", 167 | "content": "The Message" 168 | } 169 | }, 170 | "content": { 171 | "key": { 172 | "element": "string", 173 | "content": "message" 174 | }, 175 | "value": { 176 | "element": "string", 177 | "content": "Hello World!" 178 | } 179 | } 180 | } 181 | ] 182 | } 183 | }, 184 | { 185 | "element": "transition", 186 | "meta": { 187 | "title": { 188 | "element": "string", 189 | "content": "Retrieve Message" 190 | } 191 | }, 192 | "content": [ 193 | { 194 | "element": "httpTransaction", 195 | "content": [ 196 | { 197 | "element": "httpRequest", 198 | "attributes": { 199 | "method": { 200 | "element": "string", 201 | "content": "GET" 202 | } 203 | }, 204 | "content": [] 205 | }, 206 | { 207 | "element": "httpResponse", 208 | "attributes": { 209 | "statusCode": { 210 | "element": "string", 211 | "content": "200" 212 | }, 213 | "headers": { 214 | "element": "httpHeaders", 215 | "content": [ 216 | { 217 | "element": "member", 218 | "content": { 219 | "key": { 220 | "element": "string", 221 | "content": "Content-Type" 222 | }, 223 | "value": { 224 | "element": "string", 225 | "content": "application/json" 226 | } 227 | } 228 | } 229 | ] 230 | } 231 | }, 232 | "content": [ 233 | { 234 | "element": "dataStructure", 235 | "content": { 236 | "element": "Message" 237 | } 238 | }, 239 | { 240 | "element": "asset", 241 | "meta": { 242 | "classes": { 243 | "element": "array", 244 | "content": [ 245 | { 246 | "element": "string", 247 | "content": "messageBodySchema" 248 | } 249 | ] 250 | } 251 | }, 252 | "attributes": { 253 | "contentType": { 254 | "element": "string", 255 | "content": "application/schema+json" 256 | } 257 | }, 258 | "content": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n }\n}" 259 | } 260 | ] 261 | } 262 | ] 263 | } 264 | ] 265 | }, 266 | { 267 | "element": "transition", 268 | "meta": { 269 | "title": { 270 | "element": "string", 271 | "content": "Delete Message" 272 | } 273 | }, 274 | "content": [ 275 | { 276 | "element": "httpTransaction", 277 | "content": [ 278 | { 279 | "element": "httpRequest", 280 | "attributes": { 281 | "method": { 282 | "element": "string", 283 | "content": "DELETE" 284 | } 285 | }, 286 | "content": [] 287 | }, 288 | { 289 | "element": "httpResponse", 290 | "attributes": { 291 | "statusCode": { 292 | "element": "string", 293 | "content": "204" 294 | } 295 | }, 296 | "content": [] 297 | } 298 | ] 299 | } 300 | ] 301 | } 302 | ] 303 | } 304 | ] 305 | } 306 | ] 307 | } 308 | ] 309 | } 310 | 311 | -------------------------------------------------------------------------------- /test/fixtures/require-name.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "annotation", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "error" 13 | } 14 | ] 15 | } 16 | }, 17 | "attributes": { 18 | "code": { 19 | "element": "number", 20 | "content": 2 21 | }, 22 | "sourceMap": { 23 | "element": "array", 24 | "content": [ 25 | { 26 | "element": "sourceMap", 27 | "content": [ 28 | { 29 | "element": "array", 30 | "content": [ 31 | { 32 | "element": "number", 33 | "attributes": { 34 | "column": { 35 | "element": "number", 36 | "content": 1 37 | }, 38 | "line": { 39 | "element": "number", 40 | "content": 1 41 | } 42 | }, 43 | "content": 0 44 | }, 45 | { 46 | "element": "number", 47 | "attributes": { 48 | "column": { 49 | "element": "number", 50 | "content": 1 51 | }, 52 | "line": { 53 | "element": "number", 54 | "content": 2 55 | } 56 | }, 57 | "content": 12 58 | } 59 | ] 60 | } 61 | ] 62 | } 63 | ] 64 | } 65 | }, 66 | "content": "expected API name, e.g. '# '" 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /test/fixtures/valid.apib: -------------------------------------------------------------------------------- 1 | FORMAT: 1A 2 | 3 | # Group Messages 4 | 5 | # Message [/messages/{id}] 6 | 7 | + Parameters 8 | + id ... Id of a message 9 | + Values 10 | + `1` 11 | + `2` 12 | + `3` 13 | 14 | + Attributes 15 | + id (number) 16 | + message: Hello World! (string) - The Message 17 | 18 | ## Retrieve Message [GET] 19 | + Response 200 (application/json) 20 | + Attributes (Message) 21 | 22 | ## Delete Message [DELETE] 23 | + Response 204 24 | -------------------------------------------------------------------------------- /test/fixtures/valid.parse.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "" 19 | } 20 | }, 21 | "attributes": { 22 | "metadata": { 23 | "element": "array", 24 | "content": [ 25 | { 26 | "element": "member", 27 | "meta": { 28 | "classes": { 29 | "element": "array", 30 | "content": [ 31 | { 32 | "element": "string", 33 | "content": "user" 34 | } 35 | ] 36 | } 37 | }, 38 | "content": { 39 | "key": { 40 | "element": "string", 41 | "content": "FORMAT" 42 | }, 43 | "value": { 44 | "element": "string", 45 | "content": "1A" 46 | } 47 | } 48 | } 49 | ] 50 | } 51 | }, 52 | "content": [ 53 | { 54 | "element": "category", 55 | "meta": { 56 | "classes": { 57 | "element": "array", 58 | "content": [ 59 | { 60 | "element": "string", 61 | "content": "resourceGroup" 62 | } 63 | ] 64 | }, 65 | "title": { 66 | "element": "string", 67 | "content": "Messages" 68 | } 69 | }, 70 | "content": [ 71 | { 72 | "element": "resource", 73 | "meta": { 74 | "title": { 75 | "element": "string", 76 | "content": "Message" 77 | } 78 | }, 79 | "attributes": { 80 | "href": { 81 | "element": "string", 82 | "content": "/messages/{id}" 83 | }, 84 | "hrefVariables": { 85 | "element": "hrefVariables", 86 | "content": [ 87 | { 88 | "element": "member", 89 | "meta": { 90 | "description": { 91 | "element": "string", 92 | "content": "Id of a message" 93 | } 94 | }, 95 | "attributes": { 96 | "typeAttributes": { 97 | "element": "array", 98 | "content": [ 99 | { 100 | "element": "string", 101 | "content": "required" 102 | } 103 | ] 104 | } 105 | }, 106 | "content": { 107 | "key": { 108 | "element": "string", 109 | "content": "id" 110 | }, 111 | "value": { 112 | "element": "enum", 113 | "attributes": { 114 | "enumerations": { 115 | "element": "array", 116 | "content": [ 117 | { 118 | "element": "string", 119 | "content": "1" 120 | }, 121 | { 122 | "element": "string", 123 | "content": "2" 124 | }, 125 | { 126 | "element": "string", 127 | "content": "3" 128 | } 129 | ] 130 | } 131 | } 132 | } 133 | } 134 | } 135 | ] 136 | } 137 | }, 138 | "content": [ 139 | { 140 | "element": "dataStructure", 141 | "content": { 142 | "element": "object", 143 | "meta": { 144 | "id": { 145 | "element": "string", 146 | "content": "Message" 147 | } 148 | }, 149 | "content": [ 150 | { 151 | "element": "member", 152 | "content": { 153 | "key": { 154 | "element": "string", 155 | "content": "id" 156 | }, 157 | "value": { 158 | "element": "number" 159 | } 160 | } 161 | }, 162 | { 163 | "element": "member", 164 | "meta": { 165 | "description": { 166 | "element": "string", 167 | "content": "The Message" 168 | } 169 | }, 170 | "content": { 171 | "key": { 172 | "element": "string", 173 | "content": "message" 174 | }, 175 | "value": { 176 | "element": "string", 177 | "content": "Hello World!" 178 | } 179 | } 180 | } 181 | ] 182 | } 183 | }, 184 | { 185 | "element": "transition", 186 | "meta": { 187 | "title": { 188 | "element": "string", 189 | "content": "Retrieve Message" 190 | } 191 | }, 192 | "content": [ 193 | { 194 | "element": "httpTransaction", 195 | "content": [ 196 | { 197 | "element": "httpRequest", 198 | "attributes": { 199 | "method": { 200 | "element": "string", 201 | "content": "GET" 202 | } 203 | }, 204 | "content": [] 205 | }, 206 | { 207 | "element": "httpResponse", 208 | "attributes": { 209 | "statusCode": { 210 | "element": "string", 211 | "content": "200" 212 | }, 213 | "headers": { 214 | "element": "httpHeaders", 215 | "content": [ 216 | { 217 | "element": "member", 218 | "content": { 219 | "key": { 220 | "element": "string", 221 | "content": "Content-Type" 222 | }, 223 | "value": { 224 | "element": "string", 225 | "content": "application/json" 226 | } 227 | } 228 | } 229 | ] 230 | } 231 | }, 232 | "content": [ 233 | { 234 | "element": "dataStructure", 235 | "content": { 236 | "element": "Message" 237 | } 238 | }, 239 | { 240 | "element": "asset", 241 | "meta": { 242 | "classes": { 243 | "element": "array", 244 | "content": [ 245 | { 246 | "element": "string", 247 | "content": "messageBody" 248 | } 249 | ] 250 | } 251 | }, 252 | "attributes": { 253 | "contentType": { 254 | "element": "string", 255 | "content": "application/json" 256 | } 257 | }, 258 | "content": "{\n \"id\": 0,\n \"message\": \"Hello World!\"\n}" 259 | }, 260 | { 261 | "element": "asset", 262 | "meta": { 263 | "classes": { 264 | "element": "array", 265 | "content": [ 266 | { 267 | "element": "string", 268 | "content": "messageBodySchema" 269 | } 270 | ] 271 | } 272 | }, 273 | "attributes": { 274 | "contentType": { 275 | "element": "string", 276 | "content": "application/schema+json" 277 | } 278 | }, 279 | "content": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n }\n}" 280 | } 281 | ] 282 | } 283 | ] 284 | } 285 | ] 286 | }, 287 | { 288 | "element": "transition", 289 | "meta": { 290 | "title": { 291 | "element": "string", 292 | "content": "Delete Message" 293 | } 294 | }, 295 | "content": [ 296 | { 297 | "element": "httpTransaction", 298 | "content": [ 299 | { 300 | "element": "httpRequest", 301 | "attributes": { 302 | "method": { 303 | "element": "string", 304 | "content": "DELETE" 305 | } 306 | }, 307 | "content": [] 308 | }, 309 | { 310 | "element": "httpResponse", 311 | "attributes": { 312 | "statusCode": { 313 | "element": "string", 314 | "content": "204" 315 | } 316 | }, 317 | "content": [] 318 | } 319 | ] 320 | } 321 | ] 322 | } 323 | ] 324 | } 325 | ] 326 | } 327 | ] 328 | } 329 | ] 330 | } 331 | 332 | -------------------------------------------------------------------------------- /test/fixtures/warning.apib: -------------------------------------------------------------------------------- 1 | # Name 2 | 3 | # GET / 4 | -------------------------------------------------------------------------------- /test/fixtures/warning.parse.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "api" 13 | } 14 | ] 15 | }, 16 | "title": { 17 | "element": "string", 18 | "content": "Name" 19 | } 20 | }, 21 | "content": [ 22 | { 23 | "element": "resource", 24 | "meta": { 25 | "title": { 26 | "element": "string", 27 | "content": "" 28 | } 29 | }, 30 | "attributes": { 31 | "href": { 32 | "element": "string", 33 | "content": "/" 34 | } 35 | }, 36 | "content": [ 37 | { 38 | "element": "transition", 39 | "meta": { 40 | "title": { 41 | "element": "string", 42 | "content": "" 43 | } 44 | }, 45 | "content": [] 46 | } 47 | ] 48 | } 49 | ] 50 | }, 51 | { 52 | "element": "annotation", 53 | "meta": { 54 | "classes": { 55 | "element": "array", 56 | "content": [ 57 | { 58 | "element": "string", 59 | "content": "warning" 60 | } 61 | ] 62 | } 63 | }, 64 | "attributes": { 65 | "code": { 66 | "element": "number", 67 | "content": 6 68 | }, 69 | "sourceMap": { 70 | "element": "array", 71 | "content": [ 72 | { 73 | "element": "sourceMap", 74 | "content": [ 75 | { 76 | "element": "array", 77 | "content": [ 78 | { 79 | "element": "number", 80 | "attributes": { 81 | "column": { 82 | "element": "number", 83 | "content": 1 84 | }, 85 | "line": { 86 | "element": "number", 87 | "content": 3 88 | } 89 | }, 90 | "content": 8 91 | }, 92 | { 93 | "element": "number", 94 | "attributes": { 95 | "column": { 96 | "element": "number", 97 | "content": 8 98 | }, 99 | "line": { 100 | "element": "number", 101 | "content": 3 102 | } 103 | }, 104 | "content": 8 105 | } 106 | ] 107 | } 108 | ] 109 | } 110 | ] 111 | } 112 | }, 113 | "content": "action is missing a response" 114 | } 115 | ] 116 | } 117 | -------------------------------------------------------------------------------- /test/fixtures/warning.validate.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "annotation", 6 | "meta": { 7 | "classes": { 8 | "element": "array", 9 | "content": [ 10 | { 11 | "element": "string", 12 | "content": "warning" 13 | } 14 | ] 15 | } 16 | }, 17 | "attributes": { 18 | "code": { 19 | "element": "number", 20 | "content": 6 21 | }, 22 | "sourceMap": { 23 | "element": "array", 24 | "content": [ 25 | { 26 | "element": "sourceMap", 27 | "content": [ 28 | { 29 | "element": "array", 30 | "content": [ 31 | { 32 | "element": "number", 33 | "attributes": { 34 | "column": { 35 | "element": "number", 36 | "content": 1 37 | }, 38 | "line": { 39 | "element": "number", 40 | "content": 3 41 | } 42 | }, 43 | "content": 8 44 | }, 45 | { 46 | "element": "number", 47 | "attributes": { 48 | "column": { 49 | "element": "number", 50 | "content": 8 51 | }, 52 | "line": { 53 | "element": "number", 54 | "content": 3 55 | } 56 | }, 57 | "content": 8 58 | } 59 | ] 60 | } 61 | ] 62 | } 63 | ] 64 | } 65 | }, 66 | "content": "action is missing a response" 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /test/helpers/await.js: -------------------------------------------------------------------------------- 1 | const assert = require('chai').assert; 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const valid_fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'valid.apib'), 'utf8'); 6 | const warning_fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'warning.apib'), 'utf8'); 7 | const error_fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'error.apib'), 'utf8'); 8 | 9 | const valid_refract = require('../fixtures/valid.parse.json'); 10 | const valid_sourcemap_refract = require('../fixtures/valid.parse.sourcemap.json'); 11 | const warning_parse_refract = require('../fixtures/warning.parse.json'); 12 | const warning_validate_refract = require('../fixtures/warning.validate.json'); 13 | const error_refract = require('../fixtures/error.json'); 14 | 15 | module.exports = (parser) => { 16 | describe('Parse with async/await', () => { 17 | it('valid fixture without options should return parse result', async () => { 18 | const refract = await parser.parse(valid_fixture); 19 | assert.deepEqual(refract, valid_refract); 20 | }); 21 | 22 | it('valid fixture with options should return parse result', async () => { 23 | const refract = await parser.parse(valid_fixture, { generateSourceMap: true }); 24 | assert.deepEqual(refract, valid_sourcemap_refract); 25 | }); 26 | 27 | it('error fixture without options should return parse result', async () => { 28 | const refract = await parser.parse(error_fixture); 29 | assert.deepEqual(refract, error_refract); 30 | }); 31 | 32 | it('warning fixture without options should return parse result', async () => { 33 | const refract = await parser.parse(warning_fixture); 34 | assert.deepEqual(refract, warning_parse_refract); 35 | }); 36 | }); 37 | 38 | describe('Validate with async/await', () => { 39 | it('valid fixture without options should return null', async () => { 40 | const refract = await parser.validate(valid_fixture); 41 | assert.isNull(refract); 42 | }); 43 | 44 | it('valid fixture with options should return null', async () => { 45 | const refract = await parser.validate(valid_fixture, {}); 46 | assert.isNull(refract); 47 | }); 48 | 49 | it('error fixture without options should return annotations', async () => { 50 | const refract = await parser.validate(error_fixture); 51 | assert.deepEqual(refract, error_refract); 52 | }); 53 | 54 | it('warning fixture without options should return annotations', async () => { 55 | const refract = await parser.validate(warning_fixture); 56 | assert.deepEqual(refract, warning_validate_refract); 57 | }); 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /test/helpers/callback.js: -------------------------------------------------------------------------------- 1 | const assert = require('chai').assert; 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const valid_fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'valid.apib'), 'utf8'); 6 | const warning_fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'warning.apib'), 'utf8'); 7 | const error_fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'error.apib'), 'utf8'); 8 | 9 | const valid_refract = require('../fixtures/valid.parse.json'); 10 | const valid_sourcemap_refract = require('../fixtures/valid.parse.sourcemap.json'); 11 | const warning_parse_refract = require('../fixtures/warning.parse.json'); 12 | const warning_validate_refract = require('../fixtures/warning.validate.json'); 13 | const error_refract = require('../fixtures/error.json'); 14 | 15 | module.exports = (parser) => { 16 | describe('Parse with callback', () => { 17 | it('valid fixture without options should return parse result', (done) => { 18 | parser.parse(valid_fixture, (err, refract) => { 19 | assert.isNull(err); 20 | assert.deepEqual(refract, valid_refract); 21 | done(); 22 | }); 23 | }); 24 | 25 | it('valid fixture with options should return parse result', (done) => { 26 | parser.parse(valid_fixture, { generateSourceMap: true }, (err, refract) => { 27 | assert.isNull(err); 28 | assert.deepEqual(refract, valid_sourcemap_refract); 29 | done(); 30 | }); 31 | }); 32 | 33 | it('error fixture without options should return parse result', (done) => { 34 | parser.parse(error_fixture, (err, refract) => { 35 | assert.isNull(err); 36 | assert.deepEqual(refract, error_refract); 37 | done(); 38 | }); 39 | }); 40 | 41 | it('warning fixture without options should return parse result', (done) => { 42 | parser.parse(warning_fixture, (err, refract) => { 43 | assert.isNull(err); 44 | assert.deepEqual(refract, warning_parse_refract); 45 | done(); 46 | }); 47 | }); 48 | }); 49 | 50 | describe('Validate with callback', () => { 51 | it('valid fixture without options should return null', (done) => { 52 | parser.validate(valid_fixture, (err, refract) => { 53 | assert.isNull(err); 54 | assert.isNull(refract); 55 | done(); 56 | }); 57 | }); 58 | 59 | it('valid fixture with options should return null', (done) => { 60 | parser.validate(valid_fixture, {}, (err, refract) => { 61 | assert.isNull(err); 62 | assert.isNull(refract); 63 | done(); 64 | }); 65 | }); 66 | 67 | it('error fixture without options should return annotations', (done) => { 68 | parser.validate(error_fixture, (err, refract) => { 69 | assert.isNull(err); 70 | assert.deepEqual(refract, error_refract); 71 | done(); 72 | }); 73 | }); 74 | 75 | it('warning fixture without options should return annotations', (done) => { 76 | parser.validate(warning_fixture, (err, refract) => { 77 | assert.isNull(err); 78 | assert.deepEqual(refract, warning_validate_refract); 79 | done(); 80 | }); 81 | }); 82 | }); 83 | }; 84 | -------------------------------------------------------------------------------- /test/helpers/options.js: -------------------------------------------------------------------------------- 1 | const assert = require('chai').assert; 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const valid_fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'valid.apib'), 'utf8'); 6 | 7 | const valid_refract = require('../fixtures/valid.parse.json'); 8 | const valid_sourcemap_refract = require('../fixtures/valid.parse.sourcemap.json'); 9 | const require_name = require('../fixtures/require-name.json'); 10 | const generate_body = require('../fixtures/generate-body.parse.json'); 11 | const generate_body_sourcemap = require('../fixtures/generate-body.parse.sourcemap.json'); 12 | const generate_body_schema = require('../fixtures/generate-body-schema.parse.json'); 13 | const generate_body_schema_sourcemap = require('../fixtures/generate-body-schema.parse.sourcemap.json'); 14 | 15 | module.exports = (parser) => { 16 | describe('Options containing', () => { 17 | it('unsupported option when parsing should throw', () => { 18 | assert.throws(() => { 19 | parser.parse(valid_fixture, { type: 'refract' }); 20 | }, `unrecognized option 'type', expected: 'requireBlueprintName', 'generateMessageBody', 'generateMessageBodySchema', 'generateSourceMap'`); 21 | }); 22 | 23 | it('unsupported option when validating should throw', () => { 24 | assert.throws(() => { 25 | parser.validate(valid_fixture, { type: 'refract' }); 26 | }, `unrecognized option 'type', expected: 'requireBlueprintName'`); 27 | }); 28 | 29 | it('sourcemap option set to false should work', (done) => { 30 | parser.parse(valid_fixture, { generateSourceMap: false }) 31 | .then((refract) => { 32 | assert.deepEqual(refract, valid_refract); 33 | done(); 34 | }) 35 | .catch(done); 36 | }) 37 | 38 | it('old sourcemap option should work', (done) => { 39 | parser.parse(valid_fixture, { exportSourcemap: true }) 40 | .then((refract) => { 41 | assert.deepEqual(refract, valid_sourcemap_refract); 42 | done(); 43 | }) 44 | .catch(done); 45 | }); 46 | 47 | describe('requireBlueprintName', () => { 48 | describe('when parsing', () => { 49 | it('should work', (done) => { 50 | parser.parse(valid_fixture, { requireBlueprintName: true }) 51 | .then((refract) => { 52 | assert.deepEqual(refract, require_name); 53 | done(); 54 | }) 55 | .catch(done); 56 | }); 57 | 58 | it('along with sourcemap should work', (done) => { 59 | parser.parse(valid_fixture, { requireBlueprintName: true, generateSourceMap: true }) 60 | .then((refract) => { 61 | assert.deepEqual(refract, require_name); 62 | done(); 63 | }) 64 | .catch(done); 65 | }) 66 | }); 67 | 68 | it('when validating should work', (done) => { 69 | parser.validate(valid_fixture, { requireBlueprintName: true }) 70 | .then((refract) => { 71 | assert.deepEqual(refract, require_name); 72 | done(); 73 | }) 74 | .catch(done); 75 | }); 76 | }); 77 | 78 | describe('generateMessageBody', () => { 79 | describe('when parsing', () => { 80 | it('should work', (done) => { 81 | parser.parse(valid_fixture, { generateMessageBody: false }) 82 | .then((refract) => { 83 | assert.deepEqual(refract, generate_body); 84 | done(); 85 | }) 86 | .catch(done); 87 | }); 88 | 89 | it('along with sourcemap should work', (done) => { 90 | parser.parse(valid_fixture, { generateMessageBody: false, generateSourceMap: true }) 91 | .then((refract) => { 92 | assert.deepEqual(refract, generate_body_sourcemap); 93 | done(); 94 | }) 95 | .catch(done); 96 | }) 97 | }); 98 | }); 99 | 100 | describe('generateMessageBodySchema', () => { 101 | describe('when parsing', () => { 102 | it('should work', (done) => { 103 | parser.parse(valid_fixture, { generateMessageBodySchema: false }) 104 | .then((refract) => { 105 | assert.deepEqual(refract, generate_body_schema); 106 | done(); 107 | }) 108 | .catch(done); 109 | }); 110 | 111 | it('along with sourcemap should work', (done) => { 112 | parser.parse(valid_fixture, { generateMessageBodySchema: false, generateSourceMap: true }) 113 | .then((refract) => { 114 | assert.deepEqual(refract, generate_body_schema_sourcemap); 115 | done(); 116 | }) 117 | .catch(done); 118 | }) 119 | }); 120 | }); 121 | }); 122 | }; 123 | -------------------------------------------------------------------------------- /test/helpers/params.js: -------------------------------------------------------------------------------- 1 | const assert = require('chai').assert; 2 | 3 | module.exports = (parser) => { 4 | describe('Params for parse', () => { 5 | it('when none should throw', () => { 6 | assert.throws(() => { 7 | parser.parse(); 8 | }, 'wrong number of arguments, `parse(string, options, callback)` expected'); 9 | }); 10 | 11 | it('when more than 3 should throw', () => { 12 | assert.throws(() => { 13 | parser.parse('string', {}, () => {}, 'extra'); 14 | }, 'wrong number of arguments, `parse(string, options, callback)` expected'); 15 | }); 16 | 17 | it('when first is not string should throw', () => { 18 | assert.throws(() => { 19 | parser.parse({}); 20 | }, 'wrong 1st argument - string expected, `parse(string, options, [callback])`'); 21 | }); 22 | 23 | it('when more than 2 but last is not function should throw', () => { 24 | assert.throws(() => { 25 | parser.parse('string', {}, {}); 26 | }, 'wrong number of arguments, `parse(string, options)` expected'); 27 | }); 28 | 29 | it('when 3 but second is not object should throw', () => { 30 | assert.throws(() => { 31 | parser.parse('string', true, () => {}); 32 | }, 'wrong 2nd argument - object expected, `parse(string, options, [callback])`'); 33 | }); 34 | 35 | it('when 2 but second is not object or function should throw', () => { 36 | assert.throws(() => { 37 | parser.parse('string', true); 38 | }, 'wrong 2nd argument - object expected, `parse(string, options, [callback])`'); 39 | }); 40 | }); 41 | 42 | describe('Params for parseSync', () => { 43 | it('when none should throw', () => { 44 | assert.throws(() => { 45 | parser.parseSync(); 46 | }, 'wrong number of arguments, `parseSync(string, options)` expected'); 47 | }); 48 | 49 | it('when more than 2 should throw', () => { 50 | assert.throws(() => { 51 | parser.parseSync('string', {}, () => {}); 52 | }, 'wrong number of arguments, `parseSync(string, options)` expected'); 53 | }); 54 | 55 | it('when first is not string should throw', () => { 56 | assert.throws(() => { 57 | parser.parseSync({}); 58 | }, 'wrong 1st argument - string expected, `parseSync(string, options)`'); 59 | }); 60 | 61 | it('when second is not object should throw', () => { 62 | assert.throws(() => { 63 | parser.parseSync('string', true); 64 | }, 'wrong 2nd argument - object expected, `parseSync(string, options)`'); 65 | }); 66 | }); 67 | 68 | describe('Params for validate', () => { 69 | it('when none should throw', () => { 70 | assert.throws(() => { 71 | parser.validate(); 72 | }, 'wrong number of arguments, `validate(string, options, callback)` expected'); 73 | }); 74 | 75 | it('when more than 3 should throw', () => { 76 | assert.throws(() => { 77 | parser.validate('string', {}, () => {}, 'extra'); 78 | }, 'wrong number of arguments, `validate(string, options, callback)` expected'); 79 | }); 80 | 81 | it('when first is not string should throw', () => { 82 | assert.throws(() => { 83 | parser.validate({}); 84 | }, 'wrong 1st argument - string expected, `validate(string, options, [callback])`'); 85 | }); 86 | 87 | it('when more than 2 but last is not function should throw', () => { 88 | assert.throws(() => { 89 | parser.validate('string', {}, {}); 90 | }, 'wrong number of arguments, `validate(string, options)` expected'); 91 | }); 92 | 93 | it('when 3 but second is not object should throw', () => { 94 | assert.throws(() => { 95 | parser.validate('string', true, () => {}); 96 | }, 'wrong 2nd argument - object expected, `validate(string, options, [callback])`'); 97 | }); 98 | 99 | it('when 2 but second is not object or function should throw', () => { 100 | assert.throws(() => { 101 | parser.validate('string', true); 102 | }, 'wrong 2nd argument - object expected, `validate(string, options, [callback])`'); 103 | }); 104 | }); 105 | 106 | describe('Params for validateSync', () => { 107 | it('when none should throw', () => { 108 | assert.throws(() => { 109 | parser.validateSync(); 110 | }, 'wrong number of arguments, `validateSync(string, options)` expected'); 111 | }); 112 | 113 | it('when more than 2 should throw', () => { 114 | assert.throws(() => { 115 | parser.validateSync('string', {}, () => {}); 116 | }, 'wrong number of arguments, `validateSync(string, options)` expected'); 117 | }); 118 | 119 | it('when first is not string should throw', () => { 120 | assert.throws(() => { 121 | parser.validateSync({}); 122 | }, 'wrong 1st argument - string expected, `validateSync(string, options)`'); 123 | }); 124 | 125 | it('when second is not object should throw', () => { 126 | assert.throws(() => { 127 | parser.validateSync('string', true); 128 | }, 'wrong 2nd argument - object expected, `validateSync(string, options)`'); 129 | }); 130 | }); 131 | }; 132 | -------------------------------------------------------------------------------- /test/helpers/promise.js: -------------------------------------------------------------------------------- 1 | const assert = require('chai').assert; 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const valid_fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'valid.apib'), 'utf8'); 6 | const warning_fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'warning.apib'), 'utf8'); 7 | const error_fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'error.apib'), 'utf8'); 8 | 9 | const valid_refract = require('../fixtures/valid.parse.json'); 10 | const valid_sourcemap_refract = require('../fixtures/valid.parse.sourcemap.json'); 11 | const warning_parse_refract = require('../fixtures/warning.parse.json'); 12 | const warning_validate_refract = require('../fixtures/warning.validate.json'); 13 | const error_refract = require('../fixtures/error.json'); 14 | 15 | module.exports = (parser) => { 16 | describe('Parse with promise', () => { 17 | it('valid fixture without options should return parse result', (done) => { 18 | parser.parse(valid_fixture) 19 | .then(refract => { 20 | assert.deepEqual(refract, valid_refract); 21 | done(); 22 | }) 23 | .catch(done); 24 | }); 25 | 26 | it('valid fixture with options should return parse result', (done) => { 27 | parser.parse(valid_fixture, { generateSourceMap: true }) 28 | .then(refract => { 29 | assert.deepEqual(refract, valid_sourcemap_refract); 30 | done(); 31 | }) 32 | .catch(done); 33 | }); 34 | 35 | it('error fixture without options should return parse result', (done) => { 36 | parser.parse(error_fixture) 37 | .then(refract => { 38 | assert.deepEqual(refract, error_refract); 39 | done(); 40 | }) 41 | .catch(done); 42 | }); 43 | 44 | it('warning fixture without options should return parse result', (done) => { 45 | parser.parse(warning_fixture) 46 | .then(refract => { 47 | assert.deepEqual(refract, warning_parse_refract); 48 | done(); 49 | }) 50 | .catch(done); 51 | }); 52 | }); 53 | 54 | describe('Validate with promise', () => { 55 | it('valid fixture without options should return null', (done) => { 56 | parser.validate(valid_fixture) 57 | .then(refract => { 58 | assert.isNull(refract); 59 | done(); 60 | }) 61 | .catch(done); 62 | }); 63 | 64 | it('valid fixture with options should return null', (done) => { 65 | parser.validate(valid_fixture, {}) 66 | .then(refract => { 67 | assert.isNull(refract); 68 | done(); 69 | }) 70 | .catch(done); 71 | }); 72 | 73 | it('error fixture without options should return annotations', (done) => { 74 | parser.validate(error_fixture) 75 | .then(refract => { 76 | assert.deepEqual(refract, error_refract); 77 | done(); 78 | }) 79 | .catch(done); 80 | }); 81 | 82 | it('warning fixture without options should return annotations', (done) => { 83 | parser.validate(warning_fixture) 84 | .then(refract => { 85 | assert.deepEqual(refract, warning_validate_refract); 86 | done(); 87 | }) 88 | .catch(done); 89 | }); 90 | }); 91 | }; 92 | -------------------------------------------------------------------------------- /test/helpers/protagonist.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | function loadConfig() { 5 | const configPath = path.join(__dirname, '..', '..', 'build', 'config.gypi'); 6 | const config = fs.readFileSync(configPath, 'utf-8') 7 | return JSON.parse(config.replace(`# Do not edit. File was generated by node-gyp's "configure" step`, '')); 8 | } 9 | 10 | function loadProtagonist() { 11 | const configuration = loadConfig().target_defaults.default_configuration; 12 | return require(`../../build/${configuration}/protagonist`); 13 | } 14 | 15 | module.exports = loadProtagonist(); 16 | -------------------------------------------------------------------------------- /test/helpers/sync.js: -------------------------------------------------------------------------------- 1 | const assert = require('chai').assert; 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const valid_fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'valid.apib'), 'utf8'); 6 | const warning_fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'warning.apib'), 'utf8'); 7 | const error_fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'error.apib'), 'utf8'); 8 | 9 | const valid_refract = require('../fixtures/valid.parse.json'); 10 | const valid_sourcemap_refract = require('../fixtures/valid.parse.sourcemap.json'); 11 | const warning_parse_refract = require('../fixtures/warning.parse.json'); 12 | const warning_validate_refract = require('../fixtures/warning.validate.json'); 13 | const error_refract = require('../fixtures/error.json'); 14 | 15 | module.exports = (parser) => { 16 | describe('Parse sync', () => { 17 | it('valid fixture without options should return parse result', () => { 18 | const refract = parser.parseSync(valid_fixture); 19 | assert.deepEqual(refract, valid_refract); 20 | }); 21 | 22 | it('valid fixture with options should return parse result', () => { 23 | const refract = parser.parseSync(valid_fixture, { generateSourceMap: true }); 24 | assert.deepEqual(refract, valid_sourcemap_refract); 25 | }); 26 | 27 | it('error fixture without options should return parse result', () => { 28 | const refract = parser.parseSync(error_fixture); 29 | assert.deepEqual(refract, error_refract); 30 | }); 31 | 32 | it('warning fixture without options should return parse result', () => { 33 | const refract = parser.parseSync(warning_fixture); 34 | assert.deepEqual(refract, warning_parse_refract); 35 | }); 36 | }); 37 | 38 | describe('Validate sync', () => { 39 | it('valid fixture without options should return null', () => { 40 | const refract = parser.validateSync(valid_fixture); 41 | assert.isNull(refract); 42 | }); 43 | 44 | it('valid fixture with options should return null', () => { 45 | const refract = parser.validateSync(valid_fixture, {}); 46 | assert.isNull(refract); 47 | }); 48 | 49 | it('error fixture without options should return annotations', () => { 50 | const refract = parser.validateSync(error_fixture); 51 | assert.deepEqual(refract, error_refract); 52 | }); 53 | 54 | it('warning fixture without options should return annotations', () => { 55 | const refract = parser.validateSync(warning_fixture); 56 | assert.deepEqual(refract, warning_validate_refract); 57 | }); 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /test/options-test.js: -------------------------------------------------------------------------------- 1 | const optionsHelper = require('./helpers/options'); 2 | const protagonist = require('./helpers/protagonist'); 3 | 4 | optionsHelper(protagonist); 5 | -------------------------------------------------------------------------------- /test/params-test.js: -------------------------------------------------------------------------------- 1 | const paramsHelper = require('./helpers/params'); 2 | const protagonist = require('./helpers/protagonist'); 3 | 4 | paramsHelper(protagonist); 5 | -------------------------------------------------------------------------------- /test/performance/fixtures/fixture-1.apib: -------------------------------------------------------------------------------- 1 | FORMAT: 1A 2 | HOST: https://alpha-api.app.net 3 | 4 | # Real World API 5 | This API Blueprint demonstrates a real world example documenting a portion of [App.net API](http://developers.app.net). 6 | 7 | NOTE: This document is a **work in progress**. 8 | 9 | # Group Posts 10 | This section groups App.net post resources. 11 | 12 | ## Post [/stream/0/posts/{post_id}] 13 | A Post is the other central object utilized by the App.net Stream API. It has rich text and annotations which comprise all of the content a users sees in their feed. Posts are closely tied to the follow graph... 14 | 15 | + Parameters 16 | + post_id (string, `1`) ... The id of the Post. 17 | 18 | + Model (application/json) 19 | 20 | ```js 21 | { 22 | "data": { 23 | "id": "1", // note this is a string 24 | "user": { 25 | ... 26 | }, 27 | "created_at": "2012-07-16T17:25:47Z", 28 | "text": "@berg FIRST post on this new site #newsocialnetwork", 29 | "html": "@berg FIRST post on this new site #newsocialnetwork.", 30 | "source": { 31 | "client_id": "udxGzAVBdXwGtkHmvswR5MbMEeVnq6n4", 32 | "name": "Clientastic for iOS", 33 | "link": "http://app.net" 34 | }, 35 | "machine_only": false, 36 | "reply_to": null, 37 | "thread_id": "1", 38 | "num_replies": 3, 39 | "num_reposts": 0, 40 | "num_stars": 0, 41 | "entities": { 42 | "mentions": [{ 43 | "name": "berg", 44 | "id": "2", 45 | "pos": 0, 46 | "len": 5 47 | }], 48 | "hashtags": [{ 49 | "name": "newsocialnetwork", 50 | "pos": 34, 51 | "len": 17 52 | }], 53 | "links": [{ 54 | "text": "this new site", 55 | "url": "https://join.app.net" 56 | "pos": 20, 57 | "len": 13 58 | }] 59 | }, 60 | "you_reposted": false, 61 | "you_starred": false 62 | }, 63 | "meta": { 64 | "code": 200, 65 | } 66 | } 67 | ``` 68 | 69 | ### Retrieve a Post [GET] 70 | Returns a specific Post. 71 | 72 | + Response 200 73 | 74 | [Post][] 75 | 76 | ### Delete a Post [DELETE] 77 | Delete a Post. The current user must be the same user who created the Post. It returns the deleted Post on success. 78 | 79 | + Response 204 80 | 81 | ## Posts Collection [/stream/0/posts] 82 | A Collection of posts. 83 | 84 | + Model (application/json) 85 | 86 | ```js 87 | { 88 | ["data": { 89 | "id": "1", // note this is a string 90 | "user": { 91 | ... 92 | }, 93 | "created_at": "2012-07-16T17:25:47Z", 94 | "text": "@berg FIRST post on this new site #newsocialnetwork", 95 | "html": "@berg FIRST post on this new site #newsocialnetwork.", 96 | "source": { 97 | "client_id": "udxGzAVBdXwGtkHmvswR5MbMEeVnq6n4", 98 | "name": "Clientastic for iOS", 99 | "link": "http://app.net" 100 | }, 101 | "machine_only": false, 102 | "reply_to": null, 103 | "thread_id": "1", 104 | "num_replies": 3, 105 | "num_reposts": 0, 106 | "num_stars": 0, 107 | "entities": { 108 | "mentions": [{ 109 | "name": "berg", 110 | "id": "2", 111 | "pos": 0, 112 | "len": 5 113 | }], 114 | "hashtags": [{ 115 | "name": "newsocialnetwork", 116 | "pos": 34, 117 | "len": 17 118 | }], 119 | "links": [{ 120 | "text": "this new site", 121 | "url": "https://join.app.net" 122 | "pos": 20, 123 | "len": 13 124 | }] 125 | }, 126 | "you_reposted": false, 127 | "you_starred": false 128 | }, 129 | "meta": { 130 | "code": 200, 131 | }], 132 | ... 133 | } 134 | ``` 135 | 136 | ### Create a Post [POST] 137 | Create a new Post object. Mentions and hashtags will be parsed out of the post text, as will bare URLs... 138 | 139 | + Request 140 | 141 | [Post][] 142 | 143 | + Response 201 144 | 145 | [Post][] 146 | 147 | ### Retrieve all Posts [GET] 148 | Retrieves all posts. 149 | 150 | + Response 200 151 | 152 | [Posts Collection][] 153 | 154 | ## Stars [/stream/0/posts/{post_id}/star] 155 | A User’s stars are visible to others, but they are not automatically added to your followers’ streams. 156 | 157 | + Parameters 158 | + post_id (string, `1`) ... The id of the Post. 159 | 160 | ### Star a Post [POST] 161 | Save a given Post to the current User’s stars. This is just a “save” action, not a sharing action. 162 | 163 | *Note: A repost cannot be starred. Please star the parent Post.* 164 | 165 | + Response 200 166 | 167 | [Post][] 168 | 169 | ### Unstar a Post [DELETE] 170 | Remove a Star from a Post. 171 | 172 | + Response 200 173 | 174 | [Post][] 175 | -------------------------------------------------------------------------------- /test/performance/fixtures/fixture-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": [ 8 | "api" 9 | ], 10 | "title": "Real World API" 11 | }, 12 | "attributes": { 13 | "meta": [ 14 | { 15 | "element": "member", 16 | "meta": { 17 | "classes": [ 18 | "user" 19 | ] 20 | }, 21 | "content": { 22 | "key": { 23 | "element": "string", 24 | "content": "FORMAT" 25 | }, 26 | "value": { 27 | "element": "string", 28 | "content": "1A" 29 | } 30 | } 31 | }, 32 | { 33 | "element": "member", 34 | "meta": { 35 | "classes": [ 36 | "user" 37 | ] 38 | }, 39 | "content": { 40 | "key": { 41 | "element": "string", 42 | "content": "HOST" 43 | }, 44 | "value": { 45 | "element": "string", 46 | "content": "https://alpha-api.app.net" 47 | } 48 | } 49 | } 50 | ] 51 | }, 52 | "content": [ 53 | { 54 | "element": "copy", 55 | "content": "This API Blueprint demonstrates a real world example documenting a portion of [App.net API](http://developers.app.net).\n\nNOTE: This document is a **work in progress**." 56 | }, 57 | { 58 | "element": "category", 59 | "meta": { 60 | "classes": [ 61 | "resourceGroup" 62 | ], 63 | "title": "Posts" 64 | }, 65 | "content": [ 66 | { 67 | "element": "copy", 68 | "content": "This section groups App.net post resources." 69 | }, 70 | { 71 | "element": "resource", 72 | "meta": { 73 | "title": "Post" 74 | }, 75 | "attributes": { 76 | "href": "/stream/0/posts/{post_id}", 77 | "hrefVariables": { 78 | "element": "hrefVariables", 79 | "content": [ 80 | { 81 | "element": "member", 82 | "meta": { 83 | "description": "The id of the Post." 84 | }, 85 | "attributes": { 86 | "typeAttributes": [ 87 | "required" 88 | ] 89 | }, 90 | "content": { 91 | "key": { 92 | "element": "string", 93 | "content": "post_id" 94 | }, 95 | "value": { 96 | "element": "string", 97 | "content": "1" 98 | } 99 | } 100 | } 101 | ] 102 | } 103 | }, 104 | "content": [ 105 | { 106 | "element": "copy", 107 | "content": "A Post is the other central object utilized by the App.net Stream API. It has rich text and annotations which comprise all of the content a users sees in their feed. Posts are closely tied to the follow graph..." 108 | }, 109 | { 110 | "element": "transition", 111 | "meta": { 112 | "title": "Retrieve a Post" 113 | }, 114 | "content": [ 115 | { 116 | "element": "copy", 117 | "content": "Returns a specific Post." 118 | }, 119 | { 120 | "element": "httpTransaction", 121 | "content": [ 122 | { 123 | "element": "httpRequest", 124 | "attributes": { 125 | "method": "GET" 126 | }, 127 | "content": [] 128 | }, 129 | { 130 | "element": "httpResponse", 131 | "attributes": { 132 | "statusCode": "200", 133 | "headers": { 134 | "element": "httpHeaders", 135 | "content": [ 136 | { 137 | "element": "member", 138 | "content": { 139 | "key": { 140 | "element": "string", 141 | "content": "Content-Type" 142 | }, 143 | "value": { 144 | "element": "string", 145 | "content": "application/json" 146 | } 147 | } 148 | } 149 | ] 150 | } 151 | }, 152 | "content": [ 153 | { 154 | "element": "asset", 155 | "meta": { 156 | "classes": [ 157 | "messageBody" 158 | ] 159 | }, 160 | "attributes": { 161 | "contentType": "application/json" 162 | }, 163 | "content": "{\n \"data\": {\n \"id\": \"1\", // note this is a string\n \"user\": {\n ...\n },\n \"created_at\": \"2012-07-16T17:25:47Z\",\n \"text\": \"@berg FIRST post on this new site #newsocialnetwork\",\n \"html\": \"@berg FIRST post on this new site #newsocialnetwork.\",\n \"source\": {\n \"client_id\": \"udxGzAVBdXwGtkHmvswR5MbMEeVnq6n4\",\n \"name\": \"Clientastic for iOS\",\n \"link\": \"http://app.net\"\n },\n \"machine_only\": false,\n \"reply_to\": null,\n \"thread_id\": \"1\",\n \"num_replies\": 3,\n \"num_reposts\": 0,\n \"num_stars\": 0,\n \"entities\": {\n \"mentions\": [{\n \"name\": \"berg\",\n \"id\": \"2\",\n \"pos\": 0,\n \"len\": 5\n }],\n \"hashtags\": [{\n \"name\": \"newsocialnetwork\",\n \"pos\": 34,\n \"len\": 17\n }],\n \"links\": [{\n \"text\": \"this new site\",\n \"url\": \"https://join.app.net\"\n \"pos\": 20,\n \"len\": 13\n }]\n },\n \"you_reposted\": false,\n \"you_starred\": false\n },\n \"meta\": {\n \"code\": 200,\n }\n}\n" 164 | } 165 | ] 166 | } 167 | ] 168 | } 169 | ] 170 | }, 171 | { 172 | "element": "transition", 173 | "meta": { 174 | "title": "Delete a Post" 175 | }, 176 | "content": [ 177 | { 178 | "element": "copy", 179 | "content": "Delete a Post. The current user must be the same user who created the Post. It returns the deleted Post on success." 180 | }, 181 | { 182 | "element": "httpTransaction", 183 | "content": [ 184 | { 185 | "element": "httpRequest", 186 | "attributes": { 187 | "method": "DELETE" 188 | }, 189 | "content": [] 190 | }, 191 | { 192 | "element": "httpResponse", 193 | "attributes": { 194 | "statusCode": "204" 195 | }, 196 | "content": [] 197 | } 198 | ] 199 | } 200 | ] 201 | } 202 | ] 203 | }, 204 | { 205 | "element": "resource", 206 | "meta": { 207 | "title": "Posts Collection" 208 | }, 209 | "attributes": { 210 | "href": "/stream/0/posts" 211 | }, 212 | "content": [ 213 | { 214 | "element": "copy", 215 | "content": "A Collection of posts." 216 | }, 217 | { 218 | "element": "transition", 219 | "meta": { 220 | "title": "Create a Post" 221 | }, 222 | "content": [ 223 | { 224 | "element": "copy", 225 | "content": "Create a new Post object. Mentions and hashtags will be parsed out of the post text, as will bare URLs..." 226 | }, 227 | { 228 | "element": "httpTransaction", 229 | "content": [ 230 | { 231 | "element": "httpRequest", 232 | "attributes": { 233 | "method": "POST", 234 | "headers": { 235 | "element": "httpHeaders", 236 | "content": [ 237 | { 238 | "element": "member", 239 | "content": { 240 | "key": { 241 | "element": "string", 242 | "content": "Content-Type" 243 | }, 244 | "value": { 245 | "element": "string", 246 | "content": "application/json" 247 | } 248 | } 249 | } 250 | ] 251 | } 252 | }, 253 | "content": [ 254 | { 255 | "element": "asset", 256 | "meta": { 257 | "classes": [ 258 | "messageBody" 259 | ] 260 | }, 261 | "attributes": { 262 | "contentType": "application/json" 263 | }, 264 | "content": "{\n \"data\": {\n \"id\": \"1\", // note this is a string\n \"user\": {\n ...\n },\n \"created_at\": \"2012-07-16T17:25:47Z\",\n \"text\": \"@berg FIRST post on this new site #newsocialnetwork\",\n \"html\": \"@berg FIRST post on this new site #newsocialnetwork.\",\n \"source\": {\n \"client_id\": \"udxGzAVBdXwGtkHmvswR5MbMEeVnq6n4\",\n \"name\": \"Clientastic for iOS\",\n \"link\": \"http://app.net\"\n },\n \"machine_only\": false,\n \"reply_to\": null,\n \"thread_id\": \"1\",\n \"num_replies\": 3,\n \"num_reposts\": 0,\n \"num_stars\": 0,\n \"entities\": {\n \"mentions\": [{\n \"name\": \"berg\",\n \"id\": \"2\",\n \"pos\": 0,\n \"len\": 5\n }],\n \"hashtags\": [{\n \"name\": \"newsocialnetwork\",\n \"pos\": 34,\n \"len\": 17\n }],\n \"links\": [{\n \"text\": \"this new site\",\n \"url\": \"https://join.app.net\"\n \"pos\": 20,\n \"len\": 13\n }]\n },\n \"you_reposted\": false,\n \"you_starred\": false\n },\n \"meta\": {\n \"code\": 200,\n }\n}\n" 265 | } 266 | ] 267 | }, 268 | { 269 | "element": "httpResponse", 270 | "attributes": { 271 | "statusCode": "201", 272 | "headers": { 273 | "element": "httpHeaders", 274 | "content": [ 275 | { 276 | "element": "member", 277 | "content": { 278 | "key": { 279 | "element": "string", 280 | "content": "Content-Type" 281 | }, 282 | "value": { 283 | "element": "string", 284 | "content": "application/json" 285 | } 286 | } 287 | } 288 | ] 289 | } 290 | }, 291 | "content": [ 292 | { 293 | "element": "asset", 294 | "meta": { 295 | "classes": [ 296 | "messageBody" 297 | ] 298 | }, 299 | "attributes": { 300 | "contentType": "application/json" 301 | }, 302 | "content": "{\n \"data\": {\n \"id\": \"1\", // note this is a string\n \"user\": {\n ...\n },\n \"created_at\": \"2012-07-16T17:25:47Z\",\n \"text\": \"@berg FIRST post on this new site #newsocialnetwork\",\n \"html\": \"@berg FIRST post on this new site #newsocialnetwork.\",\n \"source\": {\n \"client_id\": \"udxGzAVBdXwGtkHmvswR5MbMEeVnq6n4\",\n \"name\": \"Clientastic for iOS\",\n \"link\": \"http://app.net\"\n },\n \"machine_only\": false,\n \"reply_to\": null,\n \"thread_id\": \"1\",\n \"num_replies\": 3,\n \"num_reposts\": 0,\n \"num_stars\": 0,\n \"entities\": {\n \"mentions\": [{\n \"name\": \"berg\",\n \"id\": \"2\",\n \"pos\": 0,\n \"len\": 5\n }],\n \"hashtags\": [{\n \"name\": \"newsocialnetwork\",\n \"pos\": 34,\n \"len\": 17\n }],\n \"links\": [{\n \"text\": \"this new site\",\n \"url\": \"https://join.app.net\"\n \"pos\": 20,\n \"len\": 13\n }]\n },\n \"you_reposted\": false,\n \"you_starred\": false\n },\n \"meta\": {\n \"code\": 200,\n }\n}\n" 303 | } 304 | ] 305 | } 306 | ] 307 | } 308 | ] 309 | }, 310 | { 311 | "element": "transition", 312 | "meta": { 313 | "title": "Retrieve all Posts" 314 | }, 315 | "content": [ 316 | { 317 | "element": "copy", 318 | "content": "Retrieves all posts." 319 | }, 320 | { 321 | "element": "httpTransaction", 322 | "content": [ 323 | { 324 | "element": "httpRequest", 325 | "attributes": { 326 | "method": "GET" 327 | }, 328 | "content": [] 329 | }, 330 | { 331 | "element": "httpResponse", 332 | "attributes": { 333 | "statusCode": "200", 334 | "headers": { 335 | "element": "httpHeaders", 336 | "content": [ 337 | { 338 | "element": "member", 339 | "content": { 340 | "key": { 341 | "element": "string", 342 | "content": "Content-Type" 343 | }, 344 | "value": { 345 | "element": "string", 346 | "content": "application/json" 347 | } 348 | } 349 | } 350 | ] 351 | } 352 | }, 353 | "content": [ 354 | { 355 | "element": "asset", 356 | "meta": { 357 | "classes": [ 358 | "messageBody" 359 | ] 360 | }, 361 | "attributes": { 362 | "contentType": "application/json" 363 | }, 364 | "content": "{ \n [\"data\": {\n \"id\": \"1\", // note this is a string\n \"user\": {\n ...\n },\n \"created_at\": \"2012-07-16T17:25:47Z\",\n \"text\": \"@berg FIRST post on this new site #newsocialnetwork\",\n \"html\": \"@berg FIRST post on this new site #newsocialnetwork.\",\n \"source\": {\n \"client_id\": \"udxGzAVBdXwGtkHmvswR5MbMEeVnq6n4\",\n \"name\": \"Clientastic for iOS\",\n \"link\": \"http://app.net\"\n },\n \"machine_only\": false,\n \"reply_to\": null,\n \"thread_id\": \"1\",\n \"num_replies\": 3,\n \"num_reposts\": 0,\n \"num_stars\": 0,\n \"entities\": {\n \"mentions\": [{\n \"name\": \"berg\",\n \"id\": \"2\",\n \"pos\": 0,\n \"len\": 5\n }],\n \"hashtags\": [{\n \"name\": \"newsocialnetwork\",\n \"pos\": 34,\n \"len\": 17\n }],\n \"links\": [{\n \"text\": \"this new site\",\n \"url\": \"https://join.app.net\"\n \"pos\": 20,\n \"len\": 13\n }]\n },\n \"you_reposted\": false,\n \"you_starred\": false\n },\n \"meta\": {\n \"code\": 200,\n }],\n ...\n}\n" 365 | } 366 | ] 367 | } 368 | ] 369 | } 370 | ] 371 | } 372 | ] 373 | }, 374 | { 375 | "element": "resource", 376 | "meta": { 377 | "title": "Stars" 378 | }, 379 | "attributes": { 380 | "href": "/stream/0/posts/{post_id}/star", 381 | "hrefVariables": { 382 | "element": "hrefVariables", 383 | "content": [ 384 | { 385 | "element": "member", 386 | "meta": { 387 | "description": "The id of the Post." 388 | }, 389 | "attributes": { 390 | "typeAttributes": [ 391 | "required" 392 | ] 393 | }, 394 | "content": { 395 | "key": { 396 | "element": "string", 397 | "content": "post_id" 398 | }, 399 | "value": { 400 | "element": "string", 401 | "content": "1" 402 | } 403 | } 404 | } 405 | ] 406 | } 407 | }, 408 | "content": [ 409 | { 410 | "element": "copy", 411 | "content": "A User’s stars are visible to others, but they are not automatically added to your followers’ streams." 412 | }, 413 | { 414 | "element": "transition", 415 | "meta": { 416 | "title": "Star a Post" 417 | }, 418 | "content": [ 419 | { 420 | "element": "copy", 421 | "content": "Save a given Post to the current User’s stars. This is just a “save” action, not a sharing action.\n\n*Note: A repost cannot be starred. Please star the parent Post.*" 422 | }, 423 | { 424 | "element": "httpTransaction", 425 | "content": [ 426 | { 427 | "element": "httpRequest", 428 | "attributes": { 429 | "method": "POST" 430 | }, 431 | "content": [] 432 | }, 433 | { 434 | "element": "httpResponse", 435 | "attributes": { 436 | "statusCode": "200", 437 | "headers": { 438 | "element": "httpHeaders", 439 | "content": [ 440 | { 441 | "element": "member", 442 | "content": { 443 | "key": { 444 | "element": "string", 445 | "content": "Content-Type" 446 | }, 447 | "value": { 448 | "element": "string", 449 | "content": "application/json" 450 | } 451 | } 452 | } 453 | ] 454 | } 455 | }, 456 | "content": [ 457 | { 458 | "element": "asset", 459 | "meta": { 460 | "classes": [ 461 | "messageBody" 462 | ] 463 | }, 464 | "attributes": { 465 | "contentType": "application/json" 466 | }, 467 | "content": "{\n \"data\": {\n \"id\": \"1\", // note this is a string\n \"user\": {\n ...\n },\n \"created_at\": \"2012-07-16T17:25:47Z\",\n \"text\": \"@berg FIRST post on this new site #newsocialnetwork\",\n \"html\": \"@berg FIRST post on this new site #newsocialnetwork.\",\n \"source\": {\n \"client_id\": \"udxGzAVBdXwGtkHmvswR5MbMEeVnq6n4\",\n \"name\": \"Clientastic for iOS\",\n \"link\": \"http://app.net\"\n },\n \"machine_only\": false,\n \"reply_to\": null,\n \"thread_id\": \"1\",\n \"num_replies\": 3,\n \"num_reposts\": 0,\n \"num_stars\": 0,\n \"entities\": {\n \"mentions\": [{\n \"name\": \"berg\",\n \"id\": \"2\",\n \"pos\": 0,\n \"len\": 5\n }],\n \"hashtags\": [{\n \"name\": \"newsocialnetwork\",\n \"pos\": 34,\n \"len\": 17\n }],\n \"links\": [{\n \"text\": \"this new site\",\n \"url\": \"https://join.app.net\"\n \"pos\": 20,\n \"len\": 13\n }]\n },\n \"you_reposted\": false,\n \"you_starred\": false\n },\n \"meta\": {\n \"code\": 200,\n }\n}\n" 468 | } 469 | ] 470 | } 471 | ] 472 | } 473 | ] 474 | }, 475 | { 476 | "element": "transition", 477 | "meta": { 478 | "title": "Unstar a Post" 479 | }, 480 | "content": [ 481 | { 482 | "element": "copy", 483 | "content": "Remove a Star from a Post." 484 | }, 485 | { 486 | "element": "httpTransaction", 487 | "content": [ 488 | { 489 | "element": "httpRequest", 490 | "attributes": { 491 | "method": "DELETE" 492 | }, 493 | "content": [] 494 | }, 495 | { 496 | "element": "httpResponse", 497 | "attributes": { 498 | "statusCode": "200", 499 | "headers": { 500 | "element": "httpHeaders", 501 | "content": [ 502 | { 503 | "element": "member", 504 | "content": { 505 | "key": { 506 | "element": "string", 507 | "content": "Content-Type" 508 | }, 509 | "value": { 510 | "element": "string", 511 | "content": "application/json" 512 | } 513 | } 514 | } 515 | ] 516 | } 517 | }, 518 | "content": [ 519 | { 520 | "element": "asset", 521 | "meta": { 522 | "classes": [ 523 | "messageBody" 524 | ] 525 | }, 526 | "attributes": { 527 | "contentType": "application/json" 528 | }, 529 | "content": "{\n \"data\": {\n \"id\": \"1\", // note this is a string\n \"user\": {\n ...\n },\n \"created_at\": \"2012-07-16T17:25:47Z\",\n \"text\": \"@berg FIRST post on this new site #newsocialnetwork\",\n \"html\": \"@berg FIRST post on this new site #newsocialnetwork.\",\n \"source\": {\n \"client_id\": \"udxGzAVBdXwGtkHmvswR5MbMEeVnq6n4\",\n \"name\": \"Clientastic for iOS\",\n \"link\": \"http://app.net\"\n },\n \"machine_only\": false,\n \"reply_to\": null,\n \"thread_id\": \"1\",\n \"num_replies\": 3,\n \"num_reposts\": 0,\n \"num_stars\": 0,\n \"entities\": {\n \"mentions\": [{\n \"name\": \"berg\",\n \"id\": \"2\",\n \"pos\": 0,\n \"len\": 5\n }],\n \"hashtags\": [{\n \"name\": \"newsocialnetwork\",\n \"pos\": 34,\n \"len\": 17\n }],\n \"links\": [{\n \"text\": \"this new site\",\n \"url\": \"https://join.app.net\"\n \"pos\": 20,\n \"len\": 13\n }]\n },\n \"you_reposted\": false,\n \"you_starred\": false\n },\n \"meta\": {\n \"code\": 200,\n }\n}\n" 530 | } 531 | ] 532 | } 533 | ] 534 | } 535 | ] 536 | } 537 | ] 538 | } 539 | ] 540 | } 541 | ] 542 | } 543 | ] 544 | } 545 | 546 | -------------------------------------------------------------------------------- /test/performance/fixtures/fixture-2.apib: -------------------------------------------------------------------------------- 1 | # Group Messages 2 | 3 | # Message [/messages/{id}] 4 | 5 | ## Retrieve Message [GET] 6 | + Response 200 (text/plain) 7 | 8 | Hello World! 9 | 10 | ## Delete Message [DELETE] 11 | + Response 204 12 | -------------------------------------------------------------------------------- /test/performance/fixtures/fixture-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "element": "parseResult", 3 | "content": [ 4 | { 5 | "element": "category", 6 | "meta": { 7 | "classes": [ 8 | "api" 9 | ], 10 | "title": "" 11 | }, 12 | "content": [ 13 | { 14 | "element": "category", 15 | "meta": { 16 | "classes": [ 17 | "resourceGroup" 18 | ], 19 | "title": "Messages" 20 | }, 21 | "content": [ 22 | { 23 | "element": "resource", 24 | "meta": { 25 | "title": "Message" 26 | }, 27 | "attributes": { 28 | "href": "/messages/{id}" 29 | }, 30 | "content": [ 31 | { 32 | "element": "transition", 33 | "meta": { 34 | "title": "Retrieve Message" 35 | }, 36 | "content": [ 37 | { 38 | "element": "httpTransaction", 39 | "content": [ 40 | { 41 | "element": "httpRequest", 42 | "attributes": { 43 | "method": "GET" 44 | }, 45 | "content": [] 46 | }, 47 | { 48 | "element": "httpResponse", 49 | "attributes": { 50 | "statusCode": "200", 51 | "headers": { 52 | "element": "httpHeaders", 53 | "content": [ 54 | { 55 | "element": "member", 56 | "content": { 57 | "key": { 58 | "element": "string", 59 | "content": "Content-Type" 60 | }, 61 | "value": { 62 | "element": "string", 63 | "content": "text/plain" 64 | } 65 | } 66 | } 67 | ] 68 | } 69 | }, 70 | "content": [ 71 | { 72 | "element": "asset", 73 | "meta": { 74 | "classes": [ 75 | "messageBody" 76 | ] 77 | }, 78 | "attributes": { 79 | "contentType": "text/plain" 80 | }, 81 | "content": "Hello World!\n" 82 | } 83 | ] 84 | } 85 | ] 86 | } 87 | ] 88 | }, 89 | { 90 | "element": "transition", 91 | "meta": { 92 | "title": "Delete Message" 93 | }, 94 | "content": [ 95 | { 96 | "element": "httpTransaction", 97 | "content": [ 98 | { 99 | "element": "httpRequest", 100 | "attributes": { 101 | "method": "DELETE" 102 | }, 103 | "content": [] 104 | }, 105 | { 106 | "element": "httpResponse", 107 | "attributes": { 108 | "statusCode": "204" 109 | }, 110 | "content": [] 111 | } 112 | ] 113 | } 114 | ] 115 | } 116 | ] 117 | } 118 | ] 119 | } 120 | ] 121 | } 122 | ] 123 | } 124 | 125 | -------------------------------------------------------------------------------- /test/performance/perf-protagonist.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Protagonist async threading performance test 3 | * 4 | * Run wiht fixtures: 5 | * 6 | * node ../perf-protagonist.js fixture-1.apib fixture-1.json fixture-2.apib fixture-2.json 7 | */ 8 | 9 | var fs = require('fs'); 10 | var path = require('path'); 11 | var protagonist = require('../../build/Release/protagonist'); 12 | var async = require('async'); 13 | 14 | // Total number of runs 15 | var totalRuns = 10000; 16 | 17 | // String buffers for blueprint, its ast and its alternatives 18 | var blueprint, ast, altBlueprint, altAst; 19 | 20 | // Process arguments 21 | var args = process.argv.slice(2); 22 | if (typeof args == 'undefined' || args.length !== 4) { 23 | var scriptName = path.basename(process.argv[1]); 24 | console.log('usage: ' + scriptName + ' \n'); 25 | process.exit(0); 26 | } 27 | 28 | // Create a parse function alternating blueprints based on index 29 | // \return Parse function taking a callback as its argument 30 | var createParseFunction = function createParseFunction(index) { 31 | 32 | return function (callback) { 33 | //console.log("> calling #" + index); 34 | 35 | protagonist.parse((index % 2) ? blueprint : altBlueprint, function (err, result) { 36 | 37 | //console.log("< finished #" + index); 38 | if (err) { 39 | console.error(JSON.stringify(err, null, 2)); 40 | return callback(err); 41 | } 42 | 43 | if (JSON.stringify(result) !== ((index % 2) ? ast : altAst)) { 44 | return callback(new Error('parsing result does not match its input AST (' + index + ')')); 45 | } 46 | 47 | process.stdout.write('.'); 48 | callback(); 49 | }); 50 | }; 51 | } 52 | 53 | // Normalize input JSON string 54 | // \param jsonString A JSON string buffer as read from an input file 55 | // \return normalized JSON string 56 | var normalizeJSONString = function normalizeJSONString(jsonString) { 57 | 58 | try { 59 | return JSON.stringify(JSON.parse(jsonString)); 60 | } 61 | catch (e) { 62 | console.error('input is not a valid JSON'); 63 | return; 64 | } 65 | } 66 | 67 | // Run %totalRuns paprotagonist.parse() in async parallel 68 | var runAsyncTest = function runAsyncTest() { 69 | 70 | var pool = []; 71 | 72 | for (var i = 0; i < totalRuns; ++i) { 73 | pool[i] = createParseFunction(i); 74 | } 75 | 76 | var startTime = new Date().getTime(); 77 | 78 | async.parallel(pool, function (err) { 79 | if (err) { 80 | console.error(err); 81 | process.exit(err.code); 82 | } 83 | 84 | var endTime = new Date().getTime(); 85 | var time = endTime - startTime; 86 | console.log('\ndone (' + time + 'ms)'); 87 | }); 88 | } 89 | 90 | // Read input files & run test 91 | var inputData = []; 92 | async.forEachSeries(args, function (path, callback) { 93 | 94 | fs.readFile(path, 'utf8', function (err, data) { 95 | if (err) 96 | return callback(err); 97 | 98 | inputData.push(data); 99 | callback(); 100 | }); 101 | }, function (err) { 102 | 103 | // Set up test buffers 104 | blueprint = inputData[0]; 105 | ast = normalizeJSONString(inputData[1]); 106 | altBlueprint = inputData[2]; 107 | altAst = normalizeJSONString(inputData[3]); 108 | 109 | // Run the test 110 | runAsyncTest(); 111 | }); 112 | -------------------------------------------------------------------------------- /test/promise-test.js: -------------------------------------------------------------------------------- 1 | const promiseHelper = require('./helpers/promise'); 2 | const protagonist = require('./helpers/protagonist'); 3 | 4 | promiseHelper(protagonist); 5 | -------------------------------------------------------------------------------- /test/protagonist-options-crash-test.js: -------------------------------------------------------------------------------- 1 | const protagonist = require('./helpers/protagonist'); 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const valid_fixture = fs.readFileSync(path.join(__dirname, 'fixtures', 'valid.apib'), 'utf8'); 6 | const expect = require('chai').expect; 7 | const assert = require('chai').assert; 8 | 9 | describe('Protagonist option vulerability', () => { 10 | it('should not segfault protagonist', () => { 11 | assert.throws(() => { 12 | protagonist.parse(valid_fixture, { get requireBlueprintName() { throw 'nope' }}); 13 | }, 'nope') 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /test/sync-test.js: -------------------------------------------------------------------------------- 1 | const syncHelper = require('./helpers/sync'); 2 | const protagonist = require('./helpers/protagonist'); 3 | 4 | syncHelper(protagonist); 5 | -------------------------------------------------------------------------------- /test/timeline-test.js: -------------------------------------------------------------------------------- 1 | const assert = require('chai').assert; 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const protagonist = require('./helpers/protagonist'); 5 | 6 | const valid_fixture = fs.readFileSync(path.join(__dirname, 'fixtures', 'valid.apib'), 'utf8'); 7 | 8 | describe('Async Parse', () => { 9 | it('should finish after Sync Parse', (done) => { 10 | var syncFinished = false; 11 | var syncParsed = null; 12 | 13 | protagonist.parse(valid_fixture, (err, res) => { 14 | if (syncFinished) { 15 | assert.isNull(err); 16 | assert.deepEqual(res, syncParsed); 17 | done(); 18 | } else { 19 | done(new Error('Async finished before sync')); 20 | } 21 | }); 22 | 23 | syncParsed = protagonist.parseSync(valid_fixture); 24 | syncFinished = true; 25 | }); 26 | }); 27 | 28 | describe('Async Validate', () => { 29 | it('should finish after Sync Validate', (done) => { 30 | var syncFinished = false; 31 | var syncParsed = null; 32 | 33 | protagonist.validate(valid_fixture, (err, res) => { 34 | if (syncFinished) { 35 | assert.isNull(err); 36 | assert.deepEqual(res, syncParsed); 37 | done(); 38 | } else { 39 | done(new Error('Async finished before sync')); 40 | } 41 | }); 42 | 43 | syncParsed = protagonist.validateSync(valid_fixture); 44 | syncFinished = true; 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /tools/protagonist.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Naive commandline parser 3 | */ 4 | 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var protagonist = require('../build/Release/protagonist'); 8 | //var protagonist = require('protagonist'); 9 | 10 | // Add Segfault handler 11 | // 12 | // Note: this is possible only for OS X & *nix 13 | // 14 | // Install (or add to package.json) segfault-handler: 15 | // "segfault-handler": "git://github.com/ddopson/node-segfault-handler.git#master" 16 | // 17 | //var segfaultHandler = require('segfault-handler'); 18 | //segfaultHandler.registerHandler(); 19 | 20 | // Process arguments 21 | var args = process.argv.slice(2); 22 | if (typeof args == 'undefined' || args.length !== 1) { 23 | var scriptName = path.basename(process.argv[1]); 24 | console.log('usage: ' + scriptName + ' \n'); 25 | process.exit(0); 26 | } 27 | 28 | // Read & parse 29 | fs.readFile(args[0], 'utf8', function (err, data) { 30 | if (err) throw err; 31 | 32 | protagonist.parse(data, {generateSourceMap: true}, function (err, result) { 33 | if (err) { 34 | console.log(JSON.stringify(err, null, 2)); 35 | process.exit(err.code); 36 | } 37 | 38 | console.log(JSON.stringify(result, null, 2)); 39 | }); 40 | }); 41 | --------------------------------------------------------------------------------