diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index be209e981..b220cd079 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -192,5 +192,5 @@ jobs:
touch .nojekyll
git add .
git diff --cached --exit-code >/dev/null || (git commit -am 'docs: publish from ${{ github.sha }}')
- # - name: Push
- # run: git push origin gh-pages:gh-pages
+ - name: Push
+ run: git push origin gh-pages:gh-pages
diff --git a/README.md b/README.md
index 97fa6b1fe..6496255c6 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,131 @@
-# aws-pdk
+
+
+# Getting started
+
+## What is the AWS PDK?
+
+The AWS Project Development Kit (AWS PDK) provides building blocks for common patterns together with development tools to manage and build your projects.
+
+The AWS PDK lets you define your projects programatically via the expressive power of type safe constructs available in one of 3 languages (typescript, python or java). This approach yields many benefits, including:
+
+- Ability to set up new projects within seconds, with all boilerplate already pre-configured.
+- Receive updates to previously bootstrapped projects when new versions become available i.e: updated dependenies or lint configurations.
+- Build polyglot monorepos, with build caching, cross-language build dependencies, dependency visualization and much more.
+- Leverage codified patterns which vend project and infrastructure (CDK) code.
+
+The AWS PDK is built on top of [Projen](https://github.com/projen/projen) and as such all constructs that you compose together need to be defined via a [projenrc](https://projen.io/programmatic-api.html) file.
+
+## Why use the AWS PDK?
+
+It's much easier to show than explain! Here is some PDK code (within projenrc file) that creates a Polyglot monorepo, with a React Website pre-configured with Cognito Auth and pre-integrated with a Smithy Type Safe Api.
+
+```ts
+import { CloudscapeReactTsWebsiteProject } from "@aws/pdk/cloudscape-react-ts-website";
+import { InfrastructureTsProject } from "@aws/pdk/infrastructure";
+import { MonorepoTsProject } from "@aws/pdk/monorepo";
+import {
+ DocumentationFormat,
+ Language,
+ Library,
+ ModelLanguage,
+ TypeSafeApiProject,
+} from "@aws/pdk/type-safe-api";
+import { javascript } from "projen";
+
+const monorepo = new MonorepoTsProject({
+ name: "my-project",
+ packageManager: javascript.NodePackageManager.PNPM,
+ projenrcTs: true,
+});
+
+const api = new TypeSafeApiProject({
+ parent: monorepo,
+ outdir: "packages/api",
+ name: "myapi",
+ infrastructure: {
+ language: Language.TYPESCRIPT,
+ },
+ model: {
+ language: ModelLanguage.SMITHY,
+ options: {
+ smithy: {
+ serviceName: {
+ namespace: "com.aws",
+ serviceName: "MyApi",
+ },
+ },
+ },
+ },
+ runtime: {
+ languages: [Language.TYPESCRIPT],
+ },
+ documentation: {
+ formats: [DocumentationFormat.HTML_REDOC],
+ },
+ library: {
+ libraries: [Library.TYPESCRIPT_REACT_QUERY_HOOKS],
+ },
+ handlers: {
+ languages: [Language.TYPESCRIPT],
+ },
+});
+
+const website = new CloudscapeReactTsWebsiteProject({
+ parent: monorepo,
+ outdir: "packages/website",
+ name: "website",
+ typeSafeApi: api,
+});
+
+new InfrastructureTsProject({
+ parent: monorepo,
+ outdir: "packages/infra",
+ name: "infra",
+ cloudscapeReactTsWebsite: website,
+ typeSafeApi: api,
+});
+
+monorepo.synth();
+```
+
+This code (also available in Python and Java), produces all the source code, packages and infrastructure needed to deploy a fully-operable application in the AWS cloud. All that's left to do is build and deploy it!
+
+From this ~70 lines of code above, the AWS PDK produces the following packages on your behalf:
+
+- `monorepo`: Root level project that manages interdependencies between projects within the Monorepo, provides build caching and dependency visualziation.
+- `api/model`: A project that allows you to define your API using Smithy (or OpenAPI) IDL.
+- `api/generated/documentation`: A project that automatically creates API documentation in a variety of formats.
+- `api/generated/infrastructure`: A project that automatically creates API infrastructure constructs in a type-safe manner.
+- `api/generated/libraries`: A project that automatically generates a react hooks library that can be used to call your API from a React based website.
+- `api/generated/runtime`: A project that contains server bindings for handlers to ensure type safety.
+- `api/handlers`: A project that automatically creates handler stubs, preconfigured with type-safety and a variety of value added features based on your defined API's.
+- `website`: A project which creates a React based website built using [Cloudscape](https://cloudscape.design/) that comes pre-integrated with Cognito Auth and your created API. This provides you with the ability to call your API securely.
+- `infra`: A project which sets up all CDK related infrastructure needed to deploy your application. It also comes pre-configured to generate a diagram based on your CDK code everytime you build.
+
+### Bootstrapped Source
+
+
+
+### Generated Website
+
+
+
+### Generated Diagram
+
+
+
+As you can see, the AWS PDK provides you with valuable time savings so you can focus on working on what matters most to your project.
+
+## Developing with the AWS PDK
+
+Please refer to the full documentation website.
https://aws.github.io/aws-pdk
+## Contributing to the AWS PDK
+
+https://aws.github.io/aws-pdk/contributing/index.html
+
## License
This project is licensed under the Apache-2.0 License.
diff --git a/docs/content/assets/images/rebrand_banner.png b/docs/content/assets/images/rebrand_banner.png
new file mode 100644
index 000000000..8378f6e2b
Binary files /dev/null and b/docs/content/assets/images/rebrand_banner.png differ
diff --git a/docs/content/contributing/index.md b/docs/content/contributing/index.md
index 4fb43b749..188491ffb 100644
--- a/docs/content/contributing/index.md
+++ b/docs/content/contributing/index.md
@@ -16,7 +16,8 @@ This is where the documentation site is defined and built.
Contains configurations for each project within this monorepo.
-**_As a rule of thumb, every package in the monorepo should correspond to a file in this directory._**
+!!!info
+ As a rule of thumb, every package in the monorepo should correspond to a file in this directory.
### **packages**
@@ -214,7 +215,7 @@ From the root directory run: `pdk upgrade-deps`. This will bump all dependencies
If you run into an issue that resembles:
```bash
-Type 'import(".../@aws/pdk/node_modules/aws-prototyping-sdk/node_modules/projen/lib/ignore-file").IgnoreFile' is not assignable to type 'import(".../@aws/pdk/node_modules/projen/lib/ignore-file").IgnoreFile'.
+Type 'import(".../@aws/pdk/node_modules/@aws/pdk/node_modules/projen/lib/ignore-file").IgnoreFile' is not assignable to type 'import(".../@aws/pdk/node_modules/projen/lib/ignore-file").IgnoreFile'.
Types have separate declarations of a private property '_patterns'.
```
diff --git a/docs/content/getting_started/index.md b/docs/content/getting_started/index.md
index 5e6d1251a..490315693 100644
--- a/docs/content/getting_started/index.md
+++ b/docs/content/getting_started/index.md
@@ -84,9 +84,9 @@ The AWS PDK lets you define your project structure as code in one of its support
```ts
new AwsCdkTypeScriptApp({
- cdkVersion: "2.1.0",
- name: "infra",
- defaultReleaseBranch: "main",
+ cdkVersion: "2.1.0",
+ name: "infra",
+ defaultReleaseBranch: "main",
}).synth();
```
diff --git a/docs/content/getting_started/migration_guide.md b/docs/content/getting_started/migration_guide.md
index caf90fb12..240b0f73b 100644
--- a/docs/content/getting_started/migration_guide.md
+++ b/docs/content/getting_started/migration_guide.md
@@ -28,11 +28,11 @@ We need to upgrade our dependencies to consume the new AWS PDK packages, to do s
import { CloudscapeReactTsWebsiteProject } from "@aws-prototyping-sdk/cloudscape-react-ts-website";
import { NxMonorepoProject } from "@aws-prototyping-sdk/nx-monorepo";
import {
- DocumentationFormat,
- Language,
- Library,
- ModelLanguage,
- TypeSafeApiProject,
+ DocumentationFormat,
+ Language,
+ Library,
+ ModelLanguage,
+ TypeSafeApiProject,
} from "@aws-prototyping-sdk/type-safe-api";
import { javascript } from "projen";
import { AwsCdkTypeScriptApp } from "projen/lib/awscdk";
@@ -378,11 +378,255 @@ In order to make this construct truly cross-platform, we had to make a slight tw
The `PDKPipelineTsProject` has been removed as it did not provide an adequate level of control of the generated pipeline. If you have instrumented this construct in your `projenrc.ts` file, you can simply change it to use `AwsCdkTypeScriptApp` instead and ensure the `sample` property is set to false to prevent additional sample code being generated. Be sure to ensure the construct has a dependency on `@aws/pdk` as you will still be able to use the `PDKPipeline` CDK construct contained within `@aws/pdk/pipeline`.
-## TypeSafeApi breaking changes
+## TypeSafeApi Breaking Changes
+
+### Request Parameters
+
+#### Types
+
+In the AWS Prototyping SDK, request parameters (query parameters, path parameters and header parameters) were provided to your handler methods or interceptors in two properties, "request parameters" and "request array parameters". These were typed as they were received by API Gateway without any marshalling, and as such no matter the types defined in your model, would be provided to your handlers as strings or string arrays.
+
+In AWS PDK, request parameters can all be found in the same "request parameters" property of the input, and are automatically marshalled into the type defined in your model. This means that any additional type coercion logic you may have written in your lambda handlers may need to be removed.
+
+For example, suppose we have an `Increment` operation defined in Smithy:
+
+```smithy
+@readonly
+@http(uri: "/increment", method: "GET")
+operation Increment {
+ input := {
+ @required
+ @httpQuery("value")
+ value: Integer
+ }
+ output := {
+ @required
+ result: Integer
+ }
+}
+```
+
+Previously, the `value` would be provided in `input.requestParameters.value` as a string, and your handler would have to handle converting it to a number (and returning an error response if it was not a number). Now, `value` is already converted to a number for you, and the handler wrapper will automatically respond with a `400` status code if the provided request parameter is not of the appropriate type.
+
+=== "BEFORE"
+
+ ```ts
+ import { incrementHandler, Response } from "myapi-typescript-runtime";
+
+ export const handler = incrementHandler(async ({ input }) => {
+ const numberValue = Number(input.requestParameters.value);
+ if (isNaN(numberValue)) {
+ return Response.badRequest({
+ message: 'value must be a number',
+ });
+ }
+ return Response.success({
+ result: numberValue + 1,
+ });
+ });
+ ```
+
+=== "AFTER"
+
+ ```ts
+ import { incrementHandler, Response } from "myapi-typescript-runtime";
+
+ export const handler = incrementHandler(async ({ input }) => {
+ return Response.success({
+ result: input.requestParameters.value + 1,
+ });
+ });
+ ```
+
+!!!warning
+ Request parameters have been restricted to only "primitives" (`number`, `integer`, `string` and `boolean`) or arrays of primitives, since this is the set of request parameters Smithy supports. Note also that a `string` of `date` or `date-time` format will be coerced into a language specific date object.
+
+ While OpenAPI supports encoding objects as request parameters using various schemes, these are not supported by Type Safe API and you will receive a validation error if you specify `object` request parameters in your OpenAPI spec.
+
+#### TypeScript Interceptors
+
+You may have previously defined interceptors in TypeScript which referenced the `RequestArrayParameters` type in their type signature. Since all request parameters have been merged into a single `RequestParameters` type, `RequestArrayParameters` must be deleted from your interceptor type signatures:
+
+=== "BEFORE"
+
+ ```ts hl_lines="9 15"
+ import {
+ sayHelloHandler,
+ ChainedRequestInput,
+ OperationResponse,
+ } from "myapi-typescript-runtime";
+
+ const tryCatchInterceptor = async <
+ RequestParameters,
+ RequestArrayParameters,
+ RequestBody,
+ Response extends OperationResponse
+ >(
+ request: ChainedRequestInput<
+ RequestParameters,
+ RequestArrayParameters,
+ RequestBody,
+ Response
+ >
+ ): Promise> => {
+ try {
+ return await request.chain.next(request);
+ } catch (e: any) {
+ return { statusCode: 500, body: { message: e.message } };
+ }
+ };
-- TypeSafeApi Python runtime moved to new python-nextgen OpenAPI generator, which means:
- - Properties of models can be referenced as attributes rather than dictionary syntax (eg .my_property rather than ["my_property"]).
- - Models are now serialised and deserialised using the .from_json and .to_json methods.
- - request_parameters and request_array_parameters are also models.
-- TypeSafeApi Python handlers import location changed from .apis.tags.default_api_operation_config to .api.operation_config.
-- TypeSafeApi Java classes for handlers/interceptors in runtime are no longer static subclasses of the Handlers class. The import location has changed from eg .api.Handlers.SayHello to .api.handlers.say_hello.SayHello.
\ No newline at end of file
+=== "AFTER"
+
+ ```ts
+ import {
+ sayHelloHandler,
+ ChainedRequestInput,
+ OperationResponse,
+ } from "myapi-typescript-runtime";
+
+ const tryCatchInterceptor = async <
+ RequestParameters,
+ RequestBody,
+ Response extends OperationResponse
+ >(
+ request: ChainedRequestInput<
+ RequestParameters,
+ RequestBody,
+ Response
+ >
+ ): Promise> => {
+ try {
+ return await request.chain.next(request);
+ } catch (e: any) {
+ return { statusCode: 500, body: { message: e.message } };
+ }
+ };
+
+### Python Runtime Package
+
+The Python Runtime package has been migrated to use the latest python generator (`python-nextgen` in [OpenAPI Generator v6](https://github.com/OpenAPITools/openapi-generator/releases/tag/v6.6.0), and renamed to `python` in [v7](https://github.com/OpenAPITools/openapi-generator/releases/tag/v7.0.0)).
+
+As such, there are some breaking changes which will apply to any code using the models:
+
+#### Property Access
+
+Properties of models or request parameters previously needed to be accessed using dictionary notation, while they are now referenced through dot notation:
+
+=== "BEFORE"
+
+ ```python hl_lines="9"
+ from myapi_python_runtime.model.say_hello_response_content import SayHelloResponseContent
+ from myapi_python_runtime.apis.tags.default_api_operation_config import say_hello_handler,
+ SayHelloRequest, SayHelloOperationResponses, ApiResponse
+
+ @say_hello_handler
+ def handler(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
+ return ApiResponse(
+ status_code=200,
+ body=SayHelloResponseContent(message="Hello {}".format(input.request_parameters["name"])),
+ headers={}
+ )
+ ```
+
+=== "AFTER"
+
+ ```python hl_lines="9"
+ from myapi_python_runtime.model.say_hello_response_content import SayHelloResponseContent
+ from myapi_python_runtime.api.operation_config import say_hello_handler,
+ SayHelloRequest, SayHelloOperationResponses, ApiResponse
+ from myapi_python_runtime.response import Response
+
+ @say_hello_handler
+ def handler(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
+ return Response.success(SayHelloResponseContent(
+ message="Hello {}".format(input.request_parameters.name)
+ ))
+ ```
+
+#### Import Path for Handler Wrappers
+
+The import path has changed for handler wrappers, so you will need to adjust your imports accordingly:
+
+=== "BEFORE"
+
+ Previously handler wrappers were imported from `.apis.tags.default_api_operation_config`:
+
+ ```python hl_lines="2-3"
+ from myapi_python_runtime.model.say_hello_response_content import SayHelloResponseContent
+ from myapi_python_runtime.apis.tags.default_api_operation_config import say_hello_handler,
+ SayHelloRequest, SayHelloOperationResponses, ApiResponse
+
+ @say_hello_handler
+ def handler(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
+ return ApiResponse(
+ status_code=200,
+ body=SayHelloResponseContent(message="Hello {}".format(input.request_parameters["name"])),
+ headers={}
+ )
+ ```
+
+=== "AFTER"
+
+ Handler wrappers are now imported from `.api.operation_config`:
+
+ ```python hl_lines="2-3"
+ from myapi_python_runtime.model.say_hello_response_content import SayHelloResponseContent
+ from myapi_python_runtime.api.operation_config import say_hello_handler,
+ SayHelloRequest, SayHelloOperationResponses, ApiResponse
+ from myapi_python_runtime.response import Response
+
+ @say_hello_handler
+ def handler(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
+ return Response.success(SayHelloResponseContent(
+ message="Hello {}".format(input.request_parameters.name)
+ ))
+ ```
+
+### Java Runtime Package
+
+Previously in AWS Prototyping SDK, all classes pertaining to all operations were vended as static subclasses of the `Handlers` class.
+
+In AWS PDK, these classes have been moved out from `Handlers` as their own standalone classes. Additionally, classes are grouped under a package namespace for the particular operation.
+
+=== "BEFORE"
+
+ ```java hl_lines="3-6"
+ package com.my.api;
+
+ import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHello;
+ import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHello200Response;
+ import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHelloRequestInput;
+ import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHelloResponse;
+ import com.generated.api.myapijavaruntime.runtime.model.SayHelloResponseContent;
+
+ public class SayHelloHandler extends SayHello {
+ @Override
+ public SayHelloResponse handle(SayHelloRequestInput request) {
+ return SayHello200Response.of(SayHelloResponseContent.builder()
+ .message(String.format("Hello %s", request.getInput().getRequestParameters().getName()))
+ .build());
+ }
+ }
+ ```
+
+=== "AFTER"
+
+ ```java hl_lines="3-6"
+ package com.my.api;
+
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHello;
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHello200Response;
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHelloRequestInput;
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHelloResponse;
+ import com.generated.api.myapijavaruntime.runtime.model.SayHelloResponseContent;
+
+ public class SayHelloHandler extends SayHello {
+ @Override
+ public SayHelloResponse handle(final SayHelloRequestInput request) {
+ return SayHello200Response.of(SayHelloResponseContent.builder()
+ .message(String.format("Hello %s", request.getInput().getRequestParameters().getName()))
+ .build());
+ }
+ }
+ ```
diff --git a/docs/content/getting_started/shopping_list_app.md b/docs/content/getting_started/shopping_list_app.md
index 17cebbb95..582cbe330 100644
--- a/docs/content/getting_started/shopping_list_app.md
+++ b/docs/content/getting_started/shopping_list_app.md
@@ -30,6 +30,9 @@ This is a continuation from [build your first AWS PDK project](./your_first_aws_
In order to define our API, we need to write some [Smithy](https://smithy.io/2.0/quickstart.html) code which is contained within `packages/api/model/src/main/smithy`.
+!!!info
+ More detailed information on how to use smithy can be found in the [Type Safe API Developer Guide](../developer_guides/type-safe-api/using_smithy.md)
+
### Definining our types
Firstly, let's define some types for our Shopping List application by creating the following files:
diff --git a/docs/content/getting_started/your_first_aws_pdk_project.md b/docs/content/getting_started/your_first_aws_pdk_project.md
index 67087cd70..c100fb5ca 100644
--- a/docs/content/getting_started/your_first_aws_pdk_project.md
+++ b/docs/content/getting_started/your_first_aws_pdk_project.md
@@ -541,16 +541,13 @@ Let's add this infrastructure to the monorepo by modifying our `projenrc` file t
model: {
language: ModelLanguage.SMITHY,
options: {
- smithy: {
- serviceName: {
- namespace: "com.aws",
- serviceName: "MyApi",
+ smithy: {
+ serviceName: {
+ namespace: "com.aws",
+ serviceName: "MyApi",
+ },
},
},
- },
- },
- runtime: {
- languages: [Language.TYPESCRIPT],
},
documentation: {
formats: [DocumentationFormat.HTML_REDOC],
diff --git a/docs/content/overrides/home.html b/docs/content/overrides/home.html
index b0f9cdfd2..67e373654 100644
--- a/docs/content/overrides/home.html
+++ b/docs/content/overrides/home.html
@@ -371,7 +371,7 @@ Getting started
-
+
diff --git a/packages/cloudscape-react-ts-website/docs/developer_guides/cloudscape-react-ts-website/.pages.yml b/packages/cloudscape-react-ts-website/docs/developer_guides/cloudscape-react-ts-website/.pages.yml
index 38d88527b..d7bd8040c 100644
--- a/packages/cloudscape-react-ts-website/docs/developer_guides/cloudscape-react-ts-website/.pages.yml
+++ b/packages/cloudscape-react-ts-website/docs/developer_guides/cloudscape-react-ts-website/.pages.yml
@@ -1,4 +1,5 @@
---
nav:
- index.md
+ - "API Explorer": api_explorer.md
- "Troubleshooting": troubleshooting.md
diff --git a/packages/cloudscape-react-ts-website/docs/developer_guides/cloudscape-react-ts-website/api_explorer.md b/packages/cloudscape-react-ts-website/docs/developer_guides/cloudscape-react-ts-website/api_explorer.md
new file mode 100644
index 000000000..0b21ff2c5
--- /dev/null
+++ b/packages/cloudscape-react-ts-website/docs/developer_guides/cloudscape-react-ts-website/api_explorer.md
@@ -0,0 +1,14 @@
+# API Explorer
+
+
+
+The `CloudscapeReactTsWebsiteProject` has an optional parameter called `typeSafeApi` which if passed in, automatically configures your website to set up an _API Explorer_ which will allow you to make sigv4 signed requested to your [Type Safe API](../type-safe-api/index.md).
+
+```typescript hl_lines="5"
+new CloudscapeReactTsWebsiteProject({
+ parent: monorepo,
+ outdir: "packages/website",
+ name: "website",
+ typeSafeApi: api,
+});
+```
\ No newline at end of file
diff --git a/packages/type-safe-api/docs/developer_guides/type-safe-api/.pages.yml b/packages/type-safe-api/docs/developer_guides/type-safe-api/.pages.yml
index 2d1207a2a..7fde4a29d 100644
--- a/packages/type-safe-api/docs/developer_guides/type-safe-api/.pages.yml
+++ b/packages/type-safe-api/docs/developer_guides/type-safe-api/.pages.yml
@@ -3,11 +3,12 @@ nav:
- index.md
- "Using Smithy": using_smithy.md
- "Using OpenAPI": using_openapi.md
+ - "Lambda Handlers": lambda_handlers.md
+ - "Interceptors": interceptors.md
- "Integrations": integrations.md
- "Mocking Responses": mocking_responses.md
- "Authorizers": authorizers.md
- - "Lambda Handlers": lambda_handlers.md
- - "React Query Hooks": typescript_react_query_hooks.md
- "API Keys": api_keys.md
+ - "React Query Hooks": typescript_react_query_hooks.md
- "Custom Integration: ECS & NLB": custom_integration_ecs.md
- "Troubleshooting": troubleshooting.md
diff --git a/packages/type-safe-api/docs/developer_guides/type-safe-api/custom_integration_ecs.md b/packages/type-safe-api/docs/developer_guides/type-safe-api/custom_integration_ecs.md
index 7fe3847da..b420496eb 100644
--- a/packages/type-safe-api/docs/developer_guides/type-safe-api/custom_integration_ecs.md
+++ b/packages/type-safe-api/docs/developer_guides/type-safe-api/custom_integration_ecs.md
@@ -11,59 +11,30 @@ In the usual way, create a monorepo which will be the base of your project:
```bash
mkdir smithy-ecs-workshop
cd smithy-ecs-workshop
-npx projen new --from @aws-prototyping-sdk/monorepo
+pdk new monorepo-ts
```
-## Add a Dependency on Type Safe API
-
-Update your `.projenrc.ts` to add a dependency on `@aws-prototyping-sdk/type-safe-api`:
-
-```ts
-import { MonorepoTsProject } from "@aws-prototyping-sdk/monorepo";
-
-const monorepo = new MonorepoTsProject({
- defaultReleaseBranch: "main",
- devDeps: [
- "@aws-prototyping-sdk/monorepo",
- "@aws-prototyping-sdk/type-safe-api", // <- add this!
- ],
- name: "smithy-ecs-workshop",
-});
-
-monorepo.synth();
-```
-
-Then synthesize your project by running:
-
-```bash
-yarn projen
-```
-
-Synthesizing your project will generate the projects you have defined in your `.projenrc.ts` file. Changes to project structure and configuration are defined as code. For more details, take a look at the [Projen Documentation](https://github.com/projen/projen).
-
## Set up the API, Server and Infrastructure Projects
In your `.projenrc.ts`, we’ll add three more projects:
- `TypeSafeApiProject` - this will be used for defining our API and provides generated infrastructure, clients, types and documentation
- `TypeScriptProject` - a basic typescript in which we'll implement our Express server
-- `AwsCdkTypeScriptApp` - a simple CDK app for deploying your API quickly. Note that in a production application you'd likely opt for a CI/CD pipeline to manage your deployments (eg `PDKPipelineTsProject`) instead.
+- `AwsCdkTypeScriptApp` - a simple CDK app for deploying your API. You can also use `InfrastructureTsProject` from PDK if you prefer.
```ts
-import { MonorepoTsProject } from "@aws-prototyping-sdk/monorepo";
+import { MonorepoTsProject } from "@aws/pdk/monorepo";
import {
Language,
ModelLanguage,
TypeSafeApiProject,
-} from "@aws-prototyping-sdk/type-safe-api";
+} from "@aws/pdk/type-safe-api";
import { TypeScriptProject } from "projen/lib/typescript";
-import { AwsCdkTypeScriptApp } from "projen/lib/awscdk";
+import { InfrastructureTsProject } from "@aws/pdk/infrastructure";
const monorepo = new MonorepoTsProject({
- defaultReleaseBranch: "main",
devDeps: [
- "@aws-prototyping-sdk/monorepo",
- "@aws-prototyping-sdk/type-safe-api",
+ "@aws/pdk",
],
name: "smithy-ecs-workshop",
});
@@ -108,7 +79,7 @@ const server = new TypeScriptProject({
"@types/express",
"@types/aws-lambda",
"esbuild",
- "@aws-prototyping-sdk/type-safe-api",
+ "@aws/pdk",
],
tsconfig: {
compilerOptions: {
@@ -126,11 +97,11 @@ new AwsCdkTypeScriptApp({
cdkVersion: "2.0.0",
defaultReleaseBranch: "main",
deps: [
- // Add a dependency on type-safe-api, as well as the generated infrastructure, runtime, and the server
- "@aws-prototyping-sdk/type-safe-api",
+ // Add a dependency on PDK, as well as the generated infrastructure, runtime, and the server
+ "@aws/pdk",
api.runtime.typescript!.package.packageName!,
api.infrastructure.typescript!.package.packageName,
- server.package.packageName!,
+ server.package.packageName,
],
tsconfig: {
compilerOptions: {
@@ -145,8 +116,8 @@ monorepo.synth();
We can now synthesize and build our "empty" suite of projects:
```bash
-yarn projen
-yarn build
+pdk
+pdk build
```
## ECS Infrastructure
@@ -230,7 +201,7 @@ import {
ApiGatewayIntegration,
Integration,
IntegrationRenderProps,
-} from "@aws-prototyping-sdk/type-safe-api";
+} from "@aws/pdk/type-safe-api";
import { IVpcLink } from "aws-cdk-lib/aws-apigateway";
import { INetworkLoadBalancer } from "aws-cdk-lib/aws-elasticloadbalancingv2";
@@ -295,7 +266,7 @@ import { App, Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import { LoadBalancedEcsService } from "./load-balanced-ecs-service";
import { Api } from "smithy-ecs-workshop-api-typescript-infra";
-import { Authorizers } from "@aws-prototyping-sdk/type-safe-api";
+import { Authorizers } from "@aws/pdk/type-safe-api";
import { Operations } from "smithy-ecs-workshop-api-typescript-runtime";
import { NlbIntegration } from "./nlb-integration";
@@ -463,15 +434,12 @@ Note that this may not be a fully comprehensive example - there may be more prop
We can implement our operation in `packages/server/src/say-hello.ts` using the generated lambda handler wrapper:
```ts
-import { sayHelloHandler } from "smithy-ecs-workshop-api-typescript-runtime";
+import { sayHelloHandler, Response } from "smithy-ecs-workshop-api-typescript-runtime";
export const sayHello = sayHelloHandler(async ({ input }) => {
- return {
- statusCode: 200,
- body: {
- message: `Hello ${input.requestParameters.name}`,
- },
- };
+ return Response.success({
+ message: `Hello ${input.requestParameters.name}`,
+ });
});
```
@@ -551,14 +519,15 @@ Make sure you're running [Docker](https://www.docker.com/) since the deployment
Since we updated the `.projenrc.ts` we’ll need to synthesize again. After that we can build all the packages again.
```bash
-yarn projen
-yarn build
+pdk
+pdk build
```
After you have set up AWS credentials for your target AWS account (eg. run `aws configure`), you can deploy the CDK application:
```bash
-yarn nx run smithy-ecs-workshop-infra:deploy --require-approval never
+cd packages/infra
+pdk run deploy --require-approval never
```
Once the deployment has completed, you'll see your API URL printed as a CloudFormation output.
diff --git a/packages/type-safe-api/docs/developer_guides/type-safe-api/index.md b/packages/type-safe-api/docs/developer_guides/type-safe-api/index.md
index d4765d33c..cd42685fd 100644
--- a/packages/type-safe-api/docs/developer_guides/type-safe-api/index.md
+++ b/packages/type-safe-api/docs/developer_guides/type-safe-api/index.md
@@ -4,13 +4,15 @@
[![API Documentation](https://img.shields.io/badge/view-API_Documentation-blue.svg)](../../api/typescript/type-safe-api/index.md)
[![Source Code](https://img.shields.io/badge/view-Source_Code-blue.svg)](https://github.com/aws/aws-pdk/tree/mainline/packages/type-safe-api)
+> Define your API's declaratively in a type-safe manner to reduce preventable issues and increase overall productivity.
+
The _type-safe-api_ package provides a projen project type which allows you to define an API using either [Smithy](https://smithy.io/2.0/) or [OpenAPI v3](https://swagger.io/specification/), and a construct which manages deploying this API in an API Gateway.
You can define your APIs using [Smithy](https://smithy.io/2.0/) or [OpenAPI v3](https://swagger.io/specification/), and leverage the power of generated client and server types, infrastructure, documentation, and automatic input validation.
## How does it work?
-The project will generate runtime projects from your API definition in your desired languages, which you can use both on the client side for interacting with your API, or server side for implementing your API. The project also generates a type-safe CDK construct which ensures an integration is provided for every API operation.
+The project will generate runtime projects from your API definition in your desired languages, which contain clients for interacting with your API, and server-side code for implementing your API. The project also generates a type-safe CDK construct which ensures an integration is provided for every API operation.
!!! note
@@ -24,6 +26,10 @@ The `TypeSafeApiProject` projen project creates the following directory structur
|_ main/
|_ smithy - your API definition if you chose ModelLanguage.SMITHY
|_ openapi - your API definition if you chose ModelLanguage.OPENAPI
+|_ handlers/
+ |_ typescript - lambda handlers for operations you choose to implement in TypeScript
+ |_ python - lambda handlers for operations you choose to implement in Python
+ |_ java - lambda handlers for operations you choose to implement in Java
|_ generated/
|_ runtime/ - generated types, client, and server code in the languages you specified
|_ typescript
@@ -44,21 +50,22 @@ The `TypeSafeApiProject` projen project creates the following directory structur
## Getting started
-This section describes how to get started with the type-safe API. For more information, refer to the other user guides on particular features of this library.
+This section describes how to get started with the Type Safe API. For more information, refer to the other user guides on particular features of this library.
!!! info
- Select the tabs to use this library with infrastructure and lambda handlers in the same language, but you can mix and match language. For example, you could write CDK infrastructure in Java and implement your lambda handlers in Python.
+ Select the tabs to use this library with infrastructure and lambda handlers in the same language, but you can mix and match language. For example, you could write CDK infrastructure in Java and implement some lambda handlers in Python, and others in TypeScript.
### Type Safe API project structure
The `TypeSafeApiProject` projen project sets up the project structure for you. Consider the following parameters when creating the project:
- `model` - Configure the API model. Select a `language` for the model from either [Smithy](https://smithy.io/2.0/) or [OpenAPI v3](https://swagger.io/specification/), and provide `options.smithy` or `options.openapi` depending on your choice.
-- `runtime` - Configure the generated runtime projects. Include one or more `languages` you want to write your client and/or server-side code in. These projects contain generated types defined in your model, as well as type-safe lambda handler wrappers for implementing each operation.
- `infrastructure` - Select the `language` you are writing your CDK infrastructure in. A construct will be generated in this language which can be used to deploy the API.
-- `documentation` - Specify `formats` to generate documentation in.
-- `library` - Specify additional `libraries` to generate, such as React Query hooks for use in a React website.
+- `handlers` - Optionally select the `languages` in which you wish to write lambda handlers for operations in.
+- `runtime` - Optionally configure additional generated runtime projects. Include one or more `languages` you want to write your client and/or server-side code in. These projects contain generated types defined in your model, as well as type-safe lambda handler wrappers for implementing each operation. You'll notice runtime packages are automatically generated for languages you picked for `infrastructure` and `handlers`.
+- `documentation` - Optionally specify `formats` to generate documentation in.
+- `library` - Optionally specify additional `libraries` to generate, such as React Query hooks for use in a React website.
## Create your API project
@@ -66,19 +73,13 @@ The `TypeSafeApiProject` projen project sets up the project structure for you. C
We recommend you use these projects as part of an `monorepo` project (eg. by specifying `parent: monorepo`), as it makes setting up dependencies much easier, particularly when extending your project further with a CDK app and lambda functions.
-1. To start an empty `monorepo` project, use this command:
+1.) To start an empty `monorepo` project, use this command:
```bash
-npx projen new --from @aws-prototyping-sdk/monorepo
-```
-
-2. Add `@aws-prototyping-sdk/type-safe-api` to your `MonorepoTsProject`'s `devDeps` and re-synthesize to install the dependency.
-
-```
-yarn projen
+pdk new monorepo-ts
```
-3. Edit your `.projenrc` and configure `TypeSafeApiProject`.
+2.) Edit your `.projenrc` and configure `TypeSafeApiProject`.
!!! tip
@@ -87,23 +88,21 @@ yarn projen
=== "TS"
```ts
- import { MonorepoTsProject } from "@aws-prototyping-sdk/monorepo";
+ import { MonorepoTsProject } from "@aws/pdk/monorepo";
import {
DocumentationFormat,
Language,
Library,
ModelLanguage,
TypeSafeApiProject,
- } from "@aws-prototyping-sdk/type-safe-api";
- import { AwsCdkTypeScriptApp } from "projen/lib/awscdk";
+ } from "@aws/pdk/type-safe-api";
+ import { InfrastructureTsProject } from "@aws/pdk/infrastructure";
// Create the monorepo
const monorepo = new MonorepoTsProject({
name: "my-project",
- defaultReleaseBranch: "main",
devDeps: [
- "@aws-prototyping-sdk/monorepo",
- "@aws-prototyping-sdk/type-safe-api",
+ "@aws/pdk",
],
});
@@ -124,14 +123,14 @@ yarn projen
},
},
},
- // Generate types, client and server code in TypeScript, Python and Java
- runtime: {
- languages: [Language.TYPESCRIPT, Language.PYTHON, Language.JAVA],
- },
// CDK infrastructure in TypeScript
infrastructure: {
language: Language.TYPESCRIPT,
},
+ // Lambda handlers in TypeScript
+ handlers: {
+ languages: [Language.TYPESCRIPT],
+ }
// Generate HTML documentation
documentation: {
formats: [DocumentationFormat.HTML_REDOC],
@@ -142,463 +141,607 @@ yarn projen
},
});
- // Create a CDK infrastructure project. Can also consider PDKPipelineTsProject as an alternative!
- const infra = new AwsCdkTypeScriptApp({
- defaultReleaseBranch: "main",
- cdkVersion: "2.0.0",
- parent: monorepo,
- outdir: "packages/infra",
- name: "myinfra",
- deps: [
- "@aws-prototyping-sdk/type-safe-api",
- ],
+ // Create a CDK infrastructure project
+ new InfrastructureTsProject({
+ parent: monorepo,
+ outdir: "packages/infra",
+ name: "infra",
+ typeSafeApi: api,
});
- // Infrastructure can depend on the generated API infrastructure and runtime
- infra.addDeps(api.infrastructure.typescript!.package.packageName);
- infra.addDeps(api.runtime.typescript!.package.packageName);
-
monorepo.synth();
```
=== "JAVA"
- The `.projenrc` file is written in TypeScript here in order to make use of the `monorepo`, but shows an example project definition for implementing infrastructure and lambda handlers in Java.
+ ```java
+ import software.aws.pdk.monorepo.MonorepoJavaProject;
+ import software.aws.pdk.monorepo.MonorepoJavaOptions;
+ import software.aws.pdk.infrastructure.InfrastructureJavaProject;
+ import software.aws.pdk.infrastructure.InfrastructureJavaProjectOptions;
+ import software.aws.pdk.type_safe_api.*;
+ import java.util.Arrays;
- ```ts
- import { MonorepoTsProject } from "@aws-prototyping-sdk/monorepo";
- import {
- DocumentationFormat,
- Language,
- Library,
- ModelLanguage,
- TypeSafeApiProject,
- } from "@aws-prototyping-sdk/type-safe-api";
- import { AwsCdkJavaApp } from "projen/lib/awscdk";
- import { JavaProject } from "projen/lib/java";
+ public class projenrc {
+ public static void main(String[] args) {
+ MonorepoJavaProject monorepo = new MonorepoJavaProject(MonorepoJavaOptions.builder()
+ .name("my-project")
+ .build());
- // Create the monorepo
- const monorepo = new MonorepoTsProject({
- name: "my-project",
- defaultReleaseBranch: "main",
- devDeps: [
- "@aws-prototyping-sdk/monorepo",
- "@aws-prototyping-sdk/type-safe-api",
- ],
- });
+ TypeSafeApiProject api = new TypeSafeApiProject(TypeSafeApiProjectOptions.builder()
+ .name("myapi")
+ .parent(monorepo)
+ .outdir("packages/api")
+ .model(ModelConfiguration.builder()
+ .language(ModelLanguage.SMITHY)
+ .options(ModelOptions.builder()
+ .smithy(SmithyModelOptions.builder()
+ .serviceName(SmithyServiceName.builder()
+ .namespace("com.my.company")
+ .serviceName("MyApi")
+ .build())
+ .build())
+ .build())
+ .build())
+ .infrastructure(InfrastructureConfiguration.builder()
+ .language(Language.JAVA)
+ .build())
+ .documentation(DocumentationConfiguration.builder()
+ .formats(Arrays.asList(DocumentationFormat.HTML_REDOC))
+ .build())
+ .library(LibraryConfiguration.builder()
+ .libraries(Arrays.asList(Library.TYPESCRIPT_REACT_QUERY_HOOKS))
+ .build())
+ .handlers(HandlersConfiguration.builder()
+ .languages(Arrays.asList(Language.JAVA))
+ .build())
+ .build());
- // Create the API project
- const api = new TypeSafeApiProject({
- name: "myapi",
- parent: monorepo,
- outdir: "packages/api",
- // Smithy as the model language. You can also use ModelLanguage.OPENAPI
- model: {
- language: ModelLanguage.SMITHY,
- options: {
- smithy: {
- serviceName: {
- namespace: "com.my.company",
- serviceName: "MyApi",
- },
- },
- },
- },
- // Generate types, client and server code in TypeScript, Python and Java
- runtime: {
- languages: [Language.TYPESCRIPT, Language.PYTHON, Language.JAVA],
- },
- // CDK infrastructure in Java
- infrastructure: {
- language: Language.JAVA,
- },
- // Generate HTML documentation
- documentation: {
- formats: [DocumentationFormat.HTML_REDOC],
- },
- // Generate react-query hooks to interact with the UI from a React website
- library: {
- libraries: [Library.TYPESCRIPT_REACT_QUERY_HOOKS],
- },
- });
+ new InfrastructureJavaProject(
+ InfrastructureJavaProjectOptions.builder()
+ .parent(monorepo)
+ .outdir("packages/infra")
+ .name("infra")
+ .typeSafeApi(api)
+ .build());
- // Create a Java project for our lambda functions which will implement the API operations
- const lambdas = new JavaProject({
- artifactId: "lambdas",
- groupId: "com.my.company",
- name: "lambdas",
- version: "1.0.0",
- parent: monorepo,
- outdir: "packages/lambdas",
- sample: false,
- });
+ monorepo.synth();
+ }
+ }
+ ```
- // The lambdas package needs a dependency on the generated java runtime
- monorepo.addJavaDependency(lambdas, api.runtime.java!);
+=== "PYTHON"
- // Use the maven shade plugin to build a "super jar" which we can deploy to AWS Lambda
- lambdas.pom.addPlugin("org.apache.maven.plugins/maven-shade-plugin@3.3.0", {
- configuration: {
- createDependencyReducedPom: false,
- },
- executions: [
- {
- id: "shade-task",
- phase: "package",
- goals: ["shade"],
- },
- ],
- });
+ ```python
+ from aws_pdk.monorepo import MonorepoPythonProject
+ from aws_pdk.infrastructure import InfrastructurePyProject
+ from aws_pdk.type_safe_api import *
- // Create a CDK infrastructure project. Can also consider PDKPipelineJavaProject as an alternative!
- const infra = new AwsCdkJavaApp({
- artifactId: "infra",
- groupId: "com.my.company",
- mainClass: "com.my.company.MyApp",
- version: "1.0.0",
- cdkVersion: "2.0.0",
- name: "infra",
- parent: monorepo,
- outdir: "packages/infra",
- deps: [
- "software.aws.awsprototypingsdk/type-safe-api@^0",
- ],
- });
+ monorepo = MonorepoPythonProject(
+ module_name="my_project",
+ name="my-project",
+ )
- // Add a dependency on the generated CDK infrastructure and runtime
- monorepo.addJavaDependency(infra, api.infrastructure.java!);
- monorepo.addJavaDependency(infra, api.runtime.java!);
+ api = TypeSafeApiProject(
+ name="myapi",
+ parent=monorepo,
+ outdir="packages/api",
+ model=ModelConfiguration(
+ language=ModelLanguage.SMITHY,
+ options=ModelOptions(
+ smithy=SmithyModelOptions(
+ service_name=SmithyServiceName(
+ namespace="com.amazon",
+ service_name="MyAPI"
+ )
+ )
+ )
+ ),
+ infrastructure=InfrastructureConfiguration(
+ language=Language.PYTHON
+ ),
+ documentation=DocumentationConfiguration(
+ formats=[DocumentationFormat.HTML_REDOC]
+ ),
+ handlers=HandlersConfiguration(
+ languages=[Language.PYTHON]
+ ),
+ library=LibraryConfiguration(
+ libraries=[Library.TYPESCRIPT_REACT_QUERY_HOOKS]
+ )
+ )
- // Make sure the java lambda builds before our CDK infra
- monorepo.addImplicitDependency(infra, lambdas);
+ InfrastructurePyProject(
+ parent=monorepo,
+ outdir="packages/infra",
+ name="infra",
+ type_safe_api=api,
+ )
- monorepo.synth();
+ monorepo.synth()
```
-=== "PYTHON"
+3.) Given we have modified our `projenrc` file we need to run the `pdk` command to synthesize our new API and infrastructure onto the filesystem. We can then run a first build with `pdk build`.
+
+## Implement a Lambda handler
+
+The generated runtime projects include lambda handler wrappers which provide type-safety for implementing your API operations. The generated `handlers` projects include generate stubs for you to implement for every operation which has been annotated accordingly:
+
+=== "SMITHY"
+
+ Use the `@handler` trait, and specify the language you wish to implement this operation in.
+
+ ```smithy hl_lines="3"
+ @readonly
+ @http(method: "GET", uri: "/hello")
+ @handler(language: "typescript")
+ operation SayHello {
+ input := {
+ @httpQuery("name")
+ @required
+ name: String
+ }
+ output := {
+ @required
+ message: String
+ }
+ }
+ ```
- The `.projenrc` file is written in TypeScript here in order to make use of the `monorepo`, but shows an example project definition for implementing infrastructure and lambda handlers in Python.
+=== "OPENAPI"
+
+ Use the `x-handler` vendor extension, specifying the language you wish to implement this operation in.
+
+ ```yaml hl_lines="4-5"
+ /hello:
+ get:
+ operationId: sayHello
+ x-handler:
+ language: typescript
+ parameters:
+ - in: query
+ name: name
+ schema:
+ type: string
+ required: true
+ responses:
+ 200:
+ description: Successful response
+ content:
+ 'application/json':
+ schema:
+ $ref: '#/components/schemas/SayHelloResponseContent'
+ ```
+
+!!!note
+
+ If you wish to deviate from the folder structure of the `handlers` projects, or wish to implement your operations in a language not supported by Type Safe API, or through a non-lambda interation (such as a server running in a Fargate container) you can omit the `@handler` trait or `x-handler` vendor extension.
+
+You can implement your lambda handlers in any of the supported languages, or mix and match languages for different operations if you prefer.
+
+=== "TS"
+
+ In TypeScript, you'll notice you have a lambda handler stub in `packages/api/handlers/typescript/src/say-hello.ts`:
```ts
- import { MonorepoTsProject } from "@aws-prototyping-sdk/monorepo";
import {
- DocumentationFormat,
- Language,
- Library,
- ModelLanguage,
- TypeSafeApiProject,
- } from "@aws-prototyping-sdk/type-safe-api";
- import { AwsCdkPythonApp } from "projen/lib/awscdk";
- import { PythonProject } from "projen/lib/python";
+ sayHelloHandler,
+ SayHelloChainedHandlerFunction,
+ INTERCEPTORS,
+ Response,
+ LoggingInterceptor,
+ } from "myapi-typescript-runtime";
+
+ /**
+ * Type-safe handler for the SayHello operation
+ */
+ export const sayHello: SayHelloChainedHandlerFunction = async (request) => {
+ LoggingInterceptor.getLogger(request).info("Start SayHello Operation");
+
+ // TODO: Implement SayHello Operation. `input` contains the request input.
+ const { input } = request;
+
+ return Response.internalFailure({
+ message: "Not Implemented!",
+ });
+ };
+
+ /**
+ * Entry point for the AWS Lambda handler for the SayHello operation.
+ * The sayHelloHandler method wraps the type-safe handler and manages marshalling inputs and outputs
+ */
+ export const handler = sayHelloHandler(...INTERCEPTORS, sayHello);
+ ```
- // Create the monorepo
- const monorepo = new MonorepoTsProject({
- name: "my-project",
- defaultReleaseBranch: "main",
- devDeps: [
- "@aws-prototyping-sdk/monorepo",
- "@aws-prototyping-sdk/type-safe-api",
- ],
- });
+=== "JAVA"
- // Create the API project
- const api = new TypeSafeApiProject({
- name: "myapi",
- parent: monorepo,
- outdir: "packages/api",
- // Smithy as the model language. You can also use ModelLanguage.OPENAPI
- model: {
- language: ModelLanguage.SMITHY,
- options: {
- smithy: {
- serviceName: {
- namespace: "com.my.company",
- serviceName: "MyApi",
- },
- },
- },
- },
- // Generate types, client and server code in TypeScript, Python and Java
- runtime: {
- languages: [Language.TYPESCRIPT, Language.PYTHON, Language.JAVA],
- },
- // CDK infrastructure in Python
- infrastructure: {
- language: Language.PYTHON,
- },
- // Generate HTML documentation
- documentation: {
- formats: [DocumentationFormat.HTML_REDOC],
- },
- // Generate react-query hooks to interact with the UI from a React website
- library: {
- libraries: [Library.TYPESCRIPT_REACT_QUERY_HOOKS],
- },
- });
+ In Java, you'll notice you have a lambda handler stub in `packages/api/handlers/java/src/main/java/com/generated/api/myapijavahandlers/handlers/SayHelloHandler.java`:
+
+ ```java
+ package com.generated.api.myapijavahandlers.handlers;
+
+ import com.generated.api.myapijavaruntime.runtime.api.interceptors.DefaultInterceptors;
+ import com.generated.api.myapijavaruntime.runtime.api.interceptors.powertools.LoggingInterceptor;
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.Interceptor;
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHello;
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHelloInput;
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHello500Response;
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHelloRequestInput;
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHelloResponse;
+ import com.generated.api.myapijavaruntime.runtime.model.*;
+
+ import java.util.List;
+
+ /**
+ * Entry point for the AWS Lambda handler for the SayHello operation.
+ * The SayHello class manages marshalling inputs and outputs.
+ */
+ public class SayHelloHandler extends SayHello {
+ /**
+ * Return the interceptors for this handler.
+ * You can also use the @Interceptors annotation on the class to add interceptors
+ */
+ @Override
+ public List> getInterceptors() {
+ return DefaultInterceptors.all();
+ }
+
+ /**
+ * Type-safe handler for the SayHello operation
+ */
+ @Override
+ public SayHelloResponse handle(final SayHelloRequestInput request) {
+ LoggingInterceptor.getLogger(request).info("Start SayHello Operation");
+
+ // TODO: Implement SayHello Operation. `input` contains the request input.
+ SayHelloInput input = request.getInput();
+
+ return SayHello500Response.of(InternalFailureErrorResponseContent.builder()
+ .message("Not Implemented!")
+ .build());
+ }
+ }
+ ```
- // Create a Python project for our lambda functions which will implement the API operations
- const lambdas = new PythonProject({
- parent: monorepo,
- poetry: true,
- outdir: "packages/lambdas",
- moduleName: "lambdas",
- name: "lambdas",
- version: "1.0.0",
- authorEmail: "me@example.com",
- authorName: "test",
- });
+=== "PYTHON"
- // The lambdas package needs a dependency on the generated python runtime
- monorepo.addPythonPoetryDependency(lambdas, api.runtime.python!);
+ In Python, you'll notice you have a lambda handler stub in `packages/api/handlers/python/myapi_python_handlers/say_hello.py`:
- // Add commands to the lambda project's package task to create a distributable which can be deployed to AWS Lambda
- lambdas.packageTask.exec(`mkdir -p lambda-dist && rm -rf lambda-dist/*`);
- lambdas.packageTask.exec(`cp -r ${lambdas.moduleName} lambda-dist/${lambdas.moduleName}`);
- lambdas.packageTask.exec(`poetry export --without-hashes --format=requirements.txt > lambda-dist/requirements.txt`);
- lambdas.packageTask.exec(`pip install -r lambda-dist/requirements.txt --target lambda-dist --upgrade`);
+ ```python
+ from myapi_python_runtime.models import *
+ from myapi_python_runtime.response import Response
+ from myapi_python_runtime.interceptors import INTERCEPTORS
+ from myapi_python_runtime.interceptors.powertools.logger import LoggingInterceptor
+ from myapi_python_runtime.api.operation_config import (
+ say_hello_handler, SayHelloRequest, SayHelloOperationResponses
+ )
+
+
+ def say_hello(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
+ """
+ Type-safe handler for the SayHello operation
+ """
+ LoggingInterceptor.get_logger(input).info("Start SayHello Operation")
+
+ # TODO: Implement SayHello Operation. `input` contains the request input
+
+ return Response.internal_failure(InternalFailureErrorResponseContent(
+ message="Not Implemented!"
+ ))
+
+
+ # Entry point for the AWS Lambda handler for the SayHello operation.
+ # The say_hello_handler method wraps the type-safe handler and manages marshalling inputs and outputs
+ handler = say_hello_handler(interceptors=INTERCEPTORS)(say_hello)
+ ```
- // Create a CDK infrastructure project. Can also consider PDKPipelinePyProject as an alternative!
- const infra = new AwsCdkPythonApp({
- parent: monorepo,
- poetry: true,
- outdir: "packages/infra",
- moduleName: "infra",
- name: "infra",
- version: "1.0.0",
- cdkVersion: "2.0.0",
- authorEmail: "me@example.com",
- authorName: "test",
- deps: [
- "aws_prototyping_sdk.type_safe_api@<1.0.0",
- ],
- });
+!!!note
+ You will notice the handler stubs make use of some default "interceptors". [You can read more about interceptors here](./interceptors.md).
- // Add a dependency on the generated CDK infrastructure and runtime
- monorepo.addPythonPoetryDependency(infra, api.infrastructure.python!);
- monorepo.addPythonPoetryDependency(infra, api.runtime.python!);
+We can replace the stubbed response with a real implementation:
- // Make sure the python lambdas build before our CDK infra
- monorepo.addImplicitDependency(infra, lambdas);
+=== "TS"
- monorepo.synth();
+ ```ts
+ /**
+ * Type-safe handler for the SayHello operation
+ */
+ export const sayHello: SayHelloChainedHandlerFunction = async (request) => {
+ LoggingInterceptor.getLogger(request).info("Start SayHello Operation");
+
+ const { input } = request;
+
+ return Response.success({
+ message: `Hello ${input.requestParameters.name}!`,
+ });
+ };
```
-4. After you define your `.projenrc`, run `projen` and `build` (using the appropriate commands for your package manager). For example, if you are using `yarn`, use these commands.
+=== "JAVA"
+
+ ```java
+ /**
+ * Type-safe handler for the SayHello operation
+ */
+ @Override
+ public SayHelloResponse handle(final SayHelloRequestInput request) {
+ LoggingInterceptor.getLogger(request).info("Start SayHello Operation");
+
+ // TODO: Implement SayHello Operation. `input` contains the request input.
+ SayHelloInput input = request.getInput();
+
+ return SayHello200Response.of(SayHelloResponseContent.builder()
+ .message(String.format("Hello %s!", input.getRequestParameters().getName()))
+ .build());
+ }
+ ```
+
+=== "PYTHON"
+
+ ```python
+ def say_hello(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
+ """
+ Type-safe handler for the SayHello operation
+ """
+ LoggingInterceptor.get_logger(input).info("Start SayHello Operation")
+
+ return Response.success(SayHelloResponseContent(
+ message=f"Hello {input.request_parameters.name}!"
+ ))
+ ```
-```
-yarn projen
-yarn build
-```
## Use the CDK construct
In your CDK application, using your preferred language, include the `Api` construct, vended from the generated infrastructure package.
+Given we used the AWS PDK vended infrastructure project, this will be configured for us already. Notice that our integrations have been mocked for us already, but we can replace them with our lambda implementation.
+
+!!!tip
+ Use the function constructs from the generated API infrastructure project to easily create lambda functions which reference implementations in the `api/handlers` projects.
+
=== "TS"
- Edit `packages/infra/src/main.ts` to include the `Api` construct.
+ Open `packages/infra/src/constructs/api.ts`. Notice our API has been mocked by default. We can replace the integration for our `sayHello` operation to use a lambda implementation:
- ```ts
- import { Stack, StackProps } from "aws-cdk-lib";
- import { Construct } from "constructs";
- import { Api } from "myapi-typescript-infra"; // <- generated typescript infrastructure package
+ ```typescript hl_lines="15 46-50"
+ import { UserIdentity } from "@aws/pdk/identity";
import { Authorizers, Integrations } from "@aws/pdk/type-safe-api";
- import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
+ import { Stack } from "aws-cdk-lib";
import { Cors } from "aws-cdk-lib/aws-apigateway";
- import * as path from "path";
-
- export class MyStack extends Stack {
- constructor(scope: Construct, id: string, props: StackProps = {}) {
- super(scope, id, props);
-
- // Instantiate the generated CDK construct to deploy an API Gateway API based on your model
- new Api(this, "MyApi", {
+ import {
+ AccountPrincipal,
+ AnyPrincipal,
+ Effect,
+ PolicyDocument,
+ PolicyStatement,
+ } from "aws-cdk-lib/aws-iam";
+ import { Construct } from "constructs";
+ import {
+ Api,
+ SayHelloFunction,
+ } from "myapi-typescript-infra";
+
+ /**
+ * Api construct props.
+ */
+ export interface ApiConstructProps {
+ /**
+ * Instance of the UserIdentity.
+ */
+ readonly userIdentity: UserIdentity;
+ }
+
+ /**
+ * Infrastructure construct to deploy a Type Safe API.
+ */
+ export class ApiConstruct extends Construct {
+ /**
+ * API instance
+ */
+ public readonly api: Api;
+
+ constructor(scope: Construct, id: string, props?: ApiConstructProps) {
+ super(scope, id);
+
+ this.api = new Api(this, id, {
defaultAuthorizer: Authorizers.iam(),
corsOptions: {
allowOrigins: Cors.ALL_ORIGINS,
allowMethods: Cors.ALL_METHODS,
},
- // Supply an integration for every operation
integrations: {
sayHello: {
- integration: Integrations.lambda(
- new NodejsFunction(this, "SayHelloLambda", {
- entry: path.resolve(__dirname, "say-hello.ts"),
- })
- ),
+ integration: Integrations.lambda(new SayHelloFunction(this, "SayHello")),
},
},
+ policy: new PolicyDocument({
+ statements: [
+ // Here we grant any AWS credentials from the account that the prototype is deployed in to call the api.
+ // Machine to machine fine-grained access can be defined here using more specific principals (eg roles or
+ // users) and resources (ie which api paths may be invoked by which principal) if required.
+ // If doing so, the cognito identity pool authenticated role must still be granted access for cognito users to
+ // still be granted access to the API.
+ new PolicyStatement({
+ effect: Effect.ALLOW,
+ principals: [new AccountPrincipal(Stack.of(this).account)],
+ actions: ["execute-api:Invoke"],
+ resources: ["execute-api:/*"],
+ }),
+ // Open up OPTIONS to allow browsers to make unauthenticated preflight requests
+ new PolicyStatement({
+ effect: Effect.ALLOW,
+ principals: [new AnyPrincipal()],
+ actions: ["execute-api:Invoke"],
+ resources: ["execute-api:/*/OPTIONS/*"],
+ }),
+ ],
+ }),
});
+
+ // Grant authenticated users access to invoke the api
+ props?.userIdentity.identityPool.authenticatedRole.addToPrincipalPolicy(
+ new PolicyStatement({
+ effect: Effect.ALLOW,
+ actions: ["execute-api:Invoke"],
+ resources: [this.api.api.arnForExecuteApi("*", "/*", "*")],
+ }),
+ );
}
}
```
=== "JAVA"
- Edit `packages/infra/src/main/java/com/my/api/MyApp.java` to include the `Api` construct.
+ Open `packages/infra/src/main/java/software/aws/infra/constructs/ApiConstruct.java`. Notice our API has been mocked by default. We can replace the integration for our `sayHello` operation to use a lambda implementation:
- ```java
- package com.my.api;
-
- // Imports from the generated infrastructure and runtime projects
+ ```java hl_lines="5 44-49"
+ package software.aws.infra.constructs;
+
import com.generated.api.myapijavainfra.infra.Api;
import com.generated.api.myapijavainfra.infra.ApiProps;
- import com.generated.api.myapijavaruntime.runtime.api.OperationConfig;
-
- import software.amazon.awscdk.Duration;
- import software.amazon.awscdk.services.apigateway.CorsOptions;
- import software.amazon.awscdk.services.lambda.Code;
- import software.amazon.awscdk.services.lambda.Function;
- import software.amazon.awscdk.services.lambda.FunctionProps;
- import software.amazon.awscdk.services.lambda.Runtime;
- import software.aws.awsprototypingsdk.typesafeapi.Authorizers;
- import software.aws.awsprototypingsdk.typesafeapi.Integrations;
- import software.aws.awsprototypingsdk.typesafeapi.TypeSafeApiIntegration;
-
- import software.amazon.awscdk.App;
- import software.amazon.awscdk.Stack;
-
+ import com.generated.api.myapijavainfra.infra.functions.SayHelloFunction;
+ import com.generated.api.myapijavaruntime.runtime.api.operation_config.OperationConfig;
+
import java.util.Arrays;
-
- public class MyApp {
- public static void main(final String[] args) {
- App app = new App();
- Stack s = new Stack(app, "infra");
-
- // Declare the API construct to deploy the API Gateway resources
- new Api(this, "Api", ApiProps.builder()
+
+ import software.amazon.awscdk.Stack;
+ import software.amazon.awscdk.services.apigateway.Cors;
+ import software.amazon.awscdk.services.apigateway.CorsOptions;
+ import software.amazon.awscdk.services.iam.AccountPrincipal;
+ import software.amazon.awscdk.services.iam.AnyPrincipal;
+ import software.amazon.awscdk.services.iam.Effect;
+ import software.amazon.awscdk.services.iam.PolicyDocument;
+ import software.amazon.awscdk.services.iam.PolicyDocumentProps;
+ import software.amazon.awscdk.services.iam.PolicyStatement;
+ import software.amazon.awscdk.services.iam.PolicyStatementProps;
+ import software.aws.pdk.identity.UserIdentity;
+ import software.aws.pdk.type_safe_api.Authorizers;
+ import software.aws.pdk.type_safe_api.TypeSafeApiIntegration;
+ import software.aws.pdk.type_safe_api.Integrations;
+ import software.constructs.Construct;
+
+ /**
+ * Infrastructure construct to deploy a Type Safe API.
+ */
+ public class ApiConstruct extends Construct {
+ /**
+ * API instance
+ */
+ public final Api api;
+
+ public ApiConstruct(Construct scope, String id, UserIdentity userIdentity) {
+ super(scope, id);
+
+ this.api = new Api(this, id, ApiProps.builder()
.defaultAuthorizer(Authorizers.iam())
.corsOptions(CorsOptions.builder()
- .allowOrigins(Arrays.asList("*"))
- .allowMethods(Arrays.asList("*"))
+ .allowOrigins(Cors.ALL_ORIGINS)
+ .allowMethods(Cors.ALL_METHODS)
.build())
.integrations(OperationConfig.builder()
.sayHello(TypeSafeApiIntegration.builder()
.integration(Integrations.lambda(
- // Point the lambda function to our built jar from the "lambdas" package
- new Function(s, "say-hello", FunctionProps.builder()
- .code(Code.fromAsset("../lambdas/dist/java/com/my/api/lambdas/1.0.0/lambdas-1.0.0.jar"))
- .handler("com.my.api.SayHelloHandler")
- .runtime(Runtime.JAVA_17)
- .timeout(Duration.seconds(30))
- .build())))
+ new SayHelloFunction(this, "SayHello")))
.build())
.build())
+ .policy(new PolicyDocument(PolicyDocumentProps.builder()
+ .statements(Arrays.asList(
+ // Here we grant any AWS credentials from the account that the prototype is deployed in to call the api.
+ // Machine to machine fine-grained access can be defined here using more specific principals (eg roles or
+ // users) and resources (ie which api paths may be invoked by which principal) if required.
+ // If doing so, the cognito identity pool authenticated role must still be granted access for cognito users to
+ // still be granted access to the API.
+ new PolicyStatement(PolicyStatementProps.builder()
+ .effect(Effect.ALLOW)
+ .principals(Arrays.asList(new AccountPrincipal(Stack.of(this))))
+ .actions(Arrays.asList("execute-api:Invoke"))
+ .resources(Arrays.asList("execute-api:/*"))
+ .build()),
+ // Open up OPTIONS to allow browsers to make unauthenticated preflight requests
+ new PolicyStatement(PolicyStatementProps.builder()
+ .effect(Effect.ALLOW)
+ .principals(Arrays.asList(new AnyPrincipal()))
+ .actions(Arrays.asList("execute-api:Invoke"))
+ .resources(Arrays.asList("execute-api:/*/OPTIONS/*"))
+ .build())
+ ))
+ .build()))
.build());
-
- app.synth();
+
+ userIdentity.getIdentityPool().getAuthenticatedRole()
+ .addToPrincipalPolicy(new PolicyStatement(PolicyStatementProps.builder()
+ .effect(Effect.ALLOW)
+ .actions(Arrays.asList("execute-api:Invoke"))
+ .resources(Arrays.asList(this.api.getApi().arnForExecuteApi("*", "/*", "*")))
+ .build()));
}
}
```
=== "PYTHON"
- Edit `packages/infra/infra/main.py` to include the `Api` construct.
+ Open `packages/infra/infra/constructs/api.py`. Notice our API has been mocked by default. We can replace the integration for our `sayHello` operation to use a lambda implementation:
- ```python
- import os
- from aws_cdk import Stack
+ ```python hl_lines="3 22-26"
from constructs import Construct
- from aws_cdk.aws_lambda import LayerVersion, Code, Function, Runtime
- from aws_prototyping_sdk.type_safe_api import Authorizers, TypeSafeApiIntegration, Integrations
-
- # Imports from the generated runtime and infrastructure projects
- from myapi_python_runtime.apis.tags.default_api_operation_config import OperationConfig
from myapi_python_infra.api import Api
-
- from pathlib import Path
- from os import path
-
- class MyStack(Stack):
- def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
- super().__init__(scope, construct_id, **kwargs)
-
- # Use the generated Api construct
- self.api = Api(self, 'Api',
- default_authorizer=Authorizers.iam(),
- integrations=OperationConfig(
- say_hello=TypeSafeApiIntegration(
- # Create a python lambda function from our "lambda-dist" package
- integration=Integrations.lambda_(Function(self, 'SayHello',
- runtime=Runtime.PYTHON_3_9,
- code=Code.from_asset(path.join("..", "lambdas", "lambda-dist")),
- handler="lambdas.say_hello.handler",
- )),
- ),
- ),
+ from myapi_python_infra.functions import SayHelloFunction
+ from myapi_python_runtime.api.operation_config import OperationConfig
+ from aws_cdk import Stack
+ from aws_pdk.identity import UserIdentity
+ from aws_pdk.type_safe_api import Authorizers, TypeSafeApiIntegration, Integrations
+ from aws_cdk.aws_apigateway import CorsOptions, Cors
+ from aws_cdk.aws_iam import AccountPrincipal, AnyPrincipal, Effect, PolicyDocument, PolicyStatement
+
+ # Infrastructure construct to deploy a Type Safe API.
+ class ApiConstruct(Construct):
+ def __init__(self, scope: Construct, id: str, user_identity: UserIdentity, **kwargs) -> None:
+ super().__init__(scope, id, **kwargs)
+
+ self.api = Api(self, id,
+ default_authorizer=Authorizers.iam(),
+ cors_options=CorsOptions(
+ allow_origins=Cors.ALL_ORIGINS,
+ allow_methods=Cors.ALL_METHODS
+ ),
+ integrations=OperationConfig(
+ say_hello=TypeSafeApiIntegration(
+ integration=Integrations.lambda_(SayHelloFunction(self, 'SayHello')),
+ ),
+ ),
+ policy=PolicyDocument(
+ statements=[
+ # Here we grant any AWS credentials from the account that the prototype is deployed in to call the api.
+ # Machine to machine fine-grained access can be defined here using more specific principals (eg roles or
+ # users) and resources (ie which api paths may be invoked by which principal) if required.
+ # If doing so, the cognito identity pool authenticated role must still be granted access for cognito users to
+ # still be granted access to the API.
+ PolicyStatement(
+ effect=Effect.ALLOW,
+ principals=[AccountPrincipal(Stack.of(self).account)],
+ actions=['execute-api:Invoke'],
+ resources=['execute-api:/*']
+ ),
+ # Open up OPTIONS to allow browsers to make unauthenticated preflight requests
+ PolicyStatement(
+ effect=Effect.ALLOW,
+ principals=[AnyPrincipal()],
+ actions=['execute-api:Invoke'],
+ resources=['execute-api:/*/OPTIONS/*']
+ )
+ ]
+ ))
+
+ user_identity.identity_pool.authenticated_role.add_to_principal_policy(
+ PolicyStatement(
+ effect=Effect.ALLOW,
+ actions=['execute-api:Invoke'],
+ resources=[self.api.api.arn_for_execute_api('*', '/*', '*')]
+ )
)
```
-## Implement a Lambda handler
-
-The generated runtime projects include lambda handler wrappers which provide type-safety for implementing your API operations. You can implement your lambda handlers in any of the supported languages, or mix and match languages for different operations if you prefer.
-
-=== "TS"
-
- In the TypeScript CDK application in the CDK construct, we used `NodejsFunction` which allows you to write your lambda handler code in the same `infra` project. Define `say-hello.ts` and use the generated lambda handler wrapper.
-
- For example, the implementation of `packages/infra/src/say-hello.ts` may include:
-
- ```ts
- import { sayHelloHandler } from "myapi-typescript-runtime"; // <- generated typescript runtime package
-
- // Use the handler wrapper for type-safety to ensure you correctly implement your modelled API operation
- export const handler = sayHelloHandler(async ({ input }) => {
- return {
- statusCode: 200,
- body: {
- message: `Hello ${input.requestParameters.name}`,
- },
- };
- });
- ```
-
-=== "JAVA"
-
- In your `lambdas` project you can define your lambda handler in its source directory, for example, `packages/lambdas/src/main/java/com/my/api/SayHelloHandler.java`:
-
- ```java
- package com.my.api;
-
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHello;
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHello200Response;
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHelloRequestInput;
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHelloResponse;
- import com.generated.api.myapijavaruntime.runtime.model.SayHelloResponseContent;
-
- /**
- * An example lambda handler which uses the generated handler wrapper class (Handlers.SayHello) to manage marshalling
- * inputs and outputs.
- */
- public class SayHelloHandler extends SayHello {
- @Override
- public SayHelloResponse handle(SayHelloRequestInput sayHelloRequestInput) {
- return SayHello200Response.of(SayHelloResponseContent.builder()
- .message(String.format("Hello %s", sayHelloRequestInput.getInput().getRequestParameters().getName()))
- .build());
- }
- }
- ```
-
-=== "PYTHON"
-
- In your `lambdas` project you can define your lambda handler in its source directory, for example, `packages/lambdas/lambdas/say_hello.py`:
-
- ```python
- from myapi_python_runtime.model.say_hello_response_content import SayHelloResponseContent
- from myapi_python_runtime.apis.tags.default_api_operation_config import say_hello_handler,
- SayHelloRequest, SayHelloOperationResponses, ApiResponse
-
-
- @say_hello_handler
- def handler(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
- return ApiResponse(
- status_code=200,
- body=SayHelloResponseContent(message="Hello {}".format(input.request_parameters["name"])),
- headers={}
- )
- ```
-
## Add a new operation
To add a new operation to your API, follow these steps.
@@ -614,6 +757,7 @@ Add the new operation in the `model` project, for example:
```smithy
/// Documentation about your operation can go here
@http(method: "POST", uri: "/goodbye")
+ @handler(language: "typescript") // <- you can also choose "python" or "java"
operation SayGoodbye {
input := {
@required
@@ -650,6 +794,9 @@ Add the new operation in the `model` project, for example:
/goodbye:
post:
operationId: sayGoodbye
+ # You can also choose "python" or "java" for the handler language below
+ x-handler:
+ language: "typescript"
requestBody:
content:
application/json:
@@ -687,20 +834,19 @@ Add the new operation in the `model` project, for example:
### Build your project
-To run a build in the root of your monorepo, use your package manager's build command.
+To run a build in the root of your monorepo, use the `pdk build` command:
```
-yarn build
+pdk build
```
-The build will regenerate the infrastructure, runtime, documentation, and library projects based on your updated model.
+The build will regenerate the infrastructure, runtime, documentation, and library projects based on your updated model. It will also generate a new stub for your new operation if you specified the `@handler` trait in Smithy or `x-handler` vendor extension in OpenAPI.
As you must define an integration for every operation, you may see the following build error in your CDK application.
```ts
TSError: ⨯ Unable to compile TypeScript:
-src/main.ts(16,7): error TS2741: Property 'sayGoodbye' is missing in type '{ sayHello: { integration: Integration; }; }' but required in type 'Operation
-Config'.
+src/constructs/api.ts: error TS2741: Property 'sayGoodbye' is missing in type '{ sayHello: { integration: Integration; }; }' but required in type 'OperationConfig'.
```
This is expected, so follow these steps to add an integration.
@@ -716,22 +862,14 @@ In your CDK application, add an integration for your new operation in the `Api`
...
integrations: {
sayHello: {
- integration: Integrations.lambda(
- new NodejsFunction(this, "SayHelloLambda", {
- entry: path.resolve(__dirname, "say-hello.ts"),
- })
- ),
+ integration: Integrations.lambda(new SayHelloFunction(this, "SayHello")),
},
// Add the new integration here
sayGoodbye: {
- integration: Integrations.lambda(
- new NodejsFunction(this, "SayGoodbyeLambda", {
- // Point at say-goodbye.ts which we'll define next
- entry: path.resolve(__dirname, "say-goodbye.ts"),
- })
- ),
+ integration: Integrations.lambda(new SayGoodbyeFunction(this, "SayGoodbye")),
},
},
+ ...
});
```
@@ -743,29 +881,15 @@ In your CDK application, add an integration for your new operation in the `Api`
.integrations(OperationConfig.builder()
.sayHello(TypeSafeApiIntegration.builder()
.integration(Integrations.lambda(
- new Function(s, "say-hello", FunctionProps.builder()
- .code(Code.fromAsset("../lambdas/dist/java/com/my/api/lambdas/1.0.0/lambdas-1.0.0.jar"))
- .handler("com.my.api.SayHelloHandler")
- .runtime(Runtime.JAVA_17)
- .timeout(Duration.seconds(30))
- .build())))
+ new SayHelloFunction(this, "SayHello")))
.build())
- .build())
- // Add the new integration here
.sayGoodbye(TypeSafeApiIntegration.builder()
.integration(Integrations.lambda(
- new Function(s, "say-goodbye", FunctionProps.builder()
- // We'll point at the same jar and define our handler in the same "lambdas" package
- .code(Code.fromAsset("../lambdas/dist/java/com/my/api/lambdas/1.0.0/lambdas-1.0.0.jar"))
- // Point to SayGoodbyeHandler which we'll define next
- .handler("com.my.api.SayGoodbyeHandler")
- .runtime(Runtime.JAVA_17)
- .timeout(Duration.seconds(30))
- .build())))
+ new SayGoodbyeFunction(this, "SayGoodbye")))
.build())
.build())
+ ...
.build());
-
```
=== "PYTHON"
@@ -775,90 +899,28 @@ In your CDK application, add an integration for your new operation in the `Api`
...
integrations=OperationConfig(
say_hello=TypeSafeApiIntegration(
- integration=Integrations.lambda_(Function(self, 'SayHello',
- runtime=Runtime.PYTHON_3_9,
- code=Code.from_asset(path.join("..", "lambdas", "lambda-dist")),
- handler="lambdas.say_hello.handler",
- )),
+ integration=Integrations.lambda_(SayHelloFunction(self, 'SayHello')),
),
- # Add the new integration here
say_goodbye=TypeSafeApiIntegration(
- integration=Integrations.lambda_(Function(self, 'SayHello',
- runtime=Runtime.PYTHON_3_9,
- # We'll point to the same distributable in the "lambdas" package
- code=Code.from_asset(path.join("..", "lambdas", "lambda-dist")),
- # But this time we point to the handler method in say_goodbye.py which we'll define next
- handler="lambdas.say_goodbye.handler",
- )),
+ integration=Integrations.lambda_(SayGoodbyeFunction(self, 'SayGoodbye')),
),
),
+ ...
)
```
### Implement the Lambda handler
-After you add the integration, implement the API operation:
-
-=== "TS"
-
- Add the new handler in `packages/infra/src/say-goodbye.ts`:
-
- ```ts
- import { sayGoodbyeHandler } from "myapi-typescript-runtime";
-
- export const handler = sayGoodbyeHandler(async ({ input }) => {
- return {
- statusCode: 200,
- body: {
- // This time we're referencing the name parameter from the POST request body
- message: `Goodbye ${input.body.name}`,
- },
- };
- });
- ```
-
-=== "JAVA"
-
- In your `lambdas` project, define your new lambda handler: `packages/lambdas/src/main/java/com/my/api/SayGoodbyeHandler.java`:
-
- ```java
- package com.my.api;
-
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayGoodbye;
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayGoodbye200Response;
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayGoodbyeRequestInput;
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayGoodbyeResponse;
- import com.generated.api.myapijavaruntime.runtime.model.SayGoodbyeResponseContent;
-
- public class SayGoodbyeHandler extends SayGoodbye {
- @Override
- public SayGoodbyeResponse handle(SayGoodbyeRequestInput sayGoodbyeRequestInput) {
- return SayGoodbye200Response.of(SayGoodbyeResponseContent.builder()
- .message(String.format("Goodbye %s", sayGoodbyeRequestInput.getInput().getBody().getName()))
- .build());
- }
- }
- ```
-
-=== "PYTHON"
+As described above, you'll find a lambda handler stub for your new operation, which you can edit as you wish.
- In your `lambdas` project, define your new lambda handler: `packages/lambdas/lambdas/say_goodbye.py`:
-
- ```python
- from myapi_python_runtime.model.say_goodbye_response_content import SayGoodbyeResponseContent
- from myapi_python_runtime.apis.tags.default_api_operation_config import say_goodbye_handler,
- SayGoodbyeRequest, SayGoodbyeOperationResponses, ApiResponse
+### Deploy your project
+After you implement your new operation, build your project again and deploy it:
- @say_goodbye_handler
- def handler(input: SayGoodbyeRequest, **kwargs) -> SayGoodbyeOperationResponses:
- return ApiResponse(
- status_code=200,
- body=SayGoodbyeResponseContent(message="Goodbye {}".format(input.body["name"])),
- headers={}
- )
- ```
-
-### Deploy your project
+```bash
+pdk build
+cd packages/infra
+pdk run deploy --require-approval never
+```
-After you implement your new operation, build your project again using `yarn build`, deploy, and try out your new operation.
+Try out your new API! You can use a tool such as [awscurl](https://github.com/okigan/awscurl) to make Sigv4 signed requests to your API, since we set the default authorizer to `Authorizers.iam()`. Alternatively, you can deploy the [`CloudscapeReactTsWebsiteProject`](../cloudscape-react-ts-website/index.md) and try out the [API Explorer](../cloudscape-react-ts-website/api_explorer.md).
\ No newline at end of file
diff --git a/packages/type-safe-api/docs/developer_guides/type-safe-api/interceptors.md b/packages/type-safe-api/docs/developer_guides/type-safe-api/interceptors.md
new file mode 100644
index 000000000..5eb981e49
--- /dev/null
+++ b/packages/type-safe-api/docs/developer_guides/type-safe-api/interceptors.md
@@ -0,0 +1,538 @@
+# Interceptors
+
+Interceptors provide a way to share common logic between lambda handlers. They can execute code before and/or after the lambda handler implementation is executed.
+
+## Provided Interceptors
+
+The generated runtime packages include a collection of useful interceptors, and a "default interceptors" array which lambda handler stubs will use by default.
+
+### Powertools for AWS Lambda Interceptors
+
+Logging, Tracing and Metrics interceptors are provided which use Powertools for AWS Lambda. These each initialise a logger, tracer or metrics instance and provide it to your handlers in the `interceptorContext`. You can use the static helper methods provided by each interceptor to extract the instance.
+
+Please refer to the powertools documentation for [typescript](https://docs.powertools.aws.dev/lambda/typescript/latest), [python](https://docs.powertools.aws.dev/lambda/python/latest), and [java](https://docs.powertools.aws.dev/lambda/java) for more details.
+
+### Try Catch Interceptor
+
+Another provided interceptor is the `TryCatchInterceptor` which, when used, allows you to throw responses and have them returned as errors.
+
+=== "TS"
+
+ ```ts
+ import { Response } from "myapi-typescript-runtime";
+
+ // Some deeply nested function called by your handler
+ throw Response.notFound({ message: "Resource not found!" });
+ ```
+
+=== "JAVA"
+
+ ```java
+ // Some deeply nested function called by your handler
+ throw SayHello404Response.of(NotFoundErrorResponseContent.builder()
+ .message("Resource not found!")
+ .build())
+ ```
+
+=== "PYTHON"
+
+ ```python
+ # Some deeply nested function called by your handler
+ raise Response.not_found(NotFoundErrorResponseContent(
+ message="Resource not found!"
+ ))
+ ```
+
+It will also catch and log any uncaught exceptions, before returning an opaque `500` response to the caller.
+
+### Response Headers Interceptor (CORS Interceptor)
+
+This interceptor can be used to add headers to the responses returned by your lambda handlers.
+
+=== "TS"
+
+ ```ts
+ import { corsInterceptor, buildResponseHeaderInterceptor, sayHelloHandler } from "myapi-typescript-runtime";
+
+ // Create an interceptor which returns custom headers
+ const customHeaders = buildResponseHeaderInterceptor({
+ "x-my-response-header": "value"
+ });
+
+ // Also use the cors interceptor which allows all origins and headers
+ export const handler = sayHelloHandler(corsInterceptor, customHeaders, ...);
+ ```
+
+=== "JAVA"
+
+ ```java
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.Interceptors;
+ import com.generated.api.myapijavaruntime.runtime.api.interceptors.ResponseHeadersInterceptor;
+
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHello;
+
+ // When constructed with no parameters, it returns cors headers allowing all origins and headers
+ @Interceptors({ResponseHeadersInterceptor.class})
+ public class SayHelloHandler extends SayHello {
+
+ @Override
+ public List> getInterceptors() {
+ // Return custom headers by instantiating with a parameter
+ return Arrays.asList(new ResponseHeadersInterceptor<>(Map.of(
+ "x-my-response-header", "value"
+ )));
+ }
+
+ ...
+ }
+ ```
+
+=== "PYTHON"
+
+ ```python
+ from myapi_python_runtime.interceptors.response_headers import cors_interceptor, build_response_headers_interceptor
+ from myapi_python_runtime.api.operation_config import (
+ say_hello_handler, SayHelloRequest, SayHelloOperationResponses, SayHelloResponseContent
+ )
+
+ # Create an interceptor which returns custom headers
+ custom_headers = build_response_headers_interceptor({
+ "x-my-response-header": "value"
+ })
+
+ # Also use the cors interceptor which allows all origins and headers
+ @say_hello_handler(interceptors=[cors_interceptor, custom_headers])
+ def handler(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
+ ...
+ ```
+
+
+## Using and Implementing Interceptors
+
+The lambda handler wrappers allow you to pass in a _chain_ of handler functions to handle the request. This allows you to implement middleware / interceptors for handling requests. Each handler function may choose whether or not to continue the handler chain by invoking `chain.next`.
+
+=== "TS"
+
+ In typescript, interceptors are passed as separate arguments to the generated handler wrapper, in the order in which they should be executed. Call `request.chain.next(request)` from an interceptor to delegate to the rest of the chain to handle a request. Note that the last handler in the chain (ie the actual request handler which transforms the input to the output) should not call `chain.next`.
+
+ ```ts
+ import {
+ sayHelloHandler,
+ ChainedRequestInput,
+ OperationResponse,
+ } from "myapi-typescript-runtime";
+
+ // Interceptor to wrap invocations in a try/catch, returning a 500 error for any unhandled exceptions.
+ const tryCatchInterceptor = async <
+ RequestParameters,
+ RequestBody,
+ Response extends OperationResponse,
+ >(
+ request: ChainedRequestInput<
+ RequestParameters,
+ RequestBody,
+ Response
+ >
+ ): Promise> => {
+ try {
+ return await request.chain.next(request);
+ } catch (e: any) {
+ return { statusCode: 500, body: { errorMessage: e.message } };
+ }
+ };
+
+ // tryCatchInterceptor is passed first, so it runs first and calls the second argument function (the request handler) via chain.next
+ export const handler = sayHelloHandler(
+ tryCatchInterceptor,
+ async ({ input }) => {
+ return {
+ statusCode: 200,
+ body: {
+ message: `Hello ${input.requestParameters.name}!`,
+ },
+ };
+ }
+ );
+ ```
+
+ Another example interceptor might be to record request time metrics. The example below includes the full generic type signature for an interceptor:
+
+ ```ts
+ import { ChainedRequestInput, OperationResponse } from "myapi-typescript-runtime";
+
+ const timingInterceptor = async <
+ RequestParameters,
+ RequestBody,
+ Response extends OperationResponse,
+ >(
+ request: ChainedRequestInput<
+ RequestParameters,
+ RequestBody,
+ Response
+ >
+ ): Promise => {
+ const start = Date.now();
+ const response = await request.chain.next(request);
+ const end = Date.now();
+ console.log(`Took ${end - start} ms`);
+ return response;
+ };
+ ```
+
+ Interceptors may mutate the `interceptorContext` to pass state to further interceptors or the final lambda handler, for example an `identityInterceptor` might want to extract the authenticated user from the request so that it is available in handlers.
+
+ ```ts
+ import {
+ LambdaRequestParameters,
+ LambdaHandlerChain,
+ OperationResponse,
+ ChainedRequestInput,
+ } from "myapi-typescript-runtime";
+
+ const identityInterceptor = async <
+ RequestParameters,
+ RequestBody,
+ Response extends OperationResponse,
+ >(
+ request: ChainedRequestInput<
+ RequestParameters,
+ RequestBody,
+ Response
+ >
+ ): Promise => {
+ const authenticatedUser = await getAuthenticatedUser(request.event);
+ return await request.chain.next({
+ ...request,
+ interceptorContext: {
+ ...request.interceptorContext,
+ authenticatedUser,
+ },
+ });
+ };
+ ```
+
+=== "JAVA"
+
+ In Java, interceptors can be added to a handler via the `@Interceptors` class annotation:
+
+ ```java
+ import com.generated.api.myjavaapiruntime.runtime.api.Handlers.Interceptors;
+
+ @Interceptors({ TimingInterceptor.class, TryCatchInterceptor.class })
+ public class SayHelloHandler extends SayHello {
+ @Override
+ public SayHelloResponse handle(SayHelloRequestInput sayHelloRequestInput) {
+ return SayHello200Response.of(HelloResponse.builder()
+ .message(String.format("Hello %s", sayHelloRequestInput.getInput().getRequestParameters().getName()))
+ .build());
+ }
+ }
+ ```
+
+ To write an interceptor, you can implement the `Interceptor` interface. For example, a timing interceptor:
+
+ ```java
+ import com.generated.api.myjavaapiruntime.runtime.api.Handlers.Interceptor;
+ import com.generated.api.myjavaapiruntime.runtime.api.Handlers.ChainedRequestInput;
+ import com.generated.api.myjavaapiruntime.runtime.api.Handlers.Response;
+
+ public class TimingInterceptor implements Interceptor {
+ @Override
+ public Response handle(ChainedRequestInput input) {
+ long start = System.currentTimeMillis();
+ Response res = input.getChain().next(input);
+ long end = System.currentTimeMillis();
+ System.out.printf("Took %d ms%n", end - start);
+ return res;
+ }
+ }
+ ```
+
+ Interceptors may choose to return different responses, for example to return a 500 response for any unhandled exceptions:
+
+ ```java
+ import com.generated.api.myjavaapiruntime.runtime.api.Handlers.Interceptor;
+ import com.generated.api.myjavaapiruntime.runtime.api.Handlers.ChainedRequestInput;
+ import com.generated.api.myjavaapiruntime.runtime.api.Handlers.Response;
+ import com.generated.api.myjavaapiruntime.runtime.api.Handlers.ApiResponse;
+ import com.generated.api.myjavaapiruntime.runtime.model.InternalFailureErrorResponseContent;
+
+ public class TryCatchInterceptor implements Interceptor {
+ @Override
+ public Response handle(ChainedRequestInput input) {
+ try {
+ return input.getChain().next(input);
+ } catch (Exception e) {
+ return ApiResponse.builder()
+ .statusCode(500)
+ .body(InternalFailureErrorResponseContent.builder()
+ .errorMessage(e.getMessage())
+ .build().toJson())
+ .build();
+ }
+ }
+ }
+ ```
+
+ Interceptors are permitted to mutate the "interceptor context", which is a `Map`. Each interceptor in the chain, and the final handler, can access this context:
+
+ ```java
+ public class IdentityInterceptor implements Interceptor {
+ @Override
+ public Response handle(ChainedRequestInput input) {
+ input.getInterceptorContext().put("AuthenticatedUser", this.getAuthenticatedUser(input.getEvent()));
+ return input.getChain().next(input);
+ }
+ }
+ ```
+
+ Interceptors can also mutate the response returned by the handler chain. An example use case might be adding cross-origin resource sharing headers:
+
+ ```java
+ public static class AddCorsHeadersInterceptor implements Interceptor {
+ @Override
+ public Response handle(ChainedRequestInput input) {
+ Response res = input.getChain().next(input);
+ res.getHeaders().put("Access-Control-Allow-Origin", "*");
+ res.getHeaders().put("Access-Control-Allow-Headers", "*");
+ return res;
+ }
+ }
+ ```
+
+ Interceptors referenced by the `@Interceptors` annotation must be constructable with no arguments. If more complex instantiation of your interceptor is required (for example if you are using dependency injection or wish to pass configuration to your interceptor), you may instead override the `getInterceptors` method in your handler:
+
+ ```java
+ public class SayHelloHandler extends SayHello {
+ @Override
+ public List> getInterceptors() {
+ return Arrays.asList(
+ new MyConfiguredInterceptor<>(42),
+ new MyOtherConfiguredInterceptor<>("configuration"));
+ }
+
+ @Override
+ public SayHelloResponse handle(SayHelloRequestInput sayHelloRequestInput) {
+ return SayHello200Response.of(HelloResponse.builder()
+ .message(String.format("Hello %s!", sayHelloRequestInput.getInput().getRequestParameters().getName()))
+ .build());
+ }
+ }
+ ```
+
+=== "PYTHON"
+
+ In Python, a list of interceptors can be passed as a keyword argument to the generated lambda handler decorator, for example:
+
+ ```python
+ from myapi_python_runtime.apis.tags.default_api_operation_config import say_hello_handler, SayHelloRequest, ApiResponse, SayHelloOperationResponses
+ from myapi_python_runtime.model.api_error import ApiError
+ from myapi_python_runtime.model.hello_response import HelloResponse
+
+ @say_hello_handler(interceptors=[timing_interceptor, try_catch_interceptor])
+ def handler(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
+ return ApiResponse(
+ status_code=200,
+ body=HelloResponse(message="Hello {}!".format(input.request_parameters["name"])),
+ headers={}
+ )
+ ```
+
+ Writing an interceptor is just like writing a lambda handler. Call `chain.next(input)` from an interceptor to delegate to the rest of the chain to handle a request.
+
+ ```python
+ import time
+ from myapi_python_runtime.apis.tags.default_api_operation_config import ChainedApiRequest, ApiResponse
+
+ def timing_interceptor(input: ChainedApiRequest) -> ApiResponse:
+ start = int(round(time.time() * 1000))
+ response = input.chain.next(input)
+ end = int(round(time.time() * 1000))
+ print("Took {} ms".format(end - start))
+ return response
+ ```
+
+ Interceptors may choose to return different responses, for example to return a 500 response for any unhandled exceptions:
+
+ ```python
+ import time
+ from myapi_python_runtime.model.api_error import ApiError
+ from myapi_python_runtime.apis.tags.default_api_operation_config import ChainedApiRequest, ApiResponse
+
+ def try_catch_interceptor(input: ChainedApiRequest) -> ApiResponse:
+ try:
+ return input.chain.next(input)
+ except Exception as e:
+ return ApiResponse(
+ status_code=500,
+ body=ApiError(errorMessage=str(e)),
+ headers={}
+ )
+ ```
+
+ Interceptors are permitted to mutate the "interceptor context", which is a `Dict[str, Any]`. Each interceptor in the chain, and the final handler, can access this context:
+
+ ```python
+ def identity_interceptor(input: ChainedApiRequest) -> ApiResponse:
+ input.interceptor_context["AuthenticatedUser"] = get_authenticated_user(input.event)
+ return input.chain.next(input)
+ ```
+
+ Interceptors can also mutate the response returned by the handler chain. An example use case might be adding cross-origin resource sharing headers:
+
+ ```python
+ def add_cors_headers_interceptor(input: ChainedApiRequest) -> ApiResponse:
+ response = input.chain.next(input)
+ return ApiResponse(
+ status_code=response.status_code,
+ body=response.body,
+ headers={
+ **response.headers,
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Headers": "*"
+ }
+ )
+ ```
+
+## Handler Router
+
+The lambda handler wrappers can be used in isolation as handlers for separate lambda functions. If you would like to use a single lambda function to serve all requests, you can wrap your individual handlers with a "handler router", for example:
+
+=== "TS"
+
+ ```ts
+ import {
+ handlerRouter,
+ sayHelloHandler,
+ sayGoodbyeHandler,
+ } from "myapi-typescript-runtime";
+ import { corsInterceptor } from "./interceptors";
+ import { sayGoodbye } from "./handlers/say-goodbye";
+
+ const sayHello = sayHelloHandler(async ({ input }) => {
+ return {
+ statusCode: 200,
+ body: {
+ message: `Hello ${input.requestParameters.name}!`,
+ },
+ };
+ });
+
+ export const handler = handlerRouter({
+ // Interceptors declared in this list will apply to all operations
+ interceptors: [corsInterceptor],
+ // Assign handlers to each operation here
+ handlers: {
+ sayHello,
+ sayGoodbye,
+ },
+ });
+ ```
+
+=== "JAVA"
+
+ ```java
+ import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayGoodbye;
+ import com.generated.api.myapijavaruntime.runtime.api.Handlers.HandlerRouter;
+ import com.generated.api.myapijavaruntime.runtime.api.Handlers.Interceptors;
+ import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHello;
+
+ import java.util.Arrays;
+ import java.util.List;
+
+ // Interceptors defined here apply to all operations
+ @Interceptors({ TimingInterceptor.class })
+ public class ApiHandlerRouter extends HandlerRouter {
+ // You must implement a method to return a handler for every operation
+ @Override
+ public SayHello sayHello() {
+ return new SayHelloHandler();
+ }
+
+ @Override
+ public SayGoodbye sayGoodbye() {
+ return new SayGoodbyeHandler();
+ }
+ }
+ ```
+
+=== "PYTHON"
+
+ ```python
+ from myapi_python_runtime.apis.tags.default_api_operation_config import say_hello_handler, SayHelloRequest, ApiResponse, SayHelloOperationResponses, handler_router, HandlerRouterHandlers
+ from myapi_python_runtime.model.api_error import ApiError
+ from myapi_python_runtime.model.hello_response import HelloResponse
+ from other_handlers import say_goodbye
+ from my_interceptors import cors_interceptor
+
+ @say_hello_handler
+ def say_hello(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
+ return ApiResponse(
+ status_code=200,
+ body=HelloResponse(message="Hello {}!".format(input.request_parameters["name"])),
+ headers={}
+ )
+
+ handler = handler_router(
+ # Interceptors defined here will apply to all operations
+ interceptors=[cors_interceptor],
+ handlers=HandlerRouterHandlers(
+ say_hello=say_hello,
+ say_goodbye=say_goodbye
+ )
+ )
+ ```
+
+When you use a handler router, you must specify the same lambda function for every integration in your `Api` CDK construct. To save typing, you can use the `Operations.all` method from your generated runtime package:
+
+=== "TS"
+
+ ```ts
+ import { Integrations, Authorizers } from "@aws/pdk/type-safe-api";
+ import { Operations } from "myapi-typescript-runtime";
+ import { Api } from "myapi-typescript-infra";
+ import { NodejsFunction } from "aws-cdk-lib/aws-lambda";
+
+ new Api(this, "Api", {
+ defaultAuthorizer: Authorizers.iam(),
+ // Use the same integration for every operation.
+ integrations: Operations.all({
+ integration: Integrations.lambda(new NodejsFunction(this, "router")),
+ }),
+ });
+ ```
+
+=== "JAVA"
+
+ ```java
+ import software.aws.awspdk.typesafeapi.TypeSafeApiIntegration;
+ import software.aws.awspdk.typesafeapi.Integrations;
+ import software.aws.awspdk.typesafeapi.Authorizers;
+
+ import com.generated.api.myapijavaruntime.runtime.api.Operations;
+ import com.generated.api.myapijavainfra.infra.Api;
+ import com.generated.api.myapijavainfra.infra.ApiProps;
+
+ new Api(s, "Api", ApiProps.builder()
+ .defaultAuthorizer(Authorizers.iam())
+ .integrations(Operations.all(TypeSafeApiIntegration.builder()
+ .integration(Integrations.lambda(...))
+ .build()).build())
+ .build());
+ ```
+
+=== "PYTHON"
+
+ ```python
+ from aws_pdk.type_safe_api import Integrations, TypeSafeApiIntegration, Authorizers
+ from myapi_python_runtime.apis.tags.default_api_operation_config import Operations
+ from myapi_python_infra.api import Api
+
+ Api(self, "Api",
+ default_authorizer=Authorizers.iam(),
+ # Use the same integration for every operation.
+ integrations=Operations.all(
+ integration=Integrations.lambda_(NodejsFunction(scope, "router"))
+ )
+ )
+ ```
diff --git a/packages/type-safe-api/docs/developer_guides/type-safe-api/lambda_handlers.md b/packages/type-safe-api/docs/developer_guides/type-safe-api/lambda_handlers.md
index 1af56ff88..b9af60f82 100644
--- a/packages/type-safe-api/docs/developer_guides/type-safe-api/lambda_handlers.md
+++ b/packages/type-safe-api/docs/developer_guides/type-safe-api/lambda_handlers.md
@@ -7,33 +7,30 @@ For example:
=== "TS"
```ts
- import { sayHelloHandler } from "myapi-typescript-runtime";
+ import { sayHelloHandler, Response } from "myapi-typescript-runtime";
export const handler = sayHelloHandler(async ({ input }) => {
- return {
- statusCode: 200,
- body: {
- message: `Hello ${input.requestParameters.name}!`,
- },
- };
+ return Response.success({
+ message: `Hello ${input.requestParameters.name}!`,
+ });
});
```
=== "JAVA"
```java
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHello;
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHello200Response;
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHelloRequestInput;
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHelloResponse;
- import com.generated.api.myapijavaruntime.runtime.model.HelloResponse;
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHello;
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHello200Response;
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHelloRequestInput;
+ import com.generated.api.myapijavaruntime.runtime.api.handlers.say_hello.SayHelloResponse;
+ import com.generated.api.myapijavaruntime.runtime.model.SayHelloResponseContent;
public class SayHelloHandler extends SayHello {
@Override
- public SayHelloResponse handle(SayHelloRequestInput sayHelloRequestInput) {
- return SayHello200Response.of(HelloResponse.builder()
- .message(String.format("Hello %s", sayHelloRequestInput.getInput().getRequestParameters().getName()))
+ public SayHelloResponse handle(final SayHelloRequestInput request) {
+ return SayHello200Response.of(SayHelloResponseContent.builder()
+ .message(String.format("Hello %s!", request.getInput().getRequestParameters().getName()))
.build());
}
}
@@ -42,449 +39,69 @@ For example:
=== "PYTHON"
```python
- from myapi_python_runtime.apis.tags.default_api_operation_config import say_hello_handler, SayHelloRequest, ApiResponse, SayHelloOperationResponses
+ from myapi_python_runtime.api.operation_config import say_hello_handler, SayHelloRequest, ApiResponse, SayHelloOperationResponses
from myapi_python_runtime.model.api_error import ApiError
- from myapi_python_runtime.model.hello_response import HelloResponse
+ from myapi_python_runtime.model.say_hello_response_content import SayHelloResponseContent
@say_hello_handler
def handler(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
- return ApiResponse(
- status_code=200,
- body=HelloResponse(message="Hello {}!".format(input.request_parameters["name"])),
- headers={}
+ return Response.success(
+ SayHelloResponseContent(message=f"Hello {input.request_parameters.name}!"),
)
```
-## Interceptors
+## Handler Projects
-The lambda handler wrappers allow you to pass in a _chain_ of handler functions to handle the request. This allows you to implement middleware / interceptors for handling requests. Each handler function may choose whether or not to continue the handler chain by invoking `chain.next`.
+By configuring `handlers.languages` in your `TypeSafeApiProject` and annotating your operations, you can take advantage of generated handler stubs and generated lambda function CDK constructs to speed up development even further.
-=== "TS"
+=== "SMITHY"
- In typescript, interceptors are passed as separate arguments to the generated handler wrapper, in the order in which they should be executed. Call `request.chain.next(request)` from an interceptor to delegate to the rest of the chain to handle a request. Note that the last handler in the chain (ie the actual request handler which transforms the input to the output) should not call `chain.next`.
-
- ```ts
- import {
- sayHelloHandler,
- ChainedRequestInput,
- OperationResponse,
- } from "myapi-typescript-runtime";
-
- // Interceptor to wrap invocations in a try/catch, returning a 500 error for any unhandled exceptions.
- const tryCatchInterceptor = async <
- RequestParameters,
- RequestArrayParameters,
- RequestBody,
- Response
- >(
- request: ChainedRequestInput<
- RequestParameters,
- RequestArrayParameters,
- RequestBody,
- Response
- >
- ): Promise> => {
- try {
- return await request.chain.next(request);
- } catch (e: any) {
- return { statusCode: 500, body: { errorMessage: e.message } };
- }
- };
-
- // tryCatchInterceptor is passed first, so it runs first and calls the second argument function (the request handler) via chain.next
- export const handler = sayHelloHandler(
- tryCatchInterceptor,
- async ({ input }) => {
- return {
- statusCode: 200,
- body: {
- message: `Hello ${input.requestParameters.name}!`,
- },
- };
- }
- );
- ```
-
- Another example interceptor might be to record request time metrics. The example below includes the full generic type signature for an interceptor:
-
- ```ts
- import { ChainedRequestInput } from "myapi-typescript-runtime";
-
- const timingInterceptor = async <
- RequestParameters,
- RequestArrayParameters,
- RequestBody,
- Response
- >(
- request: ChainedRequestInput<
- RequestParameters,
- RequestArrayParameters,
- RequestBody,
- Response
- >
- ): Promise => {
- const start = Date.now();
- const response = await request.chain.next(request);
- const end = Date.now();
- console.log(`Took ${end - start} ms`);
- return response;
- };
- ```
-
- Interceptors may mutate the `interceptorContext` to pass state to further interceptors or the final lambda handler, for example an `identityInterceptor` might want to extract the authenticated user from the request so that it is available in handlers.
-
- ```ts
- import {
- LambdaRequestParameters,
- LambdaHandlerChain,
- } from "myapi-typescript-runtime";
-
- const identityInterceptor = async <
- RequestParameters,
- RequestArrayParameters,
- RequestBody,
- Response
- >(
- request: ChainedRequestInput<
- RequestParameters,
- RequestArrayParameters,
- RequestBody,
- Response
- >
- ): Promise => {
- const authenticatedUser = await getAuthenticatedUser(request.event);
- return await request.chain.next({
- ...request,
- interceptorContext: {
- ...request.interceptorContext,
- authenticatedUser,
- },
- });
- };
- ```
+ Use the `@handler` trait, and specify the language you wish to implement this operation in.
-=== "JAVA"
-
- In Java, interceptors can be added to a handler via the `@Interceptors` class annotation:
-
- ```java
- import com.generated.api.myjavaapiruntime.runtime.api.Handlers.Interceptors;
-
- @Interceptors({ TimingInterceptor.class, TryCatchInterceptor.class })
- public class SayHelloHandler extends SayHello {
- @Override
- public SayHelloResponse handle(SayHelloRequestInput sayHelloRequestInput) {
- return SayHello200Response.of(HelloResponse.builder()
- .message(String.format("Hello %s", sayHelloRequestInput.getInput().getRequestParameters().getName()))
- .build());
- }
- }
- ```
-
- To write an interceptor, you can implement the `Interceptor` interface. For example, a timing interceptor:
-
- ```java
- import com.generated.api.myjavaapiruntime.runtime.api.Handlers.Interceptor;
- import com.generated.api.myjavaapiruntime.runtime.api.Handlers.ChainedRequestInput;
- import com.generated.api.myjavaapiruntime.runtime.api.Handlers.Response;
-
- public class TimingInterceptor implements Interceptor {
- @Override
- public Response handle(ChainedRequestInput input) {
- long start = System.currentTimeMillis();
- Response res = input.getChain().next(input);
- long end = System.currentTimeMillis();
- System.out.printf("Took %d ms%n", end - start);
- return res;
- }
- }
- ```
-
- Interceptors may choose to return different responses, for example to return a 500 response for any unhandled exceptions:
-
- ```java
- import com.generated.api.myjavaapiruntime.runtime.api.Handlers.Interceptor;
- import com.generated.api.myjavaapiruntime.runtime.api.Handlers.ChainedRequestInput;
- import com.generated.api.myjavaapiruntime.runtime.api.Handlers.Response;
- import com.generated.api.myjavaapiruntime.runtime.api.Handlers.ApiResponse;
- import com.generated.api.myjavaapiruntime.runtime.model.ApiError;
-
- public class TryCatchInterceptor implements Interceptor {
- @Override
- public Response handle(ChainedRequestInput input) {
- try {
- return input.getChain().next(input);
- } catch (Exception e) {
- return ApiResponse.builder()
- .statusCode(500)
- .body(ApiError.builder()
- .errorMessage(e.getMessage())
- .build().toJson())
- .build();
- }
+ ```smithy hl_lines="3"
+ @readonly
+ @http(method: "GET", uri: "/hello")
+ @handler(language: "typescript")
+ operation SayHello {
+ input := {
+ @httpQuery("name")
+ @required
+ name: String
}
- }
- ```
-
- Interceptors are permitted to mutate the "interceptor context", which is a `Map`. Each interceptor in the chain, and the final handler, can access this context:
-
- ```java
- public class IdentityInterceptor implements Interceptor {
- @Override
- public Response handle(ChainedRequestInput input) {
- input.getInterceptorContext().put("AuthenticatedUser", this.getAuthenticatedUser(input.getEvent()));
- return input.getChain().next(input);
- }
- }
- ```
-
- Interceptors can also mutate the response returned by the handler chain. An example use case might be adding cross-origin resource sharing headers:
-
- ```java
- public static class AddCorsHeadersInterceptor implements Interceptor {
- @Override
- public Response handle(ChainedRequestInput input) {
- Response res = input.getChain().next(input);
- res.getHeaders().put("Access-Control-Allow-Origin", "*");
- res.getHeaders().put("Access-Control-Allow-Headers", "*");
- return res;
- }
- }
- ```
-
- Interceptors referenced by the `@Interceptors` annotation must be constructable with no arguments. If more complex instantiation of your interceptor is required (for example if you are using dependency injection or wish to pass configuration to your interceptor), you may instead override the `getInterceptors` method in your handler:
-
- ```java
- public class SayHelloHandler extends SayHello {
- @Override
- public List> getInterceptors() {
- return Arrays.asList(
- new MyConfiguredInterceptor<>(42),
- new MyOtherConfiguredInterceptor<>("configuration"));
- }
-
- @Override
- public SayHelloResponse handle(SayHelloRequestInput sayHelloRequestInput) {
- return SayHello200Response.of(HelloResponse.builder()
- .message(String.format("Hello %s!", sayHelloRequestInput.getInput().getRequestParameters().getName()))
- .build());
+ output := {
+ @required
+ message: String
}
}
```
-=== "PYTHON"
+=== "OPENAPI"
- In Python, a list of interceptors can be passed as a keyword argument to the generated lambda handler decorator, for example:
-
- ```python
- from myapi_python_runtime.apis.tags.default_api_operation_config import say_hello_handler, SayHelloRequest, ApiResponse, SayHelloOperationResponses
- from myapi_python_runtime.model.api_error import ApiError
- from myapi_python_runtime.model.hello_response import HelloResponse
-
- @say_hello_handler(interceptors=[timing_interceptor, try_catch_interceptor])
- def handler(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
- return ApiResponse(
- status_code=200,
- body=HelloResponse(message="Hello {}!".format(input.request_parameters["name"])),
- headers={}
- )
- ```
-
- Writing an interceptor is just like writing a lambda handler. Call `chain.next(input)` from an interceptor to delegate to the rest of the chain to handle a request.
-
- ```python
- import time
- from myapi_python_runtime.apis.tags.default_api_operation_config import ChainedApiRequest, ApiResponse
-
- def timing_interceptor(input: ChainedApiRequest) -> ApiResponse:
- start = int(round(time.time() * 1000))
- response = input.chain.next(input)
- end = int(round(time.time() * 1000))
- print("Took {} ms".format(end - start))
- return response
- ```
-
- Interceptors may choose to return different responses, for example to return a 500 response for any unhandled exceptions:
-
- ```python
- import time
- from myapi_python_runtime.model.api_error import ApiError
- from myapi_python_runtime.apis.tags.default_api_operation_config import ChainedApiRequest, ApiResponse
-
- def try_catch_interceptor(input: ChainedApiRequest) -> ApiResponse:
- try:
- return input.chain.next(input)
- except Exception as e:
- return ApiResponse(
- status_code=500,
- body=ApiError(errorMessage=str(e)),
- headers={}
- )
- ```
-
- Interceptors are permitted to mutate the "interceptor context", which is a `Dict[str, Any]`. Each interceptor in the chain, and the final handler, can access this context:
-
- ```python
- def identity_interceptor(input: ChainedApiRequest) -> ApiResponse:
- input.interceptor_context["AuthenticatedUser"] = get_authenticated_user(input.event)
- return input.chain.next(input)
- ```
-
- Interceptors can also mutate the response returned by the handler chain. An example use case might be adding cross-origin resource sharing headers:
-
- ```python
- def add_cors_headers_interceptor(input: ChainedApiRequest) -> ApiResponse:
- response = input.chain.next(input)
- return ApiResponse(
- status_code=response.status_code,
- body=response.body,
- headers={
- **response.headers,
- "Access-Control-Allow-Origin": "*",
- "Access-Control-Allow-Headers": "*"
- }
- )
- ```
-
-## Handler Router
+ Use the `x-handler` vendor extension, specifying the language you wish to implement this operation in.
-The lambda handler wrappers can be used in isolation as handlers for separate lambda functions. If you would like to use a single lambda function to serve all requests, you can wrap your individual handlers with a "handler router", for example:
-
-=== "TS"
-
- ```ts
- import {
- handlerRouter,
- sayHelloHandler,
- sayGoodbyeHandler,
- } from "myapi-typescript-runtime";
- import { corsInterceptor } from "./interceptors";
- import { sayGoodbye } from "./handlers/say-goodbye";
-
- const sayHello = sayHelloHandler(async ({ input }) => {
- return {
- statusCode: 200,
- body: {
- message: `Hello ${input.requestParameters.name}!`,
- },
- };
- });
-
- export const handler = handlerRouter({
- // Interceptors declared in this list will apply to all operations
- interceptors: [corsInterceptor],
- // Assign handlers to each operation here
- handlers: {
- sayHello,
- sayGoodbye,
- },
- });
+ ```yaml hl_lines="4-5"
+ /hello:
+ get:
+ operationId: sayHello
+ x-handler:
+ language: typescript
+ parameters:
+ - in: query
+ name: name
+ schema:
+ type: string
+ required: true
+ responses:
+ 200:
+ description: Successful response
+ content:
+ 'application/json':
+ schema:
+ $ref: '#/components/schemas/SayHelloResponseContent'
```
-=== "JAVA"
+!!!note
- ```java
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayGoodbye;
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.HandlerRouter;
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.Interceptors;
- import com.generated.api.myapijavaruntime.runtime.api.Handlers.SayHello;
-
- import java.util.Arrays;
- import java.util.List;
-
- // Interceptors defined here apply to all operations
- @Interceptors({ TimingInterceptor.class })
- public class ApiHandlerRouter extends HandlerRouter {
- // You must implement a method to return a handler for every operation
- @Override
- public SayHello sayHello() {
- return new SayHelloHandler();
- }
-
- @Override
- public SayGoodbye sayGoodbye() {
- return new SayGoodbyeHandler();
- }
- }
- ```
-
-=== "PYTHON"
-
- ```python
- from myapi_python_runtime.apis.tags.default_api_operation_config import say_hello_handler, SayHelloRequest, ApiResponse, SayHelloOperationResponses, handler_router, HandlerRouterHandlers
- from myapi_python_runtime.model.api_error import ApiError
- from myapi_python_runtime.model.hello_response import HelloResponse
- from other_handlers import say_goodbye
- from my_interceptors import cors_interceptor
-
- @say_hello_handler
- def say_hello(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
- return ApiResponse(
- status_code=200,
- body=HelloResponse(message="Hello {}!".format(input.request_parameters["name"])),
- headers={}
- )
-
- handler = handler_router(
- # Interceptors defined here will apply to all operations
- interceptors=[cors_interceptor],
- handlers=HandlerRouterHandlers(
- say_hello=say_hello,
- say_goodbye=say_goodbye
- )
- )
- ```
-
-When you use a handler router, you must specify the same lambda function for every integration in your `Api` CDK construct. To save typing, you can use the `Operations.all` method from your generated runtime package:
-
-=== "TS"
+ If you wish to deviate from the folder structure of the `handlers` projects, or wish to implement your operations in a language not supported by Type Safe API, or through a non-lambda interation (such as a server running in a Fargate container) you can omit the `@handler` trait or `x-handler` vendor extension.
- ```ts
- import { Integrations, Authorizers } from "@aws/pdk/type-safe-api";
- import { Operations } from "myapi-typescript-runtime";
- import { Api } from "myapi-typescript-infra";
- import { NodejsFunction } from "aws-cdk-lib/aws-lambda";
-
- new Api(this, "Api", {
- defaultAuthorizer: Authorizers.iam(),
- // Use the same integration for every operation.
- integrations: Operations.all({
- integration: Integrations.lambda(new NodejsFunction(this, "router")),
- }),
- });
- ```
-
-=== "JAVA"
-
- ```java
- import software.aws.awsprototypingsdk.typesafeapi.TypeSafeApiIntegration;
- import software.aws.awsprototypingsdk.typesafeapi.Integrations;
- import software.aws.awsprototypingsdk.typesafeapi.Authorizers;
-
- import com.generated.api.myapijavaruntime.runtime.api.Operations;
- import com.generated.api.myapijavainfra.infra.Api;
- import com.generated.api.myapijavainfra.infra.ApiProps;
-
- new Api(s, "Api", ApiProps.builder()
- .defaultAuthorizer(Authorizers.iam())
- .integrations(Operations.all(TypeSafeApiIntegration.builder()
- .integration(Integrations.lambda(...))
- .build()).build())
- .build());
- ```
-
-=== "PYTHON"
-
- ```python
- from aws_prototyping_sdk.type_safe_api import Integrations, TypeSafeApiIntegration, Authorizers
- from myapi_python_runtime.apis.tags.default_api_operation_config import Operations
- from myapi_python_infra.api import Api
-
- Api(self, "Api",
- default_authorizer=Authorizers.iam(),
- # Use the same integration for every operation.
- integrations=Operations.all(
- integration=Integrations.lambda_(NodejsFunction(scope, "router"))
- )
- )
- ```
+You can implement your lambda handlers in any of the supported languages, or mix and match languages for different operations if you prefer.
diff --git a/packages/type-safe-api/docs/developer_guides/type-safe-api/mocking_responses.md b/packages/type-safe-api/docs/developer_guides/type-safe-api/mocking_responses.md
index 0e3ce2c33..4cda17997 100644
--- a/packages/type-safe-api/docs/developer_guides/type-safe-api/mocking_responses.md
+++ b/packages/type-safe-api/docs/developer_guides/type-safe-api/mocking_responses.md
@@ -70,8 +70,8 @@ Use `MockIntegrations.mockAll` to mock all the operations in your API by returni
=== "JAVA"
```java
- import software.aws.awsprototypingsdk.typesafeapi.TypeSafeApiIntegration;
- import software.aws.awsprototypingsdk.typesafeapi.Integrations;
+ import software.aws.awspdk.typesafeapi.TypeSafeApiIntegration;
+ import software.aws.awspdk.typesafeapi.Integrations;
import com.generated.api.myapijavainfra.infra.Api;
import com.generated.api.myapijavainfra.infra.ApiProps;
@@ -90,8 +90,8 @@ Use `MockIntegrations.mockAll` to mock all the operations in your API by returni
=== "PYTHON"
```python
- from aws_prototyping_sdk.type_safe_api import Integrations, TypeSafeApiIntegration
- from myapi_python_runtime.apis.tags.default_api_operation_config import OperationConfig
+ from aws_pdk.type_safe_api import Integrations, TypeSafeApiIntegration
+ from myapi_python_runtime.api.operation_config import OperationConfig
from myapi_python_infra.api import Api
from myapi_python_infra.mock_integrations import MockIntegrations
@@ -142,7 +142,7 @@ For operations which return JSON structures as responses, use the auto-generated
=== "PYTHON"
```python
- from myapi_python_runtime.apis.tags.default_api_operation_config import OperationConfig
+ from myapi_python_runtime.api.operation_config import OperationConfig
from myapi_python_infra.api import Api
from myapi_python_infra.mock_integrations import MockIntegrations
@@ -200,7 +200,7 @@ To customize the mock responses, pass the response body to the method for the op
=== "PYTHON"
```python
- from myapi_python_runtime.apis.tags.default_api_operation_config import OperationConfig
+ from myapi_python_runtime.api.operation_config import OperationConfig
from myapi_python_infra.api import Api
from myapi_python_infra.mock_integrations import MockIntegrations
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 821933f23..d5b541c78 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
@@ -8,9 +8,9 @@ For CDK infrastructure, pick the language you'd like to write CDK infrastructure
You can also implement your API lambda handlers in a different language to your infrastructure and `.projenrc` if you like, or even implement some operations in one language and others in another. Just make sure you generate the runtime projects for any languages you'd like to write lambda handlers in.
-### How do I customise the Generated Runtime/Infrastructure/Library Projects?
+### How do I customise the Generated Runtime/Infrastructure/Library/Handlers Projects?
-By default, the generated runtime, infrastructure and library projects are configured automatically, but you can customise them in your `.projenrc` by using `runtime.options.`, `infrastructure.options.`, or `library.options.` in your `TypeSafeApiProject`.
+By default, the generated runtime, infrastructure and library projects are configured automatically, but you can customise them in your `.projenrc` by using `runtime.options.`, `infrastructure.options.`, `handlers.options.`, or `library.options.` in your `TypeSafeApiProject`.
### How do I modify the AWS WAFv2 Web ACL my Api construct deploys?
@@ -21,61 +21,53 @@ You can customise the Web ACL configuration via the `webAclOptions` of your `Api
=== "TS"
```ts
- export class SampleApi extends Api {
- constructor(scope: Construct, id: string) {
- super(scope, id, {
- integrations: { ... },
- webAclOptions: {
- // Allow access only to specific CIDR ranges
- cidrAllowList: {
- cidrType: 'IPV4',
- cidrRanges: ['1.2.3.4/5'],
- },
- // Pick from the set here: https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html
- managedRules: [
- { vendor: 'AWS', name: 'AWSManagedRulesSQLiRuleSet' },
- ],
- },
- });
- }
- }
+ new Api(this, "Api", {
+ webAclOptions: {
+ // Allow access only to specific CIDR ranges
+ cidrAllowList: {
+ cidrType: 'IPV4',
+ cidrRanges: ['1.2.3.4/5'],
+ },
+ // Pick from the set here: https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html
+ managedRules: [
+ { vendor: 'AWS', name: 'AWSManagedRulesSQLiRuleSet' },
+ ],
+ },
+ ...
+ });
```
=== "JAVA"
```java
- public class SampleApi extends Api {
- public SampleApi(Construct scope, String id) {
- super(scope, id, Map.of(
- "integrations", Map.of(...),
- "webAclOptions", Map.of(
- // Allow access only to specific CIDR ranges
- "cidrAllowList", Map.of(
- "cidrType", "IPV4",
- "cidrRanges", List.of("1.2.3.4/5")),
- // Pick from the set here: https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html
- "managedRules", List.of(Map.of("vendor", "AWS", "name", "AWSManagedRulesSQLiRuleSet")))));
- }
- }
+ new Api(this, "Api", ApiProps.builder()
+ .webAclOptions(TypeSafeApiWebAclOptions.builder()
+ .cidrAllowList(CidrAllowList.builder()
+ .cidrType("IPV4")
+ .cidrRanges(Arrays.asList("1.2.3.4/5"))
+ .build())
+ .managedRules(Arrays.asList(ManagedRule.builder()
+ .vendor("AWS")
+ .name("AWSManagedRulesSQLiRuleSet")
+ .build()))
+ .build())
+ ...
+ .build();
```
=== "PYTHON"
```python
- class SampleApi(Api):
- def __init__(self, scope, id):
- super().__init__(scope, id,
- integrations={...},
- web_acl_options={
- # Allow access only to specific CIDR ranges
- "cidr_allow_list": {
- "cidr_type": "IPV4",
- "cidr_ranges": ["1.2.3.4/5"]
- },
- # Pick from the set here: https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html
- "managed_rules": [{"vendor": "AWS", "name": "AWSManagedRulesSQLiRuleSet"}]
- }
- )
+ Api(self, id,
+ web_acl_options=TypeSafeApiWebAclOptions(
+ cidr_allow_list=CidrAllowList(
+ cidr_type="IPV4",
+ cidr_ranges=["1.2.3.4/5"]
+ ),
+ managed_rules=[ManagedRule(vendor="AWS", name="AWSManagedRulesSQLiRuleSet")]
+ ),
+ ...
+ )
```
### How do I configure the Smithy IntelliJ Plugin?
@@ -161,14 +153,17 @@ You can consume the library using the `addSmithyDeps` method, which adds a local
.parent(monorepo)
.outdir("packages/shapes")
.modelLanguage(ModelLanguage.getSMITHY())
- .modelOptions(Map.of(
- "smithy", Map.of(
- "serviceName", Map.of(
- "namespace", "com.my.shared.shapes",
- "serviceName", "Ignored"))))
+ .modelOptions(ModelOptions.builder()
+ .smithy(SmithyModelOptions.builder()
+ .serviceName(SmithyServiceName.builder()
+ .namespace("com.my.shared.shapes")
+ .serviceName("Ignored")
+ .build())
+ .build())
+ .build())
.build();
- TypeSafeApiProject api = TypeSafeApiProject.Builder.create()....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);
@@ -186,14 +181,14 @@ You can consume the library using the `addSmithyDeps` method, which adds a local
parent=monorepo,
outdir="packages/shapes",
model_language=ModelLanguage.SMITHY,
- model_options={
- "smithy": {
- "service_name": {
- "namespace": "com.my.shared.shapes",
- "service_name": "Ignored"
- }
- }
- }
+ model_options=ModelOptions(
+ smithy=SmithyModelOptions(
+ service_name=SmithyServiceName(
+ namespace="com.my.shared.shapes",
+ service_name="Ignored"
+ )
+ )
+ )
)
api = TypeSafeApiProject(...)
@@ -251,38 +246,13 @@ For example, to customise the maven repository used to pull the OpenAPI Generato
const project = new TypeSafeApiProject({
infrastructure: {
language: Language.TYPESCRIPT,
- options: {
- // Note here the options are typed as "GeneratedTypeScriptProjectOptions" but you can
- // pass a partial one to avoid having to specify a custom project name etc
- typescript: {
- openApiGeneratorCliConfig,
- } satisfies Partial as any,
- },
- },
- runtime: {
- languages: [Language.TYPESCRIPT],
options: {
typescript: {
openApiGeneratorCliConfig,
- } satisfies Partial as any,
- },
- },
- library: {
- libraries: [Library.TYPESCRIPT_REACT_QUERY_HOOKS],
- options: {
- typescriptReactQueryHooks: {
- openApiGeneratorCliConfig,
- } satisfies Partial as any,
- },
- },
- documentation: {
- formats: [DocumentationFormat.HTML2],
- options: {
- html2: {
- openApiGeneratorCliConfig,
},
},
},
+ // Repeat for handlers, runtime, documentation, library
...
});
```
@@ -290,31 +260,22 @@ For example, to customise the maven repository used to pull the OpenAPI Generato
=== "JAVA"
```java
- OpenApiGeneratorCliConfig openApiGeneratorCliConfig = Map.of(
- "repository", Map.of(
- "downloadUrl", "https://my.custom.maven.repo/maven2/${groupId}/${artifactId}/${versionName}/${artifactId}-${versionName}.jar"));
-
- TypeSafeApiProject project = TypeSafeApiProject.Builder.create()
- .infrastructure(Map.of(
- "language", Language.getTYPESCRIPT(),
- "options", Map.of(
- "typescript", Map.of(
- "openApiGeneratorCliConfig", openApiGeneratorCliConfig))))
- .runtime(Map.of(
- "languages", List.of(Language.getTYPESCRIPT()),
- "options", Map.of(
- "typescript", Map.of(
- "openApiGeneratorCliConfig", openApiGeneratorCliConfig))))
- .library(Map.of(
- "libraries", List.of(Library.getTYPESCRIPT_REACT_QUERY_HOOKS()),
- "options", Map.of(
- "typescriptReactQueryHooks", Map.of(
- "openApiGeneratorCliConfig", openApiGeneratorCliConfig))))
- .documentation(Map.of(
- "formats", List.of(DocumentationFormat.getHTML2()),
- "options", Map.of(
- "html2", Map.of(
- "openApiGeneratorCliConfig", openApiGeneratorCliConfig))))
+ OpenApiGeneratorCliConfig openApiGeneratorCliConfig = OpenApiGeneratorCliConfig.builder()
+ .repository(OpenApiGeneratorCliConfigRepository.builder()
+ .downloadUrl("https://my.custom.maven.repo/maven2/${groupId}/${artifactId}/${versionName}/${artifactId}-${versionName}.jar")
+ .build())
+ .build();
+
+ TypeSafeApiProject project = new TypeSafeApiProject(TypeSafeApiProjectOptions.builder()
+ .infrastructure(InfrastructureConfiguration.builder()
+ .language(Language.JAVA)
+ .options(GeneratedInfrastructureCodeOptions.builder()
+ .java(GeneratedJavaInfrastructureOptions.builder()
+ .openApiGeneratorCliConfig(openApiGeneratorCliConfig)
+ .build())
+ .build())
+ .build())
+ // Repeat for handlers, runtime, documentation, library
...
.build();
```
@@ -322,46 +283,22 @@ For example, to customise the maven repository used to pull the OpenAPI Generato
=== "PYTHON"
```python
- # Example automatically generated from non-compiling source. May contain errors.
- open_api_generator_cli_config = {
- "repository": {
- "download_url": "https://my.custom.maven.repo/maven2/${groupId}/${artifactId}/${versionName}/${artifactId}-${versionName}.jar"
- }
- }
+ openapi_generator_cli_config = OpenApiGeneratorCliConfig(
+ repository=OpenApiGeneratorCliConfigRepository(
+ download_url="https://my.custom.maven.repo/maven2/${groupId}/${artifactId}/${versionName}/${artifactId}-${versionName}.jar"
+ )
+ )
project = TypeSafeApiProject(
- infrastructure={
- "language": Language.TYPESCRIPT,
- "options": {
- "typescript": {
- "open_api_generator_cli_config": open_api_generator_cli_config
- }
- }
- },
- runtime={
- "languages": [Language.TYPESCRIPT],
- "options": {
- "typescript": {
- "open_api_generator_cli_config": open_api_generator_cli_config
- }
- }
- },
- library={
- "libraries": [Library.TYPESCRIPT_REACT_QUERY_HOOKS],
- "options": {
- "typescript_react_query_hooks": {
- "open_api_generator_cli_config": open_api_generator_cli_config
- }
- }
- },
- documentation={
- "formats": [DocumentationFormat.HTML2],
- "options": {
- "html2": {
- "open_api_generator_cli_config": open_api_generator_cli_config
- }
- }
- },
+ infrastructure=InfrastructureConfiguration(
+ language=Language.PYTHON,
+ options=GeneratedInfrastructureCodeOptions(
+ python=GeneratedPythonInfrastructureOptions(
+ open_api_generator_cli_config=open_api_generator_cli_config
+ )
+ )
+ ),
+ # Repeat for handlers, runtime, documentation, library
...
)
```
@@ -407,22 +344,28 @@ You can customise the versions of Smithy dependencies by including them in `smit
=== "JAVA"
```java
- TypeSafeApiProject.Builder.create()
- .model(Map.of(
- "language", ModelLanguage.getSMITHY(),
- "options", Map.of(
- "smithy", Map.of(
- "serviceName", Map.of(
- "namespace", "com.mycompany",
- "serviceName", "MyApi"),
- "smithyBuildOptions", Map.of(
- "maven", Map.of(
- // Override the built in smithy dependencies here
- "dependencies", List.of(
- "software.amazon.smithy:smithy-cli:1.28.1",
- "software.amazon.smithy:smithy-model:1.28.1",
- "software.amazon.smithy:smithy-openapi:1.28.1",
- "software.amazon.smithy:smithy-aws-traits:1.28.1")))))))
+ new TypeSafeApiProject(TypeSafeApiProjectOptions.builder()
+ .model(ModelConfiguration.builder()
+ .language(ModelLanguage.SMITHY)
+ .options(ModelOptions.builder()
+ .smithy(SmithyModelOptions.builder()
+ .serviceName(SmithyServiceName.builder()
+ .namespace("com.my.company")
+ .serviceName("MyApi")
+ .build())
+ .smithyBuildOptions(SmithyBuildOptions.builder()
+ .maven(SmithyMavenConfiguration.builder()
+ .dependencies(Arrays.asList(
+ "software.amazon.smithy:smithy-cli:1.28.1",
+ "software.amazon.smithy:smithy-model:1.28.1",
+ "software.amazon.smithy:smithy-openapi:1.28.1",
+ "software.amazon.smithy:smithy-aws-traits:1.28.1"
+ ))
+ .build())
+ .build())
+ .build())
+ .build())
+ .build())
...
.build();
```
@@ -431,28 +374,28 @@ You can customise the versions of Smithy dependencies by including them in `smit
```python
TypeSafeApiProject(
- model={
- "language": ModelLanguage.SMITHY,
- "options": {
- "smithy": {
- "service_name": {
- "namespace": "com.mycompany",
- "service_name": "MyApi"
- },
- "smithy_build_options": {
- "maven": {
- "dependencies": [
+ model=ModelConfiguration(
+ language=ModelLanguage.SMITHY,
+ options=ModelOptions(
+ smithy=SmithyModelOptions(
+ service_name=SmithyServiceName(
+ namespace="com.amazon",
+ service_name="MyAPI"
+ ),
+ smithy_build_options=SmithyBuildOptions(
+ maven=SmithyMavenConfiguration(
+ dependencies=[
# Override the built in smithy dependencies here
"software.amazon.smithy:smithy-cli:1.28.1",
"software.amazon.smithy:smithy-model:1.28.1",
"software.amazon.smithy:smithy-openapi:1.28.1",
"software.amazon.smithy:smithy-aws-traits:1.28.1",
]
- }
- }
- }
- }
- },
+ )
+ )
+ )
+ )
+ ),
...
)
```
diff --git a/packages/type-safe-api/docs/developer_guides/type-safe-api/typescript_react_query_hooks.md b/packages/type-safe-api/docs/developer_guides/type-safe-api/typescript_react_query_hooks.md
index 7e7d60236..45c1d22be 100644
--- a/packages/type-safe-api/docs/developer_guides/type-safe-api/typescript_react_query_hooks.md
+++ b/packages/type-safe-api/docs/developer_guides/type-safe-api/typescript_react_query_hooks.md
@@ -28,16 +28,16 @@ You can generate [react-query](https://tanstack.com/query/latest) hooks for inte
```python
TypeSafeApiProject(
- library={
- "libraries": [Library.TYPESCRIPT_REACT_QUERY_HOOKS]
- },
+ library=LibraryConfiguration(
+ libraries=[Library.TYPESCRIPT_REACT_QUERY_HOOKS]
+ )
...
)
```
## Usage in a React Website
-First, make sure you add a dependency on the generated hooks library, eg in your `.projenrc`:
+First, make sure you add a dependency on the generated hooks library. This is done automatically if you pass your `TypeSafeApiProject` into the `CloudscapeReactTsWebsite`, eg in your `.projenrc`:
=== "TS"
@@ -46,24 +46,20 @@ First, make sure you add a dependency on the generated hooks library, eg in your
new CloudscapeReactTsWebsite({
...,
- deps: [
- ...
- api.library.typescriptReactQueryHooks!.package.packageName,
- ],
+ typeSafeApi: api,
});
```
=== "JAVA"
```java
- TypeSafeApiProject api = TypeSafeApiProject.Builder.create()
+ TypeSafeApiProject api = new TypeSafeApiProject(TypeSafeApiProjectOptions.builder()
...
.build();
CloudscapeReactTsWebsite.Builder.create()
...
- .deps(List.of(
- api.getLibrary().getTypescriptReactQueryHooks().getPackage().getPackageName()))
+ .typeSafeApi(api)
.build();
```
@@ -74,11 +70,15 @@ First, make sure you add a dependency on the generated hooks library, eg in your
CloudscapeReactTsWebsite(
...
- deps=[api.library.typescript_react_query_hooks.package.package_name]
+ type_safe_api=api
)
```
-Make sure to run `projen` (eg. `yarn projen`) to synthesize your `.projenrc` changes!
+!!!note
+
+ If you are not using `CloudscapeReactTsWebsite`, you can add the dependency manually using `api.library.typescriptReactQueryHooks!.package.packageName`
+
+Make sure to run `pdk` to synthesize your `.projenrc` changes.
Next, create an instance of the API client in your React Website (making sure to set the base URL and fetch instance). For example:
diff --git a/packages/type-safe-api/docs/developer_guides/type-safe-api/using_openapi.md b/packages/type-safe-api/docs/developer_guides/type-safe-api/using_openapi.md
index d9d331667..3a450c540 100644
--- a/packages/type-safe-api/docs/developer_guides/type-safe-api/using_openapi.md
+++ b/packages/type-safe-api/docs/developer_guides/type-safe-api/using_openapi.md
@@ -40,14 +40,14 @@ To use OpenAPI, in `TypeSafeApiProject`, specify it as the `model.language`.
```python
TypeSafeApiProject(
- model={
- "language": ModelLanguage.OPENAPI,
- "options": {
- "openapi": {
- "title": "MyApi"
- }
- }
- },
+ model=ModelConfiguration(
+ language=ModelLanguage.OPENAPI,
+ options=ModelOptions(
+ openapi=OpenApiModelOptions(
+ title="MyApi"
+ )
+ )
+ ),
...
)
```
@@ -65,6 +65,8 @@ paths:
/hello:
get:
operationId: sayHello
+ x-handler:
+ language: typescript
parameters:
- in: query
name: name
@@ -132,6 +134,8 @@ components:
```yaml
operationId: sayHello
+x-handler:
+ language: typescript
parameters:
- in: query
name: name
@@ -164,7 +168,3 @@ properties:
required:
- message
```
-
-!!! warning
-
- Make sure you put all request/response body schemas in the `components` section and use `$ref` to reference them, as the Python generator may not produce expected results with inline request/response body schemas.
diff --git a/packages/type-safe-api/docs/developer_guides/type-safe-api/using_smithy.md b/packages/type-safe-api/docs/developer_guides/type-safe-api/using_smithy.md
index 267ab9282..06c08862d 100644
--- a/packages/type-safe-api/docs/developer_guides/type-safe-api/using_smithy.md
+++ b/packages/type-safe-api/docs/developer_guides/type-safe-api/using_smithy.md
@@ -44,17 +44,17 @@ You can use [Smithy](https://smithy.io/2.0), an interface definition language (I
```python
TypeSafeApiProject(
- model={
- "language": ModelLanguage.SMITHY,
- "options": {
- "smithy": {
- "service_name": {
- "namespace": "com.mycompany",
- "service_name": "MyApi"
- }
- }
- }
- },
+ model=ModelConfiguration(
+ language=ModelLanguage.SMITHY,
+ options=ModelOptions(
+ smithy=SmithyModelOptions(
+ service_name=SmithyServiceName(
+ namespace="com.amazon",
+ service_name="MyApi"
+ )
+ )
+ )
+ ),
...
)
```
@@ -79,6 +79,7 @@ service Hello {
}
@readonly
+@handler(language: "typescript")
@http(method: "GET", uri: "/hello")
operation SayHello {
input := {
@@ -152,7 +153,7 @@ Smithy supports [adding API Gateway authorizers in the model itself](https://smi
If you use Smithy generated clients, some authorizer traits such as sigv4 will include configuring the client for that particular method of authorization, so it is beneficial to define authorizers in the model.
- PDK supports specifying authorizers in both the model and the construct, but the construct will take precedence where the authorizer ID is the same.
+ Type Safe API supports specifying authorizers in both the model and the construct, but the construct will take precedence where the authorizer ID is the same.
## Customizing the Smithy build
@@ -179,7 +180,7 @@ When you synthesize the `TypeSafeApiProject`, it will contain a `model/smithy-bu
openapi: {
// Customise the openapi projection here.
// See: https://smithy.io/2.0/guides/converting-to-openapi.html
- useIntegerType: true,
+ defaultTimestampFormat: "epoch-seconds",
...
}
}
@@ -212,7 +213,7 @@ When you synthesize the `TypeSafeApiProject`, it will contain a `model/smithy-bu
=== "JAVA"
```java
- TypeSafeApiProject.Builder.create()
+ new TypeSafeApiProject(TypeSafeApiProjectOptions.builder()
.name("myapi")
.model(ModelConfiguration.builder()
.language(ModelLanguage.SMITHY)
@@ -229,7 +230,7 @@ When you synthesize the `TypeSafeApiProject`, it will contain a `model/smithy-bu
"openapi", SmithyProjection.builder()
.plugins(Map.of(
"openapi", Map.of(
- "useIntegerType", true
+ "defaultTimestampFormat", "epoch-seconds"
)
))
.build(),
@@ -260,46 +261,46 @@ When you synthesize the `TypeSafeApiProject`, it will contain a `model/smithy-bu
```python
TypeSafeApiProject(
- model={
- "language": ModelLanguage.SMITHY,
- "options": {
- "smithy": {
- "service_name": {
- "namespace": "com.mycompany",
- "service_name": "MyApi"
- },
+ ModelConfiguration(
+ language=ModelLanguage.SMITHY,
+ options=ModelOptions(
+ smithy=SmithyModelOptions(
+ service_name=SmithyServiceName(
+ namespace="com.amazon",
+ service_name="MyApi"
+ ),
# Use smithyBuildOptions to control what is added to smithy-build.json.
- "smithy_build_options": {
- "projections": {
+ smithy_build_options=SmithyBuildOptions(
+ projections={
# You can customise the built-in openapi projection, used to generate the OpenAPI specification.
- "openapi": {
- "plugins": {
+ "openapi": SmithyProjection(
+ plugins={
"openapi": {
# Customise the openapi projection here.
# See: https://smithy.io/2.0/guides/converting-to-openapi.html
- "use_integer_type": True, ...
+ "defaultTimestampFormat": "epoch-seconds"
}
}
- },
+ ),
# You can add new projections here too
- "ts-client": {
- "plugins": {
+ "ts-client": SmithyProjection(
+ plugins={
"typescript-codegen": {
"package": "@my-test/smithy-generated-typescript-client",
"package_version": "0.0.1"
}
}
- }
+ )
},
# Note that any additional dependencies required for projections/plugins can be added here, which in turn will
# add them to the `smithy/build.gradle` file
- "maven": {
- "dependencies": ["software.amazon.smithy:smithy-validation-model:1.27.2"]
- }
- }
- }
- }
- },
+ maven=SmithyMavenConfiguration(
+ dependencies=["software.amazon.smithy:smithy-validation-model:1.27.2"]
+ )
+ )
+ )
+ )
+ ),
...
)
```
diff --git a/packages/type-safe-api/scripts/generators/java-cdk-infrastructure/templates/apiProps.mustache b/packages/type-safe-api/scripts/generators/java-cdk-infrastructure/templates/apiProps.mustache
index 5e5521abc..4379ecf9b 100644
--- a/packages/type-safe-api/scripts/generators/java-cdk-infrastructure/templates/apiProps.mustache
+++ b/packages/type-safe-api/scripts/generators/java-cdk-infrastructure/templates/apiProps.mustache
@@ -9,6 +9,7 @@ import software.amazon.awscdk.services.iam.PolicyDocument;
import software.aws.pdk.type_safe_api.ApiKeyOptions;
import software.aws.pdk.type_safe_api.Authorizer;
import software.aws.pdk.type_safe_api.TypeSafeApiIntegration;
+import software.aws.pdk.type_safe_api.TypeSafeApiWebAclOptions;
import {{#apiInfo}}{{#apis.0}}{{vendorExtensions.x-runtime-package}}{{/apis.0}}{{/apiInfo}}.api.operation_config.OperationConfig;
@@ -24,6 +25,7 @@ public class ApiProps implements RestApiBaseProps {
public Authorizer defaultAuthorizer;
public CorsOptions corsOptions;
public ApiKeyOptions apiKeyOptions;
+ public TypeSafeApiWebAclOptions webAclOptions;
// Rest API Props
public Boolean cloudWatchRole;
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 4409f2533..782a652af 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
@@ -141,7 +141,7 @@ export interface TypeSafeApiProjectOptions extends ProjectOptions {
/**
* Configuration for generated runtime projects (containing types, clients and server code)
*/
- readonly runtime: RuntimeConfiguration;
+ readonly runtime?: RuntimeConfiguration;
/**
* Configuration for generated infrastructure
*/
@@ -227,7 +227,7 @@ export class TypeSafeApiProject extends Project {
// the user. Likewise we generate a runtime project for any handler languages specified
const runtimeLanguages = [
...new Set([
- ...options.runtime.languages,
+ ...(options.runtime?.languages ?? []),
options.infrastructure.language,
...(options.handlers?.languages ?? []),
]),
@@ -265,17 +265,17 @@ export class TypeSafeApiProject extends Project {
ProjectUtils.isNamedInstanceOf(this.parent, NodeProject)
? this.parent.package.packageManager
: NodePackageManager.YARN,
- ...options.runtime.options?.typescript,
+ ...options.runtime?.options?.typescript,
},
pythonOptions: {
authorName: "APJ Cope",
authorEmail: "apj-cope@amazon.com",
version: "0.0.0",
- ...options.runtime.options?.python,
+ ...options.runtime?.options?.python,
},
javaOptions: {
version: "0.0.0",
- ...options.runtime.options?.java,
+ ...options.runtime?.options?.java,
},
});
@@ -327,7 +327,7 @@ export class TypeSafeApiProject extends Project {
ProjectUtils.isNamedInstanceOf(this.parent, NodeProject)
? this.parent.package.packageManager
: NodePackageManager.YARN,
- ...options.runtime.options?.typescript,
+ ...options.library?.options?.typescriptReactQueryHooks,
},
});
diff --git a/packages/type-safe-api/test/scripts/generators/__snapshots__/java-cdk-infrastructure.test.ts.snap b/packages/type-safe-api/test/scripts/generators/__snapshots__/java-cdk-infrastructure.test.ts.snap
index 04ed0b487..774f63ea5 100644
--- a/packages/type-safe-api/test/scripts/generators/__snapshots__/java-cdk-infrastructure.test.ts.snap
+++ b/packages/type-safe-api/test/scripts/generators/__snapshots__/java-cdk-infrastructure.test.ts.snap
@@ -962,6 +962,7 @@ import software.amazon.awscdk.services.iam.PolicyDocument;
import software.aws.pdk.type_safe_api.ApiKeyOptions;
import software.aws.pdk.type_safe_api.Authorizer;
import software.aws.pdk.type_safe_api.TypeSafeApiIntegration;
+import software.aws.pdk.type_safe_api.TypeSafeApiWebAclOptions;
import test.test-client.runtime.api.operation_config.OperationConfig;
@@ -977,6 +978,7 @@ public class ApiProps implements RestApiBaseProps {
public Authorizer defaultAuthorizer;
public CorsOptions corsOptions;
public ApiKeyOptions apiKeyOptions;
+ public TypeSafeApiWebAclOptions webAclOptions;
// Rest API Props
public Boolean cloudWatchRole;