Davy.Backend behaviour (davy v0.3.0)

Copy Markdown View Source

Behaviour for WebDAV storage backends.

Implement this behaviour to connect Davy to any storage system. The backend handles resource resolution, content operations, and collection management. All path arguments are lists of URI-decoded path segments.

Example

defmodule MyApp.DavBackend do
  @behaviour Davy.Backend

  @impl true
  def authenticate(_conn), do: {:ok, %{user: "anonymous"}}

  @impl true
  def resolve(_auth, path) do
    # Look up the resource at path
  end

  # ... implement remaining callbacks
end

Summary

Callbacks

Authenticate the request. Called before any operation.

Copy a resource to the destination path.

Create a collection (directory) at the given path.

Delete a resource. For collections, the backend should handle recursive deletion. Return :ok on full success, or {:error, error} with a list of partial failures via {:partial, [{path, error}]} for 207 responses.

Read file content. opts may include :range as {start, end}.

List the direct members of a collection.

Return values for the requested properties on a resource.

Move a resource to the destination path.

Create or replace a file at the given path.

Create or replace a file from a stream of binary segments.

Resolve a path to a resource. Return {:error, not_found_error} if the resource does not exist.

Set or remove properties on a resource.

Types

auth_context()

@type auth_context() :: term()

error()

@type error() :: Davy.Error.t()

path()

@type path() :: [String.t()]

property()

@type property() :: {namespace :: String.t(), name :: String.t()}

resource()

@type resource() :: Davy.Resource.t()

Callbacks

authenticate(conn)

@callback authenticate(conn :: Plug.Conn.t()) ::
  {:ok, auth_context()} | {:error, :unauthorized}

Authenticate the request. Called before any operation.

Return {:ok, auth_context} where auth_context is passed to all subsequent callbacks, or {:error, :unauthorized}.

copy(auth_context, resource, dest_path, overwrite?)

@callback copy(auth_context(), resource(), dest_path :: path(), overwrite? :: boolean()) ::
  {:ok, :created | :no_content} | {:error, error()}

Copy a resource to the destination path.

overwrite? indicates whether existing resources at the destination should be replaced. Return {:ok, :created} or {:ok, :no_content} to distinguish new vs overwritten destinations.

create_collection(auth_context, path)

@callback create_collection(auth_context(), path()) :: :ok | {:error, error()}

Create a collection (directory) at the given path.

delete(auth_context, resource)

@callback delete(auth_context(), resource()) ::
  :ok | {:error, error()} | {:partial, [{path(), error()}]}

Delete a resource. For collections, the backend should handle recursive deletion. Return :ok on full success, or {:error, error} with a list of partial failures via {:partial, [{path, error}]} for 207 responses.

get_content(auth_context, resource, opts)

@callback get_content(auth_context(), resource(), opts :: map()) ::
  {:ok, iodata() | Enumerable.t()} | {:error, error()}

Read file content. opts may include :range as {start, end}.

Return {:ok, iodata} or {:ok, enumerable} for streaming.

get_members(auth_context, resource)

@callback get_members(auth_context(), resource()) ::
  {:ok, [resource()]} | {:error, error()}

List the direct members of a collection.

get_properties(auth_context, resource, list)

@callback get_properties(auth_context(), resource(), [property()]) :: [
  {property(), {:ok, term()} | {:error, :not_found}}
]

Return values for the requested properties on a resource.

Each property is a {namespace, name} tuple. Return a list of {property, {:ok, value}} or {property, {:error, :not_found}} pairs. The value should be a string or a Saxy XML element.

move(auth_context, resource, dest_path, overwrite?)

@callback move(auth_context(), resource(), dest_path :: path(), overwrite? :: boolean()) ::
  {:ok, :created | :no_content} | {:error, error()}

Move a resource to the destination path.

overwrite? indicates whether existing resources at the destination should be replaced. Should be atomic when possible.

put_content(auth_context, path, body, opts)

@callback put_content(auth_context(), path(), body :: iodata(), opts :: map()) ::
  {:ok, resource()} | {:error, error()}

Create or replace a file at the given path.

Return {:ok, resource} with the created/updated resource.

put_content_stream(auth_context, path, t, opts)

(optional)
@callback put_content_stream(
  auth_context(),
  path(),
  Enumerable.t(binary()),
  opts :: map()
) :: {:ok, resource()} | {:error, error()}

Create or replace a file from a stream of binary segments.

Optional. When implemented, the PUT handler reads the request body in bounded slices and feeds them to the backend as a Stream instead of materialising the whole body in memory. Backends that don't implement this callback are dispatched through put_content/4 as before.

Return {:ok, resource} with the created/updated resource.

resolve(auth_context, path)

@callback resolve(auth_context(), path()) :: {:ok, resource()} | {:error, error()}

Resolve a path to a resource. Return {:error, not_found_error} if the resource does not exist.

set_properties(auth_context, resource, list)

@callback set_properties(
  auth_context(),
  resource(),
  [{:set, property(), term()} | {:remove, property()}]
) :: :ok | {:error, error()}

Set or remove properties on a resource.

Operations are {:set, property, value} or {:remove, property} tuples. Must be atomic — either all succeed or none are applied.