Magik.Contract (Magik v0.10.0) View Source

Contract helps to define a contract for function call, and do validate contract data with:

  • Validate type
  • Validate required
  • Validate in|not_in enum
  • Valiate length for string, enumerable
  • Validate number
  • Validate string against regex pattern
  • Custom validation function
  • With support nested type
  • Clean not allowed fields

@update_user_contract %{
  user: [type: User, required: true],
  attributes: [type: %{
    email: [type: :string],
    status: [type: :string, in: ~w(active in_active)]
    age: [type: :integer, number: [min: 10, max: 80]],
  }, required: true]
}

def update_user(contract) do
  with {:ok, validated_data} do
     validated_data.user
     |> Ecto.Changeset.change(validated_data.attributes)
     |> Repo.update
  else
    {:error, errors} -> IO.inspect(errors)
  end
end

NOTES: Contract only validate data, not cast data

Support validation

Type

Support built-in types;

  • boolean
  • integer,
  • float
  • number - string or integer
  • string
  • tuple
  • map
  • array
  • list
  • atom
  • function
  • keyword
  • struct
  • array of type

Example:

Magik.Contract.validate(%{name: "Bluz"}, %{name: [type: :string]})
Magik.Contract.validate(%{id: 123}, %{name: [type: :integer]})
Magik.Contract.validate(%{id: 123}, %{name: [type: {:array, :integer}]})
Magik.Contract.validate(%{user: %User{}}, %{user: [type: User]})
Magik.Contract.validate(%{user: %User{}}, %{user: [type: {:array: User}]})

Required

Magik.Contract.validate(%{name: "Bluz"}, %{name: [type: :string, required: true]})

Allow nil

Magik.Contract.validate(
                      %{name: "Bluz", email: nil},
                      %{
                        name: [type: :string],
                        email: [type: string, allow_nil: false]
                       })

Inclusion/Exclusion

Magik.Contract.validate(
                %{status: "active"},
                %{status: [type: :string, in: ~w(active in_active)]}
               )

Magik.Contract.validate(
                %{status: "active"},
                %{status: [type: :string, not_in: ~w(banned locked)]}
               )

Format

Validate string against regex pattern

Magik.Contract.validate(
              %{email: "[email protected]"},
              %{name: [type: :string, format: ~r/.+?@.+.com/]
              })

Number

Validate number value

Magik.Contract.validate(
              %{age: 200},
              %{age: [type: :integer, number[greater_than: 0, less_than: 100]]
              })

Support conditions

  • equal_to
  • greater_than_or_equal_to | min
  • greater_than
  • less_than
  • less_than_or_equal_to | max

Length

Check length of list, map, string, keyword, tuple Supported condtions are the same with Number check

Magik.Contract.validate(
              %{title: "Hello world"},
              %{age: [type: :string, length: [min: 10, max: 100]]
              })

Custom validation function

Invoke given function to validate value. The function signature must be

func(field_name ::(String.t() | atom()), value :: any(), all_params :: map()) :: :ok | {:error, message}
Magik.Contract.validate(
              %{email: "[email protected]"},
              %{email: [type: :string, func: &validate_email/3]})

def validate_email(_name, email, _params) do
  if Regex.match?(~r/[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,4}$/, email) do
    :ok
  else
    {:error, "not a valid email"}
  end
end

Nested map

Nested map declaration is the same.

data =   %{name: "Doe John", address: %{city: "HCM", street: "NVL"} }
schema = %{
    name: [type: :string],
    address: [type: %{
            city: [type: :string],
            street: [type: :string]
          }]
  }
Magik.Contract.validate(data, schema)

Nested list

data =   %{name: "Doe John", address: [%{city: "HCM", street: "NVL"}] }
address = %{ city: [type: :string],  street: [type: :string] }
schema = %{
    name: [type: :string],
    address: [type: {:array, address}]
   }
Magik.Contract.validate(data, schema)

Link to this section Summary

Functions

Validate data against given schema

Link to this section Functions

Specs

validate(data :: map(), schema :: map()) ::
  {:ok, map()} | {:error, errors :: map()}

Validate data against given schema