Skip to content

Conversation

@willsamu
Copy link

Motivation

We utilize Zod as an internal validation tool for our API. The upstream API spec is partially republished (in adapted ways) as part of our client-facing API. For this purpose, we leverage Zod v4's OpenAPI conversion capabilities. It is critical that the .meta() field is populated correctly for values that we pass down 1:1, enabling proper documentation generation and API specification accuracy.

Key Changes

1. Zod v4 .meta() Support (Main Feature)

  • Added support for Zod v4's .meta() method as an alternative to the global registry pattern
  • New configuration option: metadata: 'local' uses .meta() directly on schemas (Zod v4 API)
  • Existing option: metadata: 'true' or metadata: 'global' continues to use .register(z.globalRegistry, ...)
  • The .meta() approach better leverages Zod's native API and capabilities
  • Applies to both schema-level and parameter-level metadata

2. Fix: Parameter Attribute Precedence 🐛

Breaking behavior fix (not a breaking change in functionality):

Previously, parameter-level attributes (description, deprecated) were incorrectly overwritten by schema-level attributes. This has been corrected to follow proper OpenAPI precedence rules:

  • Parameter-level attributes now take precedence over schema-level attributes
  • Affects: description, deprecated
  • This fix ensures that explicit parameter metadata is preserved and not lost

Example of the fix:

parameters:
  - name: filter
    description: 'Filter criteria'  # This now takes precedence
    schema:
      description: 'This description should be overridden'  # No longer overwrites

3. Enhancement: Parameter Examples Support ✨

Added support for extracting and parsing examples from parameter definitions:

  • Handles both example (singular) and examples (plural/object) fields
  • Converts OpenAPI's examples object format to arrays for Zod consumption
  • Note on tradeoff: Zod's OpenAPI conversion doesn't fully handle the plural examples field as per OpenAPI spec. We convert all examples to singular example form as an array. While slightly incorrect per spec, this is an acceptable tradeoff - better to have examples in a slightly incorrect format than no examples at all.

4. Documentation & Tests

  • Updated documentation: docs/openapi-ts/plugins/zod.md:243-244
  • Added comprehensive test coverage for both metadata strategies ('local' and 'global')
  • Test specs:
    • specs/3.0.x/validators-parameter-example.yaml (parameter examples)
    • specs/3.1.x/validators-metadata-enhanced.yaml (enhanced metadata with title, deprecated, examples)
  • Snapshot tests for all combinations: v3/v4 × mini/full × local/global metadata

Breaking Changes

None - This is purely additive functionality with bug fixes:

  • ✅ New metadata: 'local' option (opt-in)
  • ✅ Existing metadata: true and metadata: 'global' continue working
  • ✅ Parameter precedence for description and deprecated` fix corrects invalid behavior (fix of documentation behaviour, not breaking change or validation or runtime behaviour)
  • ✅ Example extraction is additional functionality

Testing

All changes are covered by snapshot tests across multiple scenarios:

  • ✅ OpenAPI 3.0.x and 3.1.x
  • ✅ Zod v3 and v4 compatibility modes
  • ✅ Both metadata strategies (local and global)
  • ✅ Parameter-level examples
  • ✅ Schema-level metadata (title, description, deprecated, examples)

Note on Contribution Process

I did not find a formal contribution process (e.g., opening issues beforehand), so I proceeded directly with implementing this feature. Happy to adjust the approach if there are preferred contribution guidelines.

Samuel Will added 5 commits November 24, 2025 11:48
This enables local meta support. No breaking changes on the api. Also parses examples for previous global registry.
Parameter examples have not been extracted. Also places examples in "example" meta attribute instead of "examples" as this is utilized by zod v4s built-in support for generating OpenAPI schemas.
This is a somewhat breaking change. Previously, the deprecated and description field have been overwritten by the schema property of parameters. This leads to parameters that were marked as deprecated on the parameter-level to not be marked as deprecated in the generated output IF the schema was not marked as deprecated. Local configuration should overwrite schema configuration. This is a breaking change, as it introduces different (valid) documentation compared to how it was handled previously. The general runtime of outputs should not be affected.
There has been an issue that incorrecly parses schema-level descriptions instead of parameter level ones and example values
@bolt-new-by-stackblitz
Copy link

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@changeset-bot
Copy link

changeset-bot bot commented Nov 24, 2025

⚠️ No Changeset found

Latest commit: 9d1b848

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Nov 24, 2025

Someone is attempting to deploy a commit to the Hey API Team on Vercel.

A member of the Team first needs to authorize it.

@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. feature 🚀 New feature or request labels Nov 24, 2025
@codecov
Copy link

codecov bot commented Nov 24, 2025

Codecov Report

❌ Patch coverage is 44.28571% with 78 lines in your changes missing coverage. Please review.
✅ Project coverage is 34.45%. Comparing base (765ab22) to head (9d1b848).

Files with missing lines Patch % Lines
packages/openapi-ts/src/plugins/zod/v4/plugin.ts 0.00% 47 Missing ⚠️
packages/openapi-ts/src/plugins/zod/mini/plugin.ts 0.00% 30 Missing ⚠️
...s/openapi-ts/src/openApi/3.0.x/parser/parameter.ts 96.42% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3028      +/-   ##
==========================================
+ Coverage   33.01%   34.45%   +1.43%     
==========================================
  Files         426      426              
  Lines       32792    32907     +115     
  Branches     2126     2289     +163     
==========================================
+ Hits        10827    11337     +510     
+ Misses      21937    21542     -395     
  Partials       28       28              
Flag Coverage Δ
unittests 34.45% <44.28%> (+1.43%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

To reach required test coverage we need to export some private methods form the parameter files
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Nov 25, 2025
@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 25, 2025

Open in StackBlitz

npm i https://pkg.pr.new/hey-api/openapi-ts/@hey-api/codegen-core@3028
npm i https://pkg.pr.new/hey-api/openapi-ts/@hey-api/nuxt@3028
npm i https://pkg.pr.new/hey-api/openapi-ts/@hey-api/openapi-ts@3028
npm i https://pkg.pr.new/hey-api/openapi-ts/@hey-api/vite-plugin@3028

commit: 9d1b848

@mrlubos
Copy link
Member

mrlubos commented Nov 25, 2025

@willsamu I don't understand the motivation for this pull request? .meta() is just a syntactic sugar on top of global registry https://zod.dev/metadata?id=meta

@willsamu
Copy link
Author

@mrlubos I assumed that using the .meta decorator to override configuration from the global registry would not work, hence the implementation. After checking, I can confirm that it actually does work. So, we do not need to implement the meta decorator as additinal functionality.

However, this PR contains two more things that were added/fixed along the way:

  • Correct precedence of local attributes vs. schema attributes
  • Extracting examples from the OpenAPI spec

How should we proceed? As the meta decorator does not offer any additional functionality to users, we could remove it from the PR if you prefer to not add it to the codebase. It would be good to get the other two points merged though

@mrlubos
Copy link
Member

mrlubos commented Nov 28, 2025

@willsamu though I'm not sure about the global vs local metadata naming, I actually wouldn't mind it if some people prefer it.

The main issue is the pull request is 100% vibe coded and needs manual clean up. There are many obvious flags, such as using hasMetadata variable where it would take one look at the object API to discover this method

Screenshot 2025-11-28 at 10 11 42 pm

The only thing I don't like is the metadata construction logic. It's just as arbitrary as the current output but it also fragments implementations, so I'd remove that.

tl;dr

  1. parameter fields > schema fields fix OK
  2. parsing examples OK
  3. allowing global registry vs .meta() OK
  4. constructing arbitrary metadata for Zod NOT OK

If you can split it into 3 separate pull requests, 1 and 2 are definitely things we'd want to merge, 3 is low priority so up to you. Maybe we could add a resolver for 3 and let people define .meta(), it's really simple, I could show you what I mean later

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 🚀 New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants