Package: aws-prototyping-sdk.open-api-gateway
DEPRECATED!Please use thetype-safe-apipackage instead.open-api-gatewaywill be removed in the GA 1.0 release.type-safe-apioffers several benefits overopen-api-gateway:Code generation at build time:You no longer need to runnpx projenwhen you make changes to your API definition.You no longer need to pollute your version control with generated codeMore intuitive directory structureGenerated python projects usepoetry, allowing for better local dependencies (eg no need to deploy a lambda layer for handlers to depend on the generated handler wrappers)TypeSafeApi Migration GuideMigration will vary a little depending on the language you are using, however the following rough steps can be used to migrate a project to usetype-safe-api:Create aTypeSafeApiProjectin your.projenrc.ts, with a differentoutdirto your existingOpenApiGatewayXXXProjectorSmithyApiGatewayXXXProject(which we assume is calledapi, ieconst api = new SmithyApiGateway...). Make sure you select the appropriatemodelLanguagebased on whether your existing model is written in OpenApi or Smithy. We'll call itnewApi(egconst newApi = new TypeSafeApiProject({...)Duplicate any client dependencies in your.projenrc.tsso that packages depend on the equivalent package. (eg. if a package depends onapi.generatedTypescriptClient, make sure it depends onnewApi.typescriptClient).Duplicate any infrastructure dependencies in your.projenrc.ts- the infrastructure is now a separate subproject rather than the top level API project. (eg if a package depended onapibefore, it should now depend onnewApi.typescriptInfrastructure)Synthesize withnpx projenCopy your model into the new directory:For Smithy, you can copy everything in<oldoutdir>/smithy/src/main/smithydirectory into<newoutdir>/model/src/main/smithyFor OpenAPI, copy your<oldoutdir>/src/spec.yamlto<newoutdir>/model/src/main/openapi, and rename it tomain.yaml. Additionally, copy any other referenced OpenAPI files from<oldoutdir>/src/into<newoutdir>/model/src/main/openapiif present.Build your project using your build command (eg.yarn buildin root of monorepo if using monorepo with yarn).Update any imports for theApiconstruct to import from the new generated infrastructure project, eg.import { Api } from 'mynewapi-typescript-infra';Update any imports for the generated clients to use the new generated client packagesMake sure any custom code / files / scripts added to theOpenApiGatewayXXXProjectorSmithyApiGatewayXXXProjectare moved to their appropriate new home, most likely your infrastructure project (egAwsCdkTypeScriptApp).Remove theOpenApiGatewayXXXProjectorSmithyApiGatewayXXXProjectfrom.projenrc.tsand runnpx projenagainDelete the<oldoutdir>OpenAPI GatewayDefine your APIs usingSmithyorOpenAPI v3, and leverage the power of generated clients and documentation, automatic input validation, and type safe client and server code!This package vends a projen project type which allows you to define an API using eitherSmithyorOpenAPI v3, and a construct which manages deploying this API in API Gateway, given an integration (eg a lambda) for every operation.The project will generate models and clients from your api definition in your desired languages, and can be utilised both client side or server side in lambda handlers. The project type also generates a wrapper construct which adds type safety to ensure an integration is provided for every API operation.When you change your API specification, just runnpx projenagain to regenerate all of this!ProjectUse the provided projen project types to get started with your API quickly! There are options for TypeScript, Python and Java:SmithySmithyApiGatewayTsProjectSmithyApiGatewayPythonProjectSmithyApiGatewayJavaProjectOpenAPIOpenApiGatewayTsProjectOpenApiGatewayPythonProjectOpenApiGatewayJavaProjectWe recommend using the Smithy-based projects, given that Smithy has a less verbose and more powerful IDL (Interface Definition Language).Choose the project type based on the language you'd like towrite your CDK infrastructure in. Whichever option above you choose, you can still write your server-side code in any language.It's recommended that these projects are used as part of annx-monorepoproject, as it makes setting up dependencies much easier, and you will not need to manage the build order of generated clients.TypescriptOpenAPIWhile it is recommended to use the project within annx-monorepo, you can still use this as a standalone project if you like (egnpx projen new --from @aws-prototyping-sdk/open-api-gateway open-api-gateway-ts), however you will need to manage build order (ie building the generated client first, followed by the project).For usage in a monorepo:Create the project in your .projenrc:import{ClientLanguage,DocumentationFormat,OpenApiGatewayTsProject}from"@aws-prototyping-sdk/open-api-gateway";newOpenApiGatewayTsProject({parent:myNxMonorepo,defaultReleaseBranch:"mainline",name:"my-api",outdir:"packages/api",clientLanguages:[ClientLanguage.TYPESCRIPT,ClientLanguage.PYTHON,ClientLanguage.JAVA],documentationFormats:[DocumentationFormat.HTML2,DocumentationFormat.PLANTUML,DocumentationFormat.MARKDOWN],});In the output directory (outdir), you'll find a few files to get you started.|_ src/
    |_ spec/
        |_ spec.yaml - The OpenAPI specification - edit this to define your API
        |_ .parsed-spec.json - A json spec generated from your spec.yaml.
    |_ api/
        |_ api.ts - A CDK construct which defines the API Gateway resources to deploy your API.
        |           This wraps the OpenApiGatewayRestApi construct and provides typed interfaces for integrations specific
        |           to your API. You shouldn't need to modify this, instead just extend it as in sample-api.ts.
        |_ sample-api.ts - Example usage of the construct defined in api.ts.
        |_ sample-api.say-hello.ts - An example lambda handler for the operation defined in spec.yaml, making use of the
                                     generated lambda handler wrappers for marshalling and type safety.
|_ generated/
    |_ typescript/ - A generated typescript API client.
    |_ python/ - A generated python API client.
    |_ java/ - A generated java API client.
    |_ documentation/
        |_ html2/ - Generated html documentation
        |_ markdown/ - Generated markdown documentation
        |_ plantuml/ - Generated plant uml documentationIf you would prefer to not generate the sample code, you can passsampleCode: falsetoOpenApiGatewayTsProject.To make changes to your api, simply updatespec.yamland runnpx projento synthesize all the typesafe client/server code!TheSampleApiconstruct usesNodejsFunctionto declare the example lambda, but you are free to change this!SmithyTo create a project with Smithy as your interface definition language (IDL), you can use theSmithyApiGatewayTsProject:import{ClientLanguage,DocumentationFormat,SmithyApiGatewayTsProject}from"@aws-prototyping-sdk/open-api-gateway";newSmithyApiGatewayTsProject({parent:myNxMonorepo,defaultReleaseBranch:"mainline",name:"my-api",outdir:"packages/api",serviceName:{namespace:"com.mycompany",serviceName:"MyService",},clientLanguages:[ClientLanguage.TYPESCRIPT,ClientLanguage.PYTHON,ClientLanguage.JAVA],documentationFormats:[DocumentationFormat.HTML2,DocumentationFormat.PLANTUML,DocumentationFormat.MARKDOWN],});This will result in a directory structure similar to the following:|_ smithy/
    |_ src/
        |_ main/
            |_ smithy - The Smithy model used to define your API. You can define as many .smithy files in here as you like.
    |_ build.gradle - Smithy build gradle file - use this to add dependencies/plugins used in your smithy build
    |_ settings.gradle - Setup for the Smithy gradle project
    |_ smithy-build.json - Smithy build configuration - managed via options on the projen project
|_ src/
    |_ spec/
        |_ .parsed-spec.json - A json spec generated from your Smithy model.
    |_ api/
        |_ api.ts - A CDK construct which defines the API Gateway resources to deploy your API.
        |           This wraps the OpenApiGatewayRestApi construct and provides typed interfaces for integrations specific
        |           to your API. You shouldn't need to modify this, instead just extend it as in sample-api.ts.
        |_ sample-api.ts - Example usage of the construct defined in api.ts.
        |_ sample-api.say-hello.ts - An example lambda handler for the operation defined in spec.yaml, making use of the
                                     generated lambda handler wrappers for marshalling and type safety.
|_ generated/
    |_ typescript/ - A generated typescript API client.
    |_ python/ - A generated python API client.
    |_ java/ - A generated java API client.
    |_ documentation/
        |_ html2/ - Generated html documentation
        |_ markdown/ - Generated markdown documentation
        |_ plantuml/ - Generated plant uml documentationCustomisationYou can customise the Smithy project with several properties:newSmithyApiGatewayTsProject({parent:myNxMonorepo,defaultReleaseBranch:"mainline",name:"my-api",outdir:"packages/api",clientLanguages:[],//ThefullyqualifiedservicenameforyourAPIserviceName:{namespace:"com.mycompany",serviceName:"MyService",},//Bydefault,thecontentsof`smithy-build/output`willbeignoredbysourcecontrol.//Setthistofalsetoincludeit,forexampleifyouaregeneratingclientsdirectlyfromthesmithymodel.ignoreSmithyBuildOutput:false,//ThegradlewrapperusedforthesmithybuildiscopiedfromthePDKitselfifitdoesnotalreadyexistin//the'smithy'folder.Bydefault,thisgradlewrapperwillbeignoredbysourcecontrol.//Setthistofalseifyouwouldliketocheckthegradlewrapperintosourcecontrol,forexampleifyouwant//touseadifferentversionofthegradlewrapperinyourproject.ignoreGradleWrapper:false,//UsesmithyBuildOptionstocontrolwhatisaddedtosmithy-build.json.Seemoredetailsbelow.smithyBuildOptions:{projections:{"ts-client":{"plugins":{"typescript-codegen":{"package":"@my-test/smithy-generated-typescript-client","packageVersion":"0.0.1"}}}}}});smithyBuildOptionsallows you to customise the Smithy build, used for generating clients from the Smithy model (eg above), or customising the OpenAPI generation.OpenAPI generation can be customised by referencing theopenapiprojection as follows:smithyBuildOptions:{projections:{openapi:{plugins:{openapi:{//Customisetheopenapiprojectionhere...forbidGreedyLabels:true,...}}}}}The OpenAPI specification generated by this projection is used to create the CDK infrastructure, lambda handler wrappers, etc. Options for configuring OpenAPI generation can be foundin the Smithy OpenAPI documentation.Note that any additional dependencies required for projections/plugins can be added by modifyingsmithyBuildOptions, which in turn will add them to thesmithy/build.gradlefile:smithyBuildOptions:{maven:{dependencies:["software.amazon.smithy:smithy-validation-model:1.27.2",]}}Noticesmithy-cli,smithy-model,smithy-openapiandsmithy-aws-traitsare always included in thebuild.gradlesince they are required for converting your Smithy model to OpenAPI. You can customise the version of these dependencies by specifying them insmithyBuildOptions:smithyBuildOptions:{maven:{dependencies:["software.amazon.smithy:smithy-cli:1.27.0","software.amazon.smithy:smithy-model:1.27.0","software.amazon.smithy:smithy-openapi:1.27.0","software.amazon.smithy:smithy-aws-traits:1.27.0",]}}PythonOpenAPIAs well as typescript, you can choose to generate the cdk construct and sample handler in python.newOpenApiGatewayPythonProject({parent:myNxMonorepo,outdir:'packages/myapi',name:'myapi',moduleName:'myapi',version:'1.0.0',authorName:'jack',authorEmail:'me@example.com',clientLanguages:[ClientLanguage.TYPESCRIPT,ClientLanguage.PYTHON,ClientLanguage.JAVA],});You will need to set up a shared virtual environment and configure dependencies via the monorepo (see README.md for the nx-monorepo package). An example of a full.projenrc.tsmight be:import{nx_monorepo}from"aws-prototyping-sdk";import{ClientLanguage,OpenApiGatewayPythonProject}from"@aws-prototyping-sdk/open-api-gateway";import{AwsCdkPythonApp}from"projen/lib/awscdk";constmonorepo=newnx_monorepo.NxMonorepoProject({defaultReleaseBranch:"main",devDeps:["aws-prototyping-sdk","@aws-prototyping-sdk/open-api-gateway"],name:"open-api-test",});constapi=newOpenApiGatewayPythonProject({parent:monorepo,outdir:'packages/myapi',name:'myapi',moduleName:'myapi',version:'1.0.0',authorName:'jack',authorEmail:'me@example.com',clientLanguages:[ClientLanguage.TYPESCRIPT],venvOptions:{//Useasharedvirtualenvdir.//Thegeneratedpythonclientwillalsousethisvirtualenvdirenvdir:'../../.env',},});//Installintovirtualenvsoit's available for the cdk appapi.tasks.tryFind('install')!.exec('pip install --editable .');constapp=newAwsCdkPythonApp({authorName:"jack",authorEmail:"me@example.com",cdkVersion:"2.1.0",moduleName:"myapp",name:"myapp",version:"1.0.0",parent:monorepo,outdir:"packages/myapp",deps:[api.moduleName],venvOptions:{envdir:'../../.env',},});monorepo.addImplicitDependency(app,api);monorepo.synth();You'll find the following directory structure inpackages/myapi:|_ myapi/
    |_ spec/
        |_ spec.yaml - The OpenAPI specification - edit this to define your API
        |_ .parsed-spec.json - A json spec generated from your spec.yaml.
    |_ api/
        |_ api.py - A CDK construct which defines the API Gateway resources to deploy your API.
        |           This wraps the OpenApiGatewayRestApi construct and provides typed interfaces for integrations specific
        |           to your API. You shouldn't need to modify this, instead just extend it as in sample_api.py.
        |_ sample_api.py - Example usage of the construct defined in api.py.
        |_ handlers/
             |_ say_hello_handler_sample.py - An example lambda handler for the operation defined in spec.yaml, making use of the
                                              generated lambda handler wrappers for marshalling and type safety.
|_ generated/
    |_ typescript/ - A generated typescript API client.
    |_ python/ - A generated python API client.
    |_ java/ - A generated java API client.For simplicity, the generated code deploys a lambda layer for the generated code and its dependencies. You may choose to define an entirely separate projenPythonProjectfor your lambda handlers should you wish to add more dependencies than just the generated code.SmithySimilar to typescript, you can use theSmithyApiGatewayPythonProject.JavaOpenAPIAs well as TypeScript and Python, you can choose to generate the cdk construct and sample handler in Java.newOpenApiGatewayJavaProject({parent:monorepo,outdir:'packages/myapi',name:"myapi",groupId:"com.mycompany",artifactId:"my-api",version:"1.0.0",clientLanguages:[ClientLanguage.PYTHON,ClientLanguage.TYPESCRIPT],documentationFormats:[DocumentationFormat.HTML2,DocumentationFormat.PLANTUML,DocumentationFormat.MARKDOWN],});The output directory will look a little like this:|_ src/
    |_ spec/
        |_ spec.yaml - The OpenAPI specification - edit this to define your API
    |_ main/
        |_ java/
            |_ api/
                |_ Api.java - A CDK construct which defines the API Gateway resources to deploy your API.
                |             This wraps the OpenApiGatewayRestApi construct and provides typed interfaces for integrations specific
                |             to your API. You shouldn't need to modify this, instead just extend it as in SampleApi.java.
                |_ ApiProps.java - Defines properties for the CDK construct in Api.java
                |_ SampleApi.java - Example usage of the construct defined in Api.java
                |_ SayHelloHandler.java - An example lambda handler for the operation defined in spec.yaml, making use of the
                                        generated lambda handler wrappers for marshalling and type safety.
        |_ resources/
            |_ .parsed-spec.json - A json spec generated from your spec.yaml. This will be bundled in the project jar.
|_ generated/
    |_ typescript/ - A generated typescript API client
    |_ python/ - A generated python API client.
    |_ java/ - A generated java API client.
    |_ documentation/
        |_ html2/ - Generated html documentation
        |_ markdown/ - Generated markdown documentation
        |_ plantuml/ - Generated plant uml documentationTheSampleApiconstruct uses a lambda function which deploys the entire project jar as a simple way to get started with an api that deploys out of the box. This jar includes a lot of extra code and dependencies that you don't need in your lambda, so it's recommended that after experimenting with the construct, you separate your lambdas into anotherJavaProject. Please refer to theJava API Lambda Handlerssection of this README for details on how to set this up.SmithySimilar to typescript and python, you can use theSmithyApiGatewayJavaProject.Smithy IDLPlease refer to theSmithy documentationfor how to write models in Smithy. A basic example is provided below:$version:"2"namespaceexample.hellouseaws.protocols#restJson1@title("A Sample Hello World API")/// A sample smithy api@restJson1serviceHello{version:"1.0"operations:[SayHello]
}@readonly@http(method:"GET",uri:"/hello")operationSayHello{input:SayHelloInputoutput:SayHelloOutputerrors:[ApiError]
}stringNamestringMessage@inputstructureSayHelloInput{@httpQuery("name")@requiredname:Name}@outputstructureSayHelloOutput{@requiredmessage:Message}@error("client")structureApiError{@requirederrorMessage:Message}Supported ProtocolsCurrently onlyAWS restJson1is supported. Please ensure your service is annotated with the@restJson1trait.Multiple FilesYou can split your definition into multiple files and folders, so long as they are all under thesmithy/src/main/smithydirectory in your API project.AuthorizersSmithy supportsadding API Gateway authorizers in the model itself. Given that at model definition time one usually does not know the ARN of the user pool or lambda function for an authorizer, it is recommended to add the authorizer(s) in your Api CDK construct.If using Smithy generated clients, some authorizer traits (eg sigv4) will include configuring the client for that particular method of authorization, so it can be beneficial to still define authorizers in the model. We therefore support specifying authorizers in both the model and the construct, but note that the construct will take precedence where the authorizer ID is the same.OpenAPI SpecificationYourspec.yamlfile defines your api usingOpenAPI Version 3.0.3. An example spec might look like:openapi:3.0.3info:version:1.0.0title:Example APIpaths:/hello:get:operationId:sayHelloparameters:-in:queryname:nameschema:type:stringrequired:trueresponses:'200':description:Successful responsecontent:'application/json':schema:$ref:'#/components/schemas/HelloResponse'components:schemas:HelloResponse:type:objectproperties:message:type:stringrequired:-messageYou can divide your specification into multiple files using$ref.For example, you might choose to structure your spec as follows:|_ spec/
    |_ spec.yaml
    |_ paths/
        |_ index.yaml
        |_ sayHello.yaml
    |_ schemas/
        |_ index.yaml
        |_ helloResponse.yamlWherespec.yamllooks as follows:openapi:3.0.3info:version:1.0.0title:Example APIpaths:$ref:'./paths/index.yaml'components:schemas:$ref:'./schemas/index.yaml'paths/index.yaml:/hello:get:$ref:'./sayHello.yaml'paths/sayHello.yaml:operationId:sayHelloparameters:-in:queryname:nameschema:type:stringrequired:trueresponses:'200':description:Successful responsecontent:'application/json':schema:$ref:'../schemas/helloResponse.yaml'schemas/index.yaml:HelloResponse:$ref:'./helloResponse.yaml'schemas/helloResponse.yaml:type:objectproperties:message:type:stringrequired:-messageConstructA sample construct is generated which provides a type-safe interface for creating an API Gateway API based on your OpenAPI specification. You'll get a type error if you forget to define an integration for an operation defined in your api.import{Authorizers,Integrations}from'@aws-prototyping-sdk/open-api-gateway';import{NodejsFunction}from'aws-cdk-lib/aws-lambda-nodejs';import{Construct}from'constructs';import{Api}from'./api';/***AnexampleofhowtowirelambdahandlerfunctionstotheAPI*/exportclassSampleApiextendsApi{constructor(scope:Construct,id:string){super(scope,id,{defaultAuthorizer:Authorizers.iam(),integrations:{//EveryoperationdefinedinyourAPImusthaveanintegrationdefined!sayHello:{integration:Integrations.lambda(newNodejsFunction(scope,'say-hello')),},},});}}Sharing IntegrationsIf you would like to use the same integration for every operation, you can use theOperations.allmethod from your generated client to save repeating yourself, for example:import{Operations}from'my-api-typescript-client';import{Authorizers,Integrations}from'@aws-prototyping-sdk/open-api-gateway';import{NodejsFunction}from'aws-cdk-lib/aws-lambda-nodejs';import{Construct}from'constructs';import{Api}from'./api';exportclassSampleApiextendsApi{constructor(scope:Construct,id:string){super(scope,id,{defaultAuthorizer:Authorizers.iam(),//Usethesameintegrationforeveryoperation.integrations:Operations.all({integration:Integrations.lambda(newNodejsFunction(scope,'say-hello')),}),});}}AuthorizersTheApiconstruct allows you to define one or more authorizers for securing your API. An integration will use thedefaultAuthorizerunless anauthorizeris specified at the integration level. The following authorizers are supported:Authorizers.none- No authAuthorizers.iam- AWS IAM (Signature Version 4)Authorizers.cognito- Cognito user poolAuthorizers.custom- A custom authorizerCognito AuthorizerTo use the Cognito authorizer, one or more user pools must be provided. You can optionally specify the scopes to check if using an access token. You can use thewithScopesmethod to use the same authorizer but verify different scopes for individual integrations, for example:exportclassSampleApiextendsApi{constructor(scope:Construct,id:string){constcognitoAuthorizer=Authorizers.cognito({authorizerId:'myCognitoAuthorizer',userPools:[newUserPool(scope,'UserPool')],});super(scope,id,{defaultAuthorizer:cognitoAuthorizer,integrations:{//Everyoneintheuserpoolcancallthisoperation:sayHello:{integration:Integrations.lambda(newNodejsFunction(scope,'say-hello')),},//OnlyuserswiththegivenscopescancallthisoperationmyRestrictedOperation:{integration:Integrations.lambda(newNodejsFunction(scope,'my-restricted-operation')),authorizer:cognitoAuthorizer.withScopes('my-resource-server/my-scope'),},},});}}For more information about scopes or identity and access tokens, please see theAPI Gateway documentation.Custom AuthorizerCustom authorizers use lambda functions to handle authorizing requests. These can either be simple token-based authorizers, or more complex request-based authorizers. See theAPI Gateway documentationfor more details.An example token-based authorizer (default):Authorizers.custom({authorizerId:'myTokenAuthorizer',function:newNodejsFunction(scope,'authorizer'),});An example request-based handler. By default the identitySource will bemethod.request.header.Authorization, however you can customise this as perthe API Gateway documentation.Authorizers.custom({authorizerId:'myRequestAuthorizer',type:CustomAuthorizerType.REQUEST,identitySource:'method.request.header.MyCustomHeader, method.request.querystring.myQueryString',function:newNodejsFunction(scope,'authorizer'),});IntegrationsIntegrations are used by API Gateway to service requests.Lambda IntegrationCurrently, the only built-in integration is a lambda integration. You can construct one usingIntegrations.lambda(yourLambdaFunction).Custom IntegrationsYou can implement your own integrations by inheriting theIntegrationclass and implementing itsrendermethod. This method is responsible for returning a snippet of OpenAPI which will be added as thex-amazon-apigateway-integrationfor an operation. Please refer to theAPI Gateway Swagger Extensions documentationfor more details.You can also optionally override thegrantmethod if you need to use CDK to grant API Gateway access to invoke your integration.Generated ClientTypescriptThetypescript-fetchOpenAPI generator is used to generate OpenAPI clients for typescript. This requires an implementation offetchto be passed to the client. In the browser one can pass the built in fetch, or in NodeJS you can use an implementation such asnode-fetch.Example usage of the client in a website:import{Configuration,DefaultApi}from"my-api-typescript-client";constclient=newDefaultApi(newConfiguration({basePath:"https://xxxxxxxxxx.execute-api.ap-southeast-2.amazonaws.com",fetchApi:window.fetch.bind(window),}));awaitclient.sayHello({name:"Jack"});PythonThepythonOpenAPI generator is used to generate OpenAPI clients for python.Example usage:frommy_api_pythonimportApiClient,Configurationfrommy_api_python.api.default_apiimportDefaultApiconfiguration=Configuration(host="https://xxxxxxxxxx.execute-api.ap-southeast-2.amazonaws.com")withApiClient(configuration)asapi_client:client=DefaultApi(api_client)client.say_hello(query_params={'name':"name_example",},)You'll find details about how to use the python client in the README.md alongside your generated client.JavaThejavaOpenAPI generator is used to generate OpenAPI clients for Java.Example usage:importcom.generated.api.myapijava.client.api.DefaultApi;importcom.generated.api.myapijava.client.ApiClient;importcom.generated.api.myapijava.client.Configuration;importcom.generated.api.myapijava.client.models.HelloResponse;ApiClientclient=Configuration.getDefaultApiClient();client.setBasePath("https://xxxxxxxxxx.execute-api.ap-southeast-2.amazonaws.com");DefaultApiapi=newDefaultApi(client);HelloResponseresponse=api.sayHello("Adrian").execute()You'll find more details about how to use the Java client in the README.md alongside your generated client.Lambda Handler WrappersLambda handler wrappers are also importable from the generated client. These provide input/output type safety, ensuring that your API handlers return outputs that correspond to your specification.Typescriptimport{sayHelloHandler}from"my-api-typescript-client";exportconsthandler=sayHelloHandler(async({input})=>{return{statusCode:200,body:{message:`Hello${input.requestParameters.name}!`,},};});Handler RouterThe lambda handler wrappers can be used in isolation as handler methods for separate lambdas. If you would like to use a single lambda function to serve all requests, you can do so with thehandlerRouter.import{handlerRouter,sayHelloHandler,sayGoodbyeHandler}from"my-api-typescript-client";import{corsInterceptor}from"./interceptors";import{sayGoodbye}from"./handlers/say-goodbye";constsayHello=sayHelloHandler(async({input})=>{return{statusCode:200,body:{message:`Hello${input.requestParameters.name}!`,},};});exportconsthandler=handlerRouter({//Interceptorsdeclaredinthislistwillapplytoalloperationsinterceptors:[corsInterceptor],//Assignhandlerstoeachoperationherehandlers:{sayHello,sayGoodbye,},});Pythonfrommyapi_python.apis.tags.default_api_operation_configimportsay_hello_handler,SayHelloRequest,ApiResponse,SayHelloOperationResponsesfrommyapi_python.model.api_errorimportApiErrorfrommyapi_python.model.hello_responseimportHelloResponse@say_hello_handlerdefhandler(input:SayHelloRequest,**kwargs)->SayHelloOperationResponses:returnApiResponse(status_code=200,body=HelloResponse(message="Hello{}!".format(input.request_parameters["name"])),headers={})Handler RouterThe lambda handler wrappers can be used in isolation as handler methods for separate lambdas. If you would like to use a single lambda function to serve all requests, you can do so with thehandler_router.frommyapi_python.apis.tags.default_api_operation_configimportsay_hello_handler,SayHelloRequest,ApiResponse,SayHelloOperationResponses,handler_router,HandlerRouterHandlersfrommyapi_python.model.api_errorimportApiErrorfrommyapi_python.model.hello_responseimportHelloResponsefromother_handlersimportsay_goodbyefrommy_interceptorsimportcors_interceptor@say_hello_handlerdefsay_hello(input:SayHelloRequest,**kwargs)->SayHelloOperationResponses:returnApiResponse(status_code=200,body=HelloResponse(message="Hello{}!".format(input.request_parameters["name"])),headers={})handler=handler_router(# Interceptors defined here will apply to all operationsinterceptors=[cors_interceptor],handlers=HandlerRouterHandlers(say_hello=say_hello,say_goodbye=say_goodbye))Javaimportcom.generated.api.myapijava.client.api.Handlers.SayHello;importcom.generated.api.myapijava.client.api.Handlers.SayHello200Response;importcom.generated.api.myapijava.client.api.Handlers.SayHelloRequestInput;importcom.generated.api.myapijava.client.api.Handlers.SayHelloResponse;importcom.generated.api.myapijava.client.model.HelloResponse;publicclassSayHelloHandlerextendsSayHello{@OverridepublicSayHelloResponsehandle(SayHelloRequestInputsayHelloRequestInput){returnSayHello200Response.of(HelloResponse.builder().message(String.format("Hello %s",sayHelloRequestInput.getInput().getRequestParameters().getName())).build());}}Handler RouterThe lambda handler wrappers can be used in isolation as handler methods for separate lambdas. If you would like to use a single lambda function to serve all requests, you can do so by extending theHandlerRouterclass.importcom.generated.api.myapijava.client.api.Handlers.SayGoodbye;importcom.generated.api.myapijava.client.api.Handlers.HandlerRouter;importcom.generated.api.myapijava.client.api.Handlers.Interceptors;importcom.generated.api.myapijava.client.api.Handlers.SayHello;importjava.util.Arrays;importjava.util.List;// Interceptors defined here apply to all operations@Interceptors({TimingInterceptor.class})publicclassApiHandlerRouterextendsHandlerRouter{// You must implement a method to return a handler for every operation@OverridepublicSayHellosayHello(){returnnewSayHelloHandler();}@OverridepublicSayGoodbyesayGoodbye(){returnnewSayGoodbyeHandler();}}InterceptorsThe lambda handler wrappers allow you to pass in achainof 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 invokingchain.next.TypescriptIn typescript, interceptors are passed as separate arguments to the generated handler wrapper, in the order in which they should be executed. Callrequest.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 callchain.next.import{sayHelloHandler,ChainedRequestInput,OperationResponse,}from"my-api-typescript-client";//Interceptortowrapinvocationsinatry/catch,returninga500errorforanyunhandledexceptions.consttryCatchInterceptor=async<RequestParameters,RequestArrayParameters,RequestBody,Response>(request:ChainedRequestInput<RequestParameters,RequestArrayParameters,RequestBody,Response>):Promise<Response|OperationResponse<500,{errorMessage:string}>>=>{try{returnawaitrequest.chain.next(request);}catch(e:any){return{statusCode:500,body:{errorMessage:e.message}};}};//tryCatchInterceptorispassedfirst,soitrunsfirstandcallsthesecondargumentfunction(therequesthandler)viachain.nextexportconsthandler=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:import{ChainedRequestInput,}from'my-api-typescript-client';consttimingInterceptor=async<RequestParameters,RequestArrayParameters,RequestBody,Response>(request:ChainedRequestInput<RequestParameters,RequestArrayParameters,RequestBody,Response>):Promise<Response>=>{conststart=Date.now();constresponse=awaitrequest.chain.next(request);constend=Date.now();console.log(`Took${end-start}ms`);returnresponse;};Interceptors may mutate theinterceptorContextto pass state to further interceptors or the final lambda handler, for example anidentityInterceptormight want to extract the authenticated user from the request so that it is available in handlers.import{LambdaRequestParameters,LambdaHandlerChain,}from'my-api-typescript-client';constidentityInterceptor=async<RequestParameters,RequestArrayParameters,RequestBody,Response>(request:ChainedRequestInput<RequestParameters,RequestArrayParameters,RequestBody,Response>):Promise<Response>=>{constauthenticatedUser=awaitgetAuthenticatedUser(request.event);returnawaitrequest.chain.next({...request,interceptorContext:{...request.interceptorContext,authenticatedUser,},});};PythonIn Python, a list of interceptors can be passed as a keyword argument to the generated lambda handler decorator, for example:frommyapi_python.apis.tags.default_api_operation_configimportsay_hello_handler,SayHelloRequest,ApiResponse,SayHelloOperationResponsesfrommyapi_python.model.api_errorimportApiErrorfrommyapi_python.model.hello_responseimportHelloResponse@say_hello_handler(interceptors=[timing_interceptor,try_catch_interceptor])defhandler(input:SayHelloRequest,**kwargs)->SayHelloOperationResponses:returnApiResponse(status_code=200,body=HelloResponse(message="Hello{}!".format(input.request_parameters["name"])),headers={})Writing an interceptor is just like writing a lambda handler. Callchain.next(input)from an interceptor to delegate to the rest of the chain to handle a request.importtimefrommyapi_python.apis.tags.default_api_operation_configimportChainedApiRequest,ApiResponsedeftiming_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))returnresponseInterceptors may choose to return different responses, for example to return a 500 response for any unhandled exceptions:importtimefrommyapi_python.model.api_errorimportApiErrorfrommyapi_python.apis.tags.default_api_operation_configimportChainedApiRequest,ApiResponsedeftry_catch_interceptor(input:ChainedApiRequest)->ApiResponse:try:returninput.chain.next(input)exceptExceptionase:returnApiResponse(status_code=500,body=ApiError(errorMessage=str(e)),headers={})Interceptors are permitted to mutate the "interceptor context", which is aDict[str, Any]. Each interceptor in the chain, and the final handler, can access this context:defidentity_interceptor(input:ChainedApiRequest)->ApiResponse:input.interceptor_context["AuthenticatedUser"]=get_authenticated_user(input.event)returninput.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:defadd_cors_headers_interceptor(input:ChainedApiRequest)->ApiResponse:response=input.chain.next(input)returnApiResponse(status_code=response.status_code,body=response.body,headers={**response.headers,"Access-Control-Allow-Origin":"*","Access-Control-Allow-Headers":"*"})JavaIn Java, interceptors can be added to a handler via the@Interceptorsclass annotation:importcom.generated.api.myjavaapi.client.api.Handlers.Interceptors;@Interceptors({TimingInterceptor.class,TryCatchInterceptor.class})publicclassSayHelloHandlerextendsSayHello{@OverridepublicSayHelloResponsehandle(SayHelloRequestInputsayHelloRequestInput){returnSayHello200Response.of(HelloResponse.builder().message(String.format("Hello %s",sayHelloRequestInput.getInput().getRequestParameters().getName())).build());}}To write an interceptor, you can implement theInterceptorinterface. For example, a timing interceptor:importcom.generated.api.myjavaapi.client.api.Handlers.Interceptor;importcom.generated.api.myjavaapi.client.api.Handlers.ChainedRequestInput;importcom.generated.api.myjavaapi.client.api.Handlers.Response;publicclassTimingInterceptor<Input>implementsInterceptor<Input>{@OverridepublicResponsehandle(ChainedRequestInput<Input>input){longstart=System.currentTimeMillis();Responseres=input.getChain().next(input);longend=System.currentTimeMillis();System.out.printf("Took %d ms%n",end-start);returnres;}}Interceptors may choose to return different responses, for example to return a 500 response for any unhandled exceptions:importcom.generated.api.myjavaapi.client.api.Handlers.Interceptor;importcom.generated.api.myjavaapi.client.api.Handlers.ChainedRequestInput;importcom.generated.api.myjavaapi.client.api.Handlers.Response;importcom.generated.api.myjavaapi.client.api.Handlers.ApiResponse;importcom.generated.api.myjavaapi.client.model.ApiError;publicclassTryCatchInterceptor<Input>implementsInterceptor<Input>{@OverridepublicResponsehandle(ChainedRequestInput<Input>input){try{returninput.getChain().next(input);}catch(Exceptione){returnApiResponse.builder().statusCode(500).body(ApiError.builder().errorMessage(e.getMessage()).build().toJson()).build();}}}Interceptors are permitted to mutate the "interceptor context", which is aMap<String, Object>. Each interceptor in the chain, and the final handler, can access this context:publicclassIdentityInterceptor<Input>implementsInterceptor<Input>{@OverridepublicResponsehandle(ChainedRequestInput<Input>input){input.getInterceptorContext().put("AuthenticatedUser",this.getAuthenticatedUser(input.getEvent()));returninput.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:publicstaticclassAddCorsHeadersInterceptor<Input>implementsInterceptor<Input>{@OverridepublicResponsehandle(ChainedRequestInput<Input>input){Responseres=input.getChain().next(input);res.getHeaders().put("Access-Control-Allow-Origin","*");res.getHeaders().put("Access-Control-Allow-Headers","*");returnres;}}Interceptors with Dependency InjectionInterceptors referenced by the@Interceptorsannotation 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 thegetInterceptorsmethod in your handler:publicclassSayHelloHandlerextendsSayHello{@OverridepublicList<Interceptor<SayHelloInput>>getInterceptors(){returnArrays.asList(newMyConfiguredInterceptor<>(42),newMyOtherConfiguredInterceptor<>("configuration"));}@OverridepublicSayHelloResponsehandle(SayHelloRequestInputsayHelloRequestInput){returnSayHello200Response.of(HelloResponse.builder().message(String.format("Hello %s!",sayHelloRequestInput.getInput().getRequestParameters().getName())).build());}}Other DetailsWorkspaces andOpenApiGatewayTsProjectOpenApiGatewayTsProjectcan be used as part of a monorepo using YARN/NPM/PNPM workspaces. When used in a monorepo, a dependency is established betweenOpenApiGatewayTsProjectand the generated typescript client, which is expected to be managed by the parent monorepo (ie bothOpenApiGatewayTsProjectand the generated typescript client are parented by the monorepo).During initial project synthesis, the dependency betweenOpenApiGatewayTsProjectand the generated client is established via workspace configuration local toOpenApiGatewayTsProject, since the parent monorepo will not have updated to include the new packages in time for the initial "install".When the package manager is PNPM, this initial workspace is configured by creating a localpnpm-workspace.yamlfile, and thus if you specify your own for an instance ofOpenApiGatewayTsProject, synthesis will fail. It is most likely that you will not need to define this file locally inOpenApiGatewayTsProjectsince the monorepo copy should be used to manage all packages within the repo, however if managing this file at theOpenApiGatewayTsProjectlevel is required, please use thepnpmWorkspaceproperty ofOpenApiGatewayTsProject.Customising Generated Client ProjectsBy default, the generated clients are configured automatically, including project names. You can customise the generated client code using the<language>ProjectOptionsproperties when constructing your projen project.Python Shared Virtual EnvironmentFor adding dependencies between python projects within a monorepo you can use a single shared virtual environment, and install your python projects into that environment withpip install --editable .in the dependee. The generated python client will automatically do this if it detects it is within a monorepo.The following example shows how to configure the generated client to use a shared virtual environment:constapi=newOpenApiGatewayTsProject({parent:monorepo,name:'api',outdir:'packages/api',defaultReleaseBranch:'main',clientLanguages:[ClientLanguage.PYTHON],pythonClientOptions:{moduleName:'my_api_python',name:'my_api_python',authorName:'jack',authorEmail:'me@example.com',version:'1.0.0',venvOptions:{//Directoryrelativetothegeneratedpythonclient(inthiscasepackages/api/generated/python)envdir:'../../../../.env',},},});newPythonProject({parent:monorepo,outdir:'packages/my-python-lib',moduleName:'my_python_lib',name:'my_python_lib',authorName:'jack',authorEmail:'me@example.com',version:'1.0.0',venvOptions:{//Directoryrelativetothepythonlib(inthiscasepackages/my-python-lib)envdir:'../../.env',},//GeneratedclientcanbesafelycasttoaPythonProjectdeps:[(api.generatedClients[ClientLanguage.PYTHON]asPythonProject).moduleName],});Java API Lambda HandlersTo build your lambda handlers in Java, it's recommended to create a separateJavaProjectin your.projenrc. This needs to build a "super jar" with all of your dependencies packed into a single jar. You can use themaven-shade-pluginto achieve this (seethe java lambda docs for details). You'll need to add a dependency on the generated java client for the handler wrappers. For example, your.projenrc.tsmight look like:constapi=newOpenApiGatewayTsProject({parent:monorepo,name:'@my-test/api',outdir:'packages/api',defaultReleaseBranch:'main',clientLanguages:[ClientLanguage.JAVA],});constapiJavaClient=(api.generatedClients[ClientLanguage.JAVA]asJavaProject);constjavaLambdaProject=newJavaProject({parent:monorepo,outdir:'packages/java-lambdas',artifactId:"my-java-lambdas",groupId:"com.mycompany",name:"javalambdas",version:"1.0.0",//Addadependencyonthejavaclientdeps:[`${apiJavaClient.pom.groupId}/${apiJavaClient.pom.artifactId}@${apiJavaClient.pom.version}`],});//Setupthedependencyonthegeneratedlambdaclientmonorepo.addImplicitDependency(javaLambdaProject,apiJavaClient);javaLambdaProject.pom.addRepository({url:`file://../api/generated/java/dist/java`,id:'java-api-client',});//Usethemaven-shade-pluginaspartofthemavenpackagetaskjavaLambdaProject.pom.addPlugin('org.apache.maven.plugins/maven-shade-plugin@3.2.2',{configuration:{createDependencyReducedPom:false,finalName:'my-java-lambdas',},executions:[{id:'shade-task',phase:'package',//<-NB"phase"issupportedinprojen^0.61.37goals:['shade'],}],});//Buildthe"super jar"aspartoftheproject's package taskjavaLambdaProject.packageTask.exec('mvn clean install');You can then implement your lambda handlers in yourjava-lambdasproject using the generated lambda handler wrappers (see above).Finally, you can create a lambda function in your CDK infrastructure which points to the resultant "super jar":newApi(this,'JavaApi',{integrations:{sayHello:{function:newFunction(this,'SayHelloJava',{code:Code.fromAsset('../java-lambdas/target/my-java-lambdas.jar'),handler:'com.mycompany.SayHelloHandler',runtime:Runtime.JAVA_11,timeout:Duration.seconds(30),}),},},});Note that to ensure the jar is built before the CDK infrastructure which consumes it, you must add a dependency, eg:monorepo.addImplicitDependency(infra,javaLambdaProject);AWS WAFv2 Web ACLBy default, aWeb ACLis deployed and attached to your API Gateway Rest API with the "AWSManagedRulesCommonRuleSet", which provides protection against exploitation of a wide range of vulnerabilities, including some of the high risk and commonly occurring vulnerabilities described in OWASP publications such asOWASP Top 10.You can customise the Web ACL configuration via thewebAclOptionsof yourApiCDK construct, eg:exportclassSampleApiextendsApi{constructor(scope:Construct,id:string){super(scope,id,{integrations:{...},webAclOptions:{//AllowaccessonlytospecificCIDRrangescidrAllowList:{cidrType:'IPV4',cidrRanges:['1.2.3.4/5'],},//Pickfromthesethere:https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.htmlmanagedRules:[{vendor:'AWS',name:'AWSManagedRulesSQLiRuleSet'},],},});}}You can remove the Web ACL entirely withwebAclOptions: { disable: true }- you may wish to use this if you'd like to set up a Web ACL yourself with more control over the rules.Smithy IntelliJ PluginThe Smithy-based projects are compatible with theSmithy IntelliJ Plugin, which provides syntax highlighting and auto-complete for your Smithy model. To make use of it, perform the following steps:Install the "Smithy" plugin (underPreferences -> Plugins)Right-click on thesmithy/build.gradlefile in your Smithy API projectSelect "Link Gradle Project"Tagging OperationsOperations can be grouped together into logical collections via tags. This can be achieved in Smithy with the@tagstrait:@tags(["pets","users"])operationPurchasePet{...}Or in OpenAPI using thetagsproperty:paths:/pets/purchase:post:operationId:purchasePettags:-pets-users...When multiple tags are used, the "first" tag is considered to be the API that the operation belongs to, so in the generated client, the above example operation would be included in thePetsApiclient but not theUsersApiclient.Multiple tags are still useful for documentation generation, for exampleDocumentationFormat.HTML_REDOCwill group operations by tag in the side navigation bar.If you would like to introduce tags without breaking existing clients, we recommend first adding a tag nameddefaultto all operations.⚠️Important Note: Smithy versions below1.28.0sort tags in alphabetical order and so the "first" tag will be the earliest in the alphabet. Therefore, if using tags with older versions of Smithy, we recommend prefixing your desired first tag with an underscore (for example_default). This is rectified in1.28.0, where tag order from the@tagstrait is preserved.Breaking Changesv0.14.0- seehttps://github.com/aws/aws-prototyping-sdk/pull/280Moved smithy model files frommodeldirectory tosmithy/src/main/smithy- please move these manually as part of upgrading to0.14.0, and delete yourmodeldirectory when done.Moved smithy gradle files fromsmithy-builddirectory tosmithy- if you have added any dependencies to yoursmithy-build/build.gradlefile you will need to copy them across intosmithy/build.gradle(note dependencies in the new gradle file start withimplementationrather thansmithy).DeprecatedgradleWrapperPathoption on SmithApiGateway projects in favour ofignoreGradleWrapper: false- the gradle wrapper insmithydirectory is always used (and generated automatically if not found). If you used a custom gradle wrapper, copy it into thesmithydirectory, setignoreGradleWrapper: falseand check it in to your repository.