Skip to content

[PRFC] OpenAPI Typings for Routers#15667

Merged
freben merged 61 commits into
backstage:masterfrom
sennyeya:express-openapi-test
Apr 6, 2023
Merged

[PRFC] OpenAPI Typings for Routers#15667
freben merged 61 commits into
backstage:masterfrom
sennyeya:express-openapi-test

Conversation

@sennyeya

@sennyeya sennyeya commented Jan 10, 2023

Copy link
Copy Markdown
Contributor

Signed-off-by: Aramis Sennyey sennyeya@amazon.com

Hey, I just made a Pull Request!

PoC work to create OpenAPI documentation and integration with express-openapi library using a schema-first approach. There are a few issues with this initial work that are pretty clear around dependent services being passed into the catalog and parsing input formats.

Update: Attempted to step back from express-openapi as typings are non-existent in the library. Their validators will be useful but the primary library has minimal use wrt developer experience. Found this repository https://github.com/varanauskas/oatx which handles automatic OpenAPI 3.x spec definitions in types and adapted it to a custom express router allowing for type-verified request bodies and response bodies. There will be more work to verify params and headers.
Basic workflow would be,

  1. Write/update OpenAPI spec file.
  2. Convert file to JSON and update/create typescript file to export the definition as const.
  3. Import definition from the TS file and use to type the router.

Demo: https://drive.google.com/file/d/1uE2fAE9kjTezELFqwS_NUwThbWxmbJJ8/view?usp=share_link.

✔️ Checklist

  • A changeset describing the change and affected packages. (more info)
  • Added or updated documentation
  • Tests for new functionality and regression tests for bug fixes
  • Screenshots attached (for UI changes)
  • All your commits have a Signed-off-by line in the message. (more info)

@github-actions github-actions Bot added the area:catalog Related to the Catalog Project Area label Jan 10, 2023
@backstage-goalie

backstage-goalie Bot commented Jan 10, 2023

Copy link
Copy Markdown
Contributor

Changed Packages

Package Name Package Path Changeset Bump Current Version
@backstage/backend-openapi-utils packages/backend-openapi-utils patch v0.0.0
@backstage/repo-tools packages/repo-tools minor v0.1.4-next.1
@backstage/plugin-catalog-backend plugins/catalog-backend patch v1.8.1-next.2

@taras

taras commented Jan 10, 2023

Copy link
Copy Markdown
Member

@sennyeya nice! Did you write that OpenApi Spec file?

@sennyeya

Copy link
Copy Markdown
Contributor Author

@taras Not for this iteration, I pulled the one I generated with #15038 and fixed a bunch of the invalid fields that it created. Wanted to highlight the issues with dynamic options before committing to writing a full spec.

@taras

taras commented Jan 11, 2023

Copy link
Copy Markdown
Member

What does validateDependency do?

A small nitpick - is it possible to keep the typescript checks the same if (refreshService) { ... } instead of refreshService!. It makes the diff look bigger than it is and makes the comparison more difficult.

Which of the tools do you think we can use to generate typescript types?

@sennyeya

Copy link
Copy Markdown
Contributor Author

is it possible to keep the typescript checks the same if (refreshService) { ... } instead of refreshService!. It makes the diff look bigger than it is and makes the comparison more difficult.

This is related to validateDependency. Because we can't use an if(refreshService) to remove routes, we would need to do if(refreshService) for each operation. validateDependency does this work and throws if refreshService is falsy.

I'm taking a look at the typescript type generation, I think I want to use quicktypes to generate from an OpenAPI schema, ie pulling the components list from an OpenAPI file. It'll add the types, for now duplicating as needed from catalog-model, but won't have any access/type safety with the API methods.

@taras

taras commented Jan 11, 2023

Copy link
Copy Markdown
Member

API methods are useful types, IMO. It's also not guaranteed that components will be used. I think we need a tool that generates types starting from the operations.

@sennyeya

Copy link
Copy Markdown
Contributor Author

Definitely agree API methods are useful typings. As a dev, I would definitely prefer typed request arguments as opposed to the casting/AJV validation we're currently doing manually.

However, I don't think schema-first is the right starting place for this PoC, especially as there are 7 distinct different schemas (plus however many permutations) we should be presenting, ie 1 when the catalog is in readonly mode and 6 for various dependent services being falsy when passed in. We could also make the schema as general as possible, ie have return values that include 404, but that seems overly broad and wouldn't help consumers make simple decisions like is the POST analyze-location or the DELETE /locations/:id available by just looking at the spec.

@freben

freben commented Jan 17, 2023

Copy link
Copy Markdown
Member

I would completely ignore the concept of optionality, it's just a distraction. Just make it the full interface no matter what. The server can choose to return 501 Not Implemented if they want to. And end users can choose to have an export type CatalogClient = Omit<OriginalCatalogClient, 'deleteLocation'> or whatnot.

@freben

freben commented Jan 17, 2023

Copy link
Copy Markdown
Member

or if it's possible to split apart the large catalog api into e.g. an entities part, a locations part etc (possibly separate schemas) and then generating stubs and types separately for them, and then by hand doing export type CatalogApi = EntitiesApi & LocationsApi & ...

@sennyeya

Copy link
Copy Markdown
Contributor Author

@freben Apologies for the late response. Had to think through this some more.

I think the question I should be asking is whether we want to generate an SDK that users can consume that's published to PyPI/Maven/other package managers that has all available methods or if we want adopters to generate an SDK from the CLI that they can distribute themselves? My thinking was towards the second option.

If the second option, then I think we'd want to consider optionality.

@github-actions

github-actions Bot commented Feb 2, 2023

Copy link
Copy Markdown
Contributor

This PR has been automatically marked as stale because it has not had recent activity from the author. It will be closed if no further activity occurs. If the PR was closed and you want it re-opened, let us know and we'll re-open the PR so that you can continue the contribution!

@github-actions github-actions Bot added the stale label Feb 2, 2023
@freben freben removed the stale label Feb 3, 2023
@Rugvip Rugvip requested a review from freben February 9, 2023 16:13
@github-actions

Copy link
Copy Markdown
Contributor

This PR has been automatically marked as stale because it has not had recent activity from the author. It will be closed if no further activity occurs. If the PR was closed and you want it re-opened, let us know and we'll re-open the PR so that you can continue the contribution!

@github-actions github-actions Bot added the stale label Feb 16, 2023
@sennyeya sennyeya force-pushed the express-openapi-test branch from 6d49081 to f423eb5 Compare February 16, 2023 21:34
@sennyeya sennyeya changed the title express-openapi Integration for catalog-backend [DX] OpenAPI Router Integration for catalog-backend Feb 16, 2023
@github-actions github-actions Bot removed the stale label Feb 16, 2023
@freben

freben commented Feb 22, 2023

Copy link
Copy Markdown
Member

As a hint, you generally don't need to do much with the yarn file by hand since it's auto generated and deduping should happen in precommit. Most commonly, especially when rebasing, I just checkout the yarn.lock file from its master state before the head of the branch (do not delete it outright!) and run yarn install, letting yarn rebuild it properly for me.

@sennyeya sennyeya force-pushed the express-openapi-test branch from d4d2390 to f307f37 Compare February 22, 2023 17:54
@github-actions

github-actions Bot commented Feb 22, 2023

Copy link
Copy Markdown
Contributor

Uffizzi Preview deployment-17137 was deleted.

content:
application/json:
schema:
$ref: '#/components/schemas/RefreshOptions'

@sennyeya sennyeya Feb 22, 2023

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Request bodies shouldn't generally be refs unless they are reused in multiple places. If they're reused they should be keyed to a requestBodies key under components not just in the components.schema key.

https://swagger.io/docs/specification/describing-request-body/

required:
- url
description: A link to external information that is related to the entity.
additionalProperties: false

@sennyeya sennyeya Feb 22, 2023

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

JSON Schema doesn't restrict additional properties by default, but I think this makes sense here as we're expecting types to match the defined interfaces and whatnot when interacting with express handlers. Without this, the json schema library we're using adds a [x: string]: unknown key to all objects it parses which causes type issues with interfaces.

This may have issues further down the line with validator middleware, but that's pretty speculative.

@sennyeya

Copy link
Copy Markdown
Contributor Author

@freben That took waaay more tries than I thought it would 😅 . Hopefully, it's in the right dedup'ed state now?

sennyeya and others added 22 commits April 5, 2023 14:42
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
@freben freben force-pushed the express-openapi-test branch from e16a64f to cd9296f Compare April 5, 2023 12:43
@freben

freben commented Apr 5, 2023

Copy link
Copy Markdown
Member

(just rebasing and ensuring that yarn.lock doesn't have conflicts)

@freben freben merged commit 7f0dd6f into backstage:master Apr 6, 2023
@github-actions

github-actions Bot commented Apr 6, 2023

Copy link
Copy Markdown
Contributor

Thank you for contributing to Backstage! The changes in this pull request will be part of the 1.13.0 release, scheduled for Tue, 18 Apr 2023.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:catalog Related to the Catalog Project Area

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants