launchdarkly_server_sdk_evaluation/contexts/
context_serde.rs

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
12// Represents the three possible context formats recognized by this SDK.
13// Multi-kind contexts hold one or more nested contexts.
14// Single-kind contexts hold a single context.
15// Implicit-kind contexts represent the pre-context data format.
16pub(super) enum ContextVariant {
17    Multi(MultiKindContext),
18    Single(SingleKindStandaloneContext),
19    Implicit(UserFormat),
20}
21
22// Represents the serialization/deserialization format of a multi-kind context, which serves as a container
23// for 1 or more single-kind contexts.
24//
25// MultiKindContext is not used directly; it is an intermediate format between JSON and
26// the user-facing Context type.
27#[derive(Serialize, Deserialize)]
28pub(super) struct MultiKindContext {
29    kind: String,
30    #[serde(flatten)]
31    contexts: HashMap<Kind, SingleKindContext>,
32}
33
34// Represents the serialization/deserialization format of a single-kind context that is not
35// nested within a multi-kind context.
36//
37// SingleKindStandaloneContext is not used directly; it is an intermediate format between JSON and
38// the user-facing Context type.
39//
40// The only difference between this representation and [SingleKindContext] is the
41// requirement of a top-level 'kind' string.
42//
43// Single-kind contexts nested in a multi-kind context do not have these because the kind
44// is a key in the multi-kind object, whereas it needs to be specified explicitly in a standalone
45// single-kind context.
46#[derive(Serialize, Deserialize)]
47#[serde(rename_all = "camelCase")]
48pub(super) struct SingleKindStandaloneContext {
49    kind: Kind,
50    #[serde(flatten)]
51    context: SingleKindContext,
52}
53
54// Represents the serialization/deserialization format of a single-kind context nested
55// within a multi-context.
56//
57// SingleKindContext is not used directly; it is an intermediate format between JSON
58// and the user-facing Context type.
59#[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// UserFormat represents the serialization/deserialization data format
74// used by LaunchDarkly SDKs that do not support contexts.
75//
76// Any context that matches this format may be deserialized, but serialization will result
77// in conversion to the single-kind context format.
78#[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
105// This deserialize method is needed to discriminate between the three possible context formats supported
106// by LaunchDarkly.
107//
108// The code generated by serde's untagged enum feature is not sufficient. This is because there is overlap
109// between the fields supported by all three formats. For example, a context with 'kind' = false
110// should fail to parse. But instead, this would be deserialized as a [ContextVariant::Implicit], since it doesn't match
111// [ContextVariant::Single] because kind is specified to be a string.
112impl<'de> Deserialize<'de> for ContextVariant {
113    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
114    where
115        D: Deserializer<'de>,
116    {
117        // The 'field_identifier' attribute is currently undocumented. See:
118        // https://github.com/serde-rs/serde/issues/1221#issuecomment-382801024
119
120        #[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                // Interpret this condition (None && v.get("kind").is_some()) as meaning
131                // "could not deserialize 'kind' into a Tag (string), but 'kind' was still present
132                // in the data." serde_json::Value::Null (assumption) is the only value that fits that
133                // condition. If it were another type, like boolean, it wouldn't make it into this
134                // match expression at all.
135                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
265// This is used when unmarshalling an old-style UserFormat into a Context.
266// If we see any of these names within the "custom": {} object, logically
267// we shouldn't use ContextBuilder::set_value to set it because that could overwrite
268// any top-level attributes of the same name.
269fn 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    // Don't let custom attributes overwrite top-level properties with the same reserved names
342    #[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    // This test ensures that contexts in the implicit user format are converted into single-kind format.
365    // This involves various transformations, such as converting privateAttributeNames into a _meta key privateAttributes.
366    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    // This test ensures that single-kinded contexts are deserialized and then reserialized without any
388    // changes.
389    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    // Kind cannot be 'kind'
414    #[test_case(json!({"kind" : "kind", "key" : "a"}))]
415    #[test_case(json!({"kind" : "", "key" : "a"}))]
416    // Multi-kind must have at least one nested kind
417    #[test_case(json!({"kind" : "multi"}))]
418    #[test_case(json!({"kind" : "multi", "key" : "a"}))]
419    // Single-kind, if contains _meta key, must be an object
420    #[test_case(json!({"kind" : "user", "key" : "a", "_meta" : "string"}))]
421    // Context must either be implicit user, single, or multi.
422    #[test_case(json!({"a" : "b"}))]
423    // Single kind must contain key.
424    #[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    // An empty key is only allowed for implicit user format for backwards compatability reasons.
438    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    // The deserialization algorithm should be able to ignore unrecognized top-level
455    // property names without generating an error.
456    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    // Implicit user contexts should never be serialized. All deserialized implicit
580    // user contexts should be re-serialized as single-kind contexts with kind=user.
581    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}