4545//! [`Vcard::version`] field.
4646//!
4747//! During serialization, the value of [`Vcard::version`] is placed at index 0 in the properties array.
48- use serde:: { Deserialize , Serialize } ;
48+ use serde:: Deserialize ;
49+ use serde_with:: serde_as;
4950use std:: collections:: HashMap ;
5051
5152mod de;
5253mod ser;
5354
55+ #[ doc( hidden) ]
56+ #[ macro_use]
57+ pub mod macros;
58+
5459pub use structured:: * ;
5560pub mod structured;
5661
62+ pub type Parameters = HashMap < String , Vec < String > > ;
63+
5764/// A jCard serde type
5865#[ derive( Debug , Clone , PartialEq ) ]
5966pub struct Vcard {
@@ -84,7 +91,31 @@ pub struct Property {
8491 pub name : String ,
8592
8693 /// The list of parameters such as the laguage or the preference value.
87- pub parameters : HashMap < String , String > ,
94+ ///
95+ /// The hashmap's value [`Vec`] will be converted to a single string in JSON if it only has 1 element.
96+ ///
97+ /// ```rust
98+ /// # use vicardi::*;
99+ /// # use serde_json::json;
100+ /// # fn main() -> anyhow::Result<()> {
101+ /// let json_array = json!(["", {"foo": ["structured"]}, "", ""]);
102+ /// let property: Property = serde_json::from_value(json_array)?;
103+ /// let structured = &property.parameters;
104+ /// assert_eq!(
105+ /// structured,
106+ /// ¶meters! {"foo" => "structured"}
107+ /// );
108+ ///
109+ /// let json_string = json!(["", {"foo": "structured"}, "", ""]);
110+ /// let string = serde_json::to_value(property)?;
111+ /// assert_eq!(
112+ /// string,
113+ /// json_string
114+ /// );
115+ /// # Ok(())
116+ /// # }
117+ /// ```
118+ pub parameters : Parameters ,
88119
89120 /// The value type. E.g. `"text"`
90121 pub value_type : String ,
@@ -94,7 +125,9 @@ pub struct Property {
94125 /// When the array has multiple elements, they are appended at the level of the property array in jCard format:
95126 ///
96127 /// ```json
97- /// ["categories", {}, "text", "rust", "serde"]
128+ /// ["categories", {}, "text",
129+ /// "rust", "serde"
130+ /// ]
98131 /// ```
99132 ///
100133 /// Where rust and serde are [`PropertyValue::String`]
@@ -104,20 +137,71 @@ pub struct Property {
104137 pub values : Vec < PropertyValue > ,
105138}
106139
107- /// A [`Property::values`] can either be a simple string or an array of strings.
140+ /// A [`Property::values`] can either be a single value or an array of values. See an example json representation for
141+ /// each variant.
108142///
109- /// ```json
110- /// ["fn", {}, "text", "Vicardi"]
143+ /// Per [RFC 7095, Section 3.3.1.3](https://datatracker.ietf.org/doc/html/rfc7095#section-3.3.1.3)
144+ /// > The array element values MUST have the primitive type that matches the jCard type identifier. In RFC6350,
145+ /// > there are only structured text values and thus only JSON strings are used. For example, extensions may define
146+ /// > structured number or boolean values, where JSON number or boolean types MUST be used.
147+ /// >
148+ /// > ...
149+ /// >
150+ /// > Although it is allowed for a structured property value to hold just one component, it is RECOMMENDED to represent
151+ /// > it as a single text value instead, omitting the array completely. Nevertheless, a simple implementation MAY
152+ /// > choose to retain the array, with a single text value as its element.
111153///
112- /// ["org", {}, "text",
113- /// ["Organization", "Department", "etc"]
114- /// ]
154+ /// ```rust
155+ /// # use vicardi::*;
156+ /// # use serde_json::json;
157+ /// # fn main() -> anyhow::Result<()> {
158+ /// let json_array = json!(["structured"]);
159+ /// let structured: PropertyValue = serde_json::from_value(json_array)?;
160+ /// assert_eq!(
161+ /// structured,
162+ /// PropertyValue::Structured(vec!["structured".into()])
163+ /// );
164+ ///
165+ /// let json_string = json!("structured");
166+ /// let string = serde_json::to_value(structured)?;
167+ /// assert_eq!(
168+ /// string,
169+ /// json_string
170+ /// );
171+ /// # Ok(())
172+ /// # }
115173/// ```
116- #[ derive( Debug , Clone , PartialEq , Serialize , Deserialize ) ]
174+ #[ serde_as]
175+ #[ derive( Debug , Clone , PartialEq , Deserialize ) ]
117176#[ serde( untagged) ]
118177pub enum PropertyValue {
178+ /// ```json
179+ /// ["fn", {}, "text", "Vicardi"]
180+ /// ```
119181 String ( String ) ,
120- Structured ( Vec < String > ) ,
182+
183+ /// ```json
184+ /// ["x-use-rust", {}, "boolean", true]
185+ /// ```
186+ Bool ( bool ) ,
187+
188+ /// ```json
189+ /// ["x-karma-points", {}, "integer", 42]
190+ /// ```
191+ Integer ( i64 ) ,
192+
193+ /// ```json
194+ /// ["x-grade", {}, "integer", 1.3]
195+ /// ```
196+ Float ( f64 ) ,
197+
198+ /// Example structured string property value:
199+ /// ```json
200+ /// ["org", {}, "text",
201+ /// ["Organization", "Department", "etc"]
202+ /// ]
203+ /// ```
204+ Structured ( Vec < PropertyValue > ) ,
121205}
122206
123207impl Vcard {
@@ -134,7 +218,7 @@ impl Property {
134218 /// Creates a new property, where [`Property::values`] is a `vec![value]`.
135219 pub fn new (
136220 name : impl ToString ,
137- parameters : impl Into < Option < HashMap < String , String > > > ,
221+ parameters : impl Into < Option < Parameters > > ,
138222 value_type : impl ToString ,
139223 value : impl Into < PropertyValue > ,
140224 ) -> Self {
@@ -143,7 +227,7 @@ impl Property {
143227
144228 pub fn new_multivalued (
145229 name : impl ToString ,
146- parameters : impl Into < Option < HashMap < String , String > > > ,
230+ parameters : impl Into < Option < Parameters > > ,
147231 value_type : impl ToString ,
148232 values : Vec < PropertyValue > ,
149233 ) -> Self {
@@ -179,10 +263,7 @@ impl Property {
179263 /// # Ok(())
180264 /// # }
181265 /// ```
182- pub fn new_fn (
183- formatted : impl ToString ,
184- parameters : impl Into < Option < HashMap < String , String > > > ,
185- ) -> Self {
266+ pub fn new_fn ( formatted : impl ToString , parameters : impl Into < Option < Parameters > > ) -> Self {
186267 Self :: new ( "fn" , parameters, "text" , formatted)
187268 }
188269
@@ -204,7 +285,7 @@ impl Property {
204285 ///
205286 /// vcard.push(Property::new_adr(
206287 /// address.clone(),
207- /// Some([( "pref".to_string(), "1".to_string())].into_iter().collect())
288+ /// parameters!{ "pref" => "1"}
208289 /// ));
209290 /// vcard.push(address);
210291 ///
@@ -241,10 +322,7 @@ impl Property {
241322 /// # Ok(())
242323 /// # }
243324 /// ```
244- pub fn new_adr (
245- address : Address ,
246- parameters : impl Into < Option < HashMap < String , String > > > ,
247- ) -> Self {
325+ pub fn new_adr ( address : Address , parameters : impl Into < Option < Parameters > > ) -> Self {
248326 Self :: new ( "adr" , parameters, "text" , address)
249327 }
250328
@@ -276,7 +354,7 @@ impl Property {
276354 /// ```
277355 pub fn new_org (
278356 org : impl Into < PropertyValue > ,
279- parameters : impl Into < Option < HashMap < String , String > > > ,
357+ parameters : impl Into < Option < Parameters > > ,
280358 ) -> Self {
281359 Self :: new ( "org" , parameters, "text" , org)
282360 }
@@ -307,10 +385,10 @@ impl Property {
307385 pub fn new_tel (
308386 phone_type : impl Into < Telephone > ,
309387 number : impl AsRef < str > ,
310- parameters : impl Into < Option < HashMap < String , String > > > ,
388+ parameters : impl Into < Option < Parameters > > ,
311389 ) -> Self {
312390 let mut parameters = parameters. into ( ) . unwrap_or_default ( ) ;
313- parameters. insert ( "type" . into ( ) , phone_type. into ( ) . to_string ( ) ) ;
391+ parameters. insert ( "type" . into ( ) , vec ! [ phone_type. into( ) . to_string( ) ] ) ;
314392
315393 Self :: new ( "tel" , parameters, "uri" , format ! ( "tel:{}" , number. as_ref( ) ) )
316394 }
@@ -338,10 +416,7 @@ impl Property {
338416 /// # Ok(())
339417 /// # }
340418 /// ```
341- pub fn new_email (
342- email : impl ToString ,
343- parameters : impl Into < Option < HashMap < String , String > > > ,
344- ) -> Self {
419+ pub fn new_email ( email : impl ToString , parameters : impl Into < Option < Parameters > > ) -> Self {
345420 Self :: new ( "email" , parameters, "text" , email. to_string ( ) )
346421 }
347422}
0 commit comments