View Source Icon.Schema behaviour (ICON 2.0 SDK v0.2.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: :pos_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

@type 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
  | :pos_integer
  | :score_address
  | :signature
  | :string
  | :timestamp

External types.

@type 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.PosInteger
  | Icon.Schema.Types.SCORE
  | Icon.Schema.Types.Signature
  | Icon.Schema.Types.String
  | Icon.Schema.Types.Timestamp
  | module()

Internal types.

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

Schema state.

@type t() :: module() | map()

Schema.

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

Type.

Link to this section Callbacks

@callback init() :: t()

Callback for defining a schema.

Link to this section Functions

Schema state.

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

Uses the schema behaviour.

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

Generates a union of types.

Link to this function

apply(state, options \\ [])

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

Applies schema state.

Link to this macro

defschema(map)

View Source (macro)
@spec defschema(map()) :: Macro.t()

Generates a schema and its struct.

@spec dump(state()) :: state()

Dumps data from a schema state.

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

Generates an enum type.

Link to this function

generate(type_or_schema)

View Source
@spec 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.

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

Generates a list of types.

@spec load(state()) :: state()

Loads data from a schema state.

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

Generates a new schema state.