Shared utilities for resource handlers.
Provides helper functions for:
- JSON responses
- Error handling
- ID generation
- Parameter extraction
- Expand parameter parsing
- Idempotency handling
Usage
defmodule PaperTiger.Resources.Customer do
import PaperTiger.Resource
def create(conn) do
with {:ok, params} <- validate_params(conn.params, [:email]),
customer <- build_customer(params),
{:ok, customer} <- Store.Customers.insert(customer) do
json_response(conn, 200, customer)
else
{:error, :invalid_params, field} ->
error_response(conn, PaperTiger.Error.invalid_request("Missing param", field))
end
end
end
Summary
Functions
Sends an error response using a PaperTiger.Error struct.
Generates a Stripe-style ID with the given prefix, or uses a custom ID if provided.
Gets an integer param from a map, with default value.
Gets an optional integer param, returning nil if not present.
Sends a JSON response with the given status code and body.
Stores a response for idempotency if an idempotency key is present.
Merges update parameters into an existing resource.
Normalizes a map by converting string values that look like integers to actual integers.
Parses expand[] parameters from request params.
Extracts pagination parameters from request params.
Converts a value to boolean.
Converts a value to integer.
Validates that required parameters are present.
Functions
@spec error_response(Plug.Conn.t(), PaperTiger.Error.t()) :: Plug.Conn.t()
Sends an error response using a PaperTiger.Error struct.
Generates a Stripe-style ID with the given prefix, or uses a custom ID if provided.
When custom_id is provided and starts with the expected prefix, it's used as-is.
This enables deterministic IDs for seeding and testing scenarios.
Examples
generate_id("cus") => "cus_1234567890abcdef"
generate_id("sub") => "sub_abcdef1234567890"
generate_id("cus", "cus_seed_user_1") => "cus_seed_user_1"
generate_id("cus", nil) => "cus_1234567890abcdef"
Gets an integer param from a map, with default value.
Handles form-encoded string values by coercing to integer.
Examples
get_integer(params, :amount) # => 0 if missing
get_integer(params, :amount, 100) # => 100 if missing
get_integer(%{amount: "500"}, :amount) # => 500
Gets an optional integer param, returning nil if not present.
Unlike get_integer/3 which defaults to 0, this returns nil when the key is not present, allowing callers to distinguish between "not provided" and "explicitly set to 0".
Examples
get_optional_integer(%{trial_end: 1234}, :trial_end) # => 1234
get_optional_integer(%{trial_end: "1234"}, :trial_end) # => 1234
get_optional_integer(%{}, :trial_end) # => nil
get_optional_integer(%{trial_end: 0}, :trial_end) # => 0
@spec json_response(Plug.Conn.t(), integer(), map() | struct()) :: Plug.Conn.t()
Sends a JSON response with the given status code and body.
@spec maybe_store_idempotency(Plug.Conn.t(), map()) :: :ok
Stores a response for idempotency if an idempotency key is present.
Merges update parameters into an existing resource.
Filters out nil values and immutable fields.
Normalizes a map by converting string values that look like integers to actual integers.
Useful for handling form-encoded nested maps where integer values become strings.
Examples
normalize_integer_map(%{paid_at: "1724870574", voided_at: nil})
# => %{paid_at: 1724870574, voided_at: nil}
Parses expand[] parameters from request params.
Examples
parse_expand_params(%{expand: ["customer", "subscription"]})
=> ["customer", "subscription"]
parse_expand_params(%{})
=> []
Extracts pagination parameters from request params.
Examples
parse_pagination_params(%{limit: "10", starting_after: "cus_123"})
=> %{limit: 10, starting_after: "cus_123"}
Converts a value to boolean.
Examples
to_boolean(true) => true
to_boolean("true") => true
to_boolean("false") => false
to_boolean(nil) => false
Converts a value to integer.
Examples
to_integer(123) => 123
to_integer("123") => 123
to_integer(nil) => 0
Validates that required parameters are present.
Examples
validate_params(%{email: "test@example.com"}, [:email])
=> {:ok, %{email: "test@example.com"}}
validate_params(%{}, [:email])
=> {:error, :invalid_params, :email}