Skip to content

Commit

Permalink
[IDP-1768] Add experimental MSW plugin (#23)
Browse files Browse the repository at this point in the history
* [IDP-1768] Add unstable MSW plugin

* Add docs

* Update docs, rename to experimental

* update debug

* PR comments

* fix broken test
  • Loading branch information
tjosepo authored Jul 31, 2024
1 parent 3c6be50 commit f2f35a7
Show file tree
Hide file tree
Showing 16 changed files with 846 additions and 294 deletions.
5 changes: 5 additions & 0 deletions .changeset/dull-cougars-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@workleap/create-schemas": minor
---

Add `experimental_openapiMSWPlugin`
5 changes: 5 additions & 0 deletions .changeset/new-bears-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@workleap/create-schemas": minor
---

[BREAKING] Rename `openapiFetchPlugin` to `experimental_openapiFetchPlugin`
4 changes: 2 additions & 2 deletions debug/create-schemas.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { defineConfig } from "@workleap/create-schemas";
import { openapiFetchPlugin } from "@workleap/create-schemas/plugins";
import { experimental_openapiFetchPlugin, experimental_openapiMSWPlugin } from "@workleap/create-schemas/plugins";

export default defineConfig({
input: "v1.yaml",
outdir: "src/codegen/v1",
plugins: [openapiFetchPlugin()]
plugins: [experimental_openapiFetchPlugin(), experimental_openapiMSWPlugin()]
});
3 changes: 2 additions & 1 deletion debug/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
},
"dependencies": {
"@workleap/create-schemas": "workspace:*",
"openapi-fetch": "^0.10.2"
"openapi-fetch": "0.10.2",
"openapi-msw": "0.7.0"
}
}
2 changes: 1 addition & 1 deletion debug/src/codegen/v1/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** Do not modify. This file has been generated by @workleap/create-schemas */
/** This file has been generated by @workleap/create-schemas (https://github.com/gsoft-inc/wl-openapi-typescript). Do not modify manually. */
import type { paths } from "./types.ts";
import _createClient from "openapi-fetch";

Expand Down
5 changes: 5 additions & 0 deletions debug/src/codegen/v1/openapi-msw.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** This file has been generated by @workleap/create-schemas (https://github.com/gsoft-inc/wl-openapi-typescript). Do not modify manually. */
import type { paths } from "./types.ts";
import { createOpenApiHttp } from "openapi-msw";

export const http = createOpenApiHttp<paths>();
2 changes: 1 addition & 1 deletion debug/src/codegen/v1/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** Do not modify. This file has been generated by @workleap/create-schemas */
/** This file has been generated by @workleap/create-schemas (https://github.com/gsoft-inc/wl-openapi-typescript). Do not modify manually. */
export interface paths {
"/good-vibes-points/{userId}": {
parameters: {
Expand Down
46 changes: 46 additions & 0 deletions docs/src/msw.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,52 @@ order: -6

# Mock Service Worker

## Type-safe Handlers

The [`experimental_openapiMSWPlugin` plugin](/using-plugins/#experimental_openapimswplugin) allows you to define MSW handlers in a type-safe way.

To use this client, you need to install the [`openapi-msw`](https://www.npmjs.com/package/openapi-msw) package:

+++ pnpm
```bash
pnpm add openapi-msw
```
+++ npm
```bash
npm install openapi-msw
```
+++ yarn
```bash
yarn add openapi-msw
```
+++

**Example usage:**

```ts #2,5 create-schemas.config.ts
import { defineConfig } from "@workleap/create-schemas";
import { experimental_openapiMSWPlugin } from "@workleap/create-schemas/plugins";

export default defineConfig({
plugins: [experimental_openapiMSWPlugin()]
input: "v1.yaml",
outdir: "codegen",
});
```

```ts #5-6
import { http } from "./codegen/openapi-msw.ts";

export const handlers = [
http.get("/good-vibes-points/{userId}", ({ response }) => {
return response(200).json({ pointx: 50 });
// ^^^^^^ Property "pointx" does not exist on type { points: number }
}),
];
```

## Auto-generated handlers

*Soon...*

See https://source.mswjs.io/
60 changes: 56 additions & 4 deletions docs/src/using-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default defineConfig({

## Built-in Plugins

### `openapiFetchPlugin`
### `experimental_openapiFetchPlugin`

**Description:**

Expand All @@ -53,10 +53,10 @@ yarn add openapi-fetch

```ts #2,5 create-schemas.config.ts
import { defineConfig } from "@workleap/create-schemas";
import { openapiFetchPlugin } from "@workleap/create-schemas/plugins";
import { experimental_openapiFetchPlugin } from "@workleap/create-schemas/plugins";

export default defineConfig({
plugins: [openapiFetchPlugin()]
plugins: [experimental_openapiFetchPlugin()]
input: "v1.yaml",
outdir: "codegen",
});
Expand All @@ -80,6 +80,58 @@ if (data?.point) {
}
```

### `experimental_openapiMSWPlugin`

!!!warning Warning

This plugin is currently marked as **experimental**. It may change at any time.

!!!

**Description:**

This plugins outputs a very thin wrapper over [openapi-msw](https://www.npmjs.com/package/openapi-msw). This package lets your define typed MSW handlers.

To use this client, you need to install the `openapi-msw` package:

+++ pnpm
```bash
pnpm add openapi-msw
```
+++ npm
```bash
npm install openapi-msw
```
+++ yarn
```bash
yarn add openapi-msw
```
+++

**Example usage:**

```ts #2,5 create-schemas.config.ts
import { defineConfig } from "@workleap/create-schemas";
import { experimental_openapiMSWPlugin } from "@workleap/create-schemas/plugins";

export default defineConfig({
plugins: [experimental_openapiMSWPlugin()]
input: "v1.yaml",
outdir: "codegen",
});
```

```ts #5-6
import { http } from "./codegen/openapi-msw.ts";

export const handlers = [
http.get("/good-vibes-points/{userId}", ({ response }) => {
return response(200).json({ pointx: 50 });
// ^^^^^^ Property "pointx" does not exist on type { points: number }
}),
];
```


## Creating a Plugin

Expand Down Expand Up @@ -122,7 +174,7 @@ export default defineConfig({
});
```

## Build hooks
### Build hooks

To interact with the code generation process, a plugin may include "hooks". Hooks are function that are are called at various stages of the generation. Hooks can affect how a build is run, add a file to the output, or modify a build once complete.

Expand Down
3 changes: 2 additions & 1 deletion packages/create-schemas/src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export type { Plugin } from "./plugin.ts";
export { openapiFetchPlugin } from "./openapi-fetch-plugin.ts";
export { experimental_openapiFetchPlugin } from "./openapi-fetch-plugin.ts";
export { experimental_openapiMSWPlugin } from "./openapi-msw-plugin.ts";
4 changes: 2 additions & 2 deletions packages/create-schemas/src/plugins/openapi-fetch-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Plugin } from "./plugin.ts";
import { getRelativeModuleResolutionExtension } from "../utils.ts";
import { openapiTypeScriptId } from "./openapi-typescript-plugin.ts";

export function openapiFetchPlugin(): Plugin {
export function experimental_openapiFetchPlugin(): Plugin {
return {
name: "openapi-fetch-plugin",
async transform({ id, emitFile }) {
Expand All @@ -16,7 +16,7 @@ export function openapiFetchPlugin(): Plugin {
filename: "client.ts",
code: [
`import type { paths } from "./types${importsFileExtension}";`,
"import _createClient from \"openapi-fetch\";",
"import _createClient from \"openapi-fetch\";\n",
"export const createClient = _createClient as typeof _createClient<paths, \"application/json\">;"
].join("\n")
});
Expand Down
25 changes: 25 additions & 0 deletions packages/create-schemas/src/plugins/openapi-msw-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getRelativeModuleResolutionExtension } from "../utils.ts";
import { openapiTypeScriptId } from "./openapi-typescript-plugin.ts";
import type { Plugin } from "./plugin.ts";

export function experimental_openapiMSWPlugin(): Plugin {
return {
name: "openapi-msw-plugin",
async transform({ id, emitFile }) {
if (id !== openapiTypeScriptId) {
return;
}

const importsFileExtension = getRelativeModuleResolutionExtension();

emitFile({
filename: "openapi-msw.ts",
code: [
`import type { paths } from "./types${importsFileExtension}";`,
"import { createOpenApiHttp } from \"openapi-msw\";\n",
"export const http = createOpenApiHttp<paths>();"
].join("\n")
});
}
};
}
4 changes: 2 additions & 2 deletions packages/create-schemas/tests/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ describe.concurrent("e2e", () => {
const tempFolder = await createTemporaryFolder({ onTestFinished: onTestFinished });

const configFile = `
import { openapiFetchPlugin } from "../../../src/plugins";
import { experimental_openapiFetchPlugin } from "../../../src/plugins";
export default { plugins: [openapiFetchPlugin()] };
export default { plugins: [experimental_openapiFetchPlugin()] };
`;

await writeFile(join(tempFolder, "create-schemas.config.ts"), configFile);
Expand Down
4 changes: 2 additions & 2 deletions packages/create-schemas/tests/generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { resolveConfig } from "../src/config.ts";
import { join } from "path";
import { dataFolder } from "./fixtures.ts";
import { openapiTypeScriptFilename } from "../src/plugins/openapi-typescript-plugin.ts";
import { openapiFetchPlugin } from "../src/plugins/openapi-fetch-plugin.ts";
import { experimental_openapiFetchPlugin } from "../src/plugins/openapi-fetch-plugin.ts";

describe.concurrent("generate", () => {
test("sanitize names", async ({ expect }) => {
Expand Down Expand Up @@ -46,7 +46,7 @@ describe.concurrent("generate", () => {
test("generate client", async ({ expect }) => {
const { files } = await generate(await resolveConfig({
input: join(dataFolder, "todo.json"),
plugins: [openapiFetchPlugin()]
plugins: [experimental_openapiFetchPlugin()]
}));

const clientFile = files.find(file => file.filename === "client.ts");
Expand Down
86 changes: 83 additions & 3 deletions packages/create-schemas/tests/plugins.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@

import { assert, describe, expect, test } from "vitest";
import { assert, describe, test } from "vitest";
import { headerPlugin } from "../src/plugins/header-plugin.ts";
import { typesPlugin } from "../src/plugins/types-plugin.ts";
import { openapiTypeScriptId, openapiTypeScriptFilename } from "../src/plugins/openapi-typescript-plugin.ts";
import { resolveConfig } from "../src/config.ts";
import { experimental_openapiMSWPlugin } from "../src/plugins/openapi-msw-plugin.ts";
import { experimental_openapiFetchPlugin } from "../src/plugins/openapi-fetch-plugin.ts";

describe.concurrent("plugins", () => {
test("headerPlugin", async() => {
test("headerPlugin", async({ expect }) => {
const plugin = headerPlugin({ header: "This is a header" });

assert(plugin.transform);
Expand All @@ -27,7 +29,7 @@ describe.concurrent("plugins", () => {
`);
});

test("typesPlugin", async() => {
test("typesPlugin", async ({ expect }) => {
const plugin = typesPlugin();

assert(plugin.transform);
Expand Down Expand Up @@ -69,4 +71,82 @@ describe.concurrent("plugins", () => {
"
`);
});

test("openapiFetchPlugin generates a file when openapi typescript file generated", async ({ expect }) => {
const plugin = experimental_openapiFetchPlugin();

assert(plugin.transform);

let emittedFile: { filename: string; code: string } | undefined;
function emitFile(file: { filename: string; code: string }) {
emittedFile = file;
}

await plugin.transform({
config: await resolveConfig({ input: "openapi.json" }),
id: openapiTypeScriptId,
code: "export interface paths {}",
filename: openapiTypeScriptFilename,
emitFile
});

assert(emittedFile);

expect(emittedFile.filename).toBe("client.ts");
expect(emittedFile.code).toMatchInlineSnapshot(`
"import type { paths } from "./types.ts";
import _createClient from "openapi-fetch";
export const createClient = _createClient as typeof _createClient<paths, "application/json">;"
`);
});

test("openapiMSWPlugin generates a file when openapi typescript file generated", async ({ expect }) => {
const plugin = experimental_openapiMSWPlugin();

assert(plugin.transform);

let emittedFile: { filename: string; code: string } | undefined;
function emitFile(file: { filename: string; code: string }) {
emittedFile = file;
}

await plugin.transform({
config: await resolveConfig({ input: "openapi.json" }),
id: openapiTypeScriptId,
code: "export interface paths {}",
filename: openapiTypeScriptFilename,
emitFile
});

assert(emittedFile);

expect(emittedFile.filename).toBe("openapi-msw.ts");
expect(emittedFile.code).toMatchInlineSnapshot(`
"import type { paths } from "./types.ts";
import { createOpenApiHttp } from "openapi-msw";
export const http = createOpenApiHttp<paths>();"
`);
});

test("openapiMSWPlugin doesn't generates a file when openapi typescript file missing", async ({ expect }) => {
const plugin = experimental_openapiMSWPlugin();

assert(plugin.transform);

let emittedFile: { filename: string; code: string } | undefined;
function emitFile(file: { filename: string; code: string }) {
emittedFile = file;
}

await plugin.transform({
config: await resolveConfig({ input: "openapi.json" }),
code: "export interface paths {}",
filename: openapiTypeScriptFilename,
emitFile
});

expect(emittedFile).toBeUndefined();
});
});
Loading

0 comments on commit f2f35a7

Please sign in to comment.