edgedb-protocol 0.6.1

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

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

use edgedb_protocol::common::{Capabilities, RawTypedesc};
use edgedb_protocol::encoding::{Input, Output};
use edgedb_protocol::features::ProtocolVersion;
use edgedb_protocol::server_message::Authentication;
use edgedb_protocol::server_message::CommandDataDescription1;
use edgedb_protocol::server_message::RestoreReady;
use edgedb_protocol::server_message::ServerHandshake;
use edgedb_protocol::server_message::ServerMessage;
use edgedb_protocol::server_message::StateDataDescription;
use edgedb_protocol::server_message::{Cardinality, PrepareComplete};
use edgedb_protocol::server_message::{CommandComplete0, CommandComplete1};
use edgedb_protocol::server_message::{CommandDataDescription0, Data};
use edgedb_protocol::server_message::{ErrorResponse, ErrorSeverity};
use edgedb_protocol::server_message::{LogMessage, MessageSeverity};
use edgedb_protocol::server_message::{ParameterStatus, ServerKeyData};
use edgedb_protocol::server_message::{ReadyForCommand, TransactionState};

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!(
            ServerMessage::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);
    };
}

macro_rules! map {
    ($($key:expr => $value:expr),*) => {
        {
            #[allow(unused_mut)]
            let mut h = HashMap::new();
            $(
                h.insert($key, $value);
            )*
            h
        }
    }
}

#[test]
fn server_handshake() -> Result<(), Box<dyn Error>> {
    encoding_eq!(
        ServerMessage::ServerHandshake(ServerHandshake {
            major_ver: 1,
            minor_ver: 0,
            extensions: HashMap::new(),
        }),
        b"v\0\0\0\n\0\x01\0\0\0\0"
    );
    Ok(())
}

#[test]
fn ready_for_command() -> Result<(), Box<dyn Error>> {
    encoding_eq!(
        ServerMessage::ReadyForCommand(ReadyForCommand {
            transaction_state: TransactionState::NotInTransaction,
            headers: HashMap::new(),
        }),
        b"Z\0\0\0\x07\0\0I"
    );
    Ok(())
}

#[test]
fn error_response() -> Result<(), Box<dyn Error>> {
    encoding_eq!(
        ServerMessage::ErrorResponse(ErrorResponse {
            severity: ErrorSeverity::Error,
            code: 50397184,
            message: String::from(
                "missing required connection parameter \
                               in ClientHandshake message: \"user\""
            ),
            attributes: map! {
                257 => Bytes::from_static("Traceback (most recent call last):\n  File \"edb/server/mng_port/edgecon.pyx\", line 1077, in edb.server.mng_port.edgecon.EdgeConnection.main\n    await self.auth()\n  File \"edb/server/mng_port/edgecon.pyx\", line 178, in auth\n    raise errors.BinaryProtocolError(\nedb.errors.BinaryProtocolError: missing required connection parameter in ClientHandshake message: \"user\"\n".as_bytes())
            },
        }),
        &fs::read("tests/error_response.bin")?[..]
    );
    Ok(())
}

#[test]
fn server_key_data() -> Result<(), Box<dyn Error>> {
    encoding_eq!(
        ServerMessage::ServerKeyData(ServerKeyData { data: [0u8; 32] }),
        &fs::read("tests/server_key_data.bin")?[..]
    );
    Ok(())
}

#[test]
fn parameter_status() -> Result<(), Box<dyn Error>> {
    encoding_eq!(
        ServerMessage::ParameterStatus(ParameterStatus {
            proto: ProtocolVersion::current(),
            name: Bytes::from_static(b"pgaddr"),
            value: Bytes::from_static(b"/work/tmp/db/.s.PGSQL.60128"),
        }),
        &fs::read("tests/parameter_status.bin")?[..]
    );
    Ok(())
}

#[test]
fn command_complete0() -> Result<(), Box<dyn Error>> {
    encoding_eq_ver!(
        0,
        13,
        ServerMessage::CommandComplete0(CommandComplete0 {
            headers: HashMap::new(),
            status_data: Bytes::from_static(b"okay"),
        }),
        b"C\0\0\0\x0e\0\0\0\0\0\x04okay"
    );
    Ok(())
}

#[test]
fn command_complete1() -> Result<(), Box<dyn Error>> {
    encoding_eq_ver!(
        1,
        0,
        ServerMessage::CommandComplete1(CommandComplete1 {
            annotations: HashMap::new(),
            capabilities: Capabilities::MODIFICATIONS,
            status_data: Bytes::from_static(b"okay"),
            state: None,
        }),
        b"C\0\0\0*\0\0\0\0\0\0\0\0\0\x01\0\0\0\x04okay\
          \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
          \0\0\0\0"
    );
    Ok(())
}

#[test]
fn prepare_complete() -> Result<(), Box<dyn Error>> {
    encoding_eq!(
        ServerMessage::PrepareComplete(PrepareComplete {
            headers: HashMap::new(),
            cardinality: Cardinality::AtMostOne,
            input_typedesc_id: Uuid::from_u128(0xFF),
            output_typedesc_id: Uuid::from_u128(0x105),
        }),
        b"1\0\0\0'\0\0o\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\x01\x05"
    );
    encoding_eq!(
        ServerMessage::PrepareComplete(PrepareComplete {
            headers: HashMap::new(),
            cardinality: Cardinality::NoResult,
            input_typedesc_id: Uuid::from_u128(0xFF),
            output_typedesc_id: Uuid::from_u128(0x0),
        }),
        b"1\0\0\0'\0\0n\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"
    );
    Ok(())
}

#[test]
fn command_data_description0() -> Result<(), Box<dyn Error>> {
    encoding_eq_ver!(
        0,
        13,
        ServerMessage::CommandDataDescription0(CommandDataDescription0 {
            headers: HashMap::new(),
            result_cardinality: Cardinality::AtMostOne,
            input: RawTypedesc {
                proto: ProtocolVersion::new(0, 13),
                id: Uuid::from_u128(0xFF),
                data: Bytes::from_static(b"\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\0\0"),
            },
            output: RawTypedesc {
                proto: ProtocolVersion::new(0, 13),
                id: Uuid::from_u128(0x105),
                data: Bytes::from_static(b"\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\x05"),
            },
        }),
        bconcat!(b"T\0\0\0S\0\0o"
                     b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff"
                     b"\0\0\0\x13"
                     b"\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\0"
                     b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\x05"
                     b"\0\0\0\x11"
                     b"\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\x05")
    );
    encoding_eq_ver!(
        0,
        13,
        ServerMessage::CommandDataDescription0(CommandDataDescription0 {
            headers: HashMap::new(),
            result_cardinality: Cardinality::NoResult,
            input: RawTypedesc {
                proto: ProtocolVersion::new(0, 13),
                id: Uuid::from_u128(0xFF),
                data: Bytes::from_static(b"\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\0\0"),
            },
            output: RawTypedesc {
                proto: ProtocolVersion::new(0, 13),
                id: Uuid::from_u128(0),
                data: Bytes::from_static(b""),
            },
        }),
        bconcat!(b"T\0\0\0B\0\0n"
                     b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff"
                     b"\0\0\0\x13"
                     b"\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\0"
                     b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")
    );
    Ok(())
}

#[test]
fn command_data_description1() -> Result<(), Box<dyn Error>> {
    encoding_eq_ver!(
        1,
        0,
        ServerMessage::CommandDataDescription1(CommandDataDescription1 {
            annotations: HashMap::new(),
            capabilities: Capabilities::MODIFICATIONS,
            result_cardinality: Cardinality::AtMostOne,
            input: RawTypedesc {
                proto: ProtocolVersion::current(),
                id: Uuid::from_u128(0xFF),
                data: Bytes::from_static(b"\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\0\0"),
            },
            output: RawTypedesc {
                proto: ProtocolVersion::current(),
                id: Uuid::from_u128(0x105),
                data: Bytes::from_static(b"\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\x05"),
            },
        }),
        bconcat!(b"T\0\0\0[\0\0\0\0\0\0\0\0\0\x01o"
                     b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff"
                     b"\0\0\0\x13"
                     b"\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\0"
                     b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\x05"
                     b"\0\0\0\x11"
                     b"\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\x05")
    );
    encoding_eq_ver!(
        1,
        0,
        ServerMessage::CommandDataDescription1(CommandDataDescription1 {
            annotations: HashMap::new(),
            capabilities: Capabilities::MODIFICATIONS,
            result_cardinality: Cardinality::NoResult,
            input: RawTypedesc {
                proto: ProtocolVersion::current(),
                id: Uuid::from_u128(0xFF),
                data: Bytes::from_static(b"\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\0\0"),
            },
            output: RawTypedesc {
                proto: ProtocolVersion::current(),
                id: Uuid::from_u128(0),
                data: Bytes::from_static(b""),
            },
        }),
        bconcat!(b"T\0\0\0J\0\0\0\0\0\0\0\0\0\x01n"
                     b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff"
                     b"\0\0\0\x13"
                     b"\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\0"
                     b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")
    );
    Ok(())
}

#[test]
fn data() -> Result<(), Box<dyn Error>> {
    encoding_eq!(
        ServerMessage::Data(Data {
            data: vec![Bytes::from_static(b"\0\0\0\0\0\0\0\x01")],
        }),
        b"D\0\0\0\x12\0\x01\0\0\0\x08\0\0\0\0\0\0\0\x01"
    );
    Ok(())
}

#[test]
fn restore_ready() -> Result<(), Box<dyn Error>> {
    encoding_eq!(
        ServerMessage::RestoreReady(RestoreReady {
            jobs: 1,
            headers: HashMap::new(),
        }),
        b"+\0\0\0\x08\0\0\0\x01"
    );
    Ok(())
}

#[test]
fn authentication() -> Result<(), Box<dyn Error>> {
    encoding_eq!(
        ServerMessage::Authentication(Authentication::Ok),
        b"\x52\0\0\0\x08\x00\x00\x00\x00"
    );
    encoding_eq!(
        ServerMessage::Authentication(Authentication::Sasl {
            methods: vec![String::from("SCRAM-SHA-256")],
        }),
        b"R\0\0\0\x1d\0\0\0\n\0\0\0\x01\0\0\0\rSCRAM-SHA-256"
    );
    encoding_eq!(
        ServerMessage::Authentication(Authentication::SaslContinue {
            data: Bytes::from_static(b"sasl_interim_data"),
        }),
        b"R\0\0\0\x1d\x00\x00\x00\x0b\0\0\0\x11sasl_interim_data"
    );
    encoding_eq!(
        ServerMessage::Authentication(Authentication::SaslFinal {
            data: Bytes::from_static(b"sasl_final_data"),
        }),
        b"R\0\0\0\x1b\x00\x00\x00\x0c\0\0\0\x0fsasl_final_data"
    );
    Ok(())
}

#[test]
fn log_message() -> Result<(), Box<dyn Error>> {
    encoding_eq!(
        ServerMessage::LogMessage(LogMessage {
            severity: MessageSeverity::Notice,
            code: 0xF0_00_00_00,
            text: "changing system config".into(),
            attributes: map! {},
        }),
        b"L\0\0\0%<\xf0\0\0\0\0\0\0\x16changing system config\0\0"
    );
    Ok(())
}

#[test]
fn state_data_description() -> Result<(), Box<dyn Error>> {
    encoding_eq!(
        ServerMessage::StateDataDescription(StateDataDescription {
            typedesc: RawTypedesc {
                proto: ProtocolVersion::current(),
                id: Uuid::from_u128(0x105),
                data: Bytes::from_static(b"\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\x05"),
            },
        }),
        b"s\0\0\0)\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\x05\0\0\0\
        \x11\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\x05"
    );
    Ok(())
}