├── .gitignore ├── LICENSE ├── README.md ├── RapierThumb.jpg ├── __init__.py ├── data_model.md ├── js ├── base_api.js ├── package.json └── test │ ├── helloMessage │ ├── helloMessageApi.js │ ├── run_tests.sh │ └── test.js │ ├── run_tests.sh │ └── todoList │ ├── package.json │ ├── run_tests.sh │ ├── test.js │ └── todoListAPI.js ├── publish.sh ├── py ├── __init__.py ├── base_api.py └── test │ ├── __init__.py │ ├── hello_message │ ├── __init__.py │ ├── hello_message_api.py │ ├── run_tests.sh │ └── test.py │ ├── run_tests.sh │ └── todo_list │ ├── __init__.py │ ├── run_tests.sh │ ├── test.py │ └── todo_list_api.py ├── setup.py ├── test-servers ├── hello_message.js ├── package.json └── todo_list.js └── util ├── __init__.py ├── gen_html.py ├── gen_js_sdk.py ├── gen_openapispec.py ├── gen_py_sdk.py ├── rapier.py ├── requirements.txt ├── test ├── common.yaml ├── dog-tracker.yaml ├── gen_html │ ├── common.html │ ├── dog-tracker.html │ ├── hello-message.html │ ├── links-todo-list.html │ ├── petstore.html │ ├── property-tracker.html │ ├── run-common-test.sh │ ├── run-tests.sh │ ├── site-webmaster.html │ ├── spec-hub-with-impl.html │ ├── spec-hub.html │ ├── ssl.html │ ├── todo-list-basic.html │ ├── todo-list-with-id.html │ └── todo-list-with-self.html ├── gen_js_sdk │ ├── dog-tracker.js │ ├── hello-message.js │ ├── property-tracker.js │ ├── run-tests.sh │ └── todo-list.js ├── gen_openapispec │ ├── dog-tracker.yaml │ ├── hello-message.yaml │ ├── links-todo-list.yaml │ ├── petstore.yaml │ ├── property-tracker.yaml │ ├── run-dog-test.sh │ ├── run-hello-test.sh │ ├── run-links-todo-test.sh │ ├── run-petstore-test.sh │ ├── run-property-test.sh │ ├── run-site-webmaster-test.sh │ ├── run-spechub-test.sh │ ├── run-ssl-test.sh │ ├── run-tests.sh │ ├── run-todo-basic-test.sh │ ├── run-todo-with-id-test.sh │ ├── run-todo-with-links-test.sh │ ├── run-todo-with-self-test.sh │ ├── run-use-common-test.sh │ ├── site-webmaster.yaml │ ├── spec-hub-with-impl.yaml │ ├── spec-hub.yaml │ ├── ssl.yaml │ ├── todo-list-basic.yaml │ ├── todo-list-with-id.yaml │ ├── todo-list-with-self.yaml │ └── use-common.yaml ├── gen_py_sdk │ ├── dog-tracker.py │ ├── hello-message.py │ ├── property-tracker.py │ ├── run-tests.sh │ └── todo-list.py ├── hello-message.yaml ├── petstore.yaml ├── property-tracker.yaml ├── site-webmaster.yaml ├── spec-hub.yaml ├── ssl.yaml ├── todo-list-basic.yaml ├── todo-list-with-id.yaml ├── todo-list-with-links.yaml ├── todo-list-with-self.yaml ├── use-common.yaml └── validate_rapier │ ├── hello-message-errors.yaml │ ├── hello-message.yaml │ ├── run-deployment-validation-test.sh │ └── run-validation-tests.sh └── validate_rapier.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | # Logs 61 | logs 62 | *.log 63 | npm-debug.log* 64 | 65 | # Runtime data 66 | pids 67 | *.pid 68 | *.seed 69 | 70 | # Directory for instrumented libs generated by jscoverage/JSCover 71 | lib-cov 72 | 73 | # Coverage directory used by tools like istanbul 74 | coverage 75 | 76 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 77 | .grunt 78 | 79 | # node-waf configuration 80 | .lock-wscript 81 | 82 | # Compiled binary addons (http://nodejs.org/api/addons.html) 83 | build/Release 84 | 85 | # Dependency directory 86 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 87 | node_modules 88 | 89 | #Visual Studio Code settings 90 | .settings 91 | .vscode/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Apigee Corporation 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /RapierThumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apigee-labs/rapier/7ef9ce6ef5112477a45c777aa4882932610ce1fe/RapierThumb.jpg -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apigee-labs/rapier/7ef9ce6ef5112477a45c777aa4882932610ce1fe/__init__.py -------------------------------------------------------------------------------- /data_model.md: -------------------------------------------------------------------------------- 1 | If you are just using Rapier, you can easily learn it by following the tutorials. If you are trying to design a modification to Rapier or an extensio to Rapier, 2 | it may be useful to understand Rapier's data model and its approach to defining URLs. Consider the following Rapier spec: 3 | ```yaml 4 | entities: 5 | Mother: {} 6 | Child: 7 | properties: 8 | mother: 9 | type: string 10 | format: uri 11 | relationship: '#Mother' 12 | ``` 13 | In Rapier, this is a shorthand syntax for this: 14 | ```yaml 15 | entities: 16 | Mother: 17 | id: '#Mother' 18 | Child: 19 | properties: 20 | mother: 21 | type: string 22 | format: uri 23 | relationship: '#Mother' 24 | ``` 25 | The URI of the Mother entity is `#Mother`. This is why the line `relationship: '#Mother'` in the Child schema is valid - `#Mother` is a valid URI reference. 26 | 27 | In Rapier, the following two URIs reference different things 28 | - `#Mother` 29 | - `#/entities/Mother` 30 | 31 | The first URI reference identifies an Entity, while the second identifies a JSON object. The JSON object is not the Entity—the JSON Object describes the Entity. 32 | Because of this, `relationship: '#/entities/Child'` would be incorrect. 33 | 34 | This distinction becomes important in the following example: 35 | 36 | ```yaml 37 | entities: 38 | Mother: {} 39 | Child: 40 | properties: 41 | mother: 42 | type: string 43 | format: uri 44 | relationship: '#Mother' 45 | implementationPrivateInformation: 46 | Child: 47 | permalinkTemplate: 48 | template: /c3Rvc-Z3Jw-{implementation_key} 49 | type: integer 50 | ``` 51 | 52 | `#/entities/Child` and `#/implementationPrivateInformation/Child` are two different JSON objects, but they both describe the same entity, whose URI reference is `#Child`. 53 | This means that `#/implementationPrivateInformation/Child` is providing additional information about the same entity that was described by `#/entities/Child`. 54 | The API could also have been described as follows, although Rapier does not currently allow this syntax (maybe it should): 55 | 56 | ```yaml 57 | - isA: 'https://github.com/apigee-labs/rapier/ns#Entity' 58 | id: '#Mother' 59 | - isA: 'https://github.com/apigee-labs/rapier/ns#Entity' 60 | id: '#Child' 61 | properties: 62 | - name: mother 63 | type: string 64 | format: uri 65 | relationship: '#Mother' 66 | permalinkTemplate: 67 | template: /c3Rvc-Z3Jw-{implementation_key} 68 | type: integer 69 | ``` 70 | 71 | Those of you who are familiar with RDF or with some of the more thoughtful discussions of URLs and their meanings will find nothing original or suprising in this model. -------------------------------------------------------------------------------- /js/base_api.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('request') 4 | 5 | var base_api = function() { 6 | function BaseAPI() {} 7 | 8 | BaseAPI.prototype.retrieveHeaders = function() { 9 | return { 10 | 'Accept': 'application/json' 11 | } 12 | } 13 | 14 | BaseAPI.prototype.updateHeaders = function(etag) { 15 | return { 16 | 'Accept': 'application/json', 17 | 'Content-Type': 'application/json', 18 | 'If-Match': etag 19 | } 20 | } 21 | 22 | BaseAPI.prototype.deleteHeaders = function() { 23 | return { 24 | 'Accept': 'application/json' 25 | } 26 | } 27 | 28 | BaseAPI.prototype.createHeaders = function() { 29 | return { 30 | 'Accept': 'application/json', 31 | 'Content-Type': 'application/json' 32 | } 33 | } 34 | 35 | BaseAPI.prototype.retrieve = function(url, callback, entity, headers) { 36 | // issue a GET to retrieve a resource from the API and create an object for it 37 | var self = this; 38 | request({ 39 | url: url, 40 | headers: headers || self.retrieveHeaders() 41 | }, 42 | function (error, response, body) { 43 | self.processResourceResult(error, response, body, url, callback, entity) 44 | }) 45 | } 46 | 47 | BaseAPI.prototype.update = function(url, etag, changes, callback, entity, headers) { 48 | var self = this; 49 | request.patch({ 50 | url: url, 51 | headers: headers || self.updateHeaders(), 52 | body: JSON.stringify(changes) 53 | }, 54 | function (error, response, body) { 55 | self.processResourceResult(error, response, body, url, callback, entity) 56 | }) 57 | } 58 | 59 | BaseAPI.prototype.delete = function(url, callback, entity, headers) { 60 | var self = this; 61 | request.del({ 62 | url: url, 63 | headers: headers || self.deleteHeaders() 64 | }, 65 | function (error, response, body) { 66 | self.processResourceResult(error, response, body, url, callback, entity) 67 | }) 68 | } 69 | 70 | BaseAPI.prototype.create = function(url, body, callback, entity, headers) { 71 | var self = this; 72 | request.post({ 73 | url: url, 74 | headers: headers || self.createHeaders(), 75 | body: JSON.stringify(body) 76 | }, 77 | function (error, response, body) { 78 | self.processResourceResult(error, response, body, url, callback, entity, 'location') 79 | }); 80 | } 81 | 82 | BaseAPI.prototype.processResourceResult = function(error, response, body, url, callback, entity, location_header) { 83 | location_header = location_header ? location_header : 'content-location'; 84 | if (!error) { 85 | if (response.statusCode == 200 || response.statusCode == 201) { 86 | if (location_header in response.headers) { 87 | var location = response.headers[location_header]; 88 | if ('etag' in response.headers) { 89 | var etag = response.headers['etag']; 90 | if ('content-type' in response.headers) { 91 | var content_type = response.headers['content-type'].split(';')[0] 92 | if (content_type == 'application/json') { 93 | var jso = JSON.parse(body); 94 | try { 95 | callback(null, this.buildResourceFromJson(jso, location, etag, entity)) 96 | } catch(err) { 97 | callback(err) 98 | } 99 | } else { 100 | callback({args: ['non-json content_type ' + response.headers['content-type']]}) 101 | } 102 | } else { 103 | callback({args: ['server did not declare content_type']}) 104 | } 105 | } else { 106 | callback({args: ['server did not provide etag']}) 107 | } 108 | } else { 109 | callback({args: ['server failed to provide ' + location_header + ' header for url ' + url + 'headers ' + JSON.stringify(response.headers)]}) 110 | } 111 | } else { 112 | callback({args: ['unexpected HTTP statusCode code: ' + response.statusCode + ' url: ' + url + ' text: ' + response.body]}) 113 | } 114 | } else { 115 | callback({args: ['http error' + error]}) 116 | } 117 | } 118 | 119 | BaseAPI.prototype.buildResourceFromJson = function(jso, url, etag, entity) { 120 | if ('kind' in jso) { 121 | var kind = jso.kind; 122 | if (entity) { 123 | if (!('kind' in entity) || entity.kind == kind) { 124 | entity.updateProperties(url, jso, etag); 125 | return entity 126 | } else { 127 | throw {args: ['SDK cannot handle change of kind from' + entity.kind + ' to ' + kind]} 128 | } 129 | } else { 130 | var resourceClass = this.resourceClass(kind); 131 | if (resourceClass) { 132 | return new resourceClass(url, jso, etag) 133 | } else { 134 | throw {args: ['no resourceClass for kind ' + kind]} 135 | } 136 | } 137 | } else { 138 | if (!!entity && entity.kind) { 139 | entity.updateProperties(url, jso, etag); 140 | return entity 141 | } else { 142 | throw {args: ['no kind property in json ' + jso]} 143 | } 144 | } 145 | } 146 | 147 | BaseAPI.prototype._className = 'BaseAPI' 148 | 149 | function BaseResource(url, jso, etag) { 150 | this.kind = Object.getPrototypeOf(this)._className 151 | this.updateProperties(url, jso, etag) 152 | } 153 | 154 | BaseResource.prototype.updateProperties = function(url, jso, etag) { 155 | if (jso) { 156 | for (var key in jso) { 157 | this[key] = jso[key] 158 | } 159 | if ('_location' in jso) { 160 | this._location = jso._location 161 | } else if ('_self' in jso) { 162 | this._location = jso._self 163 | } 164 | this._jso = jso 165 | } 166 | if (url) { 167 | this._location = url 168 | } 169 | if (etag) { 170 | this._etag = etag 171 | } 172 | } 173 | 174 | BaseResource.prototype.refresh = function(callback) { 175 | if (!this._location) { 176 | callback({args: ['no _location property' + JSON.stringify(this)]}) 177 | } 178 | this.api().retrieve(this._location, callback, this) 179 | 180 | } 181 | 182 | BaseResource.prototype._className = 'BaseResource' 183 | 184 | function BaseEntity(url, jso, etag) { 185 | this._related = {} 186 | this.kind = Object.getPrototypeOf(this)._className 187 | BaseResource.call(this, url, jso, etag) 188 | } 189 | 190 | BaseEntity.prototype = Object.create(BaseResource.prototype); 191 | BaseEntity.prototype.constructor = BaseEntity; 192 | 193 | BaseEntity.prototype.getUpdateRepresentation = function() { 194 | var jso = '_jso' in this ? this._jso : {} 195 | var rslt = {} 196 | for (var key in this) { 197 | if (key.indexOf('_') !== 0 && (!(key in jso) || jso[key] != this[key])) { 198 | rslt[key] = this[key] 199 | } 200 | } 201 | return rslt 202 | } 203 | 204 | BaseEntity.prototype.update = function(callback) { 205 | // issue a PATCH or PUT to update this object from API 206 | var changes = this.getUpdateRepresentation() 207 | if (! ('_location' in this) || !this._location) { 208 | callback({args: ['this _location not set']}) 209 | } 210 | if (! ('_etag' in this) || !this._location) { 211 | callback({args: ['self _etag not set']}) 212 | } 213 | this.api().update(this._location, this._etag, changes, callback, this) 214 | } 215 | 216 | BaseEntity.prototype.delete = function(callback) { 217 | // issue a DELETE to remove this object from API 218 | if (!this._location) { 219 | callback({args: ['self location not set']}) 220 | } else { 221 | return this.api().delete(this._location, callback, this) 222 | } 223 | } 224 | 225 | BaseEntity.prototype.retrieve = function(relationship, callback) { 226 | // fetch a related resource 227 | var self = this; 228 | if (relationship in this) { 229 | var url = this[relationship]; 230 | this.api().retrieve(url, function(error, entity) { 231 | if (!error) { 232 | self._related[relationship] = entity; 233 | } 234 | callback(error, entity); 235 | }); 236 | } else { 237 | throw {args: ['no value set for property ' + relationship]} 238 | } 239 | } 240 | 241 | BaseEntity.prototype._className = 'BaseEntity' 242 | 243 | function BaseCollection(url, jso, etag) { 244 | BaseResource.call(this, url, jso, etag) 245 | } 246 | 247 | BaseCollection.prototype = Object.create(BaseResource.prototype); 248 | BaseCollection.prototype.constructor = BaseCollection; 249 | 250 | BaseCollection.prototype.updateProperties = function(url, jso, etag) { 251 | BaseResource.prototype.updateProperties.call(this, url, jso, etag) 252 | if (jso && 'items' in jso) { 253 | var items = jso['items']; 254 | this.items = {} 255 | for (var i = 0; i < items.length; i++) { 256 | var item = this.api().buildResourceFromJson(items[i]); 257 | this.items[item._location] = item 258 | } 259 | } 260 | } 261 | 262 | BaseCollection.prototype.create = function(entity, callback) { 263 | // create a new entity in the API by POSTing 264 | var self = this; 265 | if (this._location) { 266 | if ('_self' in entity && entity._self) { 267 | throw 'entity already exists in API: ' + entity 268 | } 269 | this.api().create(this._location, entity.getUpdateRepresentation(), function(error, entity) { 270 | if (!error && 'items' in self) { 271 | if (entity._self in self.items) { 272 | throw 'Duplicate id' 273 | } else { 274 | self.items[entity._self] = entity 275 | } 276 | } 277 | callback(error, entity) 278 | }, entity) 279 | } else { 280 | throw 'Collection has no _self property' 281 | } 282 | } 283 | 284 | BaseCollection.prototype._className = 'BaseCollection' 285 | 286 | return { 287 | BaseAPI: BaseAPI, 288 | BaseResource: BaseResource, 289 | BaseEntity: BaseEntity, 290 | BaseCollection: BaseCollection 291 | } 292 | } 293 | 294 | module.exports = base_api() -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js_sdk", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "base_api.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "request": "^2.61.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /js/test/helloMessage/helloMessageApi.js: -------------------------------------------------------------------------------- 1 | var baseAPI = require('./../../../js/base_api') 2 | 3 | var exports = function() { 4 | 5 | function API() {} 6 | 7 | API.prototype = Object.create(baseAPI.BaseAPI.prototype); 8 | API.prototype.wellKnownURLs = function() { 9 | return ['/message'] 10 | } 11 | API.prototype.resourceClass = function(type_name) { 12 | return type_name in classToKindMap ? classToKindMap[type_name] : baseAPI.BaseEntity 13 | } 14 | 15 | var api = new API(); 16 | 17 | var api_function = function() { 18 | return api 19 | } 20 | 21 | function HelloMessage(url, jso, etag) { 22 | baseAPI.BaseEntity.call(this, url, jso, etag) 23 | } 24 | HelloMessage.prototype = Object.create(baseAPI.BaseEntity.prototype); 25 | HelloMessage.prototype.constructor = HelloMessage; 26 | HelloMessage.prototype._className = 'HelloMessage'; 27 | HelloMessage.prototype.api = api_function; 28 | 29 | var classToKindMap = { 30 | HelloMessage: HelloMessage 31 | }; 32 | 33 | return { 34 | api: api, 35 | HelloMessage: HelloMessage 36 | } 37 | } 38 | 39 | module.exports = exports() 40 | -------------------------------------------------------------------------------- /js/test/helloMessage/run_tests.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | cd $DIR 3 | node $DIR/test.js -------------------------------------------------------------------------------- /js/test/helloMessage/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var helloMessageAPI = require('./helloMessageAPI') 4 | var api = helloMessageAPI.api 5 | 6 | api.retrieve('http://localhost:3000/message', function(error, message) { 7 | if (error) { 8 | console.log(error) 9 | } else { 10 | if (!(message instanceof helloMessageAPI.HelloMessage)) throw 'assert' 11 | message.text = "It's a JS world"; 12 | message.update(function(error) { 13 | if (error) throw error.args[0]; 14 | if (!message.text == "It's a JS world") throw 'assert' 15 | message.delete(function(error) { 16 | if (!error) { 17 | throw 'should not be allowed to delete well-known resource' 18 | } 19 | }) 20 | }) 21 | } 22 | }) -------------------------------------------------------------------------------- /js/test/run_tests.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | cd $DIR 3 | $DIR/helloMessage/run_tests.sh 4 | $DIR/todoList/run_tests.sh -------------------------------------------------------------------------------- /js/test/todoList/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todoList", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "test.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "async": "^1.4.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /js/test/todoList/run_tests.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | cd $DIR 3 | node $DIR/test.js -------------------------------------------------------------------------------- /js/test/todoList/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var async = require('async') 4 | var todoListAPI = require('./todoListAPI') 5 | var api = todoListAPI.api 6 | 7 | function test_objects() { 8 | api.retrieve('http://localhost:3001/to-dos', function(error, todoList) { 9 | todoList.retrieve('items', function(error, items) { 10 | if (error) throw JSON.stringify(error); 11 | var new_item = new todoListAPI.Item(); 12 | new_item.description = 'buy milk'; 13 | items.create(new_item, function(error) { 14 | new_item.description = 'buy more milk'; 15 | new_item.due = 'tonight'; 16 | new_item.update(function(error) { 17 | new_item.delete(function(error) { 18 | }) 19 | }) 20 | }) 21 | }) 22 | }) 23 | } 24 | 25 | test_objects() 26 | 27 | function test_api() { 28 | api.retrieve('http://localhost:3001/to-dos', function(error, todoList) { 29 | api.retrieve('http://localhost:3001/to-dos/items', function(error, entity) { 30 | var body = { 31 | kind: 'Item', 32 | description:'buy milk' 33 | }; 34 | api.create('http://localhost:3001/to-dos/items', body, function(error, entity) { 35 | var new_item = entity; 36 | var changes = { 37 | description: 'buy more milk', 38 | due: 'tonight' 39 | }; 40 | api.update('http://localhost:3001/to-dos/items;' + new_item._id, new_item._etag, changes, function(error, entity) { 41 | api.delete('http://localhost:3001/to-dos/items;' + new_item._id, function(error, entity) { 42 | }) 43 | }) 44 | }) 45 | }) 46 | }) 47 | } 48 | 49 | test_api() 50 | 51 | function test_objects_with_async() { 52 | async.waterfall([ 53 | function(callback) { 54 | api.retrieve('http://localhost:3001/to-dos', function(error, todoList) { 55 | if (error) 56 | callback(error); 57 | else if (!(todoList instanceof todoListAPI.TodoList)) 58 | callback('expected type TodoList'); 59 | else 60 | callback(null, todoList) 61 | }) 62 | }, 63 | function(todoList, callback) { 64 | todoList.retrieve('items', function(error, items) { 65 | if (error) 66 | callback(error); 67 | else if (!(items instanceof todoListAPI.Collection)) 68 | callback('expected type Collection'); 69 | else 70 | callback(null, todoList, items) 71 | }) 72 | }, 73 | function(todoList, items, w_callback) { 74 | var new_item = new todoListAPI.Item({'description':'buy milk'}); 75 | async.series([ 76 | function (callback) { 77 | items.create(new_item, function(error) { 78 | if (error) 79 | callback(error); 80 | else if (!(new_item._self)) 81 | callback('created item has no _self value'); 82 | else 83 | callback(null, 'created item') 84 | }) 85 | }, 86 | function(callback) { 87 | todoList.retrieve('items', function(error, items) { 88 | if (error) 89 | callback(error); 90 | else if (!(new_item._self in items.items)) 91 | callback('new item not in items array'); 92 | else 93 | callback(null, 'verified item in list') 94 | }) 95 | }, 96 | function(callback) { 97 | new_item.description = 'buy more milk'; 98 | new_item.due = 'tonight'; 99 | new_item.update(function(error) { 100 | if (error) 101 | callback(error); 102 | else 103 | callback(null, 'updated item'); 104 | }) 105 | }, 106 | function(callback) { 107 | new_item.refresh(function(error) { 108 | if (error) 109 | callback(error); 110 | else if (!(new_item.description == 'buy more milk')) 111 | callback('assert'); 112 | else 113 | callback(null, 'verified update'); 114 | }) 115 | }, 116 | function(callback) { 117 | new_item.delete(function(error) { 118 | if (error) 119 | callback(error); 120 | else 121 | callback(null, 'deleted new item'); 122 | }) 123 | }, 124 | function (callback) { 125 | items.refresh(function(error) { 126 | if (error) 127 | callback(error); 128 | else if (new_item._self in items.items) 129 | callback('assert'); 130 | else 131 | callback(null, 'created item') 132 | }) 133 | } 134 | ], function(error, results) { 135 | if (error) { 136 | console.log('error', error); 137 | w_callback(error) 138 | } else { 139 | console.log(results); 140 | w_callback(null, 'successfully created, updated and deleted item') 141 | } 142 | }) 143 | } 144 | ], function(error, result) { 145 | if (error) 146 | console.log('error', error); 147 | else 148 | console.log(result) 149 | }) 150 | } 151 | 152 | test_objects_with_async() -------------------------------------------------------------------------------- /js/test/todoList/todoListAPI.js: -------------------------------------------------------------------------------- 1 | var baseAPI = require('./../../../js/base_api') 2 | 3 | var exports = function() { 4 | 5 | function API() {} 6 | 7 | API.prototype = Object.create(baseAPI.BaseAPI.prototype); 8 | API.prototype.wellKnownURLs = function() { 9 | return ['/to-dos'] 10 | } 11 | API.prototype.resourceClass = function(type_name) { 12 | return type_name in classToKindMap ? classToKindMap[type_name] : baseAPI.BaseResource 13 | } 14 | 15 | var api = new API(); 16 | 17 | var api_function = function() { 18 | return api 19 | } 20 | 21 | function TodoList(url, jso, etag) { 22 | baseAPI.BaseEntity.call(this, url, jso, etag) 23 | } 24 | TodoList.prototype = Object.create(baseAPI.BaseEntity.prototype); 25 | TodoList.prototype.constructor = TodoList; 26 | TodoList.prototype._className = 'TodoList'; 27 | TodoList.prototype.api = api_function; 28 | 29 | function Item(url, jso, etag) { 30 | baseAPI.BaseEntity.call(this, url, jso, etag) 31 | } 32 | Item.prototype = Object.create(baseAPI.BaseEntity.prototype); 33 | Item.prototype.constructor = Item; 34 | Item.prototype._className = 'Item'; 35 | Item.prototype.api = api_function; 36 | 37 | function Collection(url, jso, etag) { 38 | baseAPI.BaseCollection.call(this, url, jso, etag) 39 | } 40 | Collection.prototype = Object.create(baseAPI.BaseCollection.prototype); 41 | Collection.prototype.constructor = Collection; 42 | Collection.prototype._className = 'Collection'; 43 | Collection.prototype.api = api_function; 44 | 45 | var classToKindMap = { 46 | TodoList: TodoList, 47 | Item: Item, 48 | Collection: Collection 49 | }; 50 | 51 | return { 52 | api: api, 53 | TodoList: TodoList, 54 | Item: Item, 55 | Collection: Collection 56 | } 57 | 58 | } 59 | 60 | module.exports = exports() -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | python setup.py sdist upload -r pypi -------------------------------------------------------------------------------- /py/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apigee-labs/rapier/7ef9ce6ef5112477a45c777aa4882932610ce1fe/py/__init__.py -------------------------------------------------------------------------------- /py/base_api.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from urlparse import urlparse, urlunparse 3 | 4 | class BaseAPI(object): 5 | 6 | def retrieve_headers(self): 7 | return { 8 | 'Accept': 'application/json' 9 | } 10 | 11 | def update_headers(self, etag): 12 | return { 13 | 'Accept': 'application/json', 14 | 'Content-Type': 'application/json', 15 | 'If-Match': etag 16 | } 17 | 18 | def delete_headers(self): 19 | return { 20 | 'Accept': 'application/json' 21 | } 22 | 23 | def retrieve(self, url, entity=None, headers=None): 24 | # issue a GET to retrieve a resource from the API and create an object for it 25 | r = requests.get(url, headers = headers if headers is not None else self.retrieve_headers()) 26 | return self.process_resource_result(url, r, entity) 27 | 28 | def update(self, url, etag, changes, entity=None, headers=None): 29 | r = requests.patch(url, json=changes, headers = headers if headers is not None else self.update_headers(etag)) 30 | return self.process_resource_result(url, r, entity) 31 | 32 | def delete(self, url, entity=None, headers=None): 33 | r = requests.delete(url, headers = headers if headers is not None else self.delete_headers()) 34 | return self.process_resource_result(url, r, entity) 35 | 36 | def create(self, url, body, entity=None, headers=None): 37 | r = requests.post(url, json=body, headers = headers if headers is not None else self.delete_headers()) 38 | return self.process_resource_result(url, r, entity, location_header = 'Location') 39 | 40 | def retrieve_well_known_resource(self, url, entity=None, headers=None): 41 | url_parts = list(urlparse(url)) 42 | url_parts[0] = url_parts[1] = None 43 | 44 | if urlunparse(url_parts) in self.well_known_URLs(): 45 | return self.retrieve(url, entity, headers) 46 | else: 47 | raise Exception('no such well-known resource %s. Valid urls are: %s' % (urlunparse(url_parts), self.well_known_URLs())) 48 | 49 | def process_resource_result(self, url, r, entity=None, location_header = 'Content-Location'): 50 | if r.status_code == 200 or r.status_code == 201: 51 | if location_header in r.headers: 52 | location = r.headers[location_header] 53 | if 'ETag' in r.headers: 54 | etag = r.headers['ETag'] 55 | if 'Content-Type' in r.headers: 56 | content_type = r.headers['Content-Type'].split(';')[0] 57 | if content_type == 'application/json': 58 | jso = r.json() 59 | return self.build_resource_from_json(jso, entity, location, etag) 60 | else: 61 | raise Exception('non-json content_type %s' % r.headers['Content-Type']) 62 | else: 63 | raise Exception('server did not declare content_type') 64 | else: 65 | raise Exception('server did not provide etag') 66 | else: 67 | raise Exception('server failed to provide %s header for url %s' % (location_header, url)) 68 | else: 69 | raise Exception('unexpected HTTP status_code code: %s url: %s text: %s' % (r.status_code, url, r.text)) 70 | 71 | def build_resource_from_json(self, jso, entity=None, url=None, etag=None): 72 | kind = jso.get('kind') 73 | if kind: 74 | if entity: 75 | if not hasattr(entity, 'kind') or entity.kind == kind: 76 | entity.update_attrs(jso, url, etag) 77 | return entity 78 | else: 79 | raise Exception('SDK cannot handle change of kind from %s to %s' % (entity.kind, kind)) 80 | else: 81 | resource_class = self.resource_class(kind) 82 | if resource_class: 83 | return resource_class(url, jso, etag) 84 | else: 85 | raise Exception('no resource_class for kind %s') % kind 86 | else: 87 | if entity and entity.kind: 88 | entity.update_attrs(url, jso, etag) 89 | return entity 90 | else: 91 | raise Exception('no kind property in json %s' % jso) 92 | 93 | class BaseResource(object): 94 | 95 | def __init__(self, url = None, jso = None, etag = None): 96 | self.update_attrs(jso, url, etag) 97 | 98 | def update_attrs(self, jso = None, url = None, etag = None): 99 | if jso: 100 | for key, value in jso.iteritems(): 101 | setattr(self, key, value) 102 | if '_location' in jso: 103 | self._location = jso['_location'] 104 | elif '_self' in jso: 105 | self._location = jso['_self'] 106 | self._jso = jso 107 | if url: 108 | self._location = url 109 | if etag: 110 | self._etag = etag 111 | 112 | def refresh(self): 113 | # issue a GET to refresh this object from API 114 | if not self._location: 115 | raise Exception('self location not set') 116 | return self.api().retrieve(self._location, self) 117 | 118 | class BaseEntity(BaseResource): 119 | 120 | def __init__(self, jso = None, url = None, etag = None): 121 | self._related = dict() 122 | self.kind = type(self).__name__ 123 | super(BaseEntity, self).__init__(jso, url, etag) 124 | 125 | def changes(self): 126 | jso = self._jso if hasattr(self, '_jso') else None 127 | return {key: value for key, value in self.__dict__.iteritems() if not (key.startswith('_') or (jso and key in jso and jso[key] == value))} 128 | 129 | def update(self): 130 | # issue a PATCH or PUT to update this object from API 131 | changes = self.changes() 132 | if not (hasattr(self, '_location') and self._location): 133 | raise Exception('self _location not set') 134 | if not hasattr(self, '_etag') or self._etag == None: 135 | raise Exception('self _etag not set') 136 | return self.api().update(self._location, self._etag, changes, self) 137 | 138 | def delete(self): 139 | # issue a DELETE to remove this object from API 140 | if not self._location: 141 | raise Exception('self location not set') 142 | else: 143 | return self.api().delete(self._location, self) 144 | 145 | def retrieve(self, relationship): 146 | # fetch a related resource 147 | if hasattr(self, relationship): 148 | url = getattr(self, relationship) 149 | rslt = self.api().retrieve(url) 150 | if not isinstance(rslt, Exception): 151 | self._related[relationship] = rslt 152 | return rslt 153 | else: 154 | raise Exception('no value set for %s URL' % relationship) 155 | 156 | def get_related(self, relationship, default_value): 157 | # return a previously-fetched related resource 158 | return self._related.get(relationship, default_value) 159 | 160 | class BaseCollection(BaseResource): 161 | 162 | def update_attrs(self, jso, url, etag): 163 | super(BaseCollection, self).update_attrs(jso, url, etag) 164 | if jso and 'items' in jso: 165 | self.items = {} 166 | for item in jso['items']: 167 | item_object = self.api().build_resource_from_json(item) 168 | self.items[item_object._location] = item_object 169 | 170 | def create(self, entity): 171 | # create a new entity in the API by POSTing 172 | if self._location: 173 | if hasattr(entity, '_self') and entity._self: 174 | raise Exception('entity already exists in API %s' % entity) 175 | rslt = self.api().create(self._location, entity.changes(), entity) 176 | if hasattr(self, 'items'): 177 | if entity._self in self.items: 178 | raise Exception('Duplicate id') 179 | else: 180 | self.items[entity._self] = entity 181 | return entity 182 | else: 183 | raise Exception('Collection has no _self property') -------------------------------------------------------------------------------- /py/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apigee-labs/rapier/7ef9ce6ef5112477a45c777aa4882932610ce1fe/py/test/__init__.py -------------------------------------------------------------------------------- /py/test/hello_message/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apigee-labs/rapier/7ef9ce6ef5112477a45c777aa4882932610ce1fe/py/test/hello_message/__init__.py -------------------------------------------------------------------------------- /py/test/hello_message/hello_message_api.py: -------------------------------------------------------------------------------- 1 | from rapier.py.base_api import BaseAPI, BaseEntity 2 | 3 | class HelloMessageAPI(BaseAPI): 4 | 5 | def well_known_URLs(self): 6 | return ['/message'] 7 | 8 | def resource_class(self, type_name): 9 | cls = globals().get(type_name) 10 | return cls if cls else BaseEntity 11 | 12 | api = HelloMessageAPI() 13 | 14 | class HelloMessage(BaseEntity): 15 | 16 | def api(self): 17 | return api -------------------------------------------------------------------------------- /py/test/hello_message/run_tests.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../../" )" && pwd) 3 | export PYTHONPATH=$ROOT_DIR:$PYTHONPATH 4 | python "$DIR/test.py" -------------------------------------------------------------------------------- /py/test/hello_message/test.py: -------------------------------------------------------------------------------- 1 | from rapier.py.test.hello_message.hello_message_api import api 2 | 3 | def main(): 4 | rslt = api.retrieve_well_known_resource('http://localhost:3000/message') 5 | rslt.text = 'goodbye, world' 6 | rslt = rslt.update() 7 | rslt = api.retrieve_well_known_resource('http://localhost:3000/message') 8 | assert(rslt.text == 'goodbye, world') 9 | try: 10 | rslt = rslt.delete() 11 | except Exception as e: 12 | if e.args[0].startswith('unexpected HTTP status_code code: 405 url: http://localhost:3000/message'): 13 | return 14 | else: 15 | raise e 16 | raise Exception('Deleting a well-known URL should have raised an Exception') 17 | 18 | if __name__ == '__main__': 19 | main() -------------------------------------------------------------------------------- /py/test/run_tests.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | $DIR/hello_message/run_tests.sh 3 | $DIR/todo_list/run_tests.sh -------------------------------------------------------------------------------- /py/test/todo_list/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apigee-labs/rapier/7ef9ce6ef5112477a45c777aa4882932610ce1fe/py/test/todo_list/__init__.py -------------------------------------------------------------------------------- /py/test/todo_list/run_tests.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../../" )" && pwd) 3 | export PYTHONPATH=$ROOT_DIR:$PYTHONPATH 4 | python "$DIR/test.py" -------------------------------------------------------------------------------- /py/test/todo_list/test.py: -------------------------------------------------------------------------------- 1 | from rapier.py.test.todo_list.todo_list_api import api, Item, Collection, TodoList 2 | 3 | def test_objects(): 4 | items = Collection('http://localhost:3001/to-dos/items') 5 | new_item = Item() 6 | new_item.description = 'buy milk' 7 | items.create(new_item) 8 | items.refresh() 9 | assert(new_item._self in items.items) 10 | new_item.description = 'buy gallon of milk' 11 | new_item.due = 'tonight' 12 | new_item.update() 13 | new_item.delete() 14 | 15 | def test_api(): 16 | body = {'kind': 'Item', 17 | 'description':'buy milk' 18 | } 19 | new_item = api.create('http://localhost:3001/to-dos/items', body) 20 | items = api.retrieve('http://localhost:3001/to-dos/items') 21 | assert(new_item._self in items.items) 22 | changes = {'description': 'buy gallon of milk', 23 | 'due': 'tonight' 24 | } 25 | api.update('http://localhost:3001/to-dos/items;' + new_item.id, new_item._etag, changes) 26 | api.delete('http://localhost:3001/to-dos/items;' + new_item.id) 27 | 28 | def test_raw(): 29 | import requests 30 | body = {'kind': 'Item', 31 | 'description':'buy milk' 32 | } 33 | headers = {'Content-Type': 'application/json', 34 | 'Accept': 'application/json' 35 | } 36 | r = requests.post('http://localhost:3001/to-dos/items', json = body, headers = headers) 37 | if r.status_code != 201: 38 | raise Exception('unexpected HTTP status_code code: %s url: %s text: %s' % (r.status_code, r.url, r.text)) 39 | new_item = r.json() 40 | etag = r.headers['ETag'] 41 | new_item_url = r.headers['Location'] 42 | r = requests.get('http://localhost:3001/to-dos/items', headers = {'Accept': 'application/json'}) 43 | if r.status_code != 200: 44 | raise Exception('unexpected HTTP status_code code: %s url: %s text: %s' % (r.status_code, url, r.text)) 45 | items = r.json() 46 | assert(new_item['_self'] in {item['_self'] for item in items['items']}) 47 | changes = {'description': 'buy gallon of milk', 48 | 'due': 'tonight' 49 | } 50 | headers = { 51 | 'Accept': 'application/json', 52 | 'Content-Type': 'application/json', 53 | 'If-Match': etag 54 | } 55 | r = requests.patch(new_item_url, json = changes, headers = headers) 56 | if r.status_code != 200: 57 | raise Exception('unexpected HTTP status_code code: %s url: %s text: %s' % (r.status_code, r.url, r.text)) 58 | r = requests.delete(new_item_url, json = changes, headers = {'Accept': 'application/json'}) 59 | if r.status_code != 200: 60 | raise Exception('unexpected HTTP status_code code: %s url: %s text: %s' % (r.status_code, r.url, r.text)) 61 | 62 | def main(): 63 | test_objects() 64 | test_api() 65 | test_raw() 66 | 67 | if __name__ == '__main__': 68 | main() -------------------------------------------------------------------------------- /py/test/todo_list/todo_list_api.py: -------------------------------------------------------------------------------- 1 | from rapier.py.base_api import BaseAPI, BaseResource, BaseEntity, BaseCollection 2 | 3 | class API(BaseAPI): 4 | def well_known_URLs(self): 5 | return ['/to-dos'] 6 | def resource_class(self, type_name): 7 | return classToKindMap.get(type_name, BaseResource) 8 | 9 | api = API() 10 | 11 | class APIClass(object): 12 | def api(self): 13 | return api 14 | 15 | class TodoList(BaseEntity, APIClass): 16 | pass 17 | 18 | class Item(BaseEntity, APIClass): 19 | pass 20 | 21 | class Collection(BaseCollection, APIClass): 22 | pass 23 | 24 | classToKindMap = { 25 | 'TodoList': TodoList, 26 | 'Item': Item, 27 | 'Collection': Collection 28 | } -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name = 'rapier', 5 | packages = ['util'], 6 | entry_points = { 7 | 'console_scripts': ['rapier = util.rapier:main'] 8 | }, 9 | version = '0.0.4', 10 | description = 'Generate OpenAPI specification from Rapier specification', 11 | long_description = 'Generate OpenAPI specification from Rapier specification', 12 | author = 'Martin Nally', 13 | author_email = 'mnally@apigee.com', 14 | url = 'https://github.com/apigee-labs/rapier', 15 | install_requires = ['PyYAML==3.11'] 16 | ) -------------------------------------------------------------------------------- /test-servers/hello_message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | var bodyParser = require('body-parser'); 6 | var modcount = 0; 7 | 8 | var PORT = 3000; 9 | var HOST = 'localhost'; 10 | var MESSAGE_URL = 'http://' + HOST + ':' + PORT + '/message'; 11 | var message = { 12 | _self: MESSAGE_URL, 13 | kind: 'HelloMessage', 14 | text: 'Hello, world' 15 | } 16 | 17 | app.use(bodyParser.json()); // for parsing application/json 18 | 19 | app.get('/message', function(req, res) { 20 | var accept_type = req.get('Accept'); 21 | if (typeof accept_type == 'undefined' || accept_type === '*/*'|| accept_type === 'application/json') { 22 | res.set('ETag', modcount.toString()); 23 | res.set('Content-Type', 'application/json'); 24 | res.set('Content-Location', MESSAGE_URL) 25 | res.json(message); 26 | } else { 27 | res.status(406).send('Unrecognized accept header media type: ' + accept_type) 28 | } 29 | }); 30 | 31 | app.patch('/message', function(req, res) { 32 | var content_type = req.get('Content-Type'); 33 | if (content_type === 'application/json') { 34 | var accept_type = req.get('Accept'); 35 | if (typeof accept_type == 'undefined' || accept_type === '*/*'|| accept_type === 'application/json') { 36 | for (var attrname in req.body) { message[attrname] = req.body[attrname]; } 37 | modcount +=1; 38 | res.set('ETag', modcount.toString()); 39 | res.set('Content-Type', 'application/json'); 40 | res.set('Content-Location', MESSAGE_URL) 41 | res.json(message); 42 | } else { 43 | res.status(406).send('Unrecognized accept header media type: ' + accept_type) 44 | } 45 | } else { 46 | res.status(406).send('Unrecognized content-type media type: ' + content_type) 47 | } 48 | }); 49 | 50 | app.delete('/message', function(req, res) { 51 | res.status(405); 52 | var error_msg = 'cannot delete well-known resource: /message'; 53 | var accept_type = req.get('Accept'); 54 | if (typeof accept_type == 'undefined' || accept_type === '*/*'|| accept_type === 'application/json') { 55 | res.json({text: error_msg}); 56 | } else { 57 | res.send(error_msg) 58 | } 59 | }) 60 | 61 | console.log('Listening on %d', PORT); 62 | app.listen(PORT); 63 | -------------------------------------------------------------------------------- /test-servers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "servers", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | -------------------------------------------------------------------------------- /test-servers/todo_list.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | var bodyParser = require('body-parser'); 6 | 7 | var ITEMID = 0; 8 | var PORT = 3001; 9 | var HOST = 'localhost'; 10 | var BASE_PREFIX = 'http://' + HOST + ':' + PORT 11 | var TODOS_URL = BASE_PREFIX + '/to-dos'; 12 | var TODOS = { 13 | _self: TODOS_URL, 14 | kind: 'TodoList', 15 | items: BASE_PREFIX + '/items' 16 | } 17 | var ITEMS = { 18 | _self: BASE_PREFIX + '/items', 19 | kind: 'Collection', 20 | items: [], 21 | item_type: 'Item' 22 | } 23 | 24 | app.use(bodyParser.json()); // for parsing application/json 25 | 26 | app.get('/to-dos', function(req, res) { 27 | var accept_type = req.get('Accept'); 28 | if (typeof accept_type == 'undefined' || accept_type === '*/*'|| accept_type === 'application/json') { 29 | res.set('Content-Type', 'application/json'); 30 | res.set('Content-Location', TODOS._self); 31 | res.json(TODOS); 32 | } else { 33 | res.status(406).send('Unrecognized accept header media type: ' + accept_type) 34 | } 35 | }); 36 | 37 | function getItems(req, res) { 38 | var accept_type = req.get('Accept'); 39 | if (typeof accept_type == 'undefined' || accept_type === '*/*'|| accept_type === 'application/json') { 40 | res.set('Content-Type', 'application/json'); 41 | res.set('Content-Location', ITEMS._self); 42 | res.json(ITEMS); 43 | } else { 44 | res.status(406).send('Unrecognized accept header media type: ' + accept_type) 45 | } 46 | } 47 | 48 | app.get('/to-dos/items', getItems); 49 | app.get('/items', getItems); 50 | 51 | function getItem(req, res) { 52 | var accept_type = req.get('Accept'); 53 | if (typeof accept_type == 'undefined' || accept_type === '*/*'|| accept_type === 'application/json') { 54 | var itemid = req.params.itemid; 55 | var items = ITEMS.items; 56 | var item = null; 57 | for (var i = 0; i < items.length; i++) { 58 | if (items[i].id == itemid) { 59 | item = items[i]; 60 | break; 61 | } 62 | } 63 | if (item !== null) { 64 | res.set('Content-Type', 'application/json'); 65 | res.set('Content-Location', item._self); 66 | res.set('ETag', item._etag); 67 | res.status(200); 68 | res.json(item); 69 | } else { 70 | res.status(404).send('Not Found') 71 | } 72 | } else { 73 | res.status(406).send('Unrecognized accept header media type: ' + accept_type) 74 | } 75 | } 76 | 77 | app.get('/item/:itemid', getItem); 78 | app.get('/to-dos/items;:itemid', getItem); 79 | 80 | function patchItem(req, res) { 81 | var accept_type = req.get('Accept'); 82 | if (typeof accept_type == 'undefined' || accept_type === '*/*'|| accept_type === 'application/json') { 83 | var content_type = req.get('Content-Type'); 84 | if (typeof content_type == 'undefined' || content_type === 'application/json') { 85 | var itemid = req.params.itemid; 86 | var items = ITEMS.items; 87 | var item = null; 88 | for (var i = 0; i < items.length; i++) { 89 | if (items[i].id == itemid) { 90 | item = items[i]; 91 | break; 92 | } 93 | } 94 | if (item !== null) { 95 | var changes = req.body; 96 | var error = null; 97 | for (var property in changes) { 98 | if (property.indexOf('_') == 0 || property == 'kind' || property == 'id') { 99 | res.status(400); 100 | res.set('Content-Type', 'application/json'); 101 | res.json({'text': 'Cannot modify property '+ property}); 102 | error = 400; 103 | break; 104 | } 105 | } 106 | if (error == null) { 107 | for (var property in changes) { 108 | item[property] = changes[property] 109 | } 110 | item._etag++ 111 | res.set('Content-Type', 'application/json'); 112 | res.set('Content-Location', item._self); 113 | res.set('ETag', item._etag); 114 | res.status(200); 115 | res.json(item); 116 | } 117 | } else { 118 | res.status(404).send('Not Found') 119 | } 120 | } else { 121 | res.status(406).send({text: 'Unrecognized Content-Type header media type: ' + content_type}) 122 | } 123 | } else { 124 | res.status(406).send('Unrecognized Accept header media type: ' + accept_type) 125 | } 126 | } 127 | 128 | app.patch('/item/:itemid', patchItem); 129 | app.patch('/to-dos/items;:itemid', patchItem); 130 | 131 | function deleteItem(req, res) { 132 | var accept_type = req.get('Accept'); 133 | if (typeof accept_type == 'undefined' || accept_type === '*/*'|| accept_type === 'application/json') { 134 | var content_type = req.get('Content-Type'); 135 | if (typeof content_type == 'undefined' || content_type === 'application/json') { 136 | var itemid = req.params.itemid; 137 | var items = ITEMS.items; 138 | var item = null; 139 | for (var i = 0; i < items.length; i++) { 140 | if (items[i].id == itemid) { 141 | item = items.splice(i,1)[0] 142 | break; 143 | } 144 | } 145 | if (item !== null) { 146 | res.set('Content-Type', 'application/json'); 147 | res.set('Content-Location', item._self); 148 | res.set('ETag', item._etag); 149 | res.status(200); 150 | res.json(item); 151 | } else { 152 | res.status(404).send('Not Found') 153 | } 154 | } else { 155 | res.status(406).send({text: 'Unrecognized Content-Type header media type: ' + content_type}) 156 | } 157 | } else { 158 | res.status(406).send('Unrecognized Accept header media type: ' + accept_type) 159 | } 160 | } 161 | 162 | app.delete('/item/:itemid', deleteItem); 163 | app.delete('/to-dos/items;:itemid', deleteItem); 164 | 165 | function postItem(req, res) { 166 | var accept_type = req.get('Accept'); 167 | if (typeof accept_type == 'undefined' || accept_type === '*/*'|| accept_type === 'application/json') { 168 | var content_type = req.get('Content-Type'); 169 | if (typeof content_type == 'undefined' || content_type === 'application/json') { 170 | var item = req.body; 171 | if ('kind' in item) { 172 | var itemid = ITEMID++; 173 | item._self = BASE_PREFIX + '/item/' + itemid.toString(); 174 | item.id = itemid.toString(); 175 | item._etag = 0; 176 | ITEMS.items.push(item); 177 | res.set('ETag', item._etag); 178 | res.set('Location', item._self); 179 | res.status(201).json(item) 180 | } else { 181 | res.status(400).send({text: 'No kind set for Item'}) 182 | } 183 | } else { 184 | res.status(406).send({text: 'Unrecognized Content-Type header media type: ' + content_type}) 185 | } 186 | } else { 187 | res.status(406).send('Unrecognized Accept header media type: ' + accept_type) 188 | } 189 | } 190 | 191 | app.post('/to-dos/items', postItem); 192 | app.post('/items', postItem); 193 | 194 | console.log('Listening on %d', PORT); 195 | app.listen(PORT); -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apigee-labs/rapier/7ef9ce6ef5112477a45c777aa4882932610ce1fe/util/__init__.py -------------------------------------------------------------------------------- /util/gen_html.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys, codecs, os 4 | import validate_rapier 5 | 6 | class HTMLGenerator(object): 7 | 8 | def generate_property_cell(self, property): 9 | rslt = property.get('description', '') 10 | return rslt 11 | 12 | def create_link(self, uri_ref): 13 | uri_ref = self.validator.relative_url(uri_ref) 14 | split_ref = uri_ref.split('#') 15 | split_ref[1] = split_ref[1].split('/')[-1] 16 | url = split_ref[0] 17 | if url.endswith('.yaml'): 18 | split_ref[0] = url[:-4] + 'html' 19 | uri_ref = '#'.join(split_ref) 20 | return '{}'.format(uri_ref, split_ref[1]) 21 | 22 | def generate_property_type(self, entity, property): 23 | if 'relationship' in property: 24 | relationship = property['relationship'] 25 | if isinstance(relationship, basestring): 26 | entity_urls = relationship.split() 27 | multiplicity = '0:1' 28 | elif isinstance(relationship, list): 29 | entity_urls = relationship 30 | multiplicity = '0:1' 31 | else: 32 | entity_urls = relationship['entities'] 33 | if isinstance(entity_urls, basestring): 34 | entity_urls = entity_urls.split() 35 | multiplicity = relationship.get('multiplicity', '0:1') 36 | entity_links = [self.create_link(entity_url) for entity_url in entity_urls] 37 | upper_bound = multiplicity.split(':')[-1] 38 | multi_valued = upper_bound == 'n' or int(upper_bound) > 1 39 | return '%s (%s)' % (multiplicity, ' or '.join(entity_links)) if multi_valued else 'url of %s' % ' or '.join(entity_links) 40 | elif '$ref' in property: 41 | ref = property['$ref'] 42 | return self.create_link(ref) 43 | else: 44 | type = property.get('type', '') 45 | if type == 'array': 46 | items = property['items'] 47 | rslt = '[%s]' % self.generate_property_type(entity, items) 48 | elif 'properties' in property: 49 | rslt = self.generate_properties_table(entity, property['properties']) 50 | else: 51 | format = property.get('format') 52 | if format is not None: 53 | rslt = format 54 | else: 55 | rslt = type 56 | return rslt 57 | 58 | def generate_property_usage(self, entity, property): 59 | entity_readOnly = entity.get('readOnly') 60 | if entity_readOnly is None: 61 | entity_usage = entity.get('usage') 62 | readOnly = property.get('readOnly') 63 | if readOnly is None: 64 | usage = property.get('usage') 65 | if usage is None: 66 | return 'c r u' 67 | else: 68 | result = '' 69 | if len(validate_rapier.OASValidator.c_usage_values & set(as_list(usage))) > 0: 70 | result += 'c' 71 | if len(validate_rapier.OASValidator.r_usage_values & set(as_list(usage))) > 0: 72 | result += 'r' 73 | if len(validate_rapier.OASValidator.u_usage_values & set(as_list(usage))) > 0: 74 | result += 'u' 75 | return ' '.join(result) 76 | return 'r' 77 | 78 | def generate_property_rows(self, entity, properties): 79 | rslt = '' 80 | for property_name, property in properties.iteritems(): 81 | rslt += ''' 82 | 83 | %s 84 | %s 85 | %s 86 | %s 87 | ''' % (property_name, self.generate_property_cell(property), self.generate_property_type(entity, property), self.generate_property_usage(entity, property)) 88 | return rslt 89 | 90 | def generate_properties_table(self, entity, properties): 91 | property_rows = self.generate_property_rows(entity, properties) 92 | return ''' 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | %s 103 | 104 |
Property NameProperty DescriptionProperty Typeusage
''' % property_rows 105 | 106 | def allOf(self, allOf): 107 | def replace_dot_yaml(url): 108 | split_url = url.split('#') 109 | if split_url[0].endswith('.yaml'): 110 | split_url[0] = split_url[0][:-4] + 'html' 111 | url = '#'.join(split_url) 112 | if len(allOf) > 1: 113 | rslt = ''' 114 |
115 | Includes properties and other constraints from all of: 116 | 118 |
''' 119 | row = ''' 120 |
  • %s 121 |
  • ''' 122 | rows = [row.format(self.create_link(ref['$ref'])) for ref in allOf] 123 | return rslt % ''.join(rows) 124 | else: 125 | return ''' 126 |
    127 | Includes properties and other constraints from {0} 128 |
    '''.format(self.create_link(self.entities[allOf[0]['$ref']]['id'])) 129 | 130 | def generate_entity_cell(self, entity): 131 | rslt = entity.get('description', '') 132 | if 'allOf' in entity: 133 | rslt = rslt + self.allOf(entity['allOf']) 134 | properties = entity.get('properties') 135 | if properties is not None: 136 | rslt += self.generate_properties_table(entity, properties) 137 | return rslt 138 | 139 | def create_anchor(self, name): 140 | return '{0}'.format(name) 141 | 142 | def generate_entity_rows(self, entities): 143 | rslt = '' 144 | for entity_name, entity in entities.iteritems(): 145 | rslt += ''' 146 | 147 | %s 148 | %s 149 | \n''' % (self.create_anchor(entity_name), self.generate_entity_cell(entity)) 150 | return rslt 151 | 152 | def generate_entities_table(self, spec): 153 | entities = spec.get('entities') 154 | entity_rows = self.generate_entity_rows(entities) if entities is not None else '' 155 | return \ 156 | ''' 157 | 158 | 159 | 160 | 161 | 162 | 163 | %s 164 | 165 |
    Entity NameEntity Description
    ''' % entity_rows 166 | 167 | def generate_header(self, spec): 168 | version = str(spec.get('version', 'initial')) 169 | # If it's a string that looks like a number, add quotes around it so its clear it's a string 170 | try: 171 | number = float(version) 172 | version_str = repr(version) 173 | except ValueError: 174 | version_str = version 175 | return '''

    %s

    176 |

    Id: %s

    177 |

    Version: %s

    178 |

    %s

    179 | '''% (spec.get('title', 'untitled'), spec.get('id', '"#"'), version_str, spec.get('description', 'undescribed')) 180 | 181 | def generate_html(self, filename): 182 | self.validator = validate_rapier.OASValidator() 183 | spec, errors = self.validator.validate(filename) 184 | if errors == 0: 185 | 186 | self.entities = self.validator.build_included_entity_map() 187 | entities = spec.get('entities') 188 | rslt = ''' 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 |
    197 | %s 198 |

    199 |

    200 | %s 201 |
    202 |
    203 | 204 | 205 | 206 | ''' % (self.generate_header(spec), self.generate_entities_table(spec) if entities is not None else '') 207 | UTF8Writer = codecs.getwriter('utf8') 208 | sys.stdout = UTF8Writer(sys.stdout) 209 | return rslt 210 | else: 211 | print >>sys.stderr, 'HTML generation of %s failed' % filename 212 | 213 | def as_list(value, separator = None): 214 | if isinstance(value, basestring): 215 | if separator: 216 | result = [item.strip() for item in value.split(separator)] 217 | else: 218 | result = value.split() 219 | else: 220 | if isinstance(value, (list, tuple)): 221 | result = value 222 | else: 223 | result = [value] 224 | return result 225 | 226 | def main(args): 227 | #try: 228 | if not len(args) == 1: 229 | usage = 'usage: gen_html.py filename' 230 | sys.exit(usage) 231 | html_generator = HTMLGenerator() 232 | rslt = html_generator.generate_html(*args) 233 | print rslt 234 | #except Exception as e: 235 | # print >>sys.stderr, 'HTML generation of %s failed: %s' % (args, e) 236 | 237 | if __name__ == "__main__": 238 | main(sys.argv[1:]) 239 | -------------------------------------------------------------------------------- /util/gen_js_sdk.py: -------------------------------------------------------------------------------- 1 | import yaml, sys 2 | 3 | class ClientGenerator(object): 4 | 5 | def set_rapier_spec_from_filename(self, filename): 6 | with open(filename) as f: 7 | self.rapier_spec = yaml.load(f.read()) 8 | 9 | def client_from_rapier(self, filename= None): 10 | spec = self.rapier_spec 11 | if filename: 12 | self.set_rapier_spec_from_filename(filename) 13 | 14 | entities = spec.get('entities',{}) 15 | well_known_urls = [as_list(entity.get('wellKnownURLs')) for entity in entities.itervalues() if 'wellKnownURLs' in entity] 16 | well_known_urls = [url for urls in well_known_urls for url in urls] 17 | 18 | print '''var baseAPI = require('rapier') 19 | 20 | var exports = function() { 21 | 22 | function API() {} 23 | 24 | API.prototype = Object.create(baseAPI.BaseAPI.prototype); 25 | API.prototype.well_known_URLs = function() { 26 | return %s 27 | } 28 | API.prototype.resourceClass = function(type_name) { 29 | return type_name in classToKindMap ? classToKindMap[type_name] : baseAPI.BaseResource 30 | } 31 | 32 | var api = new API(); 33 | 34 | var api_function = function() { 35 | return api 36 | }''' % well_known_urls 37 | 38 | class_template = ''' 39 | function {0}(url, jso, etag) {{ 40 | baseAPI.BaseEntity.call(this, url, jso, etag) 41 | }} 42 | {0}.prototype = Object.create(baseAPI.{1}.prototype); 43 | {0}.prototype.constructor = {0}; 44 | {0}.prototype._className = '{0}'; 45 | {0}.prototype.api = api_function;''' 46 | 47 | for entity_name in entities: 48 | print class_template.format(entity_name, 'BaseEntity') 49 | 50 | print class_template.format('Collection', 'BaseCollection') 51 | 52 | map_entries = ["{0}: {0}".format((entity_name)) for entity_name in entities] + ["Collection: Collection"] 53 | 54 | print ''' 55 | var classToKindMap = { 56 | %s 57 | }''' % ',\n '.join(map_entries) 58 | 59 | print ''' 60 | return { 61 | %s 62 | } 63 | 64 | } 65 | 66 | module.exports = exports()''' % ',\n '.join(["api: api"] + map_entries) 67 | 68 | def as_list(value, separator = None): 69 | if isinstance(value, basestring): 70 | if separator: 71 | result = [item.strip() for item in value.split(separator)] 72 | else: 73 | result = value.split() 74 | else: 75 | if isinstance(value, (list, tuple)): 76 | result = value 77 | else: 78 | result = [value] 79 | return result 80 | 81 | def main(args): 82 | generator = ClientGenerator() 83 | generator.set_rapier_spec_from_filename(args[0]) 84 | generator.client_from_rapier() 85 | 86 | if __name__ == "__main__": 87 | main(sys.argv[1:]) -------------------------------------------------------------------------------- /util/gen_py_sdk.py: -------------------------------------------------------------------------------- 1 | import yaml, sys 2 | 3 | class ClientGenerator(object): 4 | 5 | def set_rapier_spec_from_filename(self, filename): 6 | with open(filename) as f: 7 | self.rapier_spec = yaml.load(f.read()) 8 | 9 | def client_from_rapier(self, filename= None): 10 | spec = self.rapier_spec 11 | if filename: 12 | self.set_rapier_spec_from_filename(filename) 13 | 14 | entities = spec.get('entities',{}) 15 | well_known_urls = [as_list(entity.get('wellKnownURLs')) for entity in entities.itervalues() if 'wellKnownURLs' in entity] 16 | well_known_urls = [url for urls in well_known_urls for url in urls] 17 | 18 | print ''' 19 | from rapier.py.base_api import BaseAPI, BaseResource, BaseEntity, BaseCollection 20 | 21 | class API(BaseAPI): 22 | def well_known_URLs(self): 23 | return %s 24 | def resource_class(self, type_name): 25 | return classToKindMap.get(type_name, BaseResource) 26 | 27 | api = API() 28 | 29 | class APIClass(object): 30 | def api(self): 31 | return api''' % well_known_urls 32 | 33 | for entity_name in entities: 34 | print ''' 35 | class %s(BaseEntity, APIClass): 36 | pass''' % entity_name 37 | 38 | print ''' 39 | class Collection(BaseCollection, APIClass): 40 | pass''' 41 | 42 | map_values = ["'{0}': {0}".format((entity_name)) for entity_name in entities] + ["'Collection': Collection"] 43 | 44 | print ''' 45 | classToKindMap = { 46 | %s 47 | }''' % ',\n '.join(map_values) 48 | 49 | def as_list(value, separator = None): 50 | if isinstance(value, basestring): 51 | if separator: 52 | result = [item.strip() for item in value.split(separator)] 53 | else: 54 | result = value.split() 55 | else: 56 | if isinstance(value, (list, tuple)): 57 | result = value 58 | else: 59 | result = [value] 60 | return result 61 | 62 | def main(args): 63 | generator = ClientGenerator() 64 | generator.set_rapier_spec_from_filename(args[0]) 65 | generator.client_from_rapier() 66 | 67 | if __name__ == "__main__": 68 | main(sys.argv[1:]) -------------------------------------------------------------------------------- /util/rapier.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import getopt 3 | from gen_openapispec import main as gen_oas_main 4 | from validate_rapier import main as validate_main 5 | from gen_js_sdk import main as gen_js_main 6 | from gen_py_sdk import main as gen_py_main 7 | 8 | def main(): 9 | usage = 'usage: rapier [-v, --validate] [-p, --gen-python] [-j, --gen-js] [-m, --yaml-merge] [-i, --include-impl] [-t --suppress-templates] filename' 10 | try: 11 | opts, args = getopt.getopt(sys.argv[1:], 'vpjmit', ['validate', 'gen-python', 'gen-js', 'yaml-merge', 'include-impl', 'suppress-templates']) 12 | except getopt.GetoptError as err: 13 | sys.exit(str(err) + '\n' + usage) 14 | if not len(args) == 1: 15 | sys.exit(usage) 16 | opts_keys = [k for k,v in opts] 17 | 18 | if '-v' in opts_keys or '--validate' in opts_keys: 19 | validate_main(args[0]) 20 | elif '-p' in opts_keys or '--gen-python' in opts_keys: 21 | gen_py_main(args[0]) 22 | elif '-j' in opts_keys or '--gen-js' in opts_keys: 23 | gen_py_main(args[0]) 24 | else: 25 | gen_oas_main(sys.argv) -------------------------------------------------------------------------------- /util/requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML==3.11 -------------------------------------------------------------------------------- /util/test/common.yaml: -------------------------------------------------------------------------------- 1 | title: Common entities used in multiple API specifications 2 | version: "0.1" 3 | entities: 4 | Apigee: 5 | description: > 6 | An Apigee is the root of the tree of resources in an Apigee Environment (that is, an Environment that contains Apigee's own software) 7 | readOnly: true 8 | wellKnownURLs: / 9 | type: object 10 | Page: 11 | allOf: 12 | - $ref: '#/entities/Resource' 13 | properties: 14 | kind: 15 | type: string 16 | enum: [Page] 17 | items: 18 | type: array 19 | items: 20 | type: object 21 | collection: 22 | type: string 23 | format: uri 24 | next_page: 25 | type: string 26 | format: uri 27 | relationship: 28 | entities: '#Page' 29 | readOnly: True 30 | previous_page: 31 | type: string 32 | format: uri 33 | relationship: 34 | entities: '#Page' 35 | readOnly: True 36 | queryParameters: 37 | properties: 38 | items: 39 | type: string 40 | type: array 41 | required: false 42 | limit: 43 | type: integer 44 | minimum: 1 45 | maximum: 1000 46 | required: false 47 | readOnly: true 48 | Collection: 49 | allOf: 50 | - $ref: '#/entities/Resource' 51 | properties: 52 | kind: 53 | type: string 54 | enum: [Collection] 55 | items: 56 | type: array 57 | items: 58 | type: object 59 | queryParameters: 60 | properties: 61 | items: 62 | type: string 63 | type: array 64 | required: false 65 | limit: 66 | type: integer 67 | minimum: 1 68 | maximum: 1000 69 | required: false 70 | orderBy: # required if limit is provided 71 | type: string 72 | required: false 73 | direction: 74 | type: string 75 | required: false 76 | enum: ['ascending', 'descending'] 77 | readOnly: true 78 | MultiValuedRelationship: 79 | readOnly: true 80 | oneOf: 81 | - $ref: '#/entities/Collection' 82 | - $ref: '#/entities/Page' 83 | Resource: 84 | type: object 85 | properties: 86 | _self: 87 | description: > 88 | The _self property defines which resource's property-value pairs are in the data. On create, if no value for _self is given, _self will be set to the URL of the resource being created. 89 | It is possible to set a different value on create—this is used to create a resource that contans information about a different resource than itself. _self specifies RDF's 'subject' 90 | concept. It is similar to the @id property of RDF/JSON. It is permissible, although unusual, to update the value of _self. 91 | type: string 92 | format: uri 93 | usage: c r u 94 | kind: 95 | description: > 96 | Specifies the type of the entity. We avoid the word type because it's a global function in some programing languages, and we don't want name collisions. Kind is usually set by the 97 | client and then echo'd by the server. In most applications, kind is immutable after create, although some applications may allow change. 98 | type: string 99 | usage: c r u 100 | PersistentResource: 101 | allOf: 102 | - $ref: '#/entities/Resource' 103 | properties: 104 | created: 105 | description: The date and time that the resource was created. An ISO-format string. 106 | type: string 107 | format: date-time 108 | readOnly: true 109 | creator: 110 | description: The identity of the creator of the resource. A URL. 111 | type: string 112 | format: uri 113 | readOnly: true 114 | modified: 115 | description: The date and time of the last modification to the resource. An ISO-format string. 116 | type: string 117 | format: date-time 118 | readOnly: true 119 | modifier: 120 | description: The identity of the user that made the last modification to the resource. 121 | type: string 122 | format: date-time 123 | readOnly: true -------------------------------------------------------------------------------- /util/test/dog-tracker.yaml: -------------------------------------------------------------------------------- 1 | title: DogTrackerAPI 2 | version: "0.1" 3 | entities: 4 | DogTracker: 5 | allOf: 6 | - $ref: '#/entities/PersistentResource' 7 | properties: 8 | dogs: 9 | description: URL of a Collection of Dogs 10 | format: uri 11 | type: string 12 | relationship: 13 | collectionResource: '#Collection' 14 | entities: '#Dog' 15 | multiplicity: 0:n 16 | people: 17 | description: URL of a Collection of Persons 18 | format: uri 19 | type: string 20 | relationship: 21 | collectionResource: '#Collection' 22 | entities: '#Person' 23 | multiplicity: 0:n 24 | wellKnownURLs: / 25 | queryPaths: [dogs, "dogs;{name}", people, "people;{name}", "dogs;{name}/owner", "people;{name}/dogs"] 26 | readOnly: true 27 | Dog: 28 | allOf: 29 | - $ref: '#/entities/PersistentResource' 30 | properties: 31 | name: 32 | type: string 33 | birth_date: 34 | type: string 35 | fur_color: 36 | type: string 37 | owner: 38 | format: uri 39 | type: string 40 | relationship: 41 | entities: '#Person' 42 | Person: 43 | allOf: 44 | - $ref: '#/entities/PersistentResource' 45 | properties: 46 | name: 47 | type: string 48 | birth-date: 49 | type: string 50 | dogs: 51 | format: uri 52 | type: string 53 | relationship: 54 | collectionResource: '#Collection' 55 | entities: '#Dog' 56 | multiplicity: 0:n 57 | Resource: 58 | type: object 59 | properties: 60 | _self: 61 | type: string 62 | readOnly: true 63 | kind: 64 | type: string 65 | PersistentResource: 66 | allOf: 67 | - $ref: '#/entities/Resource' 68 | properties: 69 | created: 70 | type: string 71 | format: date-time 72 | readOnly: true 73 | creator: 74 | type: string 75 | format: URL 76 | readOnly: true 77 | modified: 78 | type: string 79 | format: date-time 80 | readOnly: true 81 | modifier: 82 | type: string 83 | format: date-time 84 | readOnly: true 85 | Collection: 86 | allOf: 87 | - $ref: '#/entities/Resource' 88 | properties: 89 | kind: 90 | type: string 91 | enum: [Collection] 92 | items: 93 | type: array 94 | items: 95 | type: object 96 | readOnly: true 97 | -------------------------------------------------------------------------------- /util/test/gen_html/common.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |

    Common entities used in multiple API specifications

    11 |

    Id: "#"

    12 |

    Version: '0.1'

    13 |

    undescribed

    14 | 15 |

    16 |

    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 79 | 80 | 81 | 82 | 83 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 147 | 148 | 149 | 150 | 151 | 191 | 192 | 193 | 194 |
    Entity NameEntity Description
    ApigeeAn Apigee is the root of the tree of resources in an Apigee Environment (that is, an Environment that contains Apigee's own software) 28 |
    Page 34 |
    35 | Includes properties and other constraints from Resource 36 |
    37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
    Property NameProperty DescriptionProperty Typeusage
    kindstring r
    items[object] r
    collectionuri r
    next_pageurl of Page r
    previous_pageurl of Page r
    Collection 84 |
    85 | Includes properties and other constraints from Resource 86 |
    87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 |
    Property NameProperty DescriptionProperty Typeusage
    kindstring r
    items[object] r
    MultiValuedRelationship
    Resource 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 135 | 136 | 137 | 138 | 139 | 140 | 142 | 143 | 144 | 145 | 146 |
    Property NameProperty DescriptionProperty Typeusage
    _selfThe _self property defines which resource's property-value pairs are in the data. On create, if no value for _self is given, _self will be set to the URL of the resource being created. It is possible to set a different value on create—this is used to create a resource that contans information about a different resource than itself. _self specifies RDF's 'subject' concept. It is similar to the @id property of RDF/JSON. It is permissible, although unusual, to update the value of _self. 134 | uri c r u
    kindSpecifies the type of the entity. We avoid the word type because it's a global function in some programing languages, and we don't want name collisions. Kind is usually set by the client and then echo'd by the server. In most applications, kind is immutable after create, although some applications may allow change. 141 | string c r u
    PersistentResource 152 |
    153 | Includes properties and other constraints from Resource 154 |
    155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 |
    Property NameProperty DescriptionProperty Typeusage
    createdThe date and time that the resource was created. An ISO-format string.date-time r
    creatorThe identity of the creator of the resource. A URL.uri r
    modifiedThe date and time of the last modification to the resource. An ISO-format string.date-time r
    modifierThe identity of the user that made the last modification to the resource.date-time r
    195 |
    196 |
    197 | 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /util/test/gen_html/dog-tracker.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |

    DogTrackerAPI

    11 |

    Id: "#"

    12 |

    Version: '0.1'

    13 |

    undescribed

    14 | 15 |

    16 |

    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 55 | 56 | 57 | 58 | 59 | 99 | 100 | 101 | 102 | 103 | 137 | 138 | 139 | 140 | 141 | 166 | 167 | 168 | 169 | 170 | 210 | 211 | 212 | 213 | 214 | 242 | 243 | 244 | 245 |
    Entity NameEntity Description
    DogTracker 28 |
    29 | Includes properties and other constraints from PersistentResource 30 |
    31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
    Property NameProperty DescriptionProperty Typeusage
    dogsURL of a Collection of Dogs0:n (Dog) r
    peopleURL of a Collection of Persons0:n (Person) r
    Dog 60 |
    61 | Includes properties and other constraints from PersistentResource 62 |
    63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |
    Property NameProperty DescriptionProperty Typeusage
    namestring c r u
    birth_datestring c r u
    fur_colorstring c r u
    ownerurl of Person c r u
    Person 104 |
    105 | Includes properties and other constraints from PersistentResource 106 |
    107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 |
    Property NameProperty DescriptionProperty Typeusage
    namestring c r u
    birth-datestring c r u
    dogs0:n (Dog) c r u
    Resource 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 |
    Property NameProperty DescriptionProperty Typeusage
    _selfstring r
    kindstring c r u
    PersistentResource 171 |
    172 | Includes properties and other constraints from Resource 173 |
    174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 |
    Property NameProperty DescriptionProperty Typeusage
    createddate-time r
    creatorURL r
    modifieddate-time r
    modifierdate-time r
    Collection 215 |
    216 | Includes properties and other constraints from Resource 217 |
    218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 |
    Property NameProperty DescriptionProperty Typeusage
    kindstring r
    items[object] r
    246 |
    247 |
    248 | 249 | 250 | 251 | 252 | -------------------------------------------------------------------------------- /util/test/gen_html/hello-message.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |

    Hello World API

    11 |

    Id: "#"

    12 |

    Version: initial

    13 |

    undescribed

    14 | 15 |

    16 |

    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 46 | 47 | 48 | 49 |
    Entity NameEntity Description
    HelloMessage 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
    Property NameProperty DescriptionProperty Typeusage
    textstring r u
    50 |
    51 |
    52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /util/test/gen_html/links-todo-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |

    Todo List API

    11 |

    Id: "#"

    12 |

    Version: '0.1'

    13 |

    undescribed

    14 | 15 |

    16 |

    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 46 | 47 | 48 | 49 | 50 | 75 | 76 | 77 | 78 | 79 | 98 | 99 | 100 | 101 |
    Entity NameEntity Description
    TodoList 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
    Property NameProperty DescriptionProperty Typeusage
    links[] r
    Item 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
    Property NameProperty DescriptionProperty Typeusage
    _selfuri r
    duedate-time c r u
    Collection 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
    Property NameProperty DescriptionProperty Typeusage
    contents[Item] r
    102 |
    103 |
    104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /util/test/gen_html/run-common-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | # echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | # echo $ROOT_DIR 6 | ./util/gen_html.py util/test/common.yaml > util/test/gen_html/common.html 7 | -------------------------------------------------------------------------------- /util/test/gen_html/run-tests.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | # echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | # echo $ROOT_DIR 6 | ./util/gen_html.py util/test/hello-message.yaml > util/test/gen_html/hello-message.html 7 | ./util/gen_html.py util/test/todo-list-basic.yaml > util/test/gen_html/todo-list-basic.html 8 | ./util/gen_html.py util/test/todo-list-with-id.yaml > util/test/gen_html/todo-list-with-id.html 9 | ./util/gen_html.py util/test/todo-list-with-self.yaml > util/test/gen_html/todo-list-with-self.html 10 | ./util/gen_html.py util/test/todo-list-with-links.yaml > util/test/gen_html/links-todo-list.html 11 | ./util/gen_html.py util/test/dog-tracker.yaml > util/test/gen_html/dog-tracker.html 12 | ./util/gen_html.py util/test/property-tracker.yaml > util/test/gen_html/property-tracker.html 13 | ./util/gen_html.py util/test/spec-hub.yaml > util/test/gen_html/spec-hub.html 14 | ./util/gen_html.py util/test/spec-hub.yaml > util/test/gen_html/spec-hub-with-impl.html 15 | ./util/gen_html.py util/test/ssl.yaml > util/test/gen_html/ssl.html 16 | ./util/gen_html.py util/test/site-webmaster.yaml > util/test/gen_html/site-webmaster.html 17 | ./util/gen_html.py util/test/petstore.yaml > util/test/gen_html/petstore.html 18 | ./util/gen_html.py util/test/common.yaml > util/test/gen_html/common.html 19 | -------------------------------------------------------------------------------- /util/test/gen_html/site-webmaster.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |

    Site Webmaster API

    11 |

    Id: "#"

    12 |

    Version: initial

    13 |

    undescribed

    14 | 15 |

    16 |

    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 46 | 47 | 48 | 49 | 50 | 69 | 70 | 71 | 72 |
    Entity NameEntity Description
    Site 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
    Property NameProperty DescriptionProperty Typeusage
    webmasterurl of Person c r u
    Person 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
    Property NameProperty DescriptionProperty Typeusage
    namestring c r u
    73 |
    74 |
    75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /util/test/gen_html/todo-list-basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |

    Todo List API

    11 |

    Id: "#"

    12 |

    Version: initial

    13 |

    undescribed

    14 | 15 |

    16 |

    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 46 | 47 | 48 | 49 | 50 | 75 | 76 | 77 | 78 | 79 | 98 | 99 | 100 | 101 |
    Entity NameEntity Description
    TodoList 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
    Property NameProperty DescriptionProperty Typeusage
    todos0:n (Item) r
    Item 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
    Property NameProperty DescriptionProperty Typeusage
    descriptionstring c r u
    duedate-time c r u
    Collection 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
    Property NameProperty DescriptionProperty Typeusage
    contents[Item] r
    102 |
    103 |
    104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /util/test/gen_html/todo-list-with-id.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |

    Todo List API

    11 |

    Id: "#"

    12 |

    Version: initial

    13 |

    undescribed

    14 | 15 |

    16 |

    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 46 | 47 | 48 | 49 | 50 | 81 | 82 | 83 | 84 | 85 | 104 | 105 | 106 | 107 |
    Entity NameEntity Description
    TodoList 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
    Property NameProperty DescriptionProperty Typeusage
    todos0:n (Item) r
    Item 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
    Property NameProperty DescriptionProperty Typeusage
    idstring r
    descriptionstring c r u
    duedate-time c r u
    Collection 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
    Property NameProperty DescriptionProperty Typeusage
    contents[Item] r
    108 |
    109 |
    110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /util/test/gen_html/todo-list-with-self.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |

    Todo List API

    11 |

    Id: "#"

    12 |

    Version: initial

    13 |

    undescribed

    14 | 15 |

    16 |

    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 46 | 47 | 48 | 49 | 50 | 81 | 82 | 83 | 84 | 85 | 104 | 105 | 106 | 107 |
    Entity NameEntity Description
    TodoList 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
    Property NameProperty DescriptionProperty Typeusage
    todos0:n (Item) r
    Item 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
    Property NameProperty DescriptionProperty Typeusage
    _selfuri r
    descriptionstring c r u
    duedate-time c r u
    Collection 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
    Property NameProperty DescriptionProperty Typeusage
    contents[Item] r
    108 |
    109 |
    110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /util/test/gen_js_sdk/dog-tracker.js: -------------------------------------------------------------------------------- 1 | var baseAPI = require('rapier') 2 | 3 | var exports = function() { 4 | 5 | function API() {} 6 | 7 | API.prototype = Object.create(baseAPI.BaseAPI.prototype); 8 | API.prototype.wellKnownURLs = function() { 9 | return ['/'] 10 | } 11 | API.prototype.resourceClass = function(type_name) { 12 | return type_name in classToKindMap ? classToKindMap[type_name] : baseAPI.BaseResource 13 | } 14 | 15 | var api = new API(); 16 | 17 | var api_function = function() { 18 | return api 19 | } 20 | 21 | function Person(url, jso, etag) { 22 | baseAPI.BaseEntity.call(this, url, jso, etag) 23 | } 24 | Person.prototype = Object.create(baseAPI.BaseEntity.prototype); 25 | Person.prototype.constructor = Person; 26 | Person.prototype._className = 'Person'; 27 | Person.prototype.api = api_function; 28 | 29 | function DogTracker(url, jso, etag) { 30 | baseAPI.BaseEntity.call(this, url, jso, etag) 31 | } 32 | DogTracker.prototype = Object.create(baseAPI.BaseEntity.prototype); 33 | DogTracker.prototype.constructor = DogTracker; 34 | DogTracker.prototype._className = 'DogTracker'; 35 | DogTracker.prototype.api = api_function; 36 | 37 | function Dog(url, jso, etag) { 38 | baseAPI.BaseEntity.call(this, url, jso, etag) 39 | } 40 | Dog.prototype = Object.create(baseAPI.BaseEntity.prototype); 41 | Dog.prototype.constructor = Dog; 42 | Dog.prototype._className = 'Dog'; 43 | Dog.prototype.api = api_function; 44 | 45 | function Resource(url, jso, etag) { 46 | baseAPI.BaseEntity.call(this, url, jso, etag) 47 | } 48 | Resource.prototype = Object.create(baseAPI.BaseEntity.prototype); 49 | Resource.prototype.constructor = Resource; 50 | Resource.prototype._className = 'Resource'; 51 | Resource.prototype.api = api_function; 52 | 53 | function PersistentResource(url, jso, etag) { 54 | baseAPI.BaseEntity.call(this, url, jso, etag) 55 | } 56 | PersistentResource.prototype = Object.create(baseAPI.BaseEntity.prototype); 57 | PersistentResource.prototype.constructor = PersistentResource; 58 | PersistentResource.prototype._className = 'PersistentResource'; 59 | PersistentResource.prototype.api = api_function; 60 | 61 | function Collection(url, jso, etag) { 62 | baseAPI.BaseEntity.call(this, url, jso, etag) 63 | } 64 | Collection.prototype = Object.create(baseAPI.BaseCollection.prototype); 65 | Collection.prototype.constructor = Collection; 66 | Collection.prototype._className = 'Collection'; 67 | Collection.prototype.api = api_function; 68 | 69 | var classToKindMap = { 70 | Person: Person, 71 | DogTracker: DogTracker, 72 | Dog: Dog, 73 | Resource: Resource, 74 | PersistentResource: PersistentResource, 75 | Collection: Collection 76 | } 77 | 78 | return { 79 | api: api, 80 | Person: Person, 81 | DogTracker: DogTracker, 82 | Dog: Dog, 83 | Resource: Resource, 84 | PersistentResource: PersistentResource, 85 | Collection: Collection 86 | } 87 | 88 | } 89 | 90 | module.exports = exports() 91 | -------------------------------------------------------------------------------- /util/test/gen_js_sdk/hello-message.js: -------------------------------------------------------------------------------- 1 | var baseAPI = require('rapier') 2 | 3 | var exports = function() { 4 | 5 | function API() {} 6 | 7 | API.prototype = Object.create(baseAPI.BaseAPI.prototype); 8 | API.prototype.wellKnownURLs = function() { 9 | return ['/message'] 10 | } 11 | API.prototype.resourceClass = function(type_name) { 12 | return type_name in classToKindMap ? classToKindMap[type_name] : baseAPI.BaseResource 13 | } 14 | 15 | var api = new API(); 16 | 17 | var api_function = function() { 18 | return api 19 | } 20 | 21 | function HelloMessage(url, jso, etag) { 22 | baseAPI.BaseEntity.call(this, url, jso, etag) 23 | } 24 | HelloMessage.prototype = Object.create(baseAPI.BaseEntity.prototype); 25 | HelloMessage.prototype.constructor = HelloMessage; 26 | HelloMessage.prototype._className = 'HelloMessage'; 27 | HelloMessage.prototype.api = api_function; 28 | 29 | function Collection(url, jso, etag) { 30 | baseAPI.BaseEntity.call(this, url, jso, etag) 31 | } 32 | Collection.prototype = Object.create(baseAPI.BaseCollection.prototype); 33 | Collection.prototype.constructor = Collection; 34 | Collection.prototype._className = 'Collection'; 35 | Collection.prototype.api = api_function; 36 | 37 | var classToKindMap = { 38 | HelloMessage: HelloMessage, 39 | Collection: Collection 40 | } 41 | 42 | return { 43 | api: api, 44 | HelloMessage: HelloMessage, 45 | Collection: Collection 46 | } 47 | 48 | } 49 | 50 | module.exports = exports() 51 | -------------------------------------------------------------------------------- /util/test/gen_js_sdk/property-tracker.js: -------------------------------------------------------------------------------- 1 | var baseAPI = require('rapier') 2 | 3 | var exports = function() { 4 | 5 | function API() {} 6 | 7 | API.prototype = Object.create(baseAPI.BaseAPI.prototype); 8 | API.prototype.wellKnownURLs = function() { 9 | return ['/'] 10 | } 11 | API.prototype.resourceClass = function(type_name) { 12 | return type_name in classToKindMap ? classToKindMap[type_name] : baseAPI.BaseResource 13 | } 14 | 15 | var api = new API(); 16 | 17 | var api_function = function() { 18 | return api 19 | } 20 | 21 | function Person(url, jso, etag) { 22 | baseAPI.BaseEntity.call(this, url, jso, etag) 23 | } 24 | Person.prototype = Object.create(baseAPI.BaseEntity.prototype); 25 | Person.prototype.constructor = Person; 26 | Person.prototype._className = 'Person'; 27 | Person.prototype.api = api_function; 28 | 29 | function PropertyTracker(url, jso, etag) { 30 | baseAPI.BaseEntity.call(this, url, jso, etag) 31 | } 32 | PropertyTracker.prototype = Object.create(baseAPI.BaseEntity.prototype); 33 | PropertyTracker.prototype.constructor = PropertyTracker; 34 | PropertyTracker.prototype._className = 'PropertyTracker'; 35 | PropertyTracker.prototype.api = api_function; 36 | 37 | function Bicycle(url, jso, etag) { 38 | baseAPI.BaseEntity.call(this, url, jso, etag) 39 | } 40 | Bicycle.prototype = Object.create(baseAPI.BaseEntity.prototype); 41 | Bicycle.prototype.constructor = Bicycle; 42 | Bicycle.prototype._className = 'Bicycle'; 43 | Bicycle.prototype.api = api_function; 44 | 45 | function PersistentResource(url, jso, etag) { 46 | baseAPI.BaseEntity.call(this, url, jso, etag) 47 | } 48 | PersistentResource.prototype = Object.create(baseAPI.BaseEntity.prototype); 49 | PersistentResource.prototype.constructor = PersistentResource; 50 | PersistentResource.prototype._className = 'PersistentResource'; 51 | PersistentResource.prototype.api = api_function; 52 | 53 | function Resource(url, jso, etag) { 54 | baseAPI.BaseEntity.call(this, url, jso, etag) 55 | } 56 | Resource.prototype = Object.create(baseAPI.BaseEntity.prototype); 57 | Resource.prototype.constructor = Resource; 58 | Resource.prototype._className = 'Resource'; 59 | Resource.prototype.api = api_function; 60 | 61 | function Dog(url, jso, etag) { 62 | baseAPI.BaseEntity.call(this, url, jso, etag) 63 | } 64 | Dog.prototype = Object.create(baseAPI.BaseEntity.prototype); 65 | Dog.prototype.constructor = Dog; 66 | Dog.prototype._className = 'Dog'; 67 | Dog.prototype.api = api_function; 68 | 69 | function Institution(url, jso, etag) { 70 | baseAPI.BaseEntity.call(this, url, jso, etag) 71 | } 72 | Institution.prototype = Object.create(baseAPI.BaseEntity.prototype); 73 | Institution.prototype.constructor = Institution; 74 | Institution.prototype._className = 'Institution'; 75 | Institution.prototype.api = api_function; 76 | 77 | function Collection(url, jso, etag) { 78 | baseAPI.BaseEntity.call(this, url, jso, etag) 79 | } 80 | Collection.prototype = Object.create(baseAPI.BaseCollection.prototype); 81 | Collection.prototype.constructor = Collection; 82 | Collection.prototype._className = 'Collection'; 83 | Collection.prototype.api = api_function; 84 | 85 | var classToKindMap = { 86 | Person: Person, 87 | PropertyTracker: PropertyTracker, 88 | Bicycle: Bicycle, 89 | PersistentResource: PersistentResource, 90 | Resource: Resource, 91 | Dog: Dog, 92 | Institution: Institution, 93 | Collection: Collection 94 | } 95 | 96 | return { 97 | api: api, 98 | Person: Person, 99 | PropertyTracker: PropertyTracker, 100 | Bicycle: Bicycle, 101 | PersistentResource: PersistentResource, 102 | Resource: Resource, 103 | Dog: Dog, 104 | Institution: Institution, 105 | Collection: Collection 106 | } 107 | 108 | } 109 | 110 | module.exports = exports() 111 | -------------------------------------------------------------------------------- /util/test/gen_js_sdk/run-tests.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 3 | cd $ROOT_DIR 4 | 5 | python util/gen_js_sdk.py util/test/hello-message.yaml > util/test/gen_js_sdk/hello-message.js 6 | python util/gen_js_sdk.py util/test/todo-list.yaml > util/test/gen_js_sdk/todo-list.js 7 | python util/gen_js_sdk.py util/test/dog-tracker.yaml > util/test/gen_js_sdk/dog-tracker.js 8 | python util/gen_js_sdk.py util/test/property-tracker.yaml > util/test/gen_js_sdk/property-tracker.js -------------------------------------------------------------------------------- /util/test/gen_js_sdk/todo-list.js: -------------------------------------------------------------------------------- 1 | var baseAPI = require('rapier') 2 | 3 | var exports = function() { 4 | 5 | function API() {} 6 | 7 | API.prototype = Object.create(baseAPI.BaseAPI.prototype); 8 | API.prototype.wellKnownURLs = function() { 9 | return ['/to-dos'] 10 | } 11 | API.prototype.resourceClass = function(type_name) { 12 | return type_name in classToKindMap ? classToKindMap[type_name] : baseAPI.BaseResource 13 | } 14 | 15 | var api = new API(); 16 | 17 | var api_function = function() { 18 | return api 19 | } 20 | 21 | function TodoList(url, jso, etag) { 22 | baseAPI.BaseEntity.call(this, url, jso, etag) 23 | } 24 | TodoList.prototype = Object.create(baseAPI.BaseEntity.prototype); 25 | TodoList.prototype.constructor = TodoList; 26 | TodoList.prototype._className = 'TodoList'; 27 | TodoList.prototype.api = api_function; 28 | 29 | function Item(url, jso, etag) { 30 | baseAPI.BaseEntity.call(this, url, jso, etag) 31 | } 32 | Item.prototype = Object.create(baseAPI.BaseEntity.prototype); 33 | Item.prototype.constructor = Item; 34 | Item.prototype._className = 'Item'; 35 | Item.prototype.api = api_function; 36 | 37 | function Collection(url, jso, etag) { 38 | baseAPI.BaseEntity.call(this, url, jso, etag) 39 | } 40 | Collection.prototype = Object.create(baseAPI.BaseCollection.prototype); 41 | Collection.prototype.constructor = Collection; 42 | Collection.prototype._className = 'Collection'; 43 | Collection.prototype.api = api_function; 44 | 45 | var classToKindMap = { 46 | TodoList: TodoList, 47 | Item: Item, 48 | Collection: Collection 49 | } 50 | 51 | return { 52 | api: api, 53 | TodoList: TodoList, 54 | Item: Item, 55 | Collection: Collection 56 | } 57 | 58 | } 59 | 60 | module.exports = exports() 61 | -------------------------------------------------------------------------------- /util/test/gen_openapispec/hello-message.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Hello World API 4 | version: initial 5 | consumes: 6 | - application/json 7 | produces: 8 | - application/json 9 | - text/html 10 | x-interfaces: 11 | HelloMessage: 12 | get: 13 | description: Retrieve a HelloMessage 14 | parameters: 15 | - $ref: '#/parameters/Accept' 16 | responses: 17 | '200': 18 | description: successful 19 | headers: 20 | Content-Location: 21 | description: perma-link URL of resource 22 | type: string 23 | Content-Type: 24 | description: The media type of the returned body 25 | type: string 26 | ETag: 27 | description: this value must be echoed in the If-Match header of every 28 | PATCH or PUT 29 | type: string 30 | schema: 31 | $ref: '#/definitions/HelloMessage' 32 | '401': 33 | $ref: '#/responses/401' 34 | '403': 35 | $ref: '#/responses/403' 36 | '404': 37 | $ref: '#/responses/404' 38 | '406': 39 | $ref: '#/responses/406' 40 | default: 41 | $ref: '#/responses/default' 42 | patch: 43 | consumes: 44 | - application/merge-patch+json 45 | description: Update a HelloMessage entity 46 | parameters: 47 | - $ref: '#/parameters/If-Match' 48 | - description: The subset of properties of the HelloMessage being updated 49 | in: body 50 | name: body 51 | required: true 52 | schema: 53 | $ref: '#/definitions/HelloMessage' 54 | responses: 55 | '200': 56 | description: successful 57 | headers: 58 | Content-Location: 59 | description: perma-link URL of resource 60 | type: string 61 | Content-Type: 62 | description: The media type of the returned body 63 | type: string 64 | ETag: 65 | description: this value must be echoed in the If-Match header of every 66 | PATCH or PUT 67 | type: string 68 | schema: 69 | $ref: '#/definitions/HelloMessage' 70 | '400': 71 | $ref: '#/responses/400' 72 | '401': 73 | $ref: '#/responses/401' 74 | '403': 75 | $ref: '#/responses/403' 76 | '404': 77 | $ref: '#/responses/404' 78 | '406': 79 | $ref: '#/responses/406' 80 | '409': 81 | $ref: '#/responses/409' 82 | default: 83 | $ref: '#/responses/default' 84 | delete: 85 | description: Delete a HelloMessage 86 | responses: 87 | '200': 88 | description: successful 89 | headers: 90 | Content-Location: 91 | description: perma-link URL of resource 92 | type: string 93 | Content-Type: 94 | description: The media type of the returned body 95 | type: string 96 | ETag: 97 | description: this value must be echoed in the If-Match header of every 98 | PATCH or PUT 99 | type: string 100 | schema: 101 | $ref: '#/definitions/HelloMessage' 102 | '400': 103 | $ref: '#/responses/400' 104 | '401': 105 | $ref: '#/responses/401' 106 | '403': 107 | $ref: '#/responses/403' 108 | '404': 109 | $ref: '#/responses/404' 110 | '406': 111 | $ref: '#/responses/406' 112 | default: 113 | $ref: '#/responses/default' 114 | head: 115 | description: retrieve HEAD 116 | responses: 117 | '200': 118 | $ref: '#/responses/standard_200' 119 | '401': 120 | $ref: '#/responses/401' 121 | '403': 122 | $ref: '#/responses/403' 123 | '404': 124 | $ref: '#/responses/404' 125 | default: 126 | $ref: '#/responses/default' 127 | options: 128 | description: Retrieve OPTIONS 129 | parameters: 130 | - $ref: '#/parameters/Access-Control-Request-Method' 131 | - $ref: '#/parameters/Access-Control-Request-Headers' 132 | responses: 133 | '200': 134 | $ref: '#/responses/options_200' 135 | '401': 136 | $ref: '#/responses/401' 137 | '403': 138 | $ref: '#/responses/403' 139 | '404': 140 | $ref: '#/responses/404' 141 | default: 142 | $ref: '#/responses/default' 143 | x-id: HelloMessage 144 | x-templates: 145 | '{HelloMessage-URL}': 146 | $ref: '#/x-interfaces/HelloMessage' 147 | paths: 148 | /message: 149 | $ref: '#/x-templates/{HelloMessage-URL}' 150 | definitions: 151 | HelloMessage: 152 | properties: 153 | text: 154 | type: string 155 | x-interface: '#/x-interfaces/HelloMessage' 156 | parameters: 157 | Accept: 158 | description: specifies the requested media type - required 159 | in: header 160 | name: Accept 161 | required: true 162 | type: string 163 | Access-Control-Request-Headers: 164 | description: specifies the custom headers the client wishes to use 165 | in: header 166 | name: Access-Control-Request-Headers 167 | required: true 168 | type: string 169 | Access-Control-Request-Method: 170 | description: specifies the method the client wishes to use 171 | in: header 172 | name: Access-Control-Request-Method 173 | required: true 174 | type: string 175 | If-Match: 176 | description: specifies the last known ETag value of the resource being modified 177 | in: header 178 | name: If-Match 179 | required: true 180 | type: string 181 | responses: 182 | '400': 183 | description: Bad Request. Client request in error 184 | schema: {} 185 | '401': 186 | description: Unauthorized. Client authentication token missing from request 187 | schema: {} 188 | '403': 189 | description: Forbidden. Client authentication token does not permit this method 190 | on this resource 191 | schema: {} 192 | '404': 193 | description: Not Found. Resource not found 194 | schema: {} 195 | '406': 196 | description: Not Acceptable. Requested media type not available 197 | schema: {} 198 | '409': 199 | description: Conflict. Value provided in If-Match header does not match current 200 | ETag value of resource 201 | schema: {} 202 | default: 203 | description: 5xx errors and other stuff 204 | schema: {} 205 | options_200: 206 | description: successful 207 | headers: 208 | Access-Control-Allow-Headers: 209 | description: headers allowed 210 | type: string 211 | Access-Control-Allow-Methods: 212 | description: methods allowed 213 | type: string 214 | Access-Control-Allow-Origin: 215 | description: origins allowed 216 | type: string 217 | Access-Control-Max-Age: 218 | description: length of time response can be cached 219 | type: string 220 | standard_200: 221 | description: successful 222 | headers: 223 | Content-Location: 224 | description: perma-link URL of resource 225 | type: string 226 | Content-Type: 227 | description: The media type of the returned body 228 | type: string 229 | ETag: 230 | description: this value must be echoed in the If-Match header of every PATCH 231 | or PUT 232 | type: string 233 | 234 | -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-dog-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | # echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | # echo $ROOT_DIR 6 | ./util/gen_openapispec.py util/test/dog-tracker.yaml > util/test/gen_openapispec/dog-tracker.yaml -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-hello-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | #echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | #echo $ROOT_DIR 6 | ./util/gen_openapispec.py util/test/hello-message.yaml > util/test/gen_openapispec/hello-message.yaml -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-links-todo-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | #echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | #echo $ROOT_DIR 6 | ./util/gen_openapispec.py util/test/todo-list-with-links.yaml > util/test/gen_openapispec/links-todo-list.yaml -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-petstore-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | #echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | #echo $ROOT_DIR 6 | ./util/gen_openapispec.py util/test/petstore.yaml > util/test/gen_openapispec/petstore.yaml -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-property-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | # echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | # echo $ROOT_DIR 6 | ./util/gen_openapispec.py util/test/property-tracker.yaml > util/test/gen_openapispec/property-tracker.yaml -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-site-webmaster-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | #echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | #echo $ROOT_DIR 6 | ./util/gen_openapispec.py util/test/site-webmaster.yaml > util/test/gen_openapispec/site-webmaster.yaml -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-spechub-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | #echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | #echo $ROOT_DIR 6 | ./util/gen_openapispec.py util/test/spec-hub.yaml > util/test/gen_openapispec/spec-hub.yaml 7 | ./util/gen_openapispec.py -is util/test/spec-hub.yaml > util/test/gen_openapispec/spec-hub-with-impl.yaml -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-ssl-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | #echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | #echo $ROOT_DIR 6 | ./util/gen_openapispec.py -s util/test/ssl.yaml > util/test/gen_openapispec/ssl.yaml 7 | # ./util/gen_openapispec.py -is util/test/spec-hub.yaml > util/test/gen_openapispec/spec-hub-with-impl.yaml -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-tests.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | # echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | # echo $ROOT_DIR 6 | ./util/gen_openapispec.py util/test/hello-message.yaml > util/test/gen_openapispec/hello-message.yaml 7 | ./util/gen_openapispec.py util/test/todo-list-basic.yaml > util/test/gen_openapispec/todo-list-basic.yaml 8 | ./util/gen_openapispec.py util/test/todo-list-with-id.yaml > util/test/gen_openapispec/todo-list-with-id.yaml 9 | ./util/gen_openapispec.py util/test/todo-list-with-self.yaml > util/test/gen_openapispec/todo-list-with-self.yaml 10 | ./util/gen_openapispec.py util/test/todo-list-with-links.yaml > util/test/gen_openapispec/links-todo-list.yaml 11 | ./util/gen_openapispec.py util/test/dog-tracker.yaml > util/test/gen_openapispec/dog-tracker.yaml 12 | ./util/gen_openapispec.py util/test/property-tracker.yaml > util/test/gen_openapispec/property-tracker.yaml 13 | ./util/gen_openapispec.py util/test/spec-hub.yaml > util/test/gen_openapispec/spec-hub.yaml 14 | ./util/gen_openapispec.py -i util/test/spec-hub.yaml > util/test/gen_openapispec/spec-hub-with-impl.yaml 15 | ./util/gen_openapispec.py util/test/ssl.yaml > util/test/gen_openapispec/ssl.yaml 16 | ./util/gen_openapispec.py util/test/site-webmaster.yaml > util/test/gen_openapispec/site-webmaster.yaml 17 | ./util/gen_openapispec.py util/test/petstore.yaml > util/test/gen_openapispec/petstore.yaml 18 | ./util/gen_openapispec.py util/test/use-common.yaml > util/test/gen_openapispec/use-common.yaml 19 | -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-todo-basic-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | #echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | #echo $ROOT_DIR 6 | ./util/gen_openapispec.py util/test/todo-list-basic.yaml > util/test/gen_openapispec/todo-list-basic.yaml -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-todo-with-id-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | #echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | #echo $ROOT_DIR 6 | ./util/gen_openapispec.py util/test/todo-list-with-id.yaml > util/test/gen_openapispec/todo-list-with-id.yaml -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-todo-with-links-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | #echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | #echo $ROOT_DIR 6 | ./util/gen_openapispec.py util/test/todo-list-with-links.yaml > util/test/gen_openapispec/todo-list-with-links.yaml -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-todo-with-self-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | #echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | #echo $ROOT_DIR 6 | ./util/gen_openapispec.py util/test/todo-list-with-self.yaml > util/test/gen_openapispec/todo-list-with-self.yaml -------------------------------------------------------------------------------- /util/test/gen_openapispec/run-use-common-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | #echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | #echo $ROOT_DIR 6 | ./util/gen_openapispec.py util/test/use-common.yaml > util/test/gen_openapispec/use-common.yaml -------------------------------------------------------------------------------- /util/test/gen_openapispec/site-webmaster.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Site Webmaster API 4 | version: initial 5 | consumes: 6 | - application/json 7 | produces: 8 | - application/json 9 | - text/html 10 | x-interfaces: 11 | Person: 12 | get: 13 | description: Retrieve a Person 14 | parameters: 15 | - $ref: '#/parameters/Accept' 16 | responses: 17 | '200': 18 | description: successful 19 | headers: 20 | Content-Location: 21 | description: perma-link URL of resource 22 | type: string 23 | Content-Type: 24 | description: The media type of the returned body 25 | type: string 26 | ETag: 27 | description: this value must be echoed in the If-Match header of every 28 | PATCH or PUT 29 | type: string 30 | schema: 31 | $ref: '#/definitions/Person' 32 | '401': 33 | $ref: '#/responses/401' 34 | '403': 35 | $ref: '#/responses/403' 36 | '404': 37 | $ref: '#/responses/404' 38 | '406': 39 | $ref: '#/responses/406' 40 | default: 41 | $ref: '#/responses/default' 42 | patch: 43 | consumes: 44 | - application/merge-patch+json 45 | description: Update a Person entity 46 | parameters: 47 | - $ref: '#/parameters/If-Match' 48 | - description: The subset of properties of the Person being updated 49 | in: body 50 | name: body 51 | required: true 52 | schema: 53 | $ref: '#/definitions/Person' 54 | responses: 55 | '200': 56 | description: successful 57 | headers: 58 | Content-Location: 59 | description: perma-link URL of resource 60 | type: string 61 | Content-Type: 62 | description: The media type of the returned body 63 | type: string 64 | ETag: 65 | description: this value must be echoed in the If-Match header of every 66 | PATCH or PUT 67 | type: string 68 | schema: 69 | $ref: '#/definitions/Person' 70 | '400': 71 | $ref: '#/responses/400' 72 | '401': 73 | $ref: '#/responses/401' 74 | '403': 75 | $ref: '#/responses/403' 76 | '404': 77 | $ref: '#/responses/404' 78 | '406': 79 | $ref: '#/responses/406' 80 | '409': 81 | $ref: '#/responses/409' 82 | default: 83 | $ref: '#/responses/default' 84 | delete: 85 | description: Delete a Person 86 | responses: 87 | '200': 88 | description: successful 89 | headers: 90 | Content-Location: 91 | description: perma-link URL of resource 92 | type: string 93 | Content-Type: 94 | description: The media type of the returned body 95 | type: string 96 | ETag: 97 | description: this value must be echoed in the If-Match header of every 98 | PATCH or PUT 99 | type: string 100 | schema: 101 | $ref: '#/definitions/Person' 102 | '400': 103 | $ref: '#/responses/400' 104 | '401': 105 | $ref: '#/responses/401' 106 | '403': 107 | $ref: '#/responses/403' 108 | '404': 109 | $ref: '#/responses/404' 110 | '406': 111 | $ref: '#/responses/406' 112 | default: 113 | $ref: '#/responses/default' 114 | head: 115 | description: retrieve HEAD 116 | responses: 117 | '200': 118 | $ref: '#/responses/standard_200' 119 | '401': 120 | $ref: '#/responses/401' 121 | '403': 122 | $ref: '#/responses/403' 123 | '404': 124 | $ref: '#/responses/404' 125 | default: 126 | $ref: '#/responses/default' 127 | options: 128 | description: Retrieve OPTIONS 129 | parameters: 130 | - $ref: '#/parameters/Access-Control-Request-Method' 131 | - $ref: '#/parameters/Access-Control-Request-Headers' 132 | responses: 133 | '200': 134 | $ref: '#/responses/options_200' 135 | '401': 136 | $ref: '#/responses/401' 137 | '403': 138 | $ref: '#/responses/403' 139 | '404': 140 | $ref: '#/responses/404' 141 | default: 142 | $ref: '#/responses/default' 143 | x-id: Person 144 | Site: 145 | get: 146 | description: Retrieve a Site 147 | parameters: 148 | - $ref: '#/parameters/Accept' 149 | responses: 150 | '200': 151 | description: successful 152 | headers: 153 | Content-Location: 154 | description: perma-link URL of resource 155 | type: string 156 | Content-Type: 157 | description: The media type of the returned body 158 | type: string 159 | ETag: 160 | description: this value must be echoed in the If-Match header of every 161 | PATCH or PUT 162 | type: string 163 | schema: 164 | $ref: '#/definitions/Site' 165 | '401': 166 | $ref: '#/responses/401' 167 | '403': 168 | $ref: '#/responses/403' 169 | '404': 170 | $ref: '#/responses/404' 171 | '406': 172 | $ref: '#/responses/406' 173 | default: 174 | $ref: '#/responses/default' 175 | patch: 176 | consumes: 177 | - application/merge-patch+json 178 | description: Update a Site entity 179 | parameters: 180 | - $ref: '#/parameters/If-Match' 181 | - description: The subset of properties of the Site being updated 182 | in: body 183 | name: body 184 | required: true 185 | schema: 186 | $ref: '#/definitions/Site' 187 | responses: 188 | '200': 189 | description: successful 190 | headers: 191 | Content-Location: 192 | description: perma-link URL of resource 193 | type: string 194 | Content-Type: 195 | description: The media type of the returned body 196 | type: string 197 | ETag: 198 | description: this value must be echoed in the If-Match header of every 199 | PATCH or PUT 200 | type: string 201 | schema: 202 | $ref: '#/definitions/Site' 203 | '400': 204 | $ref: '#/responses/400' 205 | '401': 206 | $ref: '#/responses/401' 207 | '403': 208 | $ref: '#/responses/403' 209 | '404': 210 | $ref: '#/responses/404' 211 | '406': 212 | $ref: '#/responses/406' 213 | '409': 214 | $ref: '#/responses/409' 215 | default: 216 | $ref: '#/responses/default' 217 | delete: 218 | description: Delete a Site 219 | responses: 220 | '200': 221 | description: successful 222 | headers: 223 | Content-Location: 224 | description: perma-link URL of resource 225 | type: string 226 | Content-Type: 227 | description: The media type of the returned body 228 | type: string 229 | ETag: 230 | description: this value must be echoed in the If-Match header of every 231 | PATCH or PUT 232 | type: string 233 | schema: 234 | $ref: '#/definitions/Site' 235 | '400': 236 | $ref: '#/responses/400' 237 | '401': 238 | $ref: '#/responses/401' 239 | '403': 240 | $ref: '#/responses/403' 241 | '404': 242 | $ref: '#/responses/404' 243 | '406': 244 | $ref: '#/responses/406' 245 | default: 246 | $ref: '#/responses/default' 247 | head: 248 | description: retrieve HEAD 249 | responses: 250 | '200': 251 | $ref: '#/responses/standard_200' 252 | '401': 253 | $ref: '#/responses/401' 254 | '403': 255 | $ref: '#/responses/403' 256 | '404': 257 | $ref: '#/responses/404' 258 | default: 259 | $ref: '#/responses/default' 260 | options: 261 | description: Retrieve OPTIONS 262 | parameters: 263 | - $ref: '#/parameters/Access-Control-Request-Method' 264 | - $ref: '#/parameters/Access-Control-Request-Headers' 265 | responses: 266 | '200': 267 | $ref: '#/responses/options_200' 268 | '401': 269 | $ref: '#/responses/401' 270 | '403': 271 | $ref: '#/responses/403' 272 | '404': 273 | $ref: '#/responses/404' 274 | default: 275 | $ref: '#/responses/default' 276 | x-id: Site 277 | x-templates: 278 | '{Site-URL}': 279 | $ref: '#/x-interfaces/Site' 280 | paths: 281 | /: 282 | $ref: '#/x-templates/{Site-URL}' 283 | definitions: 284 | Site: 285 | properties: 286 | webmaster: 287 | type: string 288 | format: uri 289 | x-interface: '#/x-interfaces/Person' 290 | x-interface: '#/x-interfaces/Site' 291 | Person: 292 | properties: 293 | name: 294 | type: string 295 | x-interface: '#/x-interfaces/Person' 296 | parameters: 297 | Accept: 298 | description: specifies the requested media type - required 299 | in: header 300 | name: Accept 301 | required: true 302 | type: string 303 | Access-Control-Request-Headers: 304 | description: specifies the custom headers the client wishes to use 305 | in: header 306 | name: Access-Control-Request-Headers 307 | required: true 308 | type: string 309 | Access-Control-Request-Method: 310 | description: specifies the method the client wishes to use 311 | in: header 312 | name: Access-Control-Request-Method 313 | required: true 314 | type: string 315 | If-Match: 316 | description: specifies the last known ETag value of the resource being modified 317 | in: header 318 | name: If-Match 319 | required: true 320 | type: string 321 | responses: 322 | '400': 323 | description: Bad Request. Client request in error 324 | schema: {} 325 | '401': 326 | description: Unauthorized. Client authentication token missing from request 327 | schema: {} 328 | '403': 329 | description: Forbidden. Client authentication token does not permit this method 330 | on this resource 331 | schema: {} 332 | '404': 333 | description: Not Found. Resource not found 334 | schema: {} 335 | '406': 336 | description: Not Acceptable. Requested media type not available 337 | schema: {} 338 | '409': 339 | description: Conflict. Value provided in If-Match header does not match current 340 | ETag value of resource 341 | schema: {} 342 | default: 343 | description: 5xx errors and other stuff 344 | schema: {} 345 | options_200: 346 | description: successful 347 | headers: 348 | Access-Control-Allow-Headers: 349 | description: headers allowed 350 | type: string 351 | Access-Control-Allow-Methods: 352 | description: methods allowed 353 | type: string 354 | Access-Control-Allow-Origin: 355 | description: origins allowed 356 | type: string 357 | Access-Control-Max-Age: 358 | description: length of time response can be cached 359 | type: string 360 | standard_200: 361 | description: successful 362 | headers: 363 | Content-Location: 364 | description: perma-link URL of resource 365 | type: string 366 | Content-Type: 367 | description: The media type of the returned body 368 | type: string 369 | ETag: 370 | description: this value must be echoed in the If-Match header of every PATCH 371 | or PUT 372 | type: string 373 | 374 | -------------------------------------------------------------------------------- /util/test/gen_openapispec/todo-list-basic.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Todo List API 4 | version: initial 5 | consumes: 6 | - application/json 7 | produces: 8 | - application/json 9 | - text/html 10 | x-interfaces: 11 | Item: 12 | get: 13 | description: Retrieve an Item 14 | parameters: 15 | - $ref: '#/parameters/Accept' 16 | responses: 17 | '200': 18 | description: successful 19 | headers: 20 | Content-Location: 21 | description: perma-link URL of resource 22 | type: string 23 | Content-Type: 24 | description: The media type of the returned body 25 | type: string 26 | ETag: 27 | description: this value must be echoed in the If-Match header of every 28 | PATCH or PUT 29 | type: string 30 | schema: 31 | $ref: '#/definitions/Item' 32 | '401': 33 | $ref: '#/responses/401' 34 | '403': 35 | $ref: '#/responses/403' 36 | '404': 37 | $ref: '#/responses/404' 38 | '406': 39 | $ref: '#/responses/406' 40 | default: 41 | $ref: '#/responses/default' 42 | patch: 43 | consumes: 44 | - application/merge-patch+json 45 | description: Update an Item entity 46 | parameters: 47 | - $ref: '#/parameters/If-Match' 48 | - description: The subset of properties of the Item being updated 49 | in: body 50 | name: body 51 | required: true 52 | schema: 53 | $ref: '#/definitions/Item' 54 | responses: 55 | '200': 56 | description: successful 57 | headers: 58 | Content-Location: 59 | description: perma-link URL of resource 60 | type: string 61 | Content-Type: 62 | description: The media type of the returned body 63 | type: string 64 | ETag: 65 | description: this value must be echoed in the If-Match header of every 66 | PATCH or PUT 67 | type: string 68 | schema: 69 | $ref: '#/definitions/Item' 70 | '400': 71 | $ref: '#/responses/400' 72 | '401': 73 | $ref: '#/responses/401' 74 | '403': 75 | $ref: '#/responses/403' 76 | '404': 77 | $ref: '#/responses/404' 78 | '406': 79 | $ref: '#/responses/406' 80 | '409': 81 | $ref: '#/responses/409' 82 | default: 83 | $ref: '#/responses/default' 84 | delete: 85 | description: Delete an Item 86 | responses: 87 | '200': 88 | description: successful 89 | headers: 90 | Content-Location: 91 | description: perma-link URL of resource 92 | type: string 93 | Content-Type: 94 | description: The media type of the returned body 95 | type: string 96 | ETag: 97 | description: this value must be echoed in the If-Match header of every 98 | PATCH or PUT 99 | type: string 100 | schema: 101 | $ref: '#/definitions/Item' 102 | '400': 103 | $ref: '#/responses/400' 104 | '401': 105 | $ref: '#/responses/401' 106 | '403': 107 | $ref: '#/responses/403' 108 | '404': 109 | $ref: '#/responses/404' 110 | '406': 111 | $ref: '#/responses/406' 112 | default: 113 | $ref: '#/responses/default' 114 | head: 115 | description: retrieve HEAD 116 | responses: 117 | '200': 118 | $ref: '#/responses/standard_200' 119 | '401': 120 | $ref: '#/responses/401' 121 | '403': 122 | $ref: '#/responses/403' 123 | '404': 124 | $ref: '#/responses/404' 125 | default: 126 | $ref: '#/responses/default' 127 | options: 128 | description: Retrieve OPTIONS 129 | parameters: 130 | - $ref: '#/parameters/Access-Control-Request-Method' 131 | - $ref: '#/parameters/Access-Control-Request-Headers' 132 | responses: 133 | '200': 134 | $ref: '#/responses/options_200' 135 | '401': 136 | $ref: '#/responses/401' 137 | '403': 138 | $ref: '#/responses/403' 139 | '404': 140 | $ref: '#/responses/404' 141 | default: 142 | $ref: '#/responses/default' 143 | x-id: Item 144 | TodoList: 145 | get: 146 | description: Retrieve a TodoList 147 | parameters: 148 | - $ref: '#/parameters/Accept' 149 | responses: 150 | '200': 151 | description: successful 152 | headers: 153 | Content-Location: 154 | description: perma-link URL of resource 155 | type: string 156 | Content-Type: 157 | description: The media type of the returned body 158 | type: string 159 | ETag: 160 | description: this value must be echoed in the If-Match header of every 161 | PATCH or PUT 162 | type: string 163 | schema: 164 | $ref: '#/definitions/TodoList' 165 | '401': 166 | $ref: '#/responses/401' 167 | '403': 168 | $ref: '#/responses/403' 169 | '404': 170 | $ref: '#/responses/404' 171 | '406': 172 | $ref: '#/responses/406' 173 | default: 174 | $ref: '#/responses/default' 175 | head: 176 | description: retrieve HEAD 177 | responses: 178 | '200': 179 | $ref: '#/responses/standard_200' 180 | '401': 181 | $ref: '#/responses/401' 182 | '403': 183 | $ref: '#/responses/403' 184 | '404': 185 | $ref: '#/responses/404' 186 | default: 187 | $ref: '#/responses/default' 188 | options: 189 | description: Retrieve OPTIONS 190 | parameters: 191 | - $ref: '#/parameters/Access-Control-Request-Method' 192 | - $ref: '#/parameters/Access-Control-Request-Headers' 193 | responses: 194 | '200': 195 | $ref: '#/responses/options_200' 196 | '401': 197 | $ref: '#/responses/401' 198 | '403': 199 | $ref: '#/responses/403' 200 | '404': 201 | $ref: '#/responses/404' 202 | default: 203 | $ref: '#/responses/default' 204 | x-id: TodoList 205 | TodoList.todos: 206 | get: 207 | responses: 208 | '200': 209 | description: description 210 | headers: 211 | Content-Location: 212 | description: perma-link URL of collection 213 | type: string 214 | Content-Type: 215 | description: The media type of the returned body 216 | type: string 217 | schema: 218 | $ref: '#/definitions/Collection' 219 | '303': 220 | $ref: '#/responses/303' 221 | '401': 222 | $ref: '#/responses/401' 223 | '403': 224 | $ref: '#/responses/403' 225 | '404': 226 | $ref: '#/responses/404' 227 | '406': 228 | $ref: '#/responses/406' 229 | default: 230 | $ref: '#/responses/default' 231 | post: 232 | description: Create a new Item 233 | parameters: 234 | - description: The representation of the new Item being created 235 | in: body 236 | name: body 237 | required: true 238 | schema: 239 | $ref: '#/definitions/Item' 240 | - description: The media type of the body 241 | in: header 242 | name: Content-Type 243 | required: true 244 | type: string 245 | responses: 246 | '201': 247 | description: Created new Item 248 | headers: 249 | Content-Type: 250 | description: The media type of the returned body 251 | type: string 252 | ETag: 253 | description: Value of ETag required for subsequent updates 254 | type: string 255 | Location: 256 | description: perma-link URL of newly-created Item 257 | type: string 258 | schema: 259 | $ref: '#/definitions/Item' 260 | '400': 261 | $ref: '#/responses/400' 262 | '401': 263 | $ref: '#/responses/401' 264 | '403': 265 | $ref: '#/responses/403' 266 | '404': 267 | $ref: '#/responses/404' 268 | '406': 269 | $ref: '#/responses/406' 270 | default: 271 | $ref: '#/responses/default' 272 | head: 273 | description: Retrieve HEAD 274 | responses: 275 | '200': 276 | $ref: '#/responses/standard_200' 277 | '401': 278 | $ref: '#/responses/401' 279 | '403': 280 | $ref: '#/responses/403' 281 | '404': 282 | $ref: '#/responses/404' 283 | default: 284 | $ref: '#/responses/default' 285 | options: 286 | description: Retrieve OPTIONS 287 | parameters: 288 | - $ref: '#/parameters/Access-Control-Request-Method' 289 | - $ref: '#/parameters/Access-Control-Request-Headers' 290 | responses: 291 | '200': 292 | $ref: '#/responses/options_200' 293 | '401': 294 | $ref: '#/responses/401' 295 | '403': 296 | $ref: '#/responses/403' 297 | '404': 298 | $ref: '#/responses/404' 299 | default: 300 | $ref: '#/responses/default' 301 | x-id: TodoList.todos 302 | x-templates: 303 | '{TodoList-URL}': 304 | $ref: '#/x-interfaces/TodoList' 305 | '{TodoList-URL}/todos': 306 | $ref: '#/x-interfaces/TodoList.todos' 307 | paths: 308 | /: 309 | $ref: '#/x-templates/{TodoList-URL}' 310 | /todos: 311 | $ref: '#/x-templates/{TodoList-URL}~1todos' 312 | definitions: 313 | TodoList: 314 | readOnly: true 315 | properties: 316 | todos: 317 | type: string 318 | format: uri 319 | readOnly: true 320 | x-interface: '#/x-interfaces/TodoList.todos' 321 | x-interface: '#/x-interfaces/TodoList' 322 | Item: 323 | properties: 324 | description: 325 | type: string 326 | due: 327 | type: string 328 | format: date-time 329 | x-interface: '#/x-interfaces/Item' 330 | Collection: 331 | readOnly: true 332 | properties: 333 | contents: 334 | type: array 335 | items: 336 | $ref: '#/definitions/Item' 337 | parameters: 338 | Accept: 339 | description: specifies the requested media type - required 340 | in: header 341 | name: Accept 342 | required: true 343 | type: string 344 | Access-Control-Request-Headers: 345 | description: specifies the custom headers the client wishes to use 346 | in: header 347 | name: Access-Control-Request-Headers 348 | required: true 349 | type: string 350 | Access-Control-Request-Method: 351 | description: specifies the method the client wishes to use 352 | in: header 353 | name: Access-Control-Request-Method 354 | required: true 355 | type: string 356 | If-Match: 357 | description: specifies the last known ETag value of the resource being modified 358 | in: header 359 | name: If-Match 360 | required: true 361 | type: string 362 | responses: 363 | '303': 364 | description: See other. Server is redirecting client to a different resource 365 | headers: 366 | Location: 367 | description: URL of other resource 368 | type: string 369 | '400': 370 | description: Bad Request. Client request in error 371 | schema: {} 372 | '401': 373 | description: Unauthorized. Client authentication token missing from request 374 | schema: {} 375 | '403': 376 | description: Forbidden. Client authentication token does not permit this method 377 | on this resource 378 | schema: {} 379 | '404': 380 | description: Not Found. Resource not found 381 | schema: {} 382 | '406': 383 | description: Not Acceptable. Requested media type not available 384 | schema: {} 385 | '409': 386 | description: Conflict. Value provided in If-Match header does not match current 387 | ETag value of resource 388 | schema: {} 389 | default: 390 | description: 5xx errors and other stuff 391 | schema: {} 392 | options_200: 393 | description: successful 394 | headers: 395 | Access-Control-Allow-Headers: 396 | description: headers allowed 397 | type: string 398 | Access-Control-Allow-Methods: 399 | description: methods allowed 400 | type: string 401 | Access-Control-Allow-Origin: 402 | description: origins allowed 403 | type: string 404 | Access-Control-Max-Age: 405 | description: length of time response can be cached 406 | type: string 407 | standard_200: 408 | description: successful 409 | headers: 410 | Content-Location: 411 | description: perma-link URL of resource 412 | type: string 413 | Content-Type: 414 | description: The media type of the returned body 415 | type: string 416 | ETag: 417 | description: this value must be echoed in the If-Match header of every PATCH 418 | or PUT 419 | type: string 420 | 421 | -------------------------------------------------------------------------------- /util/test/gen_openapispec/todo-list-with-self.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: Todo List API 4 | version: initial 5 | consumes: 6 | - application/json 7 | produces: 8 | - application/json 9 | - text/html 10 | x-interfaces: 11 | Item: 12 | get: 13 | description: Retrieve an Item 14 | parameters: 15 | - $ref: '#/parameters/Accept' 16 | responses: 17 | '200': 18 | description: successful 19 | headers: 20 | Content-Location: 21 | description: perma-link URL of resource 22 | type: string 23 | Content-Type: 24 | description: The media type of the returned body 25 | type: string 26 | ETag: 27 | description: this value must be echoed in the If-Match header of every 28 | PATCH or PUT 29 | type: string 30 | schema: 31 | $ref: '#/definitions/Item' 32 | '401': 33 | $ref: '#/responses/401' 34 | '403': 35 | $ref: '#/responses/403' 36 | '404': 37 | $ref: '#/responses/404' 38 | '406': 39 | $ref: '#/responses/406' 40 | default: 41 | $ref: '#/responses/default' 42 | patch: 43 | consumes: 44 | - application/merge-patch+json 45 | description: Update an Item entity 46 | parameters: 47 | - $ref: '#/parameters/If-Match' 48 | - description: The subset of properties of the Item being updated 49 | in: body 50 | name: body 51 | required: true 52 | schema: 53 | $ref: '#/definitions/Item' 54 | responses: 55 | '200': 56 | description: successful 57 | headers: 58 | Content-Location: 59 | description: perma-link URL of resource 60 | type: string 61 | Content-Type: 62 | description: The media type of the returned body 63 | type: string 64 | ETag: 65 | description: this value must be echoed in the If-Match header of every 66 | PATCH or PUT 67 | type: string 68 | schema: 69 | $ref: '#/definitions/Item' 70 | '400': 71 | $ref: '#/responses/400' 72 | '401': 73 | $ref: '#/responses/401' 74 | '403': 75 | $ref: '#/responses/403' 76 | '404': 77 | $ref: '#/responses/404' 78 | '406': 79 | $ref: '#/responses/406' 80 | '409': 81 | $ref: '#/responses/409' 82 | default: 83 | $ref: '#/responses/default' 84 | delete: 85 | description: Delete an Item 86 | responses: 87 | '200': 88 | description: successful 89 | headers: 90 | Content-Location: 91 | description: perma-link URL of resource 92 | type: string 93 | Content-Type: 94 | description: The media type of the returned body 95 | type: string 96 | ETag: 97 | description: this value must be echoed in the If-Match header of every 98 | PATCH or PUT 99 | type: string 100 | schema: 101 | $ref: '#/definitions/Item' 102 | '400': 103 | $ref: '#/responses/400' 104 | '401': 105 | $ref: '#/responses/401' 106 | '403': 107 | $ref: '#/responses/403' 108 | '404': 109 | $ref: '#/responses/404' 110 | '406': 111 | $ref: '#/responses/406' 112 | default: 113 | $ref: '#/responses/default' 114 | head: 115 | description: retrieve HEAD 116 | responses: 117 | '200': 118 | $ref: '#/responses/standard_200' 119 | '401': 120 | $ref: '#/responses/401' 121 | '403': 122 | $ref: '#/responses/403' 123 | '404': 124 | $ref: '#/responses/404' 125 | default: 126 | $ref: '#/responses/default' 127 | options: 128 | description: Retrieve OPTIONS 129 | parameters: 130 | - $ref: '#/parameters/Access-Control-Request-Method' 131 | - $ref: '#/parameters/Access-Control-Request-Headers' 132 | responses: 133 | '200': 134 | $ref: '#/responses/options_200' 135 | '401': 136 | $ref: '#/responses/401' 137 | '403': 138 | $ref: '#/responses/403' 139 | '404': 140 | $ref: '#/responses/404' 141 | default: 142 | $ref: '#/responses/default' 143 | x-id: Item 144 | TodoList: 145 | get: 146 | description: Retrieve a TodoList 147 | parameters: 148 | - $ref: '#/parameters/Accept' 149 | responses: 150 | '200': 151 | description: successful 152 | headers: 153 | Content-Location: 154 | description: perma-link URL of resource 155 | type: string 156 | Content-Type: 157 | description: The media type of the returned body 158 | type: string 159 | ETag: 160 | description: this value must be echoed in the If-Match header of every 161 | PATCH or PUT 162 | type: string 163 | schema: 164 | $ref: '#/definitions/TodoList' 165 | '401': 166 | $ref: '#/responses/401' 167 | '403': 168 | $ref: '#/responses/403' 169 | '404': 170 | $ref: '#/responses/404' 171 | '406': 172 | $ref: '#/responses/406' 173 | default: 174 | $ref: '#/responses/default' 175 | head: 176 | description: retrieve HEAD 177 | responses: 178 | '200': 179 | $ref: '#/responses/standard_200' 180 | '401': 181 | $ref: '#/responses/401' 182 | '403': 183 | $ref: '#/responses/403' 184 | '404': 185 | $ref: '#/responses/404' 186 | default: 187 | $ref: '#/responses/default' 188 | options: 189 | description: Retrieve OPTIONS 190 | parameters: 191 | - $ref: '#/parameters/Access-Control-Request-Method' 192 | - $ref: '#/parameters/Access-Control-Request-Headers' 193 | responses: 194 | '200': 195 | $ref: '#/responses/options_200' 196 | '401': 197 | $ref: '#/responses/401' 198 | '403': 199 | $ref: '#/responses/403' 200 | '404': 201 | $ref: '#/responses/404' 202 | default: 203 | $ref: '#/responses/default' 204 | x-id: TodoList 205 | TodoList.todos: 206 | get: 207 | responses: 208 | '200': 209 | description: description 210 | headers: 211 | Content-Location: 212 | description: perma-link URL of collection 213 | type: string 214 | Content-Type: 215 | description: The media type of the returned body 216 | type: string 217 | schema: 218 | $ref: '#/definitions/Collection' 219 | '303': 220 | $ref: '#/responses/303' 221 | '401': 222 | $ref: '#/responses/401' 223 | '403': 224 | $ref: '#/responses/403' 225 | '404': 226 | $ref: '#/responses/404' 227 | '406': 228 | $ref: '#/responses/406' 229 | default: 230 | $ref: '#/responses/default' 231 | post: 232 | description: Create a new Item 233 | parameters: 234 | - description: The representation of the new Item being created 235 | in: body 236 | name: body 237 | required: true 238 | schema: 239 | $ref: '#/definitions/Item' 240 | - description: The media type of the body 241 | in: header 242 | name: Content-Type 243 | required: true 244 | type: string 245 | responses: 246 | '201': 247 | description: Created new Item 248 | headers: 249 | Content-Type: 250 | description: The media type of the returned body 251 | type: string 252 | ETag: 253 | description: Value of ETag required for subsequent updates 254 | type: string 255 | Location: 256 | description: perma-link URL of newly-created Item 257 | type: string 258 | schema: 259 | $ref: '#/definitions/Item' 260 | '400': 261 | $ref: '#/responses/400' 262 | '401': 263 | $ref: '#/responses/401' 264 | '403': 265 | $ref: '#/responses/403' 266 | '404': 267 | $ref: '#/responses/404' 268 | '406': 269 | $ref: '#/responses/406' 270 | default: 271 | $ref: '#/responses/default' 272 | head: 273 | description: Retrieve HEAD 274 | responses: 275 | '200': 276 | $ref: '#/responses/standard_200' 277 | '401': 278 | $ref: '#/responses/401' 279 | '403': 280 | $ref: '#/responses/403' 281 | '404': 282 | $ref: '#/responses/404' 283 | default: 284 | $ref: '#/responses/default' 285 | options: 286 | description: Retrieve OPTIONS 287 | parameters: 288 | - $ref: '#/parameters/Access-Control-Request-Method' 289 | - $ref: '#/parameters/Access-Control-Request-Headers' 290 | responses: 291 | '200': 292 | $ref: '#/responses/options_200' 293 | '401': 294 | $ref: '#/responses/401' 295 | '403': 296 | $ref: '#/responses/403' 297 | '404': 298 | $ref: '#/responses/404' 299 | default: 300 | $ref: '#/responses/default' 301 | x-id: TodoList.todos 302 | x-templates: 303 | '{TodoList-URL}': 304 | $ref: '#/x-interfaces/TodoList' 305 | '{TodoList-URL}/todos': 306 | $ref: '#/x-interfaces/TodoList.todos' 307 | paths: 308 | /: 309 | $ref: '#/x-templates/{TodoList-URL}' 310 | /todos: 311 | $ref: '#/x-templates/{TodoList-URL}~1todos' 312 | definitions: 313 | TodoList: 314 | readOnly: true 315 | properties: 316 | todos: 317 | type: string 318 | format: uri 319 | x-interface: '#/x-interfaces/TodoList.todos' 320 | x-interface: '#/x-interfaces/TodoList' 321 | Item: 322 | properties: 323 | _self: 324 | type: string 325 | format: uri 326 | readOnly: true 327 | description: 328 | type: string 329 | due: 330 | type: string 331 | format: date-time 332 | x-interface: '#/x-interfaces/Item' 333 | Collection: 334 | readOnly: true 335 | properties: 336 | contents: 337 | type: array 338 | items: 339 | $ref: '#/definitions/Item' 340 | parameters: 341 | Accept: 342 | description: specifies the requested media type - required 343 | in: header 344 | name: Accept 345 | required: true 346 | type: string 347 | Access-Control-Request-Headers: 348 | description: specifies the custom headers the client wishes to use 349 | in: header 350 | name: Access-Control-Request-Headers 351 | required: true 352 | type: string 353 | Access-Control-Request-Method: 354 | description: specifies the method the client wishes to use 355 | in: header 356 | name: Access-Control-Request-Method 357 | required: true 358 | type: string 359 | If-Match: 360 | description: specifies the last known ETag value of the resource being modified 361 | in: header 362 | name: If-Match 363 | required: true 364 | type: string 365 | responses: 366 | '303': 367 | description: See other. Server is redirecting client to a different resource 368 | headers: 369 | Location: 370 | description: URL of other resource 371 | type: string 372 | '400': 373 | description: Bad Request. Client request in error 374 | schema: {} 375 | '401': 376 | description: Unauthorized. Client authentication token missing from request 377 | schema: {} 378 | '403': 379 | description: Forbidden. Client authentication token does not permit this method 380 | on this resource 381 | schema: {} 382 | '404': 383 | description: Not Found. Resource not found 384 | schema: {} 385 | '406': 386 | description: Not Acceptable. Requested media type not available 387 | schema: {} 388 | '409': 389 | description: Conflict. Value provided in If-Match header does not match current 390 | ETag value of resource 391 | schema: {} 392 | default: 393 | description: 5xx errors and other stuff 394 | schema: {} 395 | options_200: 396 | description: successful 397 | headers: 398 | Access-Control-Allow-Headers: 399 | description: headers allowed 400 | type: string 401 | Access-Control-Allow-Methods: 402 | description: methods allowed 403 | type: string 404 | Access-Control-Allow-Origin: 405 | description: origins allowed 406 | type: string 407 | Access-Control-Max-Age: 408 | description: length of time response can be cached 409 | type: string 410 | standard_200: 411 | description: successful 412 | headers: 413 | Content-Location: 414 | description: perma-link URL of resource 415 | type: string 416 | Content-Type: 417 | description: The media type of the returned body 418 | type: string 419 | ETag: 420 | description: this value must be echoed in the If-Match header of every PATCH 421 | or PUT 422 | type: string 423 | 424 | -------------------------------------------------------------------------------- /util/test/gen_py_sdk/dog-tracker.py: -------------------------------------------------------------------------------- 1 | 2 | from rapier.py.base_api import BaseAPI, BaseResource, BaseEntity, BaseCollection 3 | 4 | class API(BaseAPI): 5 | def well_known_URLs(self): 6 | return ['/'] 7 | def resource_class(self, type_name): 8 | return classToKindMap.get(type_name, BaseResource) 9 | 10 | api = API() 11 | 12 | class APIClass(object): 13 | def api(self): 14 | return api 15 | 16 | class Person(BaseEntity, APIClass): 17 | pass 18 | 19 | class Resource(BaseEntity, APIClass): 20 | pass 21 | 22 | class PersistentResource(BaseEntity, APIClass): 23 | pass 24 | 25 | class DogTracker(BaseEntity, APIClass): 26 | pass 27 | 28 | class Dog(BaseEntity, APIClass): 29 | pass 30 | 31 | class Collection(BaseEntity, APIClass): 32 | pass 33 | 34 | class Collection(BaseCollection, APIClass): 35 | pass 36 | 37 | classToKindMap = { 38 | 'Person': Person, 39 | 'Resource': Resource, 40 | 'PersistentResource': PersistentResource, 41 | 'DogTracker': DogTracker, 42 | 'Dog': Dog, 43 | 'Collection': Collection, 44 | 'Collection': Collection 45 | } 46 | -------------------------------------------------------------------------------- /util/test/gen_py_sdk/hello-message.py: -------------------------------------------------------------------------------- 1 | 2 | from rapier.py.base_api import BaseAPI, BaseResource, BaseEntity, BaseCollection 3 | 4 | class API(BaseAPI): 5 | def well_known_URLs(self): 6 | return ['/message'] 7 | def resource_class(self, type_name): 8 | return classToKindMap.get(type_name, BaseResource) 9 | 10 | api = API() 11 | 12 | class APIClass(object): 13 | def api(self): 14 | return api 15 | 16 | class HelloMessage(BaseEntity, APIClass): 17 | pass 18 | 19 | class Collection(BaseCollection, APIClass): 20 | pass 21 | 22 | classToKindMap = { 23 | 'HelloMessage': HelloMessage, 24 | 'Collection': Collection 25 | } 26 | -------------------------------------------------------------------------------- /util/test/gen_py_sdk/property-tracker.py: -------------------------------------------------------------------------------- 1 | 2 | from rapier.py.base_api import BaseAPI, BaseResource, BaseEntity, BaseCollection 3 | 4 | class API(BaseAPI): 5 | def well_known_URLs(self): 6 | return ['/'] 7 | def resource_class(self, type_name): 8 | return classToKindMap.get(type_name, BaseResource) 9 | 10 | api = API() 11 | 12 | class APIClass(object): 13 | def api(self): 14 | return api 15 | 16 | class Bicycle(BaseEntity, APIClass): 17 | pass 18 | 19 | class Dog(BaseEntity, APIClass): 20 | pass 21 | 22 | class Collection(BaseEntity, APIClass): 23 | pass 24 | 25 | class Person(BaseEntity, APIClass): 26 | pass 27 | 28 | class PropertyTracker(BaseEntity, APIClass): 29 | pass 30 | 31 | class PersistentResource(BaseEntity, APIClass): 32 | pass 33 | 34 | class Resource(BaseEntity, APIClass): 35 | pass 36 | 37 | class Institution(BaseEntity, APIClass): 38 | pass 39 | 40 | class Collection(BaseCollection, APIClass): 41 | pass 42 | 43 | classToKindMap = { 44 | 'Bicycle': Bicycle, 45 | 'Dog': Dog, 46 | 'Collection': Collection, 47 | 'Person': Person, 48 | 'PropertyTracker': PropertyTracker, 49 | 'PersistentResource': PersistentResource, 50 | 'Resource': Resource, 51 | 'Institution': Institution, 52 | 'Collection': Collection 53 | } 54 | -------------------------------------------------------------------------------- /util/test/gen_py_sdk/run-tests.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../" )" && pwd) 3 | cd $ROOT_DIR 4 | echo $ROOT_DIR 5 | python util/gen_py_sdk.py util/test/hello-message.yaml > util/test/gen_py_sdk/hello-message.py 6 | python util/gen_py_sdk.py util/test/todo-list.yaml > util/test/gen_py_sdk/todo-list.py 7 | python util/gen_py_sdk.py util/test/dog-tracker.yaml > util/test/gen_py_sdk/dog-tracker.py 8 | python util/gen_py_sdk.py util/test/property-tracker.yaml > util/test/gen_py_sdk/property-tracker.py -------------------------------------------------------------------------------- /util/test/gen_py_sdk/todo-list.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apigee-labs/rapier/7ef9ce6ef5112477a45c777aa4882932610ce1fe/util/test/gen_py_sdk/todo-list.py -------------------------------------------------------------------------------- /util/test/hello-message.yaml: -------------------------------------------------------------------------------- 1 | title: Hello World API 2 | entities: 3 | HelloMessage: 4 | wellKnownURLs: /message 5 | properties: 6 | text: 7 | type: string 8 | usage: 'r u' -------------------------------------------------------------------------------- /util/test/petstore.yaml: -------------------------------------------------------------------------------- 1 | title: Pet Store 2 | entities: 3 | Store: 4 | wellKnownURLs: /store 5 | queryPaths: pets orders users 6 | properties: 7 | pets: 8 | type: string 9 | format: uri 10 | relationship: 11 | entities: '#Pet' 12 | multiplicity: n 13 | collectionResource: '#PetCollection' 14 | orders: 15 | type: string 16 | format: uri 17 | relationship: 18 | entities: '#Order' 19 | multiplicity: n 20 | collectionResource: '#Collection' 21 | users: 22 | type: string 23 | format: uri 24 | relationship: 25 | entities: '#User' 26 | multiplicity: n 27 | collectionResource: '#Collection' 28 | logins: #or is logsin ? 29 | description: POST a Login to log yourself in. You will get back the URL of a Session. DELETE the Session to log out 30 | type: string 31 | format: uri 32 | relationship: 33 | entities: '#Session' 34 | multiplicity: n 35 | collectionResource: '#Collection' 36 | consumes: 37 | application/json: '#Login' 38 | readOnly: true 39 | Order: 40 | type: object 41 | properties: 42 | pet: 43 | type: string 44 | format: uri 45 | relationship: '#Pet' 46 | quantity: 47 | type: "integer" 48 | format: "int32" 49 | shipDate: 50 | type: "string" 51 | format: "date-time" 52 | status: 53 | type: "string" 54 | description: "Order Status" 55 | enum: 56 | - "placed" 57 | - "approved" 58 | - "delivered" 59 | complete: 60 | type: "boolean" 61 | default: false 62 | User: 63 | type: object 64 | properties: 65 | username: 66 | type: string 67 | firstName: 68 | type: string 69 | lastName: 70 | type: string 71 | email: 72 | type: string 73 | password: 74 | type: string 75 | phone: 76 | type: string 77 | userStatus: 78 | type: integer 79 | format: int32 80 | description: User Status 81 | session: 82 | type: string 83 | format: uri 84 | relationship: '#Session' 85 | Image: 86 | type: string 87 | Pet: 88 | type: object 89 | required: 90 | - name 91 | properties: 92 | category: 93 | $ref: '#/entities/Category' 94 | name: 95 | type: string 96 | example: doggie 97 | tags: 98 | type: array 99 | items: 100 | $ref: '#/entities/Tag' 101 | status: 102 | type: string 103 | description: pet status in the store 104 | enum: 105 | - available 106 | - pending 107 | - sold 108 | images: 109 | type: string 110 | format: uri 111 | relationship: 112 | entities: '#Image' 113 | multiplicity: n 114 | collectionResource: '#Collection' 115 | Inventory: 116 | type: object 117 | additionalProperties: 118 | type: string 119 | format: uri 120 | relationship: '#Pet' 121 | Session: 122 | type: object 123 | properties: 124 | rate_limit: 125 | type: integer 126 | expires_after: 127 | type: string 128 | format: date-time 129 | Login: 130 | type: object 131 | properties: 132 | username: 133 | type: string 134 | password: 135 | type: string 136 | Tag: 137 | type: "object" 138 | properties: 139 | id: 140 | type: "integer" 141 | format: "int64" 142 | name: 143 | type: "string" 144 | Category: 145 | type: object 146 | properties: 147 | id: 148 | type: integer 149 | format: int64 150 | name: 151 | type: string 152 | Collection: 153 | properties: 154 | items: 155 | type: array 156 | items: 157 | type: object 158 | readOnly: true 159 | PetCollection: 160 | properties: 161 | items: 162 | type: array 163 | items: 164 | $ref: '#/entities/Pet' 165 | queryParameters: 166 | tags: 167 | items: 168 | type: string 169 | type: array 170 | collectionFormat: multi 171 | required: false 172 | status: 173 | type: integer 174 | required: false 175 | readOnly: true 176 | -------------------------------------------------------------------------------- /util/test/property-tracker.yaml: -------------------------------------------------------------------------------- 1 | title: PropertyTrackerAPI 2 | version: "0.1" 3 | entities: 4 | PropertyTracker: 5 | allOf: 6 | - $ref: '#/entities/PersistentResource' 7 | properties: 8 | dogs: 9 | description: URL of a Collection of Dogs 10 | format: uri 11 | type: string 12 | relationship: 13 | collectionResource: '#Collection' 14 | entities: '#Dog' 15 | multiplicity: 0:n 16 | people: 17 | description: URL of a Collection of Persons 18 | format: uri 19 | type: string 20 | relationship: 21 | collectionResource: '#Collection' 22 | entities: '#Person' 23 | multiplicity: 0:n 24 | bicycles: 25 | description: URL of a Collection of Bicycles 26 | format: uri 27 | type: string 28 | relationship: 29 | collectionResource: '#Collection' 30 | entities: '#Bicycle' 31 | multiplicity: 0:n 32 | institutions: 33 | description: URL of a Collection of Institutions 34 | format: uri 35 | type: string 36 | relationship: 37 | collectionResource: '#Collection' 38 | entities: '#Institution' 39 | multiplicity: 0:n 40 | wellKnownURLs: / 41 | queryPaths: 42 | - dogs 43 | - "dogs;{name}" 44 | - bicycles 45 | - "bicycles;{name}" 46 | - people 47 | - "people;{name}" 48 | - institutions 49 | - "institutions;{name}" 50 | - "dogs;{name}/owner" 51 | - "bicycles;{name}/owner" 52 | - "institutions;{name}/assets" 53 | - "people;{name}/possessions" 54 | readOnly: true 55 | Dog: 56 | allOf: 57 | - $ref: '#/entities/PersistentResource' 58 | properties: 59 | name: 60 | type: string 61 | birth_date: 62 | type: string 63 | fur_color: 64 | type: string 65 | owner: 66 | description: URL of a Person or Institution 67 | format: uri 68 | type: string 69 | relationship: '#Person #Institution' 70 | Bicycle: 71 | allOf: 72 | - $ref: '#/entities/PersistentResource' 73 | properties: 74 | name: 75 | type: string 76 | purchase_date: 77 | type: string 78 | paint_color: 79 | type: string 80 | owner: 81 | description: URL of a Person or Institution 82 | format: uri 83 | type: string 84 | relationship: '#Person #Institution' 85 | Person: 86 | allOf: 87 | - $ref: '#/entities/PersistentResource' 88 | properties: 89 | name: 90 | type: string 91 | birth-date: 92 | type: string 93 | possessions: 94 | description: URL of a Collection of Dogs and Bicycles 95 | format: uri 96 | type: string 97 | relationship: 98 | collectionResource: '#Collection' 99 | entities: '#Dog #Bicycle' 100 | multiplicity: 0:n 101 | Institution: 102 | allOf: 103 | - $ref: '#/entities/PersistentResource' 104 | properties: 105 | name: 106 | type: string 107 | foundation-date: 108 | type: string 109 | assets: 110 | description: URL of a Collection of Dogs and Bicycles 111 | format: uri 112 | type: string 113 | relationship: 114 | collectionResource: '#Collection' 115 | entities: '#Dog #Bicycle' 116 | multiplicity: 0:n 117 | Resource: 118 | type: object 119 | properties: 120 | _self: 121 | type: string 122 | readOnly: true 123 | kind: 124 | type: string 125 | PersistentResource: 126 | allOf: 127 | - $ref: '#/entities/Resource' 128 | properties: 129 | created: 130 | type: string 131 | format: date-time 132 | readOnly: true 133 | creator: 134 | type: string 135 | format: URL 136 | readOnly: true 137 | modified: 138 | type: string 139 | format: date-time 140 | readOnly: true 141 | modifier: 142 | type: string 143 | format: date-time 144 | readOnly: true 145 | Collection: 146 | allOf: 147 | - $ref: '#/entities/Resource' 148 | properties: 149 | kind: 150 | type: string 151 | enum: [Collection] 152 | items: 153 | type: array 154 | items: 155 | type: object 156 | readOnly: true 157 | -------------------------------------------------------------------------------- /util/test/site-webmaster.yaml: -------------------------------------------------------------------------------- 1 | title: Site Webmaster API 2 | entities: 3 | Site: 4 | wellKnownURLs: / 5 | properties: 6 | webmaster: 7 | type: string 8 | format: uri 9 | relationship: '#Person' 10 | Person: 11 | properties: 12 | name: 13 | type: string 14 | -------------------------------------------------------------------------------- /util/test/spec-hub.yaml: -------------------------------------------------------------------------------- 1 | title: Doc Store API 2 | version: "0.1" 3 | consumes: application/json 4 | produces: application/json text/html 5 | conventions: 6 | patchConsumes: application/merge-patch+json 7 | errorResponse: 8 | type: string 9 | securityDefinitions: 10 | sso: 11 | type: oauth2 12 | scopes: {} 13 | flow: application 14 | tokenUrl: whatever 15 | security: 16 | - sso: [] 17 | entities: 18 | Store: 19 | allOf: 20 | - $ref: '#/entities/Resource' 21 | properties: 22 | kind: 23 | type: string 24 | enum: [Store] 25 | home: 26 | description: URL of the current user's 'home' Folder 27 | format: uri 28 | type: string 29 | relationship: '#Folder' 30 | sharedWithMe: 31 | description: URL of a Collection of Folders and Docs 32 | format: uri 33 | type: string 34 | relationship: 35 | collectionResource: '#MultiValuedRelationshipResource' 36 | entities: '#Folder #Doc' 37 | multiplicity: 0:n 38 | readOnly: true 39 | docs: 40 | description: URL of a Collection of Docs 41 | format: uri 42 | type: string 43 | relationship: 44 | collectionResource: '#MultiValuedRelationshipResource' 45 | entities: '#Doc' 46 | multiplicity: 0:n 47 | folders: 48 | description: URL of a Collection of Folders 49 | format: uri 50 | type: string 51 | relationship: 52 | collectionResource: '#MultiValuedRelationshipResource' 53 | entities: '#Folder' 54 | multiplicity: 0:n 55 | groups: 56 | description: URL of a Collection of Groups 57 | format: uri 58 | type: string 59 | relationship: 60 | collectionResource: '#MultiValuedRelationshipResource' 61 | entities: '#Group' 62 | multiplicity: 0:n 63 | wellKnownURLs: /store 64 | queryPaths: 65 | - home 66 | - home/contents 67 | - sharedWithMe 68 | - docs 69 | - folders 70 | - groups 71 | readOnly: true 72 | Folder: 73 | allOf: 74 | - $ref: '#/entities/PersistentResource' 75 | queryPaths: [contents] 76 | properties: 77 | contents: 78 | description: URL of a Collection of Docs and Folders 79 | format: uri 80 | readOnly: true 81 | type: string 82 | relationship: 83 | collectionResource: '#MultiValuedRelationshipResource' 84 | entities: '#Doc #Folder' 85 | multiplicity: 0:n 86 | folder: 87 | description: URL of a Folder 88 | format: uri 89 | type: string 90 | relationship: '#Folder' 91 | kind: 92 | type: string 93 | enum: [Folder] 94 | name: 95 | type: string 96 | permissions: 97 | type: array 98 | items: 99 | properties: 100 | actor: 101 | format: URL 102 | type: string 103 | action: 104 | type: string 105 | Doc: 106 | allOf: 107 | - $ref: '#/entities/PersistentResource' 108 | queryPaths: [content] 109 | properties: 110 | content_type: 111 | description: The media type of the content of the Doc 112 | type: string 113 | content: 114 | description: URL of a Content 115 | format: uri 116 | type: string 117 | relationship: '#Content' 118 | folder: 119 | description: URL of a Folder 120 | format: uri 121 | type: string 122 | relationship: '#Folder' 123 | kind: 124 | type: string 125 | enum: [Doc] 126 | name: 127 | type: string 128 | permissions: 129 | type: array 130 | items: 131 | properties: 132 | actor: 133 | format: URL 134 | type: string 135 | action: 136 | type: string 137 | Group: 138 | allOf: 139 | - $ref: '#/entities/PersistentResource' 140 | properties: 141 | members: 142 | description: Array of URLs of Users 143 | type: array 144 | items: 145 | type: string 146 | format: uri 147 | folder: 148 | description: URL of a Folder 149 | format: uri 150 | type: string 151 | relationship: '#Folder' 152 | kind: 153 | type: string 154 | enum: [Group] 155 | name: 156 | type: string 157 | permissions: 158 | type: array 159 | items: 160 | properties: 161 | actor: 162 | format: URL 163 | type: string 164 | action: 165 | type: string 166 | Content: 167 | type: string 168 | consumes: application/x-yaml text/yaml text/html text/plain 169 | produces: application/x-yaml text/yaml text/html text/plain 170 | Page: 171 | allOf: 172 | - $ref: '#/entities/Resource' 173 | properties: 174 | kind: 175 | type: string 176 | enum: [Page] 177 | items: 178 | type: array 179 | items: 180 | type: object 181 | collection: 182 | type: string 183 | format: uri 184 | next_page: 185 | type: string 186 | format: uri 187 | relationship: 188 | entities: '#Page' 189 | readOnly: True 190 | previous_page: 191 | type: string 192 | format: uri 193 | relationship: 194 | entities: '#Page' 195 | readOnly: True 196 | queryParameters: 197 | properties: 198 | items: 199 | type: string 200 | type: array 201 | required: false 202 | limit: 203 | type: integer 204 | minimum: 1 205 | maximum: 1000 206 | required: false 207 | readOnly: true 208 | Collection: 209 | allOf: 210 | - $ref: '#/entities/Resource' 211 | properties: 212 | kind: 213 | type: string 214 | enum: [Collection] 215 | items: 216 | type: array 217 | items: 218 | type: object 219 | queryParameters: 220 | properties: 221 | items : 222 | type: string 223 | type: array 224 | required: false 225 | limit: 226 | type: integer 227 | minimum: 1 228 | maximum: 1000 229 | required: false 230 | orderBy: # required if limit is provided 231 | type: string 232 | required: false 233 | direction: 234 | type: string 235 | required: false 236 | enum: ['ascending', 'descending'] 237 | readOnly: true 238 | MultiValuedRelationshipResource: 239 | readOnly: true 240 | oneOf: 241 | - $ref: '#/entities/Collection' 242 | - $ref: '#/entities/Page' 243 | Resource: 244 | type: object 245 | properties: 246 | _self: 247 | type: string 248 | readOnly: true 249 | kind: 250 | type: string 251 | PersistentResource: 252 | allOf: 253 | - $ref: '#/entities/Resource' 254 | properties: 255 | created: 256 | type: string 257 | format: date-time 258 | readOnly: true 259 | creator: 260 | type: string 261 | format: URL 262 | readOnly: true 263 | modified: 264 | type: string 265 | format: date-time 266 | readOnly: true 267 | modifier: 268 | type: string 269 | format: date-time 270 | readOnly: true 271 | implementationPrivateInformation: 272 | Group: 273 | permalinkTemplate: 274 | template: /c3Rvc-Z3Jw-{implementation_key} 275 | variables: 276 | implementation_key: 277 | type: integer 278 | Doc: 279 | permalinkTemplate: 280 | template: /c3Rvc-ZG9j-{implementation_key} 281 | variables: 282 | implementation_key: 283 | type: integer 284 | Folder: 285 | permalinkTemplate: 286 | template: /c3Rvc-Zmxk-{implementation_key} 287 | variables: 288 | implementation_key: 289 | type: integer 290 | -------------------------------------------------------------------------------- /util/test/ssl.yaml: -------------------------------------------------------------------------------- 1 | title: SSL API 2 | version: "0.1" 3 | conventions: 4 | queryPathSelectorLocation: pathSegment 5 | entities: 6 | Environment: 7 | wellKnownURLs: /v1/o/{org}/e/{env} 8 | queryPaths: 9 | - keystores 10 | - keystores;{name} 11 | - keystores;{name}/aliases 12 | - keystores;{name}/aliases;{name}/csr 13 | - keystores;{name}/aliases;{name}/certificate 14 | allOf: 15 | - $ref: '#/entities/PersistentResource' 16 | properties: 17 | kind: 18 | type: string 19 | enum: [Environment] 20 | keystores: 21 | format: uri 22 | type: string 23 | readOnly: true 24 | relationship: 25 | collectionResource: '#Collection' 26 | entities: '#Keystore' 27 | multiplicity: 0:n 28 | Keystore: 29 | allOf: 30 | - $ref: '#/entities/PersistentResource' 31 | properties: 32 | name: 33 | type: string 34 | kind: 35 | type: string 36 | enum: [Keystore] 37 | aliases: 38 | format: uri 39 | type: string 40 | readOnly: true 41 | relationship: 42 | collectionResource: '#Collection' 43 | entities: '#Alias' 44 | multiplicity: 0:n 45 | consumes: 46 | multi-part/form-data: '#Pkcs12Alias #KeyCertFileAlias #KeyCertJarAlias' 47 | application/json: '#SelfSignedCertAlias' 48 | Alias: 49 | allOf: 50 | - $ref: '#/entities/PersistentResource' 51 | properties: 52 | kind: 53 | type: string 54 | enum: [Alias] 55 | name: 56 | type: string 57 | key: 58 | type: string 59 | certsInfo: 60 | readOnly: true 61 | type: array 62 | items: 63 | type: object 64 | properties: 65 | version: 66 | type: integer 67 | expiryDate: 68 | type: string 69 | format: date 70 | issuer: 71 | type: string 72 | subject: 73 | type: string 74 | certificate: 75 | format: uri 76 | type: string 77 | relationship: '#Certificate' 78 | csr: 79 | format: uri 80 | type: string 81 | relationship: 82 | readOnly: true 83 | entities: '#CSR' 84 | selfSignedCertificate: 85 | format: uri 86 | type: string 87 | readOnly: true 88 | relationship: '#Certificate' 89 | Certificate: 90 | readOnly: true 91 | type: string 92 | CSR: 93 | readOnly: true 94 | type: string 95 | Pkcs12Alias: 96 | type: object 97 | properties: 98 | kind: 99 | type: string 100 | enum: [Pkcs12Alias] 101 | name: 102 | type: string 103 | password: 104 | type: string 105 | ignoreExpiryValidation: 106 | type: boolean 107 | pkcs12File: 108 | type: string 109 | format: file 110 | SelfSignedCertAlias: 111 | type: object 112 | properties: 113 | kind: 114 | type: string 115 | enum: [SelfSignedCertAlias] 116 | alias: 117 | type: string 118 | keySize: 119 | type: string 120 | certValidityInDays: 121 | type: integer 122 | sigAlg: 123 | type: string 124 | issuer: 125 | $ref: '#/entities/X500Name' 126 | subject: 127 | $ref: '#/entities/X500Name' 128 | subjectAlternativeDNSNames: 129 | type: array 130 | items: 131 | type: string 132 | KeyCertFileAlias: 133 | type: object 134 | properties: 135 | kind: 136 | type: string 137 | enum: [KeyFileCertFileAlias] 138 | name: 139 | type: string 140 | password: 141 | type: string 142 | ignoreExpiryValidation: 143 | type: boolean 144 | keyFile: 145 | type: string 146 | format: file 147 | certFile: 148 | type: string 149 | format: file 150 | KeyCertJarAlias: 151 | type: object 152 | properties: 153 | kind: 154 | type: string 155 | enum: [KeyFileCertFileAlias] 156 | name: 157 | type: string 158 | password: 159 | type: string 160 | ignoreExpiryValidation: 161 | type: boolean 162 | keyCertJar: 163 | type: string 164 | format: file 165 | X500Name: 166 | type: object 167 | properties: 168 | countryCode: 169 | type: string 170 | state: 171 | type: string 172 | locality: 173 | type: string 174 | orgName: 175 | type: string 176 | orgUnitName: 177 | type: string 178 | email: 179 | type: string 180 | PersistentResource: 181 | allOf: 182 | - $ref: '#/entities/Resource' 183 | type: object 184 | properties: 185 | created: 186 | type: string 187 | format: date-time 188 | readOnly: true 189 | creator: 190 | type: string 191 | format: URL 192 | readOnly: true 193 | modified: 194 | type: string 195 | format: date-time 196 | readOnly: true 197 | modifier: 198 | type: string 199 | format: date-time 200 | readOnly: true 201 | Resource: 202 | type: object 203 | properties: 204 | _self: 205 | type: string 206 | kind: 207 | type: string 208 | Collection: 209 | allOf: 210 | - $ref: '#/entities/Resource' 211 | properties: 212 | kind: 213 | type: string 214 | enum: [Collection] 215 | items: 216 | type: array 217 | items: 218 | type: object 219 | readOnly: true 220 | -------------------------------------------------------------------------------- /util/test/todo-list-basic.yaml: -------------------------------------------------------------------------------- 1 | title: Todo List API 2 | entities: 3 | TodoList: 4 | wellKnownURLs: / 5 | readOnly: true 6 | properties: 7 | todos: 8 | type: string 9 | format: uri 10 | readOnly: true 11 | relationship: 12 | collectionResource: '#Collection' 13 | entities: '#Item' 14 | multiplicity: 0:n 15 | queryPaths: todos 16 | Item: 17 | properties: 18 | description: 19 | type: string 20 | due: 21 | type: string 22 | format: date-time 23 | Collection: 24 | readOnly: true 25 | properties: 26 | contents: 27 | type: array 28 | items: 29 | $ref: '#/entities/Item' 30 | -------------------------------------------------------------------------------- /util/test/todo-list-with-id.yaml: -------------------------------------------------------------------------------- 1 | title: Todo List API 2 | conventions: 3 | queryPathSelectorLocation: pathSegment 4 | entities: 5 | TodoList: 6 | wellKnownURLs: / 7 | queryPaths: [todos, "todos;{id}"] 8 | readOnly: true 9 | properties: 10 | todos: 11 | type: string 12 | format: uri 13 | relationship: 14 | collectionResource: '#Collection' 15 | entities: '#Item' 16 | multiplicity: 0:n 17 | Item: 18 | properties: 19 | id: 20 | type: string 21 | readOnly: true 22 | description: 23 | type: string 24 | due: 25 | type: string 26 | format: date-time 27 | Collection: 28 | readOnly: true 29 | properties: 30 | contents: 31 | type: array 32 | items: 33 | $ref: '#/entities/Item' 34 | -------------------------------------------------------------------------------- /util/test/todo-list-with-links.yaml: -------------------------------------------------------------------------------- 1 | # Some people like to group all URI-valued properties under an array-valued 'links' property, and put 2 | # the relationship name as the value of a 'rel' property, rather than putting it in a JSON property name. 3 | # I do not understand the appeal of this pattern, but I wanted to show how you can do it with Rapier 4 | # if you want to. 5 | title: Todo List API 6 | version: "0.1" 7 | conventions: 8 | queryPathSelectorLocation: pathSegment 9 | entities: 10 | TodoList: 11 | wellKnownURLs: / 12 | queryPaths: [todos] 13 | readOnly: true 14 | properties: 15 | links: 16 | type: array 17 | items: 18 | oneOf: 19 | - type: object 20 | properties: 21 | rel: 22 | type: string 23 | enum: [todos] 24 | href: 25 | type: string 26 | format: uri 27 | relationship: 28 | name: todos 29 | collectionResource: '#Collection' 30 | entities: '#Item' 31 | multiplicity: 0:n 32 | Item: 33 | properties: 34 | _self: 35 | type: string 36 | format: uri 37 | readOnly: true 38 | due: 39 | type: string 40 | format: date-time 41 | Collection: 42 | readOnly: true 43 | properties: 44 | contents: 45 | type: array 46 | items: 47 | $ref: '#/entities/Item' 48 | -------------------------------------------------------------------------------- /util/test/todo-list-with-self.yaml: -------------------------------------------------------------------------------- 1 | title: Todo List API 2 | conventions: 3 | queryPathSelectorLocation: pathSegment 4 | entities: 5 | TodoList: 6 | wellKnownURLs: / 7 | queryPaths: [todos] 8 | readOnly: true 9 | properties: 10 | todos: 11 | type: string 12 | format: uri 13 | relationship: 14 | collectionResource: '#Collection' 15 | entities: '#Item' 16 | multiplicity: 0:n 17 | Item: 18 | properties: 19 | _self: 20 | type: string 21 | format: uri 22 | readOnly: true 23 | description: 24 | type: string 25 | due: 26 | type: string 27 | format: date-time 28 | Collection: 29 | readOnly: true 30 | properties: 31 | contents: 32 | type: array 33 | items: 34 | $ref: '#/entities/Item' 35 | -------------------------------------------------------------------------------- /util/test/use-common.yaml: -------------------------------------------------------------------------------- 1 | title: Test of using common definitions 2 | version: "0.1" 3 | consumes: application/json 4 | produces: application/json text/html 5 | conventions: 6 | patchConsumes: application/merge-patch+json 7 | errorResponse: 8 | type: string 9 | entities: 10 | Environment: 11 | allOf: 12 | - $ref: './common.yaml#/entities/PersistentResource' 13 | properties: 14 | kind: 15 | description: > 16 | The value must always be the string "Environment". This property is always set 17 | by the server in responses to GET. It must be set by the client on POST, 18 | and must not be set by the client on PATCH. (PUT is not supported) 19 | type: string 20 | enum: [Environment] 21 | Apigee: 22 | readOnly: true 23 | wellKnownURLs: / 24 | queryPaths: environments 25 | properties: 26 | environments: 27 | format: uri 28 | type: string 29 | readOnly: true 30 | relationship: 31 | collectionResource: './common.yaml#MultiValuedRelationship' 32 | multiplicity: 0:n 33 | entities: '#Environment' 34 | -------------------------------------------------------------------------------- /util/test/validate_rapier/hello-message-errors.yaml: -------------------------------------------------------------------------------- 1 | WARNING - title occurs 2 times, at line 1, column 1 and line 9, column 1 in rapier/util/test/validate_rapier/hello-message.yaml 2 | ERROR - title name must be a string: 1 after line 1 column 1 to line 1 column 6 in rapier/util/test/validate_rapier/hello-message.yaml 3 | INFO - unrecognized keyword titre at line 8, column 1 - did you mean title? in rapier/util/test/validate_rapier/hello-message.yaml 4 | -------------------------------------------------------------------------------- /util/test/validate_rapier/hello-message.yaml: -------------------------------------------------------------------------------- 1 | title: HelloWorldAPI 2 | entities: 3 | HelloMessage: 4 | wellKnownURLs: /message 5 | properties: 6 | text: 7 | type: string 8 | titre: HelloWorldAPI 9 | title: 1 10 | -------------------------------------------------------------------------------- /util/test/validate_rapier/run-deployment-validation-test.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | # echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | # echo $ROOT_DIR 6 | ./rapier/util/validate_rapier.py rapier/util/test/deployment.yaml -------------------------------------------------------------------------------- /util/test/validate_rapier/run-validation-tests.sh: -------------------------------------------------------------------------------- 1 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 2 | # echo $DIR 3 | ROOT_DIR=$( cd "$( dirname "$DIR/../../../../../" )" && pwd) 4 | cd $ROOT_DIR 5 | # echo $ROOT_DIR 6 | ./rapier/util/validate_rapier.py rapier/util/test/validate_rapier/hello-message.yaml 2> rapier/util/test/validate_rapier/hello-message-errors.yaml 7 | ./rapier/util/validate_rapier.py rapier/util/test/todo-list-basic.yaml 8 | ./rapier/util/validate_rapier.py rapier/util/test/todo-list-with-id.yaml 9 | ./rapier/util/validate_rapier.py rapier/util/test/todo-list-with-self.yaml 10 | ./rapier/util/validate_rapier.py rapier/util/test/todo-list-with-links.yaml 11 | ./rapier/util/validate_rapier.py rapier/util/test/dog-tracker.yaml 12 | ./rapier/util/validate_rapier.py rapier/util/test/property-tracker.yaml 13 | ./rapier/util/validate_rapier.py rapier/util/test/spec-hub.yaml 14 | ./rapier/util/validate_rapier.py rapier/util/test/ssl.yaml 15 | ./rapier/util/validate_rapier.py rapier/util/test/deployment.yaml 16 | ./rapier/util/validate_rapier.py rapier/util/test/site-webmaster.yaml 17 | ./rapier/util/validate_rapier.py rapier/util/test/deployment-primitives.yaml 18 | ./rapier/util/validate_rapier.py rapier/util/test/deployment-primitives-simplified.yaml 19 | --------------------------------------------------------------------------------