Davy.LockStore behaviour (davy v0.3.0)

Copy Markdown View Source

Behaviour for WebDAV lock storage.

The default implementation Davy.LockStore.ETS provides in-memory locking suitable for single-node deployments. Implement this behaviour for distributed or persistent locking.

Summary

Callbacks

Check whether a lock token is valid for the given path.

Return all active locks on descendants of the given path, regardless of depth. The lock on the path itself is NOT included.

Return all active locks on the given path.

Return all active locks covering the given path — the locks on the path itself plus any ancestor locks with depth: :infinity.

Acquire a lock on the given path with the given depth.

Refresh an existing lock's timeout. Returns updated lock info.

Release a lock by token.

Types

lock_depth()

@type lock_depth() :: 0 | :infinity

lock_info()

@type lock_info() :: %{
  token: lock_token(),
  path: path(),
  scope: lock_scope(),
  type: lock_type(),
  depth: lock_depth(),
  owner: String.t() | nil,
  timeout: pos_integer(),
  expires_at: integer()
}

lock_scope()

@type lock_scope() :: :exclusive | :shared

lock_token()

@type lock_token() :: String.t()

lock_type()

@type lock_type() :: :write

path()

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

Callbacks

check_token(path, lock_token)

@callback check_token(path(), lock_token()) :: :ok | {:error, :invalid_token}

Check whether a lock token is valid for the given path.

Returns :ok if the token identifies an active lock whose path either matches the given path exactly, or is an ancestor of it with depth: :infinity.

get_descendant_locks(path)

@callback get_descendant_locks(path()) :: [lock_info()]

Return all active locks on descendants of the given path, regardless of depth. The lock on the path itself is NOT included.

Used when DELETE targets a collection — per RFC 4918 §9.11 the request must fail if any descendant is locked by a principal who has not supplied the matching token.

get_locks(path)

@callback get_locks(path()) :: [lock_info()]

Return all active locks on the given path.

Only returns locks whose path exactly matches the given path, regardless of depth. Use get_locks_covering/1 to include ancestor locks with depth: :infinity.

get_locks_covering(path)

@callback get_locks_covering(path()) :: [lock_info()]

Return all active locks covering the given path — the locks on the path itself plus any ancestor locks with depth: :infinity.

Used when checking whether a write to a resource is blocked by a parent collection lock.

lock(path, lock_scope, lock_type, lock_depth, arg5, pos_integer)

@callback lock(
  path(),
  lock_scope(),
  lock_type(),
  lock_depth(),
  String.t() | nil,
  pos_integer()
) :: {:ok, lock_token()} | {:error, :conflict}

Acquire a lock on the given path with the given depth.

depth: 0 locks only the given path. depth: :infinity locks the path and all descendants — writes to any descendant resource must provide the lock token.

refresh(lock_token, pos_integer)

@callback refresh(lock_token(), pos_integer()) ::
  {:ok, lock_info()} | {:error, :not_found}

Refresh an existing lock's timeout. Returns updated lock info.

unlock(lock_token)

@callback unlock(lock_token()) :: :ok | {:error, :not_found}

Release a lock by token.