glyde/wire

Decoding and encoding helpers for Discord’s JSON.

Discord tells an absent key apart from a null one, writes the same numeric field as 2 or 2.0, and adds enum values without warning. Every model needs the same handling for all three.

Types

Absent, null, or set. On a PATCH those are different instructions: absent leaves the field alone, null clears it.

pub type Field(a) =
  field.Field(a)

Values

pub fn absent() -> field.Field(a)
pub fn bits_to_list(
  flags: Int,
  table: List(#(a, Int)),
) -> List(a)
pub fn defaulted_field(
  name: String,
  inner: decode.Decoder(a),
  default: a,
  next: fn(a) -> decode.Decoder(b),
) -> decode.Decoder(b)

The shape behind list_field, flag_field, int_field and string_field: a missing key and a null one both land on default. Public because a model needing it for its own type would otherwise write a copy, and the copies drift on which of absent and null they cover.

pub fn dict_field(
  name: String,
  key: decode.Decoder(k),
  value: decode.Decoder(v),
  next: fn(dict.Dict(k, v)) -> decode.Decoder(b),
) -> decode.Decoder(b)

Missing or null gives an empty dict, so there is no Option to unwrap before a dict.get.

pub fn entries(
  entries: List(#(String, field.Field(json.Json))),
) -> List(#(String, json.Json))

object one step short of a document, for rest/body.Form, which writes the attachments array into the entries before encoding them.

pub fn enum_with_fallback(
  known: fn(Int) -> option.Option(a),
  unknown: fn(Int) -> a,
) -> decode.Decoder(a)

Decode an enum Discord may extend, keeping the raw value for one glyde has not seen. Failing would turn a new channel type into a dropped event.

pub fn flag(value: Bool) -> field.Field(json.Json)

The encoding side of flag_field: true writes the key, false leaves it out. Discord defaults every one of these to false, and reads an explicit null as an instruction rather than a default.

pub fn flag_field(
  name: String,
  default: Bool,
  next: fn(Bool) -> decode.Decoder(a),
) -> decode.Decoder(a)

A boolean that may be missing or null, with a default. Discord omits most false flags, and a few default to true.

pub fn has_bit(flags: Int, bit: Int) -> Bool

For flags Discord sends as a JSON number. Permission bitfields arrive as strings and belong in glyde/permissions.

pub fn int_field(
  name: String,
  default: Int,
  next: fn(Int) -> decode.Decoder(a),
) -> decode.Decoder(a)

A whole number that may be missing or null, with a default.

pub fn integer() -> decode.Decoder(Int)

A number Discord documents as whole. Accepts 2 and 2.0, rejects 2.5. Anything that can exceed max_exact is a String, see glyde/id.

pub fn lenient(
  inner: decode.Decoder(a),
  default: a,
) -> decode.Decoder(a)

Anything that does not decode gives default, so one field Discord changed the shape of cannot lose the whole payload.

pub fn list_field(
  name: String,
  inner: decode.Decoder(a),
  next: fn(List(a)) -> decode.Decoder(b),
) -> decode.Decoder(b)

Missing or null gives []. Discord omits embeds rather than sending [], and sends "roles": null on some partial objects.

pub fn list_to_bits(
  values: List(a),
  table: List(#(a, Int)),
) -> Int
pub fn null() -> field.Field(a)
pub fn null_flag(value: Bool) -> field.Field(json.Json)

The encoding side of present_field: true writes a null, false leaves the key out.

pub fn nullable(value: option.Option(a)) -> field.Field(a)

None becomes null, for a PATCH that clears the field.

pub fn nullable_field(
  name: String,
  inner: decode.Decoder(a),
  next: fn(option.Option(a)) -> decode.Decoder(b),
) -> decode.Decoder(b)

Required, and null is a value: absent fails, null gives None. For a key whose null says something, VoiceState.channel_id (the user left) and VoiceServerUpdate.endpoint (the voice server went away). opt_field there would forge that signal out of a payload that never sent the key.

pub fn number() -> decode.Decoder(Float)

A number Discord may write with or without a decimal point: retry_after is documented as fractional seconds and then sent as 30 when it is whole. decode.float refuses one form and decode.int the other.

pub fn number_json(value: Float) -> json.Json

The write side of number: a whole Float goes out as 3, not 3.0. Discord reads either, and picking one keeps a request body byte-exact.

pub fn object(
  entries: List(#(String, field.Field(json.Json))),
) -> json.Json

Build an object, leaving absent keys out entirely. An unset key has to vanish rather than become null: Discord reads a null as “clear this”, so writing one for every untouched field wipes half the record on a PATCH.

pub fn opt(value: option.Option(a)) -> field.Field(a)

None becomes absent. Use nullable when it should clear the field.

pub fn opt_field(
  name: String,
  inner: decode.Decoder(a),
  next: fn(option.Option(a)) -> decode.Decoder(b),
) -> decode.Decoder(b)

A field that may be missing, or present and null. Both give None.

pub fn opt_list(values: List(a)) -> field.Field(List(a))

Empty becomes absent: Discord reads [] as “clear this” on several endpoints.

pub fn present(value: a) -> field.Field(a)
pub fn present_field(
  name: String,
  next: fn(Bool) -> decode.Decoder(a),
) -> decode.Decoder(a)

Whether a key is there at all. Discord marks some booleans, RoleTags.premium_subscriber among them, by writing a null.

pub fn put(
  value: field.Field(a),
  with encode: fn(a) -> json.Json,
) -> field.Field(json.Json)

Absent and Null pass through, so encode never runs on them.

pub fn put_list(
  values: field.Field(List(a)),
  with encode: fn(a) -> json.Json,
) -> field.Field(json.Json)
pub fn raw(
  next: fn(dynamic.Dynamic) -> decode.Decoder(a),
) -> decode.Decoder(a)

Keep the raw payload beside the decoded value, so a field glyde does not model is one decode.at away.

pub fn soft_field(
  name: String,
  inner: decode.Decoder(a),
  next: fn(option.Option(a)) -> decode.Decoder(b),
) -> decode.Decoder(b)

Absent, null, or the wrong shape all give None. opt_field for a field whose shape is worth failing on, this for one that is not.

pub fn string_enum_with_fallback(
  known: fn(String) -> option.Option(a),
  unknown: fn(String) -> a,
) -> decode.Decoder(a)

The string form of the same thing, for enums Discord keys by name.

pub fn string_field(
  name: String,
  default: String,
  next: fn(String) -> decode.Decoder(a),
) -> decode.Decoder(a)

A string that may be missing or null, with a default.

pub fn tri_field(
  name: String,
  inner: decode.Decoder(a),
  next: fn(field.Field(a)) -> decode.Decoder(b),
) -> decode.Decoder(b)

All three states mean something. On Message.referenced_message, absent means Discord never looked and null means the message was deleted.

Search Document