Skip to content

Commit

Permalink
feat(type-safe-api): support for modelling apis in typespec (#874)
Browse files Browse the repository at this point in the history
Adds support for modelling both REST and WebSocket APIs in TypeSpec: https://typespec.io/
  • Loading branch information
cogwirrel authored Oct 28, 2024
1 parent 8d9e216 commit c6d5b4c
Show file tree
Hide file tree
Showing 35 changed files with 24,443 additions and 25 deletions.
2 changes: 1 addition & 1 deletion docs/content/getting_started/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ To install these, follow the below links:

!!!note

If you are using another modelling language (for example OpenAPI), these are not required.
If you are using another modelling language (for example TypeSpec or OpenAPI), these are not required.

### Language specific

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ nav:
- "REST APIs":
- "Getting Started": getting_started.md
- "Using Smithy": using_smithy.md
- "Using TypeSpec": using_typespec.md
- "Using OpenAPI": using_openapi.md
- "Lambda Handlers": lambda_handlers.md
- "Interceptors": interceptors.md
Expand All @@ -16,6 +17,7 @@ nav:
- "WebSocket APIs":
- "Getting Started": websocket_getting_started.md
- "Using Smithy": websocket_using_smithy.md
- "Using TypeSpec": websocket_using_typespec.md
- "Using OpenAPI": websocket_using_openapi.md
- "Lambda Handlers": websocket_lambda_handlers.md
- "Server SDKs": websocket_server_sdk.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This section describes how to get started with the Type Safe API. For more infor

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.
- `model` - Configure the API model. Select a `language` for the model from either [Smithy](https://smithy.io/2.0/), [TypeSpec](https://typespec.io/) or [OpenAPI v3](https://swagger.io/specification/), and provide `options.smithy`, `options.typeSpec` or `options.openapi` depending on your choice.
- `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.
- `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`.
Expand Down Expand Up @@ -63,7 +63,7 @@ npx projen new --from @aws/pdk monorepo-ts --package-manager=pnpm
name: "myapi",
parent: monorepo,
outdir: "packages/api",
// Smithy as the model language. You can also use ModelLanguage.OPENAPI
// Smithy as the model language. You can also use ModelLanguage.TYPESPEC or ModelLanguage.OPENAPI
model: {
language: ModelLanguage.SMITHY,
options: {
Expand Down Expand Up @@ -240,6 +240,19 @@ The generated runtime projects include lambda handler wrappers which provide typ
}
```

=== "TYPESPEC"

Use the `@handler` decorator, and specify the language you wish to implement the operation in.

```tsp hl_lines="3"
@get
@route("/hello")
@handler({ language: "typescript" })
op SayHello(@query name: string): {
message: string;
};
```

=== "OPENAPI"

Use the `x-handler` vendor extension, specifying the language you wish to implement this operation in.
Expand Down Expand Up @@ -735,6 +748,32 @@ Add the new operation in the `model` project, for example:
}
```

=== "TYPESPEC"

In `model/src/main.tsp` or another `.tsp` file somewhere in the `model/src` directory, define the operation:

```tsp
namespace MyApi; // <- ensure the namespace is the same as in main.tsp if defining in another file

/**
* Documentation about the operation can go here
*/
@post
@route("/goodbye")
@handler({ language: "typescript" }) // <- you can also choose "python" or "java"
op SayGoodbye(
name: string,
): {
message: string;
};
```

If you defined your operation in a different file, you must import it in `model/src/main.tsp`:

```tsp
import "./operations/say-goodbye.tsp";
```

=== "OPENAPI"

In `model/src/main/openapi/main.yaml`, add the new operation under `paths`, and any new schemas under `components.schemas`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@

> Define your REST or WebSocket API's declaratively in a type-safe manner to reduce preventable issues and increase overall productivity.
The _type-safe-api_ package provides projen projects which allow you to define a REST or WebSocket API using either [Smithy](https://smithy.io/2.0/) or [OpenAPI v3](https://swagger.io/specification/), and constructs which manage deploying these APIs on API Gateway.
The _type-safe-api_ package provides projen projects which allow you to define a REST or WebSocket API using [Smithy](https://smithy.io/2.0/), [TypeSpec](https://typespec.io/) or [OpenAPI v3](https://swagger.io/specification/), and constructs which manage deploying these APIs on 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.
You can define your APIs using [Smithy](https://smithy.io/2.0/), [TypeSpec](https://typespec.io/) or [OpenAPI v3](https://swagger.io/specification/), and leverage the power of generated client and server types, infrastructure, documentation, and automatic input validation.

!!! note

WebSocket APIs are currently considered experimental and only support generating TypeScript code.

## How does it work?

Use the projen projects vended by Type Safe API to create REST APIs and WebSocket APIs. Model your API in either Smithy or OpenAPI. The project will generate type-safe clients, server-side bindings and CDK constructs in your desired languages so you can focus on implementing the business logic for your application.
Use the projen projects vended by Type Safe API to create REST APIs and WebSocket APIs. Model your API in either Smithy, TypeSpec or OpenAPI. The project will generate type-safe clients, server-side bindings and CDK constructs in your desired languages so you can focus on implementing the business logic for your application.

!!! note

Expand All @@ -26,10 +26,7 @@ The `TypeSafeApiProject` projen project creates a REST API, and produces the fol

```
|_ model/
|_ src/
|_ main/
|_ smithy - your API definition if you chose ModelLanguage.SMITHY
|_ openapi - your API definition if you chose ModelLanguage.OPENAPI
|_ src - your model source, in Smithy, TypeSpec or OpenAPI depending on your selected model language
|_ handlers/
|_ typescript - lambda handlers for operations you choose to implement in TypeScript
|_ python - lambda handlers for operations you choose to implement in Python
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Using OpenAPI

As an alternative to [Smithy](./using_smithy.md), you can use [OpenAPI Version 3.0.3](https://swagger.io/specification/) to define your API.
As an alternative to [Smithy](./using_smithy.md) or [TypeSpec](./using_typespec.md), you can use [OpenAPI Version 3.0.3](https://swagger.io/specification/) to define your API.

To use OpenAPI, in `TypeSafeApiProject`, specify it as the `model.language`.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Using TypeSpec

You can use [TypeSpec](https://typespec.io/), an interface definition language (IDL) to model your APIs. To use TypeSpec, in `TypeSafeApiProject`, specify it as your `model.language`.

Use the `namespace` to name your API. The convention is to use `PascalCase` to define your namespace.

=== "TS"

```ts
new TypeSafeApiProject({
model: {
language: ModelLanguage.TYPESPEC,
options: {
typeSpec: {
namespace: "MyApi"
},
},
},
...
});
```

=== "JAVA"

```java
TypeSafeApiProject.Builder.create()
.model(ModelConfiguration.builder()
.language(ModelLanguage.SMITHY)
.options(ModelOptions.builder()
.typeSpec(TypeSpecModelOptions.builder()
.namespace("MyApi")
.build())
.build())
.build())
...
.build();
```

=== "PYTHON"

```python
TypeSafeApiProject(
model=ModelConfiguration(
language=ModelLanguage.SMITHY,
options=ModelOptions(
type_spec=TypeSpecModelOptions(
namespace="MyApi"
)
)
),
...
)
```

!!!tip

Use the same namespace in all of your `.tsp` files to ensure they are included in your API.

## Using the TypeSpec IDL

For more information on how use TypeSpec for writing models, refer to the [TypeSpec documentation](https://typespec.io/docs/getting-started/getting-started-rest/01-setup-basic-syntax/).

You will find your API model in `model/src/main.tsp`:

```tsp
import "@typespec/http";
import "@typespec/openapi";
import "@typespec/openapi3";
import "../generated/aws-pdk/prelude.tsp";
import "./types/errors.tsp";
using Http;
using OpenAPI;
/**
* A sample TypeSpec api
*/
@service({
title: "MyApi",
})
@info({
version: "1.0",
})
namespace MyApi;
@get
@route("/hello")
@handler({ language: "typescript" })
op SayHello(@query name: string):
| {
message: string;
}
| BadRequestError
| NotAuthorizedError
| NotFoundError
| InternalFailureError;
```

### Splitting your model definition

You can split your model definition into multiple `.tsp` files in the `model/src` folder. For example, you can structure your model as follows:

```
model/src
|_ operations/
|_ say-hello.tsp
|_ models/
|_ say-hello-input.tsp
|_ say-hello-output.tsp
|_ main.tsp
```

### Defining models

In TypeSpec, you can define and reuse structures called `models`. For example, one might define a model named `Foo` in `models/foo.tsp`:

```tsp
namespace MyApi;
model Foo {
member: string;
}
```

You can reference this model in other `.tsp` files so long as you import it, for example:

```tsp
import "./foo.tsp";
namespace MyApi;
model Bar {
foo: Foo;
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This section describes how to get started with the Type Safe WebSocket API. For

The `TypeSafeWebSocketApiProject` 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.
- `model` - Configure the API model. Select a `language` for the model from either [Smithy](https://smithy.io/2.0/), [TypeSpec](https://typespec.io/) or [OpenAPI v3](https://swagger.io/specification/), and provide `options.smithy`, `options.typeSpec` or `options.openapi` depending on your choice.
- `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.
- `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 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, and server SDKs for sending messages to connected clients. You'll notice runtime packages are automatically generated for languages you picked for `infrastructure` and `handlers`.
Expand Down Expand Up @@ -60,7 +60,7 @@ npx projen new --from @aws/pdk monorepo-ts --package-manager=pnpm
name: "myapi",
parent: monorepo,
outdir: "packages/api",
// Smithy as the model language. You can also use ModelLanguage.OPENAPI
// Smithy as the model language. You can also use ModelLanguage.TYPESPEC or ModelLanguage.OPENAPI
model: {
language: ModelLanguage.SMITHY,
options: {
Expand Down Expand Up @@ -126,15 +126,15 @@ npx projen new --from @aws/pdk monorepo-ts --package-manager=pnpm

3.) Given we have modified our `projenrc` file we need to run the `npx projen` command to synthesize our new API and infrastructure onto the filesystem. We can then run a first build with `npx projen build`.

A sample API definition is generated for you in `packages/api/model`, which you are free to modify. Modelling WebSocket APIs is slightly different to REST APIs, namely each operation in a WebSocket API is one-way, sent either from a client to a server or from a server to a client. This means that WebSocket operations define only an input and do not define response types. For more details please refer to [Using Smithy](./websocket_using_smithy.md) and [Using OpenAPI](./websocket_using_smithy.md),
A sample API definition is generated for you in `packages/api/model`, which you are free to modify. Modelling WebSocket APIs is slightly different to REST APIs, namely each operation in a WebSocket API is one-way, sent either from a client to a server or from a server to a client. This means that WebSocket operations define only an input and do not define response types. For more details please refer to [Using Smithy](./websocket_using_smithy.md), [Using TypeSpec](./websocket_using_typespec.md) and [Using OpenAPI](./websocket_using_smithy.md).

## 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 generated stubs for you to implement for every operation which has been annotated accordingly:
The generated runtime projects include lambda handler wrappers which provide type-safety for implementing your `client_to_server` or `bidirectional` operations. The generated `handlers` projects include generated stubs for you to implement for every operation which has been annotated accordingly:

=== "SMITHY"

Use the `@async` trait to select the operation direction. Choose between `client_to_server`, `server_to_client` or `bidirectional`
Use the `@async` trait to select the operation direction. Choose between `client_to_server`, `server_to_client` or `bidirectional`.

Use the `@handler` trait, and specify the language you wish to implement this operation in.

Expand All @@ -153,6 +153,25 @@ The generated runtime projects include lambda handler wrappers which provide typ

The `@handler` trait may only be applied to `client_to_server` or `bidirectional` operations.


=== "TYPESPEC"

Use the `@async` decorator to select the operation direction. Choose between `client_to_server`, `server_to_client` or `bidirectional`.

Use the `@handler` trait, and specify the language you wish to implement this operation in.

```tsp hl_lines="1-2"
@async({ direction: "client_to_server" })
@handler({ language: "typescript" })
op SubscribeToNotifications(
topic: string,
): void;
```

!!!tip

The `@handler` decorator may only be applied to `client_to_server` or `bidirectional` operations.

=== "OPENAPI"

Use the `x-async` vendor extension to select the operation direction. Choose between `client_to_server`, `server_to_client` or `bidirectional`
Expand Down Expand Up @@ -390,6 +409,35 @@ Add the new operation in the `model` project, for example:
}
```

=== "TYPESPEC"

In `model/src/main.tsp`, or another `.tsp` file somewhere in `model/src`, define the operation:

```tsp
@async({ direction: "bidirectional" }) // <- you can also choose "client_to_server" or "server_to_client"
@handler({ language: "typescript" })
op Ping(
message: string,
): void;
```

If you defined your operation in a separate file, make sure you import it in `model/src/main.tsp`, eg:

```tsp
import "./operations/ping.tsp";
```

!!!note

If you defined your operation in a separate file, you will need to import the decorators from `model/generated/aws-pdk`, for example:

```tsp
import "../../generated/aws-pdk/prelude.tsp";
import "../../generated/aws-pdk/async.tsp";
```

You will also need to ensure the `namespace` is the same as the one in your `main.tsp` file.

=== "OPENAPI"

In `model/src/main/openapi/main.yaml`, add the new operation under `paths`, and any new schemas under `components.schemas`.
Expand Down Expand Up @@ -440,7 +488,7 @@ To run a build in the root of your monorepo, use the `npx projen build` command:
npx projen build
```

The build will regenerate the infrastructure, runtime, 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.
The build will regenerate the infrastructure, runtime, 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/TypeSpec or `x-handler` vendor extension in OpenAPI.

As you must define an integration for every `client_to_server` or `bidirectional` operation, you may see the following build error in your CDK application.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Using OpenAPI

As an alternative to [Smithy](./websocket_using_smithy.md), you can use [OpenAPI Version 3.0.3](https://swagger.io/specification/) to define your API.
As an alternative to [Smithy](./websocket_using_smithy.md) or [TypeSpec](./websocket_using_typespec.md), you can use [OpenAPI Version 3.0.3](https://swagger.io/specification/) to define your API.

!!!warning

Note that while Type Safe WebSocket API uses the OpenAPI format to define operations, many of the features of OpenAPI are not supported by WebSockets and therefore may be ignored if you define them in your model, for example specifying `parameters` or `responses`.

[Smithy](./websocket_using_smithy.md) is the recommended model language for WebSocket APIs.
[TypeSpec](./websocket_using_typespec.md) and [Smithy](./websocket_using_smithy.md) are the recommended model languages for WebSocket APIs.

To use OpenAPI, in `TypeSafeWebSocketApiProject`, specify it as the `model.language`.

Expand Down
Loading

0 comments on commit c6d5b4c

Please sign in to comment.