Skip to content
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

swagger-to-ts 2.0: better type generation #178

Merged
merged 2 commits into from
Apr 5, 2020
Merged

swagger-to-ts 2.0: better type generation #178

merged 2 commits into from
Apr 5, 2020

Conversation

drwpow
Copy link
Contributor

@drwpow drwpow commented Apr 4, 2020

In thinking about support for OpenAPI3, it became clear that this project’s code quality needed to improve. The first iteration was made to solve a work problem quickly and cheaply, resulting in sort of a spaghetti mess that worked for us, but broke for more complicated schemas. With more and more people depending on this and needing OpenAPI3, some changes are needed.

A major inspiration for this change is a TS genration library I rely on almost daily: GraphQL Code Generator. If you’ve used that library, this change will feel familiar to you.

Changing the generated types would break integrations for most, so this would mean a 2.0 release of swagger-to-ts. But this change is a better direction for the project overall, because it more closely matches your schema, the source of truth. I wrote a new README section explaining some of the reasoning (below), but here’s a TL;DR:

  • camelCasing/PascalCasing/interface generation was all removed; v2 preserves all your Swagger namespaces and all your $refs, without changing or inventing any
  • better generation for deeply-nested schemas
  • 25% less code in this project, while supporting more schema types
  • better support for allOf and additionalProperties
  • this paves the way for v3 with few changes

To try this version yourself:

npx @manifoldco/swagger-to-ts@alpha input.yaml -o output.ts

New README section:

Upgrading from v1 to v2

Some options were removed in v2 that will break existing setups, but don’t worry—it actually gives
you more control, and it generates more resilient types.

Explanation

In order to explain the change, let’s go through an example with the following Swagger definition
(partial):

swagger: 2.0
definitions:
  user:
    type: object
    properties:
      role:
        type: object
        properties:
          access:
            enum:
              - admin
              - user
  user_role:
    type: object
      role:
        type: string
  team:
    type: object
    properties:
      users:
        type: array
        items:
          $ref: user

This is how v1 would have generated those types:

declare namespace OpenAPI2 {
  export interface User {
    role?: UserRole;
  }
  export interface UserRole {
    access?: 'admin' | 'user';
  }
  export interface UserRole {
    role?: string;
  }
  export interface Team {
    users?: User[];
  }
}

Uh oh. It tried to be intelligent, and keep interfaces shallow by transforming user.role into
UserRole. However, we also have another user_role entry that has a conflicting UserRole
interface. This is not what we want.

v1 of this project made certain assumptions about your schema that don’t always hold true. This is how v2 generates types from that same schema:

export interface definitions {
  user: {
    role?: {
      access?: 'admin' | 'user';
    };
  };
  user_role: {
    role?: string;
  };
  team: {
    users?: definitions['user'][];
  };
}

This matches your schema more accurately, and doesn’t try to be clever by keeping things shallow. It’s also more predictable, with the generated types matching your schema naming. In your code here’s what would change:

-UserRole
+definitions['user']['role'];

While this is a change, it’s more predictable. Now you don’t have to guess whether or not user_role was renamed as; you simply chain your type from the Swagger definition you‘re used to.

Better refs

swagger-to-ts also preserves your $refs from Swagger. Notice how the old version invented a ref for User.role that at worst causes conflicts with other parts of your schema; at best is unpredictable. Transforming your schema as-is relies on your schema to handle collisions (which will probably get it right), rather than swagger-to-ts (which probably won’t).

Wrappers

The --wrapper CLI flag was removed because it was awkward having to manage part of your TypeScript definition in a CLI flag. In v2, simply compose the wrapper yourself however you’d like:

import { components as Schema1 } from './generated/schema-1.ts';
import { components as Schema2 } from './generated/schema-2.ts';

declare namespace OpenAPI3 {
  export Schema1;
  export Schema2;
}

CamelCasing

Similarly, the --camelcase was removed because it transformed your schema in unpredictable ways. It didn’t always transform words as expected. In v2, all generated types must conform to your schema’s exact naming.

@drwpow drwpow force-pushed the v2-rework branch 5 times, most recently from 6d1012c to da5c587 Compare April 5, 2020 02:12
@drwpow drwpow merged commit 9936725 into master Apr 5, 2020
@drwpow drwpow deleted the v2-rework branch April 5, 2020 02:24
This was referenced Apr 5, 2020
@drwpow drwpow changed the title swagger-to-ts v2: better type generation swagger-to-ts 2.0: better type generation Apr 5, 2020
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.

1 participant