relay_event_schema/protocol/
logentry.rs1use relay_protocol::{Annotated, Empty, Error, FromValue, IntoValue, Meta, Object, Value};
2
3use crate::processor::ProcessValue;
4use crate::protocol::JsonLenientString;
5
6#[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)]
29#[metastructure(process_func = "process_logentry", value_type = "LogEntry")]
30pub struct LogEntry {
31 #[metastructure(max_chars = 8192, max_chars_allowance = 200)]
39 pub message: Annotated<Message>,
40
41 #[metastructure(max_chars = 8192, max_chars_allowance = 200, pii = "true")]
46 pub formatted: Annotated<Message>,
47
48 #[metastructure(max_depth = 5, max_bytes = 2048, pii = "true")]
51 pub params: Annotated<Value>,
52
53 #[metastructure(additional_properties, pii = "true")]
55 pub other: Object<Value>,
56}
57
58impl From<String> for LogEntry {
59 fn from(formatted_msg: String) -> Self {
60 LogEntry {
61 formatted: Annotated::new(formatted_msg.into()),
62 ..Self::default()
63 }
64 }
65}
66
67#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
68#[metastructure(value_type = "Message", value_type = "String")]
69pub struct Message(String);
70
71impl From<String> for Message {
72 fn from(msg: String) -> Message {
73 Message(msg)
74 }
75}
76
77impl AsRef<str> for Message {
78 fn as_ref(&self) -> &str {
79 self.0.as_ref()
80 }
81}
82
83impl FromValue for LogEntry {
84 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
85 match value {
90 x @ Annotated(Some(Value::Object(_)), _) => {
91 #[derive(Debug, FromValue)]
92 struct Helper {
93 message: Annotated<String>,
94 formatted: Annotated<String>,
95 params: Annotated<Value>,
96 #[metastructure(additional_properties)]
97 other: Object<Value>,
98 }
99
100 Helper::from_value(x).map_value(|helper| {
101 let params = match helper.params {
102 a @ Annotated(Some(Value::Object(_)), _) => a,
103 a @ Annotated(Some(Value::Array(_)), _) => a,
104 a @ Annotated(None, _) => a,
105 Annotated(Some(value), _) => Annotated::from_error(
106 Error::expected("message parameters"),
107 Some(value),
108 ),
109 };
110
111 LogEntry {
112 message: helper.message.map_value(Message),
113 formatted: helper.formatted.map_value(Message),
114 params,
115 other: helper.other,
116 }
117 })
118 }
119 Annotated(None, meta) => Annotated(None, meta),
120 Annotated(Some(Value::Bool(false)), _) => Annotated(None, Meta::default()),
124 x => Annotated::new(LogEntry {
125 formatted: JsonLenientString::from_value(x)
126 .map_value(JsonLenientString::into_inner)
127 .map_value(Message),
128 ..Default::default()
129 }),
130 }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use similar_asserts::assert_eq;
137
138 use super::*;
139
140 #[test]
141 fn test_logentry_roundtrip() {
142 let json = r#"{
143 "message": "Hello, %s %s!",
144 "params": [
145 "World",
146 1
147 ],
148 "other": "value"
149}"#;
150
151 let entry = Annotated::new(LogEntry {
152 message: Annotated::new("Hello, %s %s!".to_string().into()),
153 formatted: Annotated::empty(),
154 params: Annotated::new(Value::Array(vec![
155 Annotated::new(Value::String("World".to_string())),
156 Annotated::new(Value::I64(1)),
157 ])),
158 other: {
159 let mut map = Object::new();
160 map.insert(
161 "other".to_string(),
162 Annotated::new(Value::String("value".to_string())),
163 );
164 map
165 },
166 });
167
168 assert_eq!(entry, Annotated::from_json(json).unwrap());
169 assert_eq!(json, entry.to_json_pretty().unwrap());
170 }
171
172 #[test]
173 fn test_logentry_from_message() {
174 let input = r#""hi""#;
175 let output = r#"{
176 "formatted": "hi"
177}"#;
178
179 let entry = Annotated::new(LogEntry {
180 formatted: Annotated::new("hi".to_string().into()),
181 ..Default::default()
182 });
183
184 assert_eq!(entry, Annotated::from_json(input).unwrap());
185 assert_eq!(output, entry.to_json_pretty().unwrap());
186 }
187
188 #[test]
189 fn test_logentry_empty_params() {
190 let input = r#"{"params":[]}"#;
191 let entry = Annotated::new(LogEntry {
192 params: Annotated::new(Value::Array(vec![])),
193 ..Default::default()
194 });
195
196 assert_eq!(entry, Annotated::from_json(input).unwrap());
197 assert_eq!(input, entry.to_json().unwrap());
198 }
199
200 #[test]
201 fn test_logentry_named_params() {
202 let json = r#"{
203 "message": "Hello, %s!",
204 "params": {
205 "name": "World"
206 }
207}"#;
208
209 let entry = Annotated::new(LogEntry {
210 message: Annotated::new("Hello, %s!".to_string().into()),
211 params: Annotated::new(Value::Object({
212 let mut object = Object::new();
213 object.insert(
214 "name".to_string(),
215 Annotated::new(Value::String("World".to_string())),
216 );
217 object
218 })),
219 ..LogEntry::default()
220 });
221
222 assert_eq!(entry, Annotated::from_json(json).unwrap());
223 assert_eq!(json, entry.to_json_pretty().unwrap());
224 }
225
226 #[test]
227 fn test_logentry_invalid_params() {
228 let json = r#"{
229 "message": "Hello, %s!",
230 "params": 42
231}"#;
232
233 let entry = Annotated::new(LogEntry {
234 message: Annotated::new("Hello, %s!".to_string().into()),
235 params: Annotated::from_error(
236 Error::expected("message parameters"),
237 Some(Value::I64(42)),
238 ),
239 ..LogEntry::default()
240 });
241
242 assert_eq!(entry, Annotated::from_json(json).unwrap());
243 }
244}