Skip to content

Commit

Permalink
feat(type-safe-api): add smithy shape library project for sharing smi…
Browse files Browse the repository at this point in the history
…thy models between apis

Adds a `SmithyShapeLibraryProject` which can be used to define Smithy models that can be reused in
different APIs.

Previously, one would have to declare a `SmithyModelProject` to achieve this, but this needed an
ignored `service` to be defined in the model and generated an OpenAPI spec as part of its build,
which is unnecessary.

Fixes #825
  • Loading branch information
cogwirrel committed Oct 29, 2024
1 parent c6d5b4c commit 3736e5a
Show file tree
Hide file tree
Showing 19 changed files with 5,387 additions and 306 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ nav:
- "Integrations": websocket_integrations.md
- "Authorizers": websocket_authorizers.md
- "React Hooks": websocket_typescript_react_hooks.md
- "Troubleshooting": troubleshooting.md
- "FAQ": faq.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Troubleshooting
# FAQ

### Can I write my CDK code in Java if my .projenrc is in TypeScript?

Expand Down Expand Up @@ -112,83 +112,56 @@ 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 `SmithyModelProject` on its own to create a standalone Smithy model library, which can contain the shared structures.
Yes. You can create a `SmithyShapeLibraryProject` 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.
You can consume the library using the `addSmithyDeps` method, which adds a local file dependency to the built Smithy jar, as well as setting up an implicit dependency in the monorepo if used.

=== "TS"

```ts
// Standalone model project, used as our model library
const shapes = new SmithyModelProject({
const shapes = new SmithyShapeLibraryProject({
name: "shapes",
parent: monorepo,
outdir: "packages/shapes",
smithyOptions: {
serviceName: {
namespace: "com.my.shared.shapes",
serviceName: "Ignored",
},
},
});

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.smithy!, shapes);

// Add a local file dependency on the built shapes jar
api.model.smithy!.definition.addSmithyDeps(shapes.definition);
// Add a dependency on the Smithy shape library
api.model.smithy!.addSmithyDeps(shapes);
```

=== "JAVA"

```java
// Standalone model project, used as our model library
SmithyModelProject shapes = SmithyModelProject.Builder.create()
SmithyShapeLibraryProject shapes = SmithyShapeLibraryProject.Builder.create()
.name("shapes")
.parent(monorepo)
.outdir("packages/shapes")
.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().getSmithy(), shapes.getDefinition());

// Add a local file dependency on the built shapes jar
api.getModel().getSmithy().getDefinition().addSmithyDeps(shapes.getSmithy());
// Add a dependency on the Smithy shape library
api.getModel().getSmithy().addSmithyDeps(shapes);
```

=== "PYTHON"

```python
# Standalone model project, used as our model library
shapes = SmithyModelProject(
shapes = SmithyShapeLibraryProject(
name="shapes",
parent=monorepo,
outdir="packages/shapes",
smithy_options=SmithyModelOptions(
service_name=SmithyServiceName(
namespace="com.my.shared.shapes",
service_name="Ignored"
)
)
)

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.smithy, shapes)

# Add a local file dependency on the built shapes jar
api.model.smithy.definition.add_smithy_deps(shapes.definition)
# Add a dependency on the Smithy shape library
api.model.smithy.add_smithy_deps(shapes)
```

### How do I debug my API locally?
Expand Down
5 changes: 3 additions & 2 deletions packages/type-safe-api/src/project/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ SPDX-License-Identifier: Apache-2.0 */
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/smithy/smithy-service-project-definition";
export * from "./model/openapi/open-api-project-definition";
export * from "./type-safe-api-project";
export * from "./model/type-safe-api-model-project";
export * from "./model/smithy/smithy-definition";
export * from "./model/openapi/open-api-definition";
export * from "./type-safe-websocket-api-project";
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/type-spec/type-spec-project-definition";
Expand All @@ -22,6 +21,8 @@ export * from "./model/smithy/smithy-async-model-project";
export * from "./model/smithy/smithy-model-project";
export * from "./model/type-spec/type-spec-model-project";
export * from "./model/type-spec/type-spec-async-model-project";
export * from "./model/smithy/smithy-shape-library-project";
export * from "./model/smithy/smithy-base-project";
export * from "./languages";
export * from "./types";
export * from "./typescript-project-options";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0 */
import { Project, SampleDir } from "projen";

/**
* Options for Smithy shape library sample code
*/
export interface SmithyShapeLibrarySampleCodeOptions {
/**
* Directory in which the smithy sample code should be written
*/
readonly modelDir: string;
/**
* Namespace
*/
readonly namespace: string;
}

/**
* Defines sample code for a Smithy shape library
*/
export class SmithyShapeLibrarySampleCode extends SampleDir {
constructor(
project: Project,
{ modelDir, namespace }: SmithyShapeLibrarySampleCodeOptions
) {
super(project, modelDir, {
files: {
"example.smithy": `$version: "2"
namespace ${namespace}
/// An example structure
structure Example {
@required
myProperty: String
}
`,
},
});
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
/*! 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";
Expand All @@ -16,36 +11,103 @@ import {
TypeSafeApiModelBuild,
TypeSafeApiModelBuildOutputOptions,
} from "../type-safe-api-model-build";
import { SmithyAsyncSampleCode } from "./components/smithy-async-sample-code";
import { SmithyAwsPdkAsyncPrelude } from "./components/smithy-aws-pdk-async-prelude";
import {
SmithyBaseProject,
SmithyBaseProjectOptions,
} from "./smithy-base-project";
import { SmithyProjectDefinition } from "./smithy-project-definition";
import {
SmithyServiceProjectDefinition,
SmithyServiceProjectDefinitionOptions,
} from "./smithy-service-project-definition";
import { GenerateTask } from "../../codegen/components/generate-task";
import {
buildTypeSafeApiExecCommand,
TypeSafeApiScript,
} from "../../codegen/components/utils";

/**
* Options for the Smithy WebSocket API model project
*/
export interface SmithyAsyncModelProjectOptions
extends ProjectOptions,
SmithyAsyncDefinitionOptions,
extends SmithyBaseProjectOptions,
SmithyServiceProjectDefinitionOptions,
TypeSafeApiModelBuildOutputOptions,
TypeSafeApiAsyncModelBuildOutputOptions {}

/**
* Smithy model project for a WebSocket API
*/
export class SmithyAsyncModelProject extends Project {
export class SmithyAsyncModelProject extends SmithyBaseProject {
/**
* Name of the API
*/
public readonly apiName: string;
/**
* Smithy model and build settings
*/
public readonly definition: SmithyAsyncDefinition;
public readonly definition: SmithyServiceProjectDefinition;

constructor(options: SmithyAsyncModelProjectOptions) {
super(options);
TypeSafeApiCommandEnvironment.ensure(this);

this.definition = new SmithyAsyncDefinition(this, options);
this.definition = new SmithyServiceProjectDefinition(this, {
...options,
smithyOptions: {
...options.smithyOptions,
smithyBuildOptions: {
...options.smithyOptions.smithyBuildOptions,
projections: {
...options.smithyOptions.smithyBuildOptions?.projections,
openapi: {
...options.smithyOptions.smithyBuildOptions?.projections?.openapi,
transforms: [
// Add the async transform to the openapi projection
{ name: "aws-pdk-async-transformer", args: {} },
],
},
},
},
},
});
this.apiName = options.smithyOptions.serviceName.serviceName;

const { namespace: serviceNamespace, serviceName } =
options.smithyOptions.serviceName;
const firstHandlerLanguage = options.handlerLanguages?.[0];

// Create the default smithy model
new SmithyAsyncSampleCode(this, {
modelDir: this.definition.modelDir,
serviceName,
serviceNamespace,
firstHandlerLanguage,
});

// Add the additional async prelude
new SmithyAwsPdkAsyncPrelude(this, {
generatedModelDir: this.definition.generatedModelDir,
serviceNamespace,
handlerLanguages: options.handlerLanguages,
});

const generateTask = GenerateTask.ensure(this);

// Copy the async transformer jar
generateTask.prependExec(
buildTypeSafeApiExecCommand(
TypeSafeApiScript.COPY_ASYNC_SMITHY_TRANSFORMER
)
);

this.definition.addDeps(
`file://.smithy-async/aws-pdk-smithy-async-transformer.jar`
);
this.gitignore.addPatterns(".smithy-async");

new TypeSafeApiModelBuild(this, {
openApiSpecificationPath: this.definition.openApiSpecificationPath,
smithyJsonModelPath: this.definition.smithyJsonModelPath,
Expand All @@ -62,4 +124,8 @@ export class SmithyAsyncModelProject extends Project {
apiType: "async",
});
}

public smithyProjectDefinition(): SmithyProjectDefinition {
return this.definition;
}
}
Loading

0 comments on commit 3736e5a

Please sign in to comment.