dolorealiquam
Version:
TypeScript mappings for the Bungie.net API
137 lines (118 loc) • 4.94 kB
text/typescript
import * as _ from 'underscore';
import { OpenAPIObject, PathItemObject, ParameterObject, SchemaObject, ReferenceObject } from 'openapi3-ts';
import { getRef, lastPart, getReferencedTypes, DefInfo, interfaceName, isRequestBodyObject } from './util';
export function computeTypeMaps(pathPairsByTag: { [tag: string]: [string, PathItemObject][] }, doc: OpenAPIObject) {
const allDefsEverywhere = new Set();
const defsByTag = {};
_.each(pathPairsByTag, (paths, tag) => {
const defs = findReachableComponents(tag, paths, doc);
addAll(allDefsEverywhere, defs);
defsByTag[tag] = defs;
});
const allTags = Object.keys(pathPairsByTag);
const componentsByFile = {};
const componentByDef = {};
for (const def of allDefsEverywhere) {
const tags: string[] = [];
_.each(defsByTag, (defs: Set<string>, tag) => {
if (defs.has(def)) {
tags.push(tag);
}
});
const info: DefInfo = {
def,
tags,
filename: chooseFile(def, tags, allTags),
interfaceName: interfaceName(def, doc)
};
componentsByFile[info.filename] = componentsByFile[info.filename] || [];
componentsByFile[info.filename].push(info);
componentByDef[info.def] = info;
}
return { componentsByFile, componentByDef };
}
// TODO: put enums in a separate file???
// TODO: put things ending in Request in a separate file?
// TODO: put manifest stuff in a separate file?
function chooseFile(def: string, tags: string[], allTags: string[]) {
const schemaName: string = _.last(def.split('/'))!;
const matchingTag = allTags.find((tag) => schemaName.startsWith(tag + '.'));
const filename = '/interfaces.ts';
if (matchingTag) {
return matchingTag.toLowerCase() + filename;
} else if (schemaName.startsWith('GroupsV2.')) {
return 'groupv2' + filename;
} else if (schemaName.startsWith('Destiny.')) {
return 'destiny2' + filename;
} else {
if (tags.length === 1) {
return tags[0].toLowerCase() + filename;
} else if (!tags.includes('Destiny2')) {
return 'platform.ts';
} else {
return 'common.ts';
}
}
}
function findReachableComponents(tag: string, paths: [string, PathItemObject][], doc: OpenAPIObject) {
const pathDefinitions = paths.reduce((memo: Set<string>, [path, pathDef]) => addAll(memo, findReachableComponentsFromPath(pathDef, doc)), new Set());
const allDefinitions = new Set(pathDefinitions);
pathDefinitions.forEach((definition) => addReachableComponentsFromComponent(allDefinitions, definition, doc));
return allDefinitions;
}
function addAll<T>(first: Set<T>, second: Set<T>): Set<T> {
for (const value of second) {
first.add(value);
}
return first;
}
function findReachableComponentsFromPath(pathDef: PathItemObject, doc: OpenAPIObject): Set<string> {
const methodDef = pathDef.get || pathDef.post!;
const params = (methodDef.parameters || []) as ParameterObject[];
const paramTypes = new Set(params.map((param) => getReferencedTypes(param.schema!)).filter((p) => p)) as Set<string>;
const requestBody = methodDef.requestBody;
if (requestBody && isRequestBodyObject(requestBody)) {
const schema = requestBody.content['application/json'].schema!;
const paramType = getReferencedTypes(schema);
if (paramType) {
paramTypes.add(paramType);
}
}
const returnType = getReferencedTypes(methodDef.responses['200']);
if (returnType) {
paramTypes.add(returnType);
}
return paramTypes;
}
function addReachableComponentsFromComponent(allDefinitions: Set<string>, definition: string, doc: OpenAPIObject) {
const component = getRef(doc, definition);
if (!component) {
return;
}
if (component && component.type === 'array') {
addDefinitions(allDefinitions, component.items!, doc);
} else if (component.type === 'object') {
Object.values(component.properties).forEach((schema: SchemaObject | ReferenceObject) => {
addDefinitions(allDefinitions, schema, doc);
});
(component.allOf || []).forEach((schema: SchemaObject | ReferenceObject) => {
addDefinitions(allDefinitions, schema, doc);
});
(component.additionalProperties || []).forEach((schema: SchemaObject | ReferenceObject) => {
addDefinitions(allDefinitions, schema, doc);
});
}
}
function addDefinitions(allDefinitions: Set<string>, schema: SchemaObject | ReferenceObject, doc: OpenAPIObject) {
const newDefinition = getReferencedTypes(schema);
addDefinitionsFromComponent(allDefinitions, newDefinition, doc);
if (schema['x-mapped-definition']) {
addDefinitionsFromComponent(allDefinitions, schema['x-mapped-definition'].$ref, doc);
}
}
function addDefinitionsFromComponent(allDefinitions: Set<string>, definition: string | undefined, doc: OpenAPIObject) {
if (definition && !allDefinitions.has(definition)) {
allDefinitions.add(definition);
addReachableComponentsFromComponent(allDefinitions, definition, doc);
}
}