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

feat: add typescriptDefinitionRefiner to TemplateContext options #278

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

senecolas
Copy link
Contributor

@senecolas senecolas commented Mar 29, 2024

Implement typescriptDefinitionRefiner Functionality

Overview

Following the discussion in PR #275 , this pull request introduces the typescriptDefinitionRefiner function, allowing for the customization of TypeScript type definitions generated by the openapi-zod-client tool. This functionality supports all type description customization needs, including the ability to add JSDoc comments based on OpenAPI schema properties.

Functionality

The typescriptDefinitionRefiner function is called within the recursive getTypescriptFromOpenApi before returning each Tanu type result. It enables users to modify the TypeScript definitions on the fly, providing a flexible approach to enhance the generated types, such as by adding comprehensive JSDoc comments.

Function signature

/**
 * A function to refine each Tanu type definition. Mostly useful for adding fields from SchemaObject
 * that aren't defined yet in the default type definition.
 */
typescriptDefinitionRefiner?: (
    defaultTs: ts.Node | TypeDefinitionObject | string,
    schema: SchemaObject
) => ts.Node | TypeDefinitionObject | string;

Example Usage

Below is an example demonstrating how to use the typescriptDefinitionRefiner option to add JSDoc comments to the generated TypeScript types with the help of a generateJSDocArray function. This example showcases how to dynamically generate JSDoc comments based on the OpenAPI schema, including descriptions, examples, deprecation notices, and more:

// Generate JSDoc comments from a schema object.
function generateJSDocArray(schema: SchemaObject, withTypesAndFormat = false): string[] {
  const comments: string[] = [];
  // Mapping from schema properties to JSDoc comments
  const mapping = {
      description: (value: string) => `${value}`,
      example: (value: any) => `@example ${JSON.stringify(value)}`,
      examples: (value: any[]) => value.map((example, index) => `@example Example ${index + 1}: ${JSON.stringify(example)}`).join("\n"),
      deprecated: (value: boolean) => (value ? "@deprecated" : ""),
      externalDocs: (value: { url: string }) => `@see ${value.url}`,
      // Handle additional attributes based on `withTypesAndFormat`
      type: withTypesAndFormat ? (value: string | string[]) => `@type {${Array.isArray(value) ? value.join("|") : value}}` : undefined,
      format: withTypesAndFormat ? (value: string) => `@format ${value}` : undefined,
      minimum: (value: number) => `@minimum ${value}`,
      maximum: (value: number) => `@maximum ${value}`,
      minLength: (value: number) => `@minLength ${value}`,
      maxLength: (value: number) => `@maxLength ${value}`,
      pattern: (value: string) => `@pattern ${value}`,
      enum: (value: string[]) => `@enum ${value.join(", ")}`,
  };

  // Generate JSDoc comments based on schema properties
  Object.entries(mapping).forEach(([key, mappingFunction]) => {
      const schemaValue = schema[key as keyof SchemaObject];
      if (schemaValue !== undefined && mappingFunction) {
          const result = mappingFunction(schemaValue);
          if (result) comments.push(result);
      }
  });

  // Add an empty line after the description if there are additional comments
  if (comments.length > 1 && schema.description) {
      comments.splice(1, 0, "");
  }

  return comments;
}

// Options for the `openapi-zod-client` tool, including the new `typescriptDefinitionRefiner`
const options = {
  groupStrategy: "tag-file",
  shouldExportAllSchemas: true,
  shouldExportAllTypes: true,
  typescriptDefinitionRefiner: (tsResult, schema) => {
    const jsDocComments = generateJSDocArray(schema);
    if (jsDocComments.length > 0 && typeof tsResult === "object") {
        // Add JSDoc comments to the TypeScript definition
        return t.comment(tsResult, jsDocComments);
    }
    return tsResult;
  },
};

This approach allows for significant flexibility in customizing the generated types, addressing the original concern about increasing configuration complexity and maintenance overhead.

Request for Comments

I welcome feedback on this feature, including suggestions for improvement or concerns about potential impacts. Please feel free to leave comments or questions in this pull request.

Thank you for considering this contribution to this project.

Copy link

vercel bot commented Mar 29, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
openapi-zod-client-rim4 ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 29, 2024 1:46pm

@senecolas senecolas changed the title feat: add typescriptDefinitionRefinerto TemplateContext options feat: add typescriptDefinitionRefiner to TemplateContext options Mar 29, 2024
@astahmer
Copy link
Owner

thank you, this looks great ! 🙏 could you add a test with your example from the PR description ?

@@ -294,8 +294,7 @@ TsConversionArgs): ts.Node | TypeDefinitionObject | string => {
throw new Error("Name is required to convert an object schema to a type reference");
}

const base = t.type(inheritedMeta.name, doWrapReadOnly(objectType));
if (!isPartial) return base;
if (!isPartial) return t.type(inheritedMeta.name, doWrapReadOnly(objectType));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adjustment was necessary due to an unforeseen issue where t.type caused modifications to the generated types (even when isPartial is true), leading to bugs when attempting to modify the Tanu object after that.

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

Successfully merging this pull request may close these issues.

None yet

2 participants