Rebus.Interface (rebus v0.4.0)

View Source

A validated declaration of a D-Bus interface: its methods, signals, properties and annotations.

One declaration describes an interface everywhere Rebus needs it: checking the arguments of inbound calls, typing replies, signals and property values, and generating introspection XML with Rebus.Introspection.

Example

calculator =
  Rebus.Interface.new!("org.example.Calculator",
    methods: [
      {"Add", in: [a: "i", b: "i"], out: [sum: "i"]},
      {"Log", in: [line: "s"], no_reply: true}
    ],
    signals: [{"Computed", args: [sum: "i"]}],
    properties: [
      {"Precision", "u", :readwrite},
      {"Version", "s", :read, emits_changed: :const}
    ]
  )

Declarations

new/2 takes the interface name and these options, each a list:

  • :methods - {name, opts} entries. opts takes :in and :out argument lists, :no_reply (a boolean, default false) and :annotations.
  • :signals - {name, opts} entries. opts takes :args and :annotations.
  • :properties - {name, signature, access} or {name, signature, access, opts} entries, where access is :read, :write or :readwrite. opts takes :emits_changed and :annotations.
  • :annotations - {name, value} pairs for the interface itself.

An argument list holds {name, signature} pairs, where the name is an atom or a binary, so [a: "i", b: "i"] declares two named int32 arguments. A bare signature declares an unnamed argument. Every argument and property signature must be a single complete type.

no_reply: true adds the org.freedesktop.DBus.Method.NoReply annotation, and :emits_changed adds org.freedesktop.DBus.Property.EmitsChangedSignal when it differs from the default, so introspection reports both. Either annotation may be given directly instead. An EmitsChangedSignal annotation on the interface sets the default for its properties, as the D-Bus specification describes; otherwise the default is true.

Summary

Types

Whether a property can be read, written, or both.

An introspection annotation: a name and a string value.

A reason why wrap_properties/2 could not wrap a value.

t()

A declared D-Bus interface.

A reason why new/2 rejected a declaration.

Functions

The standard org.freedesktop.DBus.Introspectable interface.

Builds an interface declaration, validating every name, signature and annotation.

Builds an interface declaration, raising ArgumentError when it is invalid.

The standard org.freedesktop.DBus.ObjectManager interface.

The standard org.freedesktop.DBus.Peer interface.

The standard org.freedesktop.DBus.Properties interface.

Builds an org.freedesktop.DBus.Properties.PropertiesChanged signal for properties declared by interface.

Builds a signal message for a signal declared by interface, typed by its declaration.

Wraps property values as the a{sv} body element that GetAll replies and PropertiesChanged signals carry, typing each value by its declaration.

Wraps property values as wrap_properties/2 does, raising ArgumentError for a property the interface does not declare.

Types

access()

@type access() :: :read | :write | :readwrite

Whether a property can be read, written, or both.

annotation()

@type annotation() :: {name :: binary(), value :: binary()}

An introspection annotation: a name and a string value.

property_error()

@type property_error() :: {:unknown_property, term()}

A reason why wrap_properties/2 could not wrap a value.

t()

@type t() :: %Rebus.Interface{
  annotations: [annotation()],
  methods: %{required(binary()) => Rebus.Interface.Method.t()},
  name: binary(),
  properties: %{required(binary()) => Rebus.Interface.Property.t()},
  signals: %{required(binary()) => Rebus.Interface.Signal.t()}
}

A declared D-Bus interface.

validation_error()

@type validation_error() ::
  {:invalid_option, term()}
  | {:invalid_interface_name, term()}
  | {:invalid_member_name, term()}
  | {:invalid_method, term()}
  | {:invalid_signal, term()}
  | {:invalid_property, term()}
  | {:invalid_arg, term()}
  | {:invalid_arg_name, term()}
  | {:invalid_signature, term()}
  | {:invalid_access, term()}
  | {:invalid_emits_changed, term()}
  | {:invalid_annotation, term()}
  | {:conflicting_annotation, {member :: binary(), annotation :: binary()}}
  | {:duplicate_member, binary()}
  | {:duplicate_property, binary()}
  | {:duplicate_annotation, binary()}
  | {:unix_fd_in_no_reply, binary()}

A reason why new/2 rejected a declaration.

Each reason carries the offending value, or the name of the member it belongs to.

Functions

introspectable()

@spec introspectable() :: t()

The standard org.freedesktop.DBus.Introspectable interface.

new(name, opts \\ [])

@spec new(
  binary(),
  keyword()
) :: {:ok, t()} | {:error, validation_error()}

Builds an interface declaration, validating every name, signature and annotation.

Names follow the D-Bus grammar: the interface name is checked as an interface name, and method, signal, property and argument names as member names. Annotation names follow the interface-name grammar, and annotation values must be strings that XML can carry. Duplicate methods, signals, properties or annotations are rejected, as is a no-reply method with a Unix file descriptor (h) among its out arguments.

Examples

iex> {:ok, interface} =
...>   Rebus.Interface.new("org.example.Calculator",
...>     methods: [{"Add", in: [a: "i", b: "i"], out: [sum: "i"]}]
...>   )
iex> interface.methods["Add"].in_signature
"ii"

iex> Rebus.Interface.new("Calculator")
{:error, {:invalid_interface_name, "Calculator"}}

new!(name, opts \\ [])

@spec new!(
  binary(),
  keyword()
) :: t()

Builds an interface declaration, raising ArgumentError when it is invalid.

object_manager()

@spec object_manager() :: t()

The standard org.freedesktop.DBus.ObjectManager interface.

peer()

@spec peer() :: t()

The standard org.freedesktop.DBus.Peer interface.

properties()

@spec properties() :: t()

The standard org.freedesktop.DBus.Properties interface.

properties_changed(interface, path, changed, invalidated, opts \\ [])

@spec properties_changed(
  t(),
  binary(),
  %{required(binary()) => term()},
  [binary()],
  keyword()
) ::
  {:ok, Rebus.Message.t()}
  | {:error,
     property_error()
     | {:invalid_property_change, binary()}
     | {:invalid_option, term()}
     | Rebus.Message.construction_error()}

Builds an org.freedesktop.DBus.Properties.PropertiesChanged signal for properties declared by interface.

changed maps property names to their new values, which are typed by the declaration. invalidated names properties that changed without their values. Each property's emits_changed decides where it may appear: one that emits true only in changed, one that emits :invalidates only in invalidated, and one that emits false or :const in neither.

opts takes :destination, :flags and :fds, as signal/5 does, so a value of a property declared with a Unix file descriptor type can refer to a descriptor.

Returns {:error, {:unknown_property, name}} for a property the interface does not declare, {:error, {:invalid_property_change, name}} for one whose declaration does not allow it where it appears, and Rebus.Message.new/2's errors otherwise.

signal(interface, path, member, body, opts \\ [])

@spec signal(t(), binary(), binary(), [term()], keyword()) ::
  {:ok, Rebus.Message.t()}
  | {:error,
     :unknown_signal
     | {:invalid_option, term()}
     | Rebus.Message.construction_error()}

Builds a signal message for a signal declared by interface, typed by its declaration.

opts takes :destination, :flags and :fds, as Rebus.Message.new/2 does. Returns {:error, :unknown_signal} when the interface does not declare member, and Rebus.Message.new/2's errors otherwise; a body that does not fit the declared signature is {:error, :invalid_body}.

Example

iex> interface =
...>   Rebus.Interface.new!("org.example.Calculator",
...>     signals: [{"Computed", args: [sum: "i"]}]
...>   )
iex> {:ok, message} =
...>   Rebus.Interface.signal(interface, "/org/example/Calc", "Computed", [42])
iex> Rebus.Message.signature(message)
"i"

wrap_properties(interface, values)

@spec wrap_properties(t(), %{required(binary()) => term()}) ::
  {:ok, [{binary(), {binary(), term()}}]} | {:error, property_error()}

Wraps property values as the a{sv} body element that GetAll replies and PropertiesChanged signals carry, typing each value by its declaration.

Entries are sorted by property name. Values are not checked against their signatures here; the message they are placed in reports one that does not fit as :invalid_body.

Example

iex> interface =
...>   Rebus.Interface.new!("org.example.Calculator",
...>     properties: [{"Precision", "u", :readwrite}, {"Version", "s", :read}]
...>   )
iex> Rebus.Interface.wrap_properties(interface, %{"Version" => "1.0", "Precision" => 3})
{:ok, [{"Precision", {"u", 3}}, {"Version", {"s", "1.0"}}]}

wrap_properties!(interface, values)

@spec wrap_properties!(t(), %{required(binary()) => term()}) :: [
  {binary(), {binary(), term()}}
]

Wraps property values as wrap_properties/2 does, raising ArgumentError for a property the interface does not declare.