launchdarkly_server_sdk/fdv2/
model.rs1use launchdarkly_server_sdk_evaluation::{Flag, Segment};
2
3use crate::stores::change_set::{self, ItemChange};
4use crate::stores::store_types::StorageItem;
5
6use super::wire::{DeleteObject, PutObject};
7
8pub type Selector = Option<String>;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ChangeSetKind {
14 None,
16 Full,
18 Partial,
20}
21
22#[derive(Debug)]
23pub(super) enum FDv2Change {
24 Put(PutObject),
25 Delete(DeleteObject),
26}
27
28#[derive(Debug)]
29pub(super) struct ChangeSet {
30 pub(super) kind: ChangeSetKind,
31 pub(super) changes: Vec<FDv2Change>,
32 pub(super) selector: Selector,
33}
34
35impl TryFrom<ChangeSet> for change_set::ChangeSet {
36 type Error = serde_json::Error;
37
38 fn try_from(wire_cs: ChangeSet) -> Result<Self, Self::Error> {
39 let ChangeSet {
40 kind,
41 changes,
42 selector,
43 } = wire_cs;
44
45 let mut translated = Vec::with_capacity(changes.len());
46 for change in changes {
47 match change {
48 FDv2Change::Put(put) => match put.kind.as_str() {
49 "flag" => translated.push(ItemChange::Flag {
50 key: put.key,
51 item: StorageItem::Item(serde_json::from_value::<Flag>(put.object)?),
52 }),
53 "segment" => translated.push(ItemChange::Segment {
54 key: put.key,
55 item: StorageItem::Item(serde_json::from_value::<Segment>(put.object)?),
56 }),
57 other => warn!("FDv2: unknown kind '{other}' in put-object, skipping"),
58 },
59 FDv2Change::Delete(del) => match del.kind.as_str() {
60 "flag" => translated.push(ItemChange::Flag {
61 key: del.key,
62 item: StorageItem::Tombstone(del.version),
63 }),
64 "segment" => translated.push(ItemChange::Segment {
65 key: del.key,
66 item: StorageItem::Tombstone(del.version),
67 }),
68 other => warn!("FDv2: unknown kind '{other}' in delete-object, skipping"),
69 },
70 }
71 }
72
73 Ok(change_set::ChangeSet {
74 kind,
75 changes: translated,
76 selector,
77 })
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use crate::test_common::{basic_flag, basic_segment};
85 use serde_json::json;
86
87 fn put(kind: &str, key: &str, version: u64, object: serde_json::Value) -> FDv2Change {
88 FDv2Change::Put(PutObject {
89 version,
90 kind: kind.into(),
91 key: key.into(),
92 object,
93 })
94 }
95
96 fn delete(kind: &str, key: &str, version: u64) -> FDv2Change {
97 FDv2Change::Delete(DeleteObject {
98 version,
99 kind: kind.into(),
100 key: key.into(),
101 })
102 }
103
104 #[test]
105 fn none_kind_translates_to_empty_changeset() {
106 let wire = ChangeSet {
107 kind: ChangeSetKind::None,
108 changes: vec![],
109 selector: Some("s-1".into()),
110 };
111 let out = change_set::ChangeSet::try_from(wire).unwrap();
112 assert_eq!(out.kind, ChangeSetKind::None);
113 assert!(out.changes.is_empty());
114 assert_eq!(out.selector.as_deref(), Some("s-1"));
115 }
116
117 #[test]
118 fn typed_puts_and_deletes_pass_through() {
119 let flag_json = serde_json::to_value(basic_flag("f1")).unwrap();
120 let segment_json = serde_json::to_value(basic_segment("s1")).unwrap();
121 let wire = ChangeSet {
122 kind: ChangeSetKind::Full,
123 changes: vec![
124 put("flag", "f1", 1, flag_json),
125 put("segment", "s1", 2, segment_json),
126 delete("flag", "old", 3),
127 ],
128 selector: Some("s-full".into()),
129 };
130 let out = change_set::ChangeSet::try_from(wire).unwrap();
131 assert_eq!(out.changes.len(), 3);
132 assert!(
133 matches!(&out.changes[0], ItemChange::Flag { key, item: StorageItem::Item(f) }
134 if key == "f1" && f.key == "f1")
135 );
136 assert!(
137 matches!(&out.changes[1], ItemChange::Segment { key, item: StorageItem::Item(s) }
138 if key == "s1" && s.key == "s1")
139 );
140 assert!(
141 matches!(&out.changes[2], ItemChange::Flag { key, item: StorageItem::Tombstone(3) }
142 if key == "old")
143 );
144 }
145
146 #[test]
147 fn unknown_kind_is_dropped() {
148 let wire = ChangeSet {
149 kind: ChangeSetKind::Partial,
150 changes: vec![put("mystery", "k", 1, json!({}))],
151 selector: None,
152 };
153 let out = change_set::ChangeSet::try_from(wire).unwrap();
154 assert!(out.changes.is_empty());
155 }
156
157 #[test]
158 fn parse_failure_returns_err() {
159 let wire = ChangeSet {
160 kind: ChangeSetKind::Full,
161 changes: vec![put("flag", "bad", 1, json!({"garbage": true}))],
162 selector: None,
163 };
164 assert!(change_set::ChangeSet::try_from(wire).is_err());
165 }
166}