mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-22 10:21:37 +00:00
Split up generators
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
/// <reference types="node" />
|
||||
import { writeFileSync } from "fs";
|
||||
import * as path from "path";
|
||||
import fetch from "node-fetch";
|
||||
import mkdirp from "mkdirp";
|
||||
|
||||
/*
|
||||
Open API TypeScript Client Generator
|
||||
|
||||
This is a quickly made bespoke Open API client generator.
|
||||
This is a bespoke Open API client generator not intended for general public use.
|
||||
It is not designed to handle the full OpenAPI spec.
|
||||
Many other more general purpose generators exist, but their output is very verbose and overly complex for our use case.
|
||||
But it does work well enough to generate a fully typed tree-shakeable client for the Cosmos resource provider.
|
||||
@@ -13,11 +15,14 @@ Results of this file should be checked into the repo.
|
||||
*/
|
||||
|
||||
// Array of strings to use for eventual output
|
||||
const output: string[] = [""];
|
||||
const outputTypes: string[] = [""];
|
||||
|
||||
const schemaURL =
|
||||
"https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2020-06-01-preview/cosmos-db.json";
|
||||
|
||||
const outputDir = path.join(__dirname, "../../src/Utils/arm/");
|
||||
mkdirp.sync(outputDir);
|
||||
|
||||
// Buckets for grouping operations based on their name
|
||||
const namespaces: { [key: string]: string[] } = {};
|
||||
|
||||
@@ -27,10 +32,11 @@ const propertyMap: { [key: string]: string } = {
|
||||
};
|
||||
|
||||
// Converts a Open API reference: "#/definitions/Foo" to a type name: Foo
|
||||
function refToType(path: string | undefined) {
|
||||
function refToType(path: string | undefined, namespace?: string) {
|
||||
// References must be in the same file. Bail to `unknown` types for remote references
|
||||
if (path && path.startsWith("#")) {
|
||||
return path.split("/").pop();
|
||||
const type = path.split("/").pop();
|
||||
return namespace ? `${namespace}.${type}` : type;
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
@@ -45,11 +51,11 @@ function camelize(str: string) {
|
||||
}
|
||||
|
||||
// Converts a body paramter to the equivalent typescript function parameter type
|
||||
function bodyParam(parameter: { schema: { $ref: string } }) {
|
||||
function bodyParam(parameter: { schema: { $ref: string } }, namespace: string) {
|
||||
if (!parameter) {
|
||||
return "";
|
||||
}
|
||||
return `,body: ${refToType(parameter.schema.$ref)}`;
|
||||
return `,body: ${refToType(parameter.schema.$ref, namespace)}`;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -65,14 +71,14 @@ function parametersFromPath(path: string) {
|
||||
type Operation = { responses: { [key: string]: { schema: { $ref: string } } } };
|
||||
|
||||
// Converts OpenAPI response definition to TypeScript return type. Uses unions if possible. Bails to unknown
|
||||
function responseType(operation: Operation) {
|
||||
function responseType(operation: Operation, namespace: string) {
|
||||
if (operation.responses) {
|
||||
return Object.keys(operation.responses)
|
||||
.map((responseCode: string) => {
|
||||
if (!operation.responses[responseCode].schema) {
|
||||
return "void";
|
||||
}
|
||||
return refToType(operation.responses[responseCode].schema.$ref);
|
||||
return refToType(operation.responses[responseCode].schema.$ref, namespace);
|
||||
})
|
||||
.join(" | ");
|
||||
}
|
||||
@@ -91,33 +97,33 @@ async function main() {
|
||||
const baseTypes = schema.definitions[definition].allOf
|
||||
.map((allof: { $ref: string }) => refToType(allof.$ref))
|
||||
.join(" & ");
|
||||
output.push(`type ${definition} = ${baseTypes} & {`);
|
||||
outputTypes.push(`export type ${definition} = ${baseTypes} & {`);
|
||||
} else {
|
||||
output.push(`interface ${definition} {`);
|
||||
outputTypes.push(`export interface ${definition} {`);
|
||||
}
|
||||
for (const prop in schema.definitions[definition].properties) {
|
||||
const property = schema.definitions[definition].properties[prop];
|
||||
if (property) {
|
||||
if (property.$ref) {
|
||||
const type = refToType(property.$ref);
|
||||
output.push(`
|
||||
outputTypes.push(`
|
||||
/* ${property.description} */
|
||||
${property.readOnly ? "readonly " : ""}${prop}: ${type}
|
||||
`);
|
||||
} else if (property.type === "array") {
|
||||
const type = refToType(property.items.$ref);
|
||||
output.push(`
|
||||
outputTypes.push(`
|
||||
/* ${property.description} */
|
||||
${property.readOnly ? "readonly " : ""}${prop}: ${type}[]
|
||||
`);
|
||||
} else if (property.type === "object") {
|
||||
const type = refToType(property.$ref);
|
||||
output.push(`
|
||||
outputTypes.push(`
|
||||
/* ${property.description} */
|
||||
${property.readOnly ? "readonly " : ""}${prop}: ${type}
|
||||
`);
|
||||
} else {
|
||||
output.push(`
|
||||
outputTypes.push(`
|
||||
/* ${property.description} */
|
||||
${property.readOnly ? "readonly " : ""}${prop}: ${
|
||||
propertyMap[property.type] ? propertyMap[property.type] : property.type
|
||||
@@ -125,36 +131,36 @@ async function main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
output.push(`}`);
|
||||
output.push("\n\n");
|
||||
outputTypes.push(`}`);
|
||||
outputTypes.push("\n\n");
|
||||
} else {
|
||||
const def = schema.definitions[definition];
|
||||
if (def.enum) {
|
||||
output.push(`
|
||||
outputTypes.push(`
|
||||
/* ${def.description} */
|
||||
type ${definition} = ${def.enum.map((v: string) => `"${v}"`).join(" | ")}`);
|
||||
output.push("\n");
|
||||
export type ${definition} = ${def.enum.map((v: string) => `"${v}"`).join(" | ")}`);
|
||||
outputTypes.push("\n");
|
||||
} else if (def.type === "string") {
|
||||
output.push(`
|
||||
outputTypes.push(`
|
||||
/* ${def.description} */
|
||||
type ${definition} = string
|
||||
export type ${definition} = string
|
||||
`);
|
||||
} else if (def.type === "array") {
|
||||
const type = refToType(def.items.$ref);
|
||||
output.push(`
|
||||
outputTypes.push(`
|
||||
/* ${def.description} */
|
||||
type ${definition} = ${type}[]
|
||||
export type ${definition} = ${type}[]
|
||||
`);
|
||||
} else if (def.type === "object" && def.additionalProperties) {
|
||||
output.push(`
|
||||
outputTypes.push(`
|
||||
/* ${def.description} */
|
||||
type ${definition} = { [key: string]: ${def.additionalProperties.type}}
|
||||
export type ${definition} = { [key: string]: ${def.additionalProperties.type}}
|
||||
`);
|
||||
} else if (def.type === "object" && def.allOf) {
|
||||
const type = refToType(def.allOf[0].$ref);
|
||||
output.push(`
|
||||
outputTypes.push(`
|
||||
/* ${def.description} */
|
||||
type ${definition} = ${type}
|
||||
export type ${definition} = ${type}
|
||||
`);
|
||||
} else {
|
||||
console.log("UNHANDLED MODEL:", def, schema.definitions[def]);
|
||||
@@ -176,10 +182,10 @@ async function main() {
|
||||
}
|
||||
namespaces[namespace].push(`
|
||||
/* ${operation.description} */
|
||||
async ${camelize(operationName)} (
|
||||
export async function ${camelize(operationName)} (
|
||||
${parametersFromPath(path)}
|
||||
${bodyParam(bodyParameter)}
|
||||
) : Promise<${responseType(operation)}> {
|
||||
${bodyParam(bodyParameter, "Types")}
|
||||
) : Promise<${responseType(operation, "Types")}> {
|
||||
return window.fetch(\`https://management.azure.com${path.replace(/{/g, "${")}\`, { method: "${method}", ${
|
||||
bodyParameter ? "body: JSON.stringify(body)" : ""
|
||||
} }).then((response) => response.json())
|
||||
@@ -190,12 +196,22 @@ async function main() {
|
||||
|
||||
// Write all grouped fetch functions to objects
|
||||
for (const namespace in namespaces) {
|
||||
output.push(`export const ${namespace} = {`);
|
||||
output.push(namespaces[namespace].join(",\n"));
|
||||
output.push(`}\n`);
|
||||
const outputClient: string[] = [""];
|
||||
outputClient.push(`import * as Types from "./types"\n\n`);
|
||||
outputClient.push(namespaces[namespace].join("\n\n"));
|
||||
writeOutputFile(`./${namespace}.ts`, outputClient);
|
||||
}
|
||||
|
||||
writeFileSync("./client.ts", output.join(""));
|
||||
writeOutputFile("./types.ts", outputTypes);
|
||||
}
|
||||
|
||||
function writeOutputFile(outputPath: string, components: string[]) {
|
||||
components.unshift(`/*
|
||||
AUTOGENERATED FILE
|
||||
Do not manually edit
|
||||
Run "npm run generateARMClients" to regenerate
|
||||
*/\n\n`);
|
||||
writeFileSync(path.join(outputDir, outputPath), components.join(""));
|
||||
}
|
||||
|
||||
main().catch(e => {
|
||||
|
||||
Reference in New Issue
Block a user