1use crate::contexts::attribute_reference::AttributeName;
2use crate::contexts::context::Kind;
3use crate::contexts::context_serde_helpers::*;
4use crate::{AttributeValue, MultiContextBuilder};
5use crate::{Context, ContextBuilder};
6use serde::de;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use serde_with::skip_serializing_none;
9use std::collections::HashMap;
10use std::convert::TryFrom;
11
12pub(super) enum ContextVariant {
17 Multi(MultiKindContext),
18 Single(SingleKindStandaloneContext),
19 Implicit(UserFormat),
20}
21
22#[derive(Serialize, Deserialize)]
28pub(super) struct MultiKindContext {
29 kind: String,
30 #[serde(flatten)]
31 contexts: HashMap<Kind, SingleKindContext>,
32}
33
34#[derive(Serialize, Deserialize)]
47#[serde(rename_all = "camelCase")]
48pub(super) struct SingleKindStandaloneContext {
49 kind: Kind,
50 #[serde(flatten)]
51 context: SingleKindContext,
52}
53
54#[skip_serializing_none]
60#[derive(Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub(super) struct SingleKindContext {
63 key: String,
64 name: Option<String>,
65 #[serde(skip_serializing_if = "is_false_bool_option")]
66 anonymous: Option<bool>,
67 #[serde(flatten)]
68 attributes: HashMap<String, AttributeValue>,
69 #[serde(rename = "_meta", skip_serializing_if = "is_none_meta_option")]
70 meta: Option<Meta>,
71}
72
73#[skip_serializing_none]
79#[derive(Deserialize)]
80#[serde(rename_all = "camelCase")]
81pub(super) struct UserFormat {
82 key: String,
83 name: Option<String>,
84 secondary: Option<String>,
85 anonymous: Option<bool>,
86 custom: Option<HashMap<String, AttributeValue>>,
87 private_attribute_names: Option<Vec<AttributeName>>,
88 first_name: Option<String>,
89 last_name: Option<String>,
90 avatar: Option<String>,
91 email: Option<String>,
92 country: Option<String>,
93 ip: Option<String>,
94}
95
96#[skip_serializing_none]
97#[derive(Serialize, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub(super) struct Meta {
100 pub secondary: Option<String>,
101 #[serde(skip_serializing_if = "is_empty_vec_option")]
102 pub private_attributes: Option<Vec<String>>,
103}
104
105impl<'de> Deserialize<'de> for ContextVariant {
113 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
114 where
115 D: Deserializer<'de>,
116 {
117 #[derive(Deserialize)]
121 #[serde(field_identifier, rename_all = "camelCase")]
122 enum Tag {
123 Multi,
124 Custom(String),
125 }
126
127 let v = serde_json::Value::deserialize(deserializer)?;
128 match Option::deserialize(&v["kind"]).map_err(de::Error::custom)? {
129 None if v.get("kind").is_some() => {
130 Err(de::Error::custom("context kind cannot be null"))
136 }
137 None => {
138 let user = UserFormat::deserialize(v).map_err(de::Error::custom)?;
139 Ok(ContextVariant::Implicit(user))
140 }
141 Some(Tag::Multi) => {
142 let multi = MultiKindContext::deserialize(v).map_err(de::Error::custom)?;
143 Ok(ContextVariant::Multi(multi))
144 }
145 Some(Tag::Custom(kind)) if kind.is_empty() => {
146 Err(de::Error::custom("context kind cannot be empty string"))
147 }
148 Some(Tag::Custom(_)) => {
149 let single =
150 SingleKindStandaloneContext::deserialize(v).map_err(de::Error::custom)?;
151 Ok(ContextVariant::Single(single))
152 }
153 }
154 }
155}
156
157impl Serialize for ContextVariant {
158 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
159 where
160 S: Serializer,
161 {
162 match self {
163 ContextVariant::Multi(multi) => multi.serialize(serializer),
164 ContextVariant::Single(single) => single.serialize(serializer),
165 ContextVariant::Implicit(_) => {
166 unimplemented!("cannot serialize implicit user contexts")
167 }
168 }
169 }
170}
171
172impl From<Context> for ContextVariant {
173 fn from(c: Context) -> Self {
174 match c.kind {
175 kind if kind.is_multi() => ContextVariant::Multi(MultiKindContext {
176 kind: "multi".to_owned(),
177 contexts: c
178 .contexts
179 .expect("multi-kind context must contain at least one nested context")
180 .into_iter()
181 .map(single_kind_context_from)
182 .collect(),
183 }),
184 _ => {
185 let (kind, nested) = single_kind_context_from(c);
186 ContextVariant::Single(SingleKindStandaloneContext {
187 kind,
188 context: nested,
189 })
190 }
191 }
192 }
193}
194
195fn single_kind_context_from(c: Context) -> (Kind, SingleKindContext) {
196 (
197 c.kind,
198 SingleKindContext {
199 key: c.key,
200 name: c.name,
201 anonymous: Some(c.anonymous),
202 attributes: c.attributes,
203 meta: Some(Meta {
204 secondary: c.secondary,
205 private_attributes: c
206 .private_attributes
207 .map(|attrs| attrs.into_iter().map(String::from).collect()),
208 }),
209 },
210 )
211}
212
213impl TryFrom<ContextVariant> for Context {
214 type Error = String;
215
216 fn try_from(variant: ContextVariant) -> Result<Self, Self::Error> {
217 match variant {
218 ContextVariant::Multi(m) => {
219 let mut multi_builder = MultiContextBuilder::new();
220 for (kind, context) in m.contexts {
221 let mut builder = ContextBuilder::new(context.key.clone());
222 let context = build_context(&mut builder, context).kind(kind).build()?;
223 multi_builder.add_context(context);
224 }
225 multi_builder.build()
226 }
227 ContextVariant::Single(context) => {
228 let mut builder = ContextBuilder::new(context.context.key.clone());
229 build_context(&mut builder, context.context)
230 .kind(context.kind)
231 .build()
232 }
233 ContextVariant::Implicit(user) => {
234 let mut builder = ContextBuilder::new(user.key.clone());
235 builder.allow_empty_key();
236 build_context_from_implicit_user(&mut builder, user).build()
237 }
238 }
239 }
240}
241
242fn build_context(b: &mut ContextBuilder, context: SingleKindContext) -> &mut ContextBuilder {
243 for (key, attr) in context.attributes {
244 b.set_value(key.as_str(), attr);
245 }
246 if let Some(anonymous) = context.anonymous {
247 b.anonymous(anonymous);
248 }
249 if let Some(name) = context.name {
250 b.name(name);
251 }
252 if let Some(meta) = context.meta {
253 if let Some(secondary) = meta.secondary {
254 b.secondary(secondary);
255 }
256 if let Some(private_attributes) = meta.private_attributes {
257 for attribute in private_attributes {
258 b.add_private_attribute(attribute);
259 }
260 }
261 }
262 b
263}
264
265fn should_skip_custom_attribute(attr_name: &str) -> bool {
270 matches!(attr_name, "kind" | "key" | "name" | "anonymous" | "_meta")
271}
272
273fn build_context_from_implicit_user(
274 b: &mut ContextBuilder,
275 user: UserFormat,
276) -> &mut ContextBuilder {
277 if let Some(anonymous) = user.anonymous {
278 b.anonymous(anonymous);
279 }
280 if let Some(secondary) = user.secondary {
281 b.secondary(secondary);
282 }
283 if let Some(name) = user.name {
284 b.name(name);
285 }
286 if let Some(x) = user.avatar {
287 b.set_string("avatar", x);
288 }
289 if let Some(x) = user.first_name {
290 b.set_string("firstName", x);
291 }
292 if let Some(x) = user.last_name {
293 b.set_string("lastName", x);
294 }
295 if let Some(x) = user.country {
296 b.set_string("country", x);
297 }
298 if let Some(x) = user.email {
299 b.set_string("email", x);
300 }
301 if let Some(x) = user.ip {
302 b.set_string("ip", x);
303 }
304 if let Some(custom) = user.custom {
305 for (key, attr) in custom {
306 if !should_skip_custom_attribute(&key) {
307 b.set_value(key.as_str(), attr);
308 }
309 }
310 }
311 if let Some(attributes) = user.private_attribute_names {
312 for attribute in attributes {
313 b.add_private_attribute(attribute);
314 }
315 }
316 b
317}
318
319#[cfg(test)]
320mod tests {
321 use crate::contexts::context_serde::{ContextVariant, UserFormat};
322 use crate::{AttributeValue, Context, ContextBuilder, MultiContextBuilder};
323 use assert_json_diff::assert_json_eq;
324 use maplit::hashmap;
325 use serde_json::json;
326 use std::convert::TryFrom;
327 use test_case::test_case;
328
329 #[test_case(json!({"key" : "foo"}),
330 json!({"kind" : "user", "key" : "foo"}))]
331 #[test_case(json!({"key" : "foo", "name" : "bar"}),
332 json!({"kind" : "user", "key" : "foo", "name" : "bar"}))]
333 #[test_case(json!({"key" : "foo", "custom" : {"a" : "b"}}),
334 json!({"kind" : "user", "key" : "foo", "a" : "b"}))]
335 #[test_case(json!({"key" : "foo", "anonymous" : true}),
336 json!({"kind" : "user", "key" : "foo", "anonymous" : true}))]
337 #[test_case(json!({"key" : "foo", "secondary" : "bar"}),
338 json!({"kind" : "user", "key" : "foo", "_meta" : {"secondary" : "bar"}}))]
339 #[test_case(json!({"key" : "foo", "ip" : "1", "privateAttributeNames" : ["ip"]}),
340 json!({"kind" : "user", "key" : "foo", "ip" : "1", "_meta" : { "privateAttributes" : ["ip"]} }))]
341 #[test_case(
343 json!({
344 "key" : "foo",
345 "name" : "bar",
346 "anonymous" : true,
347 "custom" : {
348 "kind": ".",
349 "key": ".",
350 "name": ".",
351 "anonymous": true,
352 "_meta": true,
353 "a": 1.0
354 }
355 }),
356 json!({
357 "kind": "user",
358 "key": "foo",
359 "name": "bar",
360 "anonymous": true,
361 "a": 1.0
362 })
363 )]
364 fn implicit_context_conversion(from: serde_json::Value, to: serde_json::Value) {
367 let context: Result<ContextVariant, _> = serde_json::from_value(from);
368 match context {
369 Ok(variant) => {
370 assert!(matches!(variant, ContextVariant::Implicit(_)));
371 match Context::try_from(variant) {
372 Ok(context) => {
373 assert_json_eq!(to, context);
374 }
375 Err(e) => panic!("variant should convert to context without error: {:?}", e),
376 }
377 }
378 Err(e) => panic!("test JSON should parse without error: {:?}", e),
379 }
380 }
381
382 #[test_case(json!({"kind" : "org", "key" : "foo"}))]
383 #[test_case(json!({"kind" : "user", "key" : "foo"}))]
384 #[test_case(json!({"kind" : "foo", "key" : "bar", "anonymous" : true}))]
385 #[test_case(json!({"kind" : "foo", "name" : "Foo", "key" : "bar", "a" : "b", "_meta" : {"secondary" : "baz", "privateAttributes" : ["a"]}}))]
386 #[test_case(json!({"kind" : "foo", "key" : "bar", "object" : {"a" : "b"}}))]
387 fn single_kind_context_roundtrip_identical(from: serde_json::Value) {
390 match serde_json::from_value::<Context>(from.clone()) {
391 Ok(context) => {
392 assert_json_eq!(from, context);
393 }
394 Err(e) => panic!("test JSON should convert to context without error: {:?}", e),
395 }
396 }
397
398 #[test_case(json!({"kind" : true, "key" : "a"}))]
399 #[test_case(json!({"kind" : null, "key" : "b"}))]
400 #[test_case(json!({"kind" : {}, "key" : "c"}))]
401 #[test_case(json!({"kind" : 1, "key" : "d"}))]
402 #[test_case(json!({"kind" : [], "key" : "e"}))]
403 fn reject_null_or_non_string_kind(from: serde_json::Value) {
404 match serde_json::from_value::<ContextVariant>(from) {
405 Err(e) => println!("{:?}", e),
406 Ok(c) => panic!(
407 "expected conversion to fail, but got: {:?}",
408 serde_json::to_string(&c)
409 ),
410 }
411 }
412
413 #[test_case(json!({"kind" : "kind", "key" : "a"}))]
415 #[test_case(json!({"kind" : "", "key" : "a"}))]
416 #[test_case(json!({"kind" : "multi"}))]
418 #[test_case(json!({"kind" : "multi", "key" : "a"}))]
419 #[test_case(json!({"kind" : "user", "key" : "a", "_meta" : "string"}))]
421 #[test_case(json!({"a" : "b"}))]
423 #[test_case(json!({"kind" : "user"}))]
425 #[test_case(json!({"kind" : "user", "key" : ""}))]
426 fn reject_invalid_contexts(from: serde_json::Value) {
427 match serde_json::from_value::<Context>(from) {
428 Err(e) => println!("got expected error: {:?}", e),
429 Ok(c) => panic!(
430 "expected conversion to fail, but got: {:?}",
431 serde_json::to_string(&c)
432 ),
433 }
434 }
435
436 #[test]
437 fn empty_key_allowed_for_implicit_user() {
439 let json_in = json!({
440 "key" : "",
441 });
442
443 let json_out = json!({
444 "kind" : "user",
445 "key" : ""
446 });
447
448 let context: Context = serde_json::from_value(json_in).unwrap();
449
450 assert_json_eq!(json_out, json!(context));
451 }
452
453 #[test]
454 fn unrecognized_implicit_user_props_are_ignored_without_error() {
457 let json = json!({
458 "key" : "foo",
459 "ip": "b",
460 "unknown-1" : "ignored",
461 "unknown-2" : "ignored",
462 "unknown-3" : "ignored",
463 });
464
465 match serde_json::from_value::<ContextVariant>(json) {
466 Err(e) => panic!("expected user to deserialize without error: {:?}", e),
467 Ok(c) => match c {
468 ContextVariant::Implicit(user) => {
469 assert_eq!(user.ip.unwrap_or_default(), "b");
470 }
471 _ => panic!("expected user format"),
472 },
473 }
474 }
475
476 #[test]
477 fn multi_kind_context_roundtrip() {
478 let json = json!({
479 "kind" : "multi",
480 "foo" : {
481 "key" : "foo_key",
482 "name" : "foo_name",
483 "anonymous" : true
484 },
485 "bar" : {
486 "key" : "bar_key",
487 "some" : "attribute",
488 "_meta" : {
489 "secondary" : "bar_two",
490 "privateAttributes" : [
491 "some"
492 ]
493 }
494 },
495 "baz" : {
496 "key" : "baz_key",
497 }
498 });
499
500 let multi: Context = serde_json::from_value(json.clone()).unwrap();
501 assert_json_eq!(multi, json);
502 }
503
504 #[test]
505 fn builder_generates_correct_single_kind_context() {
506 let json = json!({
507 "kind" : "org",
508 "key" : "foo",
509 "anonymous" : true,
510 "_meta" : {
511 "privateAttributes" : ["a", "b", "/c/d"],
512 "secondary" : "bar"
513 },
514 "a" : true,
515 "b" : true,
516 "c" : {
517 "d" : "e"
518 }
519 });
520
521 let mut builder = ContextBuilder::new("foo");
522 let result = builder
523 .anonymous(true)
524 .secondary("bar")
525 .kind("org")
526 .set_bool("a", true)
527 .add_private_attribute("a")
528 .set_bool("b", true)
529 .add_private_attribute("b")
530 .set_value(
531 "c",
532 AttributeValue::Object(hashmap! {
533 "d".into() => "e".into()
534 }),
535 )
536 .add_private_attribute("/c/d")
537 .build()
538 .unwrap();
539
540 assert_json_eq!(json, result);
541 }
542
543 #[test]
544 fn build_generates_correct_multi_kind_context() {
545 let json = json!({
546 "kind" : "multi",
547 "user" : {
548 "key" : "foo-key",
549 },
550 "bar" : {
551 "key" : "bar-key",
552 },
553 "baz" : {
554 "key" : "baz-key",
555 "anonymous" : true
556 }
557 });
558
559 let user = ContextBuilder::new("foo-key");
560 let mut bar = ContextBuilder::new("bar-key");
561 bar.kind("bar");
562
563 let mut baz = ContextBuilder::new("baz-key");
564 baz.kind("baz");
565 baz.anonymous(true);
566
567 let multi = MultiContextBuilder::new()
568 .add_context(user.build().expect("failed to build context"))
569 .add_context(bar.build().expect("failed to build context"))
570 .add_context(baz.build().expect("failed to build context"))
571 .build()
572 .unwrap();
573
574 assert_json_eq!(multi, json);
575 }
576
577 #[test]
578 #[should_panic]
579 fn cannot_serialize_implicit_user_context() {
582 let x = ContextVariant::Implicit(UserFormat {
583 key: "foo".to_string(),
584 name: None,
585 secondary: None,
586 anonymous: None,
587 custom: None,
588 private_attribute_names: None,
589 first_name: None,
590 last_name: None,
591 avatar: None,
592 email: None,
593 country: None,
594 ip: None,
595 });
596
597 let _ = serde_json::to_string(&x);
598 }
599}