decypher 0.2.0-alpha.6

A rust library for parsing openCypher queries.
Documentation
//! Integration tests for the generic `parse` / `parse_with_label` signatures.
//!
//! These tests verify that both functions accept a pre-built [`decypher::Parse`]
//! CST in addition to a raw `&str`, mirroring the generic pattern introduced
//! for [`decypher::analyze`] in PR #15.

use decypher::{ErrorKind, parse, parse_cst, parse_with_label};

/// `parse` accepts a pre-built CST and produces the same `Query` as parsing
/// from a string.
///
/// Unit: `parse()`
/// Precondition: A `Parse` CST built via `parse_cst`.
/// Expectation: `parse(cst)` returns `Ok` with the same statement count as
///   `parse(input_str)`.
#[test]
fn parse_from_preparsed_cst() {
    let input = "MATCH (n:Person) RETURN n.name";
    let cst = parse_cst(input);
    let query_from_cst = parse(cst).unwrap();
    let query_from_str = parse(input).unwrap();
    assert_eq!(
        query_from_cst.statements.len(),
        query_from_str.statements.len()
    );
}

/// `parse` accepts a `Parse` for a multi-statement query.
///
/// Unit: `parse()`
/// Precondition: Two semicolon-separated statements in a CST.
/// Expectation: Resulting `Query` has two statements.
#[test]
fn parse_from_cst_multi_statement() {
    let input = "RETURN 1; RETURN 2";
    let cst = parse_cst(input);
    let query = parse(cst).unwrap();
    assert_eq!(query.statements.len(), 2);
}

/// `parse_with_label` accepts a pre-built CST and attaches the label to
/// errors produced during AST construction.
///
/// Unit: `parse_with_label()`
/// Precondition: A valid `Parse` CST and a source label string.
/// Expectation: Returns `Ok`.
#[test]
fn parse_with_label_from_preparsed_cst() {
    let cst = parse_cst("MATCH (a)-[:KNOWS]->(b) RETURN b.name");
    let result = parse_with_label(cst, "my_script.cypher");
    assert!(result.is_ok());
}

/// `parse_with_label` attaches the label to errors when the CST already
/// carries parse errors.
///
/// Unit: `parse_with_label()`
/// Precondition: A `Parse` CST built from invalid Cypher, plus a label.
/// Expectation: Returns `Err` with `source_label == Some("my_script.cypher")`.
#[test]
fn parse_with_label_from_cst_with_errors() {
    let cst = parse_cst("RETURN;");
    let result = parse_with_label(cst, "my_script.cypher");
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert_eq!(err.source_label(), Some("my_script.cypher"));
}

/// `parse("")` still returns an `EmptyInput` error when given an empty string.
///
/// Unit: `parse()`
/// Precondition: Empty string input (this path goes through `From<&str> for Parse`).
/// Expectation: Returns `Err(CypherError { kind: EmptyInput, .. })`.
#[test]
fn parse_empty_string_still_returns_empty_input_error() {
    let result = parse("");
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(
        matches!(err.kind, ErrorKind::EmptyInput),
        "expected EmptyInput, got {:?}",
        err.kind
    );
}

/// `parse` accepts an owned `String`.
///
/// Unit: `parse()`
/// Precondition: An owned `String` containing a valid query.
/// Expectation: Returns `Ok`.
#[test]
fn parse_from_owned_string() {
    let input = String::from("MATCH (n) RETURN n");
    let result = parse(input);
    assert!(result.is_ok());
}

/// `parse` accepts a `&String` reference.
///
/// Unit: `parse()`
/// Precondition: A `&String` reference to a valid query.
/// Expectation: Returns `Ok`.
#[test]
fn parse_from_string_ref() {
    let input = String::from("MATCH (n) RETURN n");
    let result = parse(&input);
    assert!(result.is_ok());
}

/// Passing a raw `Parse` CST built directly from `parse_cst` (which does not
/// go through `From<&str> for Parse`) is handled without panic.
///
/// Unlike `From<&str> for Parse`, `parse_cst` does not inject an `EmptyInput`
/// error for empty input — the parser simply produces an empty `SOURCE_FILE`
/// with no errors.  Feeding that CST to `parse_with_label` returns an `Err`
/// because `build_source_file` rejects a file with zero statements.
///
/// Unit: `parse_with_label()`
/// Precondition: `parse_cst("")` — CST built via the low-level parser (no
///   `EmptyInput` injection).
/// Expectation: Returns `Err` with `source_label == Some("lbl")`.
#[test]
fn parse_with_label_from_empty_cst() {
    // parse_cst calls crate::parser::parse directly (not From<&str>), so its
    // Parse has no EmptyInput error.  The empty SOURCE_FILE has no statements,
    // and build_source_file rejects that with an internal "empty source file"
    // error — so we expect Err here.
    let cst = decypher::parse_cst("");
    let result = parse_with_label(cst, "lbl");
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert_eq!(err.source_label(), Some("lbl"));
}