import gleam/int import gleam/list import gleam/bit_string import bson/md5 import bson/uuid import bson/types import bson/custom import bson/generic import bson/object_id import birl/time import birl/duration type Entity { Entity(kind: types.Kind, value: BitString) } pub fn encode(doc: List(#(String, types.Value))) -> BitString { case document(doc) { Entity(kind: _, value: value) -> value } } fn document(doc: List(#(String, types.Value))) -> Entity { let doc = doc |> list.map(encode_kv) |> bit_string.concat let size = bit_string.byte_size(doc) + 5 Entity( kind: types.document, value: [<>, doc, <<0>>] |> bit_string.concat, ) } fn encode_kv(pair: #(String, types.Value)) -> BitString { let key = <> let value = case pair.1 { types.Null -> null() types.Min -> min() types.Max -> max() types.JS(value) -> js(value) types.Str(value) -> string(value) types.Array(value) -> array(value) types.Double(value) -> double(value) types.Boolean(value) -> boolean(value) types.Integer(value) -> integer(value) types.Document(value) -> document(value) types.DateTime(value) -> datetime(value) types.ObjectId(value) -> object_id(value) types.Timestamp(value) -> timestamp(value) types.Binary(types.MD5(value)) -> md5(value) types.Binary(types.UUID(value)) -> uuid(value) types.Binary(types.Custom(value)) -> custom(value) types.Binary(types.Generic(value)) -> generic(value) types.Regex(#(pattern, options)) -> regex(pattern, options) } case value { Entity(kind: kind, value: value) -> [kind.code, key, value] |> bit_string.concat } } fn null() -> Entity { Entity(kind: types.null, value: <<>>) } fn min() -> Entity { Entity(kind: types.min, value: <<>>) } fn max() -> Entity { Entity(kind: types.max, value: <<>>) } fn js(value: String) -> Entity { let length = bit_string.byte_size(<>) + 1 Entity(kind: types.js, value: <>) } fn string(value: String) -> Entity { let length = bit_string.byte_size(<>) + 1 Entity(kind: types.string, value: <>) } fn array(value: List(types.Value)) -> Entity { case list.index_map(value, fn(index, item) { #(int.to_string(index), item) }) |> document { Entity(kind: _, value: value) -> Entity(kind: types.array, value: value) } } fn double(value: Float) -> Entity { Entity(kind: types.double, value: <>) } fn boolean(value: Bool) -> Entity { case value { True -> Entity(kind: types.boolean, value: <<1>>) False -> Entity(kind: types.boolean, value: <<0>>) } } fn integer(value: Int) -> Entity { case value < types.int32_max && value > types.int32_min { True -> Entity(kind: types.int32, value: <>) False -> Entity(kind: types.int64, value: <>) } } fn datetime(value: time.Time) -> Entity { let duration.Duration(value) = time.difference(value, time.unix_epoch) let value = value / 1000 Entity(kind: types.datetime, value: <>) } fn object_id(value: object_id.ObjectId) -> Entity { Entity(kind: types.object_id, value: object_id.to_bit_string(value)) } fn timestamp(value: Int) -> Entity { Entity(kind: types.timestamp, value: <>) } fn md5(value: md5.MD5) -> Entity { let value = md5.to_bit_string(value) let length = bit_string.byte_size(value) Entity( kind: types.binary, value: [<>, types.md5.code, value] |> bit_string.concat, ) } fn uuid(value: uuid.UUID) -> Entity { let value = uuid.to_bit_string(value) let length = bit_string.byte_size(value) Entity( kind: types.binary, value: [<>, types.uuid.code, value] |> bit_string.concat, ) } fn custom(value: custom.Custom) -> Entity { let #(code, value) = custom.to_bit_string_with_code(value) let length = bit_string.byte_size(value) Entity( kind: types.binary, value: [<>, <>, value] |> bit_string.concat, ) } fn generic(value: generic.Generic) -> Entity { let value = generic.to_bit_string(value) let length = bit_string.byte_size(value) Entity( kind: types.binary, value: [<>, types.generic.code, value] |> bit_string.concat, ) } fn regex(pattern: String, options: String) -> Entity { Entity( kind: types.regex, value: [<>] |> bit_string.concat, ) }