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

Schema being inlined despite registering it #230

Open
JeongJuhyeon opened this issue May 12, 2024 · 5 comments
Open

Schema being inlined despite registering it #230

JeongJuhyeon opened this issue May 12, 2024 · 5 comments

Comments

@JeongJuhyeon
Copy link

JeongJuhyeon commented May 12, 2024

Hi, new user here. The output I'm getting contains no "$ref" whatsoever despite registering my schema. Any idea what's going wrong here? Fwiw, the generated OpenAPI definitions themselves are correct.

Input:

import { registry } from "../lib/openapi.ts";

export const ModificationRequest = z.object({
   // ...
});
registry.register("ModificationRequest", ModificationRequest);

  registry.registerPath({
    method: "post",
    path: "/modify",
    description: "Apply the given modifications",
    request: {
      body: {
        content: {
          "application/json": {
            schema: ModificationRequest,
          },
        },
        required: true,
      },
    },
    responses: {
      200: {
        description: "OK",
        content: {
          "application/json": {
            schema: { type: "object", properties: { OK: { type: "boolean" } } },
          },
        },
      },
    },
  });

Output:

openapi: 3.0.0
info:
title: App API
version: 1.0.0
components:
schemas:
// ...
  ModificationRequest:
    type: object
    properties: 
// ...
paths:
/modify:
  post:
    description: Apply the given modifications
    requestBody:
      required: true
      content:
        application/json:
          schema:
            type: object
            properties:
// ...
@AGalabov
Copy link
Collaborator

@JeongJuhyeon, yes I know what's the issue:

registry.register is essentially returning a new schema that has some custom metadata applied to it. So the correct way to achieve what you wanted is to do:

export const ModificationRequest = registry.register("ModificationRequest", z.object({
   // ...
}););
// everything else should remain the self

@JeongJuhyeon
Copy link
Author

@AGalabov
Thanks for the help, this has improved it but part of the problem remains.

const A = registry.register("A", ...));
const B = registry.register("B"., ...));
const C = registry.register("C", ...));

const D = registry.register("D", z.union(
  [A, B, C]
  )
);

I'm getting the result

components:
  schemas:
    A: ...
    B: ...
    C: ...
    D: 
        anyOf:
            - type: object
            - ...

That is, A B and C don't get reused as part of D, their definitions are inlined again in D.

@AGalabov
Copy link
Collaborator

@JeongJuhyeon can you provide a full reproducible script? We have such a similar test case here: https://github.com/asteasolutions/zod-to-openapi/blob/master/spec/types/union.spec.ts#L26

@JeongJuhyeon
Copy link
Author

@AGalabov Repro:

import { extendZodWithOpenApi, OpenApiGeneratorV3, OpenAPIRegistry } from "@asteasolutions/zod-to-openapi";
import YAML from "yaml";
import { z } from "zod";

extendZodWithOpenApi(z);

const registry = new OpenAPIRegistry();

const requestedModificationBaseSchema = z.object({
  target: z.enum(["apple", "banana", "grape"]),
});
const requestedModificationBase = registry.register("RequestedModificationBase", requestedModificationBaseSchema);

const RequestedTextModification = registry.register(
  "RequestedTextModification",
  z.union([z.object({ x: z.number() }), z.object({ y: z.number() })]),
);

const RequestedChairModification = registry.register(
    "RequestedChairModification",
    z.union([z.object({ z: z.number() }), z.object({ a: z.number() })]),
  );

const RequestedSubcategoryModificationSchema = z.union([RequestedTextModification, RequestedChairModification]);
const RequestedSubcategoryModification = registry.register(
  "RequestedSubcategoryModification",
  RequestedSubcategoryModificationSchema,
);

const ModificationRequestSchema = z.object({
  userPageId: z.string(),
  modifications: z.array(RequestedSubcategoryModification.and(requestedModificationBase)),
});

console.log("Registering request schema");
const ModificationRequest = registry.register("ModificationRequest", ModificationRequestSchema);

const generatedApiJson = new OpenApiGeneratorV3(registry.definitions).generateDocument({
    openapi: "3.0.0",
    info: { title: "App API", version: "1.0.0" },
});

console.log("Generated API JSON");
console.log(YAML.stringify(generatedApiJson));

Result:

openapi: 3.0.0
info:
 title: App API
 version: 1.0.0
components:
 schemas:
   RequestedModificationBase:
     type: object
     properties:
       target:
         type: string
         enum:
           - apple
           - banana
           - grape
     required:
       - target
   RequestedTextModification:
     anyOf:
       - type: object
         properties:
           x:
             type: number
         required:
           - x
       - type: object
         properties:
           y:
             type: number
         required:
           - y
   RequestedChairModification:
     anyOf:
       - type: object
         properties:
           z:
             type: number
         required:
           - z
       - type: object
         properties:
           a:
             type: number
         required:
           - a
   RequestedSubcategoryModification:
     anyOf:
       - type: object
         properties:
           x:
             type: number
         required:
           - x
       - type: object
         properties:
           y:
             type: number
         required:
           - y
       - type: object
         properties:
           z:
             type: number
         required:
           - z
       - type: object
         properties:
           a:
             type: number
         required:
           - a
   ModificationRequest:
     type: object
     properties:
       userPageId:
         type: string
       modifications:
         type: array
         items:
           allOf:
             - $ref: "#/components/schemas/RequestedSubcategoryModification"
             - $ref: "#/components/schemas/RequestedModificationBase"
     required:
       - userPageId
       - modifications
 parameters: {}
paths: {}

"RequestedChairModification" and "RequestedTextModification" are inlined in "RequestedSubcategoryModification" instead of being used as $ref.

@AGalabov
Copy link
Collaborator

@JeongJuhyeon sorry it is taking so long, but free time is hard to find.

I see your example and it turns out the problem is the fact that you are using a union inside the union. I see the problematic piece of code. I'll see what I can do about it.

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

2 participants