-use darling::FromMeta;
-use proc_macro::{self, TokenStream};
+use darling::{FromField, FromMeta};
+use proc_macro::TokenStream;
+use proc_macro2::TokenStream as TokStr;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, parse_quote};
#[derive(Debug, FromMeta, Copy, Clone, Eq, PartialEq)]
#[darling(rename_all = "snake_case")]
enum To {
- Clt,
- Srv,
+ Clt,
+ Srv,
}
#[derive(Debug, FromMeta)]
struct MacroArgs {
- to: To,
- repr: Option<syn::Type>,
- tag: Option<String>,
- content: Option<String>,
- #[darling(default)]
- enumset: bool,
+ to: To,
+ repr: Option<syn::Type>,
+ tag: Option<String>,
+ content: Option<String>,
+ #[darling(default)]
+ custom: bool,
+ #[darling(default)]
+ enumset: bool,
}
fn wrap_attr(attr: &mut syn::Attribute) {
- match attr.path.get_ident().map(|i| i.to_string()).as_deref() {
- Some("mt") => {
- let path = attr.path.clone();
- let tokens = attr.tokens.clone();
+ match attr.path.get_ident().map(|i| i.to_string()).as_deref() {
+ Some("mt") => {
+ let path = attr.path.clone();
+ let tokens = attr.tokens.clone();
- *attr = parse_quote! {
- #[cfg_attr(any(feature = "client", feature = "server"), #path #tokens)]
- };
- }
- Some("serde") => {
- let path = attr.path.clone();
- let tokens = attr.tokens.clone();
+ *attr = parse_quote! {
+ #[cfg_attr(any(feature = "client", feature = "server"), #path #tokens)]
+ };
+ }
+ Some("serde") => {
+ let path = attr.path.clone();
+ let tokens = attr.tokens.clone();
- *attr = parse_quote! {
- #[cfg_attr(feature = "serde", #path #tokens)]
- };
- }
- _ => {}
- }
+ *attr = parse_quote! {
+ #[cfg_attr(feature = "serde", #path #tokens)]
+ };
+ }
+ _ => {}
+ }
}
#[proc_macro_attribute]
pub fn mt_derive(attr: TokenStream, item: TokenStream) -> TokenStream {
- let item2 = item.clone();
+ let item2 = item.clone();
- let attr_args = parse_macro_input!(attr as syn::AttributeArgs);
- let mut input = parse_macro_input!(item2 as syn::Item);
+ let attr_args = parse_macro_input!(attr as syn::AttributeArgs);
+ let mut input = parse_macro_input!(item2 as syn::Item);
- let args = match MacroArgs::from_list(&attr_args) {
- Ok(v) => v,
- Err(e) => {
- return TokenStream::from(e.write_errors());
- }
- };
-
- let (serializer, deserializer) = match args.to {
- To::Clt => ("server", "client"),
- To::Srv => ("client", "server"),
- };
-
- let mut out = quote! {
- #[derive(Debug)]
- #[cfg_attr(feature = "random", derive(GenerateRandom))]
- #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
- };
-
- macro_rules! iter {
- ($t:expr, $f:expr) => {
- $t.iter_mut().for_each($f)
+ let args = match MacroArgs::from_list(&attr_args) {
+ Ok(v) => v,
+ Err(e) => {
+ return TokenStream::from(e.write_errors());
+ }
+ };
+
+ let (serializer, deserializer) = match args.to {
+ To::Clt => ("server", "client"),
+ To::Srv => ("client", "server"),
+ };
+
+ let mut out = quote! {
+ #[derive(Debug)]
+ #[cfg_attr(feature = "random", derive(GenerateRandom))]
+ #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+ };
+
+ macro_rules! iter {
+ ($t:expr, $f:expr) => {
+ $t.iter_mut().for_each($f)
+ };
+ }
+
+ match &mut input {
+ syn::Item::Enum(e) => {
+ iter!(e.attrs, wrap_attr);
+ iter!(e.variants, |v| {
+ iter!(v.attrs, wrap_attr);
+ iter!(v.fields, |f| iter!(f.attrs, wrap_attr));
+ });
+
+ let repr = args.repr.expect("missing repr for enum");
+
+ if args.enumset {
+ let repr_str = repr.to_token_stream().to_string();
+
+ out.extend(quote! {
+ #[derive(EnumSetType)]
+ #[enumset(repr = #repr_str, serialize_as_map)]
+ })
+ } else {
+ let has_payload = e
+ .variants
+ .iter()
+ .find_map(|v| if v.fields.is_empty() { None } else { Some(()) })
+ .is_some();
+
+ if has_payload {
+ let tag = args.tag.expect("missing tag for enum with payload");
+
+ out.extend(quote! {
+ #[cfg_attr(feature = "serde", serde(tag = #tag))]
+ });
+
+ if let Some(content) = args.content {
+ out.extend(quote! {
+ #[cfg_attr(feature = "serde", serde(content = #content))]
+ });
+ }
+ } else {
+ out.extend(quote! {
+ #[derive(Copy, Eq)]
+ });
+ }
+
+ out.extend(quote! {
+ #[repr(#repr)]
+ #[derive(Clone, PartialEq)]
+ });
+
+ if !args.custom {
+ out.extend(quote! {
+ #[cfg_attr(feature = #serializer, derive(MtSerialize))]
+ #[cfg_attr(feature = #deserializer, derive(MtDeserialize))]
+ });
+ }
+ }
+
+ out.extend(quote! {
+ #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
+ });
+ }
+ syn::Item::Struct(s) => {
+ iter!(s.attrs, wrap_attr);
+ iter!(s.fields, |f| iter!(f.attrs, wrap_attr));
+
+ out.extend(quote! {
+ #[derive(Clone, PartialEq)]
+ });
+
+ if !args.custom {
+ out.extend(quote! {
+ #[cfg_attr(feature = #serializer, derive(MtSerialize))]
+ #[cfg_attr(feature = #deserializer, derive(MtDeserialize))]
+ });
+ }
+ }
+ _ => panic!("only enum and struct supported"),
+ }
+
+ out.extend(input.to_token_stream());
+ out.into()
+}
+
+#[derive(Debug, Default, FromField)]
+#[darling(attributes(mt))]
+#[darling(default)]
+struct MtArgs {
+ const8: Option<u8>,
+ const16: Option<u16>,
+ const32: Option<u32>,
+ const64: Option<u64>,
+ size8: bool,
+ size16: bool,
+ size32: bool,
+ size64: bool,
+ len0: bool,
+ len8: bool,
+ len16: bool,
+ len32: bool,
+ len64: bool,
+ utf16: bool, // TODO
+ zlib: bool,
+ default: bool,
+}
+
+fn get_cfg(args: &MtArgs) -> syn::Type {
+ let mut ty: syn::Type = parse_quote! { mt_data::DefaultCfg };
+
+ if args.len0 {
+ ty = parse_quote! { mt_data::NoLen };
+ }
+
+ macro_rules! impl_len {
+ ($name:ident, $T:ty) => {
+ if args.$name {
+ ty = parse_quote! { $T };
+ }
+ };
+ }
+
+ impl_len!(len8, u8);
+ impl_len!(len16, u16);
+ impl_len!(len32, u32);
+ impl_len!(len64, u64);
+
+ if args.utf16 {
+ ty = parse_quote! { mt_data::Utf16<#ty> };
+ }
+
+ ty
+}
+
+/*
+fn is_ident(path: &syn::Path, ident: &str) -> bool {
+ matches!(path.segments.first().map(|p| &p.ident), Some(idt) if idt == ident)
+}
+
+fn get_type_generics<const N: usize>(path: &syn::Path) -> Option<[&syn::Type; N]> {
+ use syn::{AngleBracketedGenericArguments as Args, PathArguments::AngleBracketed};
+
+ path.segments
+ .first()
+ .map(|seg| match &seg.arguments {
+ AngleBracketed(Args { args, .. }) => args
+ .iter()
+ .flat_map(|arg| match arg {
+ syn::GenericArgument::Type(t) => Some(t),
+ _ => None,
+ })
+ .collect::<Vec<_>>()
+ .try_into()
+ .ok(),
+ _ => None,
+ })
+ .flatten()
+}
+*/
+
+fn serialize_fields(fields: &syn::Fields) -> TokStr {
+ let mut code: TokStr = (match fields {
+ syn::Fields::Named(fs) => fs
+ .named
+ .iter()
+ .map(|f| (f.ident.as_ref().unwrap().to_token_stream(), f))
+ .collect(),
+ syn::Fields::Unnamed(fs) => fs
+ .unnamed
+ .iter()
+ .enumerate()
+ .map(|(i, f)| (i.to_token_stream(), f))
+ .collect(),
+ syn::Fields::Unit => Vec::new(),
+ }).into_iter().map(|(ident, field)| {
+ let args = match MtArgs::from_field(field) {
+ Ok(v) => v,
+ Err(e) => return e.write_errors(),
};
- }
-
- match &mut input {
- syn::Item::Enum(e) => {
- iter!(e.attrs, wrap_attr);
- iter!(e.variants, |v| {
- iter!(v.attrs, wrap_attr);
- iter!(v.fields, |f| iter!(f.attrs, wrap_attr));
- });
-
- let repr = args.repr.expect("missing repr for enum");
-
- if args.enumset {
- let repr_str = repr.to_token_stream().to_string();
-
- out.extend(quote! {
- #[derive(EnumSetType)]
- #[enumset(repr = #repr_str, serialize_as_map)]
- })
- } else {
- let has_payload = e
- .variants
- .iter()
- .find_map(|v| if v.fields.is_empty() { None } else { Some(()) })
- .is_some();
-
- if has_payload {
- let tag = args.tag.expect("missing tag for enum with payload");
-
- out.extend(quote! {
- #[derive(Clone)]
- #[cfg_attr(feature = "serde", serde(tag = #tag))]
- });
- if let Some(content) = args.content {
- out.extend(quote! {
- #[cfg_attr(feature = "serde", serde(content = #content))]
- });
- }
- } else {
- out.extend(quote! {
- #[derive(Copy, Clone, PartialEq, Eq)]
+ let mut code = TokStr::new();
+
+ macro_rules! impl_const {
+ ($name:ident) => {
+ if let Some(x) = args.$name {
+ code.extend(quote! {
+ #x.mt_serialize::<mt_data::DefaultCfg>(writer)?;
});
}
+ };
+ }
- out.extend(quote! {
- #[repr(#repr)]
- #[cfg_attr(feature = #serializer, derive(MtSerialize))]
- #[cfg_attr(feature = #deserializer, derive(MtDeserialize))]
- });
- }
+ impl_const!(const8);
+ impl_const!(const16);
+ impl_const!(const32);
+ impl_const!(const64);
+
+ let cfg = get_cfg(&args);
+ code.extend(quote! {
+ mt_data::MtSerialize::mt_serialize::<#cfg>(&self.#ident, writer)?;
+ });
- out.extend(quote! {
- #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
- });
+ if args.zlib {
+ code = quote! {
+ let mut writer = {
+ let mut writer = mt_data::flate2::write::ZlibEncoder(writer, flate2::Compression::default());
+ #code
+ writer.finish()?
+ };
+ };
}
- syn::Item::Struct(s) => {
- iter!(s.attrs, wrap_attr);
- iter!(s.fields, |f| iter!(f.attrs, wrap_attr));
-
- out.extend(quote! {
- #[derive(Clone)]
- #[cfg_attr(feature = #serializer, derive(MtSerialize))]
- #[cfg_attr(feature = #deserializer, derive(MtDeserialize))]
- });
+
+ macro_rules! impl_size {
+ ($name:ident, $T:ty) => {
+ if args.$name {
+ code = quote! {
+ {
+ let buf = {
+ let mut writer = Vec::new();
+ #code
+ writer
+ };
+
+ TryInto::<$T>::try_into(buf.len())?.mt_serialize::<mt_data::DefaultCfg>();
+ }
+ };
+ }
+ };
}
- _ => panic!("only enum and struct supported"),
- }
- out.extend(input.to_token_stream());
- out.into()
+ impl_size!(size8, u8);
+ impl_size!(size16, u16);
+ impl_size!(size32, u32);
+ impl_size!(size64, u64);
+
+ code
+ }).collect();
+
+ code.extend(quote! {
+ Ok(())
+ });
+
+ code
}
#[proc_macro_derive(MtSerialize, attributes(mt))]
pub fn derive_serialize(input: TokenStream) -> TokenStream {
- let syn::DeriveInput { ident, .. } = parse_macro_input!(input);
- let output = quote! {
- impl MtSerialize for #ident {
- fn mt_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<(), mt_data::SerializeError> {
- Err(mt_data::SerializeError::Unimplemented)
+ let input = parse_macro_input!(input as syn::DeriveInput);
+ let ident = &input.ident;
+
+ let code = match &input.data {
+ syn::Data::Enum(_e) => quote! {
+ Err(mt_data::SerializeError::Unimplemented)
+ },
+ syn::Data::Struct(s) => serialize_fields(&s.fields),
+ _ => {
+ panic!("only enum and struct supported");
+ }
+ };
+
+ quote! {
+ #[automatically_derived]
+ impl mt_data::MtSerialize for #ident {
+ fn mt_serialize<C: MtCfg>(&self, writer: &mut impl std::io::Write) -> Result<(), mt_data::SerializeError> {
+ #code
}
}
- };
- output.into()
+ }.into()
}
#[proc_macro_derive(MtDeserialize, attributes(mt))]
pub fn derive_deserialize(input: TokenStream) -> TokenStream {
- let syn::DeriveInput { ident, .. } = parse_macro_input!(input);
- quote! {
- impl MtDeserialize for #ident {
- fn mt_deserialize<R: std::io::Read>(reader: &mut R) -> Result<Self, mt_data::DeserializeError> {
+ let syn::DeriveInput { ident, .. } = parse_macro_input!(input);
+ quote! {
+ #[automatically_derived]
+ impl mt_data::MtDeserialize for #ident {
+ fn mt_deserialize<C: MtCfg>(reader: &mut impl std::io::Read) -> Result<Self, mt_data::DeserializeError> {
Err(mt_data::DeserializeError::Unimplemented)
}
}
pub use enumset;
+pub use flate2;
#[cfg(feature = "random")]
pub use generate_random;
#[cfg(feature = "serde")]
pub use serde;
-use enumset::{EnumSet, EnumSetType};
+use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
+use enumset::{EnumSet, EnumSetType, EnumSetTypeWithRepr};
use mt_data_derive::mt_derive;
pub use mt_data_derive::{MtDeserialize, MtSerialize};
-use std::{collections::HashMap, fmt, io};
+use paste::paste;
+use std::{
+ collections::{HashMap, HashSet},
+ convert::Infallible,
+ fmt,
+ io::{self, Read, Write},
+ num::TryFromIntError,
+};
use thiserror::Error;
#[cfg(feature = "serde")]
#[cfg(feature = "random")]
use generate_random::GenerateRandom;
+use crate as mt_data;
+
+#[derive(Error, Debug)]
+#[error("variable length")]
+pub struct VarLen;
+
#[derive(Error, Debug)]
pub enum SerializeError {
- #[error("{0}")]
+ #[error("io error: {0}")]
IoError(#[from] io::Error),
- #[error("serialization is not implemented")]
+ #[error("collection too big: {0}")]
+ TooBig(#[from] TryFromIntError),
+ #[error("unimplemented")]
Unimplemented,
}
+impl From<Infallible> for DeserializeError {
+ fn from(_err: Infallible) -> Self {
+ unreachable!("infallible")
+ }
+}
+
#[derive(Error, Debug)]
pub enum DeserializeError {
- #[error("{0}")]
+ #[error("io error: {0}")]
IoError(#[from] io::Error),
- #[error("deserialization is not implemented")]
+ #[error("variable length not supported")]
+ NoVarlen(#[from] VarLen),
+ #[error("collection too big: {0}")]
+ TooBig(#[from] TryFromIntError),
+ #[error("unimplemented")]
Unimplemented,
}
+impl From<Infallible> for SerializeError {
+ fn from(_err: Infallible) -> Self {
+ unreachable!("infallible")
+ }
+}
+
+pub trait MtCfg:
+ Sized
+ + MtSerialize
+ + MtDeserialize
+ + TryFrom<usize, Error = Self::TryFromError>
+ + TryInto<usize, Error = Self::TryIntoError>
+{
+ type TryFromError: Into<SerializeError>;
+ type TryIntoError: Into<DeserializeError>;
+
+ #[inline]
+ fn utf16() -> bool {
+ false
+ }
+
+ fn write_len(len: usize, writer: &mut impl Write) -> Result<(), SerializeError> {
+ Ok(Self::try_from(len)
+ .map_err(|e| e.into())?
+ .mt_serialize::<DefaultCfg>(writer)?)
+ }
+}
+
+pub type DefaultCfg = u16;
+
pub trait MtSerialize: Sized {
- fn mt_serialize<W: io::Write>(&self, writer: &mut W) -> Result<(), SerializeError>;
+ fn mt_serialize<C: MtCfg>(&self, writer: &mut impl Write) -> Result<(), SerializeError>;
}
pub trait MtDeserialize: Sized {
- fn mt_deserialize<R: io::Read>(reader: &mut R) -> Result<Self, DeserializeError>;
+ fn mt_deserialize<C: MtCfg>(reader: &mut impl Read) -> Result<Self, DeserializeError>;
+}
+
+impl MtCfg for u8 {
+ type TryFromError = TryFromIntError;
+ type TryIntoError = Infallible;
+}
+
+impl MtCfg for u16 {
+ type TryFromError = TryFromIntError;
+ type TryIntoError = Infallible;
+}
+
+impl MtCfg for u32 {
+ type TryFromError = TryFromIntError;
+ type TryIntoError = TryFromIntError;
+}
+
+impl MtCfg for u64 {
+ type TryFromError = TryFromIntError;
+ type TryIntoError = TryFromIntError;
+}
+
+pub struct NoLen;
+
+impl MtSerialize for NoLen {
+ fn mt_serialize<C: MtCfg>(&self, _writer: &mut impl Write) -> Result<(), SerializeError> {
+ Ok(())
+ }
+}
+
+impl MtDeserialize for NoLen {
+ fn mt_deserialize<C: MtCfg>(_reader: &mut impl Read) -> Result<Self, DeserializeError> {
+ Ok(Self)
+ }
+}
+
+impl TryFrom<usize> for NoLen {
+ type Error = Infallible;
+
+ fn try_from(_x: usize) -> Result<Self, Self::Error> {
+ Ok(Self)
+ }
+}
+
+impl TryInto<usize> for NoLen {
+ type Error = VarLen;
+
+ fn try_into(self) -> Result<usize, Self::Error> {
+ Err(VarLen)
+ }
+}
+
+impl MtCfg for NoLen {
+ type TryFromError = Infallible;
+ type TryIntoError = VarLen;
+}
+
+pub struct Utf16<B: MtCfg>(pub B);
+
+impl<B: MtCfg> MtSerialize for Utf16<B> {
+ fn mt_serialize<C: MtCfg>(&self, writer: &mut impl Write) -> Result<(), SerializeError> {
+ self.0.mt_serialize::<DefaultCfg>(writer)
+ }
+}
+
+impl<B: MtCfg> MtDeserialize for Utf16<B> {
+ fn mt_deserialize<C: MtCfg>(reader: &mut impl Read) -> Result<Self, DeserializeError> {
+ Ok(Self(B::mt_deserialize::<DefaultCfg>(reader)?))
+ }
+}
+
+impl<B: MtCfg> TryFrom<usize> for Utf16<B> {
+ type Error = B::TryFromError;
+
+ fn try_from(x: usize) -> Result<Self, Self::Error> {
+ Ok(Self(x.try_into()?))
+ }
+}
+
+impl<B: MtCfg> TryInto<usize> for Utf16<B> {
+ type Error = B::TryIntoError;
+
+ fn try_into(self) -> Result<usize, Self::Error> {
+ self.0.try_into()
+ }
+}
+
+impl<B: MtCfg> MtCfg for Utf16<B> {
+ type TryFromError = B::TryFromError;
+ type TryIntoError = B::TryIntoError;
+
+ #[inline]
+ fn utf16() -> bool {
+ true
+ }
+}
+
+impl MtSerialize for u8 {
+ fn mt_serialize<C: MtCfg>(&self, writer: &mut impl Write) -> Result<(), SerializeError> {
+ writer.write_u8(*self)?;
+ Ok(())
+ }
+}
+
+impl MtDeserialize for u8 {
+ fn mt_deserialize<C: MtCfg>(reader: &mut impl Read) -> Result<Self, DeserializeError> {
+ Ok(reader.read_u8()?)
+ }
+}
+
+impl MtSerialize for i8 {
+ fn mt_serialize<C: MtCfg>(&self, writer: &mut impl Write) -> Result<(), SerializeError> {
+ writer.write_i8(*self)?;
+ Ok(())
+ }
+}
+
+impl MtDeserialize for i8 {
+ fn mt_deserialize<C: MtCfg>(reader: &mut impl Read) -> Result<Self, DeserializeError> {
+ Ok(reader.read_i8()?)
+ }
+}
+
+macro_rules! impl_num {
+ ($T:ty) => {
+ impl MtSerialize for $T {
+ fn mt_serialize<C: MtCfg>(
+ &self,
+ writer: &mut impl Write,
+ ) -> Result<(), SerializeError> {
+ paste! {
+ writer.[<write_ $T>]::<BigEndian>(*self)?;
+ }
+ Ok(())
+ }
+ }
+
+ impl MtDeserialize for $T {
+ fn mt_deserialize<C: MtCfg>(reader: &mut impl Read) -> Result<Self, DeserializeError> {
+ paste! {
+ Ok(reader.[<read_ $T>]::<BigEndian>()?)
+ }
+ }
+ }
+ };
+}
+
+impl_num!(u16);
+impl_num!(i16);
+
+impl_num!(u32);
+impl_num!(i32);
+impl_num!(f32);
+
+impl_num!(u64);
+impl_num!(i64);
+impl_num!(f64);
+
+impl MtSerialize for () {
+ fn mt_serialize<C: MtCfg>(&self, _writer: &mut impl Write) -> Result<(), SerializeError> {
+ Ok(())
+ }
+}
+
+impl MtSerialize for bool {
+ fn mt_serialize<C: MtCfg>(&self, writer: &mut impl Write) -> Result<(), SerializeError> {
+ (*self as u8).mt_serialize::<DefaultCfg>(writer)
+ }
+}
+
+impl<T: MtSerialize, const N: usize> MtSerialize for [T; N] {
+ fn mt_serialize<C: MtCfg>(&self, writer: &mut impl Write) -> Result<(), SerializeError> {
+ for item in self.iter() {
+ item.mt_serialize::<DefaultCfg>(writer)?;
+ }
+
+ Ok(())
+ }
+}
+
+impl<T: MtSerialize, E: EnumSetTypeWithRepr<Repr = T>> MtSerialize for EnumSet<E> {
+ fn mt_serialize<C: MtCfg>(&self, writer: &mut impl Write) -> Result<(), SerializeError> {
+ self.as_repr().mt_serialize::<DefaultCfg>(writer)
+ }
+}
+
+impl<T: MtSerialize> MtSerialize for Option<T> {
+ fn mt_serialize<C: MtCfg>(&self, writer: &mut impl Write) -> Result<(), SerializeError> {
+ match self {
+ Some(item) => item.mt_serialize::<C>(writer),
+ None => Ok(()),
+ }
+ }
+}
+
+impl<T: MtSerialize> MtSerialize for Vec<T> {
+ fn mt_serialize<C: MtCfg>(&self, writer: &mut impl Write) -> Result<(), SerializeError> {
+ C::write_len(self.len(), writer)?;
+ for item in self.iter() {
+ item.mt_serialize::<DefaultCfg>(writer)?;
+ }
+ Ok(())
+ }
+}
+
+impl<T: MtSerialize> MtSerialize for HashSet<T> {
+ fn mt_serialize<C: MtCfg>(&self, writer: &mut impl Write) -> Result<(), SerializeError> {
+ C::write_len(self.len(), writer)?;
+ for item in self.iter() {
+ item.mt_serialize::<DefaultCfg>(writer)?;
+ }
+ Ok(())
+ }
+}
+
+impl<K, V> MtSerialize for HashMap<K, V>
+where
+ K: MtSerialize + std::cmp::Eq + std::hash::Hash,
+ V: MtSerialize,
+{
+ fn mt_serialize<C: MtCfg>(&self, writer: &mut impl Write) -> Result<(), SerializeError> {
+ C::write_len(self.len(), writer)?;
+ for (key, value) in self.iter() {
+ key.mt_serialize::<DefaultCfg>(writer)?;
+ value.mt_serialize::<DefaultCfg>(writer)?;
+ }
+ Ok(())
+ }
+}
+
+impl MtSerialize for String {
+ fn mt_serialize<C: MtCfg>(&self, writer: &mut impl Write) -> Result<(), SerializeError> {
+ if C::utf16() {
+ // TODO
+ Err(SerializeError::Unimplemented)
+ } else {
+ C::write_len(self.len(), writer)?;
+ writer.write_all(self.as_bytes())?;
+
+ Ok(())
+ }
+ }
}
mod to_clt;