Rename NoticeResponse to Response; clean up tests

This commit is contained in:
Ryan Leckey 2019-06-21 22:47:15 -07:00
parent 3fa880f5f3
commit d3121c60cf
6 changed files with 263 additions and 297 deletions

View File

@ -3,24 +3,15 @@ extern crate criterion;
use bytes::Bytes;
use criterion::{black_box, Criterion};
use mason_postgres_protocol::{Decode, NoticeResponse};
use mason_postgres_protocol::{Decode, Response};
fn criterion_benchmark(c: &mut Criterion) {
// NOTE: This is sans header (for direct decoding)
const NOTICE_RESPONSE: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
c.bench_function("decode NoticeResponse", |b| {
c.bench_function("decode Response", |b| {
b.iter(|| {
let _ = NoticeResponse::decode(black_box(Bytes::from_static(NOTICE_RESPONSE))).unwrap();
})
});
c.bench_function("decode NoticeResponse and NoticeResponseFields", |b| {
b.iter(|| {
let _ = NoticeResponse::decode(black_box(Bytes::from_static(NOTICE_RESPONSE)))
.unwrap()
.fields()
.unwrap();
let _ = Response::decode(black_box(Bytes::from_static(NOTICE_RESPONSE))).unwrap();
})
});
}

View File

@ -1,28 +1,25 @@
#[macro_use]
extern crate criterion;
use criterion::{Criterion};
use mason_postgres_protocol::{Encode, NoticeResponse, Severity};
use criterion::Criterion;
use mason_postgres_protocol::{Encode, Response, Severity};
fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("encode NoticeResponse",
|b| {
let mut dst = Vec::new();
b.iter(|| {
let message = NoticeResponse::builder()
.severity(Severity::Notice)
.code("42710")
.message("extension \"uuid-ossp\" already exists, skipping")
.file("extension.c")
.line(1656)
.routine("CreateExtension")
.build();
dst.truncate(0);
message.encode(&mut dst).unwrap();
})
}
);
c.bench_function("encode Response(Builder)", |b| {
let mut dst = Vec::new();
b.iter(|| {
dst.truncate(0);
Response::builder()
.severity(Severity::Notice)
.code("42710")
.message("extension \"uuid-ossp\" already exists, skipping")
.file("extension.c")
.line(1656)
.routine("CreateExtension")
.encode(&mut dst)
.unwrap();
})
});
}
criterion_group!(benches, criterion_benchmark);

View File

@ -5,13 +5,13 @@ mod backend_key_data;
mod decode;
mod encode;
mod message;
mod notice_response;
mod ready_for_query;
mod response;
pub use self::{
decode::Decode,
encode::Encode,
message::Message,
notice_response::{NoticeResponse, NoticeResponseBuilder, NoticeResponseFields, Severity},
ready_for_query::{ReadyForQuery, TransactionStatus},
response::{Response, ResponseBuilder, Severity},
};

View File

@ -1,4 +1,4 @@
use crate::{Decode, Encode, NoticeResponse, ReadyForQuery};
use crate::{Decode, Encode, ReadyForQuery, Response};
use byteorder::{BigEndian, ReadBytesExt};
use bytes::Bytes;
use std::io::{self, Cursor};
@ -6,21 +6,21 @@ use std::io::{self, Cursor};
#[derive(Debug)]
pub enum Message {
ReadyForQuery(ReadyForQuery),
NoticeResponse(NoticeResponse),
Response(Response),
}
impl Encode for Message {
fn size_hint(&self) -> usize {
match self {
Message::ReadyForQuery(body) => body.size_hint(),
Message::NoticeResponse(body) => body.size_hint(),
Message::Response(body) => body.size_hint(),
}
}
fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> {
match self {
Message::ReadyForQuery(body) => body.encode(buf),
Message::NoticeResponse(body) => body.encode(buf),
Message::Response(body) => body.encode(buf),
}
}
}
@ -41,7 +41,7 @@ impl Decode for Message {
Ok(match token {
// FIXME: These tokens are duplicated here and in the respective encode functions
b'N' => Message::NoticeResponse(NoticeResponse::decode(src)?),
b'N' | b'E' => Message::Response(Response::decode(src)?),
b'Z' => Message::ReadyForQuery(ReadyForQuery::decode(src)?),
_ => unimplemented!("decode not implemented for token: {}", token as char),

View File

@ -59,32 +59,30 @@ impl Decode for ReadyForQuery {
#[cfg(test)]
mod tests {
use super::{ReadyForQuery, TransactionStatus};
use crate::{Decode, Encode, Message};
use crate::{Decode, Encode};
use bytes::Bytes;
use std::io;
const READY_FOR_QUERY: &[u8] = b"E";
#[test]
fn it_encodes_ready_for_query() -> io::Result<()> {
let message = ReadyForQuery { status: TransactionStatus::Error };
assert_eq!(&*message.to_bytes()?, &b"Z\0\0\0\x05E"[..]);
let mut dst = Vec::with_capacity(message.size_hint());
message.encode(&mut dst)?;
assert_eq!(&dst[5..], READY_FOR_QUERY);
Ok(())
}
#[test]
fn it_decodes_ready_for_query() -> io::Result<()> {
// FIXME: A test-utils type thing could be useful here as these 7 lines are quite..
// duplicated
let src = Bytes::from_static(READY_FOR_QUERY);
let message = ReadyForQuery::decode(src)?;
let b = Bytes::from_static(b"Z\0\0\0\x05E");
let message = Message::decode(b)?;
let body = if let Message::ReadyForQuery(body) = message {
body
} else {
unreachable!();
};
assert_eq!(body.status, TransactionStatus::Error);
assert_eq!(message.status, TransactionStatus::Error);
Ok(())
}

View File

@ -2,14 +2,13 @@ use crate::{decode::get_str, Decode, Encode};
use byteorder::{BigEndian, WriteBytesExt};
use bytes::Bytes;
use std::{
borrow::Cow,
fmt,
io::{self, Write},
ops::Range,
pin::Pin,
ptr::NonNull,
str::{self, FromStr},
};
use core::fmt::Debug;
#[derive(Debug, PartialEq, PartialOrd, Copy, Clone)]
pub enum Severity {
@ -24,6 +23,25 @@ pub enum Severity {
}
impl Severity {
pub fn is_error(&self) -> bool {
match self {
Severity::Panic | Severity::Fatal | Severity::Error => true,
_ => false,
}
}
pub fn is_notice(&self) -> bool {
match self {
Severity::Warning
| Severity::Notice
| Severity::Debug
| Severity::Info
| Severity::Log => true,
_ => false,
}
}
pub fn to_str(&self) -> &'static str {
match self {
Severity::Panic => "PANIC",
@ -59,57 +77,8 @@ impl FromStr for Severity {
}
}
// NOTE: `NoticeResponse` is lazily decoded on access to `.fields()`
#[derive(Clone)]
pub struct NoticeResponse(Bytes);
impl NoticeResponse {
#[inline]
pub fn builder() -> NoticeResponseBuilder<'static> {
NoticeResponseBuilder::new()
}
#[inline]
pub fn fields(self) -> io::Result<NoticeResponseFields> {
NoticeResponseFields::decode(self.0)
}
}
impl fmt::Debug for NoticeResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Proxy format to the results of decoding the fields
self.clone().fields().fmt(f)
}
}
impl Encode for NoticeResponse {
#[inline]
fn size_hint(&self) -> usize {
self.0.len() + 5
}
#[inline]
fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> {
buf.write_u8(b'Z')?;
buf.write_u32::<BigEndian>((4 + self.0.len()) as u32)?;
buf.write_all(&self.0)?;
Ok(())
}
}
impl Decode for NoticeResponse {
#[inline]
fn decode(src: Bytes) -> io::Result<Self>
where
Self: Sized,
{
// NOTE: Further decoding is delayed until `.fields()`
Ok(Self(src))
}
}
pub struct NoticeResponseFields {
pub struct Response {
#[used]
storage: Pin<Bytes>,
severity: Severity,
@ -132,10 +101,15 @@ pub struct NoticeResponseFields {
}
// SAFE: Raw pointers point to pinned memory inside the struct
unsafe impl Send for NoticeResponseFields {}
unsafe impl Sync for NoticeResponseFields {}
unsafe impl Send for Response {}
unsafe impl Sync for Response {}
impl Response {
#[inline]
pub fn builder() -> ResponseBuilder {
ResponseBuilder::new()
}
impl NoticeResponseFields {
#[inline]
pub fn severity(&self) -> Severity {
self.severity
@ -235,9 +209,9 @@ impl NoticeResponseFields {
}
}
impl fmt::Debug for NoticeResponseFields {
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("NoticeResponseFields")
f.debug_struct("Response")
.field("severity", &self.severity)
.field("code", &self.code())
.field("message", &self.message())
@ -259,7 +233,27 @@ impl fmt::Debug for NoticeResponseFields {
}
}
impl Decode for NoticeResponseFields {
impl Encode for Response {
#[inline]
fn size_hint(&self) -> usize {
self.storage.len() + 5
}
fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> {
if self.severity.is_error() {
buf.push(b'E');
} else {
buf.push(b'N');
}
buf.write_u32::<BigEndian>((4 + self.storage.len()) as u32)?;
buf.write_all(&self.storage)?;
Ok(())
}
}
impl Decode for Response {
fn decode(src: Bytes) -> io::Result<Self> {
let storage = Pin::new(src);
@ -371,7 +365,7 @@ impl Decode for NoticeResponseFields {
_ => {
unimplemented!(
"error/notice message field {:?} not implemented",
"response message field {:?} not implemented",
field_type as char
);
}
@ -380,10 +374,10 @@ impl Decode for NoticeResponseFields {
let severity = severity_non_local
.or_else(move || severity?.parse().ok())
.expect("required by protocol");
.expect("`severity` required by protocol");
let code = NonNull::from(code.expect("required by protocol"));
let message = NonNull::from(message.expect("required by protocol"));
let code = NonNull::from(code.expect("`code` required by protocol"));
let message = NonNull::from(message.expect("`message` required by protocol"));
let detail = detail.map(NonNull::from);
let hint = hint.map(NonNull::from);
let internal_query = internal_query.map(NonNull::from);
@ -419,32 +413,35 @@ impl Decode for NoticeResponseFields {
}
}
pub struct NoticeResponseBuilder<'a> {
severity: Severity,
code: Cow<'a, str>,
message: Cow<'a, str>,
detail: Option<Cow<'a, str>>,
hint: Option<Cow<'a, str>>,
pub struct ResponseBuilder {
storage: Vec<u8>,
severity: Option<Severity>,
code: Option<Range<usize>>,
message: Option<Range<usize>>,
detail: Option<Range<usize>>,
hint: Option<Range<usize>>,
position: Option<usize>,
internal_position: Option<usize>,
internal_query: Option<Cow<'a, str>>,
where_: Option<Cow<'a, str>>,
schema: Option<Cow<'a, str>>,
table: Option<Cow<'a, str>>,
column: Option<Cow<'a, str>>,
data_type: Option<Cow<'a, str>>,
constraint: Option<Cow<'a, str>>,
file: Option<Cow<'a, str>>,
internal_query: Option<Range<usize>>,
where_: Option<Range<usize>>,
schema: Option<Range<usize>>,
table: Option<Range<usize>>,
column: Option<Range<usize>>,
data_type: Option<Range<usize>>,
constraint: Option<Range<usize>>,
file: Option<Range<usize>>,
line: Option<usize>,
routine: Option<Cow<'a, str>>,
routine: Option<Range<usize>>,
}
impl Default for NoticeResponseBuilder<'_> {
impl Default for ResponseBuilder {
fn default() -> Self {
Self {
severity: Severity::Notice,
message: Cow::Borrowed(""),
code: Cow::Borrowed("XX000"), // internal_error
// FIXME: Remove this allocation (on the quest for zero-allocation)
storage: Vec::with_capacity(128),
severity: None,
message: None,
code: None,
detail: None,
hint: None,
position: None,
@ -463,240 +460,207 @@ impl Default for NoticeResponseBuilder<'_> {
}
}
impl<'a> NoticeResponseBuilder<'a> {
fn put_str(buf: &mut Vec<u8>, tag: u8, value: impl AsRef<str>) -> Range<usize> {
buf.push(tag);
let beg = buf.len();
buf.extend_from_slice(value.as_ref().as_bytes());
let end = buf.len();
buf.push(0);
beg..end
}
impl ResponseBuilder {
#[inline]
pub fn new() -> NoticeResponseBuilder<'a> {
pub fn new() -> ResponseBuilder {
Self::default()
}
#[inline]
pub fn severity(&mut self, severity: Severity) -> &mut Self {
self.severity = severity;
pub fn severity(mut self, severity: Severity) -> Self {
let sev = severity.to_str();
let _ = put_str(&mut self.storage, b'S', sev);
let _ = put_str(&mut self.storage, b'V', sev);
self.severity = Some(severity);
self
}
#[inline]
pub fn message(&mut self, message: impl Into<Cow<'a, str>>) -> &mut Self {
self.message = message.into();
pub fn message(mut self, message: impl AsRef<str>) -> Self {
self.message = Some(put_str(&mut self.storage, b'M', message));
self
}
#[inline]
pub fn code(&mut self, code: impl Into<Cow<'a, str>>) -> &mut Self {
self.code = code.into();
pub fn code(mut self, code: impl AsRef<str>) -> Self {
self.code = Some(put_str(&mut self.storage, b'C', code));
self
}
#[inline]
pub fn detail(&mut self, detail: impl Into<Cow<'a, str>>) -> &mut Self {
self.detail = Some(detail.into());
pub fn detail(mut self, detail: impl AsRef<str>) -> Self {
self.detail = Some(put_str(&mut self.storage, b'D', detail));
self
}
#[inline]
pub fn hint(&mut self, hint: impl Into<Cow<'a, str>>) -> &mut Self {
self.hint = Some(hint.into());
pub fn hint(mut self, hint: impl AsRef<str>) -> Self {
self.hint = Some(put_str(&mut self.storage, b'H', hint));
self
}
#[inline]
pub fn position(&mut self, position: usize) -> &mut Self {
pub fn position(mut self, position: usize) -> Self {
self.storage.push(b'P');
// PANIC: Write to Vec<u8> is infallible
itoa::write(&mut self.storage, position).unwrap();
self.storage.push(0);
self.position = Some(position);
self
}
#[inline]
pub fn internal_position(&mut self, position: usize) -> &mut Self {
pub fn internal_position(mut self, position: usize) -> Self {
self.storage.push(b'p');
// PANIC: Write to Vec<u8> is infallible
itoa::write(&mut self.storage, position).unwrap();
self.storage.push(0);
self.internal_position = Some(position);
self
}
#[inline]
pub fn internal_query(&mut self, query: impl Into<Cow<'a, str>>) -> &mut Self {
self.internal_query = Some(query.into());
pub fn internal_query(mut self, query: impl AsRef<str>) -> Self {
self.internal_query = Some(put_str(&mut self.storage, b'q', query));
self
}
#[inline]
pub fn where_(&mut self, where_: impl Into<Cow<'a, str>>) -> &mut Self {
self.where_ = Some(where_.into());
pub fn where_(mut self, where_: impl AsRef<str>) -> Self {
self.where_ = Some(put_str(&mut self.storage, b'w', where_));
self
}
#[inline]
pub fn schema(&mut self, schema: impl Into<Cow<'a, str>>) -> &mut Self {
self.schema = Some(schema.into());
pub fn schema(mut self, schema: impl AsRef<str>) -> Self {
self.schema = Some(put_str(&mut self.storage, b's', schema));
self
}
#[inline]
pub fn table(&mut self, table: impl Into<Cow<'a, str>>) -> &mut Self {
self.table = Some(table.into());
pub fn table(mut self, table: impl AsRef<str>) -> Self {
self.table = Some(put_str(&mut self.storage, b't', table));
self
}
#[inline]
pub fn column(&mut self, column: impl Into<Cow<'a, str>>) -> &mut Self {
self.column = Some(column.into());
pub fn column(mut self, column: impl AsRef<str>) -> Self {
self.column = Some(put_str(&mut self.storage, b'c', column));
self
}
#[inline]
pub fn data_type(&mut self, data_type: impl Into<Cow<'a, str>>) -> &mut Self {
self.data_type = Some(data_type.into());
pub fn data_type(mut self, data_type: impl AsRef<str>) -> Self {
self.data_type = Some(put_str(&mut self.storage, b'd', data_type));
self
}
#[inline]
pub fn constraint(&mut self, constraint: impl Into<Cow<'a, str>>) -> &mut Self {
self.constraint = Some(constraint.into());
pub fn constraint(mut self, constraint: impl AsRef<str>) -> Self {
self.constraint = Some(put_str(&mut self.storage, b'n', constraint));
self
}
#[inline]
pub fn file(&mut self, file: impl Into<Cow<'a, str>>) -> &mut Self {
self.file = Some(file.into());
pub fn file(mut self, file: impl AsRef<str>) -> Self {
self.file = Some(put_str(&mut self.storage, b'F', file));
self
}
#[inline]
pub fn line(&mut self, line: usize) -> &mut Self {
pub fn line(mut self, line: usize) -> Self {
self.storage.push(b'L');
// PANIC: Write to Vec<u8> is infallible
itoa::write(&mut self.storage, line).unwrap();
self.storage.push(0);
self.line = Some(line);
self
}
#[inline]
pub fn routine(&mut self, routine: impl Into<Cow<'a, str>>) -> &mut Self {
self.routine = Some(routine.into());
pub fn routine(mut self, routine: impl AsRef<str>) -> Self {
self.routine = Some(put_str(&mut self.storage, b'R', routine));
self
}
#[inline]
pub fn build(&self) -> NoticeResponse {
let mut buf = Vec::new();
pub fn build(mut self) -> Response {
// Add a \0 terminator
self.storage.push(0);
// FIXME: Should Encode even be fallible?
// PANIC: Cannot fail
self.encode(&mut buf).unwrap();
// Freeze the storage and Pin so we can self-reference it
let storage = Pin::new(Bytes::from(self.storage));
NoticeResponse(Bytes::from(buf))
let make_str_ref = |val: Option<Range<usize>>| unsafe {
val.map(|r| NonNull::from(str::from_utf8_unchecked(&storage[r])))
};
let code = make_str_ref(self.code);
let message = make_str_ref(self.message);
let detail = make_str_ref(self.detail);
let hint = make_str_ref(self.hint);
let internal_query = make_str_ref(self.internal_query);
let where_ = make_str_ref(self.where_);
let schema = make_str_ref(self.schema);
let table = make_str_ref(self.table);
let column = make_str_ref(self.column);
let data_type = make_str_ref(self.data_type);
let constraint = make_str_ref(self.constraint);
let file = make_str_ref(self.file);
let routine = make_str_ref(self.routine);
Response {
storage,
// FIXME: Default and don't panic here
severity: self.severity.expect("`severity` required by protocol"),
code: code.expect("`code` required by protocol"),
message: message.expect("`message` required by protocol"),
detail,
hint,
internal_query,
where_,
schema,
table,
column,
data_type,
constraint,
file,
routine,
line: self.line,
position: self.position,
internal_position: self.internal_position,
}
}
}
impl<'a> Encode for NoticeResponseBuilder<'a> {
impl Encode for ResponseBuilder {
#[inline]
fn size_hint(&self) -> usize {
// Too variable to measure efficiently
0
self.storage.len() + 6
}
fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> {
// Severity and Localized Severity (required)
let sev = self.severity.to_str().as_bytes();
buf.push(b'S');
buf.write_all(sev)?;
buf.push(0);
buf.push(b'V');
buf.write_all(sev)?;
buf.push(0);
// Code (required)
buf.push(b'C');
buf.write_all(self.code.as_bytes())?;
buf.push(0);
// Message (required)
buf.push(b'M');
buf.write_all(self.message.as_bytes())?;
buf.push(0);
// All remaining fields are optional and
// should be encoded if present
if let Some(detail) = &self.detail {
buf.push(b'D');
buf.write_all(detail.as_bytes())?;
buf.push(0);
if self.severity.as_ref().map_or(false, |s| s.is_error()) {
buf.push(b'E');
} else {
buf.push(b'N');
}
if let Some(hint) = &self.hint {
buf.push(b'H');
buf.write_all(hint.as_bytes())?;
buf.push(0);
}
if let Some(position) = &self.position {
buf.push(b'P');
itoa::write(&mut *buf, *position)?;
buf.push(0);
}
if let Some(internal_position) = &self.internal_position {
buf.push(b'p');
itoa::write(&mut *buf, *internal_position)?;
buf.push(0);
}
if let Some(internal_query) = &self.internal_query {
buf.push(b'q');
buf.write_all(internal_query.as_bytes())?;
buf.push(0);
}
if let Some(where_) = &self.where_ {
buf.push(b'w');
buf.write_all(where_.as_bytes())?;
buf.push(0);
}
if let Some(schema) = &self.schema {
buf.push(b's');
buf.write_all(schema.as_bytes())?;
buf.push(0);
}
if let Some(table) = &self.table {
buf.push(b't');
buf.write_all(table.as_bytes())?;
buf.push(0);
}
if let Some(column) = &self.column {
buf.push(b'c');
buf.write_all(column.as_bytes())?;
buf.push(0);
}
if let Some(data_type) = &self.data_type {
buf.push(b'd');
buf.write_all(data_type.as_bytes())?;
buf.push(0);
}
if let Some(constraint) = &self.constraint {
buf.push(b'n');
buf.write_all(constraint.as_bytes())?;
buf.push(0);
}
if let Some(file) = &self.file {
buf.push(b'F');
buf.write_all(file.as_bytes())?;
buf.push(0);
}
if let Some(line) = &self.line {
buf.push(b'L');
itoa::write(&mut *buf, *line)?;
buf.push(0);
}
if let Some(routine) = &self.routine {
buf.push(b'R');
buf.write_all(routine.as_bytes())?;
buf.push(0);
}
// After the final field, there is a nul terminator
buf.write_u32::<BigEndian>((5 + self.storage.len()) as u32)?;
buf.write_all(&self.storage)?;
buf.push(0);
Ok(())
@ -705,18 +669,17 @@ impl<'a> Encode for NoticeResponseBuilder<'a> {
#[cfg(test)]
mod tests {
use super::{NoticeResponse, Severity};
use super::{Response, Severity};
use crate::{Decode, Encode};
use bytes::Bytes;
use std::io;
const NOTICE_RESPONSE: &[u8] =
b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, \
const RESPONSE: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, \
skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
#[test]
fn it_encodes_notice_response() -> io::Result<()> {
let message = NoticeResponse::builder()
fn it_encodes_response() -> io::Result<()> {
let message = Response::builder()
.severity(Severity::Notice)
.code("42710")
.message("extension \"uuid-ossp\" already exists, skipping")
@ -728,23 +691,40 @@ mod tests {
let mut dst = Vec::with_capacity(message.size_hint());
message.encode(&mut dst)?;
assert_eq!(&dst[5..], NOTICE_RESPONSE);
assert_eq!(&dst[5..], RESPONSE);
Ok(())
}
#[test]
fn it_decodes_notice_response() -> io::Result<()> {
let src = Bytes::from_static(NOTICE_RESPONSE);
let message = NoticeResponse::decode(src)?;
let fields = message.fields()?;
fn it_encodes_response_builder() -> io::Result<()> {
let message = Response::builder()
.severity(Severity::Notice)
.code("42710")
.message("extension \"uuid-ossp\" already exists, skipping")
.file("extension.c")
.line(1656)
.routine("CreateExtension");
assert_eq!(fields.severity(), Severity::Notice);
assert_eq!(fields.message(), "extension \"uuid-ossp\" already exists, skipping");
assert_eq!(fields.code(), "42710");
assert_eq!(fields.file(), Some("extension.c"));
assert_eq!(fields.line(), Some(1656));
assert_eq!(fields.routine(), Some("CreateExtension"));
let mut dst = Vec::with_capacity(message.size_hint());
message.encode(&mut dst)?;
assert_eq!(&dst[5..], RESPONSE);
Ok(())
}
#[test]
fn it_decodes_response() -> io::Result<()> {
let src = Bytes::from_static(RESPONSE);
let message = Response::decode(src)?;
assert_eq!(message.severity(), Severity::Notice);
assert_eq!(message.message(), "extension \"uuid-ossp\" already exists, skipping");
assert_eq!(message.code(), "42710");
assert_eq!(message.file(), Some("extension.c"));
assert_eq!(message.line(), Some(1656));
assert_eq!(message.routine(), Some("CreateExtension"));
Ok(())
}