Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change async requirementAt to sync getRequirement #1091

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/typescript-generator/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async function buildCacheDirectory(destination) {

async function getPathsFromSpecification(specification) {
try {
return (await specification.requirementAt("#/paths")) ?? new Set();
return specification.getRequirement("#/paths") ?? new Set();
} catch (error) {
process.stderr.write(
`Could not find #/paths in the specification.\n${error}\n`,
Expand All @@ -62,7 +62,7 @@ export async function generate(

debug("creating specification from %s", source);

const specification = new Specification(source);
const specification = await Specification.fromFile(source);

debug("created specification: $o", specification);

Expand All @@ -72,7 +72,7 @@ export async function generate(

debug("got %i paths", paths.size);

const securityRequirement = await specification.requirementAt(
const securityRequirement = specification.getRequirement(
"#/components/securitySchemes",
);

Expand Down
2 changes: 1 addition & 1 deletion src/typescript-generator/requirement.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class Requirement {
}

reference() {
return this.specification.requirementAt(this.data.$ref, this.url);
return this.specification.getRequirement(this.data.$ref);
}

has(item) {
Expand Down
63 changes: 19 additions & 44 deletions src/typescript-generator/specification.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import yaml from "js-yaml";

import { readFile } from "../util/read-file.js";
import { Requirement } from "./requirement.js";
import { bundle } from "@apidevtools/json-schema-ref-parser";
import { url } from "node:inspector";

const debug = createDebug("counterfact:typescript-generator:specification");

Expand All @@ -18,56 +20,29 @@ const EMPTY_OPENAPI = `{
}`;

export class Specification {
constructor(rootUrl) {
constructor(rootRequirement) {
this.cache = new Map();
this.rootUrl = rootUrl;
this.rootUrl = rootRequirement;
this.rootRequirement = rootRequirement;
}

async requirementAt(url, fromUrl = "") {
debug("getting requirement at %s from %s", url, fromUrl);

const [file, path] = url.split("#");
const filePath = nodePath
.join(fromUrl.split("#").at(0), file)
.replaceAll("\\", "/")
// eslint-disable-next-line regexp/prefer-named-capture-group
.replace(/:\/([^/])/u, "://$1");
const fileUrl = filePath === "." ? this.rootUrl : filePath;

debug("reading specification at %s", fileUrl);

const data = await this.loadFile(fileUrl);

debug("done reading specification", fileUrl);

const rootRequirement = new Requirement(data, `${fileUrl}#`, this);

return rootRequirement.select(path.slice(1));
static async fromFile(urlOrPath) {
const specification = new Specification();
await specification.load(urlOrPath);
return specification;
}

async loadFile(urlOrPath) {
debug("loading file %s", urlOrPath);

if (this.cache.has(urlOrPath)) {
debug("cache hit");
getRequirement(url) {
debug("getting requirement at %s", url);

return this.cache.get(urlOrPath);
}

debug("cache miss, reading file at %s", urlOrPath);

const source =
urlOrPath === "_" ? EMPTY_OPENAPI : await readFile(urlOrPath, "utf8");

debug("read file");
debug("parsing YAML");

const data = await yaml.load(source);

debug("parsed YAML: %o", data);
this.cache.set(urlOrPath, data);
this.rootRequirement = new Requirement(data, `${urlOrPath}#`, this);
return this.rootRequirement.select(url.slice(2));
}

return data;
async load(urlOrPath) {
this.rootRequirement = new Requirement(
await bundle(urlOrPath),
urlOrPath,
this,
);
}
}
10 changes: 6 additions & 4 deletions test/typescript-generator/coder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,13 @@ describe("a Coder", () => {
});

it("when asked to delegate a requirement that is a $ref, looks up the $ref and returns another coder of the same type with an inline requirement", async () => {
const specification = new Specification("openapi.yaml");
const specification = new Specification(
new Requirement({
components: { schemas: { Person: { type: "string" } } },
}),
);

specification.cache.set("openapi.yaml", {
components: { schemas: { Person: { type: "string" } } },
});
specification.cache.set("openapi.yaml");

class DelegatingCoder extends Coder {}

Expand Down
33 changes: 16 additions & 17 deletions test/typescript-generator/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,28 @@ import { describe, expect, it } from "@jest/globals";
import { Coder } from "../../src/typescript-generator/coder.js";
import { Repository } from "../../src/typescript-generator/repository.js";
import { Specification } from "../../src/typescript-generator/specification.js";
import { Requirement } from "../../src/typescript-generator/requirement.js";

describe("integration Test", () => {
it("writes some code", async () => {
const specification = new Specification();

specification.cache.set("openapi.yaml", {
paths: {
"/accounts": {
get: {},
post: {},
},

"/accounts/{id}": {
get: {},
put: {},
const specification = new Specification(
new Requirement({
paths: {
"/accounts": {
get: {},
post: {},
},

"/accounts/{id}": {
get: {},
put: {},
},
},
},
});
}),
);

const repository = new Repository();
const requirement = await specification.requirementAt(
"openapi.yaml#/paths",
);
const requirement = await specification.getRequirement("#/paths");

class OperationCoder extends Coder {
*names() {
Expand Down
10 changes: 5 additions & 5 deletions test/typescript-generator/operation-type-coder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,11 @@ describe("an OperationTypeCoder", () => {
});

it("generates a complex post operation (OpenAPI 2 with produces at the root)", async () => {
const specification = new Specification();

specification.rootRequirement = new Requirement({
produces: ["application/json"],
});
const specification = new Specification(
new Requirement({
produces: ["application/json"],
}),
);

const requirement = new Requirement(
{
Expand Down
102 changes: 20 additions & 82 deletions test/typescript-generator/specification.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ describe("a Specification", () => {
await withTemporaryFiles(
{ "openapi.yaml": "hello:\n world" },
async (temporaryDirectory, { path }) => {
const specification = new Specification();
const specification = await Specification.fromFile(
path("openapi.yaml"),
);

await specification.loadFile(path("openapi.yaml"));

expect(specification.cache.get(path("openapi.yaml"))).toStrictEqual({
expect(specification.rootRequirement.data).toStrictEqual({
hello: "world",
});
},
Expand All @@ -27,13 +27,11 @@ describe("a Specification", () => {
await withTemporaryFiles(
{ "openapi.yaml": "hello:\n world" },
async (temporaryDirectory, { path }) => {
const specification = new Specification();

const { href } = pathToFileURL(path("openapi.yaml"));

await specification.loadFile(href);
const specification = await Specification.fromFile(href);

expect(specification.cache.get(href)).toStrictEqual({
expect(specification.rootRequirement.data).toStrictEqual({
hello: "world",
});
},
Expand All @@ -49,12 +47,9 @@ describe("a Specification", () => {

const server = app.listen(0);
const url = `http://localhost:${server.address().port}/`;
const specification = await Specification.fromFile(url);

const specification = new Specification();

await specification.loadFile(url);

expect(specification.cache.get(url)).toStrictEqual({
expect(specification.rootRequirement.data).toStrictEqual({
hello: "world",
});

Expand All @@ -69,87 +64,30 @@ describe("a Specification", () => {
});
});

it("returns a requirement for a URL", async () => {
const specification = new Specification();

specification.cache.set(
"openapi.yaml",
Promise.resolve({
components: {
schemas: {
Person: {
properties: {
name: { type: "string" },
phone: { type: "string" },
},

type: "object",
},
},
},
}),
);

const person = new Requirement(
{
properties: {
name: { type: "string" },
phone: { type: "string" },
},

type: "object",
it("looks for requirements in the root document if no path is provided", () => {
const person = {
properties: {
name: { type: "string" },
phone: { type: "string" },
},

"openapi.yaml#/components/schemas/Person",
specification,
);

const requirement = await specification.requirementAt(
"openapi.yaml#/components/schemas/Person",
);

expect(requirement).toStrictEqual(person);
});

it("looks for requirements in the root document if no path is provided", async () => {
const specification = new Specification("openapi.yaml");
type: "object",
};

specification.cache.set(
"openapi.yaml",
Promise.resolve({
const specification = new Specification(
new Requirement({
components: {
schemas: {
Person: {
properties: {
name: { type: "string" },
phone: { type: "string" },
},

type: "object",
},
Person: person,
},
},
}),
);

const person = new Requirement(
{
properties: {
name: { type: "string" },
phone: { type: "string" },
},

type: "object",
},

"openapi.yaml#/components/schemas/Person",
specification,
);

const requirement = await specification.requirementAt(
const requirement = specification.getRequirement(
"#/components/schemas/Person",
);

expect(requirement).toStrictEqual(person);
expect(requirement.data).toStrictEqual(person);
});
});
Loading