Background image
Featured blog post image

Changelog: Customisable Imports, OpenAPI Overlays, and Terraform generation improvements!

New features to the Speakeasy Platform - November 15th, 2023.

Welcome to another edition of the Speakeasy Changelog. In this issue, we'll give you a sneak peek into our support for OpenAPI Overlays (opens in a new tab) and how we're leveraging them to help customers customize their SDKs and other generated artifacts without changing the underlying specification.

We'll also be diving into new DevEx improvements that let you customize SDK imports, as well as exciting Terraform releases, including Pulumi support!

Sound good?

Ok, let’s go! πŸš€

OpenAPI Overlays

What is an Overlay, you ask? You can think of them as miniature OpenAPI documents that can be used to customize certain details of your API without altering the source document. Why would you want to maintain one?

  • You might want to customize your OpenAPI spec for SDK creation, but changing your spec is hard because it's generated from an API framework like FastAPI, tsoa, JOI, etc.
  • You have a lot of teams at your company creating OpenAPI specs, and asking one of them to make a change is a tough process.
  • You are setting up a Terraform provider for your OSS product and need different server URLs configured so users only hit a hosted API.

Let's look at an example. Here's a simple spec for the Speakeasy bar with only two tags defined, drinks and orders.


openapi: 3.1.0
info:
title: The Speakeasy Bar
version: 1.0.0
summary: A bar that serves drinks.
servers:
- url: https://speakeasy.bar
description: The production server.
security:
- apiKey: []
tags:
- name: drinks
description: The drinks endpoints.
- name: orders
description: The orders endpoints.
paths:
/dinner/:
post: ...
get: ...
/drinks/:
post: ...

To make an easy-to-use SDK, we've decided that a public interface should use snacks, i.e., sdk.snacks.get_orders(). As the owner of the company's SDK, you want to make this change, but that would mean making an actual code change with the team that owns the drinks and orders service. Worse yet, it's all microservices, and there is no one team that owns all the services. You can get around this sustainably with an overlay.

This overlay includes a target that you want to modify in your source document and an action to modify the target.


overlay: 1.0.0
info:
title: Overlay to fix the Speakeasy bar
version: 0.0.1
actions:
- target: "$.tags"
description: Add a Snacks tag to the global tags list
update:
- name: Snacks
description: All methods related to serving snacks
- target: "$.paths['/dinner']"
description: Remove all paths related to serving dinner
remove: true

Specify that we want to add a new tag to the global tags list $.tags and add a description of the edit you're making. Under the update label, add the name and description of the tag you want to add. Now you can use the Speakeasy CLI to merge these two documents right before generating the SDK:


speakeasy overlay apply -s openapi.yaml -o overlay.yaml >> combined.yaml

Time to celebrate πŸŽ‰ You've just created a new OpenAPI document that you can use to generate an SDK with the snacks tag. More on how to use Overlays here.

πŸ“¦ Customizable Imports

At Speakeasy, we believe that automated doesn't mean no input. Certain aspects of SDK design need to be in the hands of the API builders. That's why we've built a platform which is flexible enough to let developers craft the devex for their users. To that end, we've released customizable imports. You can now configure the structure of the import paths in your SDK, and how they are referenced by your users. As an example, for Typescript:

By default, our SDKs have created models in directories dictated by the OpenAPI spec, i.e. models/components, models/operations and models/errors. This is great for keeping your SDK organized, but it could be a bit verbose for your users, especially for less structured languages like Typescript and Python where global imports are the norm.


sdk/
β”œβ”€ models/
β”‚ β”œβ”€ components/
β”‚ β”‚ β”œβ”€ user.ts
β”‚ β”‚ β”œβ”€ drink.ts
β”‚ β”‚ └─ ...
β”‚ β”œβ”€ operations/
β”‚ β”‚ β”œβ”€ getuser.ts
β”‚ β”‚ β”œβ”€ updateuser.ts
β”‚ β”‚ β”œβ”€ getdrink.ts
β”‚ β”‚ β”œβ”€ updatedrink.ts
β”‚ β”‚ └─ ...
β”‚ └─ errors/
β”‚ β”œβ”€ sdkerror.ts
β”‚ β”œβ”€ responseerror.ts
β”‚ └─ ...
└─ ...

The import structure in this case would look like:


import { SDK } from '@speakeasy/bar'
import { User } from '@speakeasy/bar/dist/models/components/user'
import { GetDrinkRequest } from '@speakeasy/bar/dist/models/operations/user'

By default, our SDKs have created models in directories dictated by the OpenAPI spec, i.e. models/components, models/operations and models/errors. This is great for keeping your SDK organized, but it could be a bit verbose for your users, especially for less structured languages like Typescript and Python where global imports are the norm.

The import structure in this case would look like:


import { SDK } from '@speakeasy/bar'
import { User } from '@speakeasy/bar/dist/models/components/user'
import { GetDrinkRequest } from '@speakeasy/bar/dist/models/operations/user'


sdk/
β”œβ”€ models/
β”‚ β”œβ”€ components/
β”‚ β”‚ β”œβ”€ user.ts
β”‚ β”‚ β”œβ”€ drink.ts
β”‚ β”‚ └─ ...
β”‚ β”œβ”€ operations/
β”‚ β”‚ β”œβ”€ getuser.ts
β”‚ β”‚ β”œβ”€ updateuser.ts
β”‚ β”‚ β”œβ”€ getdrink.ts
β”‚ β”‚ β”œβ”€ updatedrink.ts
β”‚ β”‚ └─ ...
β”‚ └─ errors/
β”‚ β”œβ”€ sdkerror.ts
β”‚ β”œβ”€ responseerror.ts
β”‚ └─ ...
└─ ...

As an API producer, you can now configure your SDK to generate models into a single directory and import them from there. For Typescript, this would result in:


/
β”œβ”€ src
β”‚ β”œβ”€ models/
β”‚ β”‚ β”œβ”€ user.ts
β”‚ β”‚ β”œβ”€ drink.ts
β”‚ β”‚ β”œβ”€ getuser.ts
β”‚ β”‚ β”œβ”€ updateuser.ts
β”‚ β”‚ β”œβ”€ getdrink.ts
β”‚ β”‚ β”œβ”€ updatedrink.ts
β”‚ β”‚ β”œβ”€ sdkerror.ts
β”‚ β”‚ β”œβ”€ responseerror.ts
β”‚ β”‚ β”œβ”€ index.ts
β”‚ β”‚ └─ ...
β”‚ └─ ...
└─ ...

With an import structure that is flat and supports a global path as follows:


import { User, GetDrinkRequest, SDK } from '@speakeasy/bar'

As an API producer, you can now configure your SDK to generate models into a single directory and import them from there. For Typescript, this would result in:

With an import structure that is flat and supports a global path as follows:


import { User, GetDrinkRequest, SDK } from '@speakeasy/bar'


/
β”œβ”€ src
β”‚ β”œβ”€ models/
β”‚ β”‚ β”œβ”€ user.ts
β”‚ β”‚ β”œβ”€ drink.ts
β”‚ β”‚ β”œβ”€ getuser.ts
β”‚ β”‚ β”œβ”€ updateuser.ts
β”‚ β”‚ β”œβ”€ getdrink.ts
β”‚ β”‚ β”œβ”€ updatedrink.ts
β”‚ β”‚ β”œβ”€ sdkerror.ts
β”‚ β”‚ β”œβ”€ responseerror.ts
β”‚ β”‚ β”œβ”€ index.ts
β”‚ β”‚ └─ ...
β”‚ └─ ...
└─ ...

More documentation on how to configure this in your SDK's gen.yaml file can be found here.

🚒 Improvements and Bug Fixes πŸ›

Speakeasy v119.1 (opens in a new tab)

Terraform

🚒 Pulumi support for generated Terraform providers
🚒 Importing resources with Integer IDs
🚒 DataSources with no required arguments (Empty Datasources)
🚒 x-speakeasy-conflicts-with extension

Python

🚒 Oauth support for Python SDKs

Php

🚒 Support for customizable imports

Typescript

πŸ› Ensure contentType variable doesn't conflict with common parameter names
πŸ› Correctly handle x-www-form-urlencode in Typescript
🚒 unset baseURL on default axios client

Golang

πŸ› BigInt & Decimal type support within a Union type

Other:

🚒 Allow optional properties in usage snippets to be conditionally rendered
🚒 Support for customizing input/output model suffixes
🚒 Maintain OpenAPI Order in generated models
🚒 Automatic Authentication documentation generated in READMEs
🚒 Automatic Retry documentation generated in READMEs when retry extensions are used