diff --git a/src/at/cid.gleam b/src/at/cid.gleam new file mode 100644 index 0000000..c31819f --- /dev/null +++ b/src/at/cid.gleam @@ -0,0 +1,89 @@ +import at/internal/base32 +import gleam/dynamic/decode.{type Decoder} + +// TYPES ----------------------------------------------------------------------- + +pub opaque type Cid { + Cid( + version: Int, + codec: Int, + hash_type: Int, + hash_size: Int, + digest: BitArray, + ) +} + +// CONSTANTS ------------------------------------------------------------------- + +@internal +pub const zero = Cid( + version: 0, + codec: 0, + hash_type: 0, + hash_size: 0, + digest: <<>>, +) + +// CONSTRUCTORS ---------------------------------------------------------------- + +pub fn from_bit_array(bits: BitArray) -> Result(Cid, Nil) { + case bits { + <<1:8, codec:8, 0x12:8, 32:8, digest:bytes-size(32)>> -> + case codec { + 0x55 | 0x71 -> Ok(Cid(1, codec, 0x12, 32, digest)) + _ -> Error(Nil) + } + + _ -> Error(Nil) + } +} + +pub fn from_string(value: String) -> Result(Cid, Nil) { + case value { + "b" <> remaining -> + case base32.decode(remaining) { + Ok(bits) -> from_bit_array(bits) + Error(_) -> Error(Nil) + } + + _ -> Error(Nil) + } +} + +pub fn decoder() -> Decoder(Cid) { + decode.one_of(bits_decoder(), [string_decoder()]) +} + +fn bits_decoder() -> Decoder(Cid) { + use bits <- decode.then(decode.bit_array) + + case from_bit_array(bits) { + Ok(cid) -> decode.success(cid) + Error(_) -> decode.failure(zero, "Cid") + } +} + +fn string_decoder() -> Decoder(Cid) { + use string <- decode.then(decode.string) + + case from_string(string) { + Ok(cid) -> decode.success(cid) + Error(_) -> decode.failure(zero, "Cid") + } +} + +// CONVERSIONS ----------------------------------------------------------------- + +pub fn to_bit_array(cid: Cid) -> BitArray { + << + cid.version:8, + cid.codec:8, + cid.hash_type:8, + cid.hash_size:8, + cid.digest:bits, + >> +} + +pub fn to_string(cid: Cid) -> String { + "b" <> base32.encode(to_bit_array(cid), True) +}