Skip to content

Minor docs updates #1641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions docs/6.x/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ And the magic that produces this would live in a `test/utils.ts` file that can b
<details>
<summary>📄 <strong>test/utils.ts</strong></summary>

::: code-group [test/utils.ts]
::: code-group

```ts
```ts [test/utils.ts]
import type { paths } from "./api/v1"; // generated by openapi-typescript

// Settings
Expand Down Expand Up @@ -123,7 +123,7 @@ export function mockResponses(responses: {
fetchMock.mockResponse((req) => {
const mockedPath = findPath(
req.url.replace(BASE_URL, ""),
Object.keys(responses),
Object.keys(responses)
)!;
// note: we get lazy with the types here, because the inference is bad anyway and this has a `void` return signature. The important bit is the parameter signature.
if (!mockedPath || (!responses as any)[mockedPath])
Expand All @@ -142,11 +142,11 @@ export function mockResponses(responses: {
// helper function that matches a realistic URL (/users/123) to an OpenAPI path (/users/{user_id}
export function findPath(
actual: string,
testPaths: string[],
testPaths: string[]
): string | undefined {
const url = new URL(
actual,
actual.startsWith("http") ? undefined : "http://testapi.com",
actual.startsWith("http") ? undefined : "http://testapi.com"
);
const actualParts = url.pathname.split("/");
for (const p of testPaths) {
Expand Down
60 changes: 50 additions & 10 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ Note that debug messages will be suppressed if the output is `stdout`.

Example:

```yaml
::: code-group

```yaml [my-openapi-3-schema.yaml]
ErrorCode:
type: integer
format: int32
Expand All @@ -47,9 +49,13 @@ ErrorCode:
- "Something went wrong"
```

:::

Will result in:

```ts
::: code-group

```ts [my-openapi-3-schema.d.ts]
enum ErrorCode {
// User is not authorized
Unauthorized = 100
Expand All @@ -60,6 +66,8 @@ enum ErrorCode {
}
```

:::

Alternatively you can use `x-enumNames` and `x-enumDescriptions` ([NSwag/NJsonSchema](https://github.com/RicoSuter/NJsonSchema/wiki/Enums#enum-names-and-descriptions)).

## Styleguide
Expand Down Expand Up @@ -274,7 +282,9 @@ OpenAPI’s composition tools (`oneOf`/`anyOf`/`allOf`) are powerful tools for r

#### ❌ Bad

```yaml
::: code-group

```yaml [my-openapi-3-schema.yaml]
Pet:
type: object
properties:
Expand All @@ -296,19 +306,27 @@ Pet:
- $ref: "#/components/schemas/Turtle"
```

:::

This generates the following type which mixes both TypeScript unions and intersections. While this is valid TypeScript, it’s complex, and inference may not work as you intended. But the biggest offense is TypeScript can’t discriminate via the `type` property:

```ts
::: code-group

```ts [my-openapi-3-schema.d.ts]
Pet: ({
/** @enum {string} */
type?: "cat" | "dog" | "rabbit" | "snake" | "turtle";
name?: string;
}) & (components["schemas"]["Cat"] | components["schemas"]["Dog"] | components["schemas"]["Rabbit"] | components["schemas"]["Snake"] | components["schemas"]["Turtle"]);
```

:::

#### ✅ Better

```yaml
::: code-group

```yaml [my-openapi-3-schema.yaml]
Pet:
oneOf:
- $ref: "#/components/schemas/Cat"
Expand All @@ -330,13 +348,19 @@ Cat:
- cat
```

:::

The resulting generated types are not only simpler; TypeScript can now discriminate using `type` (notice `Cat` has `type` with a single enum value of `"cat"`).

```ts
::: code-group

```ts [my-openapi-3-schema.d.ts]
Pet: components["schemas"]["Cat"] | components["schemas"]["Dog"] | components["schemas"]["Rabbit"] | components["schemas"]["Snake"] | components["schemas"]["Turtle"];
Cat: { type?: "cat"; } & components["schemas"]["PetCommonProperties"];
```

:::

_Note: you optionally could provide `discriminator.propertyName: "type"` on `Pet` ([docs](https://spec.openapis.org/oas/v3.1.0#discriminator-object)) to automatically generate the `type` key, but is less explicit._

While the schema permits you to use composition in any way you like, it’s good to always take a look at the generated types and see if there’s a simpler way to express your unions & intersections. Limiting the use of `oneOf` is not the only way to do that, but often yields the greatest benefits.
Expand All @@ -345,7 +369,9 @@ While the schema permits you to use composition in any way you like, it’s good

[JSONSchema $defs](https://json-schema.org/understanding-json-schema/structuring.html#defs) can be used to provide sub-schema definitions anywhere. However, these won’t always convert cleanly to TypeScript. For example, this works:

```yaml
::: code-group

```yaml [my-openapi-3-schema.yaml]
components:
schemas:
DefType:
Expand All @@ -360,9 +386,13 @@ components:
$ref: "#/components/schemas/DefType/$defs/myDefType"
```

:::

This will transform into the following TypeScript:

```ts
::: code-group

```ts [my-openapi-3-schema.d.ts]
export interface components {
schemas: {
DefType: {
Expand All @@ -377,9 +407,13 @@ export interface components {
}
```

:::

However, this won’t:

```yaml
::: code-group

```yaml [my-openapi-3-schema.yaml]
components:
schemas:
DefType:
Expand All @@ -393,9 +427,13 @@ components:
$ref: "#/components/schemas/DefType/$defs/myDefType"
```

:::

Because it will transform into:

```ts
::: code-group

```ts [my-openapi-3-schema.d.ts]
export interface components {
schemas: {
DefType: string;
Expand All @@ -406,6 +444,8 @@ export interface components {
}
```

:::

So be wary about where you define `$defs` as they may go missing in your final generated types.

::: tip
Expand Down
58 changes: 43 additions & 15 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ npx openapi-typescript https://petstore3.swagger.io/api/v3/openapi.yaml -o petst

To transform multiple schemas, create a `redocly.yaml` file in the root of your project with [APIs defined](https://redocly.com/docs/cli/configuration/). Under `apis`, give each schema a unique name and optionally a version (the name doesn’t matter, so long as it’s unique). Set the `root` value to your schema’s entry point—this will act as the main input. For the output, set it with `x-openapi-ts.output`:

```yaml
::: code-group

```yaml [redocly.yaml]
apis:
core@v2:
root: ./openapi/openapi.yaml
Expand All @@ -41,6 +43,8 @@ apis:
output: ./openapi/external.ts
```

:::

::: tip

This will preserve schemas 1:1 input:output. To bundle multiple schemas into one, use Redocly’s [bundle command](https://redocly.com/docs/resources/multi-file-definitions/#bundle)
Expand Down Expand Up @@ -73,7 +77,9 @@ You can read more about the Redoc’s configuration options [in their docs](http

Authentication for non-public schemas is handled in your [Redocly config](https://redocly.com/docs/cli/configuration/#resolve-non-public-or-non-remote-urls). You can add headers and basic authentication like so:

```yaml
::: code-group

```yaml [redocly.yaml]
resolve:
http:
headers:
Expand All @@ -85,6 +91,8 @@ resolve:
envVariable: SECRET_AUTH
```

:::

Refer to the [Redocly docs](https://redocly.com/docs/cli/configuration/#resolve-non-public-or-non-remote-urls) for additional options.

## Flags
Expand Down Expand Up @@ -113,30 +121,42 @@ The following flags are supported in the CLI:

By default, your URLs are preserved exactly as-written in your schema:

```ts
::: code-group

```ts [my-openapi-3-schema.d.ts]
export interface paths {
"/user/{user_id}": components["schemas"]["User"];
}
```

:::

Which means your type lookups also have to match the exact URL:

```ts
import type { paths } from "./api/v1";
::: code-group

```ts [src/my-project.ts]
import type { paths } from "./my-openapi-3-schema";

const url = `/user/${id}`;
type UserResponses = paths["/user/{user_id}"]["responses"];
```

:::

But when `--path-params-as-types` is enabled, you can take advantage of dynamic lookups like so:

```ts
import type { paths } from "./api/v1";
::: code-group

```ts [src/my-project.ts]
import type { paths } from "./my-openapi-3-schema";

const url = `/user/${id}`;
type UserResponses = paths[url]["responses"]; // automatically matches `paths['/user/{user_id}']`
```

:::

Though this is a contrived example, you could use this feature to automatically infer typing based on the URL in a fetch client or in some other useful place in your application.

_Thanks, [@Powell-v2](https://github.com/Powell-v2)!_
Expand All @@ -147,7 +167,9 @@ This option is useful for generating tuples if an array type specifies `minItems

For example, given the following schema:

```yaml
::: code-group

```yaml [my-openapi-3-schema.yaml]
components:
schemas:
TupleType
Expand All @@ -158,17 +180,23 @@ components:
maxItems: 2
```

:::

Enabling `--array-length` would change the typing like so:

```diff
export interface components {
schemas: {
- TupleType: string[];
+ TupleType: [string] | [string, string];
};
}
::: code-group

```ts [my-openapi-3-schema.d.ts]
export interface components {
schemas: {
TupleType: string[]; // [!code --]
TupleType: [string] | [string, string]; // [!code ++]
};
}
```

:::

This results in more explicit typechecking of array lengths.

_Note: this has a reasonable limit, so for example `maxItems: 100` would simply flatten back down to `string[];`_
Expand Down
24 changes: 14 additions & 10 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ A good fetch wrapper should **never use generics.** Generics require more typing

After [generating your types using the CLI](/introduction), pass in the proper `paths` response for each endpoint:

```ts
::: code-group

```ts [src/my-project.ts]
import { Hono } from "hono";
import { components, paths } from "./path/to/my/types";
import { components, paths } from "./my-openapi-3-schema"; // generated by openapi-typescript

const app = new Hono();

Expand All @@ -50,6 +52,8 @@ app.get("/users", async (ctx) => {
export default app;
```

:::

::: tip

TypeChecking in server environments can be tricky, as you’re often querying databases and talking to other endpoints that TypeScript can’t introspect. But using generics will alert you of the obvious errors that TypeScript _can_ catch (and more things in your stack may have types than you realize!).
Expand Down Expand Up @@ -86,9 +90,9 @@ Let’s say we want to write our mocks in the following object structure, so we

Using our generated types we can then infer **the correct data shape** for any given path + HTTP method + status code. An example test would look like this:

::: code-group [my-test.test.ts]
::: code-group

```ts
```ts [my-test.test.ts]
import { mockResponses } from "../test/utils";

describe("My API test", () => {
Expand Down Expand Up @@ -133,10 +137,10 @@ And the magic that produces this would live in a `test/utils.ts` file that can b
<details>
<summary>📄 <strong>test/utils.ts</strong></summary>

::: code-group [test/utils.ts]
::: code-group

```ts
import type { paths } from "./api/v1"; // generated by openapi-typescript
```ts [test/utils.ts]
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript

// Settings
// ⚠️ Important: change this! This prefixes all URLs
Expand Down Expand Up @@ -171,7 +175,7 @@ export function mockResponses(responses: {
fetchMock.mockResponse((req) => {
const mockedPath = findPath(
req.url.replace(BASE_URL, ""),
Object.keys(responses),
Object.keys(responses)
)!;
// note: we get lazy with the types here, because the inference is bad anyway and this has a `void` return signature. The important bit is the parameter signature.
if (!mockedPath || (!responses as any)[mockedPath])
Expand All @@ -190,11 +194,11 @@ export function mockResponses(responses: {
// helper function that matches a realistic URL (/users/123) to an OpenAPI path (/users/{user_id}
export function findPath(
actual: string,
testPaths: string[],
testPaths: string[]
): string | undefined {
const url = new URL(
actual,
actual.startsWith("http") ? undefined : "http://testapi.com",
actual.startsWith("http") ? undefined : "http://testapi.com"
);
const actualParts = url.pathname.split("/");
for (const p of testPaths) {
Expand Down
Loading
Loading