Estructura

Extensions for Elixir structures.
Installation
def deps do
[
{:estructura, "~> 0.1"},
# optionally you might want to add `boundary` library
# it is used by `estructura` and many other projects
# more info: https://hexdocs.pm/boundary
{:boundary, "~> 0.9", runtime: false}
]
end
I suggest adding boundary as a dependency since that is used in this project.
Features
Nested Structures
Estructura.Nested provides powerful nested structure support with validation, coercion, and generation capabilities:
defmodule User do
use Estructura.Nested
defstruct [
name: "",
address: %{
city: "",
street: %{name: "", house: 0}
}
]
# Validation rules
def validate(:name, value), do: String.length(value) > 0
def validate("address.street.house", value), do: value > 0
end
# Usage
iex> user = %User{name: "John", address: %{city: "London", street: %{name: "High St", house: 42}}}
iex> User.validate(user)
{:ok, %User{...}}Type System
Estructura provides a rich type system with built-in types and scaffolds for custom types:
Built-in Types
DateTime- For handling datetime valuesDate- For date valuesTime- For time valuesURI- For URI handlingIP- For IPv4 and IPv6 addressesString- For string values
defmodule Event do
use Estructura.Nested
defstruct [
timestamp: nil,
url: nil
]
def type(:timestamp), do: Estructura.Nested.Type.DateTime
def type(:url), do: Estructura.Nested.Type.URI
endType Scaffolds
Enum Types
Create types with predefined values:
defmodule Status do
use Estructura.Nested.Type.Enum,
elements: [:pending, :active, :completed]
end
iex> Status.validate(:pending)
{:ok, :pending}
iex> Status.validate(:invalid)
{:error, "Expected :invalid to be one of: [:pending, :active, :completed]"}Tag Sets
Manage lists of predefined tags:
defmodule Categories do
use Estructura.Nested.Type.Tags,
elements: [:tech, :art, :science]
end
iex> Categories.validate([:tech, :art])
{:ok, [:tech, :art]}
iex> Categories.validate([:invalid])
{:error, "All tags are expected to be one of [:tech, :art, :science]..."}JSON Schema Support
Estructura.Nested can derive nested structures directly from JSON Schema definitions using the json_schema/1 macro:
defmodule ApiResponse do
use Estructura.Nested
json_schema %{
"type" => "object",
"properties" => %{
"id" => %{"type" => "integer", "minimum" => 1},
"name" => %{"type" => "string", "default" => "anonymous"},
"created_at" => %{"type" => "string", "format" => "date-time"},
"address" => %{
"type" => "object",
"properties" => %{
"city" => %{"type" => "string"},
"zip" => %{"type" => "string"}
}
},
"tags" => %{"type" => "array", "items" => %{"type" => "string"}},
"status" => %{"type" => "string", "enum" => ["active", "inactive"]}
},
"required" => ["id", "name"]
}
end
iex> ApiResponse.cast(%{id: 1, name: "Alice", address: %{city: "Barcelona", zip: "08001"}})
{:ok, %ApiResponse{id: 1, name: "Alice", address: %ApiResponse.Address{city: "Barcelona", zip: "08001"}, ...}}You can also load a schema from a file:
json_schema File.read!("priv/schemas/response.json")
JSON Schema types and formats are automatically mapped to Estructura types
(e.g. "date-time" -> :datetime, "uri" -> Estructura.Nested.Type.URI,
"enum" -> Estructura.Nested.Type.Enum). Features include $ref resolution,
allOf merging, nullable types, and default value extraction.
See Estructura.Nested.JsonSchema for the full type mapping reference.
Coercion and Validation
Estructura provides flexible coercion and validation:
defmodule Temperature do
use Estructura.Nested
defstruct value: 0, unit: :celsius
def coerce(:value, str) when is_binary(str) do
case Float.parse(str) do
{num, ""} -> {:ok, num}
_ -> {:error, "Invalid number"}
end
end
def validate(:value, v), do: v >= -273.15 # Absolute zero
def validate(:unit, u), do: u in [:celsius, :fahrenheit, :kelvin]
endLazy Values
Use Estructura.Lazy for deferred computation:
defmodule Cache do
use Estructura.Nested
defstruct value: Estructura.Lazy.new(&expensive_computation/1)
def expensive_computation(_), do: :timer.sleep(1000) && :computed
endFlattening and Transformation
Convert nested structures to flat representations:
defmodule User do
use Estructura.Nested, flattenable: true
defstruct name: "", address: %{city: "", postal_code: ""}
end
iex> user = %User{name: "John", address: %{city: "London", postal_code: "SW1"}}
iex> Estructura.Flattenable.flatten(user)
%{"name" => "John", "address_city" => "London", "address_postal_code" => "SW1"}Property Testing
Estructura supports property-based testing out of the box:
defmodule UserTest do
use ExUnit.Case
use ExUnitProperties
property "valid users are validated" do
check all %User{} = user <- User.__generator__() do
assert {:ok, ^user} = User.validate(user)
end
end
endChangelog
1.11.0--json_schema/1macro to deriveEstructura.Nestedfrom JSON Schema definitions1.10.0--TimeSeriestype, propagate already set values as payload toStreamData.bind/21.8.0—validate/11.7.0— better infrastructure forTypes,URI,IP,Scaffold1.6.0—jsonify: true | module()option in a call toEstructura.Flattenable.flatten/21.5.0— no:formulaedependency1.4.1— allow functions of arity 1 incontentas coercers in a call toEstructura.Aston.coerce/21.4.0— allow coercers in a call toEstructura.Aston.coerce/21.3.0— calculated fields forEstructuraandEstructura.Nested1.2.12— export type fromEstructura.Nested1.2.11— nullable coercers1.2.10— coercers for floats, and date/time values1.2.8—Estructura.Tree→Estructura.Aston+Aston.access/2to retrieve and access key by names1.2.5—use Estructura.Nested flattenable: boolean(), jason: boolean(), transformer: boolean()1.2.3— Severalcoerce/1andvalidate/1clauses, default coercers1.2.2—Estructura.Flattenable1.2.1— Generators for:datetimeand:date1.2.0—Estructura.Nestedwould attempt to split keys by a delimiter if instructed1.1.0—Estructura.Astonto hold an AST structure, like XML1.0.0— Elixir v1.16 and deps0.6.0—Estructura.Transformto produce squeezed representations of nested structs0.5.5— export declarations of bothEstructuraandEstructura.Nestedto docs0.5.4—Estructura.Nestedallowscast/1to cast nested structs from maps0.5.3—Estructura.diff/3now understands maps0.5.2—Estructura.diff/30.5.1— [BUG] FixedCollectableandEnumerableinjected implementations0.5.0—Estructura.Nestedfor nested structures with validation, coercion, and generation0.4.2— [BUG] Fixed wrong spec forput!/30.4.1—Estructura.LazyMap.keys/1,Estructura.LazyMap.fetch_all/10.4.0—Estructura.Lazy,Estructura.LazyMap0.3.2—put!/30.3.0—coercionandvalidationare now injected as behaviours0.2.0—coercion,validation,put/3