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

Unable to use local $ref components #93

Open
Mudbill opened this issue Jan 12, 2024 · 0 comments
Open

Unable to use local $ref components #93

Mudbill opened this issue Jan 12, 2024 · 0 comments

Comments

@Mudbill
Copy link

Mudbill commented Jan 12, 2024

I had a functional work-around for version 0.8.0, but 0.8.1 replaced the SwaggerUI with Scalar, which does not accept this work-around. I have tried many things, but been unable to find a working solution for referencing schema components that exist within the same OpenAPI file.

I specify the schemas with new Elysia().model({ User: UserModel })

This puts the UserModel into #/components/schemas/User in the OpenAPI file.

I would like to reference this model (or several) in a response or body, by wrapping it in an object, like so:

new Elysia()
  .model({ User: UserModel })
  .get("/users", () => {
    return { users: [] }
  }, {
    response: t.Object({
      users: t.Array(
        // Some reference to '#/components/schemas/User'
      )
    })
  })

Currently, the only way I've found to somewhat get this effect is to put the UserModel into the array, but this breaks the link to the existing schema. The end result is that the OpenAPI file duplicates the schema, inflating it exponentionally depending on how many times you wish to use it.

I want the file to appear like this

{
  "openapi": "3.1.0",
  "servers": [
    {
      "url": "http://localhost:3000",
      "description": "Test server"
    }
  ],
  "components": {
    "schemas": {
      "User": {
        "description": "User",
        "additionalProperties": false,
        "type": "object",
        "properties": {
          "email": {
            "format": "email",
            "type": "string"
          },
          "id": {
            "readOnly": true,
            "type": "integer"
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "email",
          "id",
          "name"
        ]
      }
    }
  },
  "info": {
    "title": "Custom API",
    "description": "These are the custom docs",
    "version": "1.0.0"
  },
  "paths": {
    "/users/": {
      "get": {
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "users": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/User"
                      }
                    }
                  },
                  "required": [
                    "users"
                  ]
                }
              }
            }
          }
        },
        "operationId": "getUsers",
        "summary": "Get all users"
      }
    }
  }
}

Instead it appears like this

{
  "openapi": "3.1.0",
  "servers": [
    {
      "url": "http://localhost:3000",
      "description": "Test server"
    }
  ],
  "components": {
    "schemas": {
      "User": {
        "description": "User",
        "additionalProperties": false,
        "type": "object",
        "properties": {
          "email": {
            "format": "email",
            "type": "string"
          },
          "id": {
            "readOnly": true,
            "type": "integer"
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "email",
          "id",
          "name"
        ]
      }
    }
  },
  "info": {
    "title": "Custom API",
    "description": "These are the custom docs",
    "version": "1.0.0"
  },
  "paths": {
    "/users/": {
      "get": {
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "users": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string"
                          },
                          "email": {
                            "format": "email",
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "email"
                        ]
                      }
                    }
                  },
                  "required": [
                    "users"
                  ]
                }
              }
            }
          }
        },
        "operationId": "getUsers"
      }
    }
  }
}

Some things I have tried

With SwaggerUI, I was able to get this to work with t.Array(t.Ref(UserModel)), however Scalar does not like this approach, instead attempting to fetch GET /User as it seems to think the schema is external to the file. I'm not sure I can fault Scalar for this though, as it seems the format was incorrect originally despite working in SwaggerUI.

To get it to work with SwaggerUI, the UserModel was required to specify the $id field. It seems by setting it to "User", the file ended up with "$ref": "User", and the local schema included the "$id": "User" field. I think the issue is that the $ref should be #/components/schemas/User, not just User. There may be other issues aside from this, but this is the most prominent issue I can spot. I've been reading the OpenAPI v3.1 spec, and I don't think it allows referring to local schemas simply by name, despite them having an $id, but I could be wrong. Either way, Scalar breaks with how it currently is.

Using:

  • elysia 0.8.9
  • elysia-swagger 0.8.3
  • bun 1.0.20

Reproduction

Open to see

const UserModel = t.Object(
  {
    id: t.Integer({ readOnly: true }),
    name: t.String(),
    email: t.String({ format: "email" }),
  },
  {
    $id: "User",
  }
);

const app = new Elysia()
  .use(swagger({
    documentation: {
      openapi: "3.1.0"
    }
  }))
  .model({ User: UserModel })
  .get("/users/", () => ({ users: [] }), {
    response: t.Object({
      users: t.Array(t.Ref(UserModel)),
    }),
    type: "application/json"
  });

Go to /swagger/json to get the resulting file (Scalar at /swagger just generates a blank page)
The file looks like this:

{
  "openapi": "3.1.0",
  "info": {
    "title": "Elysia Documentation",
    "description": "Development documentation",
    "version": "0.0.0"
  },
  "paths": {
    "/users/": {
      "get": {
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "users": { "type": "array", "items": { "$ref": "User" } }
                  },
                  "required": ["users"]
                }
              }
            }
          }
        },
        "operationId": "getUsers"
      }
    }
  },
  "components": {
    "schemas": {
      "User": {
        "$id": "User",
        "type": "object",
        "properties": {
          "id": { "readOnly": true, "type": "integer" },
          "name": { "type": "string" },
          "email": { "format": "email", "type": "string" }
        },
        "required": ["id", "name", "email"]
      }
    }
  }
}

If you put it into https://editor-next.swagger.io/ it renders more or less correctly

If you put it into https://docs.scalar.com/swagger-editor you can see that it fails to locate the schema, falling back to null.
Modify the $ref to be #/components/schemas/User and it will work.

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

No branches or pull requests

1 participant