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

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
tompave
Hello there, I would like to share a feature toggles library (AKA feature flags) I’ve been working on. The main package is FunWithFlags...
New
deadtrickster
I’ve just released stable versions of my Prometheus Elixir libs: Elixir client [docs]; Ecto collector [docs]; Plugs instrumenter/Export...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
archan937
It is a well-know topic within the Elixir community: “To mock or not to mock? :)” Every alchemist probably has his / her own opinion con...
New
michalmuskala
Hello everybody. I have just released Jason - a new JSON library. You might be wondering, why do we need a new library? The primary foc...
New
aditya7iyengar
Rummage.Ecto and Rummage.Phoenix provide ways to perform Searching, Sorting and Pagination over Ecto queries and Phoenix collections. Fo...
New
benlime
LiveMotion enables high performance animations declared on the server and run on the client. As a follow up to my previous thread A libr...
New
mbuhot
EctoJob A transactional job queue built with Ecto, PostgreSQL and GenStage Available on Hex.pm: ecto_job | Hex Docs: API Reference — ec...
New
ericlathrop
I built a silly site for Halloween that uses Phoenix Channels on the backend, and React on the frontend. I had many problems integrating ...
New

Other popular topics Top

freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
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
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New

Sub Categories:

We're in Beta

About us Mission Statement