openapi-ts icon indicating copy to clipboard operation
openapi-ts copied to clipboard

Duplicate remote `$ref` paths not resolved correctly

Open SimonLudwigNeteleven opened this issue 1 year ago • 13 comments

Description

The command "yarn generate" should create all generated files including "types.gen.ts" without any issues. The files are generated, but the response type of “B” is broken after the command finishes. This only happens, if both path references in the yaml file are the same for both paths. The first correct one wins, if both are the same.

This behavior breaks the type generation and I could only work around it, by making one path beginning with “http” instead of “https” in my project. This workaround is obviously more hack then a solution and works only for two paths maximum. You can’t test the hack solution with “http” in stackblitz, because only “https” is allowed for the ref path, but you can reproduce the error.

Steps to reproduce the behavior

  1. Go to my stackblitz example
  2. open the terminal
  3. enter “yarn”
  4. enter “yarn generate”
  5. inspect the generated files (types.gen.ts) in packages/test-api/navigation-api/src-gen

Expected behavior 
AResponse and BResponse should be “(unknown)”.

Reproducible example or configuration

https://stackblitz.com/edit/vitejs-vite-6tvzvt?file=packages%2Ftest-api%2Fopenapi-spec%2Fnavigation-api.yaml

OpenAPI specification (optional)

No response

System information (optional)

No response

SimonLudwigNeteleven avatar Oct 10 '24 09:10 SimonLudwigNeteleven

Nice catch, thanks for reporting @SimonLudwigNeteleven!

mrlubos avatar Oct 10 '24 10:10 mrlubos

@SimonLudwigNeteleven quick update, I found that this is most likely an issue with the underlying dependency https://github.com/APIDevTools/json-schema-ref-parser. Maybe there's a configuration for this, but a quick search didn't return anything.

For context, this is what happens internally. The first reference gets correctly bundled, for example:

{
  "type": "object",
  "properties": { "foo": { "enum": [], "type": "string" } }
}

while the second one produces this faulty reference.

{
  "$ref": "#/paths/~1a/get/responses/default/content/application~1json/schema"
}

I do plan to vertically integrate this tool eventually. Most people don't compose their schemas like this so it has not been reported before. Could you provide more context about your setup? How would you expect the remote reference to be resolved? Inline them or should they also create a reusable component and reference that?

mrlubos avatar Nov 10 '24 00:11 mrlubos

@mmospanenko could you check the question above too?

mrlubos avatar Dec 01 '24 18:12 mrlubos

we have enough complicated flow, as source builds 300k lines yml file and has a lot of mistakes. I use as reference, giving ability to override some nodes ($ref) and as result we have template with tons of refs like your ("$ref": "#/paths/~1a/get/responses/default/content/application~1json/schema"). And these refs have own refs, and so on.

Now I use multi-file structure (from Redocly with their CLI) and it solves my case. So I have source file, it splits by files, then devs can add another copy of this file structure as 'overrides' folder, we prepare 'template' output file only with required paths, it merges and builds as result.

So TS in my case builds only from output, correct file, without complicated references. And this flow is better for my case now, not sure that TS generator should care about this, but it is definitely a bug from OAS perspective.

mmospanenko avatar Dec 02 '24 09:12 mmospanenko

We are also facing this issue which unfortunately prevents us from generating the API client for our API. Below is a minimal reproduction example. Whenever a $ref is used twice, the second occurrence is not generated correctly and the generated TypeScript code has errors.

openapi.yml:

openapi: "3.1.0"
info:
  version: 0.0.0
  title: Example API spec
paths:
  /example:
    get:
      summary: Some example path
      responses:
        "200":
          description: 200 OK
        "401":
          description: 401 UNAUTHORIZED
          content:
            application/json:
              schema:
                $ref: "./components.yml#/components/schemas/Error"
        "500":
          description: 500 INTERNAL SERVER ERROR
          content:
            application/json:
              schema:
                $ref: "./components.yml#/components/schemas/Error"

components.yml

components:
  schemas:
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
        message:
          type: string

openapi-ts.config.ts

import { defineConfig } from "@hey-api/openapi-ts";

export default defineConfig({
  client: "@hey-api/client-fetch",
  input: "./openapi.yml",
  // the issues also exists when not using the experimental parser
  experimentalParser: true,
  output: {
    path: "./src/api",
    format: "prettier",
  },
});

Resulting code with errors:

// This file is auto-generated by @hey-api/openapi-ts

export type GetExampleData = {
  body?: never;
  path?: never;
  query?: never;
  url: "/example";
};

export type GetExampleErrors = {
  /**
   * 401 UNAUTHORIZED
   */
  401: {
    code: string;
    message: string;
  };
  /**
   * 500 INTERNAL SERVER ERROR
   */
  500: Schema;
};

export type GetExampleError = GetExampleErrors[keyof GetExampleErrors];

export type GetExampleResponses = {
  /**
   * 200 OK
   */
  200: unknown;
};
image

larsrickert avatar Jan 09 '25 14:01 larsrickert

Hey all, the good news is there's now https://github.com/hey-api/json-schema-ref-parser so the fix is a bit closer than before, I just need to find time to prioritise it

mrlubos avatar Jan 09 '25 14:01 mrlubos

@mrlubos Thanks! And also cudos for hey API itself, I really like it :)

So is there something I can currently do in my project to fix this or do I have to wait until hey-api/json-schema-ref-parser is integrated by you?

larsrickert avatar Jan 09 '25 14:01 larsrickert

Nothing you can do on your end to fix this I am afraid, this is a bug with how those $refs are resolved. I am not sure how much effort it will be to fix at this point.

A workaround on your end would be to bundle the schema yourself into a single file and pass that to openapi-ts, so you wouldn't have to rely on the ref resolver which has the bug

mrlubos avatar Jan 09 '25 15:01 mrlubos

Hey @mrlubos was there any update on this?

By the way, really love the lib.

pranav-growthx avatar Feb 11 '25 09:02 pranav-growthx

If anyone wants to NOT Bundle schemas and still have it work, add the same component in the yaml and then make it ref the actual external schema.

eg.

components:
  schemas:
     # hey-api's having a dependency breaking thats breaking generated types 
     # when the same external ref is repeated . 
     # Temporary fix is to create a schema that points to the external ref and then use it. 
    TeamSize:
      $ref: "./company.yaml#/components/schemas/TeamSize"

pranav-growthx avatar Feb 11 '25 10:02 pranav-growthx

I followed @mrlubos 's advice but it took me a few to try to figure it out, so I thought I'd share.

I was having a lot of trouble (honestly a lot of similar issues) using the bundler that comes with openapi-ts, so I ended up using redocly-cli to bundle. redocly-cli is really really great at dealing with openapi schemas and I think the maintainers should take a look at replacing json-schema-ref-parser - the difference is night and day.

Anyway, here's my bundle -> createClient step:

execSync(`redocly bundle ${inputUrl} --output ./.tmp/bundled.yml`);
await createClient({
  input: './.tmp/bundled.yml',
  //...
})

danieltott avatar Feb 19 '25 20:02 danieltott

Any updates on this?

danvirsen avatar May 05 '25 11:05 danvirsen

cc @carson2222

mrlubos avatar Aug 27 '25 16:08 mrlubos

Hey, thanks for all your effort! I've managed to fix the ref parser issue: https://github.com/hey-api/json-schema-ref-parser/pull/10 We will soon update the openapi-ts to use the latest ref parser 1.0.7 version.

carson2222 avatar Aug 27 '25 17:08 carson2222