Bidirectional transformation between Stripe-style REST query params and Flop format.
FlopRest is a pure transformation layer - no validation is performed. Invalid params are passed through for Flop to validate.
Quick Start
def index(conn, params) do
flop_params = FlopRest.normalize(params)
with {:ok, {events, meta}} <- Flop.validate_and_run(Event, flop_params, for: Event) do
json(conn, %{
data: EventJSON.index(events),
links: %{
self: FlopRest.build_path(conn.request_path, meta),
next: if(meta.has_next_page?, do: FlopRest.build_path(conn.request_path, meta.next_flop)),
prev: if(meta.has_previous_page?, do: FlopRest.build_path(conn.request_path, meta.previous_flop))
}
})
end
endFilters
Bare values become equality filters, operators are nested keys:
FlopRest.normalize(%{"status" => "published", "amount" => %{"gte" => "100"}})
# => %{
# "filters" => [
# %{"field" => "amount", "op" => ">=", "value" => "100"},
# %{"field" => "status", "op" => "==", "value" => "published"}
# ]
# }Sorting
Use - prefix for descending:
FlopRest.normalize(%{"sort" => "-created_at,name"})
# => %{"order_by" => ["created_at", "name"], "order_directions" => ["desc", "asc"]}Pagination
Supports cursor-based, page-based, and offset-based:
# Cursor-based
FlopRest.normalize(%{"limit" => "20", "after" => "abc"})
# => %{"first" => 20, "after" => "abc"}
# Page-based
FlopRest.normalize(%{"page" => "2", "page_size" => "25"})
# => %{"page" => 2, "page_size" => 25}
# Offset-based
FlopRest.normalize(%{"offset" => "50", "limit" => "25"})
# => %{"offset" => 50, "limit" => 25}See FlopRest.Operators for the full operator reference.
Summary
Functions
Builds a URL path with REST-style query params from a Flop struct.
Builds a URL path with REST-style query params from a Flop struct with options.
Transforms REST-style params to Flop format.
Transforms REST-style params to Flop format with options.
Converts a Flop struct or Meta struct back to REST-style query params.
Converts a Flop struct or Meta struct back to REST-style query params with options.
Functions
@spec build_path(String.t(), Flop.t() | Flop.Meta.t()) :: String.t()
Builds a URL path with REST-style query params from a Flop struct.
Existing query parameters in the path are preserved and merged with Flop params. Flop params take precedence over existing params with the same key.
Examples
iex> FlopRest.build_path("/events", %Flop{page: 2, page_size: 25})
"/events?page=2&page_size=25"
iex> FlopRest.build_path("/events", %Flop{first: 20, after: "abc"})
"/events?after=abc&limit=20"
iex> FlopRest.build_path("/events?species=dog", %Flop{page: 2, page_size: 25})
"/events?page=2&page_size=25&species=dog"
iex> FlopRest.build_path("/events?page=1", %Flop{page: 3})
"/events?page=3"
iex> FlopRest.build_path("/events", %Flop{})
"/events"
@spec build_path(String.t(), Flop.t() | Flop.Meta.t(), keyword()) :: String.t()
Builds a URL path with REST-style query params from a Flop struct with options.
Existing query parameters in the path are preserved and merged with Flop params. Flop params take precedence over existing params with the same key.
Options
:for- Schema module for looking up default values viaFlop.get_option/3. Values matching defaults will be omitted from the output.:backend- Backend module for looking up default values.
Examples
iex> FlopRest.build_path("/events", %Flop{page: 2, page_size: 25}, [])
"/events?page=2&page_size=25"
iex> FlopRest.build_path("/events", %Flop{filters: [%Flop.Filter{field: :status, op: :>=, value: 100}]}, [])
"/events?status%5Bgte%5D=100"
iex> FlopRest.build_path("/events?category=music", %Flop{page: 2, page_size: 10}, [])
"/events?category=music&page=2&page_size=10"
Transforms REST-style params to Flop format.
Examples
iex> FlopRest.normalize(%{"status" => "published"})
%{"filters" => [%{"field" => "status", "op" => "==", "value" => "published"}]}
iex> FlopRest.normalize(%{"sort" => "-created_at"})
%{"order_by" => ["created_at"], "order_directions" => ["desc"]}
iex> FlopRest.normalize(%{"page" => "2", "page_size" => "10"})
%{"page" => 2, "page_size" => 10}
iex> FlopRest.normalize(%{})
%{}
Transforms REST-style params to Flop format with options.
Options
:for- Schema module for filtering params. When provided, only fields in the schema'sfilterablelist become filters. Other params are kept in the result at the root level.
Examples
iex> FlopRest.normalize(%{"status" => "published"}, [])
%{"filters" => [%{"field" => "status", "op" => "==", "value" => "published"}]}
# With a schema that has filterable: [:name, :age]
# "custom_field" is not filterable, so it stays at root level
FlopRest.normalize(%{"name" => "Fido", "custom_field" => "value"}, for: MyApp.Pet)
# => %{
# "filters" => [%{"field" => "name", "op" => "==", "value" => "Fido"}],
# "custom_field" => "value"
# }
@spec to_query(Flop.t() | Flop.Meta.t()) :: map()
Converts a Flop struct or Meta struct back to REST-style query params.
Accepts either a Flop.t() struct or a Flop.Meta.t() struct (extracts the flop from it).
Examples
iex> FlopRest.to_query(%Flop{first: 20, after: "abc", order_by: [:name], order_directions: [:desc]})
%{"limit" => 20, "sort" => "-name", "after" => "abc"}
iex> FlopRest.to_query(%Flop{page: 2, page_size: 25})
%{"page" => 2, "page_size" => 25}
iex> FlopRest.to_query(%Flop{filters: [%Flop.Filter{field: :status, op: :==, value: "active"}]})
%{"status" => "active"}
iex> FlopRest.to_query(%Flop{})
%{}
@spec to_query( Flop.t() | Flop.Meta.t(), keyword() ) :: map()
Converts a Flop struct or Meta struct back to REST-style query params with options.
Options
:for- Schema module for looking up default values viaFlop.get_option/3. Values matching defaults will be omitted from the output.:backend- Backend module for looking up default values.
Examples
iex> FlopRest.to_query(%Flop{page: 2, page_size: 25}, [])
%{"page" => 2, "page_size" => 25}