Conversation
|
|
|
Error agent completed without reporting progress |
|
@malcolm-kee is attempting to deploy a commit to the Hey API Team on Vercel. A member of the Team first needs to authorize it. |
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3570 +/- ##
==========================================
- Coverage 39.08% 38.44% -0.65%
==========================================
Files 495 511 +16
Lines 18451 19540 +1089
Branches 5479 5907 +428
==========================================
+ Hits 7212 7512 +300
- Misses 9071 9694 +623
- Partials 2168 2334 +166
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
7a46406 to
bc978a9
Compare
|
@malcolm-kee Before I go into it, two questions:
|
bc978a9 to
dedf542
Compare
|
@mrlubos I come up with the API design and AI was doing most of the implementations while I watch. Not final. I'm happy to iterate on this, just want some progress on this plugin. |
|
The diff is big is mostly because of the tests and snapshots. |
dedf542 to
c7133e3
Compare
@hey-api/codegen-core
@hey-api/json-schema-ref-parser
@hey-api/nuxt
@hey-api/openapi-ts
@hey-api/shared
@hey-api/types
@hey-api/vite-plugin
commit: |
c7133e3 to
b119b40
Compare
| getFoo: (resultOrResolver = { | ||
| firstName: 'Marry', | ||
| lastName: 'Jane', | ||
| age: 30 | ||
| }) => http.get(toMswPath('/foo', baseUrl), typeof resultOrResolver === 'function' ? resultOrResolver : () => HttpResponse.json(resultOrResolver ?? null)), |
There was a problem hiding this comment.
To make the DX better, the plugin will infer the value if example is defined in the spec.
|
I did some refactoring/enhancements:
|
|
@mrlubos I manually validated the code and made some refactoring. It would be great if you can provide some feedbacks, especially on the API. |
ac3c300 to
b34b5bd
Compare
|
More revision:
|
b34b5bd to
9b8e509
Compare
|
Added |
9b8e509 to
1963185
Compare
|
Change plugin options from |
e3e18f0 to
7bb2ae4
Compare
|
Ideas on how to continue enhancing this PR, in case anyone want to take over this, since I might not be free to iterate on this: Implement
|
|
@malcolm-kee I'm really not the best person to comment yet! I'll see what @kettanaito thinks about the output first, and I can provide input on the plugin architecture after. I'll try to make that happen this week |
|
Hi 👋 I think the generated file looks okay to me. Otherwise, this is quite a lot of abstractions for me to provide any meaningful judgement. As I understand, developers already have some sort of API spec and now they can generate handlers for MSW out of it. If that's the case, I think this is fine! We have some official generators at @msw/source and I highly recommend checking it out to see how we are approaching the handler generation out of OpenAPI specs. Might be it helps here, too. Keep up the good work and thank you! |
|
@malcolm-kee Which prior art did you consider when designing this plugin? In other words, how did you come up with the |
|
@mrlubos that's a good question. I think I was influenced by some Java library that I've been reading recently, which I think is not a good reference. 😆 I'm happy to change to other APIs. |
7d6b2f4 to
e337675
Compare
Implemented |
@malcolm-kee Have you used any other generators in the past? Did you write MSW by hand? What would be helpful is noting what works and what doesn't work in other solutions. I'm going to play with this pull request as well and compare. More context: |
|
I did write MSW by hand, and that's why I actually implemented this API for a private project a while ago, by parsing the OpenAPI spec and use ts-morph to read the output file of hey-api/typescript. That code was pretty ugly because I also did some logic to generate fake data. Is there any chance we can publish this as experimental or with |
|
I didn't try others as they wasn't what I really wanted, because the pain point wasn't just about the fake data, it was also the need to look at the implementation details of each API call code generated by hey api, and translate it to how to define them for MSW. |
mrlubos
left a comment
There was a problem hiding this comment.
@malcolm-kee couple smaller ones
| }, | ||
| "peerDependencies": { | ||
| "typescript": ">=5.5.3 || 6.0.1-rc" | ||
| "msw": "^2", |
There was a problem hiding this comment.
@malcolm-kee why add msw here? what does it do? it would be effectively the first plugin peer dependency
| * | ||
| * @default ['example'] | ||
| */ | ||
| valueSources?: Array<'example'>; |
There was a problem hiding this comment.
Readonly probably safer here?
| valueSources?: Array<'example'>; | |
| valueSources?: ReadonlyArray<'example'>; |
| valueSources?: Array<'example'>; | ||
| }; | ||
|
|
||
| export type MswPlugin = DefinePlugin<UserConfig, UserConfig>; |
| export const defaultConfig: MswPlugin['Config'] = { | ||
| config: { | ||
| includeInEntry: false, | ||
| valueSources: ['example'], |
There was a problem hiding this comment.
@malcolm-kee what if someone were to supply an empty array?
Yes. I won't do another release for a while but after it's in main it will be under next |
@malcolm-kee can you show me an example or explain more? In the past I'd always tell people they can use another generator for MSW with Hey API for the rest. Are you saying that's not possible/painful? |
|
My two cents: I find the |
I did some homework, here is what I found: OrvalThis is actually very similar to what we have now, the API is very similar. But it doesn't make sense to use it in conjunction of hey-api, since Orval is like a replacement of Hey API. import { HttpResponse, delay, http } from 'msw';
import type { RequestHandlerOptions } from 'msw';
export const getShowPetByIdMockHandler = (
overrideResponse?:
| Pet
| ((info: Parameters<Parameters<typeof http.get>[1]>[0]) => Promise<Pet> | Pet),
options?: RequestHandlerOptions,
) => {
return http.get('*/pets/:petId', async (info) => {
await delay(1000);
return HttpResponse.json(
overrideResponse !== undefined
? typeof overrideResponse === 'function'
? await overrideResponse(info)
: overrideResponse
: getShowPetByIdResponseMock(),
{ status: 200 },
);
}, options);
};
|
|
There are a few weaknesses with the Orval supports:
but I like the idea of using I think @kettanaito point is valid, so I think we could do import { setupServer } from "msw/node";
import { createMswHandlerFactory } from "./msw.gen";
const mocks = createMswHandlerFactory({})
const server = setupServer(
...mocks.getAllMocks(),
mocks.getPetByIdMock({ status: 200, result: { id: 1, name: "Fido", photoUrls: [] } }),
);The naming clashing is avoided with
In fact, to be consistent with other client code, import { setupServer } from "msw/node";
import { getAllMocks, getPetByIdMock } from "./msw.gen";
const server = setupServer(
...getAllMocks(),
getPetByIdMock({ status: 200, result: { id: 1, name: "Fido", photoUrls: [] } }),
);Those default mock factory would be binded with |

Closes #1486
Summary
Implement
mswplugin that generates amsw.gen.tsfile with type-safe mock handler factories from OpenAPI specs. Includes individual handler creators (of) and a bulk helper (ofAll) to generate handlers for all operations at once.Important
Even though many expect fake data generation is part of this plugin, that probably overlaps with faker plugin. The only mock data handled by this plugin at the moment is the
exampledefined in the OpenAPI spec.API Design
Configuration
Usage
Design decisions
Why
createMock.of.x()instead ofcreateMock.x()? — Theofnamespace keeps individual handler creators andofAllas sibling properties without naming collisions.Why
valueSourcesinstead ofexample: boolean? — Extensible for future sources (e.g.['example', 'faker']when faker plugin is ready).onMissingMock— Operations that require a response argument (no default example) are either skipped ('skip') or return a 501 ('error'). Overrides always take precedence.Handler creator signatures
ToResponseUnion<Responses> | HttpResponseResolver<PathParams, Body>ToResponseUnion<Responses> | HttpResponseResolver<PathParams, Body>HttpResponseResolver<PathParams, Body>* Optional if the spec defines an
examplefor the dominant response.Response method selection
application/jsonHttpResponse.json()text/*HttpResponse.text()binary/octet-streamnew HttpResponse()new HttpResponse(null)When multiple 2xx responses exist, the dominant one is chosen by priority: json > text > binary > void.
Known limitations
HttpResponseResolverto avoid MSW'sDefaultBodyTypeconstraint issues with union/void response types