# `Fude.Spec`
[🔗](https://github.com/hattoriai/fude/blob/v0.1.0/lib/fude/spec.ex#L1)

A chart as plain data, to save and load back.

    spec = Fude.Spec.dump(chart)
    #=> %{"fude" => 1, "kind" => "bar", "x" => "region", "y" => %{"field" => "revenue", "aggregate" => "sum"}}

    {:ok, chart} = Fude.Spec.load(spec)

A spec is a map with string keys and JSON values, so it goes into a
`:map` column, a JSON file or a message as it is, and
`load(dump(chart))` is the chart again. It carries its version
(`"fude" => 1`): `load/1` takes the specs of every earlier version and
gives today's chart.

A spec does not hold the chart's data: it names it (`"data" =>
"sales"`), and the chart gets it when it draws. `dump(chart, data:
true)` puts the data in (`"datasets"`), for a file that stands alone.

## The shape

A chart of one layer has its kind, channels and options at the top:

    %{
      "fude" => 1,
      "kind" => "line",
      "title" => "Visits",
      "x" => %{"field" => "day", "time" => "week"},
      "y" => "count",
      "color" => "source",
      "curve" => "monotone"
    }

A chart of several layers has them in `"layers"`, each with its kind,
channels and options:

    %{"fude" => 1, "layers" => [%{"kind" => "area", ...}, %{"kind" => "rule", "y" => 1000}]}

A layer, and an annotation, may have an `"id"`, text that names it
among the others: a chart shared with `Fude.Issho` gives each one, so
that two people who change two layers at once both have their change.

A channel is a field's name, or a map of the keys of `Fude.Channel`
(`"format" => "currency:EUR"` for `{:currency, "EUR"}`); a constant is
`%{"value" => 1000}`.

## Errors

`load/1` checks the whole spec and gives every error, each with where it
is and what to do:

    {:error, [%{path: ["y", "aggregate"], message: ~s|must be one of "count", "sum", ...; did you mean "sum"?|}]}

# `error`

```elixir
@type error() :: %{path: [String.t() | non_neg_integer()], message: String.t()}
```

An error of a spec: where (a path of keys and indexes), and what is wrong.

# `t`

```elixir
@type t() :: %{required(String.t()) =&gt; term()}
```

A spec: a map with string keys and JSON values.

# `dump`

```elixir
@spec dump(Fude.Chart.t(), keyword()) :: t()
```

The chart as a spec. With `data: true`, the spec holds the chart's data
too, under `"datasets"`, as `Fude.Data` normalizes it. With `layers:
true`, a chart of one layer has it in `"layers"` too, as a chart of
several does.

# `format_errors`

```elixir
@spec format_errors([error()]) :: String.t()
```

The errors as lines of text, one for each: `y.aggregate: must be ...`.

# `load`

```elixir
@spec load(t()) :: {:ok, Fude.Chart.t()} | {:error, [error()]}
```

The chart of a spec, or every error of it. A spec of an earlier version
is first migrated to this one.

# `load!`

```elixir
@spec load!(t()) :: Fude.Chart.t()
```

The chart of a spec; raises an `ArgumentError` with every error.

# `version`

```elixir
@spec version() :: pos_integer()
```

The version of the specs that `dump/1` gives.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
