### Install Prerequisite PostgreSQL Server Source: https://github.com/ivanrublev/domo/blob/master/example_avialia/README.md Provides a Docker command to set up a PostgreSQL server, which is a prerequisite for running the example application. ```bash docker run --name postgres-domo \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=postgres \ -p 5432:5432 \ -d \ postgres:latest ``` -------------------------------- ### Configuration Precedence Example Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/configuration.md Illustrates how global, module-level, and function-level configurations are applied, with later settings overriding earlier ones. The example shows a global default overridden by a module-level setting. ```elixir # config/config.exs config :domo, gen_constructor_name: :new_struct # lib/user.ex defmodule User do use Domo, gen_constructor_name: :build # ... struct definition ... end # Usage {:ok, user} = User.build(name: "Alice") # Uses :build, not :new_struct ``` -------------------------------- ### Install Domo Dependency Source: https://github.com/ivanrublev/domo/blob/master/README.md This snippet shows how to install the Domo library using Mix.install. It's typically used at the beginning of an Elixir project or script to add the necessary dependency. ```elixir Mix.install([:domo], force: true) ``` -------------------------------- ### Example of GenConstructorName Configuration Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/types.md Demonstrates how to configure a custom constructor name using the gen_constructor_name option with Domo. ```elixir defmodule User do use Domo, gen_constructor_name: :build # Generates: # - build/1, build/2, build/0 # - build!/1, build!/0 end ``` -------------------------------- ### Domo Elixir Library Setup and Usage Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/README.md This snippet shows how to add the Domo library to your `mix.exs` file, define a validated struct with type specifications and preconditions, and demonstrates basic usage with constructor functions and reflection. ```elixir # Setup: Add to mix.exs def project do [compilers: [:domo_compiler]] end def deps do [{:domo, "~> 1.5"}] end # Define a validated struct defmodule User do use Domo defstruct name: "", age: 0 @type t :: %__MODULE__{name: String.t(), age: non_neg_integer()} # Optional: Add preconditions precond age: &(if &1 <= 150, do: :ok, else: false) end # Use it {:ok, user} = User.new(name: "Alice", age: 30) {:error, errors} = User.new(age: -5) # Reflection User.required_fields() # [:age, :name] User.typed_fields() # [:age, :name] ``` -------------------------------- ### Precondition Function Example Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/types.md Demonstrates how to define a precondition function `validate_balance` that returns `:ok`, `{:error, term()}`, or `false` to control validation behavior. ```elixir defmodule Account do use Domo defstruct [:balance, :type] @type balance :: integer() precond balance: &validate_balance/1 defp validate_balance(amount) do cond do amount >= 0 -> :ok amount < -1000 -> {:error, "Account is overdrawn"} true -> false # Triggers default error message end end end ``` -------------------------------- ### Define Preconditions for Email and Username Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/README.md Enforces constraints on data types using preconditions. This example defines rules for an email format and a minimum username length. ```elixir @type email :: String.t() precond email: &String.contains?(&1, "@") @type username :: String.t() precond username: &(byte_size(&1) >= 3) ``` -------------------------------- ### Example of RemoteTypesAsAny Configuration Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/types.md Shows how to configure remote_types_as_any to treat specific types from external modules as any(). This bypasses detailed validation for those fields. ```elixir defmodule User do use Domo, remote_types_as_any: [{ExternalLib, [:complex_type]}] defstruct [:name, :external_field] @type t :: %__MODULE__{ name: String.t(), external_field: ExternalLib.complex_type() # Will be treated as any() } end ``` -------------------------------- ### Validation Error Example (maybe_filter_precond_errors: true) Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/types.md Shows the error format when `maybe_filter_precond_errors` is true, providing a list of errors for each field. ```elixir {:error, [age: ["Age must be positive"], name: ["Name is required"]]} ``` -------------------------------- ### Validation Error Example (new/2, ensure_type/2) Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/types.md Illustrates the error format returned by `new/2` and `ensure_type/2` when validation fails, mapping field names to error messages. ```elixir {:error, [age: "Invalid value -5...", name: "Invalid value..."]} ``` -------------------------------- ### Building Dynamic Validation with Domo Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/advanced-topics.md Create dynamic validation logic by using Domo's runtime type information. This example demonstrates validating that all required fields are present in input data before attempting to create a struct. ```elixir defmodule DynamicValidator do def validate_all_required(module, data) do unless Domo.has_type_ensurer?(module) do raise "#{module} does not use Domo" end case module.new(data) do {:ok, struct} -> {:ok, struct} {:error, errors} -> missing = Enum.filter_map( module.required_fields(), fn field -> Keyword.has_key?(errors, field) end, fn field -> {field, Keyword.fetch!(errors, field)} end ) if Enum.empty?(missing) do {:ok, errors} # Some optional fields had errors else {:error, :missing_required, missing} end end end end # Usage case DynamicValidator.validate_all_required(User, input_data) do {:ok, user} -> process(user) {:error, :missing_required, missing} -> show_missing_fields(missing) end ``` -------------------------------- ### Domo Construction Error Handling Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/README.md This example demonstrates a common pattern for handling errors during struct construction with Domo. It uses a `case` statement to differentiate between successful construction (`{:ok, user}`) and validation failures (`{:error, errors}`). ```elixir case User.new(name: "Alice", age: user_input_age) do {:ok, user} -> process_user(user) {:error, errors} -> display_errors(errors) end ``` -------------------------------- ### Configure Domo Compiler in Umbrella Application Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/advanced-topics.md For umbrella projects, ensure the Domo compiler is included in the `compilers` list within the `mix.exs` file of each sub-application. This example shows configuration for a standard sub-app and one integrated with Phoenix hot-reloading. ```elixir # apps/core/mix.exs def project do [ compilers: [:domo_compiler] ++ Mix.compilers(), # ... ] end # apps/web/mix.exs (if using Phoenix hot-reload) def project do [ compilers: [:domo_compiler] ++ Mix.compilers() ++ [:domo_phoenix_hot_reload], # ... ] end ``` -------------------------------- ### Cache Validation Results for Performance Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/advanced-topics.md In performance-critical sections, consider implementing a caching mechanism to avoid redundant validation computations. This example shows a basic caching strategy using a time-to-live (TTL). ```elixir # In performance-critical paths, consider caching: defmodule CachedValidator do @cache_ttl :timer.seconds(60) def validate(data, cache_key) do case get_cached(cache_key) do {:ok, result} -> {:ok, result} :miss -> User.new(data) end end end ``` -------------------------------- ### Getting Type Information at Runtime with Domo Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/advanced-topics.md Inspect Domo-enabled modules at runtime to retrieve information about their fields, including required fields, typed fields, and fields typed as `any()`. This allows for dynamic analysis and introspection of struct types. ```elixir defmodule EntityInfo do def analyze_struct(module) do if Domo.has_type_ensurer?(module) do %{ required: module.required_fields(), typed: module.typed_fields(), typed_with_any: module.typed_fields(include_any_typed: true), type_spec: module.__t__() } else {:error, "Module does not use Domo"} end end end # Usage EntityInfo.analyze_struct(User) # %{ # required: [:email, :name], # typed: [:email, :name, :age], # typed_with_any: [...], # type_spec: "%User{email: <<...>>, name: <<...>>, age: integer() | nil}" # } ``` -------------------------------- ### Unit Test Domo Validation Rules Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/advanced-topics.md Write unit tests to verify that your Domo-defined structs correctly validate required fields, data types, and preconditions. This example uses ExUnit to assert expected validation outcomes. ```elixir defmodule UserValidationTest do use ExUnit.Case test "validates required fields" do assert {:error, errors} = User.new(%{}) assert Keyword.has_key?(errors, :email) assert Keyword.has_key?(errors, :name) end test "validates field types" do assert {:error, [age: _]} = User.new(age: "not a number") end test "validates preconditions" do assert {:error, [age: msg]} = User.new(age: -5) assert String.contains?(msg, "precondition") end test "succeeds with valid data" do assert {:ok, user} = User.new(name: "Alice", email: "alice@example.com", age: 30) assert user.name == "Alice" end end ``` -------------------------------- ### Get Required Fields Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/quick-reference.md Retrieve a list of fields that are required for struct construction using `required_fields()`. ```elixir required = User.required_fields() # [:age, :name] ``` -------------------------------- ### Create Domo Structs with Keyword List Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/api-reference/domo.md Instantiate a struct by providing a keyword list of field names and values using `User.new!()`. This method is convenient for setting specific fields during creation. ```elixir user = User.new!(name: "Alice", age: 30) ``` -------------------------------- ### Get Required Fields from Domo Struct Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/api-reference/domo.md Returns a list of struct fields that are required (non-nullable and non-any). ```elixir defmodule User do use Domo defstruct [:name, :nickname, :bio] @type t :: %__MODULE__{name: String.t(), nickname: String.t() | nil, bio: String.t() | nil} end User.required_fields() # [:name] User.required_fields(include_meta: true) # [:name] ``` -------------------------------- ### Get Type Specification Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/quick-reference.md Retrieve the binary representation of the struct's type specification using `__t__()`. ```elixir type_spec = User.__t__() # Returns binary representation of the t() type ``` -------------------------------- ### Domo Module-Level Configuration Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/README.md Demonstrates how to configure the Domo library at the module level using the `use Domo` macro. This allows for fine-grained control over default value validation, constructor function naming, and error reporting. ```elixir use Domo, skip_defaults: true, # Skip default value validation gen_constructor_name: :build, # Custom constructor name unexpected_type_error_as_warning: false # Print warnings not errors ``` -------------------------------- ### Domo Compilation Performance Comparison Source: https://github.com/ivanrublev/domo/blob/master/README.md Compares compilation times with and without Domo for a business application. Shows that Domo adds a small overhead to compilation time. ```text Mode Average (by 3 measurements) Deviation No Domo 14.826s 11.92 With Domo 15.711s 7.34 Comparison: No Domo 14.826s With Domo 15.711s - 1.06x slower ``` -------------------------------- ### Get Type Reflection String Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/advanced-topics.md Retrieves the binary representation of the `t()` type definition using `t_reflection/0` from a TypeEnsurer module. ```elixir type_string = User.TypeEnsurer.t_reflection() # Returns: "%User{name: <<_::_*8>>, age: integer() | nil}" ``` -------------------------------- ### Create PurchaseOrder with constructor arguments Source: https://github.com/ivanrublev/domo/blob/master/README.md Shows how to use the `new/1` function with a keyword list to override default struct values. The constructor accepts any Enumerable as input. ```elixir {:ok, po} = PurchaseOrder.new(%{approved_limit: 250}) ``` ```output {:ok, %PurchaseOrder{approved_limit: 250, id: 1000, items: []}} ``` -------------------------------- ### Get required fields of a struct Source: https://github.com/ivanrublev/domo/blob/master/README.md Demonstrates how to retrieve a list of fields that are required (not nil or any) for a struct using the `required_fields/0` function. ```elixir PurchaseOrder.required_fields() ``` ```output [:approved_limit, :id, :items] ``` -------------------------------- ### Large Project Configuration for Compile Output Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/configuration.md Demonstrates how to reduce compile output noise in large projects by setting `print_types_as_any_list` to false. It also shows how to customize constructor names per domain. ```elixir # Reduce compile output noise config :domo, print_types_as_any_list: false # Customize constructor names per-domain if needed # config/user_domain.exs config :domo, gen_constructor_name: :new ``` -------------------------------- ### Get Required Fields Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/errors.md Retrieve a list of fields that are mandatory for a Domo object. This is useful for ensuring data completeness before submission. ```ruby User.required_fields() ``` -------------------------------- ### Using Domo for Struct Validation Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/api-reference/domo.md Demonstrates how to use the `Domo` macro to add constructor, validation, and reflection functions to a struct. It shows creating a struct, validating an existing one, and using reflection functions. ```elixir defmodule User do use Domo defstruct name: "", age: 0 @type t :: %__MODULE__{name: String.t(), age: non_neg_integer()} end # Constructor with validation {:ok, user} = User.new(name: "Alice", age: 30) # Validation of existing struct updated_user = %{user | name: "Bob"} {:ok, valid_user} = User.ensure_type(updated_user) # Reflection User.required_fields() # [:age, :name] User.typed_fields() # [:age, :name] ``` -------------------------------- ### Recommended Ecto Project Configuration Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/configuration.md Shows recommended Domo configuration for Ecto projects, setting `skip_defaults` and `gen_constructor_name`. It also demonstrates how to use Domo within an Ecto schema. ```elixir # Recommended configuration config :domo, skip_defaults: true, gen_constructor_name: :new # In each schema: defmodule MyApp.User do use Ecto.Schema use Domo, :changeset # or use Domo, skip_defaults: true # ... end ``` -------------------------------- ### Get All Typed Fields Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/quick-reference.md Retrieve a list of all fields with type specifications using `typed_fields()`. Optionally include fields typed as `any()`. ```elixir typed = User.typed_fields() # [:age, :name] # Include fields with any() type with_any = User.typed_fields(include_any_typed: true) ``` -------------------------------- ### Defining Custom Preconditions with Domo Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/api-reference/domo.md Illustrates how to use the `precond` macro to define custom validation functions for specific types or the entire struct. It shows defining and using preconditions for ID, total, and invariant checks. ```elixir defmodule Order do use Domo defstruct id: 1000, total: 0, max_total: 1000 @type id :: non_neg_integer() precond id: &validate_id/1 @type total :: non_neg_integer() precond total: &validate_total/1 @type t :: %__MODULE__{id: id(), total: total(), max_total: non_neg_integer()} precond t: &validate_invariants/1 defp validate_id(id) do if 1000 <= id and id <= 9999 do :ok else {:error, "ID must be between 1000 and 9999"} end end defp validate_total(total) do total >= 0 end defp validate_invariants(order) do if order.total <= order.max_total do :ok else {:error, "Total (#{order.total}) exceeds maximum (#{order.max_total})"} end end end # Usage {:error, [id: msg]} = Order.new(id: 500, total: 100) {:ok, order} = Order.new(id: 1001, total: 500, max_total: 1000) ``` -------------------------------- ### Configure .formatter.exs for Domo Source: https://github.com/ivanrublev/domo/blob/master/README.md Add Domo to the import_deps in .formatter.exs to prevent `mix format` from adding extra parentheses around precond/1 macro calls. ```elixir [ import_deps: [:domo] ] ``` -------------------------------- ### Get Struct Type Spec Description Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/api-reference/domo.md Retrieves a binary string representation of the struct's 't()' type spec for debugging purposes. ```elixir defmodule User do use Domo defstruct [:name, :age] @type t :: %__MODULE__{name: String.t(), age: non_neg_integer()} end IO.puts(User.__t__()) ``` -------------------------------- ### Structs Referencing Themselves Source: https://github.com/ivanrublev/domo/blob/master/README.md Supports structs that reference themselves, enabling the construction of tree-like data structures. Example: `@type t :: %__MODULE__{left: t() | nil, right: t() | nil}`. ```elixir # Support of structs referencing themselves to build trees like @type t :: %__MODULE__{left: t() | nil, right: t() | nil} ``` -------------------------------- ### Configuring Domo to Print Types Treated as Any Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/errors.md Shows how to configure Domo to print a list of types it will treat as `any()` during its operation. This is done by setting another application environment variable. ```elixir iex> Application.put_env(:domo, :print_types_as_any_list, true) # Domo will print: "Domo will treat the following types as any()..." ``` -------------------------------- ### Handle Account Creation with User Input Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/errors.md Provides a robust way to handle account creation, processing successful creations and displaying user-friendly errors for validation failures. ```elixir {:ok, account} = Account.new(balance: 100) case Account.new(balance: user_input) do {:ok, account} -> process_account(account) {:error, errors} -> show_errors_to_user(errors) end ``` -------------------------------- ### Domo Schema with Changeset Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/configuration.md Example of defining an Ecto schema that uses Domo for changeset generation. The `:changeset` option enables automatic validation functions. ```elixir defmodule User do use Ecto.Schema use Domo, :changeset import Ecto.Changeset schema "users" do field :email, :string field :name, :string end @type t :: %__MODULE__{email: String.t(), name: String.t()} def changeset(user, attrs) do user |> cast(attrs, [:email, :name]) |> validate_type() # Available because of :changeset option end end ``` -------------------------------- ### Library Development Configuration Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/configuration.md Provides guidance for library development, advising against setting global configurations and suggesting the use of module-level configuration for per-struct needs. ```elixir # Don't set global config in libraries # Let users configure via their app config # Only use module-level config for per-struct needs defmodule MyLib.Entity do use Domo, skip_defaults: false, gen_constructor_name: :new end ``` -------------------------------- ### Get Typed Fields from Domo Struct Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/api-reference/domo.md Retrieves a list of typed field names from a Domo struct. Can optionally include fields with 'any()' types. ```elixir defmodule User do use Domo defstruct [:name, :age, :__metadata__] @type t :: %__MODULE__{name: String.t(), age: non_neg_integer(), __metadata__: any()} end User.typed_fields() # [:age, :name] User.typed_fields(include_any_typed: true) # [:__metadata__, :age, :name] User.typed_fields(include_meta: true) # [:__metadata__, :age, :name] ``` -------------------------------- ### Create a new PurchaseOrder using Domo's new function Source: https://github.com/ivanrublev/domo/blob/master/README.md Demonstrates creating a new PurchaseOrder struct using the `new/0` function generated by Domo. This function initializes the struct with default values. ```elixir PurchaseOrder.new() ``` ```output {:ok, %PurchaseOrder{approved_limit: 200, id: 1000, items: []}} ``` -------------------------------- ### Get Binary Type Representation Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/errors.md Access the binary representation of a Domo object's type. This is a low-level detail that might be used for internal processing or debugging. ```ruby User.__t__() ``` -------------------------------- ### Formatter Configuration for Domo Macros Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/configuration.md Configures `mix format` to correctly handle `precond/1` macro calls from Domo, preventing unnecessary parentheses. ```elixir # .formatter.exs [ import_deps: [:domo], # ... other config ... ] ``` -------------------------------- ### Domo Benchmark Results: Constructor vs. Ecto Changeset Source: https://github.com/ivanrublev/domo/blob/master/README.md Benchmarks the performance of Domo's `Album.new!/1` constructor against Ecto's changeset validation functions. Domo's constructor is comparable in speed to Ecto's but uses more memory. ```text Operating System: macOS CPU Information: Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz Number of Available Cores: 8 Available memory: 16 GB Elixir 1.11.0 Erlang 24.3.3 Benchmark suite executing with the following configuration: warmup: 2 s time: 8 s memory time: 2 s parallel: 1 inputs: none specified Estimated total run time: 36 s Benchmarking Domo Album.new!/1... Benchmarking Domo.Changeset validate_type/1... Benchmarking Ecto.Changeset validate_.../1... Name ips average deviation median 99th % Domo Album.new!/1 5.31 188.25 ms ±1.79% 188.28 ms 196.57 ms Ecto.Changeset validate_.../1 5.21 191.85 ms ±1.94% 191.00 ms 202.22 ms Domo.Changeset validate_type/1 3.76 266.19 ms ±1.20% 266.58 ms 271.01 ms Comparison: Domo Album.new!/1 5.31 Ecto.Changeset validate_.../1 5.21 - 1.02x slower +3.59 ms Domo.Changeset validate_type/1 3.76 - 1.41x slower +77.93 ms Memory usage statistics: Name Memory usage Domo Album.new!/1 245.73 MB Ecto.Changeset validate_.../1 186.59 MB - 0.76x memory usage -59.13956 MB Domo.Changeset validate_type/1 238.69 MB - 0.97x memory usage -7.04444 MB **All measurements for memory usage were the same** ``` -------------------------------- ### Get Typed Fields including Any type Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/api-reference/domo.md Use `User.typed_fields(include_any_typed: true)` to include fields defined with the `any()` type. This provides a comprehensive list of all fields that have type annotations. ```elixir User.typed_fields(include_any_typed: true) ``` -------------------------------- ### Get Typed Fields Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/errors.md Obtain a list of fields that have specific data types defined within a Domo object. This helps in understanding data structure and expected formats. ```ruby User.typed_fields() ``` -------------------------------- ### Format Code with Mix Source: https://github.com/ivanrublev/domo/blob/master/README.md Format the code using the 'mix format' command. Ensure all code adheres to the project's style guidelines. ```elixir mix format ``` -------------------------------- ### Get the original type spec of a struct Source: https://github.com/ivanrublev/domo/blob/master/README.md Shows how to access the original type specification (`t()`) of a struct using the `__t__/0` function, which can be useful for debugging or when using external schema generators. ```elixir PurchaseOrder.__t__() ``` ```output %PurchaseOrder{ id: integer() | nil ... } ``` -------------------------------- ### Get Typed Fields including Meta fields Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/api-reference/domo.md To include fields with underscored names (meta fields), call `User.typed_fields(include_meta: true)`. This is useful for inspecting internal or meta-information fields. ```elixir User.typed_fields(include_meta: true) ``` -------------------------------- ### Domo Global Configuration Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/README.md Shows how to configure the Domo library globally using the `config :domo` in your `config.exs` file. This affects all Domo usage within the application. ```elixir config :domo, skip_defaults: false, gen_constructor_name: :new, print_types_as_any_list: true ``` -------------------------------- ### Create Domo Structs with Defaults Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/api-reference/domo.md Use `User.new!()` to create a struct with default values for all fields. This is useful when you need an instance of the struct without providing specific data. ```elixir user = User.new!() ``` -------------------------------- ### Exclude Generated Modules from Test Coverage Source: https://github.com/ivanrublev/domo/blob/master/README.md Example configuration to exclude generated `TypeEnsurer` modules from test coverage reports. This helps in focusing the coverage metrics on your application's core logic. ```elixir # Add test_coverage configuration example to exclude generated TypeEnsurer modules from the test coverage report ``` -------------------------------- ### Handle Order Creation with User Input and Filtered Errors Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/errors.md Provides a pattern for handling order creation, including processing successful orders and displaying filtered, user-friendly error messages for struct-level precondition violations. ```elixir case Order.new(items: items, total_limit: limit) do {:ok, order} -> IO.puts("Order created successfully") {:error, [t: error_msg]} -> IO.puts("Order validation failed: #{error_msg}") {:error, other_errors} -> IO.inspect(other_errors, label: "Field errors") end # With filtered messages for user display {:error, messages} = Order.new( items: items, total_limit: limit, maybe_filter_precond_errors: true ) ``` -------------------------------- ### Nested Struct Validation Failure Example Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/errors.md Demonstrates a scenario where a nested struct within a parent struct fails validation. The error return value shows detailed nested error information. ```elixir defmodule Parent do use Domo defstruct [:children] @type t :: %__MODULE__{children: [Child.t()]} end defmodule Child do use Domo defstruct [:value] @type t :: %__MODULE__{value: non_neg_integer()} end {:error, errors} = Parent.new( children: [ %Child{value: 10}, %Child{value: -5} # Invalid! ] ) # errors contains nested error information about the invalid child ``` -------------------------------- ### Create Struct with Validation (Returning Result) Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/quick-reference.md Use the `new` constructor to create a struct, returning `{:ok, struct}` on success or `{:error, errors}` on validation failure. ```elixir {:ok, user} = User.new(name: "Alice", age: 30) {:error, errors} = User.new(age: -5) # Returns {:ok, struct} or {:error, keyword_list} ``` -------------------------------- ### Ecto Changeset Missing Required Field Example Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/errors.md Shows an Ecto schema using Domo where a required field is missing from the changeset attributes. The error structure indicates a 'can't be blank' validation error. ```elixir defmodule User do use Ecto.Schema use Domo, skip_defaults: true schema "users" do field :name, :string # Required field :bio, :string # Optional (nil by default) end @type t :: %__MODULE__{name: String.t(), bio: String.t() | nil} def changeset(user, attrs) do user |> cast(attrs, [:name, :bio]) |> Domo.Changeset.validate_type() end end changeset = User.changeset(%User{}, %{bio: "Writer"}) # changeset.valid? == false # changeset.errors == [name: {"can't be blank", [validation: :required]}] ``` -------------------------------- ### Run Linter and Tests with Mix Source: https://github.com/ivanrublev/domo/blob/master/README.md Run the linter and tests to ensure code quality and correctness. Use 'mix check' for general checks or 'mix check --failed' to re-run only failed tests. ```elixir mix check || mix check --failed ``` -------------------------------- ### print_types_as_any_list Configuration for Development Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/configuration.md Enables the console message indicating which types Domo treats as `any` during compilation. Useful for debugging type conversions in development. ```elixir # In config/dev.exs config :domo, print_types_as_any_list: true # See all type conversions ``` -------------------------------- ### User.new/2 Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/api-reference/domo.md Creates a new user struct from an enumerable or keyword list. It validates the provided data against the struct's types and preconditions. It can return either a successful struct or a map of errors. ```APIDOC ## User.new/2 ### Description Creates a new user struct from an enumerable or keyword list. It validates the provided data against the struct's types and preconditions. It can return either a successful struct or a map of errors. ### Signature ```elixir def new(enumerable :: Enumerable.t(), opts :: keyword()) :: {:ok, struct()} | {:error, any()} def new(enumerable :: Enumerable.t()) :: {:ok, struct()} | {:error, any()} def new() :: {:ok, struct()} | {:error, any()} ``` ### Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | enumerable | Enumerable.t() | [] | Any `Enumerable` that emits two-element tuples (key-value pairs) | | opts | keyword() | [] | Options for controlling error filtering behavior | ### Options | Option | Type | Default | Description | |--------|------|---------|-------------| | maybe_filter_precond_errors | boolean | false | When `true`, returns a list of error messages from precondition functions for each field instead of concatenated strings. Helpful for displaying user-friendly errors | | maybe_bypass_precond_errors | boolean | false | Internal use only | ### Return Value Returns `{:ok, struct}` if validation succeeds, or `{:error, message_by_field}` where `message_by_field` is a keyword list mapping field names to error messages or lists of error messages. ### Notes - Keys in the enumerable that don't exist in the struct are automatically discarded - Unknown keys do not cause validation to fail - Nested structs are automatically validated recursively ### Example ```elixir # Successful validation {:ok, user} = User.new(name: "Alice", age: 30) # Failed validation {:error, [age: message]} = User.new(age: -5) # Filtered precondition errors for user display {:error, [age: error_list]} = User.new(age: -5, maybe_filter_precond_errors: true) # Ignoring unknown keys {:ok, user} = User.new(name: "Alice", age: 30, unknown_field: "value") ``` ``` -------------------------------- ### Domo Compiler Configuration in mix.exs Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/README.md Illustrates how to configure Domo's compiler within the `mix.exs` file. This includes specifying the `:domo_compiler` and configuring test coverage to ignore generated TypeEnsurer modules. ```elixir def project do [ compilers: [:domo_compiler] ++ Mix.compilers(), test_coverage: [ignore_modules: [~r/\.TypeEnsurer$/]] ] end ``` -------------------------------- ### Clean and Compile Project Dependencies Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/advanced-topics.md Use this command to clean dependencies and recompile the project after dependency updates or to resolve persistent build issues. ```bash mix clean --deps && mix compile ``` -------------------------------- ### Discard Unknown Keys in Constructor Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/errors.md Demonstrates that unknown keys passed to `User.new/1` or `User.new/2` are silently ignored without raising an error. ```elixir {:ok, user} = User.new(name: "Alice", age: 30, unknown_field: "ignored") # unknown_field is ignored, no error ``` -------------------------------- ### Create Domo Structs with Map Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/api-reference/domo.md Create a struct from a map using `User.new!()`. This allows for flexible data input, especially when data is received in a map format. ```elixir user = User.new!(%{"name" => "Bob", "age" => 25}) ``` -------------------------------- ### use Domo Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/api-reference/domo.md Injects constructor, validation, and reflection functions into a struct module. It configures how Domo handles validation, naming conventions, and error reporting. ```APIDOC ## use Domo ### Description Macro that adds constructor, validation, and reflection functions to a struct module. ### Signature ```elixir defmacro __using__(opts :: keyword()) :: Macro.t() ``` ### Parameters #### opts - **opts** (keyword()) - Optional - Configuration options for Domo ### Options - **skip_defaults** (boolean) - Optional - If `true`, disables validation of default values given with `defstruct/1` to conform to the `t()` type at compile time - **gen_constructor_name** (atom) - Optional - The name of the constructor function added to the module. The raising error function name is generated automatically from the given one by adding trailing `!` - **unexpected_type_error_as_warning** (boolean) - Optional - If `true`, prints warning instead of throwing an error for field type mismatch in the raising functions - **remote_types_as_any** (keyword list) - Optional - Keyword list of type lists by modules that should be treated as `any()`. Format: `[{Module, [:type1, :type2]}, ...]` or `[{Module, :type}]` - **changeset** (boolean) - Optional - If `true`, imports `Domo.Changeset` module for Ecto integration ### Returns Macro expansion that injects the following functions into the module: - `new!/1` and `new!/0` - Constructor functions that raise on validation error - `new/2`, `new/1`, and `new/0` - Constructor functions that return `{:ok, struct}` or `{:error, errors}` - `ensure_type!/1` - Validation function that raises on error - `ensure_type/2` and `ensure_type/1` - Validation functions that return `{:ok, struct}` or `{:error, errors}` - `typed_fields/1` and `typed_fields/0` - Reflection function returning typed fields - `required_fields/1` and `required_fields/0` - Reflection function returning required fields - `__t__/0` - Reflection function returning struct's type spec description ### Example ```elixir defmodule User do use Domo defstruct name: "", age: 0 @type t :: %__MODULE__{name: String.t(), age: non_neg_integer()} end # Constructor with validation {:ok, user} = User.new(name: "Alice", age: 30) # Validation of existing struct updated_user = %{user | name: "Bob"} {:ok, valid_user} = User.ensure_type(updated_user) # Reflection User.required_fields() # [:age, :name] User.typed_fields() # [:age, :name] ``` ``` -------------------------------- ### Automatic Import of Domo.Changeset Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/api-reference/domo-changeset.md Explains how the `changeset: true` option in `use Domo` automatically imports the `Domo.Changeset` module. This simplifies usage by making functions like `validate_type/1` directly available. ```elixir defmodule User do use Ecto.Schema use Domo, changeset: true, skip_defaults: true # Domo.Changeset is automatically imported end ``` -------------------------------- ### Compile-Time: Domo Compiler Not Configured Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/errors.md This error occurs if `use Domo` is used in a project without `:domo_compiler` in the `mix.exs` compilers list. Update `mix.exs` to include the Domo compiler. ```elixir def project do [ compilers: [:domo_compiler] ++ Mix.compilers(), # ... other config ... ] end ``` -------------------------------- ### Add Domo to Project Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/quick-reference.md Add the Domo dependency to your Elixir project's mix.exs file and configure the compiler. ```elixir def deps do [{:domo, "~> 1.5"}] end def project do [compilers: [:domo_compiler] ++ Mix.compilers()] end ``` -------------------------------- ### Define Struct with Sum Types using Domo Source: https://github.com/ivanrublev/domo/blob/master/README.md Illustrates how to define a struct with a field that supports sum types (multiple possible types) using Domo. ```elixir defmodule FruitBasket do use Domo defstruct fruits: [] @type t() :: %__MODULE__{fruits: [String.t() | :banana]} end ``` -------------------------------- ### Handle ArgumentError with `new/2` Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/errors.md Demonstrates how to handle invalid arguments passed to `User.new/2` using a case statement. This is useful when you want to gracefully manage validation failures. ```elixir case User.new(name: "Alice", age: user_input_age) do {:ok, user} -> IO.puts("User created: #{user.name}") {:error, errors} -> Enum.each(errors, fn {field, message} -> IO.puts("Field #{field}: #{message}") end) end ``` -------------------------------- ### new!/1 Source: https://github.com/ivanrublev/domo/blob/master/README.md Creates a struct validating type conformance and preconditions. Raises an ArgumentError if conditions are not fulfilled, or KeyError if keys are invalid. ```APIDOC ## new!/1 ### Description Creates a struct validating type conformance and preconditions. ### Method `new!/1` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **enumerable** (Enumerable) - Any Enumerable that emits two-element tuples (key-value pairs) during enumeration. ### Request Example ```elixir MyStruct.new!(%{key1: value1, key2: value2}) ``` ### Response #### Success Response (200) - **struct_value** (instance) - The instance of the struct built from the given enumerable. #### Response Example ```elixir %MyStruct{key1: value1, key2: value2} ``` ### Error Handling - **ArgumentError**: If struct's field values do not conform to its `t()` type or precondition functions return error. - **KeyError**: If any given key-value pair does not belong to the struct. ``` -------------------------------- ### Create Struct with Keyword List or Map Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/quick-reference.md Construct a struct using keyword lists or maps, including Elixir maps and JSON-like string-keyed maps. ```elixir {:ok, user} = User.new([name: "Alice", age: 30]) {:ok, user} = User.new(%{name: "Alice", age: 30}) {:ok, user} = User.new(%{"name" => "Alice", "age" => 30}) ``` -------------------------------- ### Add Domo Dependency to mix.exs Source: https://github.com/ivanrublev/domo/blob/master/README.md Add the Domo dependency to your project's mix.exs file to include it in your project. ```elixir {:domo, "~> 1.5"} ``` -------------------------------- ### Create Struct with Validation (Raising) Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/quick-reference.md Use the `new!` constructor to create a struct, which will raise an ArgumentError if validation fails. ```elixir user = User.new!(name: "Alice", age: 30) # Raises ArgumentError on validation failure ``` -------------------------------- ### Create FruitBasket with valid sum types Source: https://github.com/ivanrublev/domo/blob/master/README.md Demonstrates creating a FruitBasket struct where the `fruits` field contains a mix of valid types as defined by the sum type. ```elixir FruitBasket.new(fruits: [:banana, "Maracuja"]) ``` ```output {:ok, %FruitBasket{fruits: [:banana, "Maracuja"]}} ``` -------------------------------- ### Rescue ArgumentError with `new!/1` Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/errors.md Shows how to rescue `ArgumentError` when using the raising variant `User.new!/1`. This is suitable for situations where an invalid input should halt execution unless explicitly handled. ```elixir begin user = User.new!(name: "Alice", age: "invalid") rescue e in ArgumentError -> IO.puts("Validation failed: #{e.message}") end ``` -------------------------------- ### Enable Phoenix Hot-Reload for Domo Source: https://github.com/ivanrublev/domo/blob/master/README.md Update mix.exs compilers to include :domo_phoenix_hot_reload for Phoenix hot-reloading of struct type ensurers. ```elixir compilers: [:domo_compiler] ++ Mix.compilers() ++ [:domo_phoenix_hot_reload] ``` -------------------------------- ### See Types Treated as Any() Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/quick-reference.md To see which types are being treated as `any()`, set the `:print_types_as_any_list` configuration and recompile. ```elixir Application.put_env(:domo, :print_types_as_any_list, true) # Recompile to see list of treated-as-any types ``` -------------------------------- ### Direct Shipment Struct Validation with Domo Source: https://github.com/ivanrublev/domo/blob/master/example_avialia/README.md Demonstrates creating and validating a Shipment struct using Domo's `new` and `ensure_type` functions. It shows how to handle potential errors and filter precondition errors. ```elixir alias ExampleAvialia.Cargos.Shipment {:ok, s} = Shipment.new(flight: "ALA-1215", kind: {:passenger_baggage, "5C"}, weight: {:kilograms, 29}, documents_count: 0, documents: []) Shipment.ensure_type(%{s | kind: {:passenger_baggage, "invalid"}}, maybe_filter_precond_errors: true) ``` -------------------------------- ### Module Overriding gen_constructor_name Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/configuration.md Demonstrates how a module can override the global `gen_constructor_name` setting. The `User` module uses the global default, while `Product` uses `:create`. ```elixir # In config/config.exs config :domo, gen_constructor_name: :build # In a module (uses global config): defmodule User do use Domo defstruct [:name] @type t :: %__MODULE__{name: String.t()} # Generated functions: build/0, build/1, build!/0, build!/1 end # In another module (overrides global config): defmodule Product do use Domo, gen_constructor_name: :create defstruct [:title] @type t :: %__MODULE__{title: String.t()} # Generated functions: create/0, create/1, create!/0, create!/1 end ``` -------------------------------- ### Basic Ecto Schema with Domo Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/quick-reference.md Integrate Domo with an Ecto schema to leverage type validation within Ecto changesets. ```elixir defmodule User do use Ecto.Schema use Domo, skip_defaults: true import Ecto.Changeset import Domo.Changeset schema "users" do field :name, :string field :age, :integer end @type t :: %__MODULE__{name: String.t(), age: non_neg_integer()} def changeset(user, attrs) do user |> cast(attrs, [:name, :age]) |> validate_type() end end ``` -------------------------------- ### Define Product Struct with Domo Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/README.md Defines a simple Elixir struct for a product using Domo. This includes defining the struct fields and their corresponding Elixir types. ```elixir defmodule Product do use Domo defstruct [:title, :price] @type t :: %__MODULE__{title: String.t(), price: Decimal.t()} end ``` -------------------------------- ### new/2 Source: https://github.com/ivanrublev/domo/blob/master/README.md Creates a struct validating type conformance and preconditions, returning the result in an {:ok, struct} tuple or {:error, message_by_field} tuple. ```APIDOC ## new/2 ### Description Creates a struct validating type conformance and preconditions, returning the result in an `{:ok, struct_value}` tuple or `{:error, message_by_field}` tuple. ### Method `new/2` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **enumerable** (Enumerable) - Any Enumerable that emits two-element tuples (key-value pairs) during enumeration. - **options** (KeywordList) - Optional. See Options below. #### Options - **`:maybe_filter_precond_errors`** (boolean) - When set to `true`, the values in `message_by_field` instead of string become a list of error messages from precondition functions. Default is `false`. ### Request Example ```elixir MyStruct.new(%{key1: value1}, maybe_filter_precond_errors: true) ``` ### Response #### Success Response (200) - **{:ok, struct_value}** (tuple) - The instance of the struct built from the given enumerable. #### Error Response - **{:error, message_by_field}** (tuple) - An appropriate error in the shape of `{:error, message_by_field}` where `message_by_field` is a keyword list of field names and error messages. #### Response Example ```elixir {:ok, %MyStruct{key1: value1}} {:error, [key1: "Invalid value"]} ``` ### Notes Keys in the `enumerable` that don't exist in the struct are automatically discarded. ``` -------------------------------- ### Global remote_types_as_any Configuration Source: https://github.com/ivanrublev/domo/blob/master/_autodocs/configuration.md Configures global handling of remote types, specifying which types from `ExternalLib` should be treated as `any`. Module-level options merge with these global settings. ```elixir config :domo, remote_types_as_any: [{ExternalLib, [:type1, :type2]}] ```