Skip to content

Instantly share code, notes, and snippets.

@KiaraGrouwstra
Last active July 19, 2016 13:12
Show Gist options
  • Save KiaraGrouwstra/179c6c0dbfe6d06681ee01314892e799 to your computer and use it in GitHub Desktop.
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
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" }
}
}
},
}
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" }
}
}
},
}
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" }
}
}
},
}
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