use crate::ddb_number::cursor_token;
use crate::errors::GraphDDBError;
use indexmap_shim::IndexMap;
pub mod indexmap_shim {
#[derive(Debug, Clone, PartialEq, Default)]
pub struct IndexMap<V> {
entries: Vec<(String, V)>,
}
impl<V> IndexMap<V> {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn insert(&mut self, key: String, value: V) {
if let Some(slot) = self.entries.iter_mut().find(|(k, _)| *k == key) {
slot.1 = value;
} else {
self.entries.push((key, value));
}
}
pub fn get(&self, key: &str) -> Option<&V> {
self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
pub fn remove(&mut self, key: &str) -> Option<V> {
let pos = self.entries.iter().position(|(k, _)| k == key)?;
Some(self.entries.remove(pos).1)
}
pub fn contains_key(&self, key: &str) -> bool {
self.entries.iter().any(|(k, _)| k == key)
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &V)> {
self.entries.iter().map(|(k, v)| (k, v))
}
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.entries.iter().map(|(k, _)| k)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl<V> FromIterator<(String, V)> for IndexMap<V> {
fn from_iter<T: IntoIterator<Item = (String, V)>>(iter: T) -> Self {
let mut map = IndexMap::new();
for (k, v) in iter {
map.insert(k, v);
}
map
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
S(String),
N(String),
Bool(bool),
Null,
L(Vec<Value>),
M(IndexMap<Value>),
}
impl Value {
pub fn to_template_string(&self) -> String {
match self {
Value::S(s) => s.clone(),
Value::N(n) => n.clone(),
Value::Bool(b) => {
if *b {
"True".to_string()
} else {
"False".to_string()
}
}
Value::Null => "None".to_string(),
_ => String::new(),
}
}
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub fn to_json(&self) -> serde_json::Value {
use serde_json::Value as J;
match self {
Value::S(s) => J::String(s.clone()),
Value::Bool(b) => J::Bool(*b),
Value::Null => J::Null,
Value::N(n) => number_to_json(n),
Value::L(items) => J::Array(items.iter().map(Value::to_json).collect()),
Value::M(map) => {
let mut obj = serde_json::Map::new();
for (k, v) in map.iter() {
obj.insert(k.clone(), v.to_json());
}
J::Object(obj)
}
}
}
}
fn number_to_json(n: &str) -> serde_json::Value {
use serde_json::Value as J;
if let Ok(token) = crate::ddb_number::cursor_token(n) {
if let Ok(num) = serde_json::from_str::<serde_json::Number>(&token) {
return J::Number(num);
}
}
if let Ok(num) = serde_json::from_str::<serde_json::Number>(n) {
return J::Number(num);
}
J::String(n.to_string())
}
pub fn cursor_json(value: &Value) -> Result<String, GraphDDBError> {
let mut out = String::new();
write_cursor_json(value, &mut out)?;
Ok(out)
}
fn write_cursor_json(value: &Value, out: &mut String) -> Result<(), GraphDDBError> {
match value {
Value::S(s) => write_json_string(s, out),
Value::N(n) => out.push_str(&cursor_token(n)?),
Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
Value::Null => out.push_str("null"),
Value::L(items) => {
out.push('[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_cursor_json(item, out)?;
}
out.push(']');
}
Value::M(map) => {
out.push('{');
for (i, (k, v)) in map.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_json_string(k, out);
out.push(':');
write_cursor_json(v, out)?;
}
out.push('}');
}
}
Ok(())
}
pub(crate) fn write_json_string(s: &str, out: &mut String) {
out.push('"');
for ch in s.chars() {
match ch {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
'\u{08}' => out.push_str("\\b"),
'\u{0C}' => out.push_str("\\f"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
c => out.push(c),
}
}
out.push('"');
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cursor_json_number_and_string_and_order() {
let mut m = IndexMap::new();
m.insert("PK".to_string(), Value::S("GROUP#g1".to_string()));
m.insert("SK".to_string(), Value::S("USER#u1".to_string()));
m.insert("n".to_string(), Value::N("100".to_string()));
let v = Value::M(m);
assert_eq!(
cursor_json(&v).unwrap(),
"{\"PK\":\"GROUP#g1\",\"SK\":\"USER#u1\",\"n\":100}"
);
}
#[test]
fn cursor_json_escapes_like_python() {
let s = Value::S("a\"b\\c\nd".to_string());
assert_eq!(cursor_json(&s).unwrap(), "\"a\\\"b\\\\c\\nd\"");
}
}