Skip to content

Commit

Permalink
refactor(open api gateway): improve project structure (#42)
Browse files Browse the repository at this point in the history
* feat(open-api-gateway): remove spurious unused project files in spec project

* refactor(open-api-gateway): more intuitive project structure

* Rename `parsed-spec.json` to `.parsed-spec.json` to indicate it doesn't need to be edited (it's
already readonly)
* Move `spec` folder into the source directory alongside `api` source code

BREAKING CHANGE: Combined `specDir` and `specFileName` into a single `specFile` parameter

* docs(open-api-gateway): update readme
  • Loading branch information
cogwirrel committed Jun 18, 2022
1 parent db81ba1 commit 1fda02b
Show file tree
Hide file tree
Showing 10 changed files with 576 additions and 1,189 deletions.
18 changes: 10 additions & 8 deletions packages/open-api-gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ new OpenApiGatewayTsProject({
In the output directory (`outdir`), you'll find a few files to get you started.

```
|_ spec/
|_ spec.yaml - The OpenAPI specification - edit this to define your API
|_ src/
|_ api.ts - A CDK construct which defines the API Gateway resources to deploy your API.
| This wraps the OpenApiGatewayLambdaApi construct and provides typed interfaces for integrations specific
| to your API. You shouldn't need to modify this, instead just extend it as in sample-api.ts.
|_ sample-api.ts - Example usage of the construct defined in api.ts.
|_ sample-api.say-hello.ts - An example lambda handler for the operation defined in spec.yaml, making use of the
generated lambda handler wrappers for marshalling and type safety.
|_ spec/
|_ spec.yaml - The OpenAPI specification - edit this to define your API
|_ .parsed-spec.json - A json spec generated from your spec.yaml.
|_ api/
|_ api.ts - A CDK construct which defines the API Gateway resources to deploy your API.
| This wraps the OpenApiGatewayLambdaApi construct and provides typed interfaces for integrations specific
| to your API. You shouldn't need to modify this, instead just extend it as in sample-api.ts.
|_ sample-api.ts - Example usage of the construct defined in api.ts.
|_ sample-api.say-hello.ts - An example lambda handler for the operation defined in spec.yaml, making use of the
generated lambda handler wrappers for marshalling and type safety.
|_ generated/
|_ typescript/ - A generated typescript API client, including generated lambda handler wrappers
```
Expand Down
1 change: 0 additions & 1 deletion packages/open-api-gateway/src/project/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,3 @@

export * from "./open-api-gateway-ts-project";
export * from "./languages";
export { OpenApiSpecConfig } from "./spec/open-api-spec-project";
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
******************************************************************************************************************** */

import * as path from "path";
import { SampleDir, TextFile, YamlFile } from "projen";
import { SampleDir, SampleFile, TextFile, YamlFile } from "projen";
import { NodePackageManager } from "projen/lib/javascript";
import {
TypeScriptProject,
Expand All @@ -29,10 +29,7 @@ import {
getTypescriptSampleTests,
TypescriptSampleCodeOptions,
} from "./samples/typescript";
import {
OpenApiSpecConfig,
OpenApiSpecProject,
} from "./spec/open-api-spec-project";
import { OpenApiSpecProject } from "./spec/open-api-spec-project";

const OPENAPI_GATEWAY_PDK_PACKAGE_NAME =
"@aws-prototyping-sdk/open-api-gateway";
Expand All @@ -41,24 +38,30 @@ const OPENAPI_GATEWAY_PDK_PACKAGE_NAME =
* Configuration for the OpenApiGatewayTsProject
*/
export interface OpenApiGatewayTsProjectOptions
extends TypeScriptProjectOptions,
OpenApiSpecConfig {
extends TypeScriptProjectOptions {
/**
* The list of languages for which clients will be generated. A typescript client will always be generated.
*/
readonly clientLanguages: ClientLanguage[];

/**
* The directory in which the OpenAPI spec should be generated, relative to the outdir of this project
* @default "spec"
*/
readonly specDir?: string;

/**
* The directory in which generated client code will be generated, relative to the outdir of this project
* @default "generated"
*/
readonly generatedCodeDir?: string;
/**
* The path to the OpenAPI specification file, relative to the project source directory (srcdir).
* @default "spec/spec.yaml"
*/
readonly specFile?: string;
/**
* The directory in which the api generated code will reside, relative to the project srcdir
*/
readonly apiSrcDir?: string;
/**
* The name of the output parsed OpenAPI specification file. Must end with .json.
* @default ".parsed-spec.json"
*/
readonly parsedSpecFileName?: string;
}

/**
Expand All @@ -74,10 +77,20 @@ export class OpenApiGatewayTsProject extends TypeScriptProject {
public readonly generatedTypescriptClient: TypeScriptProject;

/**
* The directory in which the OpenAPI spec file(s) reside, relative to the project outdir
* The directory in which the OpenAPI spec file(s) reside, relative to the project srcdir
*/
public readonly specDir: string;

/**
* The directory in which the api generated code will reside, relative to the project srcdir
*/
public readonly apiSrcDir: string;

/**
* The name of the spec file
*/
public readonly specFileName: string;

/**
* The directory in which generated client code will be generated, relative to the outdir of this project
*/
Expand All @@ -95,30 +108,25 @@ export class OpenApiGatewayTsProject extends TypeScriptProject {
super({
...options,
sampleCode: false,
// Default src dir to 'api' to allow for more readable imports, eg `import { SampleApi } from 'my-generated-api/api'`.
srcdir: options.srcdir || "api",
tsconfig: {
compilerOptions: {
// Root dir needs to include srcdir and spec
rootDir: ".",
lib: ["dom", "es2019"],
},
},
});

this.specDir = options.specDir ?? "spec";
if (options.specFile) {
this.specDir = path.dirname(options.specFile);
this.specFileName = path.basename(options.specFile);
} else {
this.specDir = "spec";
this.specFileName = "spec.yaml";
}
this.generatedCodeDir = options.generatedCodeDir ?? "generated";
this.apiSrcDir = options.apiSrcDir ?? "api";

// Allow spec to be imported, using both the source and spec directories as project roots.
this.tsconfig?.addInclude(`${this.specDir}/**/*.json`);
this.package.addField(
"main",
path.join(this.libdir, this.srcdir, "index.js")
);
this.package.addField(
"types",
path.join(this.libdir, this.srcdir, "index.d.ts")
);
// Allow json files to be imported (for importing the parsed spec)
this.tsconfig?.addInclude(`${this.srcdir}/**/*.json`);

// Set to private since this either uses workspaces or has file dependencies
this.package.addField("private", true);
Expand All @@ -130,8 +138,8 @@ export class OpenApiGatewayTsProject extends TypeScriptProject {
const spec = new OpenApiSpecProject({
name: `${this.name}-spec`,
parent: this,
outdir: this.specDir,
specFileName: options.specFileName,
outdir: path.join(this.srcdir, this.specDir),
specFileName: this.specFileName,
parsedSpecFileName: options.parsedSpecFileName,
});
spec.synth();
Expand Down Expand Up @@ -224,11 +232,14 @@ export class OpenApiGatewayTsProject extends TypeScriptProject {
typescriptClientPackageName:
this.generatedTypescriptClient.package.packageName,
sampleCode: options.sampleCode,
srcdir: this.srcdir,
apiSrcDir: path.join(this.srcdir, this.apiSrcDir),
specDir: this.specDir,
parsedSpecFileName: spec.parsedSpecFileName,
};
new SampleDir(this, this.srcdir, {
new SampleFile(this, path.join(this.srcdir, "index.ts"), {
contents: `export * from "./${this.apiSrcDir}";`,
});
new SampleDir(this, path.join(this.srcdir, this.apiSrcDir), {
files: getTypescriptSampleSource(sampleOptions),
});
new SampleDir(this, this.testdir, {
Expand Down
9 changes: 5 additions & 4 deletions packages/open-api-gateway/src/project/samples/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export interface TypescriptSampleCodeOptions {
*/
readonly sampleCode?: boolean;
/**
* Source code directory
* Api source code directory, relative to the project root
*/
readonly srcdir: string;
readonly apiSrcDir: string;
/**
* Directory where the parsed spec is output
*/
Expand Down Expand Up @@ -66,7 +66,8 @@ export interface ApiProps extends Omit<OpenApiGatewayLambdaApiProps, "spec" | "o
}
/**
* Construct for the API Gateway resources defined by the spec
* Type-safe construct for the API Gateway resources defined by the spec.
* You will likely not need to modify this file, and can instead extend it and define your integrations.
*/
export class Api extends OpenApiGatewayLambdaApi {
constructor(scope: Construct, id: string, props: ApiProps) {
Expand Down Expand Up @@ -133,7 +134,7 @@ export const getTypescriptSampleTests = (
import { Template } from "aws-cdk-lib/assertions";
import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda";
import { OperationLookup } from "${options.typescriptClientPackageName}";
import { Api } from "../${options.srcdir}";
import { Api } from "../${options.apiSrcDir}";
/**
* A simple test to ensure the api construct synthesizes correctly
Expand Down
21 changes: 10 additions & 11 deletions packages/open-api-gateway/src/project/spec/open-api-spec-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,21 @@ import { Project, SampleFile, ProjectOptions } from "projen";
import { ParsedSpec } from "./components/parsed-spec";

/**
* Configuration for the OpenAPI specification files
* Configuration for the OpenAPI spec project
*/
export interface OpenApiSpecConfig {
export interface OpenApiSpecProjectOptions extends ProjectOptions {
/**
* The name of the OpenAPI specification file.
* @default "spec.yaml"
*/
readonly specFileName?: string;
/**
* The name of the output parsed OpenAPI specification file. Must end with .json.
* @default "parsed-spec.json"
* @default ".parsed-spec.json"
*/
readonly parsedSpecFileName?: string;
}

/**
* Configuration for the OpenAPI spec project
*/
export interface OpenApiSpecProjectOptions
extends OpenApiSpecConfig,
ProjectOptions {}

/**
* Project containing the OpenAPI spec, and a parsed spec for use by the CDK construct
*/
Expand All @@ -56,8 +49,13 @@ export class OpenApiSpecProject extends Project {

constructor(options: OpenApiSpecProjectOptions) {
super(options);
// HACK: remove all components but the ones we are registering - removes .gitignore, tasks, etc since these are
// unused and a distraction for end-users!
// @ts-ignore
this._components = [];

this.specFileName = options.specFileName ?? "spec.yaml";
this.parsedSpecFileName = options.parsedSpecFileName ?? "parsed-spec.json";
this.parsedSpecFileName = options.parsedSpecFileName ?? ".parsed-spec.json";

if (!this.parsedSpecFileName.endsWith(".json")) {
throw new Error("Parsed spec file must end with .json");
Expand Down Expand Up @@ -96,6 +94,7 @@ export class OpenApiSpecProject extends Project {
return;
}
super.synth();

this.synthed = true;
}
}

0 comments on commit 1fda02b

Please sign in to comment.