Last active
July 19, 2016 13:12
-
-
Save KiaraGrouwstra/179c6c0dbfe6d06681ee01314892e799 to your computer and use it in GitHub Desktop.
PoC for OpenAPI's `$ref` (no spec changes) to demonstrate (1) detection of models semantic and (2) the info needed to link parameters to model properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const alias = { | |
"openapi": "2.0", | |
"paths": { | |
"/path1": { | |
"get": { | |
"parameters": [ | |
{ | |
"in": "path", | |
"name": "my_param", | |
"schema": { "$ref": "#/x-MyDefs/person_id" } | |
}, | |
], | |
"responses": { | |
"200": { | |
"schema": { | |
"$ref": "#/x-MyDefs/Person" | |
} | |
} | |
}, | |
} | |
}, | |
}, | |
"definitions": {}, | |
"x-MyDefs": { | |
"person_id": { "$ref": "#/x-MyDefs/Person/properties/id" }, | |
"Person": { | |
"properties": { | |
"id": { "type": "integer" } | |
} | |
} | |
}, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const direct = { | |
"openapi": "2.0", | |
"paths": { | |
"/path1": { | |
"get": { | |
"parameters": [ | |
{ | |
"in": "path", | |
"name": "my_param", | |
"schema": { "$ref": "#/x-MyDefs/Person/properties/id" } | |
}, | |
], | |
"responses": { | |
"200": { | |
"schema": { | |
"$ref": "#/x-MyDefs/Person" | |
} | |
} | |
}, | |
} | |
}, | |
}, | |
"definitions": {}, | |
"x-MyDefs": { | |
"Person": { | |
"properties": { | |
"id": { "type": "integer" } | |
} | |
} | |
}, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const external = { | |
"openapi": "2.0", | |
"paths": { | |
"/path1": { | |
"get": { | |
"parameters": [ | |
{ | |
"in": "path", | |
"name": "my_param", | |
"schema": { "$ref": "#/x-MyDefs/person_id" } | |
}, | |
], | |
"responses": { | |
"200": { | |
"schema": { | |
"$ref": "#/x-MyDefs/Person" | |
} | |
} | |
}, | |
} | |
}, | |
}, | |
"definitions": {}, | |
"x-MyDefs": { | |
"person_id": { "type": "integer" }, | |
"Person": { | |
"properties": { | |
"id": { "$ref": "#/x-MyDefs/person_id" } | |
} | |
} | |
}, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const _ = require('lodash/fp'); | |
import { external } from './external.json'; | |
import { alias } from './alias.json'; | |
import { direct } from './direct.json'; | |
// TODO: deal with `$ref`s to other/remote files -- must be somehow aware of (the location of) the current file or return the file contents along the model path to make the paths useful | |
// task #1: grab all models, i.e. parameter-/response-referenced json-schemas (not sub-schemas) that aren't just empty references themselves | |
// I'm finding models based on existing references since we can't force people to put them in one fixed place. | |
const SCHEMA_KWS = ['items', 'additionalItems', 'properties', 'patternProperties', 'additionalProperties', 'allOf', 'anyOf', 'oneOf', 'not']; | |
const SKIP_EMPTY = true; | |
const toRef = path => ['#'].concat(path).join('/'); | |
const fromRef = ref => ref.split('/').slice(1); | |
// grab all models | |
function getModels(struct) { | |
let references = []; | |
// itVal(struct, references); | |
// just search paths' parameters/responses for refs | |
_.mapValues(_.mapValues(endpoint => { | |
//itObj(endpoint.parameters, references, struct, []); | |
//itObj(endpoint.responses, references, struct, []); | |
for (let k of ['parameters', 'responses']) { | |
if(endpoint[k]) itObj(endpoint[k], references, struct, []); | |
} | |
}))(struct.paths); | |
return _.uniq(references); | |
} | |
// const itVal = (v, refs = [], struct = v) => _.isArray(v) ? itArr(v, refs, struct) : _.isPlainObject(v) ? itObj(v, refs, struct) : itScalar(v, refs, struct); | |
function itVal(v, ...args) { | |
let fn = _.isArray(v) ? itArr : | |
_.isPlainObject(v) ? itObj : () => {}; | |
fn(v, ...args); | |
} | |
//const itArr = (arr, refs, struct) => arr.map(v => itVal(v, refs, struct)); | |
function itArr(arr, ...args) { | |
for (let v of arr) { | |
itVal(v, ...args); | |
} | |
} | |
function itObj(obj, refs, struct, visited) { | |
let { $ref } = obj; | |
if($ref) { | |
let route = fromRef($ref); | |
// distinguish sub-schemas since they aren't models themselves | |
let isSubSchema = SCHEMA_KWS.some(kw => route.includes(kw)); | |
let schemaRef, schemaPath; | |
if(isSubSchema) { | |
// for sub-schemas instead add their main schema | |
let idx = route.findIndex(s => SCHEMA_KWS.includes(s)); | |
schemaPath = route.slice(0, idx); | |
schemaRef = toRef(schemaPath); | |
} else { | |
schemaRef = $ref; | |
schemaPath = route; | |
} | |
// let model_name = _.last(schemaPath); // not guaranteed unique | |
let model = _.get(schemaPath)(struct); // return with `$ref`s intact? | |
// if this $ref address hadn't been visited before, add and follow it | |
if(!visited.includes(schemaRef)) { | |
visited.push(schemaRef); | |
// iterate through the referenced schema | |
itVal(model, refs, struct, visited); | |
// optionally also filter places that are just empty references themselves | |
if(!SKIP_EMPTY || !model['$ref']) { | |
refs.push(schemaRef); | |
} | |
} | |
} | |
// _.mapValues(v => itVal(v, refs, struct))(obj); | |
for (let v of Object.values(obj)) { | |
itVal(v, refs, struct, visited); | |
} | |
} | |
// task #2: link parameter references to their model properties | |
getLinks = (struct) => { | |
let references = []; | |
for (let address of Object.values(struct.paths)) { | |
for (let endpoint of Object.values(address)) { | |
for (let param of endpoint.parameters) { | |
// find params where the schema is just a $ref | |
let $ref = _.get(['schema','$ref'])(param); | |
let dest = _.get(fromRef($ref))(struct); | |
// follow any 'redirects' | |
while(true) { | |
if(dest['$ref']) { | |
$ref = dest['$ref']; | |
dest = _.get(fromRef($ref))(struct); | |
} else { | |
break; | |
} | |
} | |
if($ref) { | |
// - the input parameter correlates to a property of the object at #/definitions/Person | |
let modelPath = paths.find(y => $ref.startsWith(y)); | |
if(modelPath) { | |
// - the structure it is part of should probably be referred to as Person. | |
let modelName = _.last(modelPath.split('/')); | |
// - the input parameter to the property of that structure is at its sub-path ./properties/id | |
let relPath = $ref.slice(modelPath.length); | |
references.push({ reference: $ref, modelName, modelPath, relPath, dest }); | |
} | |
} | |
} | |
} | |
} | |
return _.uniq(references); | |
} | |
let paths, links; | |
// note that weird locations to store the info (i.e. not `#/definitions`) are accounted for, since realistically I can't force people to put them in one fixed place. | |
paths = getModels(direct) // ["#/x-MyDefs/Person"] | |
links = getLinks(direct) // [{ modelName: "Person", modelPath: "#/x-MyDefs/Person", reference: "#/x-MyDefs/Person/properties/id", relPath: "/properties/id", dest }] | |
paths = getModels(alias) // ["#/x-MyDefs/Person"] | |
links = getLinks(alias) // [{ modelName: "Person", modelPath: "#/x-MyDefs/Person", reference: "#/x-MyDefs/Person/properties/id", relPath: "/properties/id", dest }] | |
paths = getModels(external) // ["#/x-MyDefs/person_id", "#/x-MyDefs/Person"] | |
links = getLinks(external) // [{ modelName: "person_id", modelPath: "#/x-MyDefs/person_id", reference: "#/x-MyDefs/person_id", relPath: "", dest }] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment