PaperTiger.Resource (PaperTiger v0.9.20)

View Source

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

error_response(conn, error)

@spec error_response(Plug.Conn.t(), PaperTiger.Error.t()) :: Plug.Conn.t()

Sends an error response using a PaperTiger.Error struct.

generate_id(prefix, custom_id \\ nil)

@spec generate_id(String.t(), String.t() | nil) :: String.t()

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"

get_integer(params, key, default \\ 0)

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

get_optional_integer(params, key)

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

json_response(conn, status, body)

@spec json_response(Plug.Conn.t(), integer(), map() | struct()) :: Plug.Conn.t()

Sends a JSON response with the given status code and body.

maybe_store_idempotency(conn, response)

@spec maybe_store_idempotency(Plug.Conn.t(), map()) :: :ok

Stores a response for idempotency if an idempotency key is present.

merge_updates(existing, updates, immutable_fields \\ [:id, :object, :created])

@spec merge_updates(map(), map(), [atom()]) :: map()

Merges update parameters into an existing resource.

Filters out nil values and immutable fields.

normalize_integer_map(map)

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}

parse_expand_params(params)

@spec parse_expand_params(map()) :: [String.t()]

Parses expand[] parameters from request params.

Examples

parse_expand_params(%{expand: ["customer", "subscription"]})
=> ["customer", "subscription"]

parse_expand_params(%{})
=> []

parse_pagination_params(params)

@spec parse_pagination_params(map()) :: map()

Extracts pagination parameters from request params.

Examples

parse_pagination_params(%{limit: "10", starting_after: "cus_123"})
=> %{limit: 10, starting_after: "cus_123"}

to_boolean(arg1)

Converts a value to boolean.

Examples

to_boolean(true) => true
to_boolean("true") => true
to_boolean("false") => false
to_boolean(nil) => false

to_integer(value)

Converts a value to integer.

Examples

to_integer(123) => 123
to_integer("123") => 123
to_integer(nil) => 0

validate_params(params, required_fields)

@spec validate_params(map(), [atom()]) ::
  {:ok, map()} | {:error, :invalid_params, atom()}

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}