4646//!
4747//! During serialization, the value of [`Vcard::version`] is placed at index 0 in the properties array.
4848use serde:: { Deserialize , Serialize } ;
49+ use serde_with:: { formats:: PreferOne , serde_as, OneOrMany } ;
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,65 @@ 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.
111147///
112- /// ["org", {}, "text",
113- /// ["Organization", "Department", "etc"]
114- /// ]
148+ /// ```rust
149+ /// # use vicardi::*;
150+ /// # use serde_json::json;
151+ /// # fn main() -> anyhow::Result<()> {
152+ /// let json_array = json!(["structured"]);
153+ /// let structured: PropertyValue = serde_json::from_value(json_array)?;
154+ /// assert_eq!(
155+ /// structured,
156+ /// PropertyValue::Structured(vec!["structured".into()])
157+ /// );
158+ ///
159+ /// let json_string = json!("structured");
160+ /// let string = serde_json::to_value(structured)?;
161+ /// assert_eq!(
162+ /// string,
163+ /// json_string
164+ /// );
165+ /// # Ok(())
166+ /// # }
115167/// ```
116- #[ derive( Debug , Clone , PartialEq , Serialize , Deserialize ) ]
168+ #[ serde_as]
169+ #[ derive( Debug , Clone , PartialEq , Deserialize ) ]
117170#[ serde( untagged) ]
118171pub enum PropertyValue {
172+ /// ```json
173+ /// ["fn", {}, "text", "Vicardi"]
174+ /// ```
119175 String ( String ) ,
120- Structured ( Vec < String > ) ,
176+
177+ /// ```json
178+ /// ["x-use-rust", {}, "boolean", true]
179+ /// ```
180+ Bool ( bool ) ,
181+
182+ /// ```json
183+ /// ["x-karma-points", {}, "integer", 42]
184+ /// ```
185+ Integer ( i64 ) ,
186+
187+ /// ```json
188+ /// ["x-grade", {}, "integer", 1.3]
189+ /// ```
190+ Float ( f64 ) ,
191+
192+ /// Example structured string property value:
193+ /// ```json
194+ /// ["org", {}, "text",
195+ /// ["Organization", "Department", "etc"]
196+ /// ]
197+ /// ```
198+ Structured ( Vec < PropertyValue > ) ,
121199}
122200
123201impl Vcard {
@@ -134,7 +212,7 @@ impl Property {
134212 /// Creates a new property, where [`Property::values`] is a `vec![value]`.
135213 pub fn new (
136214 name : impl ToString ,
137- parameters : impl Into < Option < HashMap < String , String > > > ,
215+ parameters : impl Into < Option < Parameters > > ,
138216 value_type : impl ToString ,
139217 value : impl Into < PropertyValue > ,
140218 ) -> Self {
@@ -143,7 +221,7 @@ impl Property {
143221
144222 pub fn new_multivalued (
145223 name : impl ToString ,
146- parameters : impl Into < Option < HashMap < String , String > > > ,
224+ parameters : impl Into < Option < Parameters > > ,
147225 value_type : impl ToString ,
148226 values : Vec < PropertyValue > ,
149227 ) -> Self {
@@ -179,10 +257,7 @@ impl Property {
179257 /// # Ok(())
180258 /// # }
181259 /// ```
182- pub fn new_fn (
183- formatted : impl ToString ,
184- parameters : impl Into < Option < HashMap < String , String > > > ,
185- ) -> Self {
260+ pub fn new_fn ( formatted : impl ToString , parameters : impl Into < Option < Parameters > > ) -> Self {
186261 Self :: new ( "fn" , parameters, "text" , formatted)
187262 }
188263
@@ -204,7 +279,7 @@ impl Property {
204279 ///
205280 /// vcard.push(Property::new_adr(
206281 /// address.clone(),
207- /// Some([( "pref".to_string(), "1".to_string())].into_iter().collect())
282+ /// parameters!{ "pref" => "1"}
208283 /// ));
209284 /// vcard.push(address);
210285 ///
@@ -241,10 +316,7 @@ impl Property {
241316 /// # Ok(())
242317 /// # }
243318 /// ```
244- pub fn new_adr (
245- address : Address ,
246- parameters : impl Into < Option < HashMap < String , String > > > ,
247- ) -> Self {
319+ pub fn new_adr ( address : Address , parameters : impl Into < Option < Parameters > > ) -> Self {
248320 Self :: new ( "adr" , parameters, "text" , address)
249321 }
250322
@@ -276,7 +348,7 @@ impl Property {
276348 /// ```
277349 pub fn new_org (
278350 org : impl Into < PropertyValue > ,
279- parameters : impl Into < Option < HashMap < String , String > > > ,
351+ parameters : impl Into < Option < Parameters > > ,
280352 ) -> Self {
281353 Self :: new ( "org" , parameters, "text" , org)
282354 }
@@ -307,10 +379,10 @@ impl Property {
307379 pub fn new_tel (
308380 phone_type : impl Into < Telephone > ,
309381 number : impl AsRef < str > ,
310- parameters : impl Into < Option < HashMap < String , String > > > ,
382+ parameters : impl Into < Option < Parameters > > ,
311383 ) -> Self {
312384 let mut parameters = parameters. into ( ) . unwrap_or_default ( ) ;
313- parameters. insert ( "type" . into ( ) , phone_type. into ( ) . to_string ( ) ) ;
385+ parameters. insert ( "type" . into ( ) , vec ! [ phone_type. into( ) . to_string( ) ] ) ;
314386
315387 Self :: new ( "tel" , parameters, "uri" , format ! ( "tel:{}" , number. as_ref( ) ) )
316388 }
@@ -338,10 +410,7 @@ impl Property {
338410 /// # Ok(())
339411 /// # }
340412 /// ```
341- pub fn new_email (
342- email : impl ToString ,
343- parameters : impl Into < Option < HashMap < String , String > > > ,
344- ) -> Self {
413+ pub fn new_email ( email : impl ToString , parameters : impl Into < Option < Parameters > > ) -> Self {
345414 Self :: new ( "email" , parameters, "text" , email. to_string ( ) )
346415 }
347416}
0 commit comments