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

Abstract Fiber Context with Interface for Version Independence #1202

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

Conversation

ReneWerner87
Copy link
Member

This PR introduces an abstraction for the Fiber context to make Fiber contrib libraries independent of the Fiber version being used.

Key Changes:

  1. Context Interface:
    • Created a Context interface that defines the required methods from fiber.Ctx for use in the middleware.
    • This allows the middleware to work with any implementation of Context, decoupling it from a specific Fiber version.
  2. Generics Support:
    • Introduced generics in the middleware handler with Handler[T Context] to ensure type safety and support for different context implementations.
  3. Test Adjustments:
    • Updated all middleware tests to use the new generic-based handler and fiber.Ctx implementation.

Known Issue:

Currently, the fiber.Ctx interface presents a limitation for complete version independence. Methods such as Status() return fiber.Ctx itself, which tightly couples the interface to the original Fiber implementation. This behavior makes the abstraction less flexible and middleware dependent on a specific Fiber version.
A fix for this issue is planned by modifying the Context interface to return the abstracted Context instead of fiber.Ctx.

This PR is an important step towards making Fiber contrib libraries more flexible, reusable, and compatible across different Fiber versions. Further refinements will follow to address the self-referential return issue and fully decouple the middleware from the Fiber framework.

@ReneWerner87 ReneWerner87 added the ✏️ Feature New feature or request label Nov 21, 2024
Copy link
Contributor

coderabbitai bot commented Nov 21, 2024

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • 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 or @coderabbitai title 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.

@ReneWerner87
Copy link
Member Author

@coderabbitai what would be your suggestion to solve the existing problem and solve the idea of the independent version

theoretically, other middlewares could also use other functions, which also give themselves back again

Copy link
Contributor

coderabbitai bot commented Nov 21, 2024

Tip

For best results, initiate chat on the files or code changes.

@ReneWerner87

To solve the existing problem and achieve version independence, one approach is to redefine the Context interface so that methods chaining and returning *fiber.Ctx are replaced with methods that return the Context interface itself. This allows for fluent method chaining while keeping the middleware independent of a specific Fiber version.

Here’s how you might redefine the Context interface:

type Context interface {
    // Other necessary methods...

    Status(status int) Context
    SendString(body string) error
    // Include all chainable methods, ensuring they return Context
}

Then, implement this interface with a wrapper around fiber.Ctx:

type FiberContextWrapper struct {
    *fiber.Ctx
}

func (c *FiberContextWrapper) Status(status int) Context {
    c.Ctx.Status(status)
    return c
}

func (c *FiberContextWrapper) SendString(body string) error {
    return c.Ctx.SendString(body)
}

// Implement other methods as needed...

By wrapping fiber.Ctx, you abstract away the concrete implementation, allowing middlewares to operate on the Context interface without direct dependency on Fiber's version. This also handles methods that return the context itself, as they now return the Context interface.

Considerations:

  • Overhead: Wrapping fiber.Ctx introduces a slight overhead due to the additional struct, but it's minimal and can be justified by the gain in flexibility.
  • Future Compatibility: When new methods are added to fiber.Ctx, you'll need to update the Context interface and the wrapper accordingly.
  • Generics: Since Go 1.18 introduced generics, you could explore using them to make the middleware even more flexible. However, interfaces might suffice for this use case.

Example Usage in Middleware:

func Middleware(next func(Context) error) func(Context) error {
    return func(c Context) error {
        // Middleware logic...
        return next(c)
    }
}

This way, your middleware is fully abstracted over the Context interface and remains independent of any specific Fiber version, accommodating other middlewares that might use chainable methods.

Let me know if you'd like further clarification or assistance with implementing this solution.

@ReneWerner87
Copy link
Member Author

@coderabbitai but using the fiber.Ctx would mean that the code is not independent of the version used

Copy link
Contributor

coderabbitai bot commented Nov 21, 2024

Tip

For best results, initiate chat on the files or code changes.

@ReneWerner87

You're right; using fiber.Ctx directly, even when wrapping it, would not make the code independent of the Fiber version, as it still relies on Fiber's concrete implementation.

An alternative solution is to define a custom Context interface that abstracts all the necessary methods without any dependency on fiber.Ctx or its methods. Your middleware would then use this interface exclusively.

Here's how you might proceed:

  1. Define a Custom Context Interface:

    Create a Context interface that includes all the methods your middleware requires, without referencing fiber.Ctx or any specific return types tied to Fiber.

    type Context interface {
        Status(status int) Context
        SendString(body string) error
        // Include all other methods needed by your middleware
    }

    Note that methods like Status should return the Context interface for method chaining.

  2. Implement the Interface for Fiber:

    Create a struct that implements this interface by embedding or referencing fiber.Ctx, but only within the implementation, not exposing it.

    type FiberContext struct {
        ctx *fiber.Ctx
    }
    
    func (c *FiberContext) Status(status int) Context {
        c.ctx.Status(status)
        return c
    }
    
    func (c *FiberContext) SendString(body string) error {
        return c.ctx.SendString(body)
    }
    
    // Implement other methods...
  3. Use the Interface in Middleware:

    Your middleware functions should accept and work with the Context interface.

    func YourMiddleware(next func(Context) error) func(Context) error {
        return func(c Context) error {
            // Middleware logic using methods from Context interface
            return next(c)
        }
    }
  4. Adapter Function:

    At the point where you integrate the middleware with Fiber, you wrap the fiber.Ctx into your FiberContext.

    app := fiber.New()
    
    app.Use(func(c *fiber.Ctx) error {
        ctx := &FiberContext{ctx: c}
        return YourMiddleware(func(c Context) error {
            // Next handler
            return c.(*FiberContext).ctx.Next()
        })(ctx)
    })

By doing this, your middleware remains independent of any specific Fiber version, as it only depends on the custom Context interface. The FiberContext implementation can be adjusted to accommodate different versions of Fiber without changing the middleware itself.

Benefits:

  • Version Independence: Middleware depends solely on the Context interface.
  • Flexibility: You can provide different implementations of Context for different versions of Fiber or even other frameworks.
  • Isolation: Changes in fiber.Ctx do not affect your middleware as long as the FiberContext implementation is updated accordingly.

Considerations:

  • Maintenance: You'll need to maintain the FiberContext implementation for each Fiber version you wish to support.
  • Method Coverage: Ensure that the Context interface includes all methods required by your middleware. Any new methods added in future versions may require updates to the interface and implementations.

Example:

Define the Context interface:

type Context interface {
    Status(status int) Context
    SendString(body string) error
    // Additional methods...
}

Implement the interface for Fiber v2:

type FiberV2Context struct {
    ctx *fiber.Ctx // Fiber v2 context
}

func (c *FiberV2Context) Status(status int) Context {
    c.ctx.Status(status)
    return c
}

func (c *FiberV2Context) SendString(body string) error {
    return c.ctx.SendString(body)
}

// Additional method implementations...

Implement for Fiber v3 similarly.

Usage in Middleware:

func YourMiddleware(next func(Context) error) func(Context) error {
    return func(c Context) error {
        // Middleware logic
        return next(c)
    }
}

Integrate with Fiber Application:

app := fiber.New()

app.Use(func(c *fiber.Ctx) error {
    var ctx Context
    // Detect Fiber version or configure accordingly
    ctx = &FiberV2Context{ctx: c} // or FiberV3Context, etc.
    return YourMiddleware(func(c Context) error {
        return c.(*FiberV2Context).ctx.Next()
    })(ctx)
})

This approach decouples your middleware from the Fiber framework entirely, achieving true version independence.

Let me know if this aligns with your goals or if you'd like to explore this further.


✏️ Learnings added
Learnt from: ReneWerner87
URL: https://github.com/gofiber/contrib/pull/1202

Timestamp: 2024-11-21T11:28:14.415Z
Learning: Using `fiber.Ctx`, even when wrapped, does not achieve version independence, as it still relies on the specific Fiber implementation.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@ReneWerner87
Copy link
Member Author

@coderabbitai we only want to support v3 and higher versions as v3 added the ctx interface for the first time

Copy link
Contributor

coderabbitai bot commented Nov 21, 2024

Tip

For best results, initiate chat on the files or code changes.

@ReneWerner87

Understood. Since you're targeting Fiber v3 and higher, which introduced the Ctx interface, you can leverage this interface directly to achieve version independence within those versions.

Here's how you might proceed:

  1. Use the fiber.Ctx Interface Directly:

    In Fiber v3, fiber.Ctx is an interface rather than a concrete struct. You can define your middleware functions to accept and operate on the fiber.Ctx interface, ensuring compatibility across all v3 and future versions that maintain this interface.

    func YourMiddleware(next fiber.Handler) fiber.Handler {
        return func(c fiber.Ctx) error {
            // Middleware logic using methods from the fiber.Ctx interface
            // Example:
            c.Set("X-Custom-Header", "CustomValue")
            return next(c)
        }
    }
  2. Leverage Interfaces for Version Independence:

    By programming against the fiber.Ctx interface, your middleware remains decoupled from any specific implementation details that might change in future versions, provided the interface methods remain consistent.

  3. Define a Custom Context Interface (Optional):

    If you need additional abstraction or anticipate changes in the fiber.Ctx interface, you might define your own Context interface extending fiber.Ctx. However, since fiber.Ctx is already an interface in v3, this may not be necessary.

    type Context interface {
        fiber.Ctx
        // Add additional methods if needed
    }

    Then, update your middleware to use this Context interface:

    func YourMiddleware(next func(Context) error) func(Context) error {
        return func(c Context) error {
            // Middleware logic
            return next(c)
        }
    }

Considerations:

  • Interface Stability: Ensure that the methods you use from fiber.Ctx are stable and documented, so future updates to Fiber won't break your middleware.

  • Dependency Management: Specify the minimum Fiber version in your go.mod file to prevent compatibility issues.

    require github.com/gofiber/fiber/v3 v3.0.0
  • Testing: Regularly test your middleware against new Fiber versions to catch any breaking changes early.

Example Middleware Function:

func CompressionMiddleware(next fiber.Handler) fiber.Handler {
    return func(c fiber.Ctx) error {
        // Perform compression logic
        c.Response().Header.Set("Content-Encoding", "gzip")
        // Continue with the next handler
        return next(c)
    }
}

Usage:

app := fiber.New()

app.Use(CompressionMiddleware)

app.Get("/", func(c fiber.Ctx) error {
    return c.SendString("Hello, World!")
})

app.Listen(":3000")

By using the fiber.Ctx interface directly, your middleware will be compatible with Fiber v3 and above, achieving the version independence you're aiming for.

Let me know if this approach works for you or if you have further questions.


🧠 Learnings used
Learnt from: ReneWerner87
PR: gofiber/contrib#0
File: :0-0
Timestamp: 2024-11-21T11:28:14.554Z
Learning: Using `fiber.Ctx`, even when wrapped, does not achieve version independence, as it still relies on the specific Fiber implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✏️ Feature New feature or request v3
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

1 participant