use bitflags::bitflags;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct ArrayIndexMode: u8 {
const NONE = 0b0000;
const ITEM = 0b0001;
const WILDCARD = 0b0010;
const POSITION = 0b0100;
const ALL = Self::ITEM.bits() | Self::WILDCARD.bits() | Self::POSITION.bits();
}
}
impl ArrayIndexMode {
pub fn has_item(&self) -> bool {
self.contains(Self::ITEM)
}
pub fn has_wildcard(&self) -> bool {
self.contains(Self::WILDCARD)
}
pub fn has_position(&self) -> bool {
self.contains(Self::POSITION)
}
}
#[derive(Serialize, Deserialize)]
struct ArrayIndexModeObject {
#[serde(default)]
item: bool,
#[serde(default)]
wildcard: bool,
#[serde(default)]
position: bool,
}
impl Serialize for ArrayIndexMode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if *self == Self::ALL {
serializer.serialize_str("all")
} else if *self == Self::NONE {
serializer.serialize_str("none")
} else {
let obj = ArrayIndexModeObject {
item: self.has_item(),
wildcard: self.has_wildcard(),
position: self.has_position(),
};
obj.serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for ArrayIndexMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
#[derive(Deserialize)]
#[serde(untagged)]
enum ModeOrPreset {
Preset(String),
Object(ArrayIndexModeObject),
}
match ModeOrPreset::deserialize(deserializer)? {
ModeOrPreset::Preset(s) => match s.to_lowercase().as_str() {
"all" => Ok(Self::ALL),
"none" => Ok(Self::NONE),
other => Err(D::Error::custom(format!(
"unknown preset '{}', expected 'all' or 'none'",
other
))),
},
ModeOrPreset::Object(obj) => {
let mut mode = Self::NONE;
if obj.item {
mode |= Self::ITEM;
}
if obj.wildcard {
mode |= Self::WILDCARD;
}
if obj.position {
mode |= Self::POSITION;
}
Ok(mode)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_is_none() {
let mode = ArrayIndexMode::default();
assert_eq!(mode, ArrayIndexMode::NONE);
assert!(!mode.has_item());
assert!(!mode.has_wildcard());
assert!(!mode.has_position());
}
#[test]
fn test_all_contains_all_flags() {
let mode = ArrayIndexMode::ALL;
assert!(mode.has_item());
assert!(mode.has_wildcard());
assert!(mode.has_position());
}
#[test]
fn test_individual_flags() {
let item = ArrayIndexMode::ITEM;
assert!(item.has_item());
assert!(!item.has_wildcard());
assert!(!item.has_position());
let wildcard = ArrayIndexMode::WILDCARD;
assert!(!wildcard.has_item());
assert!(wildcard.has_wildcard());
assert!(!wildcard.has_position());
let position = ArrayIndexMode::POSITION;
assert!(!position.has_item());
assert!(!position.has_wildcard());
assert!(position.has_position());
}
#[test]
fn test_flag_combinations() {
let combo = ArrayIndexMode::ITEM | ArrayIndexMode::WILDCARD;
assert!(combo.has_item());
assert!(combo.has_wildcard());
assert!(!combo.has_position());
}
#[test]
fn test_serialize_all_as_preset() {
let mode = ArrayIndexMode::ALL;
let json = serde_json::to_string(&mode).unwrap();
assert_eq!(json, r#""all""#);
}
#[test]
fn test_serialize_none_as_preset() {
let mode = ArrayIndexMode::NONE;
let json = serde_json::to_string(&mode).unwrap();
assert_eq!(json, r#""none""#);
}
#[test]
fn test_serialize_partial_as_object() {
let mode = ArrayIndexMode::ITEM | ArrayIndexMode::WILDCARD;
let json = serde_json::to_string(&mode).unwrap();
assert!(json.contains("\"item\":true"));
assert!(json.contains("\"wildcard\":true"));
assert!(json.contains("\"position\":false"));
}
#[test]
fn test_round_trip_none() {
let original = ArrayIndexMode::NONE;
let json = serde_json::to_string(&original).unwrap();
let parsed: ArrayIndexMode = serde_json::from_str(&json).unwrap();
assert_eq!(original, parsed);
}
#[test]
fn test_round_trip_all() {
let original = ArrayIndexMode::ALL;
let json = serde_json::to_string(&original).unwrap();
let parsed: ArrayIndexMode = serde_json::from_str(&json).unwrap();
assert_eq!(original, parsed);
}
#[test]
fn test_round_trip_partial() {
let original = ArrayIndexMode::ITEM | ArrayIndexMode::WILDCARD;
let json = serde_json::to_string(&original).unwrap();
let parsed: ArrayIndexMode = serde_json::from_str(&json).unwrap();
assert_eq!(original, parsed);
}
#[test]
fn test_deserialize_object_form() {
let json = r#"{"item":true,"wildcard":true,"position":false}"#;
let mode: ArrayIndexMode = serde_json::from_str(json).unwrap();
assert!(mode.has_item());
assert!(mode.has_wildcard());
assert!(!mode.has_position());
}
#[test]
fn test_deserialize_preset_all() {
let json = r#""all""#;
let mode: ArrayIndexMode = serde_json::from_str(json).unwrap();
assert_eq!(mode, ArrayIndexMode::ALL);
}
#[test]
fn test_deserialize_preset_none() {
let json = r#""none""#;
let mode: ArrayIndexMode = serde_json::from_str(json).unwrap();
assert_eq!(mode, ArrayIndexMode::NONE);
}
#[test]
fn test_deserialize_preset_case_insensitive() {
let cases = [
("\"ALL\"", ArrayIndexMode::ALL),
("\"None\"", ArrayIndexMode::NONE),
("\"NONE\"", ArrayIndexMode::NONE),
("\"All\"", ArrayIndexMode::ALL),
];
for (json, expected) in cases {
let mode: ArrayIndexMode = serde_json::from_str(json).unwrap();
assert_eq!(mode, expected, "Failed for input: {}", json);
}
}
#[test]
fn test_deserialize_empty_object_is_none() {
let json = r#"{}"#;
let mode: ArrayIndexMode = serde_json::from_str(json).unwrap();
assert_eq!(mode, ArrayIndexMode::NONE);
}
#[test]
fn test_deserialize_partial_object() {
let json = r#"{"wildcard":true}"#;
let mode: ArrayIndexMode = serde_json::from_str(json).unwrap();
assert!(!mode.has_item());
assert!(mode.has_wildcard());
assert!(!mode.has_position());
}
#[test]
fn test_deserialize_invalid_preset_returns_error() {
let json = r#""invalid""#;
let result: Result<ArrayIndexMode, _> = serde_json::from_str(json);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("unknown preset"),
"Error should mention 'unknown preset': {}",
err
);
}
}