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.