1use super::{attribute_reference::Reference, context::Context, context::Kind};
2use crate::AttributeValue;
3use log::warn;
4
5use std::collections::HashMap;
6
7const DEFAULT_MULTI_BUILDER_CAPACITY: usize = 3; pub struct ContextBuilder {
20 kind: String,
21 name: Option<String>,
22 anonymous: bool,
23 secondary: Option<String>,
24 private_attributes: Vec<Reference>,
25
26 key: String,
27 attributes: HashMap<String, AttributeValue>,
28
29 allow_empty_key: bool,
33}
34
35impl ContextBuilder {
36 pub fn new(key: impl Into<String>) -> Self {
38 Self {
39 kind: "user".to_owned(),
40 name: None,
41 anonymous: false,
42 secondary: None,
43 private_attributes: Vec::new(),
44 key: key.into(),
45 attributes: HashMap::new(),
46 allow_empty_key: false,
47 }
48 }
49
50 pub fn kind(&mut self, kind: impl Into<String>) -> &mut Self {
62 self.kind = kind.into();
63 self
64 }
65
66 pub fn key(&mut self, key: impl Into<String>) -> &mut Self {
71 self.key = key.into();
72 self
73 }
74
75 pub fn name(&mut self, name: impl Into<String>) -> &mut Self {
82 self.name = Some(name.into());
83 self
84 }
85
86 pub fn set_bool(&mut self, attribute_name: &str, value: bool) -> &mut Self {
92 self.set_value(attribute_name, AttributeValue::Bool(value));
93 self
94 }
95
96 pub fn set_float(&mut self, attribute_name: &str, value: f64) -> &mut Self {
105 self.set_value(attribute_name, AttributeValue::Number(value));
106 self
107 }
108
109 pub fn set_string(&mut self, attribute_name: &str, value: impl Into<String>) -> &mut Self {
115 self.set_value(attribute_name, AttributeValue::String(value.into()));
116 self
117 }
118
119 pub fn set_value(&mut self, attribute_name: &str, value: AttributeValue) -> &mut Self {
156 let _ = self.try_set_value(attribute_name, value);
157 self
158 }
159
160 pub fn try_set_value(&mut self, attribute_name: &str, value: AttributeValue) -> bool {
166 match (attribute_name, value.clone()) {
167 ("", _) => {
168 warn!("Provided attribute name is empty. Ignoring.");
169 false
170 }
171 ("kind", AttributeValue::String(s)) => {
172 self.kind(s);
173 true
174 }
175 ("kind", _) => false,
176 ("key", AttributeValue::String(s)) => {
177 self.key(s);
178 true
179 }
180 ("key", _) => false,
181 ("name", AttributeValue::String(s)) => {
182 self.name(s);
183 true
184 }
185 ("name", AttributeValue::Null) => {
186 self.name = None;
187 true
188 }
189 ("name", _) => false,
190 ("anonymous", AttributeValue::Bool(b)) => {
191 self.anonymous(b);
192 true
193 }
194 ("anonymous", _) => false,
195 ("_meta", _) => false,
196 (_, AttributeValue::Null) => {
197 self.attributes.remove(attribute_name);
198 true
199 }
200 (_, _) => {
201 self.attributes.insert(attribute_name.to_string(), value);
202 true
203 }
204 }
205 }
206
207 pub(in crate::contexts) fn secondary(&mut self, value: impl Into<String>) -> &mut Self {
217 self.secondary = Some(value.into());
218 self
219 }
220
221 pub fn add_private_attribute<R: Into<Reference>>(&mut self, reference: R) -> &mut Self {
232 self.private_attributes.push(reference.into());
233 self
234 }
235
236 pub fn remove_private_attribute<R: Into<Reference>>(&mut self, reference: R) -> &mut Self {
239 let reference = reference.into();
240 self.private_attributes
241 .retain(|private| *private != reference);
242 self
243 }
244
245 pub fn anonymous(&mut self, value: bool) -> &mut Self {
258 self.anonymous = value;
259 self
260 }
261
262 pub(super) fn allow_empty_key(&mut self) -> &mut Self {
265 self.allow_empty_key = true;
266 self
267 }
268
269 pub fn build(&self) -> Result<Context, String> {
277 let kind = Kind::try_from(self.kind.clone())?;
278
279 if kind.is_multi() {
280 return Err(String::from(
281 "context of kind \"multi\" must be built with MultiContextBuilder",
282 ));
283 }
284
285 if !self.allow_empty_key && self.key.is_empty() {
286 return Err(String::from("key cannot be empty"));
287 }
288
289 let canonical_key = canonical_key_for_kind(&kind, &self.key, true);
290
291 Ok(Context {
292 kind,
293 contexts: None,
294 name: self.name.clone(),
295 anonymous: self.anonymous,
296 secondary: self.secondary.clone(),
297 private_attributes: if self.private_attributes.is_empty() {
298 None
299 } else {
300 Some(self.private_attributes.clone())
301 },
302 key: self.key.clone(),
303 canonical_key,
304 attributes: self.attributes.clone(),
305 })
306 }
307}
308
309fn canonical_key_for_kind(kind: &Kind, key: &str, omit_user_kind: bool) -> String {
310 if omit_user_kind && kind.is_user() {
311 return key.to_owned();
312 }
313 format!("{}:{}", kind, key.replace('%', "%25").replace(':', "%3A"))
314}
315
316pub struct MultiContextBuilder {
326 contexts: Vec<Context>,
327}
328
329impl MultiContextBuilder {
330 pub fn new() -> Self {
335 Self {
336 contexts: Vec::with_capacity(DEFAULT_MULTI_BUILDER_CAPACITY),
337 }
338 }
339
340 pub fn of(contexts: Vec<Context>) -> Self {
342 let mut this = MultiContextBuilder::new();
343 for c in contexts {
344 this.add_context(c);
345 }
346 this
347 }
348
349 pub fn add_context(&mut self, context: Context) -> &mut Self {
357 let mut contexts = match context.contexts {
358 Some(multi) => multi,
359 None => vec![context],
360 };
361 self.contexts.append(&mut contexts);
362 self
363 }
364
365 pub fn build(&self) -> Result<Context, String> {
375 if self.contexts.is_empty() {
376 return Err("multi-kind context must contain at least one nested context".into());
377 }
378
379 if self.contexts.len() == 1 {
380 return Ok(self.contexts[0].clone());
381 }
382
383 let mut contexts = self.contexts.clone();
384 contexts.sort_by(|a, b| a.kind.cmp(&b.kind));
385 for (index, context) in contexts.iter().enumerate() {
386 if index > 0 && contexts[index - 1].kind == context.kind {
387 return Err("multi-kind context cannot have same kind more than once".into());
388 }
389 }
390
391 let canonicalized_key = contexts
392 .iter()
393 .map(|context| canonical_key_for_kind(context.kind(), context.key(), false))
394 .collect::<Vec<_>>()
395 .join(":");
396
397 Ok(Context {
398 kind: Kind::multi(),
399 contexts: Some(contexts),
400 name: None,
401 anonymous: false,
402 secondary: None,
403 private_attributes: None,
404 key: "".to_owned(),
405 canonical_key: canonicalized_key,
406 attributes: HashMap::new(),
407 })
408 }
409}
410
411impl Default for MultiContextBuilder {
412 fn default() -> Self {
415 Self::new()
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use super::{ContextBuilder, MultiContextBuilder};
422 use crate::{AttributeValue, Reference};
423
424 use crate::contexts::context::Kind;
425 use test_case::test_case;
426
427 #[test]
428 fn builder_can_create_correct_context() {
429 let mut builder = ContextBuilder::new("key");
430 builder
431 .kind(Kind::user())
432 .name("Name")
433 .secondary("Secondary")
434 .anonymous(true);
435
436 let context = builder.build().expect("Failed to build context");
437
438 assert!(!context.is_multi());
439 assert!(context.kind.is_user());
440 assert_eq!(Some("Name".to_string()), context.name);
441 assert_eq!(Some("Secondary".to_string()), context.secondary);
442 assert!(context.anonymous);
443 }
444
445 #[test_case("key", Kind::user(), "key")]
446 #[test_case("key", Kind::from("org"), "org:key")]
447 #[test_case("hi:there", Kind::user(), "hi:there")]
448 #[test_case("hi:there", Kind::from("org"), "org:hi%3Athere")]
449 fn builder_sets_canonical_key_correctly_for_single_context(
450 key: &str,
451 kind: Kind,
452 expected_key: &str,
453 ) {
454 let context = ContextBuilder::new(key)
455 .kind(kind)
456 .build()
457 .expect("Failed to create context");
458
459 assert_eq!(expected_key, context.canonical_key());
460 }
461
462 #[test_case(vec![("key", Kind::user())], "key")]
465 #[test_case(vec![("userKey", Kind::user()), ("orgKey", Kind::from("org"))], "org:orgKey:user:userKey")]
466 #[test_case(vec![("some user", Kind::user()), ("org:key", Kind::from("org"))], "org:org%3Akey:user:some user")]
467 #[test_case(vec![("my:key%x/y", Kind::from("org"))], "org:my%3Akey%25x/y")]
468 fn builder_sets_canonical_key_correctly_for_multiple_contexts(
469 tuples: Vec<(&str, Kind)>,
470 expected_key: &str,
471 ) {
472 let mut multi_builder = MultiContextBuilder::new();
473
474 for (key, kind) in tuples {
475 multi_builder.add_context(
476 ContextBuilder::new(key)
477 .kind(kind)
478 .build()
479 .expect("Failed to create context"),
480 );
481 }
482
483 let multi_context = multi_builder.build().expect("Failed to create context");
484 assert_eq!(expected_key, multi_context.canonical_key());
485 }
486
487 #[test_case("multi"; "Cannot set kind as multi")]
488 #[test_case("kind"; "Cannot set kind as kind")]
489 #[test_case("🦀"; "Cannot set kind as invalid character")]
490 #[test_case(" "; "Cannot set kind as only whitespace")]
491 fn build_fails_on_invalid_kinds(kind: &str) {
492 let mut builder = ContextBuilder::new("key");
493 builder.kind(kind);
494
495 let result = builder.build();
496 assert!(result.is_err());
497 }
498
499 #[test]
500 fn builder_can_set_custom_properties_by_type() {
501 let mut builder = ContextBuilder::new("key");
502 builder
503 .kind(Kind::user())
504 .set_bool("loves-rust", true)
505 .set_float("pi", 3.1459)
506 .set_string("company", "LaunchDarkly");
507
508 let context = builder.build().expect("Failed to build context");
509
510 assert_eq!(
511 &AttributeValue::Bool(true),
512 context.attributes.get("loves-rust").unwrap()
513 );
514 assert_eq!(
515 &AttributeValue::Number(3.1459),
516 context.attributes.get("pi").unwrap()
517 );
518 assert_eq!(
519 &AttributeValue::String("LaunchDarkly".to_string()),
520 context.attributes.get("company").unwrap()
521 );
522 }
523
524 #[test_case("", AttributeValue::Bool(true), false)]
525 #[test_case("kind", AttributeValue::Bool(true), false)]
526 #[test_case("kind", AttributeValue::String("user".to_string()), true)]
527 #[test_case("key", AttributeValue::Bool(true), false)]
528 #[test_case("key", AttributeValue::String("key".to_string()), true)]
529 #[test_case("name", AttributeValue::Bool(true), false)]
530 #[test_case("name", AttributeValue::String("name".to_string()), true)]
531 #[test_case("anonymous", AttributeValue::String("anonymous".to_string()), false)]
532 #[test_case("anonymous", AttributeValue::Bool(true), true)]
533 #[test_case("secondary", AttributeValue::String("secondary".to_string()), true)]
534 #[test_case("secondary", AttributeValue::Bool(true), true)]
535 #[test_case("my-custom-attribute", AttributeValue::Bool(true), true)]
536 #[test_case("my-custom-attribute", AttributeValue::String("string name".to_string()), true)]
537 fn builder_try_set_value_handles_invalid_values_correctly(
538 attribute_name: &str,
539 value: AttributeValue,
540 expected: bool,
541 ) {
542 let mut builder = ContextBuilder::new("key");
543 assert_eq!(builder.try_set_value(attribute_name, value), expected);
544 }
545
546 #[test_case("secondary", AttributeValue::String("value".to_string()))]
547 #[test_case("privateAttributes", AttributeValue::Array(vec![AttributeValue::String("value".to_string())]))]
548 fn builder_set_value_cannot_set_meta_properties(attribute_name: &str, value: AttributeValue) {
549 let builder = ContextBuilder::new("key")
550 .set_value(attribute_name, value.clone())
551 .build()
552 .unwrap();
553
554 assert_eq!(&value, builder.attributes.get(attribute_name).unwrap());
555 assert!(builder.secondary.is_none());
556 }
557
558 #[test]
559 fn builder_try_set_value_cannot_set_meta() {
560 let mut builder = ContextBuilder::new("key");
561
562 assert!(!builder.try_set_value("_meta", AttributeValue::String("value".to_string())));
563 assert_eq!(builder.build().unwrap().attributes.len(), 0);
564 }
565
566 #[test]
567 fn builder_deals_with_missing_kind_correctly() {
568 let mut builder = ContextBuilder::new("key");
569 assert!(builder.build().unwrap().kind.is_user());
570
571 builder.kind("");
572 assert!(builder.build().is_err());
573 }
574
575 #[test]
576 fn builder_deals_with_empty_key_correctly() {
577 assert!(ContextBuilder::new("").build().is_err());
578 }
579
580 #[test]
581 fn builder_handles_private_attributes() {
582 let mut builder = ContextBuilder::new("key");
583 let context = builder.build().expect("Failed to build context");
584
585 assert!(context.private_attributes.is_none());
586
587 builder.add_private_attribute("name");
588 let context = builder.build().expect("Failed to build context");
589
590 let private = context
591 .private_attributes
592 .expect("Private attributes should be set");
593
594 assert_eq!(1, private.len());
595 }
596
597 #[test]
598 fn builder_handles_removing_private_attributes() {
599 let mut builder = ContextBuilder::new("key");
600 builder
601 .add_private_attribute("name")
602 .add_private_attribute("name")
603 .add_private_attribute("/user/email");
604
605 assert_eq!(
606 3,
607 builder.build().unwrap().private_attributes.unwrap().len()
608 );
609
610 builder.remove_private_attribute("name");
612 assert_eq!(
613 1,
614 builder.build().unwrap().private_attributes.unwrap().len()
615 );
616
617 builder.remove_private_attribute("/user/email");
618 assert!(builder.build().unwrap().private_attributes.is_none());
619 }
620
621 #[test]
622 fn build_can_add_and_remove_with_different_formats() {
623 let mut builder = ContextBuilder::new("key");
624 builder
625 .add_private_attribute("name")
626 .add_private_attribute(Reference::new("name"));
627
628 assert_eq!(
629 2,
630 builder.build().unwrap().private_attributes.unwrap().len()
631 );
632
633 builder.remove_private_attribute("name");
635 assert!(builder.build().unwrap().private_attributes.is_none());
636
637 builder
639 .add_private_attribute("name")
640 .add_private_attribute(Reference::new("name"));
641 builder.remove_private_attribute(Reference::new("name"));
642 assert!(builder.build().unwrap().private_attributes.is_none());
643 }
644
645 #[test]
646 fn multi_builder_can_build_multi_context() {
647 let mut single_builder = ContextBuilder::new("key");
648 single_builder.kind(Kind::user());
649 let mut multi_builder = MultiContextBuilder::new();
650
651 multi_builder.add_context(single_builder.build().expect("Failed to build context"));
652
653 single_builder.key("second-key").kind("org".to_string());
654 multi_builder.add_context(single_builder.build().expect("Failed to build context"));
655
656 let multi_context = multi_builder
657 .build()
658 .expect("Failed to create multi context");
659
660 assert!(multi_context.is_multi());
661 assert_eq!(2, multi_context.contexts.unwrap().len());
662 }
663
664 #[test]
665 fn multi_builder_cannot_handle_more_than_one_of_same_kind() {
666 let mut single_builder = ContextBuilder::new("key");
667 single_builder.kind(Kind::user());
668 let mut multi_builder = MultiContextBuilder::new();
669
670 multi_builder.add_context(single_builder.build().expect("Failed to build context"));
671
672 single_builder.key("second-key");
673 multi_builder.add_context(single_builder.build().expect("Failed to build context"));
674
675 let result = multi_builder.build();
676 assert!(result.is_err());
677 }
678
679 #[test]
680 fn multi_builder_must_contain_another_context() {
681 assert!(MultiContextBuilder::new().build().is_err());
682 }
683
684 #[test]
685 fn multi_builder_should_flatten_multi_contexts() {
686 let cat = ContextBuilder::new("c")
687 .kind("cat")
688 .build()
689 .expect("should build cat");
690
691 let dog = ContextBuilder::new("d")
692 .kind("dog")
693 .build()
694 .expect("should build dog");
695
696 let rabbit = ContextBuilder::new("r")
697 .kind("rabbit")
698 .build()
699 .expect("should build rabbit");
700
701 let ferret = ContextBuilder::new("f")
702 .kind("ferret")
703 .build()
704 .expect("should build ferret");
705
706 let catdog = MultiContextBuilder::of(vec![cat, dog])
707 .build()
708 .expect("should build cat/dog multi-context");
709
710 let rabbitferret = MultiContextBuilder::of(vec![rabbit, ferret])
711 .build()
712 .expect("should build rabbit/ferret multi-context");
713
714 let chimera = MultiContextBuilder::of(vec![catdog, rabbitferret])
715 .build()
716 .expect("should build cat/dog/rabbit/ferret multi-context");
717
718 assert_eq!(chimera.kinds().len(), 4);
719 for k in &["cat", "dog", "rabbit", "ferret"] {
720 assert!(chimera.as_kind(&Kind::from(k)).is_some());
721 }
722 assert_eq!(chimera.canonical_key(), "cat:c:dog:d:ferret:f:rabbit:r");
723 }
724}