microsoft / typespec

https://typespec.io/
MIT License
4.48k stars 211 forks source link

Helper to construct TypeSpec unions out of existing models #1811

Open tjprescott opened 1 year ago

tjprescott commented 1 year ago

Background PR #1770 required logic that would essentially take the response types of multiple operations with shared routes and emit them as "oneOf" properties in OpenAPI3. That should have been an easy task, especially since the OpenAPI3 emitter already had logic to get the appropriate OpenAPI3 schema for existing TypeSpec types, including Union.

However, this proved infuriatingly difficult because there was no way to simply take two models and say "Please give me a TypeSpec Union of these" that I could pass to that existing logic. If you can't work with the TypeSpec types as output by the compiler, you are screwed and have to invent a bunch of intermediate models to facilitate this. It ended up being way more work than it seems like it should have.

Request It would be nice to have some kind of helper method that takes an arbitrary number of TypeSpec types and returns a TypeSpec.Union of them, but without all the type graph links that make trying to construct one of these programmatically pretty much impossible (nodes, parents, etc.).

Perhaps there could be a way to generalize this, but essentially constructing TypeSpec types seems to only be possible via the compiler, and it would be useful to be able to create them programmatically as well.

bterlson commented 1 year ago

A library to aid in constructing ephemeral types for emitters would definitely be nice. That said, you can fairly easily do the same code the checker does for creating a union, since createType is exported, I think?

tjprescott commented 1 year ago

It doesn't look like createType<T> is exported. https://github.com/microsoft/typespec/blob/main/packages/compiler/core/checker.ts#L3630

bterlson commented 1 year ago

It is, on the checker instance: https://github.com/microsoft/typespec/blob/7195605e740110850cf9ce8e7791a5a0d581cffd/packages/compiler/core/checker.ts#L362

tjprescott commented 1 year ago

@bterlson I tried this and it didn't work for the reasons that it seems to need a bunch of underlying TypeSpec nodes and so forth:

      // statusCodeResponses contains the different responses that need to be combined into a union
      const variants: UnionVariant[] = [];
      const union = program.checker.createType({
        kind: "Union",
        node: undefined,
        decorators: [],
        variants,
        get options() {
          return Array.from(this.variants.values()).map((v) => v.type);
        },
        expression: false,
      });
      for (const response of statusCodeResponses) {
        const variant = program.checker.createType({
          kind: "UnionVariant",
          type: response.type,
          name: Symbol("name"),
          decorators: [],
          node: undefined,
          union: union,
        });
        variants.push(variant as UnionVariant);

I get an error on the last line: The types of 'union.node' are incompatible between these types. Type 'undefined' is not comparable to type 'UnionStatementNode | UnionExpressionNode'.