questdb-rs 5.0.0

QuestDB Client Library for Rust
Documentation
/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2025 QuestDB
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

#[cfg(feature = "json_tests")]
pub mod json_tests {
    use indoc::indoc;
    use serde::{Deserialize, Serialize};
    use serde_json;
    use slugify::slugify;
    use std::fs::File;
    use std::io::{BufWriter, Write};
    use std::path::PathBuf;

    #[derive(Debug, Serialize, Deserialize)]
    struct Symbol {
        name: String,
        value: String,
    }

    #[derive(Debug, Serialize, Deserialize)]
    struct StringColumn {
        name: String,
        value: String,
    }

    #[derive(Debug, Serialize, Deserialize)]
    struct LongColumn {
        name: String,
        value: i64,
    }

    #[derive(Debug, Serialize, Deserialize)]
    struct DoubleColumn {
        name: String,
        value: f64,
    }

    #[derive(Debug, Serialize, Deserialize)]
    struct BooleanColumn {
        name: String,
        value: bool,
    }

    #[derive(Debug, Serialize, Deserialize)]
    #[serde(tag = "type", rename_all = "UPPERCASE")]
    enum Column {
        String(StringColumn),
        Long(LongColumn),
        Double(DoubleColumn),
        Boolean(BooleanColumn),
    }

    #[derive(Debug, Serialize, Deserialize)]
    struct Expected {
        line: Option<String>,
        #[serde(rename = "binaryBase64")]
        binary_base64: Option<String>,

        #[serde(rename = "anyLines")]
        any_lines: Option<Vec<String>>,
    }

    #[derive(Debug, Serialize, Deserialize)]
    #[serde(tag = "status", rename_all = "UPPERCASE")]
    enum Outcome {
        Success(Expected),
        Error,
    }

    #[derive(Debug, Serialize, Deserialize)]
    struct TestSpec {
        #[serde(rename = "testName")]
        test_name: String,
        table: String,
        symbols: Vec<Symbol>,
        columns: Vec<Column>,
        result: Outcome,
    }

    fn parse() -> Vec<TestSpec> {
        let mut json_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
        json_path.push("src");
        json_path.push("tests");
        json_path.push("interop");
        json_path.push("ilp-client-interop-test.json");
        let file = std::fs::File::open(json_path).unwrap();
        serde_json::from_reader(file).unwrap()
    }

    pub fn build() -> Result<(), Box<dyn std::error::Error>> {
        let specs = parse();
        // eprintln!("Parsed JSON: {:#?}", specs);
        let mut file_path = PathBuf::from(std::env::var("OUT_DIR")?);
        file_path.push("json_tests.rs");
        let mut output = BufWriter::new(File::create(file_path)?);
        // let mut output = String::new();
        writeln!(
            output,
            "{}",
            indoc! {r#"
            // This file is auto-generated by build.rs.

            use crate::{Result, ingress::{Buffer, ProtocolVersion}};
            use crate::tests::{TestResult};
            use base64ct::Base64;
            use base64ct::Encoding;
            use rstest::rstest;

            fn matches_any_line(line: &[u8], expected: &[&str]) -> bool {
                for &exp in expected {
                    if line == exp.as_bytes() {
                        return true;
                    }
                }
                eprintln!("Could not match:\n    {line:?}\nTo any of: {expected:#?}");
                false
            }
            "#}
        )?;

        for (index, spec) in specs.iter().enumerate() {
            writeln!(output, "/// {}", spec.test_name)?;
            // for line in serde_json::to_string_pretty(&spec).unwrap().split("\n") {
            //     writeln!(output, "/// {}", line)?;
            // }
            writeln!(output, "#[rstest]")?;
            writeln!(
                output,
                "fn test_{:03}_{}(\n    #[values(ProtocolVersion::V1, ProtocolVersion::V2)] version: ProtocolVersion,\n) -> TestResult {{",
                index,
                slugify!(&spec.test_name, separator = "_")
            )?;
            writeln!(output, "    let mut buffer = Buffer::new(version);")?;

            let (expected, indent) = match &spec.result {
                Outcome::Success(line) => (Some(line), ""),
                Outcome::Error => (None, "    "),
            };
            if expected.is_none() {
                writeln!(output, "    || -> Result<()> {{")?;
            }
            writeln!(output, "{indent}    buffer")?;
            writeln!(output, "{indent}        .table({:?})?", spec.table)?;
            for symbol in spec.symbols.iter() {
                writeln!(
                    output,
                    "{}        .symbol({:?}, {:?})?",
                    indent, symbol.name, symbol.value
                )?;
            }
            for column in spec.columns.iter() {
                match column {
                    Column::String(column) => writeln!(
                        output,
                        "{}        .column_str({:?}, {:?})?",
                        indent, column.name, column.value
                    )?,
                    Column::Long(column) => writeln!(
                        output,
                        "{}        .column_i64({:?}, {:?})?",
                        indent, column.name, column.value
                    )?,
                    Column::Double(column) => writeln!(
                        output,
                        "{}        .column_f64({:?}, {:?})?",
                        indent, column.name, column.value
                    )?,
                    Column::Boolean(column) => writeln!(
                        output,
                        "{}        .column_bool({:?}, {:?})?",
                        indent, column.name, column.value
                    )?,
                }
            }
            writeln!(output, "{indent}        .at_now()?;")?;
            if let Some(expected) = expected {
                if let Some(ref base64) = expected.binary_base64 {
                    writeln!(output, "    if version != ProtocolVersion::V1 {{")?;
                    writeln!(
                        output,
                        "        let exp = Base64::decode_vec(\"{base64}\").unwrap();"
                    )?;
                    writeln!(
                        output,
                        "        assert_eq!(buffer.as_bytes(), exp.as_slice());"
                    )?;
                    writeln!(output, "    }} else {{")?;
                    if let Some(ref line) = expected.line {
                        let exp_ln = format!("{line}\n");
                        writeln!(output, "        let exp = {exp_ln:?};")?;
                        writeln!(
                            output,
                            "        assert_eq!(buffer.as_bytes(), exp.as_bytes());"
                        )?;
                    } else {
                        // Checking V1 any_lines
                        let any: Vec<String> = expected
                            .any_lines
                            .as_ref()
                            .unwrap()
                            .iter()
                            .map(|line| format!("{line}\n"))
                            .collect();
                        writeln!(output, "        let any = [")?;
                        for line in any.iter() {
                            writeln!(output, "            {line:?},")?;
                        }
                        writeln!(output, "        ];")?;
                        writeln!(
                            output,
                            "        assert!(matches_any_line(buffer.as_bytes(), &any));"
                        )?;
                    }
                    writeln!(output, "    }}")?;
                } else if let Some(ref line) = expected.line {
                    let exp_ln = format!("{line}\n");
                    writeln!(output, "    let exp = {exp_ln:?};")?;
                    writeln!(output, "    assert_eq!(buffer.as_bytes(), exp.as_bytes());")?;
                } else {
                    let any: Vec<String> = expected
                        .any_lines
                        .as_ref()
                        .unwrap()
                        .iter()
                        .map(|line| format!("{line}\n"))
                        .collect();
                    writeln!(output, "    let any = [")?;
                    for line in any.iter() {
                        writeln!(output, "            {line:?},")?;
                    }
                    writeln!(output, "        ];")?;
                    writeln!(
                        output,
                        "    assert!(matches_any_line(buffer.as_bytes(), &any));"
                    )?;
                }
            } else {
                writeln!(output, "        Ok(())")?;
                writeln!(output, "    }}().unwrap_err();")?;
            }
            writeln!(output, "    Ok(())")?;
            writeln!(output, "}}")?;
        }
        Ok(())
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(not(any(feature = "tls-webpki-certs", feature = "tls-native-certs")))]
    compile_error!(
        "At least one of `tls-webpki-certs` or `tls-native-certs` features must be enabled."
    );

    #[cfg(not(any(feature = "_sender-tcp", feature = "_sender-http")))]
    compile_error!(
        "At least one of `sync-sender-tcp` or `sync-sender-http` features must be enabled"
    );

    #[cfg(not(any(feature = "aws-lc-crypto", feature = "ring-crypto")))]
    compile_error!("You must enable exactly one of the `aws-lc-crypto` or `ring-crypto` features, but none are enabled.");

    #[cfg(all(feature = "aws-lc-crypto", feature = "ring-crypto"))]
    compile_error!("You must enable exactly one of the `aws-lc-crypto` or `ring-crypto` features, but both are enabled.");

    #[cfg(feature = "json_tests")]
    {
        println!("cargo:rerun-if-changed=build.rs");
        println!("cargo:rerun-if-changed=Cargo.lock");
        println!("cargo:rerun-if-changed=src/test/interop/ilp-client-interop-test.json");

        json_tests::build()?;
    }

    Ok(())
}