diff --git a/instant-xml-macros/src/de.rs b/instant-xml-macros/src/de.rs index b1c91c0..d1fa1cd 100644 --- a/instant-xml-macros/src/de.rs +++ b/instant-xml-macros/src/de.rs @@ -1,4 +1,6 @@ -use proc_macro2::{Ident, Span, TokenStream}; +use std::collections::BTreeSet; + +use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::quote; use syn::spanned::Spanned; @@ -49,7 +51,7 @@ fn deserialize_scalar_enum( variants.extend(quote!(Ok(#serialize_as) => #ident::#v_ident,)); } - let generics = meta.xml_generics(); + let generics = meta.xml_generics(BTreeSet::new()); let (impl_generics, _, _) = generics.split_for_impl(); let (_, ty_generics, where_clause) = input.generics.split_for_impl(); @@ -90,6 +92,7 @@ fn deserialize_wrapped_enum( let ident = &input.ident; let mut variants = TokenStream::new(); + let mut borrowed = BTreeSet::new(); for variant in data.variants.iter() { let field = match &variant.fields { syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { @@ -113,7 +116,7 @@ fn deserialize_wrapped_enum( } let mut no_lifetime_type = field.ty.clone(); - discard_lifetimes(&mut no_lifetime_type); + discard_lifetimes(&mut no_lifetime_type, &mut borrowed, false, true); if !variants.is_empty() { variants.extend(quote!(else)); @@ -132,7 +135,7 @@ fn deserialize_wrapped_enum( let name = meta.tag(); let default_namespace = meta.default_namespace(); - let generics = meta.xml_generics(); + let generics = meta.xml_generics(borrowed); let (xml_impl_generics, _, _) = generics.split_for_impl(); let (_, ty_generics, where_clause) = input.generics.split_for_impl(); quote!( @@ -194,6 +197,7 @@ fn deserialize_struct( let mut declare_values = TokenStream::new(); let mut return_val = TokenStream::new(); + let mut borrowed = BTreeSet::new(); for (index, field) in fields.named.iter().enumerate() { let field_meta = match FieldMeta::from_field(field, &container_meta) { Ok(meta) => meta, @@ -205,15 +209,20 @@ fn deserialize_struct( false => &mut elements_tokens, }; - named_field( + let result = named_field( field, index, &mut declare_values, &mut return_val, tokens, + &mut borrowed, field_meta, &container_meta, ); + + if let Err(err) = result { + return err.into_compile_error(); + } } // Elements @@ -237,7 +246,7 @@ fn deserialize_struct( let ident = &input.ident; let name = container_meta.tag(); let default_namespace = container_meta.default_namespace(); - let generics = container_meta.xml_generics(); + let generics = container_meta.xml_generics(borrowed); let (xml_impl_generics, _, _) = generics.split_for_impl(); let (_, ty_generics, where_clause) = input.generics.split_for_impl(); @@ -313,9 +322,10 @@ fn named_field( declare_values: &mut TokenStream, return_val: &mut TokenStream, tokens: &mut Tokens, - field_meta: FieldMeta, + borrowed: &mut BTreeSet, + mut field_meta: FieldMeta, container_meta: &ContainerMeta, -) { +) -> Result<(), syn::Error> { let field_name = field.ident.as_ref().unwrap(); let field_tag = field_meta.tag; let default_ns = match &field_meta.ns.uri { @@ -329,8 +339,18 @@ fn named_field( None => quote!(""), }; + if field_meta.borrow && field_meta.deserialize_with.is_none() { + if is_cow(&field.ty, is_str) { + field_meta.deserialize_with = + Some(Literal::string("::instant_xml::de::borrow_cow_str")); + } else if is_cow(&field.ty, is_slice_u8) { + field_meta.deserialize_with = + Some(Literal::string("::instant_xml::de::borrow_cow_slice_u8")); + } + } + let mut no_lifetime_type = field.ty.clone(); - discard_lifetimes(&mut no_lifetime_type); + discard_lifetimes(&mut no_lifetime_type, borrowed, field_meta.borrow, true); let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site()); tokens.r#enum.extend(quote!(#enum_name,)); @@ -351,27 +371,58 @@ fn named_field( let mut #enum_name: Option<#no_lifetime_type> = None; )); + let deserialize_with = field_meta + .deserialize_with + .map(|with| { + let path = with.to_string(); + syn::parse_str::(path.trim_matches('"')).map_err(|err| { + syn::Error::new( + with.span(), + format!("failed to parse deserialize_with as path: {err}"), + ) + }) + }) + .transpose()?; + if !field_meta.attribute { - tokens.r#match.extend(quote!( - __Elements::#enum_name => match <#no_lifetime_type as FromXml>::KIND { - Kind::Element(_) => { + if let Some(with) = deserialize_with { + tokens.r#match.extend(quote!( + __Elements::#enum_name => { let mut nested = deserializer.nested(data); - FromXml::deserialize(&mut nested, &mut #enum_name)?; - } - Kind::Scalar => { - let mut nested = deserializer.nested(data); - FromXml::deserialize(&mut nested, &mut #enum_name)?; - nested.ignore()?; - } - }, - )); + #with(&mut nested, &mut #enum_name)?; + }, + )); + } else { + tokens.r#match.extend(quote!( + __Elements::#enum_name => match <#no_lifetime_type as FromXml>::KIND { + Kind::Element(_) => { + let mut nested = deserializer.nested(data); + FromXml::deserialize(&mut nested, &mut #enum_name)?; + } + Kind::Scalar => { + let mut nested = deserializer.nested(data); + FromXml::deserialize(&mut nested, &mut #enum_name)?; + nested.ignore()?; + } + }, + )); + } } else { - tokens.r#match.extend(quote!( - __Attributes::#enum_name => { - let mut nested = deserializer.for_node(Node::AttributeValue(attr.value)); - let new = <#no_lifetime_type>::deserialize(&mut nested, &mut #enum_name)?; - }, - )); + if let Some(with) = deserialize_with { + tokens.r#match.extend(quote!( + __Attributes::#enum_name => { + let mut nested = deserializer.nested(data); + #with(&mut nested, &mut #enum_name)?; + }, + )); + } else { + tokens.r#match.extend(quote!( + __Attributes::#enum_name => { + let mut nested = deserializer.for_node(Node::AttributeValue(attr.value)); + let new = <#no_lifetime_type>::deserialize(&mut nested, &mut #enum_name)?; + }, + )); + } } return_val.extend(quote!( @@ -380,6 +431,8 @@ fn named_field( None => <#no_lifetime_type>::missing_value()?, }, )); + + Ok(()) } fn deserialize_tuple_struct( @@ -397,7 +450,7 @@ fn deserialize_tuple_struct( // Varying values let mut declare_values = TokenStream::new(); let mut return_val = TokenStream::new(); - + let mut borrowed = BTreeSet::new(); for (index, field) in fields.unnamed.iter().enumerate() { if !field.attrs.is_empty() { return syn::Error::new( @@ -407,13 +460,19 @@ fn deserialize_tuple_struct( .to_compile_error(); } - unnamed_field(field, index, &mut declare_values, &mut return_val); + unnamed_field( + field, + index, + &mut declare_values, + &mut return_val, + &mut borrowed, + ); } let ident = &input.ident; let name = container_meta.tag(); let default_namespace = container_meta.default_namespace(); - let generics = container_meta.xml_generics(); + let generics = container_meta.xml_generics(borrowed); let (xml_impl_generics, _, _) = generics.split_for_impl(); let (_, ty_generics, where_clause) = input.generics.split_for_impl(); @@ -448,9 +507,10 @@ fn unnamed_field( index: usize, declare_values: &mut TokenStream, return_val: &mut TokenStream, + borrowed: &mut BTreeSet, ) { let mut no_lifetime_type = field.ty.clone(); - discard_lifetimes(&mut no_lifetime_type); + discard_lifetimes(&mut no_lifetime_type, borrowed, false, true); let name = Ident::new(&format!("v{index}"), Span::call_site()); declare_values.extend(quote!( @@ -487,7 +547,7 @@ fn deserialize_unit_struct(input: &syn::DeriveInput, meta: &ContainerMeta) -> To let ident = &input.ident; let name = meta.tag(); let default_namespace = meta.default_namespace(); - let generics = meta.xml_generics(); + let generics = meta.xml_generics(BTreeSet::new()); let (xml_impl_generics, _, _) = generics.split_for_impl(); let (_, ty_generics, where_clause) = input.generics.split_for_impl(); @@ -511,6 +571,68 @@ fn deserialize_unit_struct(input: &syn::DeriveInput, meta: &ContainerMeta) -> To ) } +fn is_cow(ty: &syn::Type, elem: fn(&syn::Type) -> bool) -> bool { + let path = match ungroup(ty) { + syn::Type::Path(ty) => &ty.path, + _ => { + return false; + } + }; + + let seg = match path.segments.last() { + Some(seg) => seg, + None => { + return false; + } + }; + + let args = match &seg.arguments { + syn::PathArguments::AngleBracketed(bracketed) => &bracketed.args, + _ => { + return false; + } + }; + + seg.ident == "Cow" + && args.len() == 2 + && match (&args[0], &args[1]) { + (syn::GenericArgument::Lifetime(_), syn::GenericArgument::Type(arg)) => elem(arg), + _ => false, + } +} + +fn is_str(ty: &syn::Type) -> bool { + is_primitive_type(ty, "str") +} + +fn is_slice_u8(ty: &syn::Type) -> bool { + match ungroup(ty) { + syn::Type::Slice(ty) => is_primitive_type(&ty.elem, "u8"), + _ => false, + } +} + +fn is_primitive_type(ty: &syn::Type, primitive: &str) -> bool { + match ungroup(ty) { + syn::Type::Path(ty) => ty.qself.is_none() && is_primitive_path(&ty.path, primitive), + _ => false, + } +} + +fn is_primitive_path(path: &syn::Path, primitive: &str) -> bool { + path.leading_colon.is_none() + && path.segments.len() == 1 + && path.segments[0].ident == primitive + && path.segments[0].arguments.is_empty() +} + +pub fn ungroup(mut ty: &syn::Type) -> &syn::Type { + while let syn::Type::Group(group) = ty { + ty = &group.elem; + } + ty +} + #[derive(Default)] struct Tokens { r#enum: TokenStream, diff --git a/instant-xml-macros/src/lib.rs b/instant-xml-macros/src/lib.rs index 23c3785..9ad4a1c 100644 --- a/instant-xml-macros/src/lib.rs +++ b/instant-xml-macros/src/lib.rs @@ -1,5 +1,8 @@ extern crate proc_macro; +use std::collections::BTreeSet; +use std::mem; + use proc_macro2::{Literal, Span, TokenStream}; use quote::{quote, ToTokens}; use syn::spanned::Spanned; @@ -71,11 +74,10 @@ impl<'input> ContainerMeta<'input> { }) } - fn xml_generics(&self) -> Generics { + fn xml_generics<'a>(&self, borrowed: BTreeSet) -> Generics { let mut xml_generics = self.input.generics.clone(); let mut xml = syn::LifetimeDef::new(syn::Lifetime::new("'xml", Span::call_site())); - xml.bounds - .extend(xml_generics.lifetimes().map(|lt| lt.lifetime.clone())); + xml.bounds.extend(borrowed.into_iter()); xml_generics.params.push(xml.into()); for param in xml_generics.type_params_mut() { @@ -105,9 +107,11 @@ impl<'input> ContainerMeta<'input> { #[derive(Debug, Default)] struct FieldMeta { attribute: bool, + borrow: bool, ns: NamespaceMeta, tag: TokenStream, serialize_with: Option, + deserialize_with: Option, } impl FieldMeta { @@ -124,9 +128,11 @@ impl FieldMeta { for (item, span) in meta_items(&input.attrs) { match item { MetaItem::Attribute => meta.attribute = true, + MetaItem::Borrow => meta.borrow = true, MetaItem::Ns(ns) => meta.ns = ns, MetaItem::Rename(lit) => meta.tag = quote!(#lit), MetaItem::SerializeWith(lit) => meta.serialize_with = Some(lit), + MetaItem::DeserializeWith(lit) => meta.deserialize_with = Some(lit), MetaItem::RenameAll(_) => { return Err(syn::Error::new( span, @@ -216,20 +222,51 @@ impl VariantMeta { } } -fn discard_lifetimes(ty: &mut syn::Type) { +fn discard_lifetimes( + ty: &mut syn::Type, + borrowed: &mut BTreeSet, + borrow: bool, + top: bool, +) { match ty { - syn::Type::Path(ty) => discard_path_lifetimes(ty), + syn::Type::Path(ty) => discard_path_lifetimes(ty, borrowed, borrow), syn::Type::Reference(ty) => { - ty.lifetime = None; - discard_lifetimes(&mut ty.elem); + if top { + // If at the top level, we'll want to borrow from `&'a str` and `&'a [u8]`. + match &*ty.elem { + syn::Type::Path(inner) if top && inner.path.is_ident("str") => { + if let Some(lt) = ty.lifetime.take() { + borrowed.insert(lt); + } + } + syn::Type::Slice(inner) if top => match &*inner.elem { + syn::Type::Path(inner) if inner.path.is_ident("u8") => { + borrowed.extend(ty.lifetime.take().into_iter()); + } + _ => {} + }, + _ => {} + } + } else if borrow { + // Otherwise, only borrow if the user has requested it. + borrowed.extend(ty.lifetime.take().into_iter()); + } else { + ty.lifetime = None; + } + + discard_lifetimes(&mut ty.elem, borrowed, borrow, false); } _ => {} } } -fn discard_path_lifetimes(path: &mut syn::TypePath) { +fn discard_path_lifetimes( + path: &mut syn::TypePath, + borrowed: &mut BTreeSet, + borrow: bool, +) { if let Some(q) = &mut path.qself { - discard_lifetimes(&mut q.ty); + discard_lifetimes(&mut q.ty, borrowed, borrow, false); } for segment in &mut path.path.segments { @@ -238,17 +275,23 @@ fn discard_path_lifetimes(path: &mut syn::TypePath) { syn::PathArguments::AngleBracketed(args) => { args.args.iter_mut().for_each(|arg| match arg { syn::GenericArgument::Lifetime(lt) => { - *lt = syn::Lifetime::new("'_", Span::call_site()) + let lt = mem::replace(lt, syn::Lifetime::new("'_", Span::call_site())); + if borrow { + borrowed.insert(lt); + } + } + syn::GenericArgument::Type(ty) => { + discard_lifetimes(ty, borrowed, borrow, false) } - syn::GenericArgument::Type(ty) => discard_lifetimes(ty), syn::GenericArgument::Binding(_) | syn::GenericArgument::Constraint(_) | syn::GenericArgument::Const(_) => {} }) } - syn::PathArguments::Parenthesized(args) => { - args.inputs.iter_mut().for_each(discard_lifetimes) - } + syn::PathArguments::Parenthesized(args) => args + .inputs + .iter_mut() + .for_each(|ty| discard_lifetimes(ty, borrowed, borrow, false)), } } } diff --git a/instant-xml-macros/src/meta.rs b/instant-xml-macros/src/meta.rs index b4b58de..bb168b7 100644 --- a/instant-xml-macros/src/meta.rs +++ b/instant-xml-macros/src/meta.rs @@ -292,6 +292,9 @@ pub(crate) fn meta_items(attrs: &[syn::Attribute]) -> Vec<(MetaItem, Span)> { if id == "attribute" { items.push((MetaItem::Attribute, span)); MetaState::Comma + } else if id == "borrow" { + items.push((MetaItem::Borrow, span)); + MetaState::Comma } else if id == "ns" { MetaState::Ns } else if id == "rename" { @@ -306,6 +309,8 @@ pub(crate) fn meta_items(attrs: &[syn::Attribute]) -> Vec<(MetaItem, Span)> { MetaState::Comma } else if id == "serialize_with" { MetaState::SerializeWith + } else if id == "deserialize_with" { + MetaState::DeserializeWith } else { panic!("unexpected key in xml attribute"); } @@ -340,6 +345,13 @@ pub(crate) fn meta_items(attrs: &[syn::Attribute]) -> Vec<(MetaItem, Span)> { items.push((MetaItem::SerializeWith(lit), span)); MetaState::Comma } + (MetaState::DeserializeWith, TokenTree::Punct(punct)) if punct.as_char() == '=' => { + MetaState::DeserializeWithValue + } + (MetaState::DeserializeWithValue, TokenTree::Literal(lit)) => { + items.push((MetaItem::DeserializeWith(lit), span)); + MetaState::Comma + } (state, tree) => { panic!( "invalid state transition while parsing xml attribute ({}, {tree})", @@ -363,6 +375,8 @@ enum MetaState { RenameAllValue, SerializeWith, SerializeWithValue, + DeserializeWith, + DeserializeWithValue, } impl MetaState { @@ -377,6 +391,8 @@ impl MetaState { MetaState::RenameAllValue => "RenameAllValue", MetaState::SerializeWith => "SerializeWith", MetaState::SerializeWithValue => "SerializeWithValue", + MetaState::DeserializeWith => "DeserializeWith", + MetaState::DeserializeWithValue => "DeserializeWithValue", } } } @@ -460,9 +476,11 @@ impl fmt::Debug for Namespace { #[derive(Debug)] pub(crate) enum MetaItem { Attribute, + Borrow, Ns(NamespaceMeta), Rename(Literal), Mode(Mode), RenameAll(Literal), SerializeWith(Literal), + DeserializeWith(Literal), } diff --git a/instant-xml-macros/src/ser.rs b/instant-xml-macros/src/ser.rs index d5f93ed..d47d818 100644 --- a/instant-xml-macros/src/ser.rs +++ b/instant-xml-macros/src/ser.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use proc_macro2::TokenStream; use quote::quote; use syn::spanned::Spanned; @@ -180,12 +182,14 @@ fn serialize_struct( let tag = meta.tag(); let mut body = TokenStream::new(); let mut attributes = TokenStream::new(); - + let mut borrowed = BTreeSet::new(); match &data.fields { syn::Fields::Named(fields) => { body.extend(quote!(serializer.end_start()?;)); for field in &fields.named { - if let Err(err) = named_field(field, &mut body, &mut attributes, &meta) { + if let Err(err) = + named_field(field, &mut body, &mut attributes, &mut borrowed, &meta) + { return err.to_compile_error(); } } @@ -194,7 +198,7 @@ fn serialize_struct( syn::Fields::Unnamed(fields) => { body.extend(quote!(serializer.end_start()?;)); for (index, field) in fields.unnamed.iter().enumerate() { - if let Err(err) = unnamed_field(field, index, &mut body) { + if let Err(err) = unnamed_field(field, index, &mut body, &mut borrowed) { return err.to_compile_error(); } } @@ -261,6 +265,7 @@ fn named_field( field: &syn::Field, body: &mut TokenStream, attributes: &mut TokenStream, + borrowed: &mut BTreeSet, meta: &ContainerMeta, ) -> Result<(), syn::Error> { let field_name = field.ident.as_ref().unwrap(); @@ -338,7 +343,7 @@ fn named_field( }; let mut no_lifetime_type = field.ty.clone(); - discard_lifetimes(&mut no_lifetime_type); + discard_lifetimes(&mut no_lifetime_type, borrowed, false, true); body.extend(quote!( self.#field_name.serialize(Some(::instant_xml::Id { ns: #ns, name: #tag }), serializer)?; )); @@ -350,6 +355,7 @@ fn unnamed_field( field: &syn::Field, index: usize, body: &mut TokenStream, + borrowed: &mut BTreeSet, ) -> Result<(), syn::Error> { if !field.attrs.is_empty() { return Err(syn::Error::new( @@ -359,7 +365,7 @@ fn unnamed_field( } let mut no_lifetime_type = field.ty.clone(); - discard_lifetimes(&mut no_lifetime_type); + discard_lifetimes(&mut no_lifetime_type, borrowed, false, true); let index = syn::Index::from(index); body.extend(quote!( self.#index.serialize(None, serializer)?; diff --git a/instant-xml/src/de.rs b/instant-xml/src/de.rs index 2e40e50..b2e647a 100644 --- a/instant-xml/src/de.rs +++ b/instant-xml/src/de.rs @@ -1,7 +1,9 @@ +use std::borrow::Cow; use std::collections::{BTreeMap, VecDeque}; use xmlparser::{ElementEnd, Token, Tokenizer}; +use crate::impls::decode; use crate::{Error, Id, Kind}; pub struct Deserializer<'cx, 'xml> { @@ -320,6 +322,38 @@ impl<'xml> Iterator for Context<'xml> { } } +pub fn borrow_cow_str<'xml>( + deserializer: &mut Deserializer<'_, 'xml>, + into: &mut Option>, +) -> Result<(), Error> { + if into.is_some() { + return Err(Error::DuplicateValue); + } + + let value = deserializer.take_str()?; + *into = Some(decode(value)); + deserializer.ignore()?; + Ok(()) +} + +pub fn borrow_cow_slice_u8<'xml>( + deserializer: &mut Deserializer<'_, 'xml>, + into: &mut Option>, +) -> Result<(), Error> { + if into.is_some() { + return Err(Error::DuplicateValue); + } + + let value = deserializer.take_str()?; + *into = Some(match decode(value) { + Cow::Borrowed(v) => Cow::Borrowed(v.as_bytes()), + Cow::Owned(v) => Cow::Owned(v.into_bytes()), + }); + + deserializer.ignore()?; + Ok(()) +} + #[derive(Debug)] pub enum Node<'xml> { Attribute(Attribute<'xml>), diff --git a/instant-xml/src/impls.rs b/instant-xml/src/impls.rs index 324f84c..97fa9c2 100644 --- a/instant-xml/src/impls.rs +++ b/instant-xml/src/impls.rs @@ -176,15 +176,9 @@ impl<'xml> FromXml<'xml> for String { return Err(Error::DuplicateValue); } - let mut value = None; - as FromXml<'xml>>::deserialize(deserializer, &mut value)?; - match value { - Some(value) => { - *into = Some(value.into_owned()); - Ok(()) - } - None => Err(Error::MissingValue(&Kind::Scalar)), - } + let value = deserializer.take_str()?; + *into = Some(decode(value).into_owned()); + Ok(()) } const KIND: Kind<'static> = Kind::Scalar; @@ -199,24 +193,27 @@ impl<'xml> FromXml<'xml> for &'xml str { return Err(Error::DuplicateValue); } - let mut value = None; - as FromXml<'xml>>::deserialize(deserializer, &mut value)?; - match value { - Some(Cow::Borrowed(s)) => { - *into = Some(s); - Ok(()) + let value = deserializer.take_str()?; + match decode(value) { + Cow::Borrowed(str) => *into = Some(str), + Cow::Owned(_) => { + return Err(Error::UnexpectedValue( + "string with escape characters cannot be deserialized as &str", + )) } - Some(Cow::Owned(_)) => Err(Error::UnexpectedValue( - "string with escape characters cannot be deserialized as &str", - )), - None => Err(Error::MissingValue(&Kind::Scalar)), } + + Ok(()) } const KIND: Kind<'static> = Kind::Scalar; } -impl<'xml> FromXml<'xml> for Cow<'xml, str> { +impl<'xml, 'a, T: ?Sized> FromXml<'xml> for Cow<'a, T> +where + T: ToOwned, + T::Owned: FromXml<'xml>, +{ fn deserialize( deserializer: &mut Deserializer<'_, 'xml>, into: &mut Option, @@ -225,9 +222,15 @@ impl<'xml> FromXml<'xml> for Cow<'xml, str> { return Err(Error::DuplicateValue); } - let value = deserializer.take_str()?; - *into = Some(decode(value)); - Ok(()) + let mut value = None; + T::Owned::deserialize(deserializer, &mut value)?; + match value { + Some(value) => { + *into = Some(Cow::Owned(value)); + Ok(()) + } + None => Err(Error::MissingValue(&Kind::Scalar)), + } } const KIND: Kind<'static> = Kind::Scalar; @@ -386,7 +389,7 @@ fn encode(input: &str) -> Result, Error> { Ok(Cow::Owned(result)) } -fn decode(input: &str) -> Cow<'_, str> { +pub(crate) fn decode(input: &str) -> Cow<'_, str> { let mut result = String::with_capacity(input.len()); let input_len = input.len(); diff --git a/instant-xml/tests/entities.rs b/instant-xml/tests/entities.rs index 3dac1b2..3aa15a0 100644 --- a/instant-xml/tests/entities.rs +++ b/instant-xml/tests/entities.rs @@ -9,6 +9,7 @@ use instant_xml::{from_str, to_string, Error, FromXml, ToXml}; struct StructSpecialEntities<'a> { string: String, str: &'a str, + #[xml(borrow)] cow: Cow<'a, str>, }