use serde_json::{Map, Number, Value};
use std::fmt;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErrorKind {
EmptyKey,
InvalidEscape,
InvalidKeyPath,
InvalidEntry,
PathBacktrackOverflow,
ContainerTypeConflict,
SparseArrayWrite,
InvalidOperationTarget,
UnsupportedValue,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Error {
kind: ErrorKind,
message: String,
}
impl Error {
fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
}
}
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for Error {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ContainerKind {
Undecided,
Array,
Object,
Scalar,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum PathSegment {
Key(String),
Index(usize),
}
#[derive(Debug, Clone)]
struct Frame {
path: Vec<PathSegment>,
kind: ContainerKind,
}
#[derive(Debug, Clone)]
struct Address {
anchor: KeyAnchor,
segments: Vec<PathSegment>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum KeyAnchor {
Named,
CurrentExplicit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum KeyPathKind {
SelfPath,
ObjectPath,
ArrayPath,
}
impl Address {
fn self_path() -> Self {
Self {
anchor: KeyAnchor::CurrentExplicit,
segments: Vec::new(),
}
}
fn named_key(key: &str) -> Self {
Self {
anchor: KeyAnchor::Named,
segments: vec![PathSegment::Key(key.to_owned())],
}
}
fn current_index(index: usize) -> Self {
Self {
anchor: KeyAnchor::CurrentExplicit,
segments: vec![PathSegment::Index(index)],
}
}
fn path_kind(&self) -> KeyPathKind {
match self.anchor {
KeyAnchor::CurrentExplicit if self.segments.is_empty() => KeyPathKind::SelfPath,
KeyAnchor::CurrentExplicit if matches!(self.segments[0], PathSegment::Index(_)) => {
KeyPathKind::ArrayPath
}
_ => KeyPathKind::ObjectPath,
}
}
fn relative_path(&self) -> &[PathSegment] {
&self.segments
}
fn encode(&self) -> Result<String> {
match self.anchor {
KeyAnchor::Named => {
if self.segments.is_empty() {
return Err(Error::new(
ErrorKind::UnsupportedValue,
"serializer cannot emit an empty named path",
));
}
let mut encoded = String::new();
for (index, segment) in self.segments.iter().enumerate() {
if index > 0 {
encoded.push('.');
}
match segment {
PathSegment::Key(key) => encoded.push_str(&encode_key(key)),
PathSegment::Index(_) => {
return Err(Error::new(
ErrorKind::UnsupportedValue,
"serializer cannot emit an index inside a named path",
));
}
}
}
Ok(encoded)
}
KeyAnchor::CurrentExplicit => {
let mut encoded = String::from(".");
for (index, segment) in self.segments.iter().enumerate() {
if index > 0 {
encoded.push('.');
}
match segment {
PathSegment::Key(key) => encoded.push_str(&encode_key(key)),
PathSegment::Index(index) => encoded.push_str(&index.to_string()),
}
}
Ok(encoded)
}
}
}
}
#[derive(Debug, Clone)]
enum Op {
Noop,
Value(Value),
Set(Address, Value),
Enter(Address),
EnterArray(Address),
Flag(Address, bool),
Append(Address, Value),
Remove(Address, Value),
}
#[derive(Debug, Clone)]
struct Command {
up: usize,
op: Op,
}
pub fn parse_str(input: &str) -> Result<Value> {
let commands = parse_commands(input)?;
let collapse_root_scalar = commands.len() == 1 && matches!(commands[0].op, Op::Value(_));
let mut engine = Engine::new(Value::Null, false);
for command in &commands {
engine.execute(command)?;
}
engine.finish(collapse_root_scalar)
}
pub fn apply_str(base: &mut Value, patch: &str) -> Result<()> {
let commands = parse_commands(patch)?;
let mut engine = Engine::new(std::mem::take(base), true);
for command in &commands {
engine.execute(command)?;
}
*base = engine.finish(false)?;
Ok(())
}
pub fn to_string(value: &Value) -> Result<String> {
let mut actual_context = Vec::<PathSegment>::new();
let mut entries = Vec::<String>::new();
match value {
Value::Object(map) => {
if map.is_empty() {
entries.push(build_entry_body(
&Address::self_path(),
EntryOp::Enter(ContainerKind::Object),
)?);
} else {
serialize_object(map, &[], &mut actual_context, &mut entries)?;
}
}
Value::Array(items) => {
if items.is_empty() {
entries.push(build_entry_body(
&Address::self_path(),
EntryOp::Enter(ContainerKind::Array),
)?);
} else {
serialize_array(items, &[], &mut actual_context, &mut entries)?;
}
}
Value::Bool(flag) => entries.push(build_entry_body(
&Address::self_path(),
EntryOp::Flag(*flag),
)?),
scalar => entries.push(build_entry_body(
&Address::self_path(),
EntryOp::Set(encode_scalar_value(scalar, ScalarPosition::Root)?),
)?),
}
Ok(entries.join(","))
}
struct Engine {
document: Value,
frames: Vec<Frame>,
}
impl Engine {
fn new(document: Value, derive_root_kind: bool) -> Self {
let kind = if derive_root_kind {
kind_from_value(&document)
} else {
ContainerKind::Undecided
};
Self {
document,
frames: vec![Frame {
path: Vec::new(),
kind,
}],
}
}
fn finish(mut self, collapse_root_scalar: bool) -> Result<Value> {
self.pop_frames(self.frames.len().saturating_sub(1))?;
if collapse_root_scalar {
if let Value::Array(mut items) = self.document {
if items.len() == 1 {
return Ok(items.remove(0));
}
return Ok(Value::Array(items));
}
}
Ok(self.document)
}
fn execute(&mut self, command: &Command) -> Result<()> {
self.pop_frames(command.up)?;
match &command.op {
Op::Noop => Ok(()),
Op::Value(value) => self.push_value(value.clone()),
Op::Set(key, value) => self.set_value(key, value.clone()),
Op::Enter(key) => self.enter(key),
Op::EnterArray(key) => self.enter_array(key),
Op::Flag(key, flag) => self.set_value(key, Value::Bool(*flag)),
Op::Append(key, value) => self.append_value(key, value.clone()),
Op::Remove(key, value) => self.remove_value(key, value),
}
}
fn pop_frames(&mut self, count: usize) -> Result<()> {
if count > self.frames.len().saturating_sub(1) {
return Err(Error::new(
ErrorKind::PathBacktrackOverflow,
"path backtrack exceeds root",
));
}
for _ in 0..count {
if let Some(frame) = self.frames.pop() {
if frame.kind == ContainerKind::Undecided {
set_value_at_path(&mut self.document, &frame.path, Value::Object(Map::new()))?;
}
}
}
Ok(())
}
fn current_frame(&self) -> &Frame {
self.frames.last().expect("frame stack is never empty")
}
fn current_frame_mut(&mut self) -> &mut Frame {
self.frames.last_mut().expect("frame stack is never empty")
}
fn refresh_current_kind(&mut self) -> Result<()> {
let path = self.current_frame().path.clone();
let kind = kind_from_value(get_mut_at_path(&mut self.document, &path)?);
self.current_frame_mut().kind = kind;
Ok(())
}
fn ensure_current_kind(&mut self, desired: ContainerKind) -> Result<()> {
let index = self.frames.len() - 1;
let current_kind = self.frames[index].kind;
match (current_kind, desired) {
(ContainerKind::Undecided, ContainerKind::Array) => {
let path = self.frames[index].path.clone();
ensure_container_at_path(&mut self.document, &path, ContainerKind::Array)?;
self.frames[index].kind = ContainerKind::Array;
Ok(())
}
(ContainerKind::Undecided, ContainerKind::Object) => {
let path = self.frames[index].path.clone();
ensure_container_at_path(&mut self.document, &path, ContainerKind::Object)?;
self.frames[index].kind = ContainerKind::Object;
Ok(())
}
(ContainerKind::Array, ContainerKind::Array)
| (ContainerKind::Object, ContainerKind::Object) => Ok(()),
(ContainerKind::Scalar, _) => Err(Error::new(
ErrorKind::InvalidOperationTarget,
"cannot apply container operation to scalar current container",
)),
_ => Err(Error::new(
ErrorKind::ContainerTypeConflict,
"current container type conflicts with operation",
)),
}
}
fn push_value(&mut self, value: Value) -> Result<()> {
self.ensure_current_kind(ContainerKind::Array)?;
let path = self.current_frame().path.clone();
let current = get_mut_at_path(&mut self.document, &path)?;
let array = current.as_array_mut().ok_or_else(|| {
Error::new(
ErrorKind::InvalidOperationTarget,
"current container is not an array",
)
})?;
array.push(value);
Ok(())
}
fn resolve_target(&mut self, key: &Address) -> Result<Vec<PathSegment>> {
match key.path_kind() {
KeyPathKind::SelfPath => Ok(Vec::new()),
KeyPathKind::ArrayPath => {
self.ensure_current_kind(ContainerKind::Array)?;
Ok(key.relative_path().to_vec())
}
KeyPathKind::ObjectPath => {
match self.current_frame().kind {
ContainerKind::Array => {
return Err(Error::new(
ErrorKind::ContainerTypeConflict,
"named keys are not allowed in an array container",
));
}
_ => self.ensure_current_kind(ContainerKind::Object)?,
}
Ok(key.relative_path().to_vec())
}
}
}
fn set_value(&mut self, key: &Address, value: Value) -> Result<()> {
let target = self.resolve_target(key)?;
let current_path = self.current_frame().path.clone();
let current = get_mut_at_path(&mut self.document, ¤t_path)?;
set_path_value(current, &target, value)?;
if target.is_empty() {
return self.refresh_current_kind();
}
Ok(())
}
fn append_value(&mut self, key: &Address, value: Value) -> Result<()> {
let target = self.resolve_target(key)?;
if target.is_empty() {
self.ensure_current_kind(ContainerKind::Array)?;
}
let current_path = self.current_frame().path.clone();
let current = get_mut_at_path(&mut self.document, ¤t_path)?;
append_path_value(current, &target, value)
}
fn remove_value(&mut self, key: &Address, value: &Value) -> Result<()> {
let target = self.resolve_target(key)?;
let current_path = self.current_frame().path.clone();
let current = get_mut_at_path(&mut self.document, ¤t_path)?;
remove_path_value(current, &target, value)
}
fn enter(&mut self, key: &Address) -> Result<()> {
self.enter_with_kind(key, ContainerKind::Object, ContainerKind::Undecided)
}
fn enter_array(&mut self, key: &Address) -> Result<()> {
self.enter_with_kind(key, ContainerKind::Array, ContainerKind::Array)
}
fn enter_with_kind(
&mut self,
key: &Address,
self_kind: ContainerKind,
child_kind: ContainerKind,
) -> Result<()> {
let target = self.resolve_target(key)?;
if target.is_empty() {
return self.ensure_current_kind(self_kind);
}
let current_path = self.current_frame().path.clone();
let current = get_mut_at_path(&mut self.document, ¤t_path)?;
ensure_relative_target_mut(current, &target, true)?;
let mut absolute = current_path;
absolute.extend(target);
let existing_kind = kind_from_value(get_mut_at_path(&mut self.document, &absolute)?);
if existing_kind == ContainerKind::Scalar {
return Err(Error::new(
ErrorKind::InvalidOperationTarget,
"cannot enter an existing scalar value",
));
}
let kind = match child_kind {
ContainerKind::Undecided => existing_kind,
ContainerKind::Array | ContainerKind::Object => {
ensure_container_at_path(&mut self.document, &absolute, child_kind)?;
child_kind
}
ContainerKind::Scalar => {
return Err(Error::new(
ErrorKind::ContainerTypeConflict,
"invalid child container kind",
));
}
};
self.frames.push(Frame {
path: absolute,
kind,
});
Ok(())
}
}
fn parse_commands(input: &str) -> Result<Vec<Command>> {
split_entries(input)?.into_iter().map(parse_entry).collect()
}
fn split_entries(input: &str) -> Result<Vec<&str>> {
let bytes = input.as_bytes();
let mut entries = Vec::new();
let mut start = 0usize;
let mut index = 0usize;
while index < bytes.len() {
if bytes[index] == b'%' {
ensure_valid_escape(bytes, index)?;
index += 3;
continue;
}
if bytes[index] == b',' {
entries.push(&input[start..index]);
index += 1;
start = index;
continue;
}
index += 1;
}
entries.push(&input[start..]);
Ok(entries)
}
fn parse_entry(raw: &str) -> Result<Command> {
let up = raw.bytes().take_while(|byte| *byte == b':').count();
let body = &raw[up..];
if body.is_empty() && up > 0 {
return Ok(Command { up, op: Op::Noop });
}
if let Some(rest) = body.strip_prefix('=') {
return Ok(Command {
up,
op: Op::Value(parse_value(rest)?),
});
}
if let Some(rest) = body.strip_prefix('+') {
if is_valid_key(rest)? {
return Ok(Command {
up,
op: Op::Flag(parse_key(rest)?, true),
});
}
}
if let Some(rest) = body.strip_prefix('-') {
if is_valid_key(rest)? {
return Ok(Command {
up,
op: Op::Flag(parse_key(rest)?, false),
});
}
}
if let Some((key_raw, operator, tail_raw)) = find_keyed_suffix(body)? {
let key = parse_key(key_raw)?;
let op = match operator {
":" => {
if !tail_raw.is_empty() {
return Err(Error::new(
ErrorKind::InvalidEntry,
"child-container entry cannot have trailing content",
));
}
Op::Enter(key)
}
"::" => {
if !tail_raw.is_empty() {
return Err(Error::new(
ErrorKind::InvalidEntry,
"array-child entry cannot have trailing content",
));
}
Op::EnterArray(key)
}
"=" => Op::Set(key, parse_value(tail_raw)?),
"+=" => Op::Append(key, parse_value(tail_raw)?),
"-=" => Op::Remove(key, parse_value(tail_raw)?),
_ => {
return Err(Error::new(
ErrorKind::InvalidEntry,
"unsupported keyed operator",
));
}
};
return Ok(Command { up, op });
}
Ok(Command {
up,
op: Op::Value(parse_value(body)?),
})
}
fn find_keyed_suffix(body: &str) -> Result<Option<(&str, &str, &str)>> {
let bytes = body.as_bytes();
let mut index = 0usize;
while index < bytes.len() {
if bytes[index] == b'%' {
ensure_valid_escape(bytes, index)?;
index += 3;
continue;
}
let operator = if index + 1 < bytes.len() && bytes[index] == b'+' && bytes[index + 1] == b'=' {
Some("+=")
} else if index + 1 < bytes.len() && bytes[index] == b'-' && bytes[index + 1] == b'=' {
Some("-=")
} else if index + 1 < bytes.len() && bytes[index] == b':' && bytes[index + 1] == b':' {
Some("::")
} else if bytes[index] == b':' {
Some(":")
} else if bytes[index] == b'=' {
Some("=")
} else {
None
};
if let Some(operator) = operator {
let key_raw = &body[..index];
if is_valid_key(key_raw)? {
let tail_index = index + operator.len();
return Ok(Some((key_raw, operator, &body[tail_index..])));
}
}
index += 1;
}
Ok(None)
}
fn is_valid_key(raw: &str) -> Result<bool> {
match parse_key(raw) {
Ok(_) => Ok(true),
Err(error)
if matches!(
error.kind(),
ErrorKind::EmptyKey | ErrorKind::InvalidKeyPath
) =>
{
Ok(false)
}
Err(error) => Err(error),
}
}
fn parse_key(raw: &str) -> Result<Address> {
if raw.is_empty() {
return Err(Error::new(ErrorKind::EmptyKey, "key cannot be empty"));
}
let explicit_current = raw.starts_with('.');
let anchor = if explicit_current {
KeyAnchor::CurrentExplicit
} else {
KeyAnchor::Named
};
let source = if explicit_current { &raw[1..] } else { raw };
if source.is_empty() {
if explicit_current {
return Ok(Address::self_path());
}
return Err(Error::new(ErrorKind::EmptyKey, "key cannot be empty"));
}
let raw_segments = split_key_segments(source)?;
let mut decoded_segments = Vec::with_capacity(raw_segments.len());
for segment in raw_segments {
let decoded = decode_percent(segment)?;
if decoded.is_empty() {
return Err(Error::new(
ErrorKind::InvalidKeyPath,
"key path segments cannot be empty",
));
}
decoded_segments.push(decoded);
}
let segments = if explicit_current && is_non_negative_index(&decoded_segments[0]) {
let index = decoded_segments[0].parse::<usize>().map_err(|_| {
Error::new(
ErrorKind::InvalidKeyPath,
"array index is too large for this platform",
)
})?;
let mut segments = Vec::with_capacity(decoded_segments.len());
segments.push(PathSegment::Index(index));
segments.extend(decoded_segments[1..].iter().cloned().map(PathSegment::Key));
segments
} else {
decoded_segments.into_iter().map(PathSegment::Key).collect()
};
Ok(Address { anchor, segments })
}
fn split_key_segments(raw: &str) -> Result<Vec<&str>> {
let bytes = raw.as_bytes();
let mut segments = Vec::new();
let mut start = 0usize;
let mut index = 0usize;
while index < bytes.len() {
if bytes[index] == b'%' {
ensure_valid_escape(bytes, index)?;
index += 3;
continue;
}
if bytes[index] == b'.' {
if start == index {
return Err(Error::new(
ErrorKind::InvalidKeyPath,
"key path contains an empty segment",
));
}
segments.push(&raw[start..index]);
index += 1;
start = index;
continue;
}
index += 1;
}
if start == raw.len() {
return Err(Error::new(
ErrorKind::InvalidKeyPath,
"key path cannot end with an empty segment",
));
}
segments.push(&raw[start..]);
Ok(segments)
}
fn parse_value(raw: &str) -> Result<Value> {
let decoded = decode_percent(raw)?;
if decoded.is_empty() {
return Ok(Value::Null);
}
if is_integer_literal(&decoded) {
if let Ok(number) = decoded.parse::<i64>() {
return Ok(Value::Number(Number::from(number)));
}
if !decoded.starts_with('-') && !decoded.starts_with('+') {
if let Ok(number) = decoded.parse::<u64>() {
return Ok(Value::Number(Number::from(number)));
}
}
}
if is_float_literal(&decoded) {
if let Ok(number) = decoded.parse::<f64>() {
if let Some(number) = Number::from_f64(number) {
return Ok(Value::Number(number));
}
}
}
Ok(Value::String(decoded))
}
fn decode_percent(raw: &str) -> Result<String> {
let bytes = raw.as_bytes();
let mut output = Vec::with_capacity(bytes.len());
let mut index = 0usize;
while index < bytes.len() {
if bytes[index] == b'%' {
ensure_valid_escape(bytes, index)?;
let high = hex_value(bytes[index + 1])?;
let low = hex_value(bytes[index + 2])?;
output.push((high << 4) | low);
index += 3;
} else {
output.push(bytes[index]);
index += 1;
}
}
String::from_utf8(output).map_err(|_| {
Error::new(
ErrorKind::InvalidEscape,
"decoded bytes are not valid UTF-8",
)
})
}
fn ensure_valid_escape(bytes: &[u8], index: usize) -> Result<()> {
if index + 2 >= bytes.len() {
return Err(Error::new(
ErrorKind::InvalidEscape,
"percent escape is incomplete",
));
}
hex_value(bytes[index + 1])?;
hex_value(bytes[index + 2])?;
Ok(())
}
fn hex_value(byte: u8) -> Result<u8> {
match byte {
b'0'..=b'9' => Ok(byte - b'0'),
b'a'..=b'f' => Ok(byte - b'a' + 10),
b'A'..=b'F' => Ok(byte - b'A' + 10),
_ => Err(Error::new(
ErrorKind::InvalidEscape,
"percent escape contains a non-hex digit",
)),
}
}
fn is_integer_literal(input: &str) -> bool {
let body = input.strip_prefix(['+', '-']).unwrap_or(input);
!body.is_empty() && body.bytes().all(|byte| byte.is_ascii_digit())
}
fn is_float_literal(input: &str) -> bool {
let body = input.strip_prefix(['+', '-']).unwrap_or(input);
let Some((left, right)) = body.split_once('.') else {
return false;
};
!right.is_empty()
&& right.bytes().all(|byte| byte.is_ascii_digit())
&& left.bytes().all(|byte| byte.is_ascii_digit())
}
fn is_non_negative_index(segment: &str) -> bool {
!segment.is_empty() && segment.bytes().all(|byte| byte.is_ascii_digit())
}
fn kind_from_value(value: &Value) -> ContainerKind {
match value {
Value::Null => ContainerKind::Undecided,
Value::Array(_) => ContainerKind::Array,
Value::Object(_) => ContainerKind::Object,
_ => ContainerKind::Scalar,
}
}
fn get_mut_at_path<'a>(root: &'a mut Value, path: &[PathSegment]) -> Result<&'a mut Value> {
let mut current = root;
for segment in path {
match segment {
PathSegment::Key(key) => {
current = current
.as_object_mut()
.ok_or_else(|| {
Error::new(
ErrorKind::InvalidOperationTarget,
"expected object while traversing path",
)
})?
.get_mut(key)
.ok_or_else(|| Error::new(ErrorKind::InvalidOperationTarget, "path does not exist"))?;
}
PathSegment::Index(index) => {
current = current
.as_array_mut()
.ok_or_else(|| {
Error::new(
ErrorKind::InvalidOperationTarget,
"expected array while traversing path",
)
})?
.get_mut(*index)
.ok_or_else(|| Error::new(ErrorKind::InvalidOperationTarget, "path does not exist"))?;
}
}
}
Ok(current)
}
fn ensure_container_at_path(
root: &mut Value,
path: &[PathSegment],
desired: ContainerKind,
) -> Result<()> {
let current = get_mut_at_path(root, path)?;
match desired {
ContainerKind::Array => match current {
Value::Null => {
*current = Value::Array(Vec::new());
Ok(())
}
Value::Array(_) => Ok(()),
_ => Err(Error::new(
ErrorKind::ContainerTypeConflict,
"existing value conflicts with container type",
)),
},
ContainerKind::Object => match current {
Value::Null => {
*current = Value::Object(Map::new());
Ok(())
}
Value::Object(_) => Ok(()),
_ => Err(Error::new(
ErrorKind::ContainerTypeConflict,
"existing value conflicts with container type",
)),
},
_ => Err(Error::new(
ErrorKind::ContainerTypeConflict,
"invalid container kind request",
)),
}
}
fn set_value_at_path(root: &mut Value, path: &[PathSegment], value: Value) -> Result<()> {
if path.is_empty() {
*root = value;
return Ok(());
}
let (parent_path, last) = path.split_at(path.len() - 1);
let parent = get_mut_at_path(root, parent_path)?;
match (&last[0], parent) {
(PathSegment::Key(key), Value::Object(map)) => {
map.insert(key.clone(), value);
Ok(())
}
(PathSegment::Index(index), Value::Array(items)) => {
if *index >= items.len() {
return Err(Error::new(
ErrorKind::InvalidOperationTarget,
"path does not exist",
));
}
items[*index] = value;
Ok(())
}
_ => Err(Error::new(
ErrorKind::InvalidOperationTarget,
"path parent has the wrong container type",
)),
}
}
fn ensure_relative_target_mut<'a>(
current: &'a mut Value,
path: &[PathSegment],
enter_mode: bool,
) -> Result<&'a mut Value> {
if path.is_empty() {
if enter_mode
&& matches!(
current,
Value::Bool(_) | Value::Number(_) | Value::String(_)
)
{
return Err(Error::new(
ErrorKind::InvalidOperationTarget,
"cannot enter an existing scalar value",
));
}
return Ok(current);
}
let mut node = current;
for (index, segment) in path.iter().enumerate() {
let is_last = index + 1 == path.len();
let next =
get_child_mut(node, segment, true)?.expect("create_missing=true always returns a child slot");
if is_last {
if enter_mode && matches!(next, Value::Bool(_) | Value::Number(_) | Value::String(_)) {
return Err(Error::new(
ErrorKind::InvalidOperationTarget,
"cannot enter an existing scalar value",
));
}
return Ok(next);
}
ensure_intermediate_container(next, &path[index + 1])?;
node = next;
}
Ok(node)
}
fn ensure_intermediate_container(slot: &mut Value, next_segment: &PathSegment) -> Result<()> {
if slot.is_null() {
*slot = match next_segment {
PathSegment::Key(_) => Value::Object(Map::new()),
PathSegment::Index(_) => Value::Array(Vec::new()),
};
}
match (next_segment, slot) {
(PathSegment::Key(_), Value::Object(_)) | (PathSegment::Index(_), Value::Array(_)) => Ok(()),
(PathSegment::Key(_), _) => Err(Error::new(
ErrorKind::InvalidOperationTarget,
"non-object value blocks relative path traversal",
)),
(PathSegment::Index(_), _) => Err(Error::new(
ErrorKind::InvalidOperationTarget,
"non-array value blocks relative path traversal",
)),
}
}
fn get_existing_relative_target_mut<'a>(
current: &'a mut Value,
path: &[PathSegment],
) -> Result<Option<&'a mut Value>> {
if path.is_empty() {
return Ok(Some(current));
}
let mut node = current;
for segment in path {
let Some(next) = get_child_mut(node, segment, false)? else {
return Ok(None);
};
node = next;
}
Ok(Some(node))
}
fn get_child_mut<'a>(
current: &'a mut Value,
segment: &PathSegment,
create_missing: bool,
) -> Result<Option<&'a mut Value>> {
match segment {
PathSegment::Key(key) => {
let object = current.as_object_mut().ok_or_else(|| {
Error::new(
ErrorKind::InvalidOperationTarget,
if create_missing {
"expected object while resolving relative path"
} else {
"expected object while traversing relative path"
},
)
})?;
if create_missing {
Ok(Some(object.entry(key.clone()).or_insert(Value::Null)))
} else {
Ok(object.get_mut(key))
}
}
PathSegment::Index(index) => {
let array = current.as_array_mut().ok_or_else(|| {
Error::new(
ErrorKind::InvalidOperationTarget,
if create_missing {
"expected array while resolving relative path"
} else {
"expected array while traversing relative path"
},
)
})?;
if *index > array.len() {
return Err(Error::new(
ErrorKind::SparseArrayWrite,
"array index would create a sparse array",
));
}
if *index == array.len() {
if create_missing {
array.push(Value::Null);
} else {
return Ok(None);
}
}
Ok(Some(&mut array[*index]))
}
}
}
fn set_path_value(current: &mut Value, path: &[PathSegment], value: Value) -> Result<()> {
let target = ensure_relative_target_mut(current, path, false)?;
*target = value;
Ok(())
}
fn append_to_slot(slot: &mut Value, value: Value) -> Result<()> {
match slot {
Value::Null => {
*slot = Value::Array(vec![value]);
Ok(())
}
Value::Array(items) => {
items.push(value);
Ok(())
}
_ => Err(Error::new(
ErrorKind::InvalidOperationTarget,
"append target exists but is not an array",
)),
}
}
fn append_path_value(current: &mut Value, path: &[PathSegment], value: Value) -> Result<()> {
let target = ensure_relative_target_mut(current, path, false)?;
append_to_slot(target, value)
}
fn remove_path_value(current: &mut Value, path: &[PathSegment], value: &Value) -> Result<()> {
if path.is_empty() {
return remove_from_slot_or_null(current, value);
}
match get_existing_relative_target_mut(current, path)? {
Some(target) => remove_from_slot(target, value),
None => Ok(()),
}
}
fn remove_from_slot(slot: &mut Value, value: &Value) -> Result<()> {
match slot {
Value::Array(items) => {
items.retain(|item| item != value);
Ok(())
}
_ => Err(Error::new(
ErrorKind::InvalidOperationTarget,
"remove target exists but is not an array",
)),
}
}
fn remove_from_slot_or_null(slot: &mut Value, value: &Value) -> Result<()> {
match slot {
Value::Null => Ok(()),
_ => remove_from_slot(slot, value),
}
}
#[derive(Clone, Copy)]
enum ScalarPosition {
Root,
ObjectField,
ArrayItem,
}
#[derive(Debug, Clone)]
enum EntryOp {
Flag(bool),
Enter(ContainerKind),
Set(String),
}
fn build_entry_body(target: &Address, op: EntryOp) -> Result<String> {
let path = target.encode()?;
Ok(match op {
EntryOp::Flag(flag) => format!("{}{}", if flag { '+' } else { '-' }, path),
EntryOp::Enter(ContainerKind::Object) | EntryOp::Enter(ContainerKind::Undecided) => {
format!("{}:", path)
}
EntryOp::Enter(ContainerKind::Array) => format!("{}::", path),
EntryOp::Enter(ContainerKind::Scalar) => {
return Err(Error::new(
ErrorKind::UnsupportedValue,
"serializer cannot emit scalar enter operations",
));
}
EntryOp::Set(value) => format!("{}={}", path, value),
})
}
fn push_target_entry(
actual_context: &mut Vec<PathSegment>,
desired_context: &[PathSegment],
target: &Address,
op: EntryOp,
next_context: Option<Vec<PathSegment>>,
entries: &mut Vec<String>,
) -> Result<()> {
let body = build_entry_body(target, op)?;
push_entry(actual_context, desired_context, body, next_context, entries)
}
fn serialize_value_at_target(
value: &Value,
target: &Address,
child_context: &[PathSegment],
desired_context: &[PathSegment],
scalar_position: ScalarPosition,
actual_context: &mut Vec<PathSegment>,
entries: &mut Vec<String>,
) -> Result<()> {
match value {
Value::Bool(flag) => push_target_entry(
actual_context,
desired_context,
target,
EntryOp::Flag(*flag),
None,
entries,
),
Value::Object(child) => {
push_target_entry(
actual_context,
desired_context,
target,
EntryOp::Enter(ContainerKind::Object),
Some(child_context.to_vec()),
entries,
)?;
serialize_object(child, child_context, actual_context, entries)
}
Value::Array(child) => {
push_target_entry(
actual_context,
desired_context,
target,
EntryOp::Enter(ContainerKind::Array),
Some(child_context.to_vec()),
entries,
)?;
if child.is_empty() {
Ok(())
} else {
serialize_array(child, child_context, actual_context, entries)
}
}
scalar => push_target_entry(
actual_context,
desired_context,
target,
EntryOp::Set(encode_scalar_value(scalar, scalar_position)?),
None,
entries,
),
}
}
fn serialize_object(
map: &Map<String, Value>,
context: &[PathSegment],
actual_context: &mut Vec<PathSegment>,
entries: &mut Vec<String>,
) -> Result<()> {
for (key, value) in map {
let child_context = context_path_with_key(context, key);
let target = Address::named_key(key);
serialize_value_at_target(
value,
&target,
&child_context,
context,
ScalarPosition::ObjectField,
actual_context,
entries,
)?;
}
Ok(())
}
fn serialize_array(
items: &[Value],
context: &[PathSegment],
actual_context: &mut Vec<PathSegment>,
entries: &mut Vec<String>,
) -> Result<()> {
for (index, value) in items.iter().enumerate() {
let child_context = context_path_with_index(context, index);
let target = Address::current_index(index);
serialize_value_at_target(
value,
&target,
&child_context,
context,
ScalarPosition::ArrayItem,
actual_context,
entries,
)?;
}
Ok(())
}
fn push_entry(
actual_context: &mut Vec<PathSegment>,
desired_context: &[PathSegment],
body: String,
next_context: Option<Vec<PathSegment>>,
entries: &mut Vec<String>,
) -> Result<()> {
if !is_ancestor(desired_context, actual_context) {
return Err(Error::new(
ErrorKind::UnsupportedValue,
"serializer attempted to jump across unrelated contexts",
));
}
let prefix = ":".repeat(actual_context.len().saturating_sub(desired_context.len()));
entries.push(format!("{}{}", prefix, body));
if let Some(next_context) = next_context {
*actual_context = next_context;
} else {
*actual_context = desired_context.to_vec();
}
Ok(())
}
fn is_ancestor(ancestor: &[PathSegment], descendant: &[PathSegment]) -> bool {
ancestor.len() <= descendant.len() && ancestor == &descendant[..ancestor.len()]
}
fn context_path_with_key(context: &[PathSegment], key: &str) -> Vec<PathSegment> {
let mut path = context.to_vec();
path.push(PathSegment::Key(key.to_owned()));
path
}
fn context_path_with_index(context: &[PathSegment], index: usize) -> Vec<PathSegment> {
let mut path = context.to_vec();
path.push(PathSegment::Index(index));
path
}
fn encode_key(key: &str) -> String {
encode_bytes(key, true)
}
fn encode_value_string(value: &str) -> String {
encode_bytes(value, false)
}
fn encode_bytes(input: &str, key_mode: bool) -> String {
let mut output = String::new();
for byte in input.as_bytes() {
let keep = if key_mode {
byte.is_ascii_alphanumeric() || *byte == b'_'
} else {
matches!(
*byte,
b'!' | b'#'..=b'\'' | b'('..=b'+' | b'-'..=b'/' | b'0'..=b'9' | b':'..=b'~'
) && *byte != b','
&& *byte != b'%'
&& *byte != b' '
};
if keep {
output.push(*byte as char);
} else {
output.push('%');
output.push(hex_digit(byte >> 4));
output.push(hex_digit(byte & 0x0F));
}
}
output
}
fn hex_digit(value: u8) -> char {
match value {
0..=9 => (b'0' + value) as char,
10..=15 => (b'A' + (value - 10)) as char,
_ => unreachable!(),
}
}
fn encode_scalar_value(value: &Value, position: ScalarPosition) -> Result<String> {
match value {
Value::Null => Ok(String::new()),
Value::Number(number) => Ok(number.to_string()),
Value::String(string) => {
if string.is_empty() {
return Err(Error::new(
ErrorKind::UnsupportedValue,
"empty strings are not representable in SSOF v1",
));
}
Ok(encode_value_string(string))
}
Value::Bool(_) if matches!(position, ScalarPosition::ObjectField) => Err(Error::new(
ErrorKind::UnsupportedValue,
"object booleans should be emitted as flags before reaching scalar encoding",
)),
Value::Bool(_) => Err(Error::new(
ErrorKind::UnsupportedValue,
"booleans outside object fields are not representable in SSOF v1",
)),
Value::Array(items) if items.is_empty() => Err(Error::new(
ErrorKind::UnsupportedValue,
"empty arrays are not representable in SSOF v1",
)),
Value::Array(_) | Value::Object(_) => Err(Error::new(
ErrorKind::UnsupportedValue,
"container value reached scalar encoding unexpectedly",
)),
}
}
#[cfg(test)]
mod tests {
use super::{apply_str, parse_str, to_string, ErrorKind};
use serde_json::json;
#[test]
fn parses_minimal_generation_example() {
let input = "system:,+debug,-legacy,db.nodes:,192.168.1.1,192.168.1.2,:users:,.0:,id=101,role=admin,:.1:,id=102,role=dev,::tasks:,start_time=02:30:00,:tags:,prod";
let parsed = parse_str(input).unwrap();
assert_eq!(
parsed,
json!({
"system": {
"debug": true,
"legacy": false,
"db": { "nodes": ["192.168.1.1", "192.168.1.2"] },
"users": [
{ "id": 101, "role": "admin" },
{ "id": 102, "role": "dev" }
],
"tasks": { "start_time": "02:30:00" },
"tags": ["prod"]
}
})
);
}
#[test]
fn applies_incremental_patch() {
let mut base = json!({
"system": {
"db": { "nodes": ["192.168.1.1", "192.168.1.2"] },
"tags": ["prod", "test"]
}
});
apply_str(&mut base, "system:,db.nodes+=192.168.1.3,tags-=test").unwrap();
assert_eq!(
base,
json!({
"system": {
"db": { "nodes": ["192.168.1.1", "192.168.1.2", "192.168.1.3"] },
"tags": ["prod"]
}
})
);
}
#[test]
fn distinguishes_object_keys_and_array_indexes() {
assert_eq!(
parse_str("name:,0=1,1=2").unwrap(),
json!({ "name": { "0": 1, "1": 2 } })
);
assert_eq!(
parse_str("name:,.0=1,.1=2").unwrap(),
json!({ "name": [1, 2] })
);
}
#[test]
fn preserves_forced_values() {
assert_eq!(
parse_str("list:,=+foo,=x+=y").unwrap(),
json!({ "list": ["+foo", "x+=y"] })
);
}
#[test]
fn rejects_sparse_arrays() {
let error = parse_str("items:,.1=2").unwrap_err();
assert_eq!(error.kind(), &ErrorKind::SparseArrayWrite);
}
#[test]
fn rejects_path_backtracking_past_root() {
let error = parse_str(":name=alice").unwrap_err();
assert_eq!(error.kind(), &ErrorKind::PathBacktrackOverflow);
}
#[test]
fn rejects_named_keys_after_array_typing() {
let error = parse_str("users:,alice,id=1").unwrap_err();
assert_eq!(error.kind(), &ErrorKind::ContainerTypeConflict);
}
#[test]
fn rejects_value_entries_inside_object_container() {
let error = parse_str("user:,name=alice,hello").unwrap_err();
assert_eq!(error.kind(), &ErrorKind::ContainerTypeConflict);
}
#[test]
fn rejects_conflicting_self_container_retyping() {
let error = parse_str(".:,.::").unwrap_err();
assert_eq!(error.kind(), &ErrorKind::ContainerTypeConflict);
let error = parse_str(".::,.:").unwrap_err();
assert_eq!(error.kind(), &ErrorKind::ContainerTypeConflict);
}
#[test]
fn follows_first_valid_key_suffix_rule() {
assert_eq!(parse_str("x=x+=y").unwrap(), json!({ "x": "x+=y" }));
assert_eq!(parse_str("a.b").unwrap(), json!("a.b"));
assert_eq!(parse_str("0").unwrap(), json!(0));
}
#[test]
fn parses_null_numeric_and_string_value_edges() {
assert_eq!(parse_str("name=").unwrap(), json!({ "name": null }));
assert_eq!(parse_str(".5").unwrap(), json!(0.5));
assert_eq!(parse_str("=+8").unwrap(), json!(8));
assert_eq!(parse_str("name=true").unwrap(), json!({ "name": "true" }));
}
#[test]
fn backtrack_only_entries_do_not_emit_implicit_null() {
assert_eq!(
parse_str(".::,.0:,x=y,:,2").unwrap(),
json!([{ "x": "y" }, 2])
);
assert_eq!(
parse_str(".::,.0:,x=y,:=,2").unwrap(),
json!([{ "x": "y" }, null, 2])
);
}
#[test]
fn round_trips_supported_values() {
let value = json!({
"system": {
"debug": true,
"db": { "nodes": ["192.168.1.1", "192.168.1.2"] },
"users": [
{ "id": 101, "role": "admin" },
{ "id": 102, "role": "dev" }
]
}
});
let encoded = to_string(&value).unwrap();
let decoded = parse_str(&encoded).unwrap();
assert_eq!(decoded, value);
}
#[test]
fn serializes_root_array_with_explicit_indexes() {
let value = json!([1, { "name": "freff" }]);
let encoded = to_string(&value).unwrap();
assert_eq!(encoded, ".0=1,.1:,name=freff");
assert_eq!(parse_str(&encoded).unwrap(), value);
}
#[test]
fn parses_percent_encoded_keys_and_values() {
let parsed = parse_str("user%2Ename=John%20Collins,percent=100%25,notes=a%2Cb").unwrap();
assert_eq!(
parsed,
json!({
"user.name": "John Collins",
"percent": "100%",
"notes": "a,b"
})
);
}
#[test]
fn parses_percent_encoded_reserved_key_characters() {
let parsed = parse_str("a%3Ab=1,c%3Dd=2,e%2Bf=3,g%2Dh=4").unwrap();
assert_eq!(
parsed,
json!({
"a:b": 1,
"c=d": 2,
"e+f": 3,
"g-h": 4
})
);
}
#[test]
fn treats_explicit_current_named_keys_like_regular_object_keys() {
assert_eq!(
parse_str("settings:,theme=dark,nested.mode=auto").unwrap(),
parse_str("settings:,.theme=dark,.nested.mode=auto").unwrap()
);
}
#[test]
fn supports_self_paths_for_current_namespace_operations() {
assert_eq!(parse_str(".:").unwrap(), json!({}));
assert_eq!(parse_str(".::").unwrap(), json!([]));
assert_eq!(parse_str(".=1").unwrap(), json!(1));
assert_eq!(
parse_str(".+=prod,.+=stable").unwrap(),
json!(["prod", "stable"])
);
assert_eq!(parse_str("+.").unwrap(), json!(true));
}
#[test]
fn supports_self_paths_inside_nested_object_context() {
assert_eq!(
parse_str("settings:,.:,+enabled,nested.mode=auto").unwrap(),
json!({
"settings": {
"enabled": true,
"nested": { "mode": "auto" }
}
})
);
}
#[test]
fn supports_self_paths_inside_nested_array_context() {
assert_eq!(
parse_str("items:,.0:,.+=prod,.+=stable").unwrap(),
json!({
"items": [["prod", "stable"]]
})
);
}
#[test]
fn supports_self_paths_in_apply_mode() {
let mut base = json!(["prod", "test"]);
apply_str(&mut base, ".-=test,.+=stable").unwrap();
assert_eq!(base, json!(["prod", "stable"]));
let mut base = json!({ "settings": null });
apply_str(&mut base, "settings:,.:,+enabled").unwrap();
assert_eq!(base, json!({ "settings": { "enabled": true } }));
}
#[test]
fn supports_double_colon_array_entry() {
assert_eq!(parse_str("items::").unwrap(), json!({ "items": [] }));
assert_eq!(
parse_str("items::,prod,stable").unwrap(),
json!({ "items": ["prod", "stable"] })
);
}
#[test]
fn rejects_trailing_content_after_container_entry() {
let error = parse_str("items:extra").unwrap_err();
assert_eq!(error.kind(), &ErrorKind::InvalidEntry);
let error = parse_str("items::extra").unwrap_err();
assert_eq!(error.kind(), &ErrorKind::InvalidEntry);
}
#[test]
fn rejects_invalid_percent_escape() {
let error = parse_str("name=bad%2").unwrap_err();
assert_eq!(error.kind(), &ErrorKind::InvalidEscape);
}
#[test]
fn append_creates_missing_array_and_remove_missing_value_is_noop() {
let mut base = json!({
"system": {}
});
apply_str(&mut base, "system:,tags+=prod,tags-=test").unwrap();
assert_eq!(
base,
json!({
"system": {
"tags": ["prod"]
}
})
);
}
#[test]
fn append_to_array_index_creates_nested_array_slot() {
let mut base = json!([]);
apply_str(&mut base, ".0+=prod,.0+=stable").unwrap();
assert_eq!(base, json!([["prod", "stable"]]));
}
#[test]
fn removing_from_non_array_target_errors() {
let mut base = json!({
"system": {
"tags": "prod"
}
});
let error = apply_str(&mut base, "system:,tags-=prod").unwrap_err();
assert_eq!(error.kind(), &ErrorKind::InvalidOperationTarget);
}
#[test]
fn rejects_entering_existing_scalar_value() {
let error = parse_str("user:,name=alice,name:").unwrap_err();
assert_eq!(error.kind(), &ErrorKind::InvalidOperationTarget);
}
#[test]
fn serializes_flags_and_escaped_content() {
let value = json!({
"flag_false": false,
"flag_true": true,
"literal": "100%",
"user.name": "John Collins"
});
let encoded = to_string(&value).unwrap();
assert!(encoded.contains("-flag_false"));
assert!(encoded.contains("+flag_true"));
assert!(encoded.contains("literal=100%25"));
assert!(encoded.contains("user%2Ename=John%20Collins"));
assert_eq!(parse_str(&encoded).unwrap(), value);
}
#[test]
fn serializes_root_boolean_with_self_flag() {
let encoded = to_string(&json!(true)).unwrap();
assert_eq!(encoded, "+.");
assert_eq!(parse_str(&encoded).unwrap(), json!(true));
}
#[test]
fn serializes_root_scalar_with_self_assignment() {
let encoded = to_string(&json!(1)).unwrap();
assert_eq!(encoded, ".=1");
assert_eq!(parse_str(&encoded).unwrap(), json!(1));
}
#[test]
fn rejects_empty_string_serialization() {
let error = to_string(&json!({ "name": "" })).unwrap_err();
assert_eq!(error.kind(), &ErrorKind::UnsupportedValue);
}
#[test]
fn serializes_empty_arrays_and_root_empty_object() {
assert_eq!(to_string(&json!({})).unwrap(), ".:");
assert_eq!(parse_str(".:").unwrap(), json!({}));
let encoded = to_string(&json!({ "items": [] })).unwrap();
assert_eq!(encoded, "items::");
assert_eq!(parse_str(&encoded).unwrap(), json!({ "items": [] }));
}
#[test]
fn round_trips_array_booleans() {
let value = json!([true, false]);
let encoded = to_string(&value).unwrap();
assert_eq!(encoded, "+.0,-.1");
assert_eq!(parse_str(&encoded).unwrap(), value);
}
}