Contexir.Layer (Contexir v0.3.0)

View Source

Provides the DSL for defining layers — refinements to existing functions that can be activated dynamically at runtime.

A “layer” is a module that declares zero or more partials (refinements) for existing functions, specifying how they should be modified under certain contexts.

Through this DSL you can:

  • Define layers (via deflayer)
  • Define refinements (partials) for specific functions, choosing execution mode: :before, :around, or :after via defpartial.
  • Group layers (via use_layers) so you can compose sets of behavior as a single logical layer.

This is the core mechanism that makes the rest of Contexir — dispatching, context propagation, and dynamic behavior — possible.

DSL Overview

deflayer LoggingLayer do
  refine SomeModule do
    # runs before the original function
    defpartial some_fun(arg1, arg2, ctx), mode: :before do
      IO.puts("about to call some_fun")
    end

    # wraps around the original and proceeds through `continue/1`
    defpartial some_fun(arg1, arg2, _ctx), mode: :around do
      IO.puts("entering")
      result = continue([arg1, arg2])
      IO.puts("exiting")
      result
    end

    # runs after the original function
    defpartial some_fun(_arg1, _arg2, _ctx), mode: :after do
      IO.puts("some_fun finished")
    end
  end
end

Summary

Functions

Declares that the current layer should run after another layer.

Declares that the current layer should run before another layer.

Declares that the current layer cannot be active with another layer.

Declares a context module that can activate layers from context values.

Declares a new layer module.

Defines a partial function — a refinement of an existing function from another module.

Returns compile-time metadata for a layer module.

Declares a context-activated layer inside defcontext.

Defines partials for a single target module.

Declares that the current layer requires another layer to be active.

Resolves layer composition metadata without activating the layers.

Resolves layer composition metadata or raises when relationships are invalid.

Resolves declarative context rules against a context map.

Resolves declarative context rules or raises when relationships are invalid.

Includes or reuses other layers inside the current one.

Functions

after_layer(layer)

(macro)

Declares that the current layer should run after another layer.

before(layer)

(macro)

Declares that the current layer should run before another layer.

conflicts_with(layer)

(macro)

Declares that the current layer cannot be active with another layer.

defcontext(name, list)

(macro)

Declares a context module that can activate layers from context values.

deflayer(name, opts \\ [], list)

(macro)

Declares a new layer module.

The deflayer macro defines a standard Elixir module configured as a Contexir layer. Inside the block, you can use defpartial/3 to define refinements for specific base module functions.

Example

deflayer LoggingLayer do
  refine Account do
    defpartial withdraw(_acc, amt, _ctx), mode: :before do
      IO.puts("[BEFORE] withdrawing #{amt}")
    end
  end
end

defpartial(signature, opts \\ [], list)

(macro)

Defines a partial function — a refinement of an existing function from another module.

The defpartial macro declares behavior for a specific mode: :before, :around, or :after.

  • :before — runs before the primary function
  • :around — wraps the next layer or base function (call continue/1)
  • :after — runs after the primary function returns

Example

defpartial Account.withdraw(acc, amt, _ctx), mode: :around do
  IO.puts("[AROUND] start")
  result = continue([acc, amt])
  IO.puts("[AROUND] end")
  result
end

info(layer)

Returns compile-time metadata for a layer module.

layer(layer, opts)

(macro)

Declares a context-activated layer inside defcontext.

refine(target, list)

(macro)

Defines partials for a single target module.

Inside a refine block, defpartial may omit the target module:

deflayer LoggingLayer do
  refine Account do
    defpartial withdraw(account, amount, _ctx), mode: :around do
      continue([account, amount])
    end
  end
end

requires(layer)

(macro)

Declares that the current layer requires another layer to be active.

resolve(layers)

Resolves layer composition metadata without activating the layers.

Resolution expands use_layers, removes duplicate layers while preserving the first occurrence, and validates requires and conflicts_with relationships.

resolve!(layers)

Resolves layer composition metadata or raises when relationships are invalid.

resolve_context(context_module, ctx)

Resolves declarative context rules against a context map.

resolve_context!(context_module, ctx)

Resolves declarative context rules or raises when relationships are invalid.

use_layers(layers)

(macro)

Includes or reuses other layers inside the current one.

This macro allows grouping multiple layers together under a single layer name, so they can be activated as a unit. The composed layer will automatically delegate to all included layers during dispatch.

Example

deflayer SecureLayer do
  use_layers [AuthLayer, LoggingLayer]
end

Contexir.with_layers [SecureLayer] do
  Account.withdraw(%{balance: 100}, 10, %{user_authenticated: true})
end

This is equivalent to activating [AuthLayer, LoggingLayer] together.