Skip to content

Commit 791faf4

Browse files
committed
chore: Unify connected and error response formats
1 parent a772843 commit 791faf4

File tree

7 files changed

+137
-97
lines changed

7 files changed

+137
-97
lines changed

contract-tests/src/bin/sse-test-api/main.rs

+5-14
Original file line numberDiff line numberDiff line change
@@ -50,25 +50,16 @@ struct Config {
5050
#[derive(Serialize, Debug)]
5151
#[serde(tag = "kind", rename_all = "camelCase")]
5252
enum EventType {
53-
Connected {
54-
status: u16,
55-
headers: HashMap<String, String>,
56-
},
57-
Event {
58-
event: Event,
59-
},
60-
Comment {
61-
comment: String,
62-
},
63-
Error {
64-
error: String,
65-
},
53+
Connected {},
54+
Event { event: Event },
55+
Comment { comment: String },
56+
Error { error: String },
6657
}
6758

6859
impl From<es::SSE> for EventType {
6960
fn from(event: es::SSE) -> Self {
7061
match event {
71-
es::SSE::Connected((status, headers)) => Self::Connected { status, headers },
62+
es::SSE::Connected(_) => Self::Connected {},
7263
es::SSE::Event(evt) => Self::Event {
7364
event: Event {
7465
event_type: evt.event_type,

eventsource-client/examples/tail.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,15 @@ fn tail_events(client: impl es::Client) -> impl Stream<Item = Result<(), ()>> {
4040
client
4141
.stream()
4242
.map_ok(|event| match event {
43-
es::SSE::Connected((status, _)) => {
44-
println!("got connected: \nstatus={}", status)
43+
es::SSE::Connected(connection) => {
44+
println!("got connected: \nstatus={}", connection.response().status())
4545
}
4646
es::SSE::Event(ev) => {
4747
println!("got an event: {}\n{}", ev.event_type, ev.data)
4848
}
4949
es::SSE::Comment(comment) => {
5050
println!("got a comment: \n{}", comment)
5151
}
52-
es::SSE::Connected(headers) => {
53-
println!("got a connection start with headers: \n{:?}", headers)
54-
}
5552
})
5653
.map_err(|err| eprintln!("error streaming events: {:?}", err))
5754
}

eventsource-client/src/client.rs

+18-20
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@ use hyper::{
77
},
88
header::{HeaderMap, HeaderName, HeaderValue},
99
service::Service,
10-
Body, Request, StatusCode, Uri,
10+
Body, Request, Uri,
1111
};
1212
use log::{debug, info, trace, warn};
1313
use pin_project::pin_project;
1414
use std::{
1515
boxed,
16-
collections::HashMap,
17-
fmt::{self, Debug, Display, Formatter},
16+
fmt::{self, Debug, Formatter},
1817
future::Future,
1918
io::ErrorKind,
2019
pin::Pin,
@@ -28,8 +27,14 @@ use tokio::{
2827
time::Sleep,
2928
};
3029

31-
use crate::error::{Error, Result};
32-
use crate::{config::ReconnectOptions, ResponseWrapper};
30+
use crate::{
31+
config::ReconnectOptions,
32+
response::{ErrorBody, Response},
33+
};
34+
use crate::{
35+
error::{Error, Result},
36+
event_parser::ConnectionDetails,
37+
};
3338

3439
use hyper::client::HttpConnector;
3540
use hyper_timeout::TimeoutConnector;
@@ -396,7 +401,7 @@ where
396401
return match event {
397402
SSE::Connected(_) => Poll::Ready(Some(Ok(event))),
398403
SSE::Event(ref evt) => {
399-
*this.last_event_id = evt.id.clone();
404+
this.last_event_id.clone_from(&evt.id);
400405

401406
if let Some(retry) = evt.retry {
402407
this.retry_strategy
@@ -405,7 +410,6 @@ where
405410
Poll::Ready(Some(Ok(event)))
406411
}
407412
SSE::Comment(_) => Poll::Ready(Some(Ok(event))),
408-
SSE::Connected(_) => Poll::Ready(Some(Ok(event))),
409413
};
410414
}
411415

@@ -442,24 +446,17 @@ where
442446
self.as_mut().project().retry_strategy.reset(Instant::now());
443447
self.as_mut().reset_redirects();
444448

445-
let headers = resp.headers();
446-
let mut map = HashMap::new();
447-
for (key, value) in headers.iter() {
448-
let key = key.to_string();
449-
let value = match value.to_str() {
450-
Ok(value) => value.to_string(),
451-
Err(_) => String::from(""),
452-
};
453-
map.insert(key, value);
454-
}
455-
let status = resp.status().as_u16();
449+
let status = resp.status();
450+
let headers = resp.headers().clone();
456451

457452
self.as_mut()
458453
.project()
459454
.state
460455
.set(State::Connected(resp.into_body()));
461456

462-
return Poll::Ready(Some(Ok(SSE::Connected((status, map)))));
457+
return Poll::Ready(Some(Ok(SSE::Connected(ConnectionDetails::new(
458+
Response::new(status, headers),
459+
)))));
463460
}
464461

465462
if resp.status() == 301 || resp.status() == 307 {
@@ -486,7 +483,8 @@ where
486483
self.as_mut().project().state.set(State::New);
487484

488485
return Poll::Ready(Some(Err(Error::UnexpectedResponse(
489-
ResponseWrapper::new(resp),
486+
Response::new(resp.status(), resp.headers().clone()),
487+
ErrorBody::new(resp.into_body()),
490488
))));
491489
}
492490
Err(e) => {

eventsource-client/src/error.rs

+4-51
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,6 @@
1-
use std::collections::HashMap;
1+
use crate::response::{ErrorBody, Response};
22

3-
use hyper::{body::Buf, Body, Response};
4-
5-
pub struct ResponseWrapper {
6-
response: Response<Body>,
7-
}
8-
9-
impl ResponseWrapper {
10-
pub fn new(response: Response<Body>) -> Self {
11-
Self { response }
12-
}
13-
pub fn status(&self) -> u16 {
14-
self.response.status().as_u16()
15-
}
16-
pub fn headers(&self) -> std::result::Result<HashMap<&str, &str>, HeaderError> {
17-
let headers = self.response.headers();
18-
let mut map = HashMap::new();
19-
for (key, value) in headers.iter() {
20-
let key = key.as_str();
21-
let value = match value.to_str() {
22-
Ok(value) => value,
23-
Err(err) => return Err(HeaderError::new(Box::new(err))),
24-
};
25-
map.insert(key, value);
26-
}
27-
Ok(map)
28-
}
29-
30-
pub async fn body_bytes(self) -> Result<Vec<u8>> {
31-
let body = self.response.into_body();
32-
33-
let buf = match hyper::body::aggregate(body).await {
34-
Ok(buf) => buf,
35-
Err(err) => return Err(Error::HttpStream(Box::new(err))),
36-
};
37-
38-
Ok(buf.chunk().to_vec())
39-
}
40-
}
41-
42-
impl std::fmt::Debug for ResponseWrapper {
43-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44-
f.debug_struct("ResponseWrapper")
45-
.field("status", &self.status())
46-
.finish()
47-
}
48-
}
49-
50-
/// Error type for invalid response headers encountered in ResponseWrapper.
3+
/// Error type for invalid response headers encountered in ResponseDetails.
514
#[derive(Debug)]
525
pub struct HeaderError {
536
/// Wrapped inner error providing details about the header issue.
@@ -81,7 +34,7 @@ pub enum Error {
8134
/// An invalid request parameter
8235
InvalidParameter(Box<dyn std::error::Error + Send + Sync + 'static>),
8336
/// The HTTP response could not be handled.
84-
UnexpectedResponse(ResponseWrapper),
37+
UnexpectedResponse(Response, ErrorBody),
8538
/// An error reading from the HTTP response body.
8639
HttpStream(Box<dyn std::error::Error + Send + Sync + 'static>),
8740
/// The HTTP response stream ended
@@ -105,7 +58,7 @@ impl std::fmt::Display for Error {
10558
TimedOut => write!(f, "timed out"),
10659
StreamClosed => write!(f, "stream closed"),
10760
InvalidParameter(err) => write!(f, "invalid parameter: {err}"),
108-
UnexpectedResponse(r) => {
61+
UnexpectedResponse(r, _) => {
10962
let status = r.status();
11063
write!(f, "unexpected response: {status}")
11164
}

eventsource-client/src/event_parser.rs

+21-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
use std::{
2-
collections::{HashMap, VecDeque},
3-
convert::TryFrom,
4-
str::from_utf8,
5-
};
1+
use std::{collections::VecDeque, convert::TryFrom, str::from_utf8};
62

73
use hyper::body::Bytes;
84
use log::{debug, log_enabled, trace};
95
use pin_project::pin_project;
106

7+
use crate::response::Response;
8+
119
use super::error::{Error, Result};
1210

1311
#[derive(Default, PartialEq)]
@@ -36,7 +34,7 @@ impl EventData {
3634

3735
#[derive(Debug, Eq, PartialEq)]
3836
pub enum SSE {
39-
Connected((u16, HashMap<String, String>)),
37+
Connected(ConnectionDetails),
4038
Event(Event),
4139
Comment(String),
4240
}
@@ -75,6 +73,22 @@ impl TryFrom<EventData> for Option<SSE> {
7573
}
7674
}
7775

76+
#[derive(Clone, Debug, Eq, PartialEq)]
77+
pub struct ConnectionDetails {
78+
response: Response,
79+
}
80+
81+
impl ConnectionDetails {
82+
pub(crate) fn new(response: Response) -> Self {
83+
Self { response }
84+
}
85+
86+
/// Returns information describing the response at the time of connection.
87+
pub fn response(&self) -> &Response {
88+
&self.response
89+
}
90+
}
91+
7892
#[derive(Clone, Debug, Eq, PartialEq)]
7993
pub struct Event {
8094
pub event_type: String,
@@ -235,7 +249,7 @@ impl EventParser {
235249
self.last_event_id = Some(value.to_string());
236250
}
237251

238-
event_data.id = self.last_event_id.clone()
252+
event_data.id.clone_from(&self.last_event_id)
239253
} else if key == "retry" {
240254
match value.parse::<u64>() {
241255
Ok(retry) => {

eventsource-client/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ mod client;
3131
mod config;
3232
mod error;
3333
mod event_parser;
34+
mod response;
3435
mod retry;
3536

3637
pub use client::*;
3738
pub use config::*;
3839
pub use error::*;
3940
pub use event_parser::Event;
4041
pub use event_parser::SSE;
42+
pub use response::Response;

eventsource-client/src/response.rs

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use hyper::body::Buf;
2+
use hyper::{header::HeaderValue, Body, HeaderMap, StatusCode};
3+
4+
use crate::{Error, HeaderError};
5+
6+
pub struct ErrorBody {
7+
body: Body,
8+
}
9+
10+
impl ErrorBody {
11+
pub fn new(body: Body) -> Self {
12+
Self { body }
13+
}
14+
15+
/// Returns the body of the response as a vector of bytes.
16+
///
17+
/// Caution: This method reads the entire body into memory. You should only use this method if
18+
/// you know the response is of a reasonable size.
19+
pub async fn body_bytes(self) -> Result<Vec<u8>, Error> {
20+
let buf = match hyper::body::aggregate(self.body).await {
21+
Ok(buf) => buf,
22+
Err(err) => return Err(Error::HttpStream(Box::new(err))),
23+
};
24+
25+
Ok(buf.chunk().to_vec())
26+
}
27+
}
28+
29+
impl std::fmt::Debug for ErrorBody {
30+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31+
f.debug_struct("ErrorBody").finish()
32+
}
33+
}
34+
35+
#[derive(Clone, Debug, Eq, PartialEq)]
36+
pub struct Response {
37+
status_code: StatusCode,
38+
headers: HeaderMap<HeaderValue>,
39+
}
40+
41+
impl Response {
42+
pub fn new(status_code: StatusCode, headers: HeaderMap<HeaderValue>) -> Self {
43+
Self {
44+
status_code,
45+
headers,
46+
}
47+
}
48+
49+
/// Returns the status code of this response.
50+
pub fn status(&self) -> u16 {
51+
self.status_code.as_u16()
52+
}
53+
54+
/// Returns the list of header keys present in this response.
55+
pub fn get_header_keys(&self) -> Vec<&str> {
56+
self.headers.keys().map(|key| key.as_str()).collect()
57+
}
58+
59+
/// Returns the value of a header.
60+
///
61+
/// If the header contains more than one value, only the first value is returned. Refer to
62+
/// [`get_header_values`] for a method that returns all values.
63+
pub fn get_header_value(&self, key: &str) -> std::result::Result<Option<&str>, HeaderError> {
64+
if let Some(value) = self.headers.get(key) {
65+
value
66+
.to_str()
67+
.map(Some)
68+
.map_err(|e| HeaderError::new(Box::new(e)))
69+
} else {
70+
Ok(None)
71+
}
72+
}
73+
74+
/// Returns all values for a header.
75+
///
76+
/// If the header contains only one value, it will be returned as a single-element vector.
77+
/// Refer to [`get_header_value`] for a method that returns only a single value.
78+
pub fn get_header_values(&self, key: &str) -> std::result::Result<Vec<&str>, HeaderError> {
79+
self.headers
80+
.get_all(key)
81+
.iter()
82+
.map(|value| value.to_str().map_err(|e| HeaderError::new(Box::new(e))))
83+
.collect()
84+
}
85+
}

0 commit comments

Comments
 (0)