anatine / zod-plugins

Plugins and utilities for Zod
640 stars 89 forks source link

Parse frontmatter in Zod descriptions #180

Open phpnode opened 9 months ago

phpnode commented 9 months ago

This PR adds support for parsing front-matter in Zod type descriptions. This allows us to propagate metadata from the description into the generated OpenAPI spec. For example:

const User = z.object({
  name: z.string().optional().describe(`
    ---
    title: Name
    deprecated: use fullName instead
    ---
    # Name
    The name of the thing.
  `),
   fullName: z.string()
})

becomes:

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "# Name\nThe name of the thing.",
      "deprecated": true,
      "x-title": "Name",
      "x-deprecated": "use fullName instead"
    },
    "fullName": {
      "type": "string"
    }
  },
  "required": ["fullName"]
}

Descriptions that don't include front matter are left intact.

nx-cloud[bot] commented 9 months ago

☁️ Nx Cloud Report

CI is running/has finished running commands for commit 6ab25f56816527b65cf636ff034f58fec39db097. As they complete they will appear below. Click to see the status, the terminal output, and the build insights.

📂 See all runs for this CI Pipeline Execution


✅ Successfully ran 2 targets - [`nx affected:test --base=origin/main --codeCoverage`](https://cloud.nx.app/runs/lnFgMXTm53?utm_source=pull-request&utm_medium=comment) - [`nx affected:lint --base=origin/main`](https://cloud.nx.app/runs/sRZH2HYo7Q?utm_source=pull-request&utm_medium=comment)

Sent with 💌 from NxCloud.

Brian-McBride commented 8 months ago

This is great, but it does add a large dependency on Front Matter. I wonder if there is a way we can set up a plugin system to support additional parsing like this? It seems heavy to add for the majority of users who are likely not using FrontMatter.

For example, you could just do:

const User = z.object({
  name: z.string().optional().describe(
    frontMatter(`
      ---
      title: Name
      deprecated: use fullName instead
      ---
      # Name
      The name of the thing.
    `)
  ),
   fullName: z.string()
})

That should give you the same result without adding the dependency to the full library.

Brian-McBride commented 8 months ago

Oh, sorry, I didn't see you were mapping frontmatter results out inside your function. That is more complicated, but you could probably create helper function to sit inside of the describe.

Brian-McBride commented 8 months ago

Maybe we can add in an optional parser function option in the generateSchema function?

LIke:

generateSchema(
  zodRef: OpenApiZodAny,
  useOutput?: boolean,
  optionalParsers?: {
    describeParser: (zodRef: OpenApiZodAny) => SchemaObject
  }
): SchemaObject

Then we can mod the base code similar to how you did.

return merge(baseSchema, describeParser?.(zodRef), ...schemas);