├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bin └── yang-swagger ├── config └── default.yaml ├── package.json ├── swagger.yaml ├── yang-json-schema.yang ├── yang-openapi.coffee ├── yang-openapi.js ├── yang-openapi.yang └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | *~ 40 | 41 | # ignore generated *.js files inside lib and dist 42 | lib/* 43 | dist/* 44 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yang-swagger 2 | 3 | YANG model-driven swagger/openapi transform 4 | 5 | [![NPM Version][npm-image]][npm-url] 6 | [![NPM Downloads][downloads-image]][downloads-url] 7 | 8 | ## Installation 9 | 10 | ```bash 11 | $ npm install -g yang-swagger 12 | ``` 13 | 14 | The preferred installation is *global* for easy access to the 15 | `yang-swagger` utility but can also be used as a dependency module to 16 | help generate swagger/openapi specification as part of your project. 17 | 18 | > NOTE: Currently, Swagger/OpenAPI 2.0 Specification does **NOT** 19 | > support JSON-schema `anyOf` and `oneOf` directives, which means 20 | > that we cannot properly treat YANG `choice/case` statements during 21 | > conversion. As a work-around, the current transform will simply 22 | > serialize all configuration nodes from the `choice/case` sections 23 | > into a flat list of properties. 24 | 25 | ## Quick Start 26 | 27 | ```bash 28 | $ yang-swagger -f yaml -o swagger.yaml yang-openapi 29 | ``` 30 | 31 | The above example will import the `yang-openapi` YANG module and 32 | transform into swagger specification YAML file. 33 | 34 | ``` 35 | Usage: yang-swagger [options] modules... 36 | 37 | Options: 38 | -c, --config Use to retrieve configuration data (default: uses 'config' directory) 39 | -f, --format Convert to YAML or JSON (default: json) 40 | -o, --output Write to if specified, otherwise to console 41 | ``` 42 | 43 | Using the `--config` option will allow you to specify where to find 44 | the configuration data for the `yang-openapi` YANG module. By default, 45 | it will check the `config` directory but you can specify any arbitrary 46 | file location for specifying the `yang-openapi:info` configuration 47 | data. 48 | 49 | You can also use it as a library module: 50 | 51 | ```coffeescript 52 | swag = require("yang-swagger").eval { 53 | 'yang-openapi:info': 54 | title: "my-api" 55 | description: "describe purpose" 56 | version: "1.0" 57 | contact: 58 | name: "your name" 59 | url: "http://some/website" 60 | email: "your email" 61 | license: 62 | name: "Apache-2.0" 63 | } 64 | swag.in('transform') 65 | .invoke modules: [ 'yang-openapi' ] 66 | .then (output) -> 67 | console.log "do something with " 68 | ``` 69 | 70 | For more information on programmatic usage, be sure to take a look at 71 | the References listed below. 72 | 73 | ## References 74 | 75 | - [Apiary Documentation](http://docs.yangswagger.apiary.io) 76 | - [Working with Multiple Schemas](http://github.com/corenova/yang-js/blob/master/TUTORIAL.md#working-with-multiple-schemas) 77 | - [Expressing Interfaces](http://github.com/corenova/yang-express) 78 | - [Using YANG with JavaScript](http://github.com/corenova/yang-js) 79 | 80 | ## License 81 | [Apache 2.0](LICENSE) 82 | 83 | This software is brought to you by 84 | [Corenova Technologies](http://www.corenova.com). We'd love to hear 85 | your feedback. Please feel free to reach me at 86 | anytime with questions, suggestions, etc. 87 | 88 | [npm-image]: https://img.shields.io/npm/v/yang-swagger.svg 89 | [npm-url]: https://npmjs.org/package/yang-swagger 90 | [downloads-image]: https://img.shields.io/npm/dt/yang-swagger.svg 91 | [downloads-url]: https://npmjs.org/package/yang-swagger 92 | -------------------------------------------------------------------------------- /bin/yang-swagger: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const argv = require('minimist')(process.argv.slice(2), { 6 | boolean: [ 'help' ], 7 | alias: { 8 | help: 'h', 9 | config: 'c', 10 | format: 'f', 11 | output: 'o' 12 | }, 13 | string: [ 'config', 'format', 'output' ] 14 | }); 15 | 16 | if (argv.h === true) { 17 | let help; 18 | help = " Usage: yang-swagger [options] modules...\n\n"; 19 | help += " Options:\n"; 20 | help += " -c, --config Use to retrieve configuration data (default: uses 'config' directory)\n"; 21 | help += " -f, --format Convert to YAML or JSON (default: json)\n"; 22 | help += " -o, --output Write to if specified, otherwise to console\n"; 23 | console.info(help); 24 | process.exit(); 25 | } 26 | 27 | let config = require('config'); 28 | if (argv.config) { 29 | config = config.util.parseFile(path.resolve(argv.config)); 30 | } 31 | const swagger = require('..').eval(config) 32 | 33 | swagger.in('transform').do({modules: argv._}) 34 | .then(res => res.spec.serialize(argv)) 35 | .then(res => { 36 | if (argv.output) { 37 | fs.writeFileSync(argv.output, res.data, 'utf-8'); 38 | } else { 39 | console.log(res.data); 40 | } 41 | return res; 42 | }) 43 | .catch(err => console.error(err)); 44 | -------------------------------------------------------------------------------- /config/default.yaml: -------------------------------------------------------------------------------- 1 | yang-openapi:info: 2 | title: YANG-to-SWAGGER 3 | description: | 4 | Provides YANG model-driven schema transformations to 5 | swagger/openapi schema formats. 6 | This output is self-generated using its own YANG model 7 | `yang-openapi.yang` as input to generate the swagger/openapi 8 | specification. 9 | version: '1.0' 10 | contact: 11 | name: Peter K. Lee 12 | url: https://github.com/corenova/yang-swagger 13 | email: peter@corenova.com 14 | license: 15 | name: 'Apache-2.0' 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yang-swagger", 3 | "version": "2.3.4", 4 | "description": "YANG model-driven swagger/openapi transform", 5 | "keywords": [ 6 | "yang", 7 | "transform", 8 | "swagger", 9 | "openapi", 10 | "yang2swagger", 11 | "model", 12 | "schema", 13 | "adaptive", 14 | "validate", 15 | "object", 16 | "rfc6020" 17 | ], 18 | "author": "Peter Lee ", 19 | "homepage": "https://github.com/corenova/yang-swagger", 20 | "license": "Apache-2.0", 21 | "repository": "corenova/yang-swagger", 22 | "main": "./yang-openapi.js", 23 | "bin": { 24 | "yang-swagger": "./bin/yang-swagger" 25 | }, 26 | "preferGlobal": true, 27 | "yang": { 28 | "search": [ 29 | ".", 30 | "yang-js" 31 | ] 32 | }, 33 | "dependencies": { 34 | "config": "^3.0.1", 35 | "debug": "^4.1.1", 36 | "js-yaml": "^3.13.1", 37 | "minimist": "^1.2.0", 38 | "traverse": "^0.6.6", 39 | "yang-js": "^0.24.63" 40 | }, 41 | "devDependencies": { 42 | "coffeescript": "2", 43 | "mocha": "^5.2.0", 44 | "rimraf": "^2.5.2", 45 | "should": "~3.1.3" 46 | }, 47 | "scripts": { 48 | "clean": "rimraf dist/* lib/*", 49 | "prepare:dist": "yarn clean -s && mkdir -p dist", 50 | "prepare:src": "coffee -c yang-openapi.coffee", 51 | "prepare:api": "bin/yang-swagger -f yaml -o swagger.yaml yang-openapi", 52 | "prepare": "yarn prepare:dist && yarn prepare:src", 53 | "prepublishOnly": "yarn prepare:api", 54 | "pretest": "yarn prepare:src", 55 | "test": "mocha" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /yang-json-schema.yang: -------------------------------------------------------------------------------- 1 | module yang-json-schema { 2 | namespace "urn:ietf:params:xml:ns:yang:yang-json-schema"; 3 | prefix json; 4 | yang-version 1.1; 5 | 6 | import ietf-yang-types { prefix yang; } 7 | import ietf-inet-types { prefix inet; } 8 | 9 | organization 10 | "Corenova Technologies, Inc."; 11 | contact 12 | "Peter K. Lee "; 13 | reference 14 | "https://json-schema.org"; 15 | 16 | /* 17 | * Type definitions 18 | */ 19 | typedef json-schema-ref { 20 | type inet:uri; 21 | } 22 | /* 23 | * Groupings 24 | */ 25 | grouping json-datatype { 26 | leaf type { 27 | type enumeration { 28 | enum string; 29 | enum number; 30 | enum integer; 31 | enum boolean; 32 | enum array; 33 | enum file; 34 | enum object; 35 | } 36 | } 37 | leaf format { 38 | type union { 39 | type enumeration { 40 | enum int32; 41 | enum int64; 42 | enum float; 43 | enum double; 44 | enum byte; 45 | enum binary; 46 | enum date; 47 | enum date-time; 48 | enum password; 49 | } 50 | type string; 51 | } 52 | } 53 | } 54 | grouping json-schema { 55 | leaf $ref { 56 | type json-schema-ref; 57 | } 58 | uses json:json-datatype; 59 | leaf-list required { 60 | type string; 61 | // type leafref { 62 | // path '../property/name'; 63 | // } 64 | } 65 | list property { 66 | key name; 67 | leaf name { 68 | type yang:yang-identifier; 69 | } 70 | container schema { 71 | uses json:json-schema { 72 | when "../name"; 73 | } 74 | } 75 | } 76 | container items { 77 | when "../type = 'array'"; 78 | uses json:json-schema { 79 | when "../type"; 80 | } 81 | } 82 | list allOf { 83 | uses json:json-schema { 84 | when "not(../type)"; 85 | } 86 | } 87 | list anyOf { 88 | uses json:json-schema { 89 | when "not(../type)"; 90 | } 91 | } 92 | list oneOf { 93 | uses json:json-schema { 94 | when "not(../property)"; 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /yang-openapi.coffee: -------------------------------------------------------------------------------- 1 | # OPENAPI (swagger) specification feature 2 | 3 | debug = require('debug')('yang-swagger') if process.env.DEBUG? 4 | traverse = require 'traverse' 5 | Yang = require 'yang-js' 6 | yaml = require 'js-yaml' 7 | 8 | # TODO: this should be handled via separate yang-json-schema module 9 | definitions = {} 10 | yang2jstype = (schema) -> 11 | switch schema.state.basetype 12 | when 'uint8','int8' 13 | type: 'integer' 14 | format: 'byte' 15 | when 'uint16','uint32','uint64','int16','int32','int64' 16 | type: 'integer' 17 | format: schema.state.basetype 18 | when 'binary' 19 | type: 'string' 20 | format: 'binary' 21 | when 'decimal64' 22 | type: 'number' 23 | format: 'double' 24 | when 'union' 25 | anyOf: [].concat(schema.type).map (s) => yang2jstype s, true 26 | type: 'string' 27 | format: schema.tag 28 | when 'boolean' 29 | type: 'boolean' 30 | format: schema.tag 31 | when 'enumeration' 32 | type: 'string' 33 | format: schema.tag 34 | enum: schema.enum.map (e) -> e.tag 35 | else 36 | # TODO: handle pattern? 37 | type: 'string' 38 | format: schema.tag 39 | 40 | yang2jsprop = (schema) -> 41 | js = 42 | description: schema.description?.tag 43 | default: schema.default?.tag 44 | return js unless schema.type? 45 | datatype = yang2jstype schema.type 46 | js[k] = v for k, v of datatype 47 | return js 48 | 49 | yang2jsobj = (schema) -> 50 | return {} unless schema? 51 | #debug? "[#{schema.uri}] converting schema to JSON-schema" 52 | js = 53 | description: schema.description?.tag 54 | required = [] 55 | property = schema.nodes 56 | .filter (x) -> x.kind not in ['action', 'choice'] and x.parent is schema 57 | .map (node) -> 58 | if node.mandatory?.valueOf() is true 59 | required.push node.tag 60 | name: node.tag 61 | schema: yang2jschema node.origin ? node 62 | 63 | choices = schema.choice?.filter (x) -> x.parent is schema 64 | .map (choice) -> anyOf: choice.case?.map (node) -> yang2jsobj node.origin ? node 65 | 66 | refs = schema.uses?.filter (x) -> x.parent is schema 67 | switch 68 | when refs?.length 69 | refs.forEach (ref) -> 70 | unless definitions[ref.tag]? 71 | debug? "[yang2jsobj] defining #{ref.tag} using #{schema.uri}" 72 | definitions[ref.tag] = true 73 | definitions[ref.tag] = yang2jsobj schema.lookup('grouping', ref.tag) 74 | 75 | if refs.length > 1 or property.length 76 | js.allOf = refs.map (ref) -> '$ref': "#/definitions/#{ref.tag}" 77 | if property.length 78 | js.allOf.push 79 | required: if required.length then required else undefined 80 | property: property 81 | if choices?.length 82 | js.allOf.push choices... 83 | else 84 | ref = refs[0] 85 | js['$ref'] = "#/definitions/#{ref.tag}" 86 | when choices?.length 87 | if choices.length > 1 or property.length 88 | js.allOf = [].concat choices 89 | if property.length 90 | js.allOf.push 91 | required: if required.length then required else undefined 92 | property: property 93 | else 94 | js.anyOf = choices[0].anyOf 95 | else 96 | js.type = 'object' 97 | js.property = property if property.length 98 | js.required = required if required.length 99 | return js 100 | 101 | yang2jschema = (schema, item=false) -> 102 | return {} unless schema? 103 | switch schema.kind 104 | when 'leaf' then yang2jsprop schema 105 | when 'leaf-list' then type: 'array', items: yang2jsprop schema 106 | when 'list' 107 | unless item then type: 'array', items: yang2jsobj schema 108 | else yang2jsobj schema 109 | when 'grouping' then {} 110 | else yang2jsobj schema 111 | 112 | discoverOperations = (schema, item=false) -> 113 | debug? "[discoverOperations] inspecting #{schema.uri}" 114 | origin = schema.root.tag 115 | deprecated = schema.status?.valueOf() is 'deprecated' 116 | switch 117 | when schema.kind in [ 'rpc', 'action' ] then [ 118 | method: 'post' 119 | tags: [ origin ] 120 | description: schema.description?.tag 121 | summary: "Invokes #{schema.tag} in #{schema.parent.tag}" 122 | deprecated: deprecated 123 | parameter: [ 124 | name: "#{schema.tag}:input" 125 | in: 'body' 126 | description: schema.input?.description?.tag 127 | schema: yang2jschema schema.input 128 | ] 129 | response: [ 130 | code: 200 131 | description: "Expected response of #{schema.tag}" 132 | schema: yang2jschema schema.output 133 | ] 134 | ] 135 | when schema.kind is 'list' and not item then [ 136 | method: 'post' 137 | tags: [ origin ] 138 | description: schema.description?.tag 139 | summary: "Creates one or more new #{schema.tag} in #{schema.parent.tag}" 140 | deprecated: deprecated 141 | parameter: [ 142 | name: "#{schema.tag}" 143 | in: 'body' 144 | description: schema.description?.tag 145 | schema: yang2jschema schema 146 | ] 147 | response: [ 148 | code: 200 149 | description: "Expected response for creating #{schema.tag}(s) in collection" 150 | schema: yang2jschema schema 151 | ] 152 | , 153 | method: 'get' 154 | tags: [ origin ] 155 | summary: "List all #{schema.tag}s from #{schema.parent.tag}" 156 | deprecated: deprecated 157 | response: [ 158 | code: 200 159 | description: "Expected response of #{schema.tag}s" 160 | schema: yang2jschema schema 161 | ] 162 | , 163 | method: 'put' 164 | tags: [ origin ] 165 | summary: "Replace the entire #{schema.tag} collection" 166 | deprecated: deprecated 167 | parameter: [ 168 | name: "#{schema.tag}" 169 | in: 'body' 170 | description: schema.description?.tag 171 | schema: yang2jschema schema 172 | ] 173 | response: [ 174 | code: 201 175 | description: "Expected response for replacing collection" 176 | ] 177 | , 178 | method: 'patch' 179 | tags: [ origin ] 180 | summary: "Merge items into the #{schema.tag} collection" 181 | deprecated: deprecated 182 | parameter: [ 183 | name: "#{schema.tag}" 184 | in: 'body' 185 | description: schema.description?.tag 186 | schema: yang2jschema schema 187 | ] 188 | response: [ 189 | code: 201 190 | description: "Expected response for merging into collection" 191 | ] 192 | ] 193 | else [ 194 | method: 'get' 195 | tags: [ origin ] 196 | description: schema.description?.tag 197 | summary: "View detail on #{schema.tag}" 198 | deprecated: deprecated 199 | response: [ 200 | code: 200 201 | description: "Expected response of #{schema.tag}" 202 | schema: yang2jschema schema, item 203 | ] 204 | , 205 | method: 'put' 206 | tags: [ origin ] 207 | summary: "Update details on #{schema.tag}" 208 | deprecated: deprecated 209 | parameter: [ 210 | name: "#{schema.tag}" 211 | in: 'body' 212 | description: schema.description?.tag 213 | schema: yang2jschema schema, item 214 | ] 215 | response: [ 216 | code: 200 217 | description: "Expected response of #{schema.tag}" 218 | schema: yang2jschema schema, item 219 | ] 220 | , 221 | method: 'patch' 222 | tags: [ origin ] 223 | summary: "Merge details on #{schema.tag}" 224 | deprecated: deprecated 225 | parameter: [ 226 | name: "#{schema.tag}" 227 | in: 'body' 228 | description: schema.description?.tag 229 | schema: yang2jschema schema, item 230 | ] 231 | response: [ 232 | code: 200 233 | description: "Expected response of #{schema.tag}" 234 | schema: yang2jschema schema, item 235 | ] 236 | , 237 | method: 'delete' 238 | tags: [ origin ] 239 | summary: "Delete #{schema.tag} from #{schema.parent.tag}" 240 | deprecated: deprecated 241 | response: [ 242 | code: 204 243 | description: "Expected response for delete" 244 | ] 245 | ] 246 | 247 | discoverPathParameter = (schema) -> 248 | debug? "[discoverPathParameter] inspecting #{schema.uri}" 249 | switch 250 | when not schema.key? 251 | name: 'index' 252 | in: 'path' 253 | required: true 254 | type: 'integer' 255 | format: 'int64' 256 | description: "An index key identifying #{schema.tag} item (may change over time)" 257 | when schema.key.tag.length > 1 258 | name: "#{schema.key.tag.join('+')}" 259 | in: 'path' 260 | required: true 261 | type: 'string' 262 | format: 'composite' 263 | description: "A composite key uniquely identifying #{schema.tag} item" 264 | else 265 | param = 266 | name: "#{schema.key.valueOf()}" 267 | in: 'path' 268 | required: true 269 | description: "A key uniquely identifying #{schema.tag} item" 270 | param[k] = v for k, v of yang2jsprop schema.locate(param.name) when v? 271 | return param 272 | 273 | discoverPaths = (schema) -> 274 | return [] unless schema.kind in [ 'list', 'container', 'rpc', 'action' ] 275 | return [] if schema['if-feature']? # XXX ignore if-feature entries... 276 | 277 | name = "/#{schema.datakey}" 278 | debug? "[discoverPaths] inspecting #{schema.uri}" 279 | paths = [ 280 | name: name 281 | operation: discoverOperations schema 282 | ] 283 | subpaths = [].concat (discoverPaths sub for sub in schema.nodes)... 284 | switch schema.kind 285 | when 'list' 286 | param = discoverPathParameter(schema) 287 | # test if any subpaths have same param.name 288 | for sub in subpaths 289 | if (sub.parameter?.some (x) -> x.name is param.name) 290 | param.name = "#{schema.tag}-#{param.name}" 291 | break 292 | subpaths.forEach (x) -> 293 | x.parameter?.push param 294 | x.parameter ?= [ param ] 295 | x.name = "#{name}/{#{param.name}}" + x.name 296 | debug? "[discoverPaths] subpath #{x.name} has parameters: #{x.parameter.map (p) -> p.name}" 297 | paths.push 298 | name: "#{name}/{#{param.name}}" 299 | parameter: [ param ] 300 | operation: discoverOperations(schema,true) 301 | when 'container' 302 | subpaths.forEach (x) -> x.name = name + x.name 303 | debug? "[discoverPaths] discovered #{paths.length} paths with #{subpaths.length} subpaths inside #{schema.uri}" 304 | paths.concat subpaths... 305 | 306 | discoverTags = (schema) -> 307 | return [] unless schema.kind in [ 'list', 'container', 'rpc', 'action' ] 308 | return [] if schema['if-feature']? # XXX ignore if-feature entries... 309 | 310 | name = "/#{schema.datakey}" 311 | debug? "[discoverTags] inspecting #{schema.uri}" 312 | tag = 313 | name: name 314 | description: schema.description?.tag ? "Operations related to #{name}" 315 | if schema.reference?.tag 316 | tag.externalDocs = 317 | description: "Find out more about #{name}" 318 | url: schema.reference.tag 319 | return tag 320 | 321 | serializeJSchema = (jschema) -> 322 | return unless jschema? 323 | isProperty = ('property' of jschema) 324 | o = {} 325 | o[k] = v for k, v of jschema when k isnt 'property' 326 | o.properties = jschema.property?.reduce ((a, _prop) -> 327 | a[_prop.name] = serializeJSchema _prop.schema 328 | return a 329 | ), {} 330 | o.items = serializeJSchema o.items 331 | o.allOf = o.allOf?.map (x) -> serializeJSchema x 332 | 333 | # Swagger 2.0 does NOT support anyOf or oneOf 334 | #o.anyOf = o.anyOf?.map (x) -> serializeJSchema x 335 | #o.oneOf = o.oneOf?.map (x) -> serializeJSchema x 336 | switch 337 | when o.anyOf? and isProperty 338 | o.type ?= 'object' 339 | o.properties ?= {} 340 | o.anyOf.forEach (x) -> 341 | x.property?.forEach (prop) -> 342 | o.properties[prop.name] = serializeJSchema prop.schema 343 | delete o.anyOf 344 | when o.anyOf? 345 | o.type ?= 'string' 346 | o.format ?= 'union' 347 | delete o.anyOf 348 | return o 349 | 350 | module.exports = require('./yang-openapi.yang').bind { 351 | 352 | transform: (ctx, input) -> 353 | debug? "[transform] using '#{input['@choice']}' as source" 354 | switch input['@choice'] 355 | when 'swagger-file' then throw ctx.error "swagger-file transform feature not yet supported!" 356 | debug? "[transform] importing '#{input.modules}'" 357 | modules = input.modules 358 | .map (name) => ctx.schema.constructor.import(name) 359 | .filter (x) -> x? 360 | unless modules.length 361 | throw ctx.error "unable to transform without any modules" 362 | found = modules.map (x) -> x.datakey 363 | debug? "[transform] transforming #{found}" 364 | definitions = {} # XXX - usage of globals is a hack (will have concurrency issues) 365 | output = 366 | spec: 367 | swagger: '2.0' 368 | info: ctx.get('/info') 369 | consumes: [ "application/json" ] 370 | produces: [ "application/json" ] 371 | tag: modules 372 | .map (m) -> 373 | tag = name: m.tag, description: "Operations related to #{m.tag}" 374 | if m.reference?.tag 375 | tag.externalDocs = 376 | description: "Find out more about #{m.tag}" 377 | url: m.reference.tag 378 | return tag 379 | path: modules 380 | .map (m) -> discoverPaths(schema) for schema in m.nodes 381 | .reduce ((a,b) -> a.concat b...), [] 382 | definition: (name: k, schema: v for k, v of definitions) 383 | return output 384 | 385 | 'grouping(specification)/serialize': (ctx, input) -> 386 | debug? "[#{ctx.path}] serializing specification" 387 | spec = ctx.toJSON(false) 388 | spec.tags = spec.tag 389 | spec.paths = spec.path.reduce ((a,_path) -> 390 | path = a[_path.name] = '$ref': _path['$ref'] 391 | for op in _path.operation ? [] 392 | operation = path[op.method] = {} 393 | operation[k] = v for k, v of op when k not in [ 'method', 'parameter', 'response' ] 394 | operation.parameters = traverse(op.parameter).map (x) -> 395 | @update serializeJSchema(x), true if @key is 'schema' 396 | operation.responses = op.response.reduce ((x,_res) -> 397 | x[_res.code] = 398 | description: _res.description 399 | schema: serializeJSchema _res.schema 400 | return x 401 | ), {} 402 | path.parameters = _path.parameter 403 | return a 404 | ), {} 405 | spec.definitions = spec.definition?.reduce ((a,_def) -> 406 | a[_def.name] = serializeJSchema _def.schema 407 | return a 408 | ), {} 409 | delete spec.tag 410 | delete spec.path 411 | delete spec.definition 412 | delete spec.serialize 413 | spec = traverse(spec).map (x) -> @remove() unless x? 414 | return 415 | data: switch input.format 416 | when 'json' then JSON.stringify spec, null, 2 417 | when 'yaml' then yaml.dump spec, lineWidth: -1 418 | } 419 | -------------------------------------------------------------------------------- /yang-openapi.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.5.1 2 | (function() { 3 | // OPENAPI (swagger) specification feature 4 | var Yang, debug, definitions, discoverOperations, discoverPathParameter, discoverPaths, discoverTags, serializeJSchema, traverse, yaml, yang2jschema, yang2jsobj, yang2jsprop, yang2jstype; 5 | 6 | if (process.env.DEBUG != null) { 7 | debug = require('debug')('yang-swagger'); 8 | } 9 | 10 | traverse = require('traverse'); 11 | 12 | Yang = require('yang-js'); 13 | 14 | yaml = require('js-yaml'); 15 | 16 | // TODO: this should be handled via separate yang-json-schema module 17 | definitions = {}; 18 | 19 | yang2jstype = function(schema) { 20 | switch (schema.state.basetype) { 21 | case 'uint8': 22 | case 'int8': 23 | return { 24 | type: 'integer', 25 | format: 'byte' 26 | }; 27 | case 'uint16': 28 | case 'uint32': 29 | case 'uint64': 30 | case 'int16': 31 | case 'int32': 32 | case 'int64': 33 | return { 34 | type: 'integer', 35 | format: schema.state.basetype 36 | }; 37 | case 'binary': 38 | return { 39 | type: 'string', 40 | format: 'binary' 41 | }; 42 | case 'decimal64': 43 | return { 44 | type: 'number', 45 | format: 'double' 46 | }; 47 | case 'union': 48 | return { 49 | anyOf: [].concat(schema.type).map((s) => { 50 | return yang2jstype(s, true); 51 | }), 52 | type: 'string', 53 | format: schema.tag 54 | }; 55 | case 'boolean': 56 | return { 57 | type: 'boolean', 58 | format: schema.tag 59 | }; 60 | case 'enumeration': 61 | return { 62 | type: 'string', 63 | format: schema.tag, 64 | enum: schema.enum.map(function(e) { 65 | return e.tag; 66 | }) 67 | }; 68 | default: 69 | return { 70 | // TODO: handle pattern? 71 | type: 'string', 72 | format: schema.tag 73 | }; 74 | } 75 | }; 76 | 77 | yang2jsprop = function(schema) { 78 | var datatype, js, k, ref1, ref2, v; 79 | js = { 80 | description: (ref1 = schema.description) != null ? ref1.tag : void 0, 81 | default: (ref2 = schema.default) != null ? ref2.tag : void 0 82 | }; 83 | if (schema.type == null) { 84 | return js; 85 | } 86 | datatype = yang2jstype(schema.type); 87 | for (k in datatype) { 88 | v = datatype[k]; 89 | js[k] = v; 90 | } 91 | return js; 92 | }; 93 | 94 | yang2jsobj = function(schema) { 95 | var choices, js, property, ref, ref1, ref2, ref3, refs, required; 96 | if (schema == null) { 97 | return {}; 98 | } 99 | //debug? "[#{schema.uri}] converting schema to JSON-schema" 100 | js = { 101 | description: (ref1 = schema.description) != null ? ref1.tag : void 0 102 | }; 103 | required = []; 104 | property = schema.nodes.filter(function(x) { 105 | var ref2; 106 | return ((ref2 = x.kind) !== 'action' && ref2 !== 'choice') && x.parent === schema; 107 | }).map(function(node) { 108 | var ref2, ref3; 109 | if (((ref2 = node.mandatory) != null ? ref2.valueOf() : void 0) === true) { 110 | required.push(node.tag); 111 | } 112 | return { 113 | name: node.tag, 114 | schema: yang2jschema((ref3 = node.origin) != null ? ref3 : node) 115 | }; 116 | }); 117 | choices = (ref2 = schema.choice) != null ? ref2.filter(function(x) { 118 | return x.parent === schema; 119 | }).map(function(choice) { 120 | var ref3; 121 | return { 122 | anyOf: (ref3 = choice.case) != null ? ref3.map(function(node) { 123 | var ref4; 124 | return yang2jsobj((ref4 = node.origin) != null ? ref4 : node); 125 | }) : void 0 126 | }; 127 | }) : void 0; 128 | refs = (ref3 = schema.uses) != null ? ref3.filter(function(x) { 129 | return x.parent === schema; 130 | }) : void 0; 131 | switch (false) { 132 | case !(refs != null ? refs.length : void 0): 133 | refs.forEach(function(ref) { 134 | if (definitions[ref.tag] == null) { 135 | if (typeof debug === "function") { 136 | debug(`[yang2jsobj] defining ${ref.tag} using ${schema.uri}`); 137 | } 138 | definitions[ref.tag] = true; 139 | return definitions[ref.tag] = yang2jsobj(schema.lookup('grouping', ref.tag)); 140 | } 141 | }); 142 | if (refs.length > 1 || property.length) { 143 | js.allOf = refs.map(function(ref) { 144 | return { 145 | '$ref': `#/definitions/${ref.tag}` 146 | }; 147 | }); 148 | if (property.length) { 149 | js.allOf.push({ 150 | required: required.length ? required : void 0, 151 | property: property 152 | }); 153 | } 154 | if (choices != null ? choices.length : void 0) { 155 | js.allOf.push(...choices); 156 | } 157 | } else { 158 | ref = refs[0]; 159 | js['$ref'] = `#/definitions/${ref.tag}`; 160 | } 161 | break; 162 | case !(choices != null ? choices.length : void 0): 163 | if (choices.length > 1 || property.length) { 164 | js.allOf = [].concat(choices); 165 | if (property.length) { 166 | js.allOf.push({ 167 | required: required.length ? required : void 0, 168 | property: property 169 | }); 170 | } 171 | } else { 172 | js.anyOf = choices[0].anyOf; 173 | } 174 | break; 175 | default: 176 | js.type = 'object'; 177 | if (property.length) { 178 | js.property = property; 179 | } 180 | if (required.length) { 181 | js.required = required; 182 | } 183 | } 184 | return js; 185 | }; 186 | 187 | yang2jschema = function(schema, item = false) { 188 | if (schema == null) { 189 | return {}; 190 | } 191 | switch (schema.kind) { 192 | case 'leaf': 193 | return yang2jsprop(schema); 194 | case 'leaf-list': 195 | return { 196 | type: 'array', 197 | items: yang2jsprop(schema) 198 | }; 199 | case 'list': 200 | if (!item) { 201 | return { 202 | type: 'array', 203 | items: yang2jsobj(schema) 204 | }; 205 | } else { 206 | return yang2jsobj(schema); 207 | } 208 | break; 209 | case 'grouping': 210 | return {}; 211 | default: 212 | return yang2jsobj(schema); 213 | } 214 | }; 215 | 216 | discoverOperations = function(schema, item = false) { 217 | var deprecated, origin, ref1, ref10, ref11, ref12, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9; 218 | if (typeof debug === "function") { 219 | debug(`[discoverOperations] inspecting ${schema.uri}`); 220 | } 221 | origin = schema.root.tag; 222 | deprecated = ((ref1 = schema.status) != null ? ref1.valueOf() : void 0) === 'deprecated'; 223 | switch (false) { 224 | case (ref2 = schema.kind) !== 'rpc' && ref2 !== 'action': 225 | return [ 226 | { 227 | method: 'post', 228 | tags: [origin], 229 | description: (ref3 = schema.description) != null ? ref3.tag : void 0, 230 | summary: `Invokes ${schema.tag} in ${schema.parent.tag}`, 231 | deprecated: deprecated, 232 | parameter: [ 233 | { 234 | name: `${schema.tag}:input`, 235 | in: 'body', 236 | description: (ref4 = schema.input) != null ? (ref5 = ref4.description) != null ? ref5.tag : void 0 : void 0, 237 | schema: yang2jschema(schema.input) 238 | } 239 | ], 240 | response: [ 241 | { 242 | code: 200, 243 | description: `Expected response of ${schema.tag}`, 244 | schema: yang2jschema(schema.output) 245 | } 246 | ] 247 | } 248 | ]; 249 | case !(schema.kind === 'list' && !item): 250 | return [ 251 | { 252 | method: 'post', 253 | tags: [origin], 254 | description: (ref6 = schema.description) != null ? ref6.tag : void 0, 255 | summary: `Creates one or more new ${schema.tag} in ${schema.parent.tag}`, 256 | deprecated: deprecated, 257 | parameter: [ 258 | { 259 | name: `${schema.tag}`, 260 | in: 'body', 261 | description: (ref7 = schema.description) != null ? ref7.tag : void 0, 262 | schema: yang2jschema(schema) 263 | } 264 | ], 265 | response: [ 266 | { 267 | code: 200, 268 | description: `Expected response for creating ${schema.tag}(s) in collection`, 269 | schema: yang2jschema(schema) 270 | } 271 | ] 272 | }, 273 | { 274 | method: 'get', 275 | tags: [origin], 276 | summary: `List all ${schema.tag}s from ${schema.parent.tag}`, 277 | deprecated: deprecated, 278 | response: [ 279 | { 280 | code: 200, 281 | description: `Expected response of ${schema.tag}s`, 282 | schema: yang2jschema(schema) 283 | } 284 | ] 285 | }, 286 | { 287 | method: 'put', 288 | tags: [origin], 289 | summary: `Replace the entire ${schema.tag} collection`, 290 | deprecated: deprecated, 291 | parameter: [ 292 | { 293 | name: `${schema.tag}`, 294 | in: 'body', 295 | description: (ref8 = schema.description) != null ? ref8.tag : void 0, 296 | schema: yang2jschema(schema) 297 | } 298 | ], 299 | response: [ 300 | { 301 | code: 201, 302 | description: "Expected response for replacing collection" 303 | } 304 | ] 305 | }, 306 | { 307 | method: 'patch', 308 | tags: [origin], 309 | summary: `Merge items into the ${schema.tag} collection`, 310 | deprecated: deprecated, 311 | parameter: [ 312 | { 313 | name: `${schema.tag}`, 314 | in: 'body', 315 | description: (ref9 = schema.description) != null ? ref9.tag : void 0, 316 | schema: yang2jschema(schema) 317 | } 318 | ], 319 | response: [ 320 | { 321 | code: 201, 322 | description: "Expected response for merging into collection" 323 | } 324 | ] 325 | } 326 | ]; 327 | default: 328 | return [ 329 | { 330 | method: 'get', 331 | tags: [origin], 332 | description: (ref10 = schema.description) != null ? ref10.tag : void 0, 333 | summary: `View detail on ${schema.tag}`, 334 | deprecated: deprecated, 335 | response: [ 336 | { 337 | code: 200, 338 | description: `Expected response of ${schema.tag}`, 339 | schema: yang2jschema(schema, 340 | item) 341 | } 342 | ] 343 | }, 344 | { 345 | method: 'put', 346 | tags: [origin], 347 | summary: `Update details on ${schema.tag}`, 348 | deprecated: deprecated, 349 | parameter: [ 350 | { 351 | name: `${schema.tag}`, 352 | in: 'body', 353 | description: (ref11 = schema.description) != null ? ref11.tag : void 0, 354 | schema: yang2jschema(schema, 355 | item) 356 | } 357 | ], 358 | response: [ 359 | { 360 | code: 200, 361 | description: `Expected response of ${schema.tag}`, 362 | schema: yang2jschema(schema, 363 | item) 364 | } 365 | ] 366 | }, 367 | { 368 | method: 'patch', 369 | tags: [origin], 370 | summary: `Merge details on ${schema.tag}`, 371 | deprecated: deprecated, 372 | parameter: [ 373 | { 374 | name: `${schema.tag}`, 375 | in: 'body', 376 | description: (ref12 = schema.description) != null ? ref12.tag : void 0, 377 | schema: yang2jschema(schema, 378 | item) 379 | } 380 | ], 381 | response: [ 382 | { 383 | code: 200, 384 | description: `Expected response of ${schema.tag}`, 385 | schema: yang2jschema(schema, 386 | item) 387 | } 388 | ] 389 | }, 390 | { 391 | method: 'delete', 392 | tags: [origin], 393 | summary: `Delete ${schema.tag} from ${schema.parent.tag}`, 394 | deprecated: deprecated, 395 | response: [ 396 | { 397 | code: 204, 398 | description: "Expected response for delete" 399 | } 400 | ] 401 | } 402 | ]; 403 | } 404 | }; 405 | 406 | discoverPathParameter = function(schema) { 407 | var k, param, ref1, v; 408 | if (typeof debug === "function") { 409 | debug(`[discoverPathParameter] inspecting ${schema.uri}`); 410 | } 411 | switch (false) { 412 | case !(schema.key == null): 413 | return { 414 | name: 'index', 415 | in: 'path', 416 | required: true, 417 | type: 'integer', 418 | format: 'int64', 419 | description: `An index key identifying ${schema.tag} item (may change over time)` 420 | }; 421 | case !(schema.key.tag.length > 1): 422 | return { 423 | name: `${schema.key.tag.join('+')}`, 424 | in: 'path', 425 | required: true, 426 | type: 'string', 427 | format: 'composite', 428 | description: `A composite key uniquely identifying ${schema.tag} item` 429 | }; 430 | default: 431 | param = { 432 | name: `${schema.key.valueOf()}`, 433 | in: 'path', 434 | required: true, 435 | description: `A key uniquely identifying ${schema.tag} item` 436 | }; 437 | ref1 = yang2jsprop(schema.locate(param.name)); 438 | for (k in ref1) { 439 | v = ref1[k]; 440 | if (v != null) { 441 | param[k] = v; 442 | } 443 | } 444 | return param; 445 | } 446 | }; 447 | 448 | discoverPaths = function(schema) { 449 | var i, len, name, param, paths, ref1, ref2, sub, subpaths; 450 | if ((ref1 = schema.kind) !== 'list' && ref1 !== 'container' && ref1 !== 'rpc' && ref1 !== 'action') { 451 | return []; 452 | } 453 | if (schema['if-feature'] != null) { 454 | return []; 455 | } 456 | name = `/${schema.datakey}`; 457 | if (typeof debug === "function") { 458 | debug(`[discoverPaths] inspecting ${schema.uri}`); 459 | } 460 | paths = [ 461 | { 462 | name: name, 463 | operation: discoverOperations(schema) 464 | } 465 | ]; 466 | subpaths = [].concat(...((function() { 467 | var i, len, ref2, results; 468 | ref2 = schema.nodes; 469 | results = []; 470 | for (i = 0, len = ref2.length; i < len; i++) { 471 | sub = ref2[i]; 472 | results.push(discoverPaths(sub)); 473 | } 474 | return results; 475 | })())); 476 | switch (schema.kind) { 477 | case 'list': 478 | param = discoverPathParameter(schema); 479 | // test if any subpaths have same param.name 480 | for (i = 0, len = subpaths.length; i < len; i++) { 481 | sub = subpaths[i]; 482 | if ((ref2 = sub.parameter) != null ? ref2.some(function(x) { 483 | return x.name === param.name; 484 | }) : void 0) { 485 | param.name = `${schema.tag}-${param.name}`; 486 | break; 487 | } 488 | } 489 | subpaths.forEach(function(x) { 490 | var ref3; 491 | if ((ref3 = x.parameter) != null) { 492 | ref3.push(param); 493 | } 494 | if (x.parameter == null) { 495 | x.parameter = [param]; 496 | } 497 | x.name = `${name}/{${param.name}}` + x.name; 498 | return typeof debug === "function" ? debug(`[discoverPaths] subpath ${x.name} has parameters: ${x.parameter.map(function(p) { 499 | return p.name; 500 | })}`) : void 0; 501 | }); 502 | paths.push({ 503 | name: `${name}/{${param.name}}`, 504 | parameter: [param], 505 | operation: discoverOperations(schema, true) 506 | }); 507 | break; 508 | case 'container': 509 | subpaths.forEach(function(x) { 510 | return x.name = name + x.name; 511 | }); 512 | } 513 | if (typeof debug === "function") { 514 | debug(`[discoverPaths] discovered ${paths.length} paths with ${subpaths.length} subpaths inside ${schema.uri}`); 515 | } 516 | return paths.concat(...subpaths); 517 | }; 518 | 519 | discoverTags = function(schema) { 520 | var name, ref1, ref2, ref3, ref4, tag; 521 | if ((ref1 = schema.kind) !== 'list' && ref1 !== 'container' && ref1 !== 'rpc' && ref1 !== 'action') { 522 | return []; 523 | } 524 | if (schema['if-feature'] != null) { 525 | return []; 526 | } 527 | name = `/${schema.datakey}`; 528 | if (typeof debug === "function") { 529 | debug(`[discoverTags] inspecting ${schema.uri}`); 530 | } 531 | tag = { 532 | name: name, 533 | description: (ref2 = (ref3 = schema.description) != null ? ref3.tag : void 0) != null ? ref2 : `Operations related to ${name}` 534 | }; 535 | if ((ref4 = schema.reference) != null ? ref4.tag : void 0) { 536 | tag.externalDocs = { 537 | description: `Find out more about ${name}`, 538 | url: schema.reference.tag 539 | }; 540 | } 541 | return tag; 542 | }; 543 | 544 | serializeJSchema = function(jschema) { 545 | var isProperty, k, o, ref1, ref2, v; 546 | if (jschema == null) { 547 | return; 548 | } 549 | isProperty = 'property' in jschema; 550 | o = {}; 551 | for (k in jschema) { 552 | v = jschema[k]; 553 | if (k !== 'property') { 554 | o[k] = v; 555 | } 556 | } 557 | o.properties = (ref1 = jschema.property) != null ? ref1.reduce((function(a, _prop) { 558 | a[_prop.name] = serializeJSchema(_prop.schema); 559 | return a; 560 | }), {}) : void 0; 561 | o.items = serializeJSchema(o.items); 562 | o.allOf = (ref2 = o.allOf) != null ? ref2.map(function(x) { 563 | return serializeJSchema(x); 564 | }) : void 0; 565 | switch (false) { 566 | // Swagger 2.0 does NOT support anyOf or oneOf 567 | //o.anyOf = o.anyOf?.map (x) -> serializeJSchema x 568 | //o.oneOf = o.oneOf?.map (x) -> serializeJSchema x 569 | case !((o.anyOf != null) && isProperty): 570 | if (o.type == null) { 571 | o.type = 'object'; 572 | } 573 | if (o.properties == null) { 574 | o.properties = {}; 575 | } 576 | o.anyOf.forEach(function(x) { 577 | var ref3; 578 | return (ref3 = x.property) != null ? ref3.forEach(function(prop) { 579 | return o.properties[prop.name] = serializeJSchema(prop.schema); 580 | }) : void 0; 581 | }); 582 | delete o.anyOf; 583 | break; 584 | case o.anyOf == null: 585 | if (o.type == null) { 586 | o.type = 'string'; 587 | } 588 | if (o.format == null) { 589 | o.format = 'union'; 590 | } 591 | delete o.anyOf; 592 | } 593 | return o; 594 | }; 595 | 596 | module.exports = require('./yang-openapi.yang').bind({ 597 | transform: function(ctx, input) { 598 | var found, k, modules, output, v; 599 | if (typeof debug === "function") { 600 | debug(`[transform] using '${input['@choice']}' as source`); 601 | } 602 | switch (input['@choice']) { 603 | case 'swagger-file': 604 | throw ctx.error("swagger-file transform feature not yet supported!"); 605 | } 606 | if (typeof debug === "function") { 607 | debug(`[transform] importing '${input.modules}'`); 608 | } 609 | modules = input.modules.map((name) => { 610 | return ctx.schema.constructor.import(name); 611 | }).filter(function(x) { 612 | return x != null; 613 | }); 614 | if (!modules.length) { 615 | throw ctx.error("unable to transform without any modules"); 616 | } 617 | found = modules.map(function(x) { 618 | return x.datakey; 619 | }); 620 | if (typeof debug === "function") { 621 | debug(`[transform] transforming ${found}`); 622 | } 623 | definitions = {}; // XXX - usage of globals is a hack (will have concurrency issues) 624 | output = { 625 | spec: { 626 | swagger: '2.0', 627 | info: ctx.get('/info'), 628 | consumes: ["application/json"], 629 | produces: ["application/json"], 630 | tag: modules.map(function(m) { 631 | var ref1, tag; 632 | tag = { 633 | name: m.tag, 634 | description: `Operations related to ${m.tag}` 635 | }; 636 | if ((ref1 = m.reference) != null ? ref1.tag : void 0) { 637 | tag.externalDocs = { 638 | description: `Find out more about ${m.tag}`, 639 | url: m.reference.tag 640 | }; 641 | } 642 | return tag; 643 | }), 644 | path: modules.map(function(m) { 645 | var i, len, ref1, results, schema; 646 | ref1 = m.nodes; 647 | results = []; 648 | for (i = 0, len = ref1.length; i < len; i++) { 649 | schema = ref1[i]; 650 | results.push(discoverPaths(schema)); 651 | } 652 | return results; 653 | }).reduce((function(a, b) { 654 | return a.concat(...b); 655 | }), []), 656 | definition: (function() { 657 | var results; 658 | results = []; 659 | for (k in definitions) { 660 | v = definitions[k]; 661 | results.push({ 662 | name: k, 663 | schema: v 664 | }); 665 | } 666 | return results; 667 | })() 668 | } 669 | }; 670 | return output; 671 | }, 672 | 'grouping(specification)/serialize': function(ctx, input) { 673 | var ref1, spec; 674 | if (typeof debug === "function") { 675 | debug(`[${ctx.path}] serializing specification`); 676 | } 677 | spec = ctx.toJSON(false); 678 | spec.tags = spec.tag; 679 | spec.paths = spec.path.reduce((function(a, _path) { 680 | var i, k, len, op, operation, path, ref1, ref2, v; 681 | path = a[_path.name] = { 682 | '$ref': _path['$ref'] 683 | }; 684 | ref2 = (ref1 = _path.operation) != null ? ref1 : []; 685 | for (i = 0, len = ref2.length; i < len; i++) { 686 | op = ref2[i]; 687 | operation = path[op.method] = {}; 688 | for (k in op) { 689 | v = op[k]; 690 | if (k !== 'method' && k !== 'parameter' && k !== 'response') { 691 | operation[k] = v; 692 | } 693 | } 694 | operation.parameters = traverse(op.parameter).map(function(x) { 695 | if (this.key === 'schema') { 696 | return this.update(serializeJSchema(x), true); 697 | } 698 | }); 699 | operation.responses = op.response.reduce((function(x, _res) { 700 | x[_res.code] = { 701 | description: _res.description, 702 | schema: serializeJSchema(_res.schema) 703 | }; 704 | return x; 705 | }), {}); 706 | } 707 | path.parameters = _path.parameter; 708 | return a; 709 | }), {}); 710 | spec.definitions = (ref1 = spec.definition) != null ? ref1.reduce((function(a, _def) { 711 | a[_def.name] = serializeJSchema(_def.schema); 712 | return a; 713 | }), {}) : void 0; 714 | delete spec.tag; 715 | delete spec.path; 716 | delete spec.definition; 717 | delete spec.serialize; 718 | spec = traverse(spec).map(function(x) { 719 | if (x == null) { 720 | return this.remove(); 721 | } 722 | }); 723 | return { 724 | data: (function() { 725 | switch (input.format) { 726 | case 'json': 727 | return JSON.stringify(spec, null, 2); 728 | case 'yaml': 729 | return yaml.dump(spec, { 730 | lineWidth: -1 731 | }); 732 | } 733 | })() 734 | }; 735 | } 736 | }); 737 | 738 | }).call(this); 739 | -------------------------------------------------------------------------------- /yang-openapi.yang: -------------------------------------------------------------------------------- 1 | module yang-openapi { 2 | namespace "urn:ietf:params:xml:ns:yang:yang-openapi"; 3 | prefix openapi; 4 | yang-version 1.1; 5 | 6 | import ietf-inet-types { prefix inet; } 7 | import ietf-yang-types { prefix yang; } 8 | import yang-json-schema { prefix json; } 9 | import yang-meta-types { prefix meta; } 10 | 11 | organization 12 | "Corenova Technologies, Inc."; 13 | contact 14 | "Peter K. Lee "; 15 | reference 16 | "https://openapis.org"; 17 | 18 | /* 19 | * Identities 20 | */ 21 | identity protocol { 22 | description "describes transfer protocol"; 23 | } 24 | identity http { 25 | base protocol; 26 | } 27 | identity https { 28 | base protocol; 29 | } 30 | identity ws { 31 | base protocol; 32 | } 33 | identity wss { 34 | base protocol; 35 | } 36 | /* 37 | * Type definitions 38 | */ 39 | typedef mimetype { 40 | type string { 41 | pattern '^.+\/.+$'; 42 | } 43 | } 44 | typedef http-status-code { 45 | type uint16 { 46 | range '100..102 | 200..208 | 226 | 300..308 | 400..431 | 500..511'; 47 | } 48 | } 49 | typedef http-method { 50 | type enumeration { 51 | enum get; 52 | enum put; 53 | enum post; 54 | enum delete; 55 | enum options; 56 | enum head; 57 | enum patch; 58 | } 59 | } 60 | typedef grouping-identifier { 61 | type string { 62 | length "1..max"; 63 | pattern '[\w\-]*:?[a-zA-Z_][a-zA-Z0-9\-_.]*'; 64 | pattern '[\w\-]*:?.|..|[^xX].*|.[^mM].*|..[^lL].*'; 65 | } 66 | } 67 | /* 68 | * Groupings 69 | */ 70 | grouping info { 71 | description 72 | "Contains metadata information to describe API."; 73 | leaf title { type meta:title; mandatory true; } 74 | leaf description { type meta:description; } 75 | leaf version { type meta:api-version; mandatory true; } 76 | container contact { 77 | leaf name { type meta:person-name; } 78 | leaf url { type inet:uri; } 79 | leaf email { type meta:email-address; } 80 | } 81 | container license { 82 | leaf name { type meta:license; mandatory true; } 83 | leaf url { type inet:uri; } 84 | } 85 | } 86 | grouping parameters-list { 87 | list parameter { 88 | key "name"; 89 | leaf name { 90 | type string; 91 | mandatory true; 92 | } 93 | leaf in { 94 | type enumeration { 95 | enum query; 96 | enum header; 97 | enum path; 98 | enum formData; 99 | enum body; 100 | } 101 | mandatory true; 102 | } 103 | leaf description { 104 | type meta:description; 105 | } 106 | leaf required { 107 | type boolean; 108 | default false; 109 | description "if ../in = 'path' this must be true"; 110 | } 111 | uses json:json-datatype; 112 | container schema { 113 | when "../in = 'body'"; 114 | uses json:json-schema; 115 | } 116 | /* TODO 117 | * allow-empty-value 118 | * items 119 | * collection-format 120 | * default 121 | * maximum 122 | * exclusive-maximum 123 | * minimum 124 | * exclusive-minimum 125 | * max-length 126 | * min-length 127 | * pattern 128 | * max-items 129 | * min-items 130 | * unique-items 131 | * enum 132 | * multiple-of 133 | */ 134 | } 135 | } 136 | grouping response { 137 | leaf description { 138 | type meta:description; 139 | mandatory true; 140 | } 141 | container schema { 142 | uses json:json-schema; 143 | } 144 | list header { 145 | 146 | } 147 | list example { 148 | key mime; 149 | leaf mime { 150 | type mimetype; 151 | mandatory true; 152 | } 153 | container data; 154 | } 155 | } 156 | grouping responses-list { 157 | list response { 158 | key code; 159 | leaf code { 160 | type http-status-code; 161 | } 162 | uses openapi:response; 163 | } 164 | } 165 | grouping operations-list { 166 | grouping operation { 167 | leaf-list tags { 168 | type yang:yang-identifier; 169 | } 170 | leaf summary { 171 | type meta:description { 172 | length 0..120; 173 | } 174 | } 175 | leaf description { 176 | type meta:description; 177 | } 178 | leaf operationId { 179 | type string; 180 | } 181 | leaf-list consumes { 182 | type mimetype; 183 | } 184 | leaf-list produces { 185 | type mimetype; 186 | } 187 | leaf-list schemes { 188 | type identityref { 189 | base protocol; 190 | } 191 | } 192 | leaf deprecated { 193 | type boolean; 194 | default false; 195 | } 196 | uses openapi:parameters-list; 197 | uses openapi:responses-list; 198 | list security { 199 | key name; 200 | leaf name; 201 | // TODO 202 | } 203 | } 204 | list operation { 205 | //node:hash ".."; 206 | key method; 207 | leaf method { 208 | type http-method; 209 | } 210 | uses operation; 211 | } 212 | } 213 | grouping paths-list { 214 | grouping path-item { 215 | leaf $ref { 216 | type json:json-schema-ref; 217 | } 218 | uses openapi:operations-list; 219 | uses openapi:parameters-list; 220 | } 221 | list path { 222 | key name; 223 | leaf name { 224 | type inet:uri; 225 | } 226 | uses path-item; 227 | min-elements 1; 228 | } 229 | } 230 | grouping definitions-list { 231 | list definition { 232 | key name; 233 | leaf name { 234 | type openapi:grouping-identifier; 235 | } 236 | container schema { 237 | uses json:json-schema; 238 | } 239 | } 240 | } 241 | grouping responses-def-list { 242 | list response { 243 | key name; 244 | leaf name { 245 | type yang:yang-identifier; 246 | } 247 | uses openapi:response; 248 | } 249 | } 250 | grouping tags-list { 251 | list tag { 252 | key name; 253 | leaf name { 254 | type yang:yang-identifier; 255 | } 256 | leaf description { 257 | type meta:description; 258 | } 259 | container externalDocs { 260 | leaf description { 261 | type meta:description; 262 | } 263 | leaf url { 264 | type inet:uri; 265 | } 266 | } 267 | } 268 | } 269 | grouping specification { 270 | leaf swagger { 271 | type meta:api-version; 272 | mandatory true; 273 | must "current() = '2.0'"; 274 | } 275 | container info { 276 | uses openapi:info; 277 | } 278 | leaf host { 279 | type string; 280 | } 281 | leaf base-path { 282 | type inet:uri; 283 | } 284 | leaf-list schemes { 285 | type identityref { 286 | base protocol; 287 | } 288 | } 289 | leaf-list consumes { 290 | type mimetype; 291 | min-elements 1; 292 | } 293 | leaf-list produces { 294 | type mimetype; 295 | min-elements 1; 296 | } 297 | uses openapi:paths-list; 298 | uses openapi:definitions-list; 299 | uses openapi:responses-def-list; 300 | uses openapi:tags-list; 301 | /* TODO 302 | * parameters 303 | * security-definitions 304 | * security 305 | * externalDocs 306 | */ 307 | 308 | action serialize { 309 | description 310 | "The serialize converts current Swagger/OpenAPI specification object 311 | into string format based on requested output format."; 312 | input { 313 | leaf format { 314 | type enumeration { 315 | enum json; 316 | enum yaml; 317 | } 318 | default json; 319 | } 320 | } 321 | output { 322 | leaf data { 323 | description "contains the serialized specification data"; 324 | type string; 325 | } 326 | } 327 | } 328 | } 329 | /* 330 | * Configuration data nodes 331 | */ 332 | container info { 333 | description 334 | "Contains API description metadata for use during 'transform' output"; 335 | uses openapi:info; 336 | } 337 | 338 | /* 339 | * Remote procedures 340 | */ 341 | rpc transform { 342 | description 343 | "The transform accepts YANG module names/locations or pre-existing 344 | Swagger specification file contents and converts into YANG 345 | schema-defined Swagger/OpenAPI specification object instance."; 346 | input { 347 | choice source { 348 | case yang-modules { 349 | leaf-list modules { 350 | type yang:yang-identifier; 351 | min-elements 1; 352 | } 353 | } 354 | case swagger-file { 355 | leaf file { 356 | type meta:file-name; 357 | mandatory true; 358 | } 359 | leaf format { 360 | type enumeration { 361 | enum json; 362 | enum yaml; 363 | } 364 | mandatory true; 365 | } 366 | status planned; 367 | } 368 | } 369 | // TODO 370 | // openapi:example 'application/json' { 371 | // value 372 | // '{ 373 | // "modules": [ "yang-openapi" ] 374 | // }'; 375 | // } 376 | } 377 | output { 378 | container spec { 379 | uses openapi:specification; 380 | } 381 | } 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | argparse@^1.0.7: 6 | version "1.0.10" 7 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 8 | integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== 9 | dependencies: 10 | sprintf-js "~1.0.2" 11 | 12 | balanced-match@^1.0.0: 13 | version "1.0.0" 14 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 15 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 16 | 17 | brace-expansion@^1.1.7: 18 | version "1.1.11" 19 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 20 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 21 | dependencies: 22 | balanced-match "^1.0.0" 23 | concat-map "0.0.1" 24 | 25 | browser-stdout@1.3.1: 26 | version "1.3.1" 27 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" 28 | integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== 29 | 30 | coffeescript@2: 31 | version "2.5.1" 32 | resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-2.5.1.tgz#b2442a1f2c806139669534a54adc35010559d16a" 33 | integrity sha512-J2jRPX0eeFh5VKyVnoLrfVFgLZtnnmp96WQSLAS8OrLm2wtQLcnikYKe1gViJKDH7vucjuhHvBKKBP3rKcD1tQ== 34 | 35 | commander@2.15.1: 36 | version "2.15.1" 37 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" 38 | integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== 39 | 40 | "comparse@>= 0.9.x", comparse@^0.9.3: 41 | version "0.9.3" 42 | resolved "https://registry.yarnpkg.com/comparse/-/comparse-0.9.3.tgz#2ce73a4895e0ee1b333db11689c2f55c0ff9a962" 43 | integrity sha1-LOc6SJXg7hszPbEWicL1XA/5qWI= 44 | 45 | concat-map@0.0.1: 46 | version "0.0.1" 47 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 48 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 49 | 50 | config@^3.0.1: 51 | version "3.3.1" 52 | resolved "https://registry.yarnpkg.com/config/-/config-3.3.1.tgz#b6a70e2908a43b98ed20be7e367edf0cc8ed5a19" 53 | integrity sha512-+2/KaaaAzdwUBE3jgZON11L1ggLLhpf2FsGrfqYFHZW22ySGv/HqYIXrBwKKvn+XZh1UBUjHwAcrfsSkSygT+Q== 54 | dependencies: 55 | json5 "^2.1.1" 56 | 57 | debug@3.1.0: 58 | version "3.1.0" 59 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 60 | integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== 61 | dependencies: 62 | ms "2.0.0" 63 | 64 | debug@^4.1.1: 65 | version "4.1.1" 66 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" 67 | integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== 68 | dependencies: 69 | ms "^2.1.1" 70 | 71 | delegates@^1.0.0: 72 | version "1.0.0" 73 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" 74 | integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= 75 | 76 | diff@3.5.0: 77 | version "3.5.0" 78 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" 79 | integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== 80 | 81 | escape-string-regexp@1.0.5: 82 | version "1.0.5" 83 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 84 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 85 | 86 | esprima@^4.0.0: 87 | version "4.0.1" 88 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 89 | integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 90 | 91 | fs.realpath@^1.0.0: 92 | version "1.0.0" 93 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 94 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 95 | 96 | glob@7.1.2: 97 | version "7.1.2" 98 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 99 | integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== 100 | dependencies: 101 | fs.realpath "^1.0.0" 102 | inflight "^1.0.4" 103 | inherits "2" 104 | minimatch "^3.0.4" 105 | once "^1.3.0" 106 | path-is-absolute "^1.0.0" 107 | 108 | glob@^7.1.3: 109 | version "7.1.6" 110 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 111 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 112 | dependencies: 113 | fs.realpath "^1.0.0" 114 | inflight "^1.0.4" 115 | inherits "2" 116 | minimatch "^3.0.4" 117 | once "^1.3.0" 118 | path-is-absolute "^1.0.0" 119 | 120 | growl@1.10.5: 121 | version "1.10.5" 122 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" 123 | integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== 124 | 125 | has-flag@^3.0.0: 126 | version "3.0.0" 127 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 128 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 129 | 130 | he@1.1.1: 131 | version "1.1.1" 132 | resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" 133 | integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= 134 | 135 | indent-string@^2.1.0: 136 | version "2.1.0" 137 | resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" 138 | integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= 139 | dependencies: 140 | repeating "^2.0.0" 141 | 142 | inflight@^1.0.4: 143 | version "1.0.6" 144 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 145 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 146 | dependencies: 147 | once "^1.3.0" 148 | wrappy "1" 149 | 150 | inherits@2: 151 | version "2.0.4" 152 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 153 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 154 | 155 | is-finite@^1.0.0: 156 | version "1.1.0" 157 | resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" 158 | integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== 159 | 160 | js-yaml@^3.13.1: 161 | version "3.14.0" 162 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" 163 | integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== 164 | dependencies: 165 | argparse "^1.0.7" 166 | esprima "^4.0.0" 167 | 168 | json5@^2.1.1: 169 | version "2.1.3" 170 | resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" 171 | integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== 172 | dependencies: 173 | minimist "^1.2.5" 174 | 175 | minimatch@3.0.4, minimatch@^3.0.4: 176 | version "3.0.4" 177 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 178 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 179 | dependencies: 180 | brace-expansion "^1.1.7" 181 | 182 | minimist@0.0.8: 183 | version "0.0.8" 184 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 185 | integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= 186 | 187 | minimist@^1.2.0, minimist@^1.2.5: 188 | version "1.2.5" 189 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 190 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 191 | 192 | mkdirp@0.5.1: 193 | version "0.5.1" 194 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 195 | integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= 196 | dependencies: 197 | minimist "0.0.8" 198 | 199 | mocha@^5.2.0: 200 | version "5.2.0" 201 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" 202 | integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== 203 | dependencies: 204 | browser-stdout "1.3.1" 205 | commander "2.15.1" 206 | debug "3.1.0" 207 | diff "3.5.0" 208 | escape-string-regexp "1.0.5" 209 | glob "7.1.2" 210 | growl "1.10.5" 211 | he "1.1.1" 212 | minimatch "3.0.4" 213 | mkdirp "0.5.1" 214 | supports-color "5.4.0" 215 | 216 | ms@2.0.0: 217 | version "2.0.0" 218 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 219 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 220 | 221 | ms@^2.1.1: 222 | version "2.1.2" 223 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 224 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 225 | 226 | once@^1.3.0: 227 | version "1.4.0" 228 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 229 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 230 | dependencies: 231 | wrappy "1" 232 | 233 | path-is-absolute@^1.0.0: 234 | version "1.0.1" 235 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 236 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 237 | 238 | repeating@^2.0.0: 239 | version "2.0.1" 240 | resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" 241 | integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= 242 | dependencies: 243 | is-finite "^1.0.0" 244 | 245 | rimraf@^2.5.2: 246 | version "2.7.1" 247 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" 248 | integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== 249 | dependencies: 250 | glob "^7.1.3" 251 | 252 | should@~3.1.3: 253 | version "3.1.4" 254 | resolved "https://registry.yarnpkg.com/should/-/should-3.1.4.tgz#ac280c6e5fc9d35c77d68b95ef1bc60bd554a731" 255 | integrity sha1-rCgMbl/J01x31ouV7xvGC9VUpzE= 256 | 257 | sprintf-js@~1.0.2: 258 | version "1.0.3" 259 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 260 | integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= 261 | 262 | stacktrace-parser@^0.1.4: 263 | version "0.1.10" 264 | resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a" 265 | integrity sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg== 266 | dependencies: 267 | type-fest "^0.7.1" 268 | 269 | supports-color@5.4.0: 270 | version "5.4.0" 271 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" 272 | integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== 273 | dependencies: 274 | has-flag "^3.0.0" 275 | 276 | traverse@^0.6.6: 277 | version "0.6.6" 278 | resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" 279 | integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc= 280 | 281 | type-fest@^0.7.1: 282 | version "0.7.1" 283 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" 284 | integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== 285 | 286 | wrappy@1: 287 | version "1.0.2" 288 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 289 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 290 | 291 | xparse@^1.0.0: 292 | version "1.0.0" 293 | resolved "https://registry.yarnpkg.com/xparse/-/xparse-1.0.0.tgz#4775a4bae747c8263ce51af9cef95ee169ac2efd" 294 | integrity sha1-R3WkuudHyCY85Rr5zvle4WmsLv0= 295 | dependencies: 296 | comparse "^0.9.3" 297 | 298 | yang-js@^0.24.63: 299 | version "0.24.63" 300 | resolved "https://registry.yarnpkg.com/yang-js/-/yang-js-0.24.63.tgz#9f600b7c9e802d0ab58eada215b7ed1c9b471787" 301 | integrity sha512-+OPZb5psT6F/QEJfwgONYEziCRUvXzkhx21r6RA/rdQ2aAx/urqsgbaX32PkOlISRAB+hyyUVzRZnNnN/703tA== 302 | dependencies: 303 | debug "^4.1.1" 304 | delegates "^1.0.0" 305 | indent-string "^2.1.0" 306 | stacktrace-parser "^0.1.4" 307 | xparse "^1.0.0" 308 | yang-parser "^0.2.1" 309 | 310 | yang-parser@^0.2.1: 311 | version "0.2.1" 312 | resolved "https://registry.yarnpkg.com/yang-parser/-/yang-parser-0.2.1.tgz#b937ca78d887b81559c71c608fe9d850ebb7a77a" 313 | integrity sha1-uTfKeNiHuBVZxxxgj+nYUOu3p3o= 314 | dependencies: 315 | comparse ">= 0.9.x" 316 | --------------------------------------------------------------------------------