# `TypeSafeAPI.Question`
[🔗](https://github.com/typesend/typesafe_ai/blob/v0.1.0-alpha.3/lib/typesafe_api/question.ex#L1)

The three question types and the functions that validate and encode them.

`instructions` is optional on all three types: the API requires only `type`
for a Noul and `type` plus `criteria` for a Choice or a Score. Pass `nil`
(or omit it, for `TypeSafeAPI.noul/1`) and the key is left out of the request
rather than sent as `null`.

Constructors never validate; validation happens once, locally, when a
question set is sent (`TypeSafeAPI.evaluate/4`), so a Score with one level or a
Choice with one option fails fast with a `:validation` error value instead
of a round trip to the API. Only the three known types are accepted;
anything else is a local `:validation` error too.

Questions built at compile time (say, in a module attribute) can be checked
eagerly with `validate!/1`, which raises `ArgumentError` so the mistake shows
up at the line that made it.

`questions` given to `TypeSafeAPI.evaluate/4` are a keyword list, or a list of
`{id, question}` pairs — never a map: question order is the order the model
reads them in and the order they travel on the wire, and an Elixir map has no
order to preserve. `normalize/1` turns the list into an ordered
`[{id, question}]` list, which is the shape the rest of the typed layer works
with, and rejects two ids that would collide on the wire (`:billing` and
`"billing"` are the same JSON key).

Validation is also a promise about encoding: a question that passes
`validate/1` can always be JSON-encoded. Descriptions and instructions are
walked recursively, and anything `JSON` cannot encode — a struct, a tuple, a
keyword list, a pid, a map keyed by something other than a string or an atom —
is a `:validation` error rather than an exception from inside the encoder.

# `description`

```elixir
@type description() :: String.t() | map() | list()
```

Anything the API accepts as a description: a string, map, or list.

# `input`

```elixir
@type input() :: [{TypeSafeAPI.Keys.key(), t()}]
```

Questions as the caller passes them: a keyword list or list of pairs.

# `instructions`

```elixir
@type instructions() :: description() | nil
```

Instructions for a question. Optional on every type: a `nil` is left out of
the request rather than sent as `null`.

# `t`

```elixir
@type t() ::
  TypeSafeAPI.Question.Noul.t()
  | TypeSafeAPI.Question.Choice.t()
  | TypeSafeAPI.Question.Score.t()
```

# `encode`

```elixir
@spec encode(t()) :: map()
```

Encodes a validated question to its JSON-ready wire map.

# `encode_all`

```elixir
@spec encode_all([{TypeSafeAPI.Keys.key(), t()}]) :: TypeSafeAPI.JSON.Encoded.t()
```

Encodes a normalized question list to the wire `questions` object,
preserving the caller's order.

The result is a `TypeSafeAPI.JSON.Encoded`: the object is serialized to JSON
bytes here, once, so a question set reused across many states
(`TypeSafeAPI.evaluate_many/4`) is not re-serialized per request. The
`TypeSafeAPI.JSON.OrderedObject` it was built from is still available as
`encoded.object`.

# `normalize`

```elixir
@spec normalize(input()) ::
  {:ok, [{TypeSafeAPI.Keys.key(), t()}]} | {:error, TypeSafeAPI.Error.t()}
```

Turns a keyword list (or list of `{id, question}` pairs) into an ordered
`[{id, question}]` list, validating every id and question and rejecting two
ids that share a wire name.

Maps are not accepted: they have no order, and question order is what the
model sees. Pass a keyword list instead.

# `validate`

```elixir
@spec validate(term()) :: :ok | {:error, String.t()}
```

Validates a single question struct.

# `validate!`

```elixir
@spec validate!(t()) :: t()
```

Validates a question and raises `ArgumentError` if it is malformed.

Returns the question unchanged, so it can wrap a constructor:

    @urgent TypeSafeAPI.Question.validate!(TypeSafeAPI.noul("Urgent?", true: "time-sensitive"))

---

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