Current section
Files
Jump to
Current section
Files
lib/a2ui/function_call.ex
defmodule A2UI.FunctionCall do
@moduledoc """
A client-evaluated function call used as a dynamic value in component properties.
The server emits FunctionCall values; the client evaluates them.
This enables computed values like formatted strings, validation conditions,
and client-side navigation without server round-trips.
## Wire format
{"call": "formatString", "args": {"template": "Hello ${/name}"}, "returnType": "string"}
## Standard functions
A2UI v0.9 defines 14 standard functions:
- **Validation:** required, regex, length, numeric, email
- **Formatting:** formatString, formatNumber, formatCurrency, formatDate, pluralize
- **Logic:** and, or, not
- **Actions:** openUrl
## Examples
# Format a string with data bindings
%A2UI.FunctionCall{
call: "formatString",
args: %{"template" => "Hello ${/user/name}"},
return_type: "string"
}
# Validate a field is not empty
A2UI.FunctionCall.required(%A2UI.BoundValue{path: "/form/name"})
"""
@type t :: %__MODULE__{
call: String.t(),
args: map(),
return_type: String.t() | nil
}
@enforce_keys [:call]
defstruct [:call, :return_type, args: %{}]
@standard_functions ~w(
required regex length numeric email
formatString formatNumber formatCurrency formatDate pluralize
and or not openUrl
)
@doc "Returns the list of 14 standard A2UI function names."
@spec standard_functions() :: [String.t()]
def standard_functions, do: @standard_functions
@doc "Creates a FunctionCall."
@spec new(String.t(), map(), String.t() | nil) :: t()
def new(call, args \\ %{}, return_type \\ nil)
def new(call, args, return_type) when is_binary(call) and is_map(args) do
%__MODULE__{call: call, args: args, return_type: return_type}
end
@doc "Creates a `formatString` function call with a template."
@spec format_string(String.t()) :: t()
def format_string(template) when is_binary(template) do
%__MODULE__{call: "formatString", args: %{"template" => template}, return_type: "string"}
end
@doc "Creates an `openUrl` function call."
@spec open_url(String.t()) :: t()
def open_url(url) when is_binary(url) do
%__MODULE__{call: "openUrl", args: %{"url" => url}}
end
@doc "Creates a `required` validation function call."
@spec required(term()) :: t()
def required(value_ref) do
%__MODULE__{call: "required", args: %{"value" => value_ref}, return_type: "boolean"}
end
@doc "Creates a `regex` validation function call."
@spec regex(term(), String.t()) :: t()
def regex(value_ref, pattern) when is_binary(pattern) do
%__MODULE__{
call: "regex",
args: %{"value" => value_ref, "pattern" => pattern},
return_type: "boolean"
}
end
@doc "Creates a `length` validation function call."
@spec length(term(), keyword()) :: t()
def length(value_ref, opts) when is_list(opts) do
args = %{"value" => value_ref}
args = if opts[:min], do: Map.put(args, "min", opts[:min]), else: args
args = if opts[:max], do: Map.put(args, "max", opts[:max]), else: args
%__MODULE__{call: "length", args: args, return_type: "boolean"}
end
@doc "Creates a `numeric` validation function call."
@spec numeric(term()) :: t()
def numeric(value_ref) do
%__MODULE__{call: "numeric", args: %{"value" => value_ref}, return_type: "boolean"}
end
@doc "Creates an `email` validation function call."
@spec email(term()) :: t()
def email(value_ref) do
%__MODULE__{call: "email", args: %{"value" => value_ref}, return_type: "boolean"}
end
@doc "Creates a `formatNumber` function call."
@spec format_number(term()) :: t()
def format_number(value_ref) do
%__MODULE__{call: "formatNumber", args: %{"value" => value_ref}, return_type: "string"}
end
@doc "Creates a `formatCurrency` function call."
@spec format_currency(term(), String.t()) :: t()
def format_currency(value_ref, currency_code) when is_binary(currency_code) do
%__MODULE__{
call: "formatCurrency",
args: %{"value" => value_ref, "currencyCode" => currency_code},
return_type: "string"
}
end
@doc "Creates a `formatDate` function call."
@spec format_date(term(), String.t()) :: t()
def format_date(value_ref, format) when is_binary(format) do
%__MODULE__{
call: "formatDate",
args: %{"value" => value_ref, "format" => format},
return_type: "string"
}
end
@doc "Creates a `pluralize` function call."
@spec pluralize(term(), term(), term()) :: t()
def pluralize(count, singular, plural) do
%__MODULE__{
call: "pluralize",
args: %{"count" => count, "singular" => singular, "plural" => plural},
return_type: "string"
}
end
@doc "Creates an `and` logic function call."
@spec fn_and(list()) :: t()
def fn_and(conditions) when is_list(conditions) do
%__MODULE__{call: "and", args: %{"conditions" => conditions}, return_type: "boolean"}
end
@doc "Creates an `or` logic function call."
@spec fn_or(list()) :: t()
def fn_or(conditions) when is_list(conditions) do
%__MODULE__{call: "or", args: %{"conditions" => conditions}, return_type: "boolean"}
end
@doc "Creates a `not` logic function call."
@spec fn_not(term()) :: t()
def fn_not(condition) do
%__MODULE__{call: "not", args: %{"condition" => condition}, return_type: "boolean"}
end
end