use relay_protocol::{Annotated, Empty, Error, FromValue, IntoValue, Meta, Object, Value};
use crate::processor::ProcessValue;
use crate::protocol::JsonLenientString;
#[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)]
#[metastructure(process_func = "process_logentry", value_type = "LogEntry")]
pub struct LogEntry {
#[metastructure(max_chars = 8192, max_chars_allowance = 200)]
pub message: Annotated<Message>,
#[metastructure(max_chars = 8192, max_chars_allowance = 200, pii = "true")]
pub formatted: Annotated<Message>,
#[metastructure(max_depth = 5, max_bytes = 2048, pii = "true")]
pub params: Annotated<Value>,
#[metastructure(additional_properties, pii = "true")]
pub other: Object<Value>,
}
impl From<String> for LogEntry {
fn from(formatted_msg: String) -> Self {
LogEntry {
formatted: Annotated::new(formatted_msg.into()),
..Self::default()
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
#[metastructure(value_type = "Message", value_type = "String")]
pub struct Message(String);
impl From<String> for Message {
fn from(msg: String) -> Message {
Message(msg)
}
}
impl AsRef<str> for Message {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl FromValue for LogEntry {
fn from_value(value: Annotated<Value>) -> Annotated<Self> {
match value {
x @ Annotated(Some(Value::Object(_)), _) => {
#[derive(Debug, FromValue)]
struct Helper {
message: Annotated<String>,
formatted: Annotated<String>,
params: Annotated<Value>,
#[metastructure(additional_properties)]
other: Object<Value>,
}
Helper::from_value(x).map_value(|helper| {
let params = match helper.params {
a @ Annotated(Some(Value::Object(_)), _) => a,
a @ Annotated(Some(Value::Array(_)), _) => a,
a @ Annotated(None, _) => a,
Annotated(Some(value), _) => Annotated::from_error(
Error::expected("message parameters"),
Some(value),
),
};
LogEntry {
message: helper.message.map_value(Message),
formatted: helper.formatted.map_value(Message),
params,
other: helper.other,
}
})
}
Annotated(None, meta) => Annotated(None, meta),
Annotated(Some(Value::Bool(false)), _) => Annotated(None, Meta::default()),
x => Annotated::new(LogEntry {
formatted: JsonLenientString::from_value(x)
.map_value(JsonLenientString::into_inner)
.map_value(Message),
..Default::default()
}),
}
}
}
#[cfg(test)]
mod tests {
use similar_asserts::assert_eq;
use super::*;
#[test]
fn test_logentry_roundtrip() {
let json = r#"{
"message": "Hello, %s %s!",
"params": [
"World",
1
],
"other": "value"
}"#;
let entry = Annotated::new(LogEntry {
message: Annotated::new("Hello, %s %s!".to_string().into()),
formatted: Annotated::empty(),
params: Annotated::new(Value::Array(vec![
Annotated::new(Value::String("World".to_string())),
Annotated::new(Value::I64(1)),
])),
other: {
let mut map = Object::new();
map.insert(
"other".to_string(),
Annotated::new(Value::String("value".to_string())),
);
map
},
});
assert_eq!(entry, Annotated::from_json(json).unwrap());
assert_eq!(json, entry.to_json_pretty().unwrap());
}
#[test]
fn test_logentry_from_message() {
let input = r#""hi""#;
let output = r#"{
"formatted": "hi"
}"#;
let entry = Annotated::new(LogEntry {
formatted: Annotated::new("hi".to_string().into()),
..Default::default()
});
assert_eq!(entry, Annotated::from_json(input).unwrap());
assert_eq!(output, entry.to_json_pretty().unwrap());
}
#[test]
fn test_logentry_empty_params() {
let input = r#"{"params":[]}"#;
let entry = Annotated::new(LogEntry {
params: Annotated::new(Value::Array(vec![])),
..Default::default()
});
assert_eq!(entry, Annotated::from_json(input).unwrap());
assert_eq!(input, entry.to_json().unwrap());
}
#[test]
fn test_logentry_named_params() {
let json = r#"{
"message": "Hello, %s!",
"params": {
"name": "World"
}
}"#;
let entry = Annotated::new(LogEntry {
message: Annotated::new("Hello, %s!".to_string().into()),
params: Annotated::new(Value::Object({
let mut object = Object::new();
object.insert(
"name".to_string(),
Annotated::new(Value::String("World".to_string())),
);
object
})),
..LogEntry::default()
});
assert_eq!(entry, Annotated::from_json(json).unwrap());
assert_eq!(json, entry.to_json_pretty().unwrap());
}
#[test]
fn test_logentry_invalid_params() {
let json = r#"{
"message": "Hello, %s!",
"params": 42
}"#;
let entry = Annotated::new(LogEntry {
message: Annotated::new("Hello, %s!".to_string().into()),
params: Annotated::from_error(
Error::expected("message parameters"),
Some(Value::I64(42)),
),
..LogEntry::default()
});
assert_eq!(entry, Annotated::from_json(json).unwrap());
}
}