Skip to content

Add a plugin system #9

Closed
Closed
@ejpcmac

Description

@ejpcmac

Steps

  • Start a toy project using TypedStruct and a plugin-to-be
  • Find a plugin idea
  • Start the plugin project
  • Define the plugin API and try concepts (try Plugin system #12)
  • Merge Plugin system #12
  • Adapt to the final v0.2.0 API while trying the implementation
  • Write documentation
  • Write tests

Rationale

Structs are used to represent many things in Elixir. Other libraries sometimes provide their own macros to define special functions alongside the struct. While integrating them with TypedStruct could sometimes be handy, there are two main issues:

  1. there is potentially a large number of libraries to integrate and maintain compatibility with,
  2. it would add code to TypedStruct that would be use by only a restricted number of projects.

There are already features request for Ecto (#7) and lens libraries (#8). More could come, so I think a plugin system would be a better solution. While I do not see currently how it would fit for the Ecto case, simpler cases like defining special functions from the struct fields could be handled by a function:

defmodule MyStruct do
  use TypedStruct

  # This would contain the define_lenses/? function.
  # Its parameters and return type are yet to define.
  import LensPlugin

  typedstruct do
    plugin :define_lenses

    field :name, String.t()
    field :age, integer(), some: :option
  end
end

Plugin functions would be called after the fields have been processed, with any option like some: :option made available to it. The complete API is to define, but here is a first idea. It could be interesting to provide some helpers for users to define their own top-level macros to replace typedstruct or extending it. Integration with Ecto could be explored in such a way.

API definition

In its PR #12, @uberbrodt defines the plugin API as a behaviour. This seems to be a good start.

Plugin definition

A TypedStruct plugin is a module that implements the TypedStruct.Plugin behaviour. This behaviour is made of several callbacks, all optional to let some flexibility to the user:

  • @macrocallback init(opts): lets the user inject some code in the module before the fields are processed. This can be used to register attributes or adding new macros in the scope.
  • @callback field(name, type, opts): called on each field, after it has been processed by TypedStruct.
  • @callback after_fields(opts): called after the fields, but before the struct and type definition.

Plugin usage

defmodule MyStruct do
  use TypedStruct

  typedstruct do
    plugin FirstPlugin
    plugin WithOptions, some: :option, another: "option"

    field :name, String.t()
    field :age, integer(), some: :option
  end
end

Options passed to the plugins are passed to the callbacks. The field/4 callbacks gets both the field-specific options and the plugin general option in its opts keyword.

Metadata

Metadata

Assignees

Labels

Projects

Status

Solved

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions