edgedb-protocol 0.6.0

Low-level protocol implemenentation for EdgeDB database client. Use edgedb-tokio for applications instead.
Documentation
use std::collections::HashMap;
use std::error::Error;

use uuid::Uuid;
use bytes::{Bytes, BytesMut};

use edgedb_protocol::encoding::{Input, Output};
use edgedb_protocol::common::{Capabilities, CompilationFlags, State};
use edgedb_protocol::features::ProtocolVersion;
use edgedb_protocol::client_message::{ClientMessage, ClientHandshake};
use edgedb_protocol::client_message::{ExecuteScript, Execute0, Execute1};
use edgedb_protocol::client_message::{Parse, Prepare, IoFormat, Cardinality};
use edgedb_protocol::client_message::{DescribeStatement, DescribeAspect};
use edgedb_protocol::client_message::{SaslInitialResponse};
use edgedb_protocol::client_message::{SaslResponse};
use edgedb_protocol::client_message::{OptimisticExecute};
use edgedb_protocol::client_message::Restore;

mod base;

macro_rules! encoding_eq_ver {
    ($major: expr, $minor: expr, $message: expr, $bytes: expr) => {
        let proto = ProtocolVersion::new($major, $minor);
        let data: &[u8] = $bytes;
        let mut bytes = BytesMut::new();
        $message.encode(&mut Output::new(&proto, &mut bytes))?;
        println!("Serialized bytes {:?}", bytes);
        let bytes = bytes.freeze();
        assert_eq!(&bytes[..], data);
        assert_eq!(
            ClientMessage::decode(
                &mut Input::new(proto, Bytes::copy_from_slice(data))
            )?,
            $message,
        );
    }
}

macro_rules! encoding_eq {
    ($message: expr, $bytes: expr) => {
        let (major, minor) = ProtocolVersion::current().version_tuple();
        encoding_eq_ver!(major, minor, $message, $bytes);
    }
}

#[test]
fn client_handshake() -> Result<(), Box<dyn Error>> {
    encoding_eq!(ClientMessage::ClientHandshake(ClientHandshake {
        major_ver: 1,
        minor_ver: 2,
        params: HashMap::new(),
        extensions: HashMap::new(),
    }), b"\x56\x00\x00\x00\x0C\x00\x01\x00\x02\x00\x00\x00\x00");
    Ok(())
}

#[test]
fn execute_script() -> Result<(), Box<dyn Error>> {
    encoding_eq!(ClientMessage::ExecuteScript(ExecuteScript {
        headers: HashMap::new(),
        script_text: String::from("START TRANSACTION"),
    }), b"Q\0\0\0\x1b\0\0\0\0\0\x11START TRANSACTION");
    Ok(())
}

#[test]
fn prepare() -> Result<(), Box<dyn Error>> {
    encoding_eq_ver!(0, 13, ClientMessage::Prepare(Prepare {
        headers: HashMap::new(),
        io_format: IoFormat::Binary,
        expected_cardinality: Cardinality::AtMostOne,
        statement_name: Bytes::from_static(b"example"),
        command_text: String::from("SELECT 1;"),
    }), b"P\0\0\0 \0\0bo\0\0\0\x07example\0\0\0\tSELECT 1;");
    Ok(())
}

#[test]
fn parse() -> Result<(), Box<dyn Error>> {
    encoding_eq_ver!(1, 0, ClientMessage::Parse(Parse {
        annotations: HashMap::new(),
        allowed_capabilities: Capabilities::MODIFICATIONS,
        compilation_flags: CompilationFlags::INJECT_OUTPUT_TYPE_NAMES,
        implicit_limit: Some(77),
        output_format: IoFormat::Binary,
        expected_cardinality: Cardinality::AtMostOne,
        command_text: String::from("SELECT 1;"),
        state: State {
            typedesc_id: Uuid::from_u128(0),
            data: Bytes::from(""),
        },
    }), b"P\0\0\0A\0\0\0\0\0\0\0\0\0\x01\0\0\0\0\0\0\0\x02\0\0\0\0\0\0\0Mbo\
          \0\0\0\tSELECT 1;\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
    Ok(())
}

#[test]
fn describe_statement() -> Result<(), Box<dyn Error>> {
    encoding_eq!(ClientMessage::DescribeStatement(DescribeStatement {
        headers: HashMap::new(),
        aspect: DescribeAspect::DataDescription,
        statement_name: Bytes::from_static(b"example"),
    }), b"D\0\0\0\x12\0\0T\0\0\0\x07example");
    Ok(())
}

#[test]
fn execute0() -> Result<(), Box<dyn Error>> {
    encoding_eq_ver!(0, 13, ClientMessage::Execute0(Execute0 {
        headers: HashMap::new(),
        statement_name: Bytes::from_static(b"example"),
        arguments: Bytes::new(),
    }), b"E\0\0\0\x15\0\0\0\0\0\x07example\0\0\0\0");
    Ok(())
}

#[test]
fn execute1() -> Result<(), Box<dyn Error>> {
    encoding_eq_ver!(1, 0, ClientMessage::Execute1(Execute1 {
        annotations: HashMap::new(),
        allowed_capabilities: Capabilities::MODIFICATIONS,
        compilation_flags: CompilationFlags::INJECT_OUTPUT_TYPE_NAMES,
        implicit_limit: Some(77),
        output_format: IoFormat::Binary,
        expected_cardinality: Cardinality::AtMostOne,
        command_text: String::from("SELECT 1;"),
        state: State {
            typedesc_id: Uuid::from_u128(0),
            data: Bytes::from(""),
        },
        input_typedesc_id: Uuid::from_u128(123),
        output_typedesc_id: Uuid::from_u128(456),
        arguments: Bytes::new(),
    }), b"O\0\0\0e\0\0\0\0\0\0\0\0\0\x01\0\0\0\0\0\0\0\x02\0\0\0\0\0\0\0Mbo\
          \0\0\0\tSELECT 1;\
          \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
          \0\0\0{\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\xc8\0\0\0\0");
    Ok(())
}

#[test]
fn optimistic_execute() -> Result<(), Box<dyn Error>> {
    encoding_eq_ver!(0, 13,
        ClientMessage::OptimisticExecute(OptimisticExecute {
            headers: HashMap::new(),
            io_format: IoFormat::Binary,
            expected_cardinality: Cardinality::AtMostOne,
            command_text: String::from("COMMIT"),
            input_typedesc_id: Uuid::from_u128(0xFF),
            output_typedesc_id: Uuid::from_u128(0x0),
            arguments: Bytes::new(),
        }),  b"O\0\0\06\0\0bo\0\0\0\x06COMMIT\
               \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\
               \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
    Ok(())
}

#[test]
fn sync() -> Result<(), Box<dyn Error>> {
    encoding_eq!(ClientMessage::Sync, b"S\0\0\0\x04");
    Ok(())
}

#[test]
fn flush() -> Result<(), Box<dyn Error>> {
    encoding_eq!(ClientMessage::Flush, b"H\0\0\0\x04");
    Ok(())
}

#[test]
fn terminate() -> Result<(), Box<dyn Error>> {
    encoding_eq!(ClientMessage::Terminate, b"X\0\0\0\x04");
    Ok(())
}

#[test]
fn authentication() -> Result<(), Box<dyn Error>> {
    encoding_eq!(ClientMessage::AuthenticationSaslInitialResponse(
        SaslInitialResponse {
            method: "SCRAM-SHA-256".into(),
            data: "n,,n=tutorial,r=%NR65>7bQ2S3jzl^k$G&b1^A".into(),
        }),
        bconcat!(b"p\0\0\0A\0\0\0\rSCRAM-SHA-256"
                 b"\0\0\0(n,,n=tutorial,"
                 b"r=%NR65>7bQ2S3jzl^k$G&b1^A"));
    encoding_eq!(ClientMessage::AuthenticationSaslResponse(
        SaslResponse {
            data: bconcat!(b"c=biws,"
                           b"r=%NR65>7bQ2S3jzl^k$G&b1^A"
                           b"YsykYKRbp/Gli53UEElsGb4I,"
                           b"p=UNQQkuQ0m5RRy24Ovzj/"
                           b"sCevUB36WTDbGXIWbCIsJmo=")
                           .clone().freeze(),
        }),
        bconcat!(b"r\0\0\0p"
                 b"\0\0\0hc=biws,"
                 b"r=%NR65>7bQ2S3jzl^k$G&b1^A"
                 b"YsykYKRbp/Gli53UEElsGb4I,"
                 b"p=UNQQkuQ0m5RRy24Ovzj/"
                 b"sCevUB36WTDbGXIWbCIsJmo="));
    Ok(())
}

#[test]
fn restore() -> Result<(), Box<dyn Error>> {
    encoding_eq!(ClientMessage::Restore(Restore {
        headers: HashMap::new(),
        jobs: 1,
        data: Bytes::from_static(b"TEST"),
    }), b"<\x00\x00\x00\x0C\x00\x00\x00\x01TEST");
    Ok(())
}