PB.Schema.Plugin behaviour (PB v0.2.0)

Copy Markdown View Source

Behaviour for libraries that compile their own data into a PB schema, such as the protovalidate rules of pb_validate.

Plugins are listed in the :plugins option of PB.compile/2 and use PB.Schema. Each entry is a module or a {module, opts} tuple:

PB.compile(descriptor_set, plugins: [MyPlugin, {OtherPlugin, mode: :strict}])

Compiling

compile/2 receives the final, prepared schema, after projections and runtime preparation, and returns it with the plugin's data added. Plugins run in list order, so a plugin sees the data of the plugins listed before it. The descriptor options of messages, fields, oneofs, and extensions are kept on the prepared schema, so a plugin reads its configuration from them.

The compiled schema records which plugins compiled it, and with what options. When PB derives a new schema from a compiled one, such as narrowing it to a few root messages, it drops every recorded plugin's data and runs the plugins again on the result, so plugin data never describes a schema it no longer belongs to.

Where plugin data lives

A plugin stores its data under one key: its own module. That key may be set on the schema map itself, on a message (an entry of the schema's :messages), on a field or extension (an entry of a message's :fields_by_name or :extensions_by_name, or of the schema's :extensions_by_name), on a oneof (an entry of a message's :oneofs), and on an enum (an entry of the schema's :enums):

schema = Map.put(schema, MyPlugin, %{built_at: now})
field = Map.put(field, MyPlugin, %{checks: checks})

These are the only places PB looks when it drops plugin data. Other copies of messages and fields in the prepared schema, such as :fields_by_number, are runtime indexes; plugins must not write to them, and should not read plugin data from them.

The value under a plugin's key is the plugin's own business, but it must be plain data that Macro.escape/1 accepts (structs, maps, compiled regexes), because use PB.Schema embeds the compiled schema in a module.

Schema module functions

A plugin may also implement schema_functions/1, returning code that use PB.Schema injects into every schema module listing the plugin. The code can call __pb_schema__/0 for the module's compiled schema.

Summary

Types

A :plugins entry: a plugin module, or a plugin module and its options.

Callbacks

Adds the plugin's data to a prepared schema.

Returns quoted code to inject into a use PB.Schema module that lists this plugin, given the plugin's options.

Types

entry()

@type entry() :: module() | {module(), keyword()}

A :plugins entry: a plugin module, or a plugin module and its options.

Callbacks

compile(schema, opts)

@callback compile(PB.schema(), opts :: keyword()) :: PB.schema()

Adds the plugin's data to a prepared schema.

opts are the options given with the plugin's entry, [] for a bare module. Raise to reject the schema or the options.

schema_functions(opts)

(optional)
@callback schema_functions(opts :: keyword()) :: Macro.t()

Returns quoted code to inject into a use PB.Schema module that lists this plugin, given the plugin's options.