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

Mark openapi attributes as required for reads #1772

Merged
merged 3 commits into from
Oct 14, 2024

Conversation

thomassnielsen
Copy link
Contributor

@thomassnielsen thomassnielsen commented Oct 14, 2024

Fixes #1744 by marking all fields as required for read requests. This allows for more accurate documentation and better type safety for generated clients, until sparse fieldsets are implemented.

Copy link
Contributor

coderabbitai bot commented Oct 14, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The changes in this pull request focus on enhancing the RESTfulOpenAPIGenerator class within the rest-generator.ts file. The updates include the addition of multiple methods aimed at improving the generation of OpenAPI specifications for RESTful APIs. Key modifications involve the structured creation of paths and components, handling of security schemes, and detailed descriptions for resource operations. The overall logic for generating OpenAPI documents has been significantly refined to ensure comprehensive and well-structured outputs.

Changes

File Path Change Summary
packages/plugins/openapi/src/rest-generator.ts - Updated RESTfulOpenAPIGenerator class with new methods for generating OpenAPI specifications.
- Added methods: generate(), generatePaths(), generateComponents(), makeResourceList(), makeResourceCreate(), makeResourceFetch(), makeResourceUpdate(), makeResourceDelete(), makeRelatedFetch(), makeRelationshipFetch(), makeRelationshipCreate(), makeRelationshipUpdate(), generateFilterParameters(), generateSharedComponents(), generateParameters(), generateEnumComponent(), generateDataModelComponents(), generateModelEntity(), generateField(), fieldTypeToOpenAPISchema().
- Enhanced logic for handling paths, components, and security schemes in OpenAPI generation.

Assessment against linked issues

Objective Addressed Explanation
Mark nonnullable attributes as required in RESTApiHandler get requests (1744) The changes do not address the requirement to mark nonnullable attributes as required in the OpenAPI schema.

Possibly related PRs

  • OpenApi @@id support #1757: This PR also modifies the RESTfulOpenAPIGenerator class in rest-generator.ts, specifically enhancing the generateFilterParameters and generateModelEntity methods, which are relevant to the overall improvements in OpenAPI specification generation addressed in the main PR.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
packages/plugins/openapi/src/rest-generator.ts (2)

Line range hint 958-970: Clarify ID requirement in create mode

In the generateModelEntity method (lines 958-970), when in 'create' mode, you conditionally require the id field if there are ID fields without default values. Consider adding a comment or refining the logic to clarify under what circumstances the id field is required during creation, as this can impact client implementations.

If the API allows clients to provide their own IDs when creating resources, ensure this is clearly documented in the OpenAPI specification.


Line range hint 606-621: Potential overlap in filter parameters for relationship fields

In the generateFilterParameters method (lines 606-621), relationship fields are skipped for filtering due to the comment:

// TODO: how to express nested filters?

However, clients may need to filter on related resources. Consider implementing support for nested filters or updating the OpenAPI specification to document the current limitations.

Implementing nested filtering would enhance the API's usability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between aae9b60 and 45d3287.

⛔ Files ignored due to path filters (2)
  • packages/plugins/openapi/tests/baseline/rest-3.0.0.baseline.yaml is excluded by !**/*.yaml
  • packages/plugins/openapi/tests/baseline/rest-3.1.0.baseline.yaml is excluded by !**/*.yaml
📒 Files selected for processing (1)
  • packages/plugins/openapi/src/rest-generator.ts (1 hunks)
🧰 Additional context used
🔇 Additional comments (6)
packages/plugins/openapi/src/rest-generator.ts (6)

877-879: Correctly marking all fields as required in read mode

By adding all fields to the required array when mode === 'read' (lines 877-879), the OpenAPI schema now accurately reflects that all attributes are included in the response, aligning with the JSON:API specification. This ensures that the generated documentation is consistent with the actual API behavior.


875-879: Ensure nullable fields are appropriately handled in the schema

While all fields are marked as required in read mode, please verify that nullable fields are correctly represented in the OpenAPI schema. In OpenAPI, a field can be required but still allow null values if nullable: true is set. This distinction informs clients that a field will always be present but may contain a null value.

To confirm that nullable fields are properly handled, you can review the schema generation logic for nullable attributes.


Line range hint 992-1003: Accurate mapping of field types to OpenAPI schema

The fieldTypeToOpenAPISchema method provides a comprehensive and accurate mapping of data model field types to OpenAPI schema types. Utilizing ts-pattern for pattern matching enhances readability and maintainability.


Line range hint 923-934: Include relationships in response schemas

In the generateDataModelComponents method (lines 923-934), you've added relationships to the response schemas for both single and list responses. This inclusion ensures that the generated OpenAPI specification properly documents the relationships between resources, which is crucial for clients to navigate the API effectively.


875-879: Consistent handling of required properties across modes

There appears to be differing logic for determining required fields between 'create' and 'read' modes (lines 875-879 and 958-970). In 'create' mode, fields without defaults are required unless they are optional or array relations. In 'read' mode, all fields are marked as required. Verify that this inconsistency is intentional and that it reflects the API's expected behavior.

Consider whether the required properties in 'create' mode should also include non-nullable fields to ensure consistency.

Also applies to: 958-970


Line range hint 549-570: Ensure security schemes are correctly applied

The methods generating operations (e.g., makeResourceList, makeResourceCreate, etc.) include a security property that depends on resource metadata and policy analysis (lines 549-570). Verify that the security requirements are accurately reflected based on the access control policies, ensuring that the generated OpenAPI specification enforces the intended authentication and authorization constraints.

Consider adding tests to validate that the security schemes are correctly assigned to each operation.

@thomassnielsen
Copy link
Contributor Author

As far as I can tell the test fails aren't related to my changes here(?)

@thomassnielsen
Copy link
Contributor Author

I've only been running the tests within the openapi plugin folder and they succeed locally. I don't have enough memory to comfortably run the entire test suite locally.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (3)
packages/plugins/openapi/src/rest-generator.ts (3)

Line range hint 812-916: Consider refactoring generateModelEntity for improved readability and maintainability.

The generateModelEntity method is quite long and handles multiple responsibilities. Consider breaking it down into smaller, more focused methods to improve readability and maintainability. Here's a suggested refactor:

 private generateModelEntity(model: DataModel, mode: 'read' | 'create' | 'update'): OAPI.SchemaObject {
-    const idFields = model.fields.filter((f) => isIdField(f));
-    // For compound ids, each component is also exposed as a separate field
-    const fields = idFields.length > 1 ? model.fields : model.fields.filter((f) => !isIdField(f));
-
-    const attributes: Record<string, OAPI.SchemaObject> = {};
-    const relationships: Record<string, OAPI.ReferenceObject | OAPI.SchemaObject> = {};
-
-    const required: string[] = [];
-
-    for (const field of fields) {
-        if (isRelationshipField(field)) {
-            let relType: string;
-            if (mode === 'create' || mode === 'update') {
-                relType = field.type.array ? '_toManyRelationship' : '_toOneRelationship';
-            } else {
-                relType = field.type.array ? '_toManyRelationshipWithLinks' : '_toOneRelationshipWithLinks';
-            }
-            relationships[field.name] = this.wrapNullable(this.ref(relType), field.type.optional);
-        } else {
-            attributes[field.name] = this.generateField(field);
-            if (
-                mode === 'create' &&
-                !field.type.optional &&
-                !hasAttribute(field, '@default') &&
-                // collection relation fields are implicitly optional
-                !(isDataModel(field.$resolvedType?.decl) && field.type.array)
-            ) {
-                required.push(field.name);
-            } else if (mode === 'read') {
-                // Until we support sparse fieldsets, all fields are required for read operations
-                required.push(field.name);
-            }
-        }
-    }
+    const { attributes, relationships, required } = this.generateAttributesAndRelationships(model, mode);
+    const properties = this.generateProperties(model, mode, attributes, relationships);
+    const toplevelRequired = this.generateToplevelRequired(model, mode);

-    const toplevelRequired = ['type', 'attributes'];
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    let properties: any = {
-        type: { type: 'string' },
-        attributes: {
-            type: 'object',
-            required: required.length > 0 ? required : undefined,
-            properties: attributes,
-        },
-    };
-
-    if (mode === 'create') {
-        // 'id' is required if there's no default value
-        const idFields = model.fields.filter((f) => isIdField(f));
-        if (idFields.length && idFields.every((f) => !hasAttribute(f, '@default'))) {
-            properties = { id: { type: 'string' }, ...properties };
-            toplevelRequired.unshift('id');
-        }
-    } else {
-        // 'id' always required for read and update
-        properties = { id: { type: 'string' }, ...properties };
-        toplevelRequired.unshift('id');
-    }

     // eslint-disable-next-line @typescript-eslint/no-explicit-any
     const result: any = {
         type: 'object',
         description: `The "${model.name}" model`,
         required: toplevelRequired,
         properties,
     } satisfies OAPI.SchemaObject;

-    if (Object.keys(relationships).length > 0) {
-        result.properties.relationships = {
-            type: 'object',
-            properties: relationships,
-        };
-    }
+    this.addRelationshipsToResult(result, relationships);

     return result;
 }

+private generateAttributesAndRelationships(model: DataModel, mode: 'read' | 'create' | 'update') {
+    // Implementation details...
+}
+
+private generateProperties(model: DataModel, mode: 'read' | 'create' | 'update', attributes: Record<string, OAPI.SchemaObject>, relationships: Record<string, OAPI.ReferenceObject | OAPI.SchemaObject>) {
+    // Implementation details...
+}
+
+private generateToplevelRequired(model: DataModel, mode: 'read' | 'create' | 'update') {
+    // Implementation details...
+}
+
+private addRelationshipsToResult(result: any, relationships: Record<string, OAPI.ReferenceObject | OAPI.SchemaObject>) {
+    // Implementation details...
+}

This refactoring breaks down the large method into smaller, more focused methods. Each new method handles a specific aspect of the entity generation, making the code more modular and easier to maintain.


Line range hint 926-945: Enhance error handling and documentation in fieldTypeToOpenAPISchema.

While the method effectively maps Prisma field types to OpenAPI schema types, consider the following improvements:

  1. Add error handling for unknown types:
 private fieldTypeToOpenAPISchema(type: DataModelFieldType): OAPI.ReferenceObject | OAPI.SchemaObject {
     return match(type.type)
         .with('String', () => ({ type: 'string' }))
         .with(P.union('Int', 'BigInt'), () => ({ type: 'integer' }))
         .with('Float', () => ({ type: 'number' }))
         .with('Decimal', () => this.oneOf({ type: 'number' }, { type: 'string' }))
         .with('Boolean', () => ({ type: 'boolean' }))
         .with('DateTime', () => ({ type: 'string', format: 'date-time' }))
         .with('Bytes', () => ({ type: 'string', format: 'byte', description: 'Base64 encoded byte array' }))
         .with('Json', () => ({}))
         .otherwise((t) => {
             const fieldDecl = type.reference?.ref;
-            invariant(fieldDecl, `Type ${t} is not a model reference`);
-            return this.ref(fieldDecl?.name);
+            if (!fieldDecl) {
+                throw new Error(`Unsupported field type: ${t}`);
+            }
+            return this.ref(fieldDecl.name);
         });
 }
  1. Add JSDoc comments to improve documentation:
+/**
+ * Converts a Prisma field type to an OpenAPI schema object.
+ * @param type - The Prisma field type to convert.
+ * @returns An OpenAPI schema object or reference.
+ * @throws {Error} If an unsupported field type is encountered.
+ */
 private fieldTypeToOpenAPISchema(type: DataModelFieldType): OAPI.ReferenceObject | OAPI.SchemaObject {
     // ... (existing implementation)
 }

These changes will improve error handling for unsupported types and provide better documentation for developers working with this method.


Line range hint 1-1010: Overall assessment: Solid implementation with room for improvement

The RESTfulOpenAPIGenerator class provides a comprehensive implementation for generating OpenAPI specifications for RESTful APIs. The code is generally well-structured and covers various aspects of the OpenAPI specification.

Main areas for improvement:

  1. Code organization: Consider breaking down larger methods (e.g., generateModelEntity) into smaller, more focused methods to improve readability and maintainability.
  2. Error handling: Enhance error handling in methods like fieldTypeToOpenAPISchema to provide more informative error messages for unsupported types.
  3. Documentation: Add JSDoc comments to key methods to improve code documentation and make it easier for other developers to understand and maintain the code.
  4. Configuration options: Consider adding more configuration options, such as the ability to specify required fields in read operations, to provide greater flexibility in API design.

Addressing these points will further enhance the quality and maintainability of the codebase.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 45d3287 and 74cd346.

📒 Files selected for processing (1)
  • packages/plugins/openapi/src/rest-generator.ts (1 hunks)
🧰 Additional context used
🔇 Additional comments (1)
packages/plugins/openapi/src/rest-generator.ts (1)

Line range hint 918-924: LGTM: The generateField method is well-implemented.

The generateField method effectively handles the generation of OpenAPI schema for individual fields, including support for array types and nullable fields. The implementation is concise and clear.

@ymc9
Copy link
Member

ymc9 commented Oct 14, 2024

I've only been running the tests within the openapi plugin folder and they succeed locally. I don't have enough memory to comfortably run the entire test suite locally.

Hey it's caused by a typing issue with new release of tanstack-query. Test cases previously didn't pin to a specific dependency version. I've made a fix and merged it into your branch.

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.

2 participants