From 8d9e2161cf225feee7150463b8bddc802bbaebde Mon Sep 17 00:00:00 2001 From: Jack Stevenson Date: Fri, 25 Oct 2024 11:39:08 +1100 Subject: [PATCH] feat(type-safe-api): separate model projects per model language (#872) Rather than sharing a single `TypeSafeApiModelProject` for both Smithy and OpenAPI, we separate them into their own projects. This improves the ability to add future additional model languages since they can inherit from other projen project types (not just `Project`). This also makes instantiating standalone Smithy libraries more intuitive as they are created with `SmithyModelProject`, rather than `TypeSafeApiModelProject` and modelLanguage set to SMITHY, therefore no `!` needs to be used to access the possibly undefined `smithy` property. BREAKING CHANGE: Removed `TypeSafeApiModelProject`. Please instantiate Smithy shape libraries with `SmithyModelProject`. re #825 --- .../cloudscape-react-ts-website-project.ts | 2 +- .../type-safe-api/troubleshooting.md | 57 +++---- .../codegen/components/generate-task.ts | 41 +++++ .../type-safe-api-command-environment.ts | 6 +- .../src/project/codegen/generate.ts | 126 ++++++++++++++- packages/type-safe-api/src/project/index.ts | 7 +- .../src/project/model/model-readme.ts | 40 +++++ .../openapi/open-api-async-definition.ts | 8 +- .../openapi/open-api-async-model-project.ts | 65 ++++++++ .../model/openapi/open-api-definition.ts | 8 +- .../model/openapi/open-api-model-project.ts | 53 +++++++ .../openapi/open-api-project-definition.ts | 8 +- .../model/smithy/smithy-async-definition.ts | 12 +- .../smithy/smithy-async-model-project.ts | 65 ++++++++ .../project/model/smithy/smithy-definition.ts | 7 +- .../model/smithy/smithy-model-project.ts | 52 +++++++ .../model/smithy/smithy-project-definition.ts | 15 +- .../model/type-safe-api-async-model-build.ts | 49 ++++++ .../model/type-safe-api-model-build.ts | 60 +++++++ .../model/type-safe-api-model-project-base.ts | 138 ----------------- .../model/type-safe-api-model-project.ts | 75 ++------- .../type-safe-websocket-api-model-project.ts | 92 +---------- .../src/project/type-safe-api-project.ts | 58 +++---- .../type-safe-websocket-api-project.ts | 80 +++++----- packages/type-safe-api/src/project/types.ts | 57 +++++++ .../model/type-safe-api-model-project.test.ts | 146 +++++++----------- ...e-safe-websocket-api-model-project.test.ts | 78 ++++------ .../project/type-safe-api-project.test.ts | 32 ++++ .../type-safe-websocket-api-project.test.ts | 32 ++++ 29 files changed, 882 insertions(+), 587 deletions(-) create mode 100644 packages/type-safe-api/src/project/codegen/components/generate-task.ts create mode 100644 packages/type-safe-api/src/project/model/model-readme.ts create mode 100644 packages/type-safe-api/src/project/model/openapi/open-api-async-model-project.ts create mode 100644 packages/type-safe-api/src/project/model/openapi/open-api-model-project.ts create mode 100644 packages/type-safe-api/src/project/model/smithy/smithy-async-model-project.ts create mode 100644 packages/type-safe-api/src/project/model/smithy/smithy-model-project.ts create mode 100644 packages/type-safe-api/src/project/model/type-safe-api-async-model-build.ts create mode 100644 packages/type-safe-api/src/project/model/type-safe-api-model-build.ts delete mode 100644 packages/type-safe-api/src/project/model/type-safe-api-model-project-base.ts diff --git a/packages/cloudscape-react-ts-website/src/cloudscape-react-ts-website-project.ts b/packages/cloudscape-react-ts-website/src/cloudscape-react-ts-website-project.ts index 4005e0066..867d272b6 100644 --- a/packages/cloudscape-react-ts-website/src/cloudscape-react-ts-website-project.ts +++ b/packages/cloudscape-react-ts-website/src/cloudscape-react-ts-website-project.ts @@ -229,7 +229,7 @@ export class CloudscapeReactTsWebsiteProject extends ReactTypeScriptProject { `mkdir -p ${targetApiSpecFolder} && cp ${path.relative( this.outdir, tsApi.model.outdir - )}/.api.json ${targetApiSpecPath}` + )}/${tsApi.model.parsedSpecFile} ${targetApiSpecPath}` ); } diff --git a/packages/type-safe-api/docs/developer_guides/type-safe-api/troubleshooting.md b/packages/type-safe-api/docs/developer_guides/type-safe-api/troubleshooting.md index e616ef287..1631d2afe 100644 --- a/packages/type-safe-api/docs/developer_guides/type-safe-api/troubleshooting.md +++ b/packages/type-safe-api/docs/developer_guides/type-safe-api/troubleshooting.md @@ -112,7 +112,7 @@ If you would like to introduce tags without breaking existing clients, we recomm ### I have multiple Smithy-based APIs, can they share common structures? -Yes. You can create a `TypeSafeApiModelProject` on its own to create a standalone Smithy model library, which can contain the shared structures. +Yes. You can create a `SmithyModelProject` on its own to create a standalone Smithy model library, which can contain the shared structures. You can consume the library using the `addSmithyDeps` method, which adds a local file dependency to the built Smithy jar. @@ -120,17 +120,14 @@ You can consume the library using the `addSmithyDeps` method, which adds a local ```ts // Standalone model project, used as our model library - const shapes = new TypeSafeApiModelProject({ + const shapes = new SmithyModelProject({ name: "shapes", parent: monorepo, outdir: "packages/shapes", - modelLanguage: ModelLanguage.SMITHY, - modelOptions: { - smithy: { - serviceName: { - namespace: "com.my.shared.shapes", - serviceName: "Ignored", - }, + smithyOptions: { + serviceName: { + namespace: "com.my.shared.shapes", + serviceName: "Ignored", }, }, }); @@ -138,55 +135,49 @@ You can consume the library using the `addSmithyDeps` method, which adds a local const api = new TypeSafeApiProject({ ... }); // Add the implicit monorepo dependency (if using the monorepo) to ensure the shape library is built before the api model - monorepo.addImplicitDependency(api.model, shapes); + monorepo.addImplicitDependency(api.model.smithy!, shapes); // Add a local file dependency on the built shapes jar - api.model.smithy!.addSmithyDeps(shapes.smithy!); + api.model.smithy!.definition.addSmithyDeps(shapes.definition); ``` === "JAVA" ```java // Standalone model project, used as our model library - TypeSafeApiModelProject shapes = TypeSafeApiModelProject.Builder.create() + SmithyModelProject shapes = SmithyModelProject.Builder.create() .name("shapes") .parent(monorepo) .outdir("packages/shapes") - .modelLanguage(ModelLanguage.getSMITHY()) - .modelOptions(ModelOptions.builder() - .smithy(SmithyModelOptions.builder() - .serviceName(SmithyServiceName.builder() - .namespace("com.my.shared.shapes") - .serviceName("Ignored") - .build()) - .build()) - .build()) + .smithyOptions(SmithyModelOptions.builder() + .serviceName(SmithyServiceName.builder() + .namespace("com.my.shared.shapes") + .serviceName("Ignored") + .build()) + .build()) .build(); TypeSafeApiProject api = new TypeSafeApiProject(TypeSafeApiProjectOptions.builder()....build(); // Add the implicit monorepo dependency (if using the monorepo) to ensure the shape library is built before the api model - monorepo.addImplicitDependency(api.getModel(), shapes); + monorepo.addImplicitDependency(api.getModel().getSmithy(), shapes.getDefinition()); // Add a local file dependency on the built shapes jar - api.model.smithy.addSmithyDeps(shapes.getSmithy()); + api.getModel().getSmithy().getDefinition().addSmithyDeps(shapes.getSmithy()); ``` === "PYTHON" ```python # Standalone model project, used as our model library - shapes = TypeSafeApiModelProject( + shapes = SmithyModelProject( name="shapes", parent=monorepo, outdir="packages/shapes", - model_language=ModelLanguage.SMITHY, - model_options=ModelOptions( - smithy=SmithyModelOptions( - service_name=SmithyServiceName( - namespace="com.my.shared.shapes", - service_name="Ignored" - ) + smithy_options=SmithyModelOptions( + service_name=SmithyServiceName( + namespace="com.my.shared.shapes", + service_name="Ignored" ) ) ) @@ -194,10 +185,10 @@ You can consume the library using the `addSmithyDeps` method, which adds a local api = TypeSafeApiProject(...) # Add the implicit monorepo dependency (if using the monorepo) to ensure the shape library is built before the api model - monorepo.add_implicit_dependency(api.model, shapes) + monorepo.add_implicit_dependency(api.model.smithy, shapes) # Add a local file dependency on the built shapes jar - api.model.smithy.add_smithy_deps(shapes.smithy) + api.model.smithy.definition.add_smithy_deps(shapes.definition) ``` ### How do I debug my API locally? diff --git a/packages/type-safe-api/src/project/codegen/components/generate-task.ts b/packages/type-safe-api/src/project/codegen/components/generate-task.ts new file mode 100644 index 000000000..723e35d04 --- /dev/null +++ b/packages/type-safe-api/src/project/codegen/components/generate-task.ts @@ -0,0 +1,41 @@ +/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 */ +import { ProjectUtils } from "@aws/monorepo"; +import { Component, Project, Task } from "projen"; + +/** + * Component which manages a "generate" task for a project + */ +export class GenerateTask extends Component { + /** + * Retrieves an instance of GenerateTask if one is associated to the given project. + * + * @param project project instance. + */ + static of(project: Project): Task | undefined { + return ( + project.components.find((c) => + ProjectUtils.isNamedInstanceOf(c, GenerateTask) + ) as GenerateTask | undefined + )?.task; + } + + /** + * Retrieves an instance of GenerateTask if one is associated to the given project, + * otherwise creates a GenerateTask instance for the project. + * + * @param project project instance. + */ + static ensure(project: Project): Task { + return GenerateTask.of(project) || new GenerateTask(project).task; + } + + public readonly task: Task; + + constructor(project: Project) { + super(project); + + this.task = + project.tasks.tryFind("generate") ?? project.addTask("generate"); + } +} diff --git a/packages/type-safe-api/src/project/codegen/components/type-safe-api-command-environment.ts b/packages/type-safe-api/src/project/codegen/components/type-safe-api-command-environment.ts index 4edf6e3df..deda40d24 100644 --- a/packages/type-safe-api/src/project/codegen/components/type-safe-api-command-environment.ts +++ b/packages/type-safe-api/src/project/codegen/components/type-safe-api-command-environment.ts @@ -2,7 +2,7 @@ SPDX-License-Identifier: Apache-2.0 */ import { ProjectUtils } from "@aws/monorepo"; import { Component, Project } from "projen"; -import { TypeScriptProject } from "projen/lib/typescript"; +import { NodeProject } from "projen/lib/javascript"; import { getTypeSafeApiTaskEnvironment } from "./utils"; /** @@ -41,8 +41,8 @@ export class TypeSafeApiCommandEnvironment extends Component { project.tasks.addEnvironment(key, value) ); - // TypeScript projects need a dev dependency on PDK to ensure npx resolves to the correct pdk - if (ProjectUtils.isNamedInstanceOf(project, TypeScriptProject)) { + // Node projects need a dev dependency on PDK to ensure npx resolves to the correct pdk + if (ProjectUtils.isNamedInstanceOf(project, NodeProject)) { project.addDevDeps("@aws/pdk@^0"); } } diff --git a/packages/type-safe-api/src/project/codegen/generate.ts b/packages/type-safe-api/src/project/codegen/generate.ts index db8e38b1e..03677fd4a 100644 --- a/packages/type-safe-api/src/project/codegen/generate.ts +++ b/packages/type-safe-api/src/project/codegen/generate.ts @@ -10,10 +10,6 @@ import { WebSocketDocumentationFormat, WebSocketLibrary, } from "../languages"; -import { - GeneratedDocumentationOptions, - GeneratedWebSocketDocumentationOptions, -} from "../types"; import { GeneratedAsyncApiHtmlDocumentationProject } from "./documentation/generated-asyncapi-html-documentation-project"; import { GeneratedAsyncApiMarkdownDocumentationProject } from "./documentation/generated-asyncapi-markdown-documentation-project"; import { GeneratedHtmlRedocDocumentationProject } from "./documentation/generated-html-redoc-documentation-project"; @@ -64,6 +60,21 @@ import { GeneratedTypescriptRuntimeProject, GeneratedTypescriptTypesProjectOptions, } from "./runtime/generated-typescript-runtime-project"; +import { OpenApiAsyncModelProject } from "../model/openapi/open-api-async-model-project"; +import { OpenApiModelProject } from "../model/openapi/open-api-model-project"; +import { SmithyAsyncModelProject } from "../model/smithy/smithy-async-model-project"; +import { SmithyModelProject } from "../model/smithy/smithy-model-project"; +import { TypeSafeApiAsyncModelBuildOutputOptions } from "../model/type-safe-api-async-model-build"; +import { TypeSafeApiModelBuildOutputOptions } from "../model/type-safe-api-model-build"; +import { TypeSafeApiModelProjectOptions } from "../model/type-safe-api-model-project"; +import { TypeSafeWebSocketApiModelProjectOptions } from "../model/type-safe-websocket-api-model-project"; +import { + GeneratedDocumentationOptions, + GeneratedWebSocketDocumentationOptions, + ModelLanguage, + ModelProject, + WebSocketModelProject, +} from "../types"; const logger = getLogger(); @@ -648,6 +659,113 @@ export const generateLibraryProjects = ( return generatedLibraries; }; +export interface CommonModelProjectOptions { + readonly name: string; + readonly parent?: Project; + readonly outdir: string; +} + +export interface GenerateModelProjectOptions + extends CommonModelProjectOptions, + TypeSafeApiModelProjectOptions, + TypeSafeApiModelBuildOutputOptions {} + +export const generateModelProject = ({ + modelLanguage, + modelOptions, + ...options +}: GenerateModelProjectOptions): ModelProject => { + if (modelLanguage === ModelLanguage.SMITHY) { + if (!modelOptions.smithy) { + throw new Error( + `modelOptions.smithy is required when selected model language is ${ModelLanguage.SMITHY}` + ); + } + + const smithy = new SmithyModelProject({ + ...options, + smithyOptions: modelOptions.smithy, + }); + return { + smithy, + parsedSpecFile: options.parsedSpecFile, + apiName: smithy.apiName, + outdir: smithy.outdir, + }; + } else if (modelLanguage === ModelLanguage.OPENAPI) { + if (!modelOptions.openapi) { + throw new Error( + `modelOptions.openapi is required when selected model language is ${ModelLanguage.OPENAPI}` + ); + } + + const openapi = new OpenApiModelProject({ + ...options, + openApiOptions: modelOptions.openapi, + }); + return { + openapi, + parsedSpecFile: options.parsedSpecFile, + apiName: openapi.apiName, + outdir: openapi.outdir, + }; + } else { + throw new Error(`Unknown model language ${modelLanguage}`); + } +}; + +export interface GenerateAsyncModelProjectOptions + extends CommonModelProjectOptions, + TypeSafeWebSocketApiModelProjectOptions, + TypeSafeApiAsyncModelBuildOutputOptions, + TypeSafeApiModelBuildOutputOptions {} + +export const generateAsyncModelProject = ({ + modelLanguage, + modelOptions, + ...options +}: GenerateAsyncModelProjectOptions): WebSocketModelProject => { + if (modelLanguage === ModelLanguage.SMITHY) { + if (!modelOptions.smithy) { + throw new Error( + `modelOptions.smithy is required when selected model language is ${ModelLanguage.SMITHY}` + ); + } + + const smithy = new SmithyAsyncModelProject({ + ...options, + smithyOptions: modelOptions.smithy, + }); + return { + smithy, + parsedSpecFile: options.parsedSpecFile, + asyncApiSpecFile: options.asyncApiSpecFile, + apiName: smithy.apiName, + outdir: smithy.outdir, + }; + } else if (modelLanguage === ModelLanguage.OPENAPI) { + if (!modelOptions.openapi) { + throw new Error( + `modelOptions.openapi is required when selected model language is ${ModelLanguage.OPENAPI}` + ); + } + + const openapi = new OpenApiAsyncModelProject({ + ...options, + openApiOptions: modelOptions.openapi, + }); + return { + openapi, + parsedSpecFile: options.parsedSpecFile, + asyncApiSpecFile: options.asyncApiSpecFile, + apiName: openapi.apiName, + outdir: openapi.outdir, + }; + } else { + throw new Error(`Unknown model language ${modelLanguage}`); + } +}; + /** * Returns a generated client project for the given language */ diff --git a/packages/type-safe-api/src/project/index.ts b/packages/type-safe-api/src/project/index.ts index 37fe99f78..6f0909f2d 100644 --- a/packages/type-safe-api/src/project/index.ts +++ b/packages/type-safe-api/src/project/index.ts @@ -1,6 +1,7 @@ /*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -export * from "./model/type-safe-api-model-project-base"; +export * from "./model/type-safe-api-model-build"; +export * from "./model/type-safe-api-async-model-build"; export * from "./model/smithy/smithy-project-definition"; export * from "./model/openapi/open-api-project-definition"; export * from "./type-safe-api-project"; @@ -12,6 +13,10 @@ export * from "./model/type-safe-websocket-api-model-project"; export * from "./model/smithy/smithy-async-definition"; export * from "./model/openapi/open-api-async-definition"; export * from "./model/smithy/types"; +export * from "./model/openapi/open-api-model-project"; +export * from "./model/openapi/open-api-async-model-project"; +export * from "./model/smithy/smithy-async-model-project"; +export * from "./model/smithy/smithy-model-project"; export * from "./languages"; export * from "./types"; export * from "./typescript-project-options"; diff --git a/packages/type-safe-api/src/project/model/model-readme.ts b/packages/type-safe-api/src/project/model/model-readme.ts new file mode 100644 index 000000000..07a821a8c --- /dev/null +++ b/packages/type-safe-api/src/project/model/model-readme.ts @@ -0,0 +1,40 @@ +/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 */ +import * as path from "path"; +import { Project, SampleFile } from "projen"; +import { ModelLanguage } from "../types"; + +/** + * Options for the model readme + */ +export interface ModelReadmeOptions { + /** + * Language for the model + */ + readonly modelLanguage: ModelLanguage; + /** + * Type of API + */ + readonly apiType: "rest" | "async"; +} + +/** + * README.md file for a model project + */ +export class ModelReadme extends SampleFile { + constructor(project: Project, options: ModelReadmeOptions) { + super(project, "README.md", { + sourcePath: path.resolve( + __dirname, + "..", + "..", + "..", + "samples", + "type-safe-api", + "readme", + `model-${options.apiType}`, + `${options.modelLanguage}.md` + ), + }); + } +} diff --git a/packages/type-safe-api/src/project/model/openapi/open-api-async-definition.ts b/packages/type-safe-api/src/project/model/openapi/open-api-async-definition.ts index 88bddb138..d04e251ba 100644 --- a/packages/type-safe-api/src/project/model/openapi/open-api-async-definition.ts +++ b/packages/type-safe-api/src/project/model/openapi/open-api-async-definition.ts @@ -1,11 +1,10 @@ /*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -import { SampleFile } from "projen"; +import { Project, SampleFile } from "projen"; import { OpenApiProjectDefinition, OpenApiProjectDefinitionOptions, } from "./open-api-project-definition"; -import { TypeSafeWebSocketApiModelProject } from "../type-safe-websocket-api-model-project"; /** * Options for the OpenAPI Spec @@ -17,10 +16,7 @@ export interface OpenApiAsyncDefinitionOptions * The OpenAPI Spec */ export class OpenApiAsyncDefinition extends OpenApiProjectDefinition { - constructor( - project: TypeSafeWebSocketApiModelProject, - options: OpenApiAsyncDefinitionOptions - ) { + constructor(project: Project, options: OpenApiAsyncDefinitionOptions) { super(project, options); const firstHandlerLanguage = options.handlerLanguages?.[0]; diff --git a/packages/type-safe-api/src/project/model/openapi/open-api-async-model-project.ts b/packages/type-safe-api/src/project/model/openapi/open-api-async-model-project.ts new file mode 100644 index 000000000..cb1dec456 --- /dev/null +++ b/packages/type-safe-api/src/project/model/openapi/open-api-async-model-project.ts @@ -0,0 +1,65 @@ +/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 */ +import { Project, ProjectOptions } from "projen"; +import { + OpenApiAsyncDefinitionOptions, + OpenApiAsyncDefinition, +} from "./open-api-async-definition"; +import { TypeSafeApiCommandEnvironment } from "../../codegen/components/type-safe-api-command-environment"; +import { ModelLanguage } from "../../types"; +import { ModelReadme } from "../model-readme"; +import { + TypeSafeApiAsyncModelBuild, + TypeSafeApiAsyncModelBuildOutputOptions, +} from "../type-safe-api-async-model-build"; +import { + TypeSafeApiModelBuild, + TypeSafeApiModelBuildOutputOptions, +} from "../type-safe-api-model-build"; + +/** + * Options for an OpenAPI WebSocket API model + */ +export interface OpenApiAsyncModelProjectOptions + extends ProjectOptions, + OpenApiAsyncDefinitionOptions, + TypeSafeApiModelBuildOutputOptions, + TypeSafeApiAsyncModelBuildOutputOptions {} + +/** + * Project for defining an OpenAPI model for a WebSocket API + */ +export class OpenApiAsyncModelProject extends Project { + /** + * Name of the API + */ + public readonly apiName: string; + /** + * OpenAPI specification component + */ + public readonly definition: OpenApiAsyncDefinition; + + constructor(options: OpenApiAsyncModelProjectOptions) { + super(options); + TypeSafeApiCommandEnvironment.ensure(this); + + this.definition = new OpenApiAsyncDefinition(this, options); + + this.apiName = options.openApiOptions.title; + + new TypeSafeApiModelBuild(this, { + openApiSpecificationPath: this.definition.openApiSpecificationPath, + parsedSpecFile: options.parsedSpecFile, + }); + + new TypeSafeApiAsyncModelBuild(this, { + parsedSpecFile: options.parsedSpecFile, + asyncApiSpecFile: options.asyncApiSpecFile, + }); + + new ModelReadme(this, { + modelLanguage: ModelLanguage.OPENAPI, + apiType: "async", + }); + } +} diff --git a/packages/type-safe-api/src/project/model/openapi/open-api-definition.ts b/packages/type-safe-api/src/project/model/openapi/open-api-definition.ts index 8002b5eea..f3147d3bf 100644 --- a/packages/type-safe-api/src/project/model/openapi/open-api-definition.ts +++ b/packages/type-safe-api/src/project/model/openapi/open-api-definition.ts @@ -1,11 +1,10 @@ /*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -import { SampleFile } from "projen"; +import { Project, SampleFile } from "projen"; import { OpenApiProjectDefinition, OpenApiProjectDefinitionOptions, } from "./open-api-project-definition"; -import { TypeSafeApiModelProject } from "../type-safe-api-model-project"; /** * Options for the OpenAPI Spec @@ -17,10 +16,7 @@ export interface OpenApiDefinitionOptions * The OpenAPI Spec */ export class OpenApiDefinition extends OpenApiProjectDefinition { - constructor( - project: TypeSafeApiModelProject, - options: OpenApiDefinitionOptions - ) { + constructor(project: Project, options: OpenApiDefinitionOptions) { super(project, options); const firstHandlerLanguage = options.handlerLanguages?.[0]; diff --git a/packages/type-safe-api/src/project/model/openapi/open-api-model-project.ts b/packages/type-safe-api/src/project/model/openapi/open-api-model-project.ts new file mode 100644 index 000000000..0efbb8e7d --- /dev/null +++ b/packages/type-safe-api/src/project/model/openapi/open-api-model-project.ts @@ -0,0 +1,53 @@ +/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 */ +import { Project, ProjectOptions } from "projen"; +import { + OpenApiDefinition, + OpenApiDefinitionOptions, +} from "./open-api-definition"; +import { TypeSafeApiCommandEnvironment } from "../../codegen/components/type-safe-api-command-environment"; +import { ModelLanguage } from "../../types"; +import { ModelReadme } from "../model-readme"; +import { + TypeSafeApiModelBuild, + TypeSafeApiModelBuildOutputOptions, +} from "../type-safe-api-model-build"; + +export interface OpenApiModelProjectOptions + extends ProjectOptions, + OpenApiDefinitionOptions, + TypeSafeApiModelBuildOutputOptions {} + +/** + * Project for defining an OpenAPI model for a REST API + */ +export class OpenApiModelProject extends Project { + /** + * Name of the API + */ + public readonly apiName: string; + + /** + * OpenAPI specification component + */ + public readonly definition: OpenApiDefinition; + + constructor(options: OpenApiModelProjectOptions) { + super(options); + TypeSafeApiCommandEnvironment.ensure(this); + + this.definition = new OpenApiDefinition(this, options); + + this.apiName = options.openApiOptions.title; + + new TypeSafeApiModelBuild(this, { + openApiSpecificationPath: this.definition.openApiSpecificationPath, + parsedSpecFile: options.parsedSpecFile, + }); + + new ModelReadme(this, { + modelLanguage: ModelLanguage.OPENAPI, + apiType: "rest", + }); + } +} diff --git a/packages/type-safe-api/src/project/model/openapi/open-api-project-definition.ts b/packages/type-safe-api/src/project/model/openapi/open-api-project-definition.ts index 723dc2a7b..b79d2b90e 100644 --- a/packages/type-safe-api/src/project/model/openapi/open-api-project-definition.ts +++ b/packages/type-safe-api/src/project/model/openapi/open-api-project-definition.ts @@ -1,9 +1,8 @@ /*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -import { Component } from "projen"; +import { Component, Project } from "projen"; import { Language } from "../../languages"; import { OpenApiModelOptions } from "../../types"; -import { TypeSafeApiModelProjectBase } from "../type-safe-api-model-project-base"; /** * Options for the OpenAPI Spec @@ -29,10 +28,7 @@ export class OpenApiProjectDefinition extends Component { public readonly openApiSpecificationPath: string = "src/main/openapi/main.yaml"; - constructor( - project: TypeSafeApiModelProjectBase, - _options: OpenApiProjectDefinitionOptions - ) { + constructor(project: Project, _options: OpenApiProjectDefinitionOptions) { super(project); } } diff --git a/packages/type-safe-api/src/project/model/smithy/smithy-async-definition.ts b/packages/type-safe-api/src/project/model/smithy/smithy-async-definition.ts index b15f7539f..e1de1c260 100644 --- a/packages/type-safe-api/src/project/model/smithy/smithy-async-definition.ts +++ b/packages/type-safe-api/src/project/model/smithy/smithy-async-definition.ts @@ -1,16 +1,17 @@ /*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ +import { Project } from "projen"; import { SmithyAsyncSampleCode } from "./components/smithy-async-sample-code"; import { SmithyAwsPdkAsyncPrelude } from "./components/smithy-aws-pdk-async-prelude"; import { SmithyProjectDefinition, SmithyProjectDefinitionOptions, } from "./smithy-project-definition"; +import { GenerateTask } from "../../codegen/components/generate-task"; import { TypeSafeApiScript, buildTypeSafeApiExecCommand, } from "../../codegen/components/utils"; -import { TypeSafeWebSocketApiModelProject } from "../type-safe-websocket-api-model-project"; /** * Options for a smithy build project @@ -22,10 +23,7 @@ export interface SmithyAsyncDefinitionOptions * Creates a project which transforms a Smithy model for an async API to OpenAPI */ export class SmithyAsyncDefinition extends SmithyProjectDefinition { - constructor( - project: TypeSafeWebSocketApiModelProject, - options: SmithyAsyncDefinitionOptions - ) { + constructor(project: Project, options: SmithyAsyncDefinitionOptions) { super(project, { ...options, smithyOptions: { @@ -65,8 +63,10 @@ export class SmithyAsyncDefinition extends SmithyProjectDefinition { handlerLanguages: options.handlerLanguages, }); + const generateTask = GenerateTask.ensure(project); + // Copy the async transformer jar - project.generateTask.prependExec( + generateTask.prependExec( buildTypeSafeApiExecCommand( TypeSafeApiScript.COPY_ASYNC_SMITHY_TRANSFORMER ) diff --git a/packages/type-safe-api/src/project/model/smithy/smithy-async-model-project.ts b/packages/type-safe-api/src/project/model/smithy/smithy-async-model-project.ts new file mode 100644 index 000000000..bbe5f4199 --- /dev/null +++ b/packages/type-safe-api/src/project/model/smithy/smithy-async-model-project.ts @@ -0,0 +1,65 @@ +/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 */ +import { Project, ProjectOptions } from "projen"; +import { + SmithyAsyncDefinitionOptions, + SmithyAsyncDefinition, +} from "./smithy-async-definition"; +import { TypeSafeApiCommandEnvironment } from "../../codegen/components/type-safe-api-command-environment"; +import { ModelLanguage } from "../../types"; +import { ModelReadme } from "../model-readme"; +import { + TypeSafeApiAsyncModelBuild, + TypeSafeApiAsyncModelBuildOutputOptions, +} from "../type-safe-api-async-model-build"; +import { + TypeSafeApiModelBuild, + TypeSafeApiModelBuildOutputOptions, +} from "../type-safe-api-model-build"; + +/** + * Options for the Smithy WebSocket API model project + */ +export interface SmithyAsyncModelProjectOptions + extends ProjectOptions, + SmithyAsyncDefinitionOptions, + TypeSafeApiModelBuildOutputOptions, + TypeSafeApiAsyncModelBuildOutputOptions {} + +/** + * Smithy model project for a WebSocket API + */ +export class SmithyAsyncModelProject extends Project { + /** + * Name of the API + */ + public readonly apiName: string; + /** + * Smithy model and build settings + */ + public readonly definition: SmithyAsyncDefinition; + + constructor(options: SmithyAsyncModelProjectOptions) { + super(options); + TypeSafeApiCommandEnvironment.ensure(this); + + this.definition = new SmithyAsyncDefinition(this, options); + this.apiName = options.smithyOptions.serviceName.serviceName; + + new TypeSafeApiModelBuild(this, { + openApiSpecificationPath: this.definition.openApiSpecificationPath, + smithyJsonModelPath: this.definition.smithyJsonModelPath, + parsedSpecFile: options.parsedSpecFile, + }); + + new TypeSafeApiAsyncModelBuild(this, { + parsedSpecFile: options.parsedSpecFile, + asyncApiSpecFile: options.asyncApiSpecFile, + }); + + new ModelReadme(this, { + modelLanguage: ModelLanguage.SMITHY, + apiType: "async", + }); + } +} diff --git a/packages/type-safe-api/src/project/model/smithy/smithy-definition.ts b/packages/type-safe-api/src/project/model/smithy/smithy-definition.ts index a7d9b68b4..3f4ec374d 100644 --- a/packages/type-safe-api/src/project/model/smithy/smithy-definition.ts +++ b/packages/type-safe-api/src/project/model/smithy/smithy-definition.ts @@ -1,11 +1,11 @@ /*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ +import { Project } from "projen"; import { SmithySampleCode } from "./components/smithy-sample-code"; import { SmithyProjectDefinition, SmithyProjectDefinitionOptions, } from "./smithy-project-definition"; -import { TypeSafeApiModelProject } from "../type-safe-api-model-project"; /** * Options for a smithy build project @@ -17,10 +17,7 @@ export interface SmithyDefinitionOptions * Creates a project which transforms a Smithy model to OpenAPI */ export class SmithyDefinition extends SmithyProjectDefinition { - constructor( - project: TypeSafeApiModelProject, - options: SmithyDefinitionOptions - ) { + constructor(project: Project, options: SmithyDefinitionOptions) { super(project, options); const { namespace: serviceNamespace, serviceName } = diff --git a/packages/type-safe-api/src/project/model/smithy/smithy-model-project.ts b/packages/type-safe-api/src/project/model/smithy/smithy-model-project.ts new file mode 100644 index 000000000..593a34d3b --- /dev/null +++ b/packages/type-safe-api/src/project/model/smithy/smithy-model-project.ts @@ -0,0 +1,52 @@ +/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 */ +import { Project, ProjectOptions } from "projen"; +import { SmithyDefinition, SmithyDefinitionOptions } from "./smithy-definition"; +import { TypeSafeApiCommandEnvironment } from "../../codegen/components/type-safe-api-command-environment"; +import { ModelLanguage } from "../../types"; +import { ModelReadme } from "../model-readme"; +import { + TypeSafeApiModelBuild, + TypeSafeApiModelBuildOutputOptions, +} from "../type-safe-api-model-build"; + +/** + * Options for the Smithy REST API model + */ +export interface SmithyModelProjectOptions + extends ProjectOptions, + SmithyDefinitionOptions, + TypeSafeApiModelBuildOutputOptions {} + +/** + * Smithy model project for a REST API + */ +export class SmithyModelProject extends Project { + /** + * Name of the API + */ + public readonly apiName: string; + /** + * Smithy model and build settings + */ + public readonly definition: SmithyDefinition; + + constructor(options: SmithyModelProjectOptions) { + super(options); + TypeSafeApiCommandEnvironment.ensure(this); + + this.definition = new SmithyDefinition(this, options); + this.apiName = options.smithyOptions.serviceName.serviceName; + + new TypeSafeApiModelBuild(this, { + openApiSpecificationPath: this.definition.openApiSpecificationPath, + smithyJsonModelPath: this.definition.smithyJsonModelPath, + parsedSpecFile: options.parsedSpecFile, + }); + + new ModelReadme(this, { + modelLanguage: ModelLanguage.SMITHY, + apiType: "rest", + }); + } +} diff --git a/packages/type-safe-api/src/project/model/smithy/smithy-project-definition.ts b/packages/type-safe-api/src/project/model/smithy/smithy-project-definition.ts index 4ab17f03c..81d6e4d0d 100644 --- a/packages/type-safe-api/src/project/model/smithy/smithy-project-definition.ts +++ b/packages/type-safe-api/src/project/model/smithy/smithy-project-definition.ts @@ -1,19 +1,19 @@ /*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ import * as path from "path"; -import { Component } from "projen"; +import { Component, Project } from "projen"; import { SmithyBuild } from "projen/lib/smithy/smithy-build"; import { SmithyAwsPdkPrelude } from "./components/smithy-aws-pdk-prelude"; import { SmithyBuildGradleFile } from "./components/smithy-build-gradle-file"; import { SmithySettingsGradleFile } from "./components/smithy-settings-gradle-file"; import { DEFAULT_SMITHY_VERSION } from "./version"; +import { GenerateTask } from "../../codegen/components/generate-task"; import { buildTypeSafeApiExecCommand, TypeSafeApiScript, } from "../../codegen/components/utils"; import { Language } from "../../languages"; import { SmithyModelOptions } from "../../types"; -import { TypeSafeApiModelProjectBase } from "../type-safe-api-model-project-base"; /** * Options for a smithy build project @@ -64,10 +64,7 @@ export class SmithyProjectDefinition extends Component { */ protected readonly generatedModelDir: string; - constructor( - project: TypeSafeApiModelProjectBase, - options: SmithyProjectDefinitionOptions - ) { + constructor(project: Project, options: SmithyProjectDefinitionOptions) { super(project); const { smithyOptions } = options; @@ -193,14 +190,16 @@ export class SmithyProjectDefinition extends Component { "model.json" ); + const generateTask = GenerateTask.ensure(project); + // Copy the gradle files during build if they don't exist. We don't overwrite to allow users to BYO gradle wrapper // and set `ignoreGradleWrapper: false`. - project.generateTask.exec( + generateTask.exec( buildTypeSafeApiExecCommand(TypeSafeApiScript.COPY_GRADLE_WRAPPER) ); // Build with gradle to generate smithy projections, and any other tasks - project.generateTask.exec("./gradlew build"); + generateTask.exec("./gradlew build"); if (smithyOptions.ignoreSmithyBuildOutput ?? true) { // Ignore the build directory, and smithy-output which was the old build directory for the cli-based generation diff --git a/packages/type-safe-api/src/project/model/type-safe-api-async-model-build.ts b/packages/type-safe-api/src/project/model/type-safe-api-async-model-build.ts new file mode 100644 index 000000000..6a75eb12e --- /dev/null +++ b/packages/type-safe-api/src/project/model/type-safe-api-async-model-build.ts @@ -0,0 +1,49 @@ +/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 */ +import { Component, Project } from "projen"; +import { TypeSafeApiModelBuildOutputOptions } from "./type-safe-api-model-build"; +import { GenerateTask } from "../codegen/components/generate-task"; +import { + buildTypeSafeApiExecCommand, + TypeSafeApiScript, +} from "../codegen/components/utils"; + +/** + * Output of the OpenAPI to AsyncAPI task + */ +export interface TypeSafeApiAsyncModelBuildOutputOptions { + /** + * Path to the generated AsyncAPI specification (relative to the project root) + */ + readonly asyncApiSpecFile: string; +} + +/** + * Options for converting OpenAPI to AsyncAPI + */ +export interface TypeSafeApiAsyncModelBuildOptions + extends TypeSafeApiAsyncModelBuildOutputOptions, + TypeSafeApiModelBuildOutputOptions {} + +/** + * Adds a task to convert the OpenAPI specification into an AsyncAPI specification, + * which can be used for documentation generation. + */ +export class TypeSafeApiAsyncModelBuild extends Component { + constructor(project: Project, options: TypeSafeApiAsyncModelBuildOptions) { + super(project); + + const { parsedSpecFile, asyncApiSpecFile } = options; + + const generateTask = GenerateTask.ensure(project); + + generateTask.exec( + buildTypeSafeApiExecCommand( + TypeSafeApiScript.GENERATE_ASYNCAPI_SPEC, + `--specPath ${parsedSpecFile} --outputPath ${asyncApiSpecFile}` + ) + ); + + project.addGitIgnore(asyncApiSpecFile); + } +} diff --git a/packages/type-safe-api/src/project/model/type-safe-api-model-build.ts b/packages/type-safe-api/src/project/model/type-safe-api-model-build.ts new file mode 100644 index 000000000..ae634260a --- /dev/null +++ b/packages/type-safe-api/src/project/model/type-safe-api-model-build.ts @@ -0,0 +1,60 @@ +/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 */ +import { Component, Project } from "projen"; +import { GenerateTask } from "../codegen/components/generate-task"; +import { + buildTypeSafeApiExecCommand, + TypeSafeApiScript, +} from "../codegen/components/utils"; + +/** + * Output for the OpenAPI parse/bundle task + */ +export interface TypeSafeApiModelBuildOutputOptions { + /** + * Path of the parsed/bundled OpenAPI specification (relative to the project root) + */ + readonly parsedSpecFile: string; +} + +/** + * Options for configuring the OpenAPI parse/bundle task + */ +export interface TypeSafeApiModelBuildOptions + extends TypeSafeApiModelBuildOutputOptions { + /** + * Path to the OpenAPI specification + */ + readonly openApiSpecificationPath: string; + /** + * Optional path to the Smithy JSON model (for Smithy projects only) + */ + readonly smithyJsonModelPath?: string; +} + +/** + * Adds the build task for parsing/bundling an OpenAPI spec ready for use by code generation projects + */ +export class TypeSafeApiModelBuild extends Component { + constructor(project: Project, options: TypeSafeApiModelBuildOptions) { + super(project); + + const { openApiSpecificationPath, smithyJsonModelPath, parsedSpecFile } = + options; + + const generateTask = GenerateTask.ensure(project); + + generateTask.exec( + buildTypeSafeApiExecCommand( + TypeSafeApiScript.PARSE_OPENAPI_SPEC, + `--specPath ${openApiSpecificationPath} --outputPath ${parsedSpecFile}${ + smithyJsonModelPath ? ` --smithyJsonPath ${smithyJsonModelPath}` : "" + }` + ) + ); + + project.compileTask.spawn(generateTask); + + project.addGitIgnore(parsedSpecFile); + } +} diff --git a/packages/type-safe-api/src/project/model/type-safe-api-model-project-base.ts b/packages/type-safe-api/src/project/model/type-safe-api-model-project-base.ts deleted file mode 100644 index 6b7371cbc..000000000 --- a/packages/type-safe-api/src/project/model/type-safe-api-model-project-base.ts +++ /dev/null @@ -1,138 +0,0 @@ -/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. -SPDX-License-Identifier: Apache-2.0 */ -import { Project, ProjectOptions, Task } from "projen"; -import { OpenApiProjectDefinition } from "./openapi/open-api-project-definition"; -import { SmithyProjectDefinition } from "./smithy/smithy-project-definition"; -import { TypeSafeApiCommandEnvironment } from "../codegen/components/type-safe-api-command-environment"; -import { - buildTypeSafeApiExecCommand, - TypeSafeApiScript, -} from "../codegen/components/utils"; -import { Language } from "../languages"; -import { - ModelLanguage, - ModelOptions, - OpenApiModelOptions, - SmithyModelOptions, -} from "../types"; - -export interface TypeSafeApiModelProjectBaseOptions extends ProjectOptions { - /** - * Language the model is defined in - */ - readonly modelLanguage: ModelLanguage; - /** - * Options for the model - */ - readonly modelOptions: ModelOptions; - /** - * The languages users have specified for handler projects (if any) - */ - readonly handlerLanguages?: Language[]; -} - -export abstract class TypeSafeApiModelProjectBase extends Project { - /** - * Name of the final bundled OpenAPI specification - */ - public readonly parsedSpecFile: string = ".api.json"; - /** - * Reference to the task used for generating the final bundled OpenAPI specification - */ - public readonly generateTask: Task; - - /** - * Name of the API. If Smithy, will resolve to serviceName otherwise it will use title. - */ - public readonly apiName; - - constructor(options: TypeSafeApiModelProjectBaseOptions) { - super(options); - TypeSafeApiCommandEnvironment.ensure(this); - - this.generateTask = this.addTask("generate"); - - // Add the API definition - const { specPath, smithyJsonModelPath } = this.addApiDefinition(options); - - this.apiName = - options.modelOptions.smithy?.serviceName.serviceName ?? - options.modelOptions.openapi?.title; - - // Parse and bundle the openapi specification - this.addParseAndBundleTask(specPath, smithyJsonModelPath); - - // Run the generate task as part of build - this.compileTask.spawn(this.generateTask); - } - - protected abstract addSmithyApiDefinition( - options: SmithyModelOptions, - handlerLanguages?: Language[] - ): SmithyProjectDefinition; - protected abstract addOpenApiDefinition( - options: OpenApiModelOptions, - handlerLanguages?: Language[] - ): OpenApiProjectDefinition; - - private addApiDefinition = ({ - modelLanguage, - modelOptions, - handlerLanguages, - }: TypeSafeApiModelProjectBaseOptions): { - specPath: string; - smithyJsonModelPath?: string; - } => { - if (modelLanguage === ModelLanguage.SMITHY) { - if (!modelOptions.smithy) { - throw new Error( - `modelOptions.smithy is required when selected model language is ${ModelLanguage.SMITHY}` - ); - } - - const smithyOptions = modelOptions.smithy; - const smithy = this.addSmithyApiDefinition( - smithyOptions, - handlerLanguages - ); - - return { - specPath: smithy.openApiSpecificationPath, - smithyJsonModelPath: smithy.smithyJsonModelPath, - }; - } else if (modelLanguage === ModelLanguage.OPENAPI) { - if (!modelOptions.openapi) { - throw new Error( - `modelOptions.openapi is required when selected model language is ${ModelLanguage.OPENAPI}` - ); - } - - const openApiOptions = modelOptions.openapi; - const openapi = this.addOpenApiDefinition( - openApiOptions, - handlerLanguages - ); - return { specPath: openapi.openApiSpecificationPath }; - } else { - throw new Error(`Unknown model language ${modelLanguage}`); - } - }; - - private addParseAndBundleTask = ( - openApiSpecificationPath: string, - smithyJsonModelPath?: string - ) => { - this.generateTask.exec( - buildTypeSafeApiExecCommand( - TypeSafeApiScript.PARSE_OPENAPI_SPEC, - `--specPath ${openApiSpecificationPath} --outputPath ${ - this.parsedSpecFile - }${ - smithyJsonModelPath ? ` --smithyJsonPath ${smithyJsonModelPath}` : "" - }` - ) - ); - - this.addGitIgnore(this.parsedSpecFile); - }; -} diff --git a/packages/type-safe-api/src/project/model/type-safe-api-model-project.ts b/packages/type-safe-api/src/project/model/type-safe-api-model-project.ts index 7ad0d81d2..f522d4490 100644 --- a/packages/type-safe-api/src/project/model/type-safe-api-model-project.ts +++ b/packages/type-safe-api/src/project/model/type-safe-api-model-project.ts @@ -1,74 +1,19 @@ /*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -import * as path from "path"; -import { SampleFile } from "projen"; -import { OpenApiDefinition } from "./openapi/open-api-definition"; -import { OpenApiProjectDefinition } from "./openapi/open-api-project-definition"; -import { SmithyDefinition } from "./smithy/smithy-definition"; -import { SmithyProjectDefinition } from "./smithy/smithy-project-definition"; -import { - TypeSafeApiModelProjectBase, - TypeSafeApiModelProjectBaseOptions, -} from "./type-safe-api-model-project-base"; import { Language } from "../languages"; -import { OpenApiModelOptions, SmithyModelOptions } from "../types"; +import { ModelLanguage, ModelOptions } from "../types"; -export interface TypeSafeApiModelProjectOptions - extends TypeSafeApiModelProjectBaseOptions {} - -export class TypeSafeApiModelProject extends TypeSafeApiModelProjectBase { +export interface TypeSafeApiModelProjectOptions { /** - * Reference to the Smithy definition component. Will be defined if the model language is Smithy + * Language the model is defined in */ - public readonly smithy?: SmithyDefinition; - + readonly modelLanguage: ModelLanguage; /** - * Reference to the OpenAPI definition component. Will be defined if the model language is OpenAPI + * Options for the model */ - public readonly openapi?: OpenApiDefinition; - - constructor(options: TypeSafeApiModelProjectOptions) { - super(options); - - // Add the README as a sample file which the user may edit - new SampleFile(this, "README.md", { - sourcePath: path.resolve( - __dirname, - "..", - "..", - "..", - "samples", - "type-safe-api", - "readme", - "model-rest", - `${options.modelLanguage}.md` - ), - }); - } - - protected addSmithyApiDefinition( - options: SmithyModelOptions, - handlerLanguages?: Language[] | undefined - ): SmithyProjectDefinition { - const smithy = new SmithyDefinition(this, { - smithyOptions: options, - handlerLanguages, - }); - // @ts-ignore called from constructor - this.smithy = smithy; - return smithy; - } - - protected addOpenApiDefinition( - options: OpenApiModelOptions, - handlerLanguages?: Language[] | undefined - ): OpenApiProjectDefinition { - const openapi = new OpenApiDefinition(this, { - openApiOptions: options, - handlerLanguages, - }); - // @ts-ignore called from constructor - this.openapi = openapi; - return openapi; - } + readonly modelOptions: ModelOptions; + /** + * The languages users have specified for handler projects (if any) + */ + readonly handlerLanguages?: Language[]; } diff --git a/packages/type-safe-api/src/project/model/type-safe-websocket-api-model-project.ts b/packages/type-safe-api/src/project/model/type-safe-websocket-api-model-project.ts index e4ca0befb..a6329c0e6 100644 --- a/packages/type-safe-api/src/project/model/type-safe-websocket-api-model-project.ts +++ b/packages/type-safe-api/src/project/model/type-safe-websocket-api-model-project.ts @@ -1,94 +1,6 @@ /*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -import * as path from "path"; -import { SampleFile } from "projen"; -import { OpenApiAsyncDefinition } from "./openapi/open-api-async-definition"; -import { OpenApiProjectDefinition } from "./openapi/open-api-project-definition"; -import { SmithyAsyncDefinition } from "./smithy/smithy-async-definition"; -import { SmithyProjectDefinition } from "./smithy/smithy-project-definition"; -import { - TypeSafeApiModelProjectBase, - TypeSafeApiModelProjectBaseOptions, -} from "./type-safe-api-model-project-base"; -import { - TypeSafeApiScript, - buildTypeSafeApiExecCommand, -} from "../codegen/components/utils"; -import { Language } from "../languages"; -import { OpenApiModelOptions, SmithyModelOptions } from "../types"; +import { TypeSafeApiModelProjectOptions } from "./type-safe-api-model-project"; export interface TypeSafeWebSocketApiModelProjectOptions - extends TypeSafeApiModelProjectBaseOptions {} - -export class TypeSafeWebSocketApiModelProject extends TypeSafeApiModelProjectBase { - /** - * Reference to the Smithy definition component. Will be defined if the model language is Smithy - */ - public readonly smithy?: SmithyAsyncDefinition; - - /** - * Reference to the OpenAPI definition component. Will be defined if the model language is OpenAPI - */ - public readonly openapi?: OpenApiAsyncDefinition; - - /** - * Async API specification path, generated by Type Safe API for documentation generation - */ - public readonly asyncApiSpecFile: string = ".asyncapi.json"; - - constructor(options: TypeSafeWebSocketApiModelProjectOptions) { - super(options); - - // Add the README as a sample file which the user may edit - new SampleFile(this, "README.md", { - sourcePath: path.resolve( - __dirname, - "..", - "..", - "..", - "samples", - "type-safe-api", - "readme", - "model-async", - `${options.modelLanguage}.md` - ), - }); - - // Add a task to convert the OpenAPI into an AsyncAPI specification, - // which can be used for documentation generation. - this.generateTask.exec( - buildTypeSafeApiExecCommand( - TypeSafeApiScript.GENERATE_ASYNCAPI_SPEC, - `--specPath ${this.parsedSpecFile} --outputPath ${this.asyncApiSpecFile}` - ) - ); - - this.addGitIgnore(this.asyncApiSpecFile); - } - - protected addSmithyApiDefinition( - options: SmithyModelOptions, - handlerLanguages?: Language[] | undefined - ): SmithyProjectDefinition { - const smithy = new SmithyAsyncDefinition(this, { - smithyOptions: options, - handlerLanguages, - }); - // @ts-ignore called from constructor - this.smithy = smithy; - return smithy; - } - - protected addOpenApiDefinition( - options: OpenApiModelOptions, - handlerLanguages?: Language[] | undefined - ): OpenApiProjectDefinition { - const openapi = new OpenApiAsyncDefinition(this, { - openApiOptions: options, - handlerLanguages, - }); - // @ts-ignore called from constructor - this.openapi = openapi; - return openapi; - } -} + extends TypeSafeApiModelProjectOptions {} diff --git a/packages/type-safe-api/src/project/type-safe-api-project.ts b/packages/type-safe-api/src/project/type-safe-api-project.ts index 6667f813b..41725a73d 100644 --- a/packages/type-safe-api/src/project/type-safe-api-project.ts +++ b/packages/type-safe-api/src/project/type-safe-api-project.ts @@ -21,6 +21,7 @@ import { generateInfraProject, generateLibraryProjects, generateHandlersProjects, + generateModelProject, } from "./codegen/generate"; import { GeneratedJavaHandlersProject } from "./codegen/handlers/generated-java-handlers-project"; import { GeneratedPythonHandlersProject } from "./codegen/handlers/generated-python-handlers-project"; @@ -29,7 +30,6 @@ import { GeneratedJavaRuntimeProject } from "./codegen/runtime/generated-java-ru import { GeneratedPythonRuntimeProject } from "./codegen/runtime/generated-python-runtime-project"; import { GeneratedTypescriptRuntimeProject } from "./codegen/runtime/generated-typescript-runtime-project"; import { DocumentationFormat, Language, Library } from "./languages"; -import { TypeSafeApiModelProject } from "./model/type-safe-api-model-project"; import { GeneratedRuntimeCodeOptions, GeneratedCodeProjects, @@ -42,6 +42,7 @@ import { GeneratedInfrastructureCodeOptions, GeneratedHandlersCodeOptions, ProjectCollections, + ModelProject, } from "./types"; /** @@ -177,7 +178,7 @@ export class TypeSafeApiProject extends Project { /** * Project for the api model */ - public readonly model: TypeSafeApiModelProject; + public readonly model: ModelProject; /** * Generated runtime projects. When `runtime.languages` includes the corresponding language, the project can be * assumed to be defined. @@ -217,16 +218,30 @@ export class TypeSafeApiProject extends Project { const handlerLanguages = [...new Set(options.handlers?.languages ?? [])]; + // Try to infer monorepo default release branch, otherwise default to mainline unless overridden + const defaultReleaseBranch = + nxWorkspace?.affected.defaultBase ?? "mainline"; + const packageManager = + this.parent && ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) + ? this.parent.package.packageManager + : NodePackageManager.PNPM; + // API Definition project containing the model const modelDir = "model"; - this.model = new TypeSafeApiModelProject({ + const parsedSpecFile = ".api.json"; + + this.model = generateModelProject({ parent: nxWorkspace ? this.parent : this, outdir: nxWorkspace ? path.join(options.outdir!, modelDir) : modelDir, name: `${options.name}-model`, modelLanguage: options.model.language, modelOptions: options.model.options, handlerLanguages, + parsedSpecFile, }); + const modelProject = [this.model.openapi, this.model.smithy].filter( + (m) => m + )[0] as Project; // Ensure we always generate a runtime project for the infrastructure language, regardless of what was specified by // the user. Likewise we generate a runtime project for any handler languages specified @@ -263,13 +278,8 @@ export class TypeSafeApiProject extends Project { // Spec path relative to each generated runtime dir parsedSpecPath: parsedSpecRelativeToGeneratedPackageDir, typescriptOptions: { - // Try to infer monorepo default release branch, otherwise default to mainline unless overridden - defaultReleaseBranch: nxWorkspace?.affected?.defaultBase ?? "mainline", - packageManager: - this.parent && - ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) - ? this.parent.package.packageManager - : NodePackageManager.PNPM, + defaultReleaseBranch, + packageManager, commitGeneratedCode: options.runtime?.options?.typescript?.commitGeneratedCode ?? options.commitGeneratedCode ?? @@ -336,13 +346,8 @@ export class TypeSafeApiProject extends Project { // Spec path relative to each generated library dir parsedSpecPath: parsedSpecRelativeToGeneratedPackageDir, typescriptReactQueryHooksOptions: { - // Try to infer monorepo default release branch, otherwise default to mainline unless overridden - defaultReleaseBranch: nxWorkspace?.affected.defaultBase ?? "mainline", - packageManager: - this.parent && - ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) - ? this.parent.package.packageManager - : NodePackageManager.PNPM, + defaultReleaseBranch, + packageManager, commitGeneratedCode: options.library?.options?.typescriptReactQueryHooks ?.commitGeneratedCode ?? @@ -359,7 +364,7 @@ export class TypeSafeApiProject extends Project { ...Object.values(generatedDocs), ...Object.values(generatedLibraryProjects), ].forEach((project) => { - NxProject.ensure(project).addImplicitDependency(this.model); + NxProject.ensure(project).addImplicitDependency(modelProject); }); } @@ -496,13 +501,8 @@ export class TypeSafeApiProject extends Project { // Spec path relative to each generated infra package dir parsedSpecPath: parsedSpecRelativeToGeneratedPackageDir, typescriptOptions: { - // Try to infer monorepo default release branch, otherwise default to mainline unless overridden - defaultReleaseBranch: nxWorkspace?.affected.defaultBase ?? "mainline", - packageManager: - this.parent && - ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) - ? this.parent.package.packageManager - : NodePackageManager.PNPM, + defaultReleaseBranch, + packageManager, commitGeneratedCode: options.infrastructure.options?.typescript?.commitGeneratedCode ?? options.commitGeneratedCode ?? @@ -578,7 +578,7 @@ export class TypeSafeApiProject extends Project { } this.infrastructure = infraProjects; - NxProject.ensure(infraProject).addImplicitDependency(this.model); + NxProject.ensure(infraProject).addImplicitDependency(modelProject); // Expose collections of projects const allRuntimes = Object.values(generatedRuntimeProjects); @@ -588,14 +588,14 @@ export class TypeSafeApiProject extends Project { const allHandlers = Object.values(generatedHandlersProjects); this.all = { - model: [this.model], + model: [modelProject], runtimes: allRuntimes, infrastructure: allInfrastructure, libraries: allLibraries, documentation: allDocumentation, handlers: allHandlers, projects: [ - this.model, + modelProject, ...allRuntimes, ...allInfrastructure, ...allLibraries, @@ -607,7 +607,7 @@ export class TypeSafeApiProject extends Project { if (!nxWorkspace) { // Add a task for the non-monorepo case to build the projects in the right order [ - this.model, + modelProject, ...Object.values(generatedRuntimeProjects), infraProject, ...Object.values(generatedLibraryProjects), diff --git a/packages/type-safe-api/src/project/type-safe-websocket-api-project.ts b/packages/type-safe-api/src/project/type-safe-websocket-api-project.ts index 6cf08d61c..e28a45a3d 100644 --- a/packages/type-safe-api/src/project/type-safe-websocket-api-project.ts +++ b/packages/type-safe-api/src/project/type-safe-websocket-api-project.ts @@ -20,6 +20,7 @@ import { generateAsyncHandlersProjects, generateAsyncInfraProject, generateAsyncLibraryProjects, + generateAsyncModelProject, } from "./codegen/generate"; import { GeneratedJavaAsyncHandlersProject } from "./codegen/handlers/generated-java-async-handlers-project"; import { GeneratedPythonAsyncHandlersProject } from "./codegen/handlers/generated-python-async-handlers-project"; @@ -32,7 +33,6 @@ import { WebSocketDocumentationFormat, WebSocketLibrary, } from "./languages"; -import { TypeSafeWebSocketApiModelProject } from "./model/type-safe-websocket-api-model-project"; import { ModelConfiguration } from "./type-safe-api-project"; import { GeneratedRuntimeCodeOptions, @@ -44,6 +44,7 @@ import { GeneratedWebSocketLibraryProjects, GeneratedWebSocketDocumentationProjects, GeneratedWebSocketDocumentationOptions, + WebSocketModelProject, } from "./types"; /** @@ -161,9 +162,9 @@ export interface TypeSafeWebSocketApiProjectOptions extends ProjectOptions { */ export class TypeSafeWebSocketApiProject extends Project { /** - * Project for the api model + * Project for the api model. */ - public readonly model: TypeSafeWebSocketApiModelProject; + public readonly model: WebSocketModelProject; /** * Generated runtime projects. When `runtime.languages` includes the corresponding language, the project can be * assumed to be defined. @@ -203,16 +204,31 @@ export class TypeSafeWebSocketApiProject extends Project { const handlerLanguages = [...new Set(options.handlers?.languages ?? [])]; + // Try to infer monorepo default release branch, otherwise default to mainline unless overridden + const defaultReleaseBranch = + nxWorkspace?.affected.defaultBase ?? "mainline"; + const packageManager = + this.parent && ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) + ? this.parent.package.packageManager + : NodePackageManager.PNPM; + // API Definition project containing the model const modelDir = "model"; - this.model = new TypeSafeWebSocketApiModelProject({ + const parsedSpecFile = ".api.json"; + const asyncApiSpecFile = ".asyncapi.json"; + this.model = generateAsyncModelProject({ parent: nxWorkspace ? this.parent : this, outdir: nxWorkspace ? path.join(options.outdir!, modelDir) : modelDir, name: `${options.name}-model`, modelLanguage: options.model.language, modelOptions: options.model.options, handlerLanguages, + parsedSpecFile, + asyncApiSpecFile, }); + const modelProject = [this.model.openapi, this.model.smithy].filter( + (m) => m + )[0] as Project; // Ensure we always generate a runtime project for the infrastructure language, regardless of what was specified by // the user. Likewise we generate a runtime project for any handler languages specified @@ -271,14 +287,8 @@ export class TypeSafeWebSocketApiProject extends Project { // Spec path relative to each generated runtime dir parsedSpecPath: parsedSpecRelativeToGeneratedPackageDir, typescriptOptions: { - // Try to infer monorepo default release branch, otherwise default to mainline unless overridden - defaultReleaseBranch: - nxWorkspace?.affected?.defaultBase ?? "mainline", - packageManager: - this.parent && - ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) - ? this.parent.package.packageManager - : NodePackageManager.PNPM, + defaultReleaseBranch, + packageManager, ...options.runtime?.options?.typescript, }, pythonOptions: { @@ -354,25 +364,15 @@ export class TypeSafeWebSocketApiProject extends Project { // Spec path relative to each generated library dir parsedSpecPath: parsedSpecRelativeToGeneratedPackageDir, typescriptWebSocketClientOptions: { - // Try to infer monorepo default release branch, otherwise default to mainline unless overridden - defaultReleaseBranch: nxWorkspace?.affected.defaultBase ?? "mainline", - packageManager: - this.parent && - ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) - ? this.parent.package.packageManager - : NodePackageManager.PNPM, + defaultReleaseBranch, + packageManager, ...options.library?.options?.typescriptWebSocketClient, }, typescriptWebSocketHooksOptions: { - // Try to infer monorepo default release branch, otherwise default to mainline unless overridden - defaultReleaseBranch: nxWorkspace?.affected.defaultBase ?? "mainline", + defaultReleaseBranch, clientPackageName: options.library?.options?.typescriptWebSocketClient?.name, - packageManager: - this.parent && - ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) - ? this.parent.package.packageManager - : NodePackageManager.PNPM, + packageManager, ...options.library?.options?.typescriptWebSocketHooks, }, }); @@ -384,7 +384,7 @@ export class TypeSafeWebSocketApiProject extends Project { ...Object.values(generatedDocs), ...Object.values(generatedLibraryProjects), ].forEach((project) => { - NxProject.ensure(project).addImplicitDependency(this.model); + NxProject.ensure(project).addImplicitDependency(modelProject); }); } @@ -452,13 +452,8 @@ export class TypeSafeWebSocketApiProject extends Project { // Spec path relative to each generated handlers package dir parsedSpecPath: parsedSpecRelativeToHandlersDir, typescriptOptions: { - // Try to infer monorepo default release branch, otherwise default to mainline unless overridden - defaultReleaseBranch: nxWorkspace?.affected.defaultBase ?? "mainline", - packageManager: - this.parent && - ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) - ? this.parent.package.packageManager - : NodePackageManager.PNPM, + defaultReleaseBranch, + packageManager, ...options.handlers?.options?.typescript, }, pythonOptions: { @@ -530,13 +525,8 @@ export class TypeSafeWebSocketApiProject extends Project { // Spec path relative to each generated infra package dir parsedSpecPath: parsedSpecRelativeToGeneratedPackageDir, typescriptOptions: { - // Try to infer monorepo default release branch, otherwise default to mainline unless overridden - defaultReleaseBranch: nxWorkspace?.affected.defaultBase ?? "mainline", - packageManager: - this.parent && - ProjectUtils.isNamedInstanceOf(this.parent, NodeProject) - ? this.parent.package.packageManager - : NodePackageManager.PNPM, + defaultReleaseBranch, + packageManager, ...options.infrastructure.options?.typescript, }, pythonOptions: { @@ -605,7 +595,7 @@ export class TypeSafeWebSocketApiProject extends Project { } this.infrastructure = infraProjects; - NxProject.ensure(infraProject).addImplicitDependency(this.model); + NxProject.ensure(infraProject).addImplicitDependency(modelProject); // Expose collections of projects const allRuntimes = Object.values(generatedRuntimeProjects); @@ -615,14 +605,14 @@ export class TypeSafeWebSocketApiProject extends Project { const allHandlers = Object.values(generatedHandlersProjects); this.all = { - model: [this.model], + model: [modelProject], runtimes: allRuntimes, infrastructure: allInfrastructure, libraries: allLibraries, documentation: allDocumentation, handlers: allHandlers, projects: [ - this.model, + modelProject, ...allRuntimes, ...allInfrastructure, ...allLibraries, @@ -634,7 +624,7 @@ export class TypeSafeWebSocketApiProject extends Project { if (!nxWorkspace) { // Add a task for the non-monorepo case to build the projects in the right order [ - this.model, + modelProject, ...Object.values(generatedRuntimeProjects), infraProject, ...Object.values(generatedLibraryProjects), diff --git a/packages/type-safe-api/src/project/types.ts b/packages/type-safe-api/src/project/types.ts index 0467e9748..bf53ee997 100644 --- a/packages/type-safe-api/src/project/types.ts +++ b/packages/type-safe-api/src/project/types.ts @@ -6,6 +6,10 @@ import { PythonProject } from "projen/lib/python"; import { TypeScriptProject } from "projen/lib/typescript"; import { JavaProjectOptions } from "./java-project-options"; import { JavaVersion, NodeVersion, PythonVersion } from "./languages"; +import { OpenApiAsyncModelProject } from "./model/openapi/open-api-async-model-project"; +import { OpenApiModelProject } from "./model/openapi/open-api-model-project"; +import { SmithyAsyncModelProject } from "./model/smithy/smithy-async-model-project"; +import { SmithyModelProject } from "./model/smithy/smithy-model-project"; import { SmithyBuildOptions } from "./model/smithy/types"; import { PythonProjectOptions } from "./python-project-options"; import { TypeScriptProjectOptions } from "./typescript-project-options"; @@ -364,6 +368,59 @@ export interface GeneratedCodeProjects { readonly java?: JavaProject; } +/** + * Common details for API model projects + */ +export interface ModelProjectDetails { + /** + * Name of the API + */ + readonly apiName: string; + /** + * Name of the bundled OpenAPI specification file + */ + readonly parsedSpecFile: string; + /** + * Directory of the model project + */ + readonly outdir: string; +} + +/** + * Model project references + */ +export interface ModelProject extends ModelProjectDetails { + /** + * Reference to the Smithy model project. Will be defined if the model language is Smithy + */ + readonly smithy?: SmithyModelProject; + + /** + * Reference to the OpenAPI model project. Will be defined if the model language is OpenAPI + */ + readonly openapi?: OpenApiModelProject; +} + +/** + * WebSocket model project references + */ +export interface WebSocketModelProject extends ModelProjectDetails { + /** + * File name of the generated async api specification + */ + readonly asyncApiSpecFile: string; + + /** + * Reference to the Smithy model project. Will be defined if the model language is Smithy + */ + readonly smithy?: SmithyAsyncModelProject; + + /** + * Reference to the OpenAPI model project. Will be defined if the model language is OpenAPI + */ + readonly openapi?: OpenApiAsyncModelProject; +} + /** * Options for generated libraries */ diff --git a/packages/type-safe-api/test/project/model/type-safe-api-model-project.test.ts b/packages/type-safe-api/test/project/model/type-safe-api-model-project.test.ts index 50e4c7c05..c1d3c66de 100644 --- a/packages/type-safe-api/test/project/model/type-safe-api-model-project.test.ts +++ b/packages/type-safe-api/test/project/model/type-safe-api-model-project.test.ts @@ -1,60 +1,60 @@ /*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ import path from "path"; -import { Language, ModelLanguage, TypeSafeApiModelProject } from "../../../src"; +import { + Language, + OpenApiModelProject, + SmithyModelProject, +} from "../../../src"; import { synthProject, synthSmithyProject } from "../snapshot-utils"; describe("Type Safe Api Model Project Unit Tests", () => { it("Smithy", () => { - const project = new TypeSafeApiModelProject({ + const project = new SmithyModelProject({ outdir: path.resolve(__dirname, "smithy-model"), name: "smithy-model", - modelLanguage: ModelLanguage.SMITHY, - modelOptions: { - smithy: { - serviceName: { - namespace: "com.test", - serviceName: "MyService", - }, + smithyOptions: { + serviceName: { + namespace: "com.test", + serviceName: "MyService", }, }, + parsedSpecFile: ".api.json", }); expect(synthSmithyProject(project)).toMatchSnapshot(); }); it("Smithy With Build Options", () => { - const project = new TypeSafeApiModelProject({ + const project = new SmithyModelProject({ outdir: path.resolve(__dirname, "smithy-model-with-build-options"), name: "smithy-model-with-build-options", - modelLanguage: ModelLanguage.SMITHY, - modelOptions: { - smithy: { - serviceName: { - namespace: "com.test", - serviceName: "MyService", - }, - smithyBuildOptions: { - additionalSources: [ - "foo/bar", - path.resolve(__dirname, "some-other-directory"), - ], - projections: { - openapi: { - plugins: { - openapi: { - forbidGreedyLabels: true, - ignoreUnsupportedTraits: true, - }, + smithyOptions: { + serviceName: { + namespace: "com.test", + serviceName: "MyService", + }, + smithyBuildOptions: { + additionalSources: [ + "foo/bar", + path.resolve(__dirname, "some-other-directory"), + ], + projections: { + openapi: { + plugins: { + openapi: { + forbidGreedyLabels: true, + ignoreUnsupportedTraits: true, }, }, }, }, }, }, + parsedSpecFile: ".api.json", }); - project.smithy!.addSources( + project.definition.addSources( "yet/another", path.resolve(__dirname, "another-absolute") ); @@ -63,106 +63,74 @@ describe("Type Safe Api Model Project Unit Tests", () => { }); it("Smithy With Dependencies", () => { - const lib = new TypeSafeApiModelProject({ + const lib = new SmithyModelProject({ outdir: path.resolve(__dirname, "smithy-model-lib"), name: "smithy-model-lib", - modelLanguage: ModelLanguage.SMITHY, - modelOptions: { - smithy: { - serviceName: { - namespace: "com.shared", - serviceName: "Lib", - }, + smithyOptions: { + serviceName: { + namespace: "com.shared", + serviceName: "Lib", }, }, + parsedSpecFile: ".api.json", }); - const consumer = new TypeSafeApiModelProject({ + const consumer = new SmithyModelProject({ outdir: path.resolve(__dirname, "smithy-model-consumer"), name: "smithy-model-consumer", - modelLanguage: ModelLanguage.SMITHY, - modelOptions: { - smithy: { - serviceName: { - namespace: "com.test", - serviceName: "Consumer", - }, + smithyOptions: { + serviceName: { + namespace: "com.test", + serviceName: "Consumer", }, }, + parsedSpecFile: ".api.json", }); - consumer.smithy!.addSmithyDeps(lib.smithy!); + consumer.definition.addSmithyDeps(lib.definition); expect(synthSmithyProject(consumer)).toMatchSnapshot(); }); it("OpenAPI", () => { - const project = new TypeSafeApiModelProject({ + const project = new OpenApiModelProject({ outdir: path.resolve(__dirname, "openapi-model"), name: "openapi-model", - modelLanguage: ModelLanguage.OPENAPI, - modelOptions: { - openapi: { - title: "MyService", - }, + openApiOptions: { + title: "MyService", }, + parsedSpecFile: ".api.json", }); expect(synthProject(project)).toMatchSnapshot(); }); it("Smithy With Handlers", () => { - const project = new TypeSafeApiModelProject({ + const project = new SmithyModelProject({ outdir: path.resolve(__dirname, "smithy-handlers"), name: "smithy-handlers", - modelLanguage: ModelLanguage.SMITHY, - modelOptions: { - smithy: { - serviceName: { - namespace: "com.test", - serviceName: "MyService", - }, + smithyOptions: { + serviceName: { + namespace: "com.test", + serviceName: "MyService", }, }, handlerLanguages: [Language.PYTHON, Language.TYPESCRIPT], + parsedSpecFile: ".api.json", }); expect(synthSmithyProject(project)).toMatchSnapshot(); }); it("OpenAPI With Handlers", () => { - const project = new TypeSafeApiModelProject({ + const project = new OpenApiModelProject({ outdir: path.resolve(__dirname, "openapi-handlers"), name: "openapi-handlers", - modelLanguage: ModelLanguage.OPENAPI, - modelOptions: { - openapi: { - title: "MyService", - }, + openApiOptions: { + title: "MyService", }, handlerLanguages: [Language.JAVA, Language.PYTHON], + parsedSpecFile: ".api.json", }); expect(synthProject(project)).toMatchSnapshot(); }); - - it("Throws For Missing Smithy Options", () => { - expect(() => { - new TypeSafeApiModelProject({ - outdir: path.resolve(__dirname, "smithy"), - name: "smithy", - modelLanguage: ModelLanguage.SMITHY, - modelOptions: {}, - }); - }).toThrow(/modelOptions.smithy is required.*/); - }); - - it("Throws For Missing OpenAPI Options", () => { - expect(() => { - new TypeSafeApiModelProject({ - outdir: path.resolve(__dirname, "openapi"), - name: "openapi", - modelLanguage: ModelLanguage.OPENAPI, - modelOptions: {}, - }); - }).toThrow(/modelOptions.openapi is required.*/); - }); }); diff --git a/packages/type-safe-api/test/project/model/type-safe-websocket-api-model-project.test.ts b/packages/type-safe-api/test/project/model/type-safe-websocket-api-model-project.test.ts index 6cf2801e3..86f699b8d 100644 --- a/packages/type-safe-api/test/project/model/type-safe-websocket-api-model-project.test.ts +++ b/packages/type-safe-api/test/project/model/type-safe-websocket-api-model-project.test.ts @@ -3,99 +3,73 @@ SPDX-License-Identifier: Apache-2.0 */ import path from "path"; import { Language, - ModelLanguage, - TypeSafeWebSocketApiModelProject, + OpenApiAsyncModelProject, + SmithyAsyncModelProject, } from "../../../src"; import { synthProject, synthSmithyProject } from "../snapshot-utils"; describe("Type Safe Api Model Project Unit Tests", () => { it("Smithy", () => { - const project = new TypeSafeWebSocketApiModelProject({ + const project = new SmithyAsyncModelProject({ outdir: path.resolve(__dirname, "smithy-async-model"), name: "smithy-async-model", - modelLanguage: ModelLanguage.SMITHY, - modelOptions: { - smithy: { - serviceName: { - namespace: "com.test", - serviceName: "MyService", - }, + smithyOptions: { + serviceName: { + namespace: "com.test", + serviceName: "MyService", }, }, + parsedSpecFile: ".api.json", + asyncApiSpecFile: ".asyncapi.json", }); expect(synthSmithyProject(project)).toMatchSnapshot(); }); it("OpenAPI", () => { - const project = new TypeSafeWebSocketApiModelProject({ + const project = new OpenApiAsyncModelProject({ outdir: path.resolve(__dirname, "openapi-async-model"), name: "openapi-async-model", - modelLanguage: ModelLanguage.OPENAPI, - modelOptions: { - openapi: { - title: "MyService", - }, + openApiOptions: { + title: "MyService", }, + parsedSpecFile: ".api.json", + asyncApiSpecFile: ".asyncapi.json", }); expect(synthProject(project)).toMatchSnapshot(); }); it("Smithy With Handlers", () => { - const project = new TypeSafeWebSocketApiModelProject({ + const project = new SmithyAsyncModelProject({ outdir: path.resolve(__dirname, "smithy-async-handlers"), name: "smithy-async-handlers", - modelLanguage: ModelLanguage.SMITHY, - modelOptions: { - smithy: { - serviceName: { - namespace: "com.test", - serviceName: "MyService", - }, + smithyOptions: { + serviceName: { + namespace: "com.test", + serviceName: "MyService", }, }, handlerLanguages: [Language.PYTHON, Language.TYPESCRIPT], + parsedSpecFile: ".api.json", + asyncApiSpecFile: ".asyncapi.json", }); expect(synthSmithyProject(project)).toMatchSnapshot(); }); it("OpenAPI With Handlers", () => { - const project = new TypeSafeWebSocketApiModelProject({ + const project = new OpenApiAsyncModelProject({ outdir: path.resolve(__dirname, "openapi-async-handlers"), name: "openapi-async-handlers", - modelLanguage: ModelLanguage.OPENAPI, - modelOptions: { - openapi: { - title: "MyService", - }, + openApiOptions: { + title: "MyService", }, handlerLanguages: [Language.JAVA, Language.PYTHON], + parsedSpecFile: ".api.json", + asyncApiSpecFile: ".asyncapi.json", }); expect(synthProject(project)).toMatchSnapshot(); }); - - it("Throws For Missing Smithy Options", () => { - expect(() => { - new TypeSafeWebSocketApiModelProject({ - outdir: path.resolve(__dirname, "smithy-async"), - name: "smithy-async", - modelLanguage: ModelLanguage.SMITHY, - modelOptions: {}, - }); - }).toThrow(/modelOptions.smithy is required.*/); - }); - - it("Throws For Missing OpenAPI Options", () => { - expect(() => { - new TypeSafeWebSocketApiModelProject({ - outdir: path.resolve(__dirname, "openapi-async"), - name: "openapi-async", - modelLanguage: ModelLanguage.OPENAPI, - modelOptions: {}, - }); - }).toThrow(/modelOptions.openapi is required.*/); - }); }); diff --git a/packages/type-safe-api/test/project/type-safe-api-project.test.ts b/packages/type-safe-api/test/project/type-safe-api-project.test.ts index ace029f2b..cea897442 100644 --- a/packages/type-safe-api/test/project/type-safe-api-project.test.ts +++ b/packages/type-safe-api/test/project/type-safe-api-project.test.ts @@ -515,4 +515,36 @@ describe("Type Safe Api Project Unit Tests", () => { ]) ); }); + + it("Throws For Missing Smithy Options", () => { + expect(() => { + new TypeSafeApiProject({ + outdir: path.resolve(__dirname, "smithy"), + name: "smithy", + model: { + language: ModelLanguage.SMITHY, + options: {}, + }, + infrastructure: { + language: Language.TYPESCRIPT, + }, + }); + }).toThrow(/modelOptions.smithy is required.*/); + }); + + it("Throws For Missing OpenAPI Options", () => { + expect(() => { + new TypeSafeApiProject({ + outdir: path.resolve(__dirname, "openapi"), + name: "openapi", + model: { + language: ModelLanguage.OPENAPI, + options: {}, + }, + infrastructure: { + language: Language.TYPESCRIPT, + }, + }); + }).toThrow(/modelOptions.openapi is required.*/); + }); }); diff --git a/packages/type-safe-api/test/project/type-safe-websocket-api-project.test.ts b/packages/type-safe-api/test/project/type-safe-websocket-api-project.test.ts index b4b85b91b..d3ef8bbe7 100644 --- a/packages/type-safe-api/test/project/type-safe-websocket-api-project.test.ts +++ b/packages/type-safe-api/test/project/type-safe-websocket-api-project.test.ts @@ -385,4 +385,36 @@ describe("Type Safe Api Project Unit Tests", () => { }).toThrow(/.*not supported.*/); } ); + + it("Throws For Missing Smithy Options", () => { + expect(() => { + new TypeSafeWebSocketApiProject({ + outdir: path.resolve(__dirname, "smithy-async"), + name: "smithy-async", + model: { + language: ModelLanguage.SMITHY, + options: {}, + }, + infrastructure: { + language: Language.TYPESCRIPT, + }, + }); + }).toThrow(/modelOptions.smithy is required.*/); + }); + + it("Throws For Missing OpenAPI Options", () => { + expect(() => { + new TypeSafeWebSocketApiProject({ + outdir: path.resolve(__dirname, "openapi-async"), + name: "openapi-async", + model: { + language: ModelLanguage.OPENAPI, + options: {}, + }, + infrastructure: { + language: Language.TYPESCRIPT, + }, + }); + }).toThrow(/modelOptions.openapi is required.*/); + }); });