Skip to content

Commit c4d6b88

Browse files
committed
Add library error handling
1 parent 8bf04f0 commit c4d6b88

File tree

9 files changed

+288
-111
lines changed

9 files changed

+288
-111
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ data.insert("payload_length".to_string(), json!(27));
131131

132132
let mut ev = client.new_event();
133133
ev.add(data);
134-
ev.send(&mut client);
134+
// In production code, please check return of `.send()`
135+
ev.send(&mut client).err();
135136
```
136137

137138
[API reference]: https://docs.rs/libhoney-rust

examples/client.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
11
use env_logger;
2+
use log;
23

34
use libhoney;
5+
use libhoney::Error;
46
use libhoney::FieldHolder;
57

6-
fn main() {
8+
fn main() -> Result<(), Error> {
79
env_logger::init();
810

911
let mut client = libhoney::init(libhoney::Config {
1012
options: libhoney::client::Options {
11-
api_key: std::env::var("HONEYCOMB_API_KEY").unwrap(),
12-
dataset: std::env::var("HONEYCOMB_DATASET").unwrap(),
13+
api_key: std::env::var("HONEYCOMB_API_KEY").expect("need to set HONEYCOMB_API_KEY"),
14+
dataset: std::env::var("HONEYCOMB_DATASET").expect("need to set HONEYCOMB_DATASET"),
1315
..Default::default()
1416
},
1517
transmission_options: libhoney::transmission::Options::default(),
1618
});
1719
let mut event = client.new_event();
1820
event.add_field("extra", libhoney::Value::String("wheeee".to_string()));
1921
event.add_field("extra_ham", libhoney::Value::String("cheese".to_string()));
20-
event.send(&mut client);
21-
let response = client.responses().iter().next().unwrap();
22-
assert_eq!(response.error, None);
23-
client.close();
22+
match event.send(&mut client) {
23+
Ok(()) => {
24+
let response = client.responses().iter().next().unwrap();
25+
assert_eq!(response.error, None);
26+
}
27+
Err(e) => {
28+
log::error!("Could not send event: {}", e);
29+
}
30+
}
31+
client.close()
2432
}

src/builder.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,32 @@ use std::collections::HashMap;
33
use serde_json::Value;
44

55
use crate::client::Options;
6+
use crate::errors::Result;
67
use crate::event::Event;
78
use crate::fields::FieldHolder;
89

910
/// Shorthand type for the function to be passed to the `add_dynamic_field` calls
1011
pub type DynamicFieldFunc = fn() -> Value;
1112

1213
impl FieldHolder for Builder {
13-
fn get_fields(&mut self) -> &mut HashMap<String, Value> {
14-
&mut self.fields
14+
fn add(&mut self, data: HashMap<String, Value>) {
15+
self.fields.extend(data);
16+
}
17+
18+
/// add_field adds a field to the current (event/builder) fields
19+
fn add_field(&mut self, name: &str, value: Value) {
20+
self.fields.insert(name.to_string(), value);
21+
}
22+
23+
/// add_func iterates over the results from func (until Err) and adds the results to
24+
/// the event/builder fields
25+
fn add_func<F>(&mut self, func: F)
26+
where
27+
F: Fn() -> Result<(String, Value)>,
28+
{
29+
while let Ok((name, value)) = func() {
30+
self.add_field(&name, value);
31+
}
1532
}
1633
}
1734

src/client.rs

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crossbeam_channel::Receiver;
77
use log::info;
88
use serde_json::Value;
99

10+
use crate::errors::Result;
1011
use crate::fields::FieldHolder;
1112
use crate::response::Response;
1213
use crate::Event;
@@ -109,9 +110,9 @@ impl Client {
109110

110111
/// close waits for all in-flight messages to be sent. You should call close() before
111112
/// app termination.
112-
pub fn close(mut self) {
113+
pub fn close(mut self) -> Result<()> {
113114
info!("closing libhoney client");
114-
self.transmission.stop();
115+
self.transmission.stop()
115116
}
116117

117118
/// flush closes and reopens the Transmission, ensuring events are sent without
@@ -120,10 +121,11 @@ impl Client {
120121
/// Flush if asynchronous sends are not guaranteed to run (i.e. running in AWS Lambda)
121122
/// Flush is not thread safe - use it only when you are sure that no other parts of
122123
/// your program are calling Send
123-
pub fn flush(&mut self) {
124+
pub fn flush(&mut self) -> Result<()> {
124125
info!("flushing libhoney client");
125-
self.transmission.stop();
126+
self.transmission.stop()?;
126127
self.transmission.start();
128+
Ok(())
127129
}
128130

129131
/// new_builder creates a new event builder. The builder inherits any Dynamic or
@@ -146,16 +148,16 @@ impl Client {
146148

147149
#[cfg(test)]
148150
mod tests {
149-
use super::*;
151+
use super::{Client, FieldHolder, Options, Transmission, Value};
150152
use crate::transmission;
151153

152154
#[test]
153155
fn test_init() {
154156
let client = Client::new(
155157
Options::default(),
156-
Transmission::new(transmission::Options::default()),
158+
Transmission::new(transmission::Options::default()).unwrap(),
157159
);
158-
client.close();
160+
client.close().unwrap();
159161
}
160162

161163
#[test]
@@ -179,36 +181,37 @@ mod tests {
179181
api_host: api_host.to_string(),
180182
..Options::default()
181183
},
182-
Transmission::new(transmission::Options::default()),
184+
Transmission::new(transmission::Options::default()).unwrap(),
183185
);
184186

185187
let mut event = client.new_event();
186188
event.add_field("some_field", Value::String("some_value".to_string()));
187189
event.metadata = Some(json!("some metadata in a string"));
188-
event.send(&mut client);
190+
event.send(&mut client).unwrap();
189191

190192
let response = client.responses().iter().next().unwrap();
191193
assert_eq!(response.status_code, Some(StatusCode::ACCEPTED));
192194
assert_eq!(response.metadata, Some(json!("some metadata in a string")));
193195

194-
client.flush();
196+
client.flush().unwrap();
195197

196198
event = client.new_event();
197199
event.add_field("some_field", Value::String("some_value".to_string()));
198200
event.metadata = Some(json!("some metadata in a string"));
199-
event.send(&mut client);
201+
event.send(&mut client).unwrap();
200202

201203
let response = client.responses().iter().next().unwrap();
202204
assert_eq!(response.status_code, Some(StatusCode::ACCEPTED));
203205
assert_eq!(response.metadata, Some(json!("some metadata in a string")));
204206

205-
client.close();
207+
client.close().unwrap();
206208
}
207209

208210
#[test]
209211
fn test_send_without_api_key() {
210212
use serde_json::json;
211-
use std::time::Duration;
213+
214+
use crate::errors::ErrorKind;
212215

213216
let api_host = &mockito::server_url();
214217
let _m = mockito::mock(
@@ -225,17 +228,19 @@ mod tests {
225228
api_host: api_host.to_string(),
226229
..Options::default()
227230
},
228-
Transmission::new(transmission::Options::default()),
231+
Transmission::new(transmission::Options::default()).unwrap(),
229232
);
230233

231234
let mut event = client.new_event();
232235
event.add_field("some_field", Value::String("some_value".to_string()));
233236
event.metadata = Some(json!("some metadata in a string"));
234-
event.send(&mut client);
235-
client
236-
.responses()
237-
.recv_timeout(Duration::from_millis(100))
238-
.err();
239-
client.close();
237+
let err = event.send(&mut client).err().unwrap();
238+
239+
assert_eq!(err.kind, ErrorKind::MissingOption);
240+
assert_eq!(
241+
err.message,
242+
"missing option 'api_key', can't send to Honeycomb"
243+
);
244+
client.close().unwrap();
240245
}
241246
}

src/errors.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use std::error::Error as _;
2+
use std::fmt;
3+
use std::io;
4+
5+
/// Result shorthand for a `std::result::Result` wrapping our own `Error`
6+
pub type Result<T> = std::result::Result<T, Error>;
7+
8+
/// Type of error, exposed through `Error` member `kind`
9+
#[derive(Debug, Copy, Clone, PartialEq)]
10+
pub enum ErrorKind {
11+
/// Event has no populated fields
12+
MissingEventFields,
13+
14+
/// Mandatory client/transmission Option is missing
15+
MissingOption,
16+
17+
/// User Func error
18+
UserFuncError,
19+
20+
/// Sender full
21+
ChannelError,
22+
23+
/// Any IO related error
24+
Io,
25+
}
26+
27+
/// Error
28+
#[derive(Debug)]
29+
pub struct Error {
30+
/// Error message
31+
pub message: String,
32+
/// Type of error
33+
pub kind: ErrorKind,
34+
}
35+
36+
impl Error {
37+
#[doc(hidden)]
38+
pub(crate) fn missing_event_fields() -> Self {
39+
Self {
40+
message: String::from("event has no data"),
41+
kind: ErrorKind::MissingEventFields,
42+
}
43+
}
44+
45+
#[doc(hidden)]
46+
pub(crate) fn missing_option(option: &str, extra: &str) -> Self {
47+
Self {
48+
message: format!("missing option '{}', {}", option, extra),
49+
kind: ErrorKind::MissingOption,
50+
}
51+
}
52+
53+
#[doc(hidden)]
54+
pub(crate) fn sender_full(sender: &str) -> Self {
55+
Self {
56+
message: format!("sender '{}' is full", sender),
57+
kind: ErrorKind::ChannelError,
58+
}
59+
}
60+
61+
#[doc(hidden)]
62+
pub(crate) fn with_description(description: &str, kind: ErrorKind) -> Self {
63+
Error {
64+
message: format!("error: {}", description),
65+
kind,
66+
}
67+
}
68+
}
69+
70+
impl std::error::Error for Error {
71+
fn description(&self) -> &str {
72+
&*self.message
73+
}
74+
}
75+
76+
impl fmt::Display for Error {
77+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78+
writeln!(f, "{}", self.message)
79+
}
80+
}
81+
82+
impl From<io::Error> for Error {
83+
fn from(e: io::Error) -> Self {
84+
Error::with_description(e.description(), ErrorKind::Io)
85+
}
86+
}
87+
88+
impl<T> From<crossbeam_channel::SendError<T>> for Error {
89+
fn from(e: crossbeam_channel::SendError<T>) -> Self {
90+
Error::with_description(&e.to_string(), ErrorKind::ChannelError)
91+
}
92+
}

0 commit comments

Comments
 (0)