From d3121c60cfa77010d3fa29e09ec09acb92ca688c Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Fri, 21 Jun 2019 22:47:15 -0700 Subject: [PATCH] Rename NoticeResponse to Response; clean up tests --- mason-postgres-protocol/benches/decode.rs | 15 +- mason-postgres-protocol/benches/encode.rs | 37 +- mason-postgres-protocol/src/lib.rs | 4 +- mason-postgres-protocol/src/message.rs | 10 +- .../src/ready_for_query.rs | 24 +- .../src/{notice_response.rs => response.rs} | 470 +++++++++--------- 6 files changed, 263 insertions(+), 297 deletions(-) rename mason-postgres-protocol/src/{notice_response.rs => response.rs} (58%) diff --git a/mason-postgres-protocol/benches/decode.rs b/mason-postgres-protocol/benches/decode.rs index a89cdd18..a0ef3635 100644 --- a/mason-postgres-protocol/benches/decode.rs +++ b/mason-postgres-protocol/benches/decode.rs @@ -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(); }) }); } diff --git a/mason-postgres-protocol/benches/encode.rs b/mason-postgres-protocol/benches/encode.rs index 86fb0a26..0364cf2a 100644 --- a/mason-postgres-protocol/benches/encode.rs +++ b/mason-postgres-protocol/benches/encode.rs @@ -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); diff --git a/mason-postgres-protocol/src/lib.rs b/mason-postgres-protocol/src/lib.rs index 06e82ef8..de0a3043 100644 --- a/mason-postgres-protocol/src/lib.rs +++ b/mason-postgres-protocol/src/lib.rs @@ -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}, }; diff --git a/mason-postgres-protocol/src/message.rs b/mason-postgres-protocol/src/message.rs index 5cbec16c..54dc51d5 100644 --- a/mason-postgres-protocol/src/message.rs +++ b/mason-postgres-protocol/src/message.rs @@ -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) -> 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), diff --git a/mason-postgres-protocol/src/ready_for_query.rs b/mason-postgres-protocol/src/ready_for_query.rs index 9295c617..1da1430e 100644 --- a/mason-postgres-protocol/src/ready_for_query.rs +++ b/mason-postgres-protocol/src/ready_for_query.rs @@ -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(()) } diff --git a/mason-postgres-protocol/src/notice_response.rs b/mason-postgres-protocol/src/response.rs similarity index 58% rename from mason-postgres-protocol/src/notice_response.rs rename to mason-postgres-protocol/src/response.rs index c1e514a4..a8571c73 100644 --- a/mason-postgres-protocol/src/notice_response.rs +++ b/mason-postgres-protocol/src/response.rs @@ -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::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) -> io::Result<()> { - buf.write_u8(b'Z')?; - buf.write_u32::((4 + self.0.len()) as u32)?; - buf.write_all(&self.0)?; - - Ok(()) - } -} - -impl Decode for NoticeResponse { - #[inline] - fn decode(src: Bytes) -> io::Result - where - Self: Sized, - { - // NOTE: Further decoding is delayed until `.fields()` - Ok(Self(src)) - } -} - -pub struct NoticeResponseFields { +pub struct Response { #[used] storage: Pin, 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) -> io::Result<()> { + if self.severity.is_error() { + buf.push(b'E'); + } else { + buf.push(b'N'); + } + + buf.write_u32::((4 + self.storage.len()) as u32)?; + buf.write_all(&self.storage)?; + + Ok(()) + } +} + +impl Decode for Response { fn decode(src: Bytes) -> io::Result { 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>, - hint: Option>, +pub struct ResponseBuilder { + storage: Vec, + severity: Option, + code: Option>, + message: Option>, + detail: Option>, + hint: Option>, position: Option, internal_position: Option, - internal_query: Option>, - where_: Option>, - schema: Option>, - table: Option>, - column: Option>, - data_type: Option>, - constraint: Option>, - file: Option>, + internal_query: Option>, + where_: Option>, + schema: Option>, + table: Option>, + column: Option>, + data_type: Option>, + constraint: Option>, + file: Option>, line: Option, - routine: Option>, + routine: Option>, } -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, tag: u8, value: impl AsRef) -> Range { + 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>) -> &mut Self { - self.message = message.into(); + pub fn message(mut self, message: impl AsRef) -> Self { + self.message = Some(put_str(&mut self.storage, b'M', message)); self } #[inline] - pub fn code(&mut self, code: impl Into>) -> &mut Self { - self.code = code.into(); + pub fn code(mut self, code: impl AsRef) -> Self { + self.code = Some(put_str(&mut self.storage, b'C', code)); self } #[inline] - pub fn detail(&mut self, detail: impl Into>) -> &mut Self { - self.detail = Some(detail.into()); + pub fn detail(mut self, detail: impl AsRef) -> Self { + self.detail = Some(put_str(&mut self.storage, b'D', detail)); self } #[inline] - pub fn hint(&mut self, hint: impl Into>) -> &mut Self { - self.hint = Some(hint.into()); + pub fn hint(mut self, hint: impl AsRef) -> 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 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 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>) -> &mut Self { - self.internal_query = Some(query.into()); + pub fn internal_query(mut self, query: impl AsRef) -> Self { + self.internal_query = Some(put_str(&mut self.storage, b'q', query)); self } #[inline] - pub fn where_(&mut self, where_: impl Into>) -> &mut Self { - self.where_ = Some(where_.into()); + pub fn where_(mut self, where_: impl AsRef) -> Self { + self.where_ = Some(put_str(&mut self.storage, b'w', where_)); self } #[inline] - pub fn schema(&mut self, schema: impl Into>) -> &mut Self { - self.schema = Some(schema.into()); + pub fn schema(mut self, schema: impl AsRef) -> Self { + self.schema = Some(put_str(&mut self.storage, b's', schema)); self } #[inline] - pub fn table(&mut self, table: impl Into>) -> &mut Self { - self.table = Some(table.into()); + pub fn table(mut self, table: impl AsRef) -> Self { + self.table = Some(put_str(&mut self.storage, b't', table)); self } #[inline] - pub fn column(&mut self, column: impl Into>) -> &mut Self { - self.column = Some(column.into()); + pub fn column(mut self, column: impl AsRef) -> Self { + self.column = Some(put_str(&mut self.storage, b'c', column)); self } #[inline] - pub fn data_type(&mut self, data_type: impl Into>) -> &mut Self { - self.data_type = Some(data_type.into()); + pub fn data_type(mut self, data_type: impl AsRef) -> Self { + self.data_type = Some(put_str(&mut self.storage, b'd', data_type)); self } #[inline] - pub fn constraint(&mut self, constraint: impl Into>) -> &mut Self { - self.constraint = Some(constraint.into()); + pub fn constraint(mut self, constraint: impl AsRef) -> Self { + self.constraint = Some(put_str(&mut self.storage, b'n', constraint)); self } #[inline] - pub fn file(&mut self, file: impl Into>) -> &mut Self { - self.file = Some(file.into()); + pub fn file(mut self, file: impl AsRef) -> 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 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>) -> &mut Self { - self.routine = Some(routine.into()); + pub fn routine(mut self, routine: impl AsRef) -> 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>| 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) -> 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::((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(()) }