DripDrop.Helpers (DripDrop v0.1.0)

Copy Markdown View Source

Shared helpers for data shaping, module resolution, and small parsing tasks.

Domain modules should use these helpers when the logic is generic enough to apply across dispatch, hooks, templates, and policies.

Summary

Functions

Returns the existing atom for a binary, falling back to the original binary when the atom is unknown. Used to normalize map keys from external input without growing the global atom table.

Atomizes string keys in a map using String.to_existing_atom/1. Falls back to returning the original map unchanged when any key is unknown — this preserves all-or-nothing semantics so callers downstream can rely on a single key shape (atom-only or string-only) rather than a mixed map.

Extracts and lowercases the first email address in a value.

Extracts and lowercases the domain of the first email address in a value.

Converts an atom or existing atom string into an atom.

Fetches key from map, transparently accepting either a string or atom key. Looks up the literal key first; on miss, tries atom_or_string/1 to resolve a matching atom key without growing the atom table. Returns default when neither shape is present.

Reads a dotted path from a map with string or existing-atom keys.

Same as http_method!/1, but returns default for unknown input instead of raising. Use this for untrusted input (request payloads, user maps).

Coerces an HTTP method into the lowercase atom Req and Plug expect. Raises when the value is not one of GET / POST / PUT / PATCH / DELETE.

Resolves a module from an atom or existing Elixir module name.

Extracts the recipient domain from a value. Accepts either a payload-like map with to/recipient keys (string or atom) or a plain email-like string. Returns the lowercased domain part, or nil when no email-shaped value is present.

Calculates the next scheduled timestamp for a timing struct.

Normalizes a string key (trim, downcase, replace - with _). Atoms pass through unchanged. Returns nil for nil. Used for channel/provider keys and other slug-style identifiers.

Recursively converts map keys to strings.

Functions

atom_or_string(value)

@spec atom_or_string(atom() | binary()) :: atom() | binary()

Returns the existing atom for a binary, falling back to the original binary when the atom is unknown. Used to normalize map keys from external input without growing the global atom table.

atomize_existing_keys_strict(map)

@spec atomize_existing_keys_strict(map()) :: map()

Atomizes string keys in a map using String.to_existing_atom/1. Falls back to returning the original map unchanged when any key is unknown — this preserves all-or-nothing semantics so callers downstream can rely on a single key shape (atom-only or string-only) rather than a mixed map.

email_address(value)

@spec email_address(term()) :: binary() | nil

Extracts and lowercases the first email address in a value.

email_domain(value)

@spec email_domain(term()) :: binary() | nil

Extracts and lowercases the domain of the first email address in a value.

existing_atom(value, unknown_reason \\ :unknown_module_or_function)

@spec existing_atom(atom() | binary() | nil, term()) ::
  {:ok, atom()} | {:error, term()}

Converts an atom or existing atom string into an atom.

fetch_string_or_atom_key(map, key, default \\ nil)

@spec fetch_string_or_atom_key(map() | nil, atom() | binary(), term()) :: term()

Fetches key from map, transparently accepting either a string or atom key. Looks up the literal key first; on miss, tries atom_or_string/1 to resolve a matching atom key without growing the atom table. Returns default when neither shape is present.

Use anywhere config / credential / payload maps may arrive with string OR atom keys, instead of writing Map.get(map, key) || Map.get(map, String.to_atom(key)) (which is unsafe — String.to_atom grows the atom table).

get_path(data, path)

@spec get_path(term(), binary() | nil) :: term()

Reads a dotted path from a map with string or existing-atom keys.

http_method(method, default \\ :post)

@spec http_method(atom() | binary() | nil, atom()) ::
  :get | :post | :put | :patch | :delete

Same as http_method!/1, but returns default for unknown input instead of raising. Use this for untrusted input (request payloads, user maps).

http_method!(method)

@spec http_method!(atom() | binary()) :: :get | :post | :put | :patch | :delete

Coerces an HTTP method into the lowercase atom Req and Plug expect. Raises when the value is not one of GET / POST / PUT / PATCH / DELETE.

Use this for trusted input (e.g. enum-validated DB columns) where any failure represents a real invariant break.

module_from_string(module, missing_reason, unknown_reason \\ :unknown_module_or_function)

@spec module_from_string(module() | binary() | nil, term(), term()) ::
  {:ok, module()} | {:error, term()}

Resolves a module from an atom or existing Elixir module name.

recipient_domain(value)

@spec recipient_domain(term()) :: binary() | nil

Extracts the recipient domain from a value. Accepts either a payload-like map with to/recipient keys (string or atom) or a plain email-like string. Returns the lowercased domain part, or nil when no email-shaped value is present.

Mirrors the sender-side extraction (email_domain/1 invoked against a from/reply_to field) for the recipient side, used by the per-recipient-domain rate-limit scope to bucket sends by recipient ISP.

scheduled_for(timing)

@spec scheduled_for(Ecto.Schema.t()) :: DateTime.t()

Calculates the next scheduled timestamp for a timing struct.

slugify_key(key)

@spec slugify_key(atom() | binary() | nil) :: atom() | binary() | nil

Normalizes a string key (trim, downcase, replace - with _). Atoms pass through unchanged. Returns nil for nil. Used for channel/provider keys and other slug-style identifiers.

stringify_keys(value)

@spec stringify_keys(term()) :: term()

Recursively converts map keys to strings.