use std::fmt::Debug;
use std::marker::PhantomData;
use arrow2::array::{
Array, BinaryArray, BooleanArray, MutableArray, MutableBinaryArray, MutableBooleanArray,
MutablePrimitiveArray, MutableUtf8Array, PrimitiveArray, Utf8Array,
};
use arrow2::bitmap::{Bitmap, MutableBitmap};
use arrow2::buffer::Buffer;
use arrow2::datatypes::DataType as ArrowLogicalType;
use arrow2::io::parquet::write::Encoding;
use arrow2::types::NativeType;
use bytes::BufMut;
use crate::columnar::sealed::ColumnRef;
use crate::columnar::{
ColumnCfg, ColumnFormat, ColumnGet, ColumnPush, Data, DataType, PartDecoder, PartEncoder,
Schema,
};
use crate::dyn_struct::{
ColumnsMut, ColumnsRef, DynStruct, DynStructCfg, DynStructCol, DynStructMut, DynStructRef,
};
use crate::stats::{BytesStats, OptionStats, PrimitiveStats, StatsFn, StructStats};
use crate::{Codec, Codec64, Opaque, ShardId};
#[derive(Debug, Default)]
pub struct UnitSchema;
impl PartEncoder<'_, ()> for UnitSchema {
fn encode(&mut self, _val: &()) {}
}
impl PartDecoder<'_, ()> for UnitSchema {
fn decode(&self, _idx: usize, _val: &mut ()) {}
}
impl Schema<()> for UnitSchema {
type Encoder<'a> = Self;
type Decoder<'a> = Self;
fn columns(&self) -> DynStructCfg {
DynStructCfg::from(Vec::new())
}
fn decoder<'a>(&self, cols: ColumnsRef<'a>) -> Result<Self::Decoder<'a>, String> {
let () = cols.finish()?;
Ok(UnitSchema)
}
fn encoder<'a>(&self, cols: ColumnsMut<'a>) -> Result<Self::Encoder<'a>, String> {
let (_len, ()) = cols.finish()?;
Ok(UnitSchema)
}
}
impl Codec for () {
type Storage = ();
type Schema = UnitSchema;
fn codec_name() -> String {
"()".into()
}
fn encode<B>(&self, _buf: &mut B)
where
B: BufMut,
{
}
fn decode<'a>(buf: &'a [u8]) -> Result<Self, String> {
if !buf.is_empty() {
return Err(format!("decode expected empty buf got {} bytes", buf.len()));
}
Ok(())
}
}
pub struct SimpleEncoder<'a, X, T: Data>(&'a mut usize, SimpleEncoderFn<'a, X, T>);
enum SimpleEncoderFn<'a, X, T: Data> {
Cast {
col: &'a mut T::Mut,
encode: for<'b> fn(&'b X) -> T::Ref<'b>,
},
Push {
col: &'a mut T::Mut,
encode: fn(&mut T::Mut, &X),
},
}
impl<'a, X, T: Data> PartEncoder<'a, X> for SimpleEncoder<'a, X, T> {
fn encode(&mut self, val: &X) {
*self.0 += 1;
match &mut self.1 {
SimpleEncoderFn::Cast { col, encode } => ColumnPush::<T>::push(*col, encode(val)),
SimpleEncoderFn::Push { col, encode } => encode(col, val),
}
}
}
pub struct SimpleDecoder<'a, X, T: Data> {
col: &'a T::Col,
decode: fn(T::Ref<'a>, &mut X),
}
impl<'a, X, T: Data> PartDecoder<'a, X> for SimpleDecoder<'a, X, T> {
fn decode(&self, idx: usize, val: &mut X) {
(self.decode)(ColumnGet::<T>::get(self.col, idx), val)
}
}
pub struct SimpleSchema<X, T: Data>(PhantomData<(X, T)>);
impl<X, T: Data> SimpleSchema<X, T> {
pub fn columns(cfg: &T::Cfg) -> DynStructCfg {
DynStructCfg::from(vec![("".to_owned(), cfg.as_type(), StatsFn::Default)])
}
pub fn decoder<'a>(
mut cols: ColumnsRef<'a>,
decode: fn(T::Ref<'a>, &mut X),
) -> Result<SimpleDecoder<'a, X, T>, String> {
let col = cols.col::<T>("")?;
let () = cols.finish()?;
Ok(SimpleDecoder { col, decode })
}
pub fn encoder<'a>(
mut cols: ColumnsMut<'a>,
encode: for<'b> fn(&'b X) -> T::Ref<'b>,
) -> Result<SimpleEncoder<'a, X, T>, String> {
let col = cols.col::<T>("")?;
let (len, ()) = cols.finish()?;
Ok(SimpleEncoder(len, SimpleEncoderFn::Cast { col, encode }))
}
pub fn push_encoder<'a>(
mut cols: ColumnsMut<'a>,
encode: fn(&mut T::Mut, &X),
) -> Result<SimpleEncoder<'a, X, T>, String> {
let col = cols.col::<T>("")?;
let (len, ()) = cols.finish()?;
Ok(SimpleEncoder(len, SimpleEncoderFn::Push { col, encode }))
}
}
#[derive(Debug, Clone, Default)]
pub struct StringSchema;
impl Schema<String> for StringSchema {
type Encoder<'a> = SimpleEncoder<'a, String, String>;
type Decoder<'a> = SimpleDecoder<'a, String, String>;
fn columns(&self) -> DynStructCfg {
SimpleSchema::<String, String>::columns(&())
}
fn decoder<'a>(&self, cols: ColumnsRef<'a>) -> Result<Self::Decoder<'a>, String> {
SimpleSchema::<String, String>::decoder(cols, |val, ret| val.clone_into(ret))
}
fn encoder<'a>(&self, cols: ColumnsMut<'a>) -> Result<Self::Encoder<'a>, String> {
SimpleSchema::<String, String>::encoder(cols, |val| val.as_str())
}
}
impl Codec for String {
type Storage = ();
type Schema = StringSchema;
fn codec_name() -> String {
"String".into()
}
fn encode<B>(&self, buf: &mut B)
where
B: BufMut,
{
buf.put(self.as_bytes())
}
fn decode<'a>(buf: &'a [u8]) -> Result<Self, String> {
String::from_utf8(buf.to_owned()).map_err(|err| err.to_string())
}
}
#[derive(Debug, Clone, Default)]
pub struct VecU8Schema;
impl Schema<Vec<u8>> for VecU8Schema {
type Encoder<'a> = SimpleEncoder<'a, Vec<u8>, Vec<u8>>;
type Decoder<'a> = SimpleDecoder<'a, Vec<u8>, Vec<u8>>;
fn columns(&self) -> DynStructCfg {
SimpleSchema::<Vec<u8>, Vec<u8>>::columns(&())
}
fn decoder<'a>(&self, cols: ColumnsRef<'a>) -> Result<Self::Decoder<'a>, String> {
SimpleSchema::<Vec<u8>, Vec<u8>>::decoder(cols, |val, ret| val.clone_into(ret))
}
fn encoder<'a>(&self, cols: ColumnsMut<'a>) -> Result<Self::Encoder<'a>, String> {
SimpleSchema::<Vec<u8>, Vec<u8>>::encoder(cols, |val| val.as_slice())
}
}
impl Codec for Vec<u8> {
type Storage = ();
type Schema = VecU8Schema;
fn codec_name() -> String {
"Vec<u8>".into()
}
fn encode<B>(&self, buf: &mut B)
where
B: BufMut,
{
buf.put(self.as_slice())
}
fn decode<'a>(buf: &'a [u8]) -> Result<Self, String> {
Ok(buf.to_owned())
}
}
impl Codec for ShardId {
type Storage = ();
type Schema = ShardIdSchema;
fn codec_name() -> String {
"ShardId".into()
}
fn encode<B: BufMut>(&self, buf: &mut B) {
buf.put(self.to_string().as_bytes())
}
fn decode<'a>(buf: &'a [u8]) -> Result<Self, String> {
let shard_id = String::from_utf8(buf.to_owned()).map_err(|err| err.to_string())?;
shard_id.parse()
}
}
#[derive(Debug)]
pub struct ShardIdSchema;
impl Schema<ShardId> for ShardIdSchema {
type Encoder<'a> = SimpleEncoder<'a, ShardId, String>;
type Decoder<'a> = SimpleDecoder<'a, ShardId, String>;
fn columns(&self) -> DynStructCfg {
SimpleSchema::<ShardId, String>::columns(&())
}
fn decoder<'a>(&self, cols: ColumnsRef<'a>) -> Result<Self::Decoder<'a>, String> {
SimpleSchema::<ShardId, String>::decoder(cols, |val, ret| {
*ret = val.parse().expect("should be valid ShardId")
})
}
fn encoder<'a>(&self, cols: ColumnsMut<'a>) -> Result<Self::Encoder<'a>, String> {
SimpleSchema::<ShardId, String>::push_encoder(cols, |col, val| {
ColumnPush::<String>::push(col, &val.to_string())
})
}
}
impl Codec64 for i64 {
fn codec_name() -> String {
"i64".to_owned()
}
fn encode(&self) -> [u8; 8] {
self.to_le_bytes()
}
fn decode(buf: [u8; 8]) -> Self {
i64::from_le_bytes(buf)
}
}
impl Codec64 for u64 {
fn codec_name() -> String {
"u64".to_owned()
}
fn encode(&self) -> [u8; 8] {
self.to_le_bytes()
}
fn decode(buf: [u8; 8]) -> Self {
u64::from_le_bytes(buf)
}
}
impl Opaque for u64 {
fn initial() -> Self {
u64::MIN
}
}
impl Opaque for i64 {
fn initial() -> Self {
i64::MIN
}
}
impl Data for bool {
type Cfg = ();
type Ref<'a> = bool;
type Col = Bitmap;
type Mut = MutableBitmap;
type Stats = PrimitiveStats<bool>;
}
impl ColumnCfg<bool> for () {
fn as_type(&self) -> DataType {
DataType {
optional: false,
format: ColumnFormat::Bool,
}
}
}
impl Data for Option<bool> {
type Cfg = ();
type Ref<'a> = Option<bool>;
type Col = BooleanArray;
type Mut = MutableBooleanArray;
type Stats = OptionStats<PrimitiveStats<bool>>;
}
impl ColumnCfg<Option<bool>> for () {
fn as_type(&self) -> DataType {
DataType {
optional: true,
format: ColumnFormat::Bool,
}
}
}
macro_rules! data_primitive {
($data:ident, $format:expr) => {
impl Data for $data {
type Cfg = ();
type Ref<'a> = $data;
type Col = Buffer<$data>;
type Mut = Vec<$data>;
type Stats = PrimitiveStats<$data>;
}
impl ColumnCfg<$data> for () {
fn as_type(&self) -> DataType {
DataType {
optional: false,
format: $format,
}
}
}
impl Data for Option<$data> {
type Cfg = ();
type Ref<'a> = Option<$data>;
type Col = PrimitiveArray<$data>;
type Mut = MutablePrimitiveArray<$data>;
type Stats = OptionStats<PrimitiveStats<$data>>;
}
impl ColumnCfg<Option<$data>> for () {
fn as_type(&self) -> DataType {
DataType {
optional: true,
format: $format,
}
}
}
};
}
data_primitive!(u8, ColumnFormat::U8);
data_primitive!(u16, ColumnFormat::U16);
data_primitive!(u32, ColumnFormat::U32);
data_primitive!(u64, ColumnFormat::U64);
data_primitive!(i8, ColumnFormat::I8);
data_primitive!(i16, ColumnFormat::I16);
data_primitive!(i32, ColumnFormat::I32);
data_primitive!(i64, ColumnFormat::I64);
data_primitive!(f32, ColumnFormat::F32);
data_primitive!(f64, ColumnFormat::F64);
impl Data for Vec<u8> {
type Cfg = ();
type Ref<'a> = &'a [u8];
type Col = BinaryArray<i32>;
type Mut = MutableBinaryArray<i32>;
type Stats = BytesStats;
}
impl ColumnCfg<Vec<u8>> for () {
fn as_type(&self) -> DataType {
DataType {
optional: false,
format: ColumnFormat::Bytes,
}
}
}
impl Data for Option<Vec<u8>> {
type Cfg = ();
type Ref<'a> = Option<&'a [u8]>;
type Col = BinaryArray<i32>;
type Mut = MutableBinaryArray<i32>;
type Stats = OptionStats<BytesStats>;
}
impl ColumnCfg<Option<Vec<u8>>> for () {
fn as_type(&self) -> DataType {
DataType {
optional: true,
format: ColumnFormat::Bytes,
}
}
}
impl Data for String {
type Cfg = ();
type Ref<'a> = &'a str;
type Col = Utf8Array<i32>;
type Mut = MutableUtf8Array<i32>;
type Stats = PrimitiveStats<String>;
}
impl ColumnCfg<String> for () {
fn as_type(&self) -> DataType {
DataType {
optional: false,
format: ColumnFormat::String,
}
}
}
impl Data for Option<String> {
type Cfg = ();
type Ref<'a> = Option<&'a str>;
type Col = Utf8Array<i32>;
type Mut = MutableUtf8Array<i32>;
type Stats = OptionStats<PrimitiveStats<String>>;
}
impl ColumnCfg<Option<String>> for () {
fn as_type(&self) -> DataType {
DataType {
optional: true,
format: ColumnFormat::String,
}
}
}
impl Data for DynStruct {
type Cfg = DynStructCfg;
type Ref<'a> = DynStructRef<'a>;
type Col = DynStructCol;
type Mut = DynStructMut;
type Stats = StructStats;
}
impl ColumnCfg<DynStruct> for DynStructCfg {
fn as_type(&self) -> DataType {
DataType {
optional: false,
format: ColumnFormat::Struct(self.clone()),
}
}
}
impl Data for Option<DynStruct> {
type Cfg = DynStructCfg;
type Ref<'a> = Option<DynStructRef<'a>>;
type Col = DynStructCol;
type Mut = DynStructMut;
type Stats = OptionStats<StructStats>;
}
impl ColumnCfg<Option<DynStruct>> for DynStructCfg {
fn as_type(&self) -> DataType {
DataType {
optional: true,
format: ColumnFormat::Struct(self.clone()),
}
}
}
impl ColumnRef<()> for Bitmap {
fn cfg(&self) -> &() {
&()
}
fn len(&self) -> usize {
self.len()
}
fn to_arrow(&self) -> (Encoding, Box<dyn Array>) {
let array = BooleanArray::new(ArrowLogicalType::Boolean, self.clone(), None);
(Encoding::Plain, Box::new(array))
}
fn from_arrow(_cfg: &(), array: &Box<dyn Array>) -> Result<Self, String> {
let array = array
.as_any()
.downcast_ref::<BooleanArray>()
.ok_or_else(|| format!("expected BooleanArray but was {:?}", array.data_type()))?;
if array.validity().is_some() {
return Err("unexpected validity for non-optional bool".to_owned());
}
Ok(array.values().clone())
}
}
impl ColumnGet<bool> for Bitmap {
fn get<'a>(&'a self, idx: usize) -> bool {
self.get_bit(idx)
}
}
impl ColumnPush<bool> for MutableBitmap {
fn push<'a>(&mut self, val: bool) {
<MutableBitmap>::push(self, val)
}
}
impl ColumnRef<()> for BooleanArray {
fn cfg(&self) -> &() {
&()
}
fn len(&self) -> usize {
self.len()
}
fn to_arrow(&self) -> (Encoding, Box<dyn Array>) {
(Encoding::Plain, Box::new(self.clone()))
}
fn from_arrow(_cfg: &(), array: &Box<dyn Array>) -> Result<Self, String> {
let array = array
.as_any()
.downcast_ref::<BooleanArray>()
.ok_or_else(|| format!("expected BooleanArray but was {:?}", array.data_type()))?;
Ok(array.clone())
}
}
impl ColumnGet<Option<bool>> for BooleanArray {
fn get<'a>(&'a self, idx: usize) -> Option<bool> {
if self.validity().map_or(true, |x| x.get_bit(idx)) {
Some(self.value(idx))
} else {
None
}
}
}
impl ColumnPush<Option<bool>> for MutableBooleanArray {
fn push<'a>(&mut self, val: Option<bool>) {
<MutableBooleanArray>::push(self, val)
}
}
macro_rules! arrowable_primitive {
($data:ident, $encoding:expr) => {
impl ColumnRef<()> for Buffer<$data> {
fn cfg(&self) -> &() {
&()
}
fn len(&self) -> usize {
self.len()
}
fn to_arrow(&self) -> (Encoding, Box<dyn Array>) {
let array = PrimitiveArray::new($data::PRIMITIVE.into(), self.clone(), None);
($encoding, Box::new(array.clone()))
}
fn from_arrow(_cfg: &(), array: &Box<dyn Array>) -> Result<Self, String> {
let array = array
.as_any()
.downcast_ref::<PrimitiveArray<$data>>()
.ok_or_else(|| {
format!(
"expected {} but was {:?}",
std::any::type_name::<PrimitiveArray<$data>>(),
array.data_type()
)
})?;
if array.validity().is_some() {
return Err(format!(
"unexpected validity for non-optional {}",
std::any::type_name::<$data>()
));
}
Ok(array.values().clone())
}
}
impl ColumnGet<$data> for Buffer<$data> {
fn get<'a>(&'a self, idx: usize) -> $data {
self[idx]
}
}
impl ColumnPush<$data> for Vec<$data> {
fn push<'a>(&mut self, val: $data) {
<Vec<$data>>::push(self, val)
}
}
impl ColumnRef<()> for PrimitiveArray<$data> {
fn cfg(&self) -> &() {
&()
}
fn len(&self) -> usize {
self.len()
}
fn to_arrow(&self) -> (Encoding, Box<dyn Array>) {
($encoding, Box::new(self.clone()))
}
fn from_arrow(_cfg: &(), array: &Box<dyn Array>) -> Result<Self, String> {
let array = array
.as_any()
.downcast_ref::<PrimitiveArray<$data>>()
.ok_or_else(|| {
format!(
"expected {} but was {:?}",
std::any::type_name::<PrimitiveArray<$data>>(),
array.data_type()
)
})?;
Ok(array.clone())
}
}
impl ColumnGet<Option<$data>> for PrimitiveArray<$data> {
fn get<'a>(&'a self, idx: usize) -> Option<$data> {
if self.validity().map_or(true, |x| x.get_bit(idx)) {
Some(self.value(idx))
} else {
None
}
}
}
impl ColumnPush<Option<$data>> for MutablePrimitiveArray<$data> {
fn push<'a>(&mut self, val: Option<$data>) {
<MutablePrimitiveArray<$data>>::push(self, val)
}
}
};
}
arrowable_primitive!(u8, Encoding::Plain);
arrowable_primitive!(u16, Encoding::Plain);
arrowable_primitive!(u32, Encoding::Plain);
arrowable_primitive!(u64, Encoding::Plain);
arrowable_primitive!(i8, Encoding::Plain);
arrowable_primitive!(i16, Encoding::Plain);
arrowable_primitive!(i32, Encoding::Plain);
arrowable_primitive!(i64, Encoding::Plain);
arrowable_primitive!(f32, Encoding::Plain);
arrowable_primitive!(f64, Encoding::Plain);
impl ColumnRef<()> for BinaryArray<i32> {
fn cfg(&self) -> &() {
&()
}
fn len(&self) -> usize {
self.len()
}
fn to_arrow(&self) -> (Encoding, Box<dyn Array>) {
(Encoding::Plain, Box::new(self.clone()))
}
fn from_arrow(_cfg: &(), array: &Box<dyn Array>) -> Result<Self, String> {
let array = array
.as_any()
.downcast_ref::<BinaryArray<i32>>()
.ok_or_else(|| format!("expected BinaryArray<i32> but was {:?}", array.data_type()))?;
Ok(array.clone())
}
}
impl ColumnGet<Vec<u8>> for BinaryArray<i32> {
fn get<'a>(&'a self, idx: usize) -> &'a [u8] {
assert!(self.validity().is_none());
self.value(idx)
}
}
impl ColumnGet<Option<Vec<u8>>> for BinaryArray<i32> {
fn get<'a>(&'a self, idx: usize) -> Option<&'a [u8]> {
if self.validity().map_or(true, |x| x.get_bit(idx)) {
Some(self.value(idx))
} else {
None
}
}
}
impl ColumnPush<Vec<u8>> for MutableBinaryArray<i32> {
fn push<'a>(&mut self, val: &'a [u8]) {
assert!(self.validity().is_none());
<MutableBinaryArray<i32>>::push(self, Some(val))
}
}
impl ColumnPush<Option<Vec<u8>>> for MutableBinaryArray<i32> {
fn push<'a>(&mut self, val: Option<&'a [u8]>) {
<MutableBinaryArray<i32>>::push(self, val)
}
}
impl ColumnRef<()> for Utf8Array<i32> {
fn cfg(&self) -> &() {
&()
}
fn len(&self) -> usize {
self.len()
}
fn to_arrow(&self) -> (Encoding, Box<dyn Array>) {
(Encoding::Plain, Box::new(self.clone()))
}
fn from_arrow(_cfg: &(), array: &Box<dyn Array>) -> Result<Self, String> {
let array = array
.as_any()
.downcast_ref::<Utf8Array<i32>>()
.ok_or_else(|| format!("expected Utf8Array<i32> but was {:?}", array.data_type()))?;
Ok(array.clone())
}
}
impl ColumnGet<String> for Utf8Array<i32> {
fn get<'a>(&'a self, idx: usize) -> &'a str {
assert!(self.validity().is_none());
self.value(idx)
}
}
impl ColumnGet<Option<String>> for Utf8Array<i32> {
fn get<'a>(&'a self, idx: usize) -> Option<&'a str> {
if self.validity().map_or(true, |x| x.get_bit(idx)) {
Some(self.value(idx))
} else {
None
}
}
}
impl ColumnPush<String> for MutableUtf8Array<i32> {
fn push<'a>(&mut self, val: &'a str) {
assert!(self.validity().is_none());
<MutableUtf8Array<i32>>::push(self, Some(val))
}
}
impl ColumnPush<Option<String>> for MutableUtf8Array<i32> {
fn push<'a>(&mut self, val: Option<&'a str>) {
<MutableUtf8Array<i32>>::push(self, val)
}
}
#[derive(Debug)]
pub struct TodoSchema<T>(PhantomData<T>);
impl<T> Default for TodoSchema<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T> PartEncoder<'_, T> for TodoSchema<T> {
fn encode(&mut self, _val: &T) {
panic!("TODO")
}
}
impl<T> PartDecoder<'_, T> for TodoSchema<T> {
fn decode(&self, _idx: usize, _val: &mut T) {
panic!("TODO")
}
}
impl<T: Debug + Send + Sync> Schema<T> for TodoSchema<T> {
type Encoder<'a> = Self;
type Decoder<'a> = Self;
fn columns(&self) -> DynStructCfg {
panic!("TODO")
}
fn decoder<'a>(&self, _cols: ColumnsRef<'a>) -> Result<Self::Decoder<'a>, String> {
panic!("TODO")
}
fn encoder<'a>(&self, _cols: ColumnsMut<'a>) -> Result<Self::Encoder<'a>, String> {
panic!("TODO")
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use serde_json::json;
use super::*;
#[mz_ore::test]
fn fmt_ids() {
assert_eq!(
format!("{}", ShardId([0u8; 16])),
"s00000000-0000-0000-0000-000000000000"
);
assert_eq!(
format!("{:?}", ShardId([0u8; 16])),
"ShardId(00000000-0000-0000-0000-000000000000)"
);
assert_eq!(
ShardId::from_str("s00000000-0000-0000-0000-000000000000"),
Ok(ShardId([0u8; 16]))
);
assert_eq!(
ShardId::from_str("x00000000-0000-0000-0000-000000000000"),
Err(
"invalid ShardId x00000000-0000-0000-0000-000000000000: incorrect prefix"
.to_string()
)
);
assert_eq!(
ShardId::from_str("s0"),
Err(
"invalid ShardId s0: invalid length: expected length 32 for simple format, found 1"
.to_string()
)
);
assert_eq!(
ShardId::from_str("s00000000-0000-0000-0000-000000000000FOO"),
Err("invalid ShardId s00000000-0000-0000-0000-000000000000FOO: invalid character: expected an optional prefix of `urn:uuid:` followed by [0-9a-fA-F-], found `O` at 38".to_string())
);
}
#[mz_ore::test]
fn shard_id_human_readable_serde() {
#[derive(Debug, Serialize, Deserialize)]
struct ShardIdContainer {
shard_id: ShardId,
}
let id =
ShardId::from_str("s00000000-1234-5678-0000-000000000000").expect("valid shard id");
assert_eq!(
id,
serde_json::from_value(serde_json::to_value(id).expect("serializable"))
.expect("deserializable")
);
assert_eq!(
id,
serde_json::from_str("\"s00000000-1234-5678-0000-000000000000\"")
.expect("deserializable")
);
let json = json!({ "shard_id": id });
assert_eq!(
"{\"shard_id\":\"s00000000-1234-5678-0000-000000000000\"}",
&json.to_string()
);
let container: ShardIdContainer = serde_json::from_value(json).expect("deserializable");
assert_eq!(container.shard_id, id);
}
}