Snodo.Extension behaviour (snodo v0.2.0)

Copy Markdown View Source

Behaviour for explicitly installed, exact-versioned out-of-tree extensions.

Extension-owned routes deliberately cover top-level client-to-server requests only. An optional around_dispatch/4 callback can wrap core protocol execution for an advertised, exact-version-compatible extension. Its continuation accepts a request context, allowing middleware to pass a derived immutable context to the next extension and ultimately the core handler. Middleware also runs before peer negotiation succeeds, and can inspect Snodo.Context.extensions to distinguish negotiated requests.

An optional missing_capability_error/2 callback can customize the error for an extension-owned method when the server advertised the extension but the client did not. Extensions without the callback retain method-not-found behavior.

An optional transport_policy/2 callback can adapt the selected protocol's transport policy for an extension-owned route. The callback is considered only for an exact-version-compatible route advertised by the server; installed-only extensions cannot affect transport admission.

Extensions may optionally contribute fields to subscriptions/listen with subscription_filter/2 and shape matching source events with shape_subscription_event/3. The framework invokes these callbacks only for an installed, advertised, exact-version-compatible extension and retains per-extension filter ownership through the stream lifecycle.

Other outbound methods and MRTR-embedded operations require additional routing infrastructure and cannot be advertised as extension-owned routes through this behaviour yet.

Summary

Types

continuation()

@type continuation() :: (Snodo.Context.t() -> dispatch_result())

dispatch_result()

@type dispatch_result() :: {:ok, Snodo.Result.t()} | {:error, Snodo.Error.t()}

Callbacks

around_dispatch(operation, params, t, continuation)

(optional)
@callback around_dispatch(
  operation :: term(),
  params :: map(),
  Snodo.Context.t(),
  continuation()
) ::
  dispatch_result()

dispatch(operation, params, t)

@callback dispatch(operation :: term(), params :: map(), Snodo.Context.t()) ::
  dispatch_result()

id()

@callback id() :: String.t()

methods()

@callback methods() :: [Snodo.Extension.Method.t()]

missing_capability_error(method, t)

(optional)
@callback missing_capability_error(method :: String.t(), Snodo.Context.t()) ::
  Snodo.Error.t() | :method_not_found

negotiate(client_settings, server_settings)

@callback negotiate(client_settings :: map(), server_settings :: map()) ::
  {:ok, map()} | :not_negotiated | {:error, Snodo.Error.t()}

shape_error(t, t)

@callback shape_error(Snodo.Error.t(), Snodo.Context.t()) :: map()

shape_result(operation, t, t)

@callback shape_result(operation :: term(), Snodo.Result.t(), Snodo.Context.t()) :: map()

shape_subscription_event(t, id, t)

(optional)
@callback shape_subscription_event(
  Snodo.Subscription.Event.t(),
  Snodo.Envelope.id(),
  Snodo.Context.t()
) :: map()

subscription_filter(requested_filter, t)

(optional)
@callback subscription_filter(requested_filter :: map(), Snodo.Context.t()) ::
  {:ok, accepted_filter :: map()} | {:error, Snodo.Error.t()}

transport_policy(t, t)

(optional)

validate_operation(operation, params, t)

@callback validate_operation(operation :: term(), params :: map(), Snodo.Context.t()) ::
  :ok | {:error, Snodo.Error.t()}