Skip to content

Spread properties lost when base shape shares file with imported schemas #147

@daniel-rose

Description

@daniel-rose

Description

When a Zod schema file imports external schemas and also defines a non-exported base shape that is spread into exported schemas, the spread properties are silently dropped from the generated OpenAPI output. Only the non-spread properties survive.

Minimal Reproduction

schemas/base.ts

import { z } from 'zod'
import { addressSchema } from './addressSchema' // any external import

const personBaseShape = {
  name: z.string().describe('Full name'),
  age: z.number().int().describe('Age in years'),
}

export const employeeSchema = z
  .object({
    ...personBaseShape,
    role: z.string().describe('Job role'),
  })
  .meta({ id: 'Employee' })

Expected OpenAPI output for Employee:

{
  "type": "object",
  "properties": {
    "name": { "type": "string", "description": "Full name" },
    "age": { "type": "integer", "description": "Age in years" },
    "role": { "type": "string", "description": "Job role" }
  },
  "required": ["name", "age", "role"]
}

Actual output:

{
  "type": "object",
  "properties": {
    "role": { "type": "string", "description": "Job role" }
  },
  "required": ["role"]
}

Root Cause

In preprocessAllSchemasInFile, this.currentFilePath, this.currentAST, and this.currentImports are set once at the start of the function. However, processZodNode can trigger convertZodSchemaToOpenApiprocessFileForZodSchemapreprocessAllSchemasInFile(importedFile) for each imported schema. After returning from those recursive calls, this.currentFilePath now points to the last imported file — not the original file.

When the next schema in the original file is processed (e.g. employeeSchema after addressSchema was resolved), resolveConstObjectNode("personBaseShape") searches in the wrong file context and returns null. The spread is silently skipped.

Suggested Fix

Restore this.currentFilePath, this.currentAST, and this.currentImports before each processZodNode call in the traversal handlers of preprocessAllSchemasInFile:

ExportNamedDeclaration: (path) => {
  // ...
  if (!this.getStoredSchema(schemaName)) {
    this.processingSchemas.add(schemaName);
+   this.currentFilePath = filePath;
+   this.currentAST = ast;
+   this.currentImports = importedModules;
    const schema = this.processZodNode(declaration.init);
    // ...
  }
},
VariableDeclaration: (path) => {
  // ...
  if (!this.getStoredSchema(schemaName) && !this.processingSchemas.has(schemaName)) {
    this.processingSchemas.add(schemaName);
+   this.currentFilePath = filePath;
+   this.currentAST = ast;
+   this.currentImports = importedModules;
    const schema = this.processZodNode(declaration.init);
    // ...
  }
},

Environment

  • next-openapi-gen v1.3.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions