Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 65 additions & 4 deletions sentry-types/src/protocol/v7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ use std::str;
use std::time::SystemTime;

use self::debugid::{CodeId, DebugId};
use serde::Serializer;
use serde::{Deserialize, Serialize};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;
use url::Url;
use uuid::Uuid;
Expand Down Expand Up @@ -1477,6 +1476,60 @@ into_context!(Trace, TraceContext);
into_context!(Gpu, GpuContext);
into_context!(Profile, ProfileContext);

const INFERABLE_CONTEXTS: &[&str] = &[
"device", "os", "runtime", "app", "browser", "trace", "gpu", "profile",
];

struct ContextsVisitor;

impl<'de> de::Visitor<'de> for ContextsVisitor {
type Value = Map<String, Context>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("contexts object")
}

fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
let mut map: Map<String, Context> = Map::new();

while let Some((key, mut value)) = access.next_entry::<String, Value>()? {
let typed_value = value
.as_object_mut()
.map(|ctx| {
if !ctx.contains_key("type") {
let type_key = if INFERABLE_CONTEXTS.contains(&key.as_str()) {
key.clone().into()
} else {
Value::String("unknown".into())
};
ctx.insert(String::from("type"), type_key);
}
ctx.to_owned()
})
.ok_or_else(|| de::Error::custom("expected valid `context` object"))?;

match serde_json::from_value(serde_json::to_value(typed_value).unwrap()) {
Ok(context) => {
map.insert(key, context);
}
Err(e) => return Err(de::Error::custom(e.to_string())),
}
}

Ok(map)
}
}

fn deserialize_contexts<'de, D>(deserializer: D) -> Result<Map<String, Context>, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(ContextsVisitor {})
}

mod event {
use super::*;

Expand Down Expand Up @@ -1578,7 +1631,11 @@ pub struct Event<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub request: Option<Request>,
/// Optional contexts.
#[serde(default, skip_serializing_if = "Map::is_empty")]
#[serde(
default,
skip_serializing_if = "Map::is_empty",
deserialize_with = "deserialize_contexts"
)]
pub contexts: Map<String, Context>,
/// List of breadcrumbs to send along.
#[serde(default, skip_serializing_if = "Values::is_empty")]
Expand Down Expand Up @@ -1943,7 +2000,11 @@ pub struct Transaction<'a> {
/// The collection of finished spans part of this transaction.
pub spans: Vec<Span>,
/// Optional contexts.
#[serde(default, skip_serializing_if = "Map::is_empty")]
#[serde(
default,
skip_serializing_if = "Map::is_empty",
deserialize_with = "deserialize_contexts"
)]
pub contexts: Map<String, Context>,
/// Optionally HTTP request data to be sent along.
#[serde(default, skip_serializing_if = "Option::is_none")]
Expand Down
69 changes: 69 additions & 0 deletions sentry-types/tests/test_protocol_v7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,75 @@ mod test_contexts {
\"contexts\":{\"other\":{\"type\":\"unknown\",\"aha\":\"oho\"}}}"
);
}

#[test]
fn test_typeless_context() {
let payload = r#"
{
"event_id": "d43e86c96e424a93a4fbda156dd17341",
"timestamp": 1514103120,
"contexts": {
"device": {
"name": "iphone",
"family": "iphone",
"model": "iphone7,3",
"model_id": "AH223",
"arch": "arm64"
},
"os": { "name": "iOS", "version": "11.4.2", "build": "ADSA23" },
"runtime": { "name": "magicvm", "version": "5.3" },
"app": {
"app_start_time": "2018-02-08T22:21:57Z",
"build_type": "testflight",
"app_name": "Baz App",
"app_version": "1.0",
"app_build": "100001"
},
"browser": { "name": "Chrome", "version": "59.0.3071" },
"gpu": {
"name": "AMD Radeon Pro 560",
"vendor_name": "Apple",
"memory_size": 4096
},
"trace": {
"trace_id": "12312012123120121231201212312012",
"span_id": "0415201309082013",
"parent_span_id": null,
"description": "<OrganizationContext>",
"op": "http.server"
},
"random": { "aha": "oho" }
}
}
"#;

let event: v7::Event = serde_json::from_str(payload).unwrap();
let ctx = event.contexts;

assert!(ctx.contains_key("device"));
assert_eq!(ctx.get("device").unwrap().type_name(), "device");

assert!(ctx.contains_key("os"));
assert_eq!(ctx.get("os").unwrap().type_name(), "os");

assert!(ctx.contains_key("runtime"));
assert_eq!(ctx.get("runtime").unwrap().type_name(), "runtime");

assert!(ctx.contains_key("app"));
assert_eq!(ctx.get("app").unwrap().type_name(), "app");

assert!(ctx.contains_key("browser"));
assert_eq!(ctx.get("browser").unwrap().type_name(), "browser");

assert!(ctx.contains_key("trace"));
assert_eq!(ctx.get("trace").unwrap().type_name(), "trace");

assert!(ctx.contains_key("gpu"));
assert_eq!(ctx.get("gpu").unwrap().type_name(), "gpu");

assert!(ctx.contains_key("random"));
assert_eq!(ctx.get("random").unwrap().type_name(), "unknown");
}
}

#[test]
Expand Down