IvanR

IvanR

Domo - model a business domain with type-safe structs and field type range checks

Domo makes your structure types work for data conformance validation. And it enables automatic range checking for field types at run-time.

Domo adds a new/1 constructor function to the structure module that builds an instance only if all fields conform to the struct’s t() type and all specified precondition functions for filed types return true.

That is useful for boundary data validation coming as decoded JSON from Jason or from :erlang.binary_to_term/1, and for validation when converting one core struct into another like in CQRS framework Commanded.

Domo makes it possible to validate model types in microservice setups between depending applications by sharing common modules with type definitions among codebases.

See typical usage in Readme.md via:

Run in Livebook

Shortly it looks like:

defmodule Customer do
  use Domo

  defstruct title: :none, name: "", age: 0

  @type title :: :mr | :ms | :dr | :none
  @type name :: String.t()

  @type age :: non_neg_integer()
  precond age: &(&1 < 300)

  @type t :: %__MODULE__{title: title(), name: name(), age: age()}
  precond t: &(String.length(&1.name) < 10)
end

iex(1)> Customer.new(title: :dr, name: "John", age: 25)           
{:ok, %Customer{age: 25, name: "John", title: :dr}}

iex(2)> Customer.new(title: :dr, name: nil, age: 25)              
{:error, [name: "Invalid value nil for field :name of %Customer{}. Expected the value \
matching the <<_::_*8>> type."]}

iex(3)> Customer.new(title: :dr, name: "Johnny be good", age: 25) 
{:error, [t: "Invalid value %Customer{age: 25, name: \"Johnny be good\", title: :dr}. \
Expected the value matching the Customer.t() type. And a true value from the precondition \
function \"&(String.length(&1.name) < 10)\" defined for Customer.t() type."]}

Example applications:

Domo main features are:

  • automatic generation of constructor and insurance functions validating struct’s fields conformance to @type t(), which are: new!/1, new/1, ensure_type!/1, and ensure_type/1
  • validation of nested structs referenced in the type spec
  • support for boolean precondition functions attached to the user-defined type or the whole struct’s t() type for values range check
  • validation of struct default values at compile-time
  • recompilation of dependencies when type definition changes

More information here:

Most Liked

IvanR

IvanR

The precond, which works only in association with the given type, is more like an extension to constrain the type’s values even more or to provide a custom error message.

The main difference in approaches is that Domo focuses on declarative constraint definition for structs with types combinations. And that the validation of nested structs is supported automatically just by @type spec. You can find an example illustrating that in the readme.

With Domo, it takes less code to have validation functions for structs. That can take less time to change them later.

IvanR

IvanR

1.5.8 - September 11, 2022

  • Domo generated constructor functions new!/1/0 and new/2/1/0 become overridable.
  • Improves Ecto changeset validation. Now the Domo.Changeset.validate_type/1 skips has_many and other assoc fields automatically, so the cast_assoc(:field) can be called to do the associations validation sequentually.

The one validate_type/1 call in the changeset gives a 1.5x slower execution than the bunch of equivalent Ecto’s validate_... calls.

At the same time, the execution duration of the pure Domo generated constructor function new!/1 is the same as of building a struct with Ecto changeset approach.

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

You can find all benchmark results in the README :victory_hand:

The overridable constructors open the remarkable feature of injecting the generated default values into a struct, keeping the values validated. Let the struct using Domo have a token field, then the generation of the default value can be done like that:

def new!([_|_] = fields) do
  super(Keyword.merge(fields, token: random_string(8)))
end

So no matter what random_string returns, the super function will ensure that it matches the type of the token field defined in t().

Magnificent! :slightly_smiling_face:

IvanR

IvanR

1.5.10 - November 16, 2022

  • Improve compatibility with ElixirLS. After running mix deps.update domo, please, remove .elixir_ls in your project directory and reopen VSCode.

Now struct type checking errors are in the VSCode view :tada:

IvanR

IvanR

v1.2.4 – June 6, 2021

  • Speedup resolving the struct types
  • Limit the number of allowed fields types combinations to 4096
  • Support Range.t() and MapSet.t()
  • Keep type ensurers source code after compiling an umbrella project
  • Remove preconditions manifest file on mix clean command
  • List processed structs giving mix --verbose option
IvanR

IvanR

v1.2.9 - August 8, 2021

Domo is lightened by extraction of the tagged_tuple library (finally! :tada:)

Documentation is rewritten from scratch.

The /example_avialia project is updated to demonstrate using of Domo to validate Ecto changesets.

Other changes:

  • Fix bug to acknowledge that type has been changed after a failed compilation.

  • Fix bug to match structs not using Domo with a field of any() type with and without precondition.

  • Add typed_fields/1 and required_fields/1 functions.

  • Add maybe_filter_precond_errors: true option that filters errors from precondition functions for better output for the user.

Where Next?

Popular in Libraries Top

josevalim
Hi everyone, We would like to announce that Plataformatec is working on a new MySQL driver called MyXQL. Our goal is to eventually integ...
New
Crowdhailer
The latest release of Ace (0.10.0) includes serving content over HTTP/2. I have started writing a webserver to teach my self more about...
New
nikokozak
Hello all, I’ve been working on Svonix - a library for quickly integrating Svelte components into Phoenix views. It’s a much-needed succ...
New
cjen07
parameterized pipe in elixir: |n&gt; edit: negative index in |n&gt; and mixed usage with |&gt; are supported example: use ParamP...
New
mindok
What is ContEx? A pure Elixir server-side data plotting/charting library outputting SVG. It has nice barcharts in particular and works g...
New
blatyo
https://www.conduitframework.com/ The best overview for how things are tied together is this presentation. Modules and functions are pre...
New
benlime
I created a new library GitHub - benvp/ex_cva: Class Variance Authority for Elixir which aims to make it very easy to define different va...
New
Crowdhailer
I have been updating a library that allows you to pipe between functions that use the erlang result tuple convention. Assuming you have...
New
OvermindDL1
I created a new library (rather I pulled out a couple files from my big project), it manages an operating system PID file for the BEAM. ...
New
Azolo
Hey everyone, I just released WebSockex which is a Elixir WebSocket client. WebSockex strives to work as a OTP special process, be RFC6...
New

Other popular topics Top

sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
977 41022 311
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
William
I would like to know that is there any online source for learning Phoenix Framework for building E-Commerce Store? Any advantage on build...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New

Sub Categories:

We're in Beta

About us Mission Statement