tokmd-gate 1.14.0

Policy evaluation engine for tokmd analysis receipts
Documentation
//! RFC 6901 JSON Pointer implementation.

use serde_json::Value;

/// Resolve a JSON Pointer against a JSON value.
///
/// Implements RFC 6901 JSON Pointer syntax:
/// - Empty string "" refers to the whole document
/// - "/" refers to an empty key
/// - "/foo/bar" navigates to obj["foo"]["bar"]
/// - "/0" navigates to array index 0
/// - "~0" escapes to "~"
/// - "~1" escapes to "/"
///
/// # Examples
///
/// ```
/// use serde_json::json;
/// use tokmd_gate::resolve_pointer;
///
/// let doc = json!({"foo": {"bar": 42}});
/// assert_eq!(resolve_pointer(&doc, "/foo/bar"), Some(&json!(42)));
///
/// let arr = json!({"items": [1, 2, 3]});
/// assert_eq!(resolve_pointer(&arr, "/items/1"), Some(&json!(2)));
/// ```
pub fn resolve_pointer<'a>(value: &'a Value, pointer: &str) -> Option<&'a Value> {
    // Empty pointer refers to whole document
    if pointer.is_empty() {
        return Some(value);
    }

    // Pointer must start with /
    if !pointer.starts_with('/') {
        return None;
    }

    let mut current = value;

    for token in pointer[1..].split('/') {
        // Unescape tokens per RFC 6901
        let unescaped = unescape_token(token)?;

        current = match current {
            Value::Object(map) => map.get(&unescaped)?,
            Value::Array(arr) => {
                // Parse array index with RFC 6901 semantics:
                // only unsigned base-10, no leading zeros except "0".
                let idx = parse_array_index(&unescaped)?;
                arr.get(idx)?
            }
            _ => return None,
        };
    }

    Some(current)
}

/// Unescape a JSON Pointer token per RFC 6901.
/// ~1 -> /
/// ~0 -> ~
fn unescape_token(token: &str) -> Option<String> {
    let mut output = String::with_capacity(token.len());
    let mut chars = token.chars();

    while let Some(ch) = chars.next() {
        if ch != '~' {
            output.push(ch);
            continue;
        }

        match chars.next() {
            Some('0') => output.push('~'),
            Some('1') => output.push('/'),
            _ => return None,
        }
    }

    Some(output)
}

fn parse_array_index(token: &str) -> Option<usize> {
    if token == "0" {
        return Some(0);
    }

    if token.is_empty() || token.starts_with('0') || !token.chars().all(|ch| ch.is_ascii_digit()) {
        return None;
    }

    token.parse().ok()
}

/// Escape a string for use in a JSON Pointer.
/// / -> ~1
/// ~ -> ~0
#[cfg(test)]
fn escape_token(s: &str) -> String {
    s.replace('~', "~0").replace('/', "~1")
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn test_empty_pointer() {
        let doc = json!({"foo": 1});
        assert_eq!(resolve_pointer(&doc, ""), Some(&doc));
    }

    #[test]
    fn test_simple_path() {
        let doc = json!({"foo": {"bar": 42}});
        assert_eq!(resolve_pointer(&doc, "/foo"), Some(&json!({"bar": 42})));
        assert_eq!(resolve_pointer(&doc, "/foo/bar"), Some(&json!(42)));
    }

    #[test]
    fn test_array_index() {
        let doc = json!({"items": [10, 20, 30]});
        assert_eq!(resolve_pointer(&doc, "/items/0"), Some(&json!(10)));
        assert_eq!(resolve_pointer(&doc, "/items/2"), Some(&json!(30)));
        assert_eq!(resolve_pointer(&doc, "/items/3"), None);
    }

    #[test]
    fn test_escaped_tokens() {
        let doc = json!({"a/b": {"c~d": 1}});
        assert_eq!(resolve_pointer(&doc, "/a~1b/c~0d"), Some(&json!(1)));
    }

    #[test]
    fn test_invalid_escape_sequence() {
        let doc = json!({"a~2b": 1, "a~b": 2});
        assert_eq!(resolve_pointer(&doc, "/a~2b"), None);
        assert_eq!(resolve_pointer(&doc, "/a~"), None);
    }

    #[test]
    fn test_invalid_pointer() {
        let doc = json!({"foo": 1});
        // Missing leading slash
        assert_eq!(resolve_pointer(&doc, "foo"), None);
        // Non-existent path
        assert_eq!(resolve_pointer(&doc, "/bar"), None);
    }

    #[test]
    fn test_nested_arrays() {
        let doc = json!({"matrix": [[1, 2], [3, 4]]});
        assert_eq!(resolve_pointer(&doc, "/matrix/0/1"), Some(&json!(2)));
        assert_eq!(resolve_pointer(&doc, "/matrix/1/0"), Some(&json!(3)));
    }

    #[test]
    fn test_invalid_array_indexes() {
        let doc = json!({"items": ["zero", "one", "two"]});
        assert_eq!(resolve_pointer(&doc, "/items/01"), None);
        assert_eq!(resolve_pointer(&doc, "/items/+1"), None);
    }

    #[test]
    fn test_escape_token() {
        assert_eq!(escape_token("a/b"), "a~1b");
        assert_eq!(escape_token("a~b"), "a~0b");
        assert_eq!(escape_token("a/b~c"), "a~1b~0c");
    }
}