use chrono::prelude::*;
use serde_json::{to_string, Value};
use url::Url;
use yyid::yyid_string as uuidv4_string;
use std::collections::HashMap;
use std::env;
use std::str::FromStr;
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct StackFrame {
pub filename: String,
pub function: String,
pub lineno: u32,
pub pre_context: Vec<String>,
pub post_context: Vec<String>,
pub context_line: String,
pub in_app: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct SDK {
pub name: String,
pub version: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct Device {
pub name: String,
pub version: String,
pub build: Option<String>
}
#[derive(Clone, Debug, PartialEq)]
pub struct Event {
pub event_id: String,
pub message: String,
pub timestamp: String,
pub level: String,
pub logger: String,
pub platform: String,
pub sdk: SDK,
pub device: Device,
pub culprit: Option<String>,
pub server_name: Option<String>,
pub stacktrace: Option<Vec<StackFrame>>,
pub release: Option<String>,
pub tags: HashMap<String, String>,
pub environment: Option<String>,
pub modules: HashMap<String, String>,
pub extra: HashMap<String, Value>,
pub fingerprint: Vec<String>,
}
pub fn prep_string(to_prep: &str) -> String {
let mut to_return = to_prep.to_owned();
if to_prep != "" {
if to_prep.starts_with("\"") {
let tlen = to_return.len();
to_return.remove(0);
to_return.truncate(tlen - 2);
}
}
to_return
}
impl Event {
pub fn to_string(&self) -> String {
let mut value: Value = json!({
"event_id": self.event_id,
"message": self.message,
"timestamp": self.timestamp,
"level": self.level,
"logger": self.logger,
"platform": self.platform,
"sdk": json!(self.sdk),
"device": json!(self.device),
"culprit": json!(self.culprit),
"server_name": json!(self.server_name),
"release": json!(self.release),
});
let tag_length = self.tags.len();
if tag_length > 0 {
value["tags"] = json!(self.tags);
}
if let Some(ref environment) = self.environment {
value["environment"] = json!(environment);
}
let modules_len = self.modules.len();
if modules_len > 0 {
value["modules"] = json!(self.modules);
}
let extra_len = self.extra.len();
if extra_len > 0 {
value["extra"] = json!(self.extra);
}
if let Some(ref stacktrace) = self.stacktrace {
let frames = stacktrace
.iter()
.map(|item| json!(item))
.collect::<Vec<Value>>();
value["stacktrace"] = json!({
"frames": json!(frames),
});
}
let fingerprint_len = self.fingerprint.len();
if fingerprint_len > 0 {
value["fingerprint"] = json!(self.fingerprint);
}
to_string(&value).unwrap()
}
}
impl Event {
pub fn new(
logger: &str,
level: &str,
message: &str,
culprit: Option<&str>,
fingerprint: Option<Vec<String>>,
server_name: Option<&str>,
stacktrace: Option<Vec<StackFrame>>,
release: Option<&str>,
environment: Option<&str>,
device: Option<Device>,
) -> Event {
Event {
event_id: uuidv4_string().replace("-", ""),
message: message.to_owned(),
timestamp: Utc::now().format("%Y-%m-%dT%H:%M:%S").to_string(),
level: level.to_owned(),
logger: logger.to_owned(),
platform: "other".to_string(),
sdk: SDK {
name: "sentry-rs".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
},
device: device.unwrap_or(Device {
name: env::consts::FAMILY.to_owned(),
version: env::consts::OS.to_owned(),
build: None,
}),
culprit: culprit.map(|c| c.to_owned()),
server_name: server_name.map(|c| c.to_owned()),
stacktrace: stacktrace,
release: release.map(|c| c.to_owned()),
tags: HashMap::new(),
environment: environment.map(|c| c.to_owned()),
modules: HashMap::new(),
extra: HashMap::new(),
fingerprint: fingerprint.unwrap_or(vec![]),
}
}
pub fn add_tag(&mut self, key: String, value: String) {
self.tags.insert(key, value);
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SentryCredentials {
pub scheme: String,
pub key: String,
pub secret: String,
pub host: Option<String>,
pub project_id: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CredentialsParseError {
BadUrl,
NoApiKey,
NoApiSecret,
NoHostname,
BadProjectId,
NoProjectId,
}
impl FromStr for SentryCredentials {
type Err = CredentialsParseError;
fn from_str(to_parse: &str) -> Result<SentryCredentials, CredentialsParseError> {
let attempt_parse = Url::parse(to_parse);
if attempt_parse.is_err() {
return Err(CredentialsParseError::BadUrl);
}
let parsed = attempt_parse.unwrap();
let scheme = parsed.scheme();
let potential_username = parsed.username();
if potential_username.is_empty() {
return Err(CredentialsParseError::NoApiKey);
}
let potential_password = parsed.password();
if potential_password.is_none() {
return Err(CredentialsParseError::NoApiSecret);
}
let potential_hostname = parsed.host_str();
if potential_hostname.is_none() {
return Err(CredentialsParseError::NoHostname);
}
let potential_project_id = parsed.path_segments().and_then(|paths| paths.last());
if potential_project_id.is_none() {
return Err(CredentialsParseError::BadProjectId);
}
let project_id = potential_project_id.unwrap();
if project_id.is_empty() {
return Err(CredentialsParseError::NoProjectId);
}
Ok(SentryCredentials {
scheme: scheme.to_owned(),
key: potential_username.to_owned(),
secret: potential_password.unwrap().to_owned(),
host: Some(potential_hostname.unwrap().to_owned()),
project_id: project_id.to_owned(),
})
}
}