xso: use values instead of types for text codecs
This allows stateful or configurable codecs without having to express all configuration in the type name itself. For example, we could have a Base64 type with configurable Base64 engines without having to duplicate the Base64 type itself. (Note that the different engines in the Base64 crate are values, not types.)
This commit is contained in:
parent
b9fc15977b
commit
271c31c9d4
10 changed files with 150 additions and 101 deletions
|
@ -5,7 +5,7 @@
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use xso::{
|
use xso::{
|
||||||
text::{Base64, StripWhitespace},
|
text::{Base64, StripWhitespace, TextCodec},
|
||||||
AsXml, FromXml,
|
AsXml, FromXml,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ pub struct Info {
|
||||||
#[xml(namespace = ns::AVATAR_DATA, name = "data")]
|
#[xml(namespace = ns::AVATAR_DATA, name = "data")]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
/// Vector of bytes representing the avatar’s image.
|
/// Vector of bytes representing the avatar’s image.
|
||||||
#[xml(text(codec = Base64<StripWhitespace>))]
|
#[xml(text(codec = Base64.filtered(StripWhitespace)))]
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub struct Handshake {
|
||||||
///
|
///
|
||||||
/// If None, it is the successful reply from the server, the stream is now
|
/// If None, it is the successful reply from the server, the stream is now
|
||||||
/// fully established and both sides can now exchange stanzas.
|
/// fully established and both sides can now exchange stanzas.
|
||||||
#[xml(text(codec = FixedHex<20>))]
|
#[xml(text(codec = FixedHex::<20>))]
|
||||||
pub data: Option<[u8; 20]>,
|
pub data: Option<[u8; 20]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
use xso::{
|
use xso::{
|
||||||
error::Error,
|
error::Error,
|
||||||
text::{Base64, StripWhitespace},
|
text::{Base64, StripWhitespace, TextCodec},
|
||||||
AsXml, FromXml,
|
AsXml, FromXml,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ pub struct Type {
|
||||||
#[xml(namespace = ns::VCARD, name = "BINVAL")]
|
#[xml(namespace = ns::VCARD, name = "BINVAL")]
|
||||||
pub struct Binval {
|
pub struct Binval {
|
||||||
/// The actual data.
|
/// The actual data.
|
||||||
#[xml(text(codec = Base64<StripWhitespace>))]
|
#[xml(text(codec = Base64.filtered(StripWhitespace)))]
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub struct VCardUpdate {
|
||||||
#[xml(namespace = ns::VCARD_UPDATE, name = "photo")]
|
#[xml(namespace = ns::VCARD_UPDATE, name = "photo")]
|
||||||
pub struct Photo {
|
pub struct Photo {
|
||||||
/// The SHA1 hash of the avatar. Empty when there is no photo.
|
/// The SHA1 hash of the avatar. Empty when there is no photo.
|
||||||
#[xml(text(codec = FixedHex<20>))]
|
#[xml(text(codec = FixedHex::<20>))]
|
||||||
pub data: Option<[u8; 20]>,
|
pub data: Option<[u8; 20]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ enum FieldKind {
|
||||||
/// The field maps to the character data of the element.
|
/// The field maps to the character data of the element.
|
||||||
Text {
|
Text {
|
||||||
/// Optional codec to use
|
/// Optional codec to use
|
||||||
codec: Option<Type>,
|
codec: Option<Expr>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// The field maps to a child
|
/// The field maps to a child
|
||||||
|
@ -321,10 +321,10 @@ impl FieldDef {
|
||||||
let FromEventsScope { ref text, .. } = scope;
|
let FromEventsScope { ref text, .. } = scope;
|
||||||
let field_access = scope.access_field(&self.member);
|
let field_access = scope.access_field(&self.member);
|
||||||
let finalize = match codec {
|
let finalize = match codec {
|
||||||
Some(codec_ty) => {
|
Some(codec) => {
|
||||||
let decode = text_codec_decode_fn(codec_ty.clone(), self.ty.clone());
|
let decode = text_codec_decode_fn(self.ty.clone());
|
||||||
quote! {
|
quote! {
|
||||||
#decode(#field_access)?
|
#decode(&#codec, #field_access)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
@ -429,9 +429,9 @@ impl FieldDef {
|
||||||
|
|
||||||
FieldKind::Text { ref codec } => {
|
FieldKind::Text { ref codec } => {
|
||||||
let generator = match codec {
|
let generator = match codec {
|
||||||
Some(codec_ty) => {
|
Some(codec) => {
|
||||||
let encode = text_codec_encode_fn(codec_ty.clone(), self.ty.clone());
|
let encode = text_codec_encode_fn(self.ty.clone());
|
||||||
quote! { #encode(#bound_name)? }
|
quote! { #encode(&#codec, #bound_name)? }
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let as_xml_text = as_xml_text_fn(self.ty.clone());
|
let as_xml_text = as_xml_text_fn(self.ty.clone());
|
||||||
|
|
|
@ -408,7 +408,7 @@ pub(crate) enum XmlFieldMeta {
|
||||||
/// `#[xml(text)]`
|
/// `#[xml(text)]`
|
||||||
Text {
|
Text {
|
||||||
/// The path to the optional codec type.
|
/// The path to the optional codec type.
|
||||||
codec: Option<Type>,
|
codec: Option<Expr>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// `#[xml(child)`
|
/// `#[xml(child)`
|
||||||
|
@ -497,7 +497,7 @@ impl XmlFieldMeta {
|
||||||
|
|
||||||
/// Parse a `#[xml(text)]` meta.
|
/// Parse a `#[xml(text)]` meta.
|
||||||
fn text_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
|
fn text_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
|
||||||
let mut codec: Option<Type> = None;
|
let mut codec: Option<Expr> = None;
|
||||||
if meta.input.peek(Token![=]) {
|
if meta.input.peek(Token![=]) {
|
||||||
Ok(Self::Text {
|
Ok(Self::Text {
|
||||||
codec: Some(meta.value()?.parse()?),
|
codec: Some(meta.value()?.parse()?),
|
||||||
|
|
|
@ -298,22 +298,13 @@ pub(crate) fn as_xml_text_fn(ty: Type) -> Expr {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a [`syn::TypePath`] referring to
|
/// Construct a [`syn::Path`] referring to `::xso::TextCodec::<#for_ty>`,
|
||||||
/// `<#codec_ty as ::xso::TextCodec::<#for_ty>>` and return the
|
/// returing the span of `for_ty` alongside it.
|
||||||
/// [`syn::Span`] of the `codec_ty` alongside it.
|
fn text_codec_of(for_ty: Type) -> (Span, Path) {
|
||||||
fn text_codec_of(codec_ty: Type, for_ty: Type) -> (Span, TypePath) {
|
let span = for_ty.span();
|
||||||
let span = codec_ty.span();
|
|
||||||
(
|
(
|
||||||
span,
|
span,
|
||||||
TypePath {
|
Path {
|
||||||
qself: Some(QSelf {
|
|
||||||
lt_token: syn::token::Lt { spans: [span] },
|
|
||||||
ty: Box::new(codec_ty),
|
|
||||||
position: 2,
|
|
||||||
as_token: Some(syn::token::As { span }),
|
|
||||||
gt_token: syn::token::Gt { spans: [span] },
|
|
||||||
}),
|
|
||||||
path: Path {
|
|
||||||
leading_colon: Some(syn::token::PathSep {
|
leading_colon: Some(syn::token::PathSep {
|
||||||
spans: [span, span],
|
spans: [span, span],
|
||||||
}),
|
}),
|
||||||
|
@ -337,37 +328,36 @@ fn text_codec_of(codec_ty: Type, for_ty: Type) -> (Span, TypePath) {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a [`syn::Expr`] referring to
|
/// Construct a [`syn::Expr`] referring to
|
||||||
/// `<#codec_ty as ::xso::TextCodec::<#for_ty>>::encode`.
|
/// `::xso::TextCodec::<#for_ty>::encode`.
|
||||||
pub(crate) fn text_codec_encode_fn(codec_ty: Type, for_ty: Type) -> Expr {
|
pub(crate) fn text_codec_encode_fn(for_ty: Type) -> Expr {
|
||||||
let (span, mut ty) = text_codec_of(codec_ty, for_ty);
|
let (span, mut path) = text_codec_of(for_ty);
|
||||||
ty.path.segments.push(PathSegment {
|
path.segments.push(PathSegment {
|
||||||
ident: Ident::new("encode", span),
|
ident: Ident::new("encode", span),
|
||||||
arguments: PathArguments::None,
|
arguments: PathArguments::None,
|
||||||
});
|
});
|
||||||
Expr::Path(ExprPath {
|
Expr::Path(ExprPath {
|
||||||
attrs: Vec::new(),
|
attrs: Vec::new(),
|
||||||
qself: ty.qself,
|
qself: None,
|
||||||
path: ty.path,
|
path: path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a [`syn::Expr`] referring to
|
/// Construct a [`syn::Expr`] referring to
|
||||||
/// `<#codec_ty as ::xso::TextCodec::<#for_ty>>::decode`.
|
/// `::xso::TextCodec::<#for_ty>::decode`.
|
||||||
pub(crate) fn text_codec_decode_fn(codec_ty: Type, for_ty: Type) -> Expr {
|
pub(crate) fn text_codec_decode_fn(for_ty: Type) -> Expr {
|
||||||
let (span, mut ty) = text_codec_of(codec_ty, for_ty);
|
let (span, mut path) = text_codec_of(for_ty);
|
||||||
ty.path.segments.push(PathSegment {
|
path.segments.push(PathSegment {
|
||||||
ident: Ident::new("decode", span),
|
ident: Ident::new("decode", span),
|
||||||
arguments: PathArguments::None,
|
arguments: PathArguments::None,
|
||||||
});
|
});
|
||||||
Expr::Path(ExprPath {
|
Expr::Path(ExprPath {
|
||||||
attrs: Vec::new(),
|
attrs: Vec::new(),
|
||||||
qself: ty.qself,
|
qself: None,
|
||||||
path: ty.path,
|
path: path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,22 @@
|
||||||
Version NEXT:
|
Version NEXT:
|
||||||
0000-00-00 Jonas Schäfer <jonas@zombofant.net>
|
0000-00-00 Jonas Schäfer <jonas@zombofant.net>
|
||||||
|
* Breaking
|
||||||
|
- The methods of `TextCodec<T>` now have `&self` receivers. This also
|
||||||
|
implies that `#[xml(text(codec = ..))]` now takes expressions instead
|
||||||
|
of type paths.
|
||||||
|
|
||||||
|
Because all implementations provided by `xso` were in fact unit
|
||||||
|
structs, this should not change most invocations, with two exceptions:
|
||||||
|
|
||||||
|
1. The type argument of `Base64` was removed. Replace all
|
||||||
|
`Base64<Foo>` references with `Base64.filtered(Foo)` to update
|
||||||
|
your code.
|
||||||
|
|
||||||
|
2. `FixedHex<N>` is not a valid expression. You will have to update
|
||||||
|
your code to use `FixedHex::<N>` instead.
|
||||||
|
|
||||||
|
This change overall allows for more flexibility in the implementation
|
||||||
|
of text codecs.
|
||||||
* Added
|
* Added
|
||||||
- Support for child elements in derive macros. Child elements may also
|
- Support for child elements in derive macros. Child elements may also
|
||||||
be wrapped in Option or Box.
|
be wrapped in Option or Box.
|
||||||
|
|
|
@ -35,6 +35,7 @@ such:
|
||||||
is also a path.
|
is also a path.
|
||||||
- *string literal*: A string literal, like `"hello world!"`.
|
- *string literal*: A string literal, like `"hello world!"`.
|
||||||
- *type*: A Rust type.
|
- *type*: A Rust type.
|
||||||
|
- *expression*: A Rust expression.
|
||||||
- *ident*: A Rust identifier.
|
- *ident*: A Rust identifier.
|
||||||
- flag: Has no value. The key's mere presence has relevance and it must not be
|
- flag: Has no value. The key's mere presence has relevance and it must not be
|
||||||
followed by a `=` sign.
|
followed by a `=` sign.
|
||||||
|
@ -258,9 +259,9 @@ element.
|
||||||
|
|
||||||
| Key | Value type | Description |
|
| Key | Value type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `codec` | *type* | Optional [`TextCodec`] implementation which is used to encode or decode the field. |
|
| `codec` | *expression* | Optional [`TextCodec`] implementation which is used to encode or decode the field. |
|
||||||
|
|
||||||
If `codec` is given, the given `codec` must implement
|
If `codec` is given, the given `codec` value must implement
|
||||||
[`TextCodec<T>`][`TextCodec`] where `T` is the type of the field.
|
[`TextCodec<T>`][`TextCodec`] where `T` is the type of the field.
|
||||||
|
|
||||||
If `codec` is *not* given, the field's type must implement [`FromXmlText`] for
|
If `codec` is *not* given, the field's type must implement [`FromXmlText`] for
|
||||||
|
|
115
xso/src/text.rs
115
xso/src/text.rs
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
//! Module containing implementations for conversions to/from XML text.
|
//! Module containing implementations for conversions to/from XML text.
|
||||||
|
|
||||||
#[cfg(feature = "base64")]
|
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
@ -138,23 +137,67 @@ convert_via_fromstr_and_display! {
|
||||||
/// [`FromXml`][`macro@crate::FromXml`] derive macro for details.
|
/// [`FromXml`][`macro@crate::FromXml`] derive macro for details.
|
||||||
pub trait TextCodec<T> {
|
pub trait TextCodec<T> {
|
||||||
/// Decode a string value into the type.
|
/// Decode a string value into the type.
|
||||||
fn decode(s: String) -> Result<T, Error>;
|
fn decode(&self, s: String) -> Result<T, Error>;
|
||||||
|
|
||||||
/// Encode the type as string value.
|
/// Encode the type as string value.
|
||||||
///
|
///
|
||||||
/// If this returns `None`, the string value is not emitted at all.
|
/// If this returns `None`, the string value is not emitted at all.
|
||||||
fn encode(value: &T) -> Result<Option<Cow<'_, str>>, Error>;
|
fn encode<'x>(&self, value: &'x T) -> Result<Option<Cow<'x, str>>, Error>;
|
||||||
|
|
||||||
|
/// Apply a filter to this codec.
|
||||||
|
///
|
||||||
|
/// Filters preprocess strings before they are handed to the codec for
|
||||||
|
/// parsing, allowing to, for example, make the codec ignore irrelevant
|
||||||
|
/// content by stripping it.
|
||||||
|
// NOTE: The bound on T is needed because any given type A may implement
|
||||||
|
// TextCodec for any number of types. If we pass T down to the `Filtered`
|
||||||
|
// struct, rustc can do type inferrence on which `TextCodec`
|
||||||
|
// implementation the `filtered` method is supposed to have been called
|
||||||
|
// on.
|
||||||
|
fn filtered<F: TextFilter>(self, filter: F) -> Filtered<F, Self, T>
|
||||||
|
where
|
||||||
|
// placing the bound here (instead of on the `TextCodec<T>` trait
|
||||||
|
// itself) preserves object-safety of TextCodec<T>.
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Filtered {
|
||||||
|
filter,
|
||||||
|
codec: self,
|
||||||
|
bound: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper struct to apply a filter to a codec.
|
||||||
|
///
|
||||||
|
/// You can construct a value of this type via [`TextCodec::filtered`].
|
||||||
|
// NOTE: see the note on TextCodec::filtered for why we bind `T` here, too.
|
||||||
|
pub struct Filtered<F, C, T> {
|
||||||
|
filter: F,
|
||||||
|
codec: C,
|
||||||
|
bound: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, F: TextFilter, C: TextCodec<T>> TextCodec<T> for Filtered<F, C, T> {
|
||||||
|
fn decode(&self, s: String) -> Result<T, Error> {
|
||||||
|
let s = self.filter.preprocess(s);
|
||||||
|
self.codec.decode(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode<'x>(&self, value: &'x T) -> Result<Option<Cow<'x, str>>, Error> {
|
||||||
|
self.codec.encode(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Text codec which does no transform.
|
/// Text codec which does no transform.
|
||||||
pub struct Plain;
|
pub struct Plain;
|
||||||
|
|
||||||
impl TextCodec<String> for Plain {
|
impl TextCodec<String> for Plain {
|
||||||
fn decode(s: String) -> Result<String, Error> {
|
fn decode(&self, s: String) -> Result<String, Error> {
|
||||||
Ok(s)
|
Ok(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(value: &String) -> Result<Option<Cow<'_, str>>, Error> {
|
fn encode<'x>(&self, value: &'x String) -> Result<Option<Cow<'x, str>>, Error> {
|
||||||
Ok(Some(Cow::Borrowed(value.as_str())))
|
Ok(Some(Cow::Borrowed(value.as_str())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,7 +206,7 @@ impl TextCodec<String> for Plain {
|
||||||
pub struct EmptyAsNone;
|
pub struct EmptyAsNone;
|
||||||
|
|
||||||
impl TextCodec<Option<String>> for EmptyAsNone {
|
impl TextCodec<Option<String>> for EmptyAsNone {
|
||||||
fn decode(s: String) -> Result<Option<String>, Error> {
|
fn decode(&self, s: String) -> Result<Option<String>, Error> {
|
||||||
if s.is_empty() {
|
if s.is_empty() {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
} else {
|
} else {
|
||||||
|
@ -171,7 +214,7 @@ impl TextCodec<Option<String>> for EmptyAsNone {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(value: &Option<String>) -> Result<Option<Cow<'_, str>>, Error> {
|
fn encode<'x>(&self, value: &'x Option<String>) -> Result<Option<Cow<'x, str>>, Error> {
|
||||||
Ok(match value.as_ref() {
|
Ok(match value.as_ref() {
|
||||||
Some(v) if !v.is_empty() => Some(Cow::Borrowed(v.as_str())),
|
Some(v) if !v.is_empty() => Some(Cow::Borrowed(v.as_str())),
|
||||||
Some(_) | None => None,
|
Some(_) | None => None,
|
||||||
|
@ -183,7 +226,7 @@ impl TextCodec<Option<String>> for EmptyAsNone {
|
||||||
pub struct EmptyAsError;
|
pub struct EmptyAsError;
|
||||||
|
|
||||||
impl TextCodec<String> for EmptyAsError {
|
impl TextCodec<String> for EmptyAsError {
|
||||||
fn decode(s: String) -> Result<String, Error> {
|
fn decode(&self, s: String) -> Result<String, Error> {
|
||||||
if s.is_empty() {
|
if s.is_empty() {
|
||||||
Err(Error::Other("Empty text node."))
|
Err(Error::Other("Empty text node."))
|
||||||
} else {
|
} else {
|
||||||
|
@ -191,7 +234,7 @@ impl TextCodec<String> for EmptyAsError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(value: &String) -> Result<Option<Cow<'_, str>>, Error> {
|
fn encode<'x>(&self, value: &'x String) -> Result<Option<Cow<'x, str>>, Error> {
|
||||||
if value.is_empty() {
|
if value.is_empty() {
|
||||||
Err(Error::Other("Empty text node."))
|
Err(Error::Other("Empty text node."))
|
||||||
} else {
|
} else {
|
||||||
|
@ -205,14 +248,14 @@ impl TextCodec<String> for EmptyAsError {
|
||||||
/// This may be used by codecs to allow to customize some of their behaviour.
|
/// This may be used by codecs to allow to customize some of their behaviour.
|
||||||
pub trait TextFilter {
|
pub trait TextFilter {
|
||||||
/// Process the incoming string and return the result of the processing.
|
/// Process the incoming string and return the result of the processing.
|
||||||
fn preprocess(s: String) -> String;
|
fn preprocess(&self, s: String) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Text preprocessor which returns the input unchanged.
|
/// Text preprocessor which returns the input unchanged.
|
||||||
pub struct NoFilter;
|
pub struct NoFilter;
|
||||||
|
|
||||||
impl TextFilter for NoFilter {
|
impl TextFilter for NoFilter {
|
||||||
fn preprocess(s: String) -> String {
|
fn preprocess(&self, s: String) -> String {
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,7 +264,7 @@ impl TextFilter for NoFilter {
|
||||||
pub struct StripWhitespace;
|
pub struct StripWhitespace;
|
||||||
|
|
||||||
impl TextFilter for StripWhitespace {
|
impl TextFilter for StripWhitespace {
|
||||||
fn preprocess(s: String) -> String {
|
fn preprocess(&self, s: String) -> String {
|
||||||
let s: String = s
|
let s: String = s
|
||||||
.chars()
|
.chars()
|
||||||
.filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t')
|
.filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t')
|
||||||
|
@ -237,56 +280,54 @@ impl TextFilter for StripWhitespace {
|
||||||
/// will make the implementation ignore any whitespace within the text.
|
/// will make the implementation ignore any whitespace within the text.
|
||||||
#[cfg(feature = "base64")]
|
#[cfg(feature = "base64")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
|
||||||
pub struct Base64<Filter: TextFilter = NoFilter>(PhantomData<Filter>);
|
pub struct Base64;
|
||||||
|
|
||||||
#[cfg(feature = "base64")]
|
#[cfg(feature = "base64")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
|
||||||
impl<Filter: TextFilter> TextCodec<Vec<u8>> for Base64<Filter> {
|
impl TextCodec<Vec<u8>> for Base64 {
|
||||||
fn decode(s: String) -> Result<Vec<u8>, Error> {
|
fn decode(&self, s: String) -> Result<Vec<u8>, Error> {
|
||||||
let value = Filter::preprocess(s);
|
|
||||||
StandardBase64Engine
|
StandardBase64Engine
|
||||||
.decode(value.as_bytes())
|
.decode(s.as_bytes())
|
||||||
.map_err(Error::text_parse_error)
|
.map_err(Error::text_parse_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(value: &Vec<u8>) -> Result<Option<Cow<'_, str>>, Error> {
|
fn encode<'x>(&self, value: &'x Vec<u8>) -> Result<Option<Cow<'x, str>>, Error> {
|
||||||
Ok(Some(Cow::Owned(StandardBase64Engine.encode(&value))))
|
Ok(Some(Cow::Owned(StandardBase64Engine.encode(&value))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "base64")]
|
#[cfg(feature = "base64")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
|
||||||
impl<'x, Filter: TextFilter> TextCodec<Cow<'x, [u8]>> for Base64<Filter> {
|
impl<'x> TextCodec<Cow<'x, [u8]>> for Base64 {
|
||||||
fn decode(s: String) -> Result<Cow<'x, [u8]>, Error> {
|
fn decode(&self, s: String) -> Result<Cow<'x, [u8]>, Error> {
|
||||||
let value = Filter::preprocess(s);
|
|
||||||
StandardBase64Engine
|
StandardBase64Engine
|
||||||
.decode(value.as_bytes())
|
.decode(s.as_bytes())
|
||||||
.map_err(Error::text_parse_error)
|
.map_err(Error::text_parse_error)
|
||||||
.map(Cow::Owned)
|
.map(Cow::Owned)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode<'a>(value: &'a Cow<'x, [u8]>) -> Result<Option<Cow<'a, str>>, Error> {
|
fn encode<'a>(&self, value: &'a Cow<'x, [u8]>) -> Result<Option<Cow<'a, str>>, Error> {
|
||||||
Ok(Some(Cow::Owned(StandardBase64Engine.encode(&value))))
|
Ok(Some(Cow::Owned(StandardBase64Engine.encode(&value))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "base64")]
|
#[cfg(feature = "base64")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
|
||||||
impl<T, Filter: TextFilter> TextCodec<Option<T>> for Base64<Filter>
|
impl<T> TextCodec<Option<T>> for Base64
|
||||||
where
|
where
|
||||||
Base64<Filter>: TextCodec<T>,
|
Base64: TextCodec<T>,
|
||||||
{
|
{
|
||||||
fn decode(s: String) -> Result<Option<T>, Error> {
|
fn decode(&self, s: String) -> Result<Option<T>, Error> {
|
||||||
if s.is_empty() {
|
if s.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
Ok(Some(Self::decode(s)?))
|
Ok(Some(self.decode(s)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(decoded: &Option<T>) -> Result<Option<Cow<'_, str>>, Error> {
|
fn encode<'x>(&self, decoded: &'x Option<T>) -> Result<Option<Cow<'x, str>>, Error> {
|
||||||
decoded
|
decoded
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(Self::encode)
|
.map(|x| self.encode(x))
|
||||||
.transpose()
|
.transpose()
|
||||||
.map(Option::flatten)
|
.map(Option::flatten)
|
||||||
}
|
}
|
||||||
|
@ -298,7 +339,7 @@ where
|
||||||
pub struct FixedHex<const N: usize>;
|
pub struct FixedHex<const N: usize>;
|
||||||
|
|
||||||
impl<const N: usize> TextCodec<[u8; N]> for FixedHex<N> {
|
impl<const N: usize> TextCodec<[u8; N]> for FixedHex<N> {
|
||||||
fn decode(s: String) -> Result<[u8; N], Error> {
|
fn decode(&self, s: String) -> Result<[u8; N], Error> {
|
||||||
if s.len() != 2 * N {
|
if s.len() != 2 * N {
|
||||||
return Err(Error::Other("Invalid length"));
|
return Err(Error::Other("Invalid length"));
|
||||||
}
|
}
|
||||||
|
@ -312,7 +353,7 @@ impl<const N: usize> TextCodec<[u8; N]> for FixedHex<N> {
|
||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(value: &[u8; N]) -> Result<Option<Cow<'_, str>>, Error> {
|
fn encode<'x>(&self, value: &'x [u8; N]) -> Result<Option<Cow<'x, str>>, Error> {
|
||||||
let mut bytes = String::with_capacity(N * 2);
|
let mut bytes = String::with_capacity(N * 2);
|
||||||
for byte in value {
|
for byte in value {
|
||||||
bytes.extend(format!("{:02x}", byte).chars());
|
bytes.extend(format!("{:02x}", byte).chars());
|
||||||
|
@ -325,17 +366,17 @@ impl<T, const N: usize> TextCodec<Option<T>> for FixedHex<N>
|
||||||
where
|
where
|
||||||
FixedHex<N>: TextCodec<T>,
|
FixedHex<N>: TextCodec<T>,
|
||||||
{
|
{
|
||||||
fn decode(s: String) -> Result<Option<T>, Error> {
|
fn decode(&self, s: String) -> Result<Option<T>, Error> {
|
||||||
if s.is_empty() {
|
if s.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
Ok(Some(Self::decode(s)?))
|
Ok(Some(self.decode(s)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(decoded: &Option<T>) -> Result<Option<Cow<'_, str>>, Error> {
|
fn encode<'x>(&self, decoded: &'x Option<T>) -> Result<Option<Cow<'x, str>>, Error> {
|
||||||
decoded
|
decoded
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(Self::encode)
|
.map(|x| self.encode(x))
|
||||||
.transpose()
|
.transpose()
|
||||||
.map(Option::flatten)
|
.map(Option::flatten)
|
||||||
}
|
}
|
||||||
|
@ -345,7 +386,7 @@ where
|
||||||
pub struct ColonSeparatedHex;
|
pub struct ColonSeparatedHex;
|
||||||
|
|
||||||
impl TextCodec<Vec<u8>> for ColonSeparatedHex {
|
impl TextCodec<Vec<u8>> for ColonSeparatedHex {
|
||||||
fn decode(s: String) -> Result<Vec<u8>, Error> {
|
fn decode(&self, s: String) -> Result<Vec<u8>, Error> {
|
||||||
assert_eq!((s.len() + 1) % 3, 0);
|
assert_eq!((s.len() + 1) % 3, 0);
|
||||||
let mut bytes = Vec::with_capacity((s.len() + 1) / 3);
|
let mut bytes = Vec::with_capacity((s.len() + 1) / 3);
|
||||||
for i in 0..(1 + s.len()) / 3 {
|
for i in 0..(1 + s.len()) / 3 {
|
||||||
|
@ -359,7 +400,7 @@ impl TextCodec<Vec<u8>> for ColonSeparatedHex {
|
||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(decoded: &Vec<u8>) -> Result<Option<Cow<'_, str>>, Error> {
|
fn encode<'x>(&self, decoded: &'x Vec<u8>) -> Result<Option<Cow<'x, str>>, Error> {
|
||||||
// TODO: Super inefficient!
|
// TODO: Super inefficient!
|
||||||
let mut bytes = Vec::with_capacity(decoded.len());
|
let mut bytes = Vec::with_capacity(decoded.len());
|
||||||
for byte in decoded {
|
for byte in decoded {
|
||||||
|
|
Loading…
Reference in a new issue