Skip to content

Try swagger-typescript-api for real #535

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 22 commits into from
Dec 7, 2021
Merged

Try swagger-typescript-api for real #535

merged 22 commits into from
Dec 7, 2021

Conversation

david-crespo
Copy link
Collaborator

@david-crespo david-crespo commented Nov 28, 2021

As noted in #458, swagger-typescript-api generates much better types, especially for enums. Another benefit is that generated code is much smaller, so the core bundle (just our code, not vendor code) goes from 137 KB to 82 KB.

Another advantage is that it is trivial to point the generator to a custom set of templates, so we could easily take the default ones, check them in here, and tweak as needed. Overriding the templates in the official openapi-generator is a nightmare by comparison. It's also much easier to fork, as discussed below. No longer relevant, as I had to fork anyway.

Summary of changes

  • Swap out openapi-generator for swagger-typescript-api in scripts and swap out corresponding generated clients
  • Use patch-package'd swagger-typescript-api instead of the real one
  • Convert useApiQuery and friends to use the new client
  • Update mutate calls to use body instead of named params key, e.g.,
    - projectCreate: { name, description },
    + body: { name, description },
  • ProjectCreateForm and InstanceCreateForm take onSuccess callbacks so we can assert they get called in the tests
  • Wrap every use of a date field in new Date() because the format functions require a Date, and the new client doesn't parse them for us like the old one did

Bug made me fork

There appears to be a bug in the --extract-request-params flag, which pulls path and query params into a single params object. It doesn't seem to work for all request functions — there are a few that still have separate path param args. I'll indicate one with a comment. I figured out the cause (I think) and wrote up an issue on the STA repo here: acacode/swagger-typescript-api#322. It would be trivial to fix in the code. Unfortunately, it would be a lot of work to fix it in template overrides alone (which would be nice because we wouldn't have to fork the repo). I think it would be possible, but it would be a lot worse than forking because the logic would be all twisted.

Summary of changes in the fork

See patches/swagger-typescript-api+9.3.1.patch.

I forked STA at oxidecomputer/swagger-typescript-api with what seems like a fix for the bug, and f5aba99 shows the result of pointing to the fork: params are extracted into a single object like they're supposed to.

  • Fix the bug above — always extract query params (commit)
  • Hard-code module namespace for all routes to methods so they all go on a single api.methods object (relevant line of commit — commit has a bunch of formatting noise due to commit hook in repo)
  • Delete prepare script that runs all tests after cloning, which made yarn install take several minutes (commit)

Considerations

Cloning from GitHub is kind of annoying. Maybe we can try to get (acceptable versions of) our changes to the code merged so we can then only override the templates.

@vercel
Copy link

vercel bot commented Nov 28, 2021

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/oxidecomputer/console-ui-storybook/HfEKkHfQP39fjinzG7J2ysuMtdnh
✅ Preview: https://console-ui-storybook-git-swagger-typescript-api-oxidecomputer.vercel.app

@github-actions
Copy link
Contributor

Preview will be deployed at https://console-git-swagger-typescript-api.internal.oxide.computer

@@ -76,7 +76,7 @@ const ProjectsPage = () => {
</section>
<footer className="p-4 border-t border-gray-400 text-xs">
<span className="uppercase">
{formatDistanceToNowStrict(item.timeCreated, {
{formatDistanceToNowStrict(new Date(item.timeCreated), {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't parse dates by default. we fix this on the generator side by automatically parsing timeCreated and timeModified fields (or maybe any field that starts with time) as dates.

@david-crespo david-crespo temporarily deployed to Preview VM November 28, 2021 21:37 Inactive
* @request POST:/organizations/{organization_name}/projects
*/
organizationProjectsPost: (
organizationName: Name,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a bug to me. Compare to organizationProjectsGet, which puts the organizationName path param in an object. This is a problem for the way I want to extract the params arg type in the hook. I need there to be a fixed number of arguments. There might be a workaround but it would be gnarly.

| { instance: string; state: 'attached' }
| { instance: string; state: 'detaching' }
| { state: 'destroyed' }
| { state: 'faulted' }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is 👌 👌 👌


/**
* An IPv4 subnet, including prefix and subnet mask
* @pattern ^(10\.(25[0-5]|[1-2][0-4][0-9]|[1-9][0-9]|[0-9]\.){2}(25[0-5]|[1-2][0-4][0-9]|[1-9][0-9]|[0-9])/(1[0-9]|2[0-8]|[8-9]))$^(172\.16\.(25[0-5]|[1-2][0-4][0-9]|[1-9][0-9]|[0-9])\.(25[0-5]|[1-2][0-4][0-9]|[1-9][0-9]|[0-9])/(1[2-9]|2[0-8]))$^(192\.168\.(25[0-5]|[1-2][0-4][0-9]|[1-9][0-9]|[0-9])\.(25[0-5]|[1-2][0-4][0-9]|[1-9][0-9]|[0-9])/(1[6-9]|2[0-8]))$
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like we could easily tweak the templates to get this pattern in the code so we could use it for validation client-side

@david-crespo david-crespo force-pushed the swagger-typescript-api branch from c4f62ec to 057a7db Compare November 29, 2021 22:02
options?: UseMutationOptions<
Result<A[M]>,
ErrorResponse,
{ params: Params<A[M]>; body?: Body<A[M]> }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old way was more like Params<A[M]> & { body?: Body<A[M]> }, i.e., the path params are at top level and only body is under a key. That could work fine for us here. I'll probably try it just to make the diff smaller.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

confirmed a5a7816 takes a lot of noise out of the diff

@jessfraz jessfraz temporarily deployed to Preview VM December 6, 2021 21:52 Inactive
@david-crespo
Copy link
Collaborator Author

the stats look really bad, but if you exclude yarn.lock and the generated client it's surprisingly small

~/oxide/console $ git diff --stat main -- . ":(exclude)yarn.lock" ":(exclude)libs/api/__generated__/"
 .github/workflows/packer.yaml                               |  2 +-
 Dockerfile                                                  |  1 +
 app/components/InstancesTable.tsx                           | 10 ++---
 app/pages/LoginPage.tsx                                     | 11 ++---
 app/pages/ProjectCreatePage.tsx                             | 27 ++++++++----
 app/pages/ProjectsPage.tsx                                  |  2 +-
 app/pages/__tests__/InstanceCreateForm.spec.tsx             | 21 +++++++---
 app/pages/__tests__/ProjectCreatePage.spec.tsx              | 19 ++++++---
 app/pages/project/instances/create/InstancesCreatePage.tsx  | 19 +++++----
 app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx |  2 +-
 app/util/errors.spec.ts                                     | 29 +++++--------
 app/util/errors.ts                                          |  8 ++--
 libs/api-mocks/instance.ts                                  |  6 +--
 libs/api-mocks/org.ts                                       |  4 +-
 libs/api-mocks/project.ts                                   |  4 +-
 libs/api/__tests__/hooks.spec.tsx                           | 13 +++---
 libs/api/hooks.ts                                           | 77 +++++++++++++++++++---------------
 libs/api/index.ts                                           | 26 +++++++-----
 libs/api/instance-can.ts                                    |  2 +-
 libs/table/QueryTable.tsx                                   | 17 +++-----
 libs/table/cells/DateCell.tsx                               |  6 +--
 libs/table/cells/InstanceStatusCell.tsx                     |  2 +-
 package.json                                                |  1 +
 packer/oxapi_demo                                           | 27 ++++++++----
 packer/provision.sh                                         |  3 ++
 tools/create_gcp_instance.sh                                |  2 +-
 tools/generate_api_client.sh                                | 15 ++-----
 27 files changed, 196 insertions(+), 160 deletions(-)
 ``

const mock = fetchMock.post(instancesUrl, { status: 201, body: instance })

const projectPath = `/orgs/${org.name}/projects/${project.name}`
expect(window.location.pathname).not.toEqual(projectPath)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was never a good way to do this anyway, so I don't miss it. the problem was the location was being set by a previously run test, so this not equal was failing

API_VERSION: de84bb85d6ca264b1adddac9822e173aeb2b433a
API_VERSION: 038dbd04fbc292b5f7880c0d0c723ef182145cb2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I've seriously got to fix this. If the file isn't changed in the current PR, this shouldn't be ran. Why it happens anyway is a mystery to me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it watches the packer directory, which did have changes

<ProjectCreateForm orgName={orgName} />
<ProjectCreateForm
orgName={orgName}
onSuccess={(project) => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a function that takes the created project is a surprisingly natural interface here

@david-crespo david-crespo temporarily deployed to Preview VM December 6, 2021 22:46 Inactive
@david-crespo david-crespo marked this pull request as ready for review December 6, 2021 22:51
@jessfraz jessfraz temporarily deployed to Preview VM December 7, 2021 00:24 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants