use std::convert::TryFrom;
use std::{collections::HashMap, convert::TryInto};
use launchdarkly_server_sdk_evaluation::{Flag, Segment, Versioned};
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Deserialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum PatchTarget {
Flag(StorageItem<Flag>),
Segment(StorageItem<Segment>),
Other(serde_json::Value),
}
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub enum StorageItem<T> {
Item(T),
Tombstone(u64),
}
impl<T> From<T> for StorageItem<T> {
fn from(flag: T) -> Self {
Self::Item(flag)
}
}
impl<T> From<StorageItem<T>> for Option<T> {
fn from(val: StorageItem<T>) -> Self {
match val {
StorageItem::Item(i) => Some(i),
_ => None,
}
}
}
impl<T: Versioned> Versioned for StorageItem<T> {
fn version(&self) -> u64 {
match self {
Self::Item(i) => i.version(),
Self::Tombstone(version) => *version,
}
}
fn is_greater_than_or_equal(&self, version: u64) -> bool {
self.version() >= version
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct AllData<F, S> {
pub flags: HashMap<String, F>,
pub segments: HashMap<String, S>,
}
impl From<AllData<Flag, Segment>> for AllData<StorageItem<Flag>, StorageItem<Segment>> {
fn from(all_data: AllData<Flag, Segment>) -> Self {
Self {
flags: all_data
.flags
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect(),
segments: all_data
.segments
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect(),
}
}
}
impl TryFrom<AllData<Flag, Segment>> for AllData<SerializedItem, SerializedItem> {
type Error = serde_json::Error;
fn try_from(all_data: AllData<Flag, Segment>) -> Result<Self, Self::Error> {
let flags: Result<HashMap<String, SerializedItem>, Self::Error> = all_data
.flags
.into_iter()
.map(|(key, flag)| {
let item = StorageItem::Item(flag);
match SerializedItem::try_from(item) {
Ok(serialized_item) => Ok((key, serialized_item)),
Err(e) => Err(e),
}
})
.collect();
let segments: Result<HashMap<String, SerializedItem>, Self::Error> = all_data
.segments
.into_iter()
.map(|(key, segment)| {
let item = StorageItem::Item(segment);
match SerializedItem::try_from(item) {
Ok(serialized_item) => Ok((key, serialized_item)),
Err(e) => Err(e),
}
})
.collect();
let flags = flags?;
let segments = segments?;
Ok(AllData { flags, segments })
}
}
pub enum DataKind {
Flag,
Segment,
}
#[derive(Clone)]
pub struct SerializedItem {
pub version: u64,
pub deleted: bool,
pub serialized_item: String,
}
#[derive(Serialize, Deserialize)]
struct SerializedTombstone {
version: u64,
key: String,
deleted: bool,
}
impl SerializedTombstone {
fn new(version: u64) -> Self {
Self {
version,
key: "$deleted".to_string(),
deleted: true,
}
}
}
impl TryInto<StorageItem<Flag>> for SerializedItem {
type Error = serde_json::Error;
fn try_into(self) -> Result<StorageItem<Flag>, Self::Error> {
if self.deleted {
return Ok(StorageItem::Tombstone(self.version));
}
let flag_result: Result<Flag, serde_json::Error> =
serde_json::from_str(&self.serialized_item);
match flag_result {
Ok(flag) => Ok(StorageItem::Item(flag)),
Err(e) => {
let serialized_tombstone: Result<SerializedTombstone, serde_json::Error> =
serde_json::from_str(&self.serialized_item);
match serialized_tombstone {
Ok(tombstone) if tombstone.key == "$deleted" && tombstone.deleted => {
Ok(StorageItem::Tombstone(tombstone.version))
}
_ => Err(e),
}
}
}
}
}
pub trait SerializeToSerializedItem {
fn serialize_to_serialized_item(self) -> Result<SerializedItem, serde_json::Error>;
}
impl<T: Versioned> SerializeToSerializedItem for StorageItem<T>
where
T: Serialize,
{
fn serialize_to_serialized_item(self) -> Result<SerializedItem, serde_json::Error> {
match self {
StorageItem::Item(flag) => Ok(SerializedItem {
version: flag.version(),
deleted: false,
serialized_item: serde_json::to_string(&flag)?,
}),
StorageItem::Tombstone(version) => Ok(SerializedItem {
version,
deleted: true,
serialized_item: json!({
"version": version,
"key": "$deleted",
"deleted": true
})
.to_string(),
}),
}
}
}
impl<T> TryFrom<StorageItem<T>> for SerializedItem
where
T: Versioned,
T: Serialize,
{
type Error = serde_json::Error;
fn try_from(storage_item: StorageItem<T>) -> Result<Self, Self::Error> {
match storage_item {
StorageItem::Item(item) => Ok(SerializedItem {
version: item.version(),
deleted: false,
serialized_item: serde_json::to_string(&item)?,
}),
StorageItem::Tombstone(version) => {
let tombstone = SerializedTombstone::new(version);
Ok(SerializedItem {
version,
deleted: true,
serialized_item: serde_json::to_string(&tombstone)?,
})
}
}
}
}
impl TryInto<StorageItem<Segment>> for SerializedItem {
type Error = serde_json::Error;
fn try_into(self) -> Result<StorageItem<Segment>, Self::Error> {
if self.deleted {
return Ok(StorageItem::Tombstone(self.version));
}
let segment_result: Result<Segment, serde_json::Error> =
serde_json::from_str(&self.serialized_item);
match segment_result {
Ok(segment) => Ok(StorageItem::Item(segment)),
Err(e) => {
let serialized_tombstone: Result<SerializedTombstone, serde_json::Error> =
serde_json::from_str(&self.serialized_item);
match serialized_tombstone {
Ok(tombstone) if tombstone.key == "$deleted" && tombstone.deleted => {
Ok(StorageItem::Tombstone(tombstone.version))
}
_ => Err(e),
}
}
}
}
}
#[cfg(test)]
mod tests {
use std::convert::{TryFrom, TryInto};
use launchdarkly_server_sdk_evaluation::{Flag, Segment};
use crate::{
test_common::{basic_flag, basic_segment},
SerializedItem,
};
use super::StorageItem;
#[test]
fn flag_can_be_serialized_and_back() {
let flag = basic_flag("flag-key");
let item = StorageItem::Item(flag.clone());
let result = SerializedItem::try_from(item);
assert!(result.is_ok());
let serialized_item = result.unwrap();
assert_eq!(flag.version, serialized_item.version);
assert!(!serialized_item.deleted);
let result: Result<StorageItem<Flag>, serde_json::Error> = serialized_item.try_into();
match result {
Ok(StorageItem::Item(f)) => {
assert_eq!(f.key, flag.key);
}
_ => panic!("Item failed to deserialize into flag"),
}
}
#[test]
fn flag_tombstone_can_be_serialized_and_back() {
let item: StorageItem<Flag> = StorageItem::Tombstone(42);
let result = SerializedItem::try_from(item);
assert!(result.is_ok());
let serialized_item = result.unwrap();
assert_eq!(42, serialized_item.version);
assert!(serialized_item.deleted);
let result: Result<StorageItem<Flag>, serde_json::Error> = serialized_item.try_into();
match result {
Ok(StorageItem::Tombstone(v)) => {
assert_eq!(v, 42);
}
_ => panic!("Item failed to deserialize into flag"),
}
}
#[test]
fn serialized_flag_with_0_version_uses_serialied_information() {
let item: StorageItem<Flag> = StorageItem::Tombstone(42);
let serialized = SerializedItem::try_from(item).unwrap();
let serialized_item = SerializedItem {
version: 0,
deleted: false,
serialized_item: serialized.serialized_item,
};
let result: Result<StorageItem<Flag>, serde_json::Error> = serialized_item.try_into();
match result {
Ok(StorageItem::Tombstone(v)) => {
assert_eq!(v, 42);
}
_ => panic!("Item failed to deserialize into flag"),
}
}
#[test]
fn segment_can_be_serialized_and_back() {
let segment = basic_segment("segment-key");
let item = StorageItem::Item(segment.clone());
let result = SerializedItem::try_from(item);
assert!(result.is_ok());
let serialized_item = result.unwrap();
assert_eq!(segment.version, serialized_item.version);
assert!(!serialized_item.deleted);
let result: Result<StorageItem<Segment>, serde_json::Error> = serialized_item.try_into();
match result {
Ok(StorageItem::Item(f)) => {
assert_eq!(f.key, segment.key);
}
_ => panic!("Item failed to deserialize into segment"),
}
}
#[test]
fn segment_tombstone_can_be_serialized_and_back() {
let item: StorageItem<Segment> = StorageItem::Tombstone(42);
let result = SerializedItem::try_from(item);
assert!(result.is_ok());
let serialized_item = result.unwrap();
assert_eq!(42, serialized_item.version);
assert!(serialized_item.deleted);
let result: Result<StorageItem<Segment>, serde_json::Error> = serialized_item.try_into();
match result {
Ok(StorageItem::Tombstone(v)) => {
assert_eq!(v, 42);
}
_ => panic!("Item failed to deserialize into segment"),
}
}
#[test]
fn serialized_segment_with_0_version_uses_serialied_information() {
let item: StorageItem<Segment> = StorageItem::Tombstone(42);
let serialized = SerializedItem::try_from(item).unwrap();
let serialized_item = SerializedItem {
version: 0,
deleted: false,
serialized_item: serialized.serialized_item,
};
let result: Result<StorageItem<Segment>, serde_json::Error> = serialized_item.try_into();
match result {
Ok(StorageItem::Tombstone(v)) => {
assert_eq!(v, 42);
}
_ => panic!("Item failed to deserialize into segment"),
}
}
}