Current section
Files
Jump to
Current section
Files
lib/phoenix/sync.ex
defmodule Phoenix.Sync do
alias Electric.Client.ShapeDefinition
@shape_keys [:namespace, :where, :columns]
@shape_params @shape_keys |> Enum.map(&to_string/1)
@type shape_specification :: [
unquote(NimbleOptions.option_typespec(Phoenix.Sync.PredefinedShape.schema()))
]
@type shape_definition ::
String.t()
| Ecto.Queryable.t()
| shape_definition()
@type param_override ::
{:namespace, String.t()}
| {:table, String.t()}
| {:where, String.t()}
| {:columns, String.t()}
@type param_overrides :: [param_override()]
defdelegate plug_opts(), to: Phoenix.Sync.Application
defdelegate client!(), to: Phoenix.Sync.Client, as: :new!
_ = """
Use request query parameters to create a `Electric.Client.ShapeDefinition`.
Useful when creating authorization endpoints that validate a user's access to
a specific shape.
## Parameters
### Required
- `table` - the Postgres [table name](https://electric-sql.com/docs/guides/shapes#table)
Note: `table` is not required in the parameters if a `:table` override is set.
### Optional
- `where` - the [Shape's where clause](https://electric-sql.com/docs/guides/shapes#where-clause)
- `columns` - The columns to include in the shape.
- `namespace` - The Postgres namespace (also called `SCHEMA`).
See
[`Electric.Client.ShapeDefinition.new/2`](`Electric.Client.ShapeDefinition.new/2`)
for more details on the parameters.
### Examples
# pass the Plug.Conn struct for a request
iex> Phoenix.Sync.shape_from_params(%Plug.Conn{params: %{"table" => "items", "where" => "visible = true" }})
{:ok, %Electric.Client.ShapeDefinition{table: "items", where: "visible = true"}}
# or a simple parameter map
iex> Phoenix.Sync.shape_from_params(%{"table" => "items", "columns" => "id,name,value" })
{:ok, %Electric.Client.ShapeDefinition{table: "items", columns: ["id", "name", "value"]}}
iex> Phoenix.Sync.shape_from_params(%{"columns" => "id,name,value" })
{:error, "Missing `table` parameter"}
## Overriding Parameter Values
If you want to hard-code some elements of the shape, ignoring the values from
the request, or to set defaults, then use the `overrides` to set specific
values for elements of the shape.
### Examples
iex> Phoenix.Sync.shape_from_params(%{"columns" => "id,name,value"}, table: "things")
{:ok, %Electric.Client.ShapeDefinition{table: "things", columns: ["id", "name", "value"]}}
iex> Phoenix.Sync.shape_from_params(%{"table" => "ignored"}, table: "things")
{:ok, %Electric.Client.ShapeDefinition{table: "things"}}
"""
@doc false
@spec shape_from_params(Plug.Conn.t() | Plug.Conn.params(), overrides :: param_overrides()) ::
{:ok, Electric.Client.ShapeDefinition.t()} | {:error, String.t()}
def shape_from_params(conn_or_map, overrides \\ [])
def shape_from_params(%Plug.Conn{} = conn, overrides) do
%{params: params} = Plug.Conn.fetch_query_params(conn)
shape_from_params(params, overrides)
end
def shape_from_params(params, overrides) when is_map(params) do
shape_params =
params
|> Map.take(@shape_params)
|> Map.new(fn
{"columns", ""} ->
{:columns, nil}
{"columns", v} when is_binary(v) ->
{:columns, :binary.split(v, ",", [:global, :trim_all])}
{k, v} ->
{String.to_existing_atom(k), v}
end)
if table = Keyword.get(overrides, :table, Map.get(params, "table")) do
ShapeDefinition.new(
table,
Enum.map(@shape_keys, fn k ->
{k, Keyword.get(overrides, k, Map.get(shape_params, k))}
end)
)
else
{:error, "Missing `table` parameter"}
end
end
end