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

Add a PluginAPI #904

Closed
wants to merge 22 commits into from
Closed

Add a PluginAPI #904

wants to merge 22 commits into from

Conversation

mackoj
Copy link
Contributor

@mackoj mackoj commented Sep 10, 2024

Hello again,

I’d like to share the progress on a general plugin API, along with a specific sub-plugin API for image serialization, following up on our previous discussion here.

Motivation

Some tasks, like exporting images in various formats, can't be handled easily with strategies alone. By introducing this plugin system, we can make swift-snapshot-testing more modular, flexible, and extensible. This allows for future enhancements without requiring changes to the core library, making it the ideal solution for cross-cutting concerns like image serialization.

Key Objectives:

  • Maintain API Stability: The plugin system coexists with the current API, ensuring no breaking changes or regressions.
  • Extensibility: The architecture is abstract, allowing for the easy addition of plugins for new features. For example, I’ve introduced sub-plugins for image serialization to support formats like JPEG XL, HEIC, and WebP.
  • Simplicity: The system is easy to implement, enabling developers to create their own plugins for various use cases.
  • Auto-registration: While manual registration is used now, future versions can support automatic registration across platforms without relying on Objective-C runtime.

Why a Plugin API?

Certain functionalities like image encoding and decoding are better served by a plugin API rather than strategies. The plugin system gives swift-snapshot-testing a powerful mechanism for handling additional functionality without bloating the core library.

Current Image Serialization Plugins:

This opens the door for future sub-plugin APIs, making swift-snapshot-testing a flexible and evolving ecosystem.

Important Files:

  • Sources/ImageSerializationPlugin/ImageSerializationPlugin.swift: Defines the Plugin API for image serialization.
  • Sources/SnapshotTesting/Plug-ins/ImageSerializer.swift: Manages image serialization.
  • Sources/SnapshotTesting/Plug-ins/PluginRegistry.swift: Abstract plugin management.
  • Sources/SnapshotTestingPlugin/SnapshotTestingPlugin.swift: Defines the core plugin API, which ImageSerializationPlugin conforms to.

The changes in Sources/SnapshotTesting/Snapshotting mainly pass through the new imageFormat parameter.

Next Steps:

  • add tests
  • documentation
  • create article
  • add support for withSnapshotTesting

Future Directions:

  • Explore auto-registration mechanisms for cross-platform support without relying on Objective-C runtime.

How It Works

The Core Plugin API

The plugin architecture is designed to allow developers to register plugins that provide specific functionality without modifying the core library. Plugins are registered via a PluginRegistry and retrieved dynamically at runtime.

Core Flow:

  1. Plugin Registration: Plugins conform to the SnapshotTestingPlugin protocol, providing an identifier and specific behavior. The PluginRegistry manages the available plugins.

  2. Plugin Lookup: When a plugin is needed (e.g., for image serialization), the PluginRegistry retrieves the plugin by its identifier. The system also supports querying all plugins of a specific type.

  3. Extensibility: The architecture is designed for future expansion, allowing other cross-cutting concerns like logging, network requests, or file management to be addressed via plugins.

Image Serialization via Plugins

The first practical use of this plugin architecture is in image encoding and decoding via the ImageSerializationPlugin. This allows support for various formats, including JPEG XL, HEIC, and WebP. In our repository, for example, we have a folder containing 1,000 snapshot-test images, which totals 202.1 MB when saved as PNG files. By switching to JPEG XL, this size is reduced to just 60.7 MB a 70% decrease in storage usage.

  • ImageSerializationPlugin: This protocol enables third parties to provide their own image encoders/decoders. Each plugin specifies the imageFormat it supports and implements encodeImage and decodeImage for handling that format.

  • ImageSerializer Class: This class manages the encoding and decoding process, using the plugin system to dynamically choose the appropriate plugin for the image format.

Example in Action:

  1. The ImageSerializer queries the PluginRegistry for all registered image serialization plugins.
  2. It finds, for instance, the JXLImageSerializer plugin, which has registered itself with the identifier "jxl".
  3. The decodeImage method of JXLImageSerializer is called to decode the data into a SnapImage.

This modular approach makes it easy to add new image formats by simply adding plugins.


Diagram

classDiagram
    class PluginRegistry {
        -plugins: Dictionary<String, AnyObject>
        +registerPlugin(plugin: SnapshotTestingPlugin)
        +plugin(identifier: String) Output
        +allPlugins() Output[]
        +imageSerializerPlugins() ImageSerialization[]
        +registerAllPlugins()
    }
    
    class SnapshotTestingPlugin {
        +identifier: String
    }
    
    class ImageSerialization {
        +encodeImage(image: SnapImage) Data?
        +decodeImage(data: Data) SnapImage?
        +imageFormat: ImageSerializationFormat
    }
    
    class ImageSerializationPlugin {
        +imageFormat: ImageSerializationFormat
        +identifier: String
        +encodeImage(image: SnapImage) Data?
        +decodeImage(data: Data) SnapImage?
    }
    
    PluginRegistry --> SnapshotTestingPlugin : Registers
    PluginRegistry --> ImageSerialization : Provides Plugins
    ImageSerialization <|-- ImageSerializationPlugin : Inherits
    ImageSerializationPlugin <|-- HEICImageSerializer
    ImageSerializationPlugin <|-- JXLImageSerializer
    ImageSerializationPlugin <|-- WEBPImageSerializer

    class ImageSerializer {
        -plugins: [ImageSerialization]
        +encodeImage(image: SnapImage, imageFormat: ImageSerializationFormat) Data?
        +decodeImage(data: Data, imageFormat: ImageSerializationFormat) SnapImage?
    }
    
    ImageSerializer --> PluginRegistry : Fetches Plugins
    ImageSerializer --> ImageSerialization : Uses Plugins

    class ImageSerializationFormat {
        +png: String
        +plugins: String
    }
    
    ImageSerializationFormat --> ImageSerializationPlugin : Identifies
Loading

This demonstrates how the Plugin API serves as the backbone for managing various plugins in our case image formats via ImageSerializationPlugin.


By implementing this plugin system, we make swift-snapshot-testing more modular, future-proof, and easy to extend, without complicating the existing API.

Looking forward to your feedback!

@mackoj
Copy link
Contributor Author

mackoj commented Sep 11, 2024

Now that I have something a bit better even if it's still early but it doesn’t break the API and adds a PluginAPI, @stephencelis and @mbrandonw, how can this be improved? What is missing, and how do you think we can enhance the design of the API? Do you think this has any chance to be merged ?

Thanks for your feedback.

@mackoj mackoj marked this pull request as ready for review September 11, 2024 08:10
@mackoj mackoj changed the title Image Serializer Plugin API Add a Image Serializer Plugin API Sep 11, 2024
@mackoj mackoj changed the title Add a Image Serializer Plugin API Add a Plugin API Sep 11, 2024
@mackoj mackoj changed the title Add a Plugin API Add a PluginAPI Sep 11, 2024
@mackoj
Copy link
Contributor Author

mackoj commented Sep 14, 2024

This PR might be too messy, so I’ve split it to better explain its content:

@mackoj mackoj closed this Sep 14, 2024
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.

1 participant