Cinder.Filter.Helpers (Cinder v0.5.0)

View Source

Helper functions for building and validating custom filters.

This module provides common patterns and utilities that custom filter developers can use to simplify their implementations.

Usage

defmodule MyApp.Filters.CustomFilter do
  use Cinder.Filter
  import Cinder.Filter.Helpers

  @impl true
  def process(raw_value, column) do
    with {:ok, trimmed} <- validate_string_input(raw_value),
         {:ok, parsed} <- parse_custom_value(trimmed) do
      build_filter(:my_filter, parsed, :equals)
    else
      _ -> nil
    end
  end
end

Summary

Functions

Builds a relationship-aware Ash query filter with embedded field support.

Debug helper for filter development.

Extracts filter options with type safety.

Converts URL-safe format back to embedded field notation.

Converts field notation to human-readable labels.

Common empty value check for most filter types.

Parses field notation to determine field type and structure.

Converts embedded field notation to URL-safe format.

Validates and parses comma-separated values.

Validates date input in ISO 8601 format.

Validates embedded field syntax and returns appropriate error messages.

Validates that a module properly implements the Cinder.Filter behaviour.

Validates a filter structure has required fields.

Validates float input with optional min/max bounds.

Validates hex color input.

Validates integer input with optional min/max bounds.

Validates operator is in allowed list.

Validates and trims string input, returning error for empty strings.

Functions

build_ash_filter(query, field, value, operator, opts \\ [])

Builds a relationship-aware Ash query filter with embedded field support.

Handles direct fields, relationship fields using dot notation, and embedded fields using bracket notation.

Examples

build_ash_filter(query, "name", "John", :equals)
build_ash_filter(query, "user.name", "John", :equals)
build_ash_filter(query, "profile[:first_name]", "John", :equals)
build_ash_filter(query, "settings[:address][:street]", "Main St", :contains)

build_filter(type, value, operator, extra_fields \\ [])

Builds a standard filter map.

Examples

iex> build_filter(:my_filter, "value", :equals)
%{type: :my_filter, value: "value", operator: :equals}

iex> build_filter(:slider, 50, :less_than_or_equal, case_sensitive: false)
%{type: :slider, value: 50, operator: :less_than_or_equal, case_sensitive: false}

debug_filter(filter_name, message, data \\ %{})

Debug helper for filter development.

Logs filter processing information when debug is enabled.

Examples

debug_filter("MyFilter", "processing input", %{input: "test"})

extract_option(options, key, default)

Extracts filter options with type safety.

Examples

iex> extract_option([min: 0, max: 100], :min, 50)
0

iex> extract_option([], :min, 50)
50

iex> extract_option(%{min: 0, max: 100}, :min, 50)
0

field_notation_from_url_safe(field)

Converts URL-safe format back to embedded field notation.

Examples

iex> field_notation_from_url_safe("profile__first_name")
"profile[:first_name]"

iex> field_notation_from_url_safe("settings__address__street")
"settings[:address][:street]"

humanize_embedded_field(field)

Converts field notation to human-readable labels.

Examples

iex> humanize_embedded_field("profile[:first_name]")
"Profile > First Name"

iex> humanize_embedded_field("user.profile[:first_name]")
"User > Profile > First Name"

is_empty_value?(value)

Common empty value check for most filter types.

Examples

iex> is_empty_value?(nil)
true

iex> is_empty_value?("")
true

iex> is_empty_value?([])
true

iex> is_empty_value?(%{value: nil})
true

iex> is_empty_value?("test")
false

parse_field_notation(field)

Parses field notation to determine field type and structure.

Examples

iex> parse_field_notation("username")
{:direct, "username"}

iex> parse_field_notation("user.name")
{:relationship, ["user"], "name"}

iex> parse_field_notation("profile[:first_name]")
{:embedded, "profile", "first_name"}

iex> parse_field_notation("settings[:address][:street]")
{:nested_embedded, "settings", ["address", "street"]}

url_safe_field_notation(field)

Converts embedded field notation to URL-safe format.

Examples

iex> url_safe_field_notation("profile[:first_name]")
"profile__first_name"

iex> url_safe_field_notation("settings[:address][:street]")
"settings__address__street"

validate_csv_input(input, opts \\ [])

Validates and parses comma-separated values.

Examples

iex> validate_csv_input("a,b,c")
{:ok, ["a", "b", "c"]}

iex> validate_csv_input("a, b , c ", trim: true)
{:ok, ["a", "b", "c"]}

iex> validate_csv_input("", min_length: 1)
{:error, :empty}

validate_date_input(value)

Validates date input in ISO 8601 format.

Examples

iex> validate_date_input("2023-12-25")
{:ok, ~D[2023-12-25]}

iex> validate_date_input("invalid-date")
{:error, :invalid}

validate_embedded_field_syntax(field)

Validates embedded field syntax and returns appropriate error messages.

Examples

iex> validate_embedded_field_syntax("profile[:first_name]")
:ok

iex> validate_embedded_field_syntax("profile[invalid]")
{:error, "Invalid embedded field syntax: missing colon"}

validate_filter_implementation(module)

Validates that a module properly implements the Cinder.Filter behaviour.

Examples

iex> validate_filter_implementation(MyApp.Filters.ValidFilter)
{:ok, "Filter implementation is valid"}

iex> validate_filter_implementation(InvalidModule)
{:error, ["Missing callback: render/4", "Missing callback: process/2"]}

validate_filter_structure(filter)

Validates a filter structure has required fields.

Examples

iex> validate_filter_structure(%{type: :text, value: "test", operator: :equals})
{:ok, %{type: :text, value: "test", operator: :equals}}

iex> validate_filter_structure(%{type: :text, value: "test"})
{:error, :missing_operator}

validate_float_input(input, opts \\ [])

Validates float input with optional min/max bounds.

Examples

iex> validate_float_input("42.5")
{:ok, 42.5}

iex> validate_float_input("42.5", min: 0.0, max: 100.0)
{:ok, 42.5}

iex> validate_float_input("150.0", max: 100.0)
{:error, :out_of_bounds}

iex> validate_float_input("abc")
{:error, :invalid}

validate_hex_color_input(value)

Validates hex color input.

Examples

iex> validate_hex_color_input("#FF0000")
{:ok, "#ff0000"}

iex> validate_hex_color_input("#fff")
{:error, :invalid}

iex> validate_hex_color_input("red")
{:error, :invalid}

validate_integer_input(input, opts \\ [])

Validates integer input with optional min/max bounds.

Examples

iex> validate_integer_input("42")
{:ok, 42}

iex> validate_integer_input("42", min: 0, max: 100)
{:ok, 42}

iex> validate_integer_input("150", max: 100)
{:error, :out_of_bounds}

iex> validate_integer_input("abc")
{:error, :invalid}

validate_operator(operator, allowed_operators)

Validates operator is in allowed list.

Examples

iex> validate_operator(:equals, [:equals, :contains])
{:ok, :equals}

iex> validate_operator(:invalid, [:equals, :contains])
{:error, :invalid_operator}

validate_string_input(value)

Validates and trims string input, returning error for empty strings.

Examples

iex> validate_string_input("  hello  ")
{:ok, "hello"}

iex> validate_string_input("")
{:error, :empty}

iex> validate_string_input(nil)
{:error, :invalid}