open-cypher 0.5.0

An unofficial Rust parser for the openCypher query language.
Documentation
use open_cypher::{DiagnosticCode, lex, parse, parse_recovering};
use proptest::prelude::*;

fn identifier_strategy() -> impl Strategy<Value = String> {
    "[a-z][a-zA-Z0-9_]{0,15}".prop_map(|suffix| format!("v_{suffix}"))
}

prop_compose! {
    fn simple_query_strategy()
        (name in identifier_strategy(), value in -1_000_000i64..1_000_000)
        -> String
    {
        format!(
            "MATCH ({name}:Generated {{value: {value}}}) WHERE {name}.value >= {value} RETURN {name}"
        )
    }
}

proptest! {
    #[test]
    fn generated_valid_queries_parse(source in simple_query_strategy()) {
        prop_assert!(parse(&source).is_ok(), "generated query failed: {source}");
    }

    #[test]
    fn arbitrary_input_never_panics(source in any::<String>()) {
        let _ = lex(&source);
        let _ = parse(&source);
        let recovered = parse_recovering(&source);
        prop_assert!(recovered.diagnostics.len() <= 32);
        prop_assert!(
            recovered
                .diagnostics
                .iter()
                .all(|diagnostic| diagnostic.code != DiagnosticCode::Internal),
            "user input surfaced an internal parser diagnostic: {source:?}"
        );
    }

    #[test]
    fn legal_whitespace_does_not_change_acceptance(
        left_space in "[ \\t\\r\\n]{1,8}",
        right_space in "[ \\t\\r\\n]{1,8}",
    ) {
        let compact = "MATCH (n) RETURN n";
        let expanded = format!("MATCH{left_space}(n){right_space}RETURN{left_space}n");
        prop_assert!(parse(compact).is_ok());
        prop_assert!(parse(&expanded).is_ok(), "expanded query failed: {expanded:?}");
    }

    #[test]
    fn lex_and_recovery_are_deterministic(source in any::<String>()) {
        let first_lex = lex(&source);
        let second_lex = lex(&source);
        prop_assert_eq!(first_lex.tokens, second_lex.tokens);
        prop_assert_eq!(first_lex.diagnostics, second_lex.diagnostics);

        let first_parse = parse_recovering(&source);
        let second_parse = parse_recovering(&source);
        prop_assert_eq!(first_parse.diagnostics, second_parse.diagnostics);
    }
}