feat: completely overhauling how we're handling response data
| đĨ Resolves #536 |
|---|
This completely refactors how we're handling returning data to always return an object containing response data, response status, response headers, and the raw Response object. This is obviously a breaking change but by doing this we can not only make it very easy to access data out of the response (something you couldn't do before), but also remove the overly complicated parseResponse config option.
Here's what a petstore request looked like originally where you needed to access the response status and data:
const petstore = require('@api/petstore');
petstore.config({ parseResponse: false });
await petstore
.listPets()
.then(async (res) => {
const data = await res.json();
console.log(`[${res.status}] and my pets name is ${data[0].name}!`);
});
And now:
const petstore = require('@api/petstore');
const { data, status, headers, res } = await petstore.listPets();
console.log(`[${status}] and my pets name is ${data[0].name}!`);
Less mess to access data you might need? â
Cleaner code? â
â ? â
Additionally now with this data schema we're also now able to type the HTTP statuses that an operation might return, as seen in this ugly video I recorded:
https://user-images.githubusercontent.com/33762/196828483-3d561b9c-9dac-4725-ab7d-9bcd89123180.mov
And beyond that, we're now also able to do the same for responses that hit HTTP code that falls between 400 and 599 with the introduction of a new FetchError error handler! It contains all the same data as a regular request does: data, status, headers, and res.
await petstore
.deletePet({ id: petId })
.catch(({ status, data }) => {
// status = 404
// data = 'Not Found'
});
Or if you'd like to check for something other than FetchError:
await petstore
.deletePet({ id: petId })
.catch(err => {
if (!(err instanceof FetchError)) {
throw err;
}
// err.status = 404
// err.data = 'Not Found'
});
âšī¸ Removing HTTP method generics
Due to a lot of complexities and drawbacks with typing our typed HTTP method generic accessor overloads, all SDKs codegen'd with api or used dynamically will no longer support being able to make a request with a generic HTTP method (i.e. sdk.delete('/pet/1234')).
The reason behind this is that it it's incredibly difficult to coerce TypeScript into supporting a typed sdk.get('/pet/1234', { ...metadata... }) method overload while at the same time also supporting a generic sdk.get('/any/path') type. Not only does TypeScript in this case think that you're trying to use the /pet/1234 accessor, and asks you to supply metadata for a request that probably doesn't need it, but once you do add metadata for that request, TypeScript then fails and says that /any/path doesnt match /pet/1234.
Beyond this, it's also incredibly difficult to support these types of generic accessors with regard to how we handle path parameters. You would expect a sdk.get('/pet/1234') request for the /pet/{id} request to work just fine but because the id parameter is a required path parameter (all path parameters are), if you executed only sdk.get('/pet/1234'), TypeScript would complain about the required metadata type containing id not being present. At that point if you're doing sdk.get('/pet/1234', { id: 1234 }) or sdk.get('/pet/{petId}, { id: 1234 }) you're really better off using sdk.getPet({ id: 1234 }) instead.
Beyond that, these generic HTTP method accessors aren't surfaced in the code snippets we generate and the only way to know about them is to read the docs as they aren't mentioned anywhere else.
So all this said, we're removing the generic sdk[http method]() accessors from all forms of api. All API integrations with this library will now be much more deliberate and codegen'd SDKs will have significantly less bloat.
We may add a generic sdk.fetch() method at some point down the line that wraps the fetch API, but honestly if you need to access an endpoint that doesn't exist in the OpenAPI definition you're using this library probably isn't for you (or at for that use-case).
đ§° Changes
- [ ] Refactor how we're handling data that we return from operation calls into a new shape that contains the parsed data (from
res.json()orres.text()), the HTTP status code, response headers, and the raw Response object that we got back from thefetchcall. - [ ] Improve the TypeScript typing on operations in codegen to allow us to individually type each response shape with the documented HTTP code it's a part of.
- [ ] Remove generic HTTP method accessors from all forms of the library: dynamic and codegen.
- [ ] Running
descriptionandsummarydata that we add into docblocks during codegen into our word wrapper utility. - [ ] Updated snippet generation to surface this new request flow.
đ§Ŧ QA & Testing
There's a lot going on here but it's all covered by the test suite.
I'm more than happy to walk through any parts if you've got questions.