View Source Icon.Schema behaviour (ICON 2.0 SDK v0.1.3)

This module defines a schema.

Schemas serve the purpose of validating both requests and responses. The idea is to have a map defining the types and validations for our JSON payloads.

defining-schemas

Defining Schemas

A schema can be either anonymous or not. For non-anonymous schemas, we need to use this module and define a schema using defschema/1 e.g. the following is an (incomplete) transaction.

defmodule Transaction do
  use Icon.Schema

  defschema(%{
    from: {:eoa_address, required: true},
    to: {:address, required: true},
    value: {:loop, default: 0}
  })
end

As seen in the previous example, the values change depending on the types and options each key has. The available primitive types are:

Then we have complex types:

  • Anonymous schema: t().
  • A homogeneous list of type t: list(t).
  • Any of the types listed in the list: any([{atom(), t}], atom()). The first atom is the value of the second atom in the params.

Additionally, we can implement our own primite types and named schemas with the Icon.Schema.Type behaviour and this behaviour respectively. The module name should be used as the actual type.

The available options are the following:

  • default - Default value for the key. It can be a closure for late binding.
  • required - Whether the key is required or not.
  • field - Name of the key to check to choose the right any() type. This value should be an atom(), so it'll probably come from an enum() type.

Note: nil and "" are considered empty values. They will be ignored for not mandatory keys and will add errors for mandatory keys.

variable-keys

Variable Keys

In certain cases, the keys of a map will not be defined in advaced. The wildcard key ":$variable" can be used to catch those variable keys e.g. the following schema defines a map of variable keys that map to :loop values:

%{"$variable": :loop}

So, if we get the following:

%{
  "0" => "0x2a",
  "1" => "0x54"
}

it will load it as follows:

%{
  "0" => 42,
  "1" => 84
}

schema-caching

Schema Caching

When a schema is generated with the function generate/1, it is also cached as a :persistent_term in order to avoid generating the same thing twice. This makes the first schema generation slower, but accessing the generated schema should be then quite fast.

schema-struct

Schema Struct

When defining a schema with use Icon.Schema, we can use the apply/2 function to put the loaded data into the struct e.g. for the following schema:

defmodule Block do
  use Icon.Schema

  defschema(%{
    height: :integer,
    hash: :hash
    transactions: list(Transaction)
  })
end

we can then validate a payload with the following:

payload = %{
  "height" => "0x2a",
  "hash" => "c71303ef8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238",
  "transactions" => [
    %{
      "from" => "hxbe258ceb872e08851f1f59694dac2558708ece11",
      "to" => "cxb0776ee37f5b45bfaea8cff1d8232fbb6122ec32",
      "value" => "0x2a"
    }
  ]
}

%Block{
  height: 42,
  hash: "0xc71303ef8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238",
  transactions: [
    %Transaction{
      from: "hxbe258ceb872e08851f1f59694dac2558708ece11",
      to: "cxb0776ee37f5b45bfaea8cff1d8232fbb6122ec32",
      value: 42
    }
  ]
} =
  Block
  |> Icon.Schema.generate()
  |> Icon.Schema.new(payload)
  |> Icon.Schema.load()
  |> Icon.Schema.apply(into: Block)

Link to this section Summary

Types

External types.

Internal types.

Schema state.

t()

Schema.

Type.

Callbacks

Callback for defining a schema.

Functions

Schema state.

Uses the schema behaviour.

Generates a union of types.

Applies schema state.

Generates a schema and its struct.

Dumps data from a schema state.

Generates an enum type.

Generates a full type_or_schema, given a schema definition. It caches the generated schema, to avoid regenarating the same every time.

Generates a list of types.

Loads data from a schema state.

Generates a new schema state.

Link to this section Types

Specs

external_type() ::
  internal_type()
  | {:list, external_type()}
  | {:any, [{atom(), external_type()}], atom()}
  | {:enum, [atom()]}
  | :address
  | :any
  | :binary_data
  | :boolean
  | :eoa_address
  | :error
  | :event_log
  | :hash
  | :integer
  | :loop
  | :score_address
  | :signature
  | :string
  | :timestamp

External types.

Specs

internal_type() ::
  {:list, internal_type()}
  | {:any, [{atom(), internal_type()}], atom()}
  | {:enum, [atom()]}
  | t()
  | Icon.Schema.Error
  | Icon.Schema.Types.Address
  | Icon.Schema.Types.Any
  | Icon.Schema.Types.BinaryData
  | Icon.Schema.Types.Boolean
  | Icon.Schema.Types.EOA
  | Icon.Schema.Types.EventLog
  | Icon.Schema.Types.Hash
  | Icon.Schema.Types.Integer
  | Icon.Schema.Types.Loop
  | Icon.Schema.Types.SCORE
  | Icon.Schema.Types.Signature
  | Icon.Schema.Types.String
  | Icon.Schema.Types.Timestamp
  | module()

Internal types.

Specs

state() :: %Icon.Schema{
  data: map(),
  errors: map(),
  is_valid?: boolean(),
  params: map(),
  schema: t()
}

Schema state.

Specs

t() :: module() | map()

Schema.

Specs

type() :: external_type() | {external_type(), keyword()}

Type.

Link to this section Callbacks

Specs

init() :: t()

Callback for defining a schema.

Link to this section Functions

Schema state.

Specs

__using__(any()) :: Macro.t()

Uses the schema behaviour.

Specs

any([{atom(), external_type()}], atom()) ::
  {:any, [{atom(), external_type()}], atom()}

Generates a union of types.

Link to this function

apply(state, options \\ [])

View Source

Specs

apply(
  state(),
  keyword()
) :: {:ok, any()} | {:error, Icon.Schema.Error.t()}

Applies schema state.

Link to this macro

defschema(map)

View Source (macro)

Specs

defschema(map()) :: Macro.t()

Generates a schema and its struct.

Specs

dump(state()) :: state()

Dumps data from a schema state.

Specs

enum([atom()]) :: {:enum, [atom()]}

Generates an enum type.

Link to this function

generate(type_or_schema)

View Source

Specs

generate(type() | t()) :: t() | no_return()

Generates a full type_or_schema, given a schema definition. It caches the generated schema, to avoid regenarating the same every time.

Specs

list(external_type()) :: {:list, external_type()}

Generates a list of types.

Specs

load(state()) :: state()

Loads data from a schema state.

Specs

new(t(), map() | keyword() | any()) :: state()

Generates a new schema state.