use serde_json::{Map, Value, json};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Phase {
Enter,
Step,
Exit,
}
impl Phase {
pub fn code(self) -> &'static str {
match self {
Phase::Enter => "enter",
Phase::Step => "step",
Phase::Exit => "exit",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TraceVariable {
pub name: String,
pub context_type: String,
pub value: Value,
pub data_type: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TraceEvent {
pub phase: Phase,
pub depth: usize,
pub source: Option<String>,
pub position: String,
pub variables: Vec<TraceVariable>,
pub messages: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TraceStatus {
Complete,
Stopped,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Trace {
pub status: TraceStatus,
pub events: Vec<TraceEvent>,
}
pub fn debug_parameters(result: Option<&Value>, trace: &Trace, error: Option<&str>) -> Value {
let mut parameters = Vec::new();
if let Some(result) = result {
parameters.push(json!({ "name": "return", "resource": result }));
}
parameters.push(json!({
"name": "traceStatus",
"valueCode": match trace.status {
TraceStatus::Complete => "complete",
TraceStatus::Stopped => "stopped",
}
}));
for event in &trace.events {
let mut parts = Vec::new();
if let Some(source) = &event.source {
parts.push(json!({ "name": "source", "valueString": source }));
}
parts.push(json!({ "name": "expressionPosition", "valueString": event.position }));
parts.push(json!({ "name": "phase", "valueCode": event.phase.code() }));
parts.push(json!({ "name": "depth", "valueInteger": event.depth }));
for variable in &event.variables {
parts.push(variable_parameter(variable));
}
for message in &event.messages {
parts.push(json!({
"name": "message",
"part": [{ "name": "text", "valueString": message }]
}));
}
parameters.push(json!({ "name": "trace", "part": parts }));
}
if let Some(error) = error {
parameters.push(json!({
"name": "outcome",
"resource": {
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "processing", "diagnostics": error }]
}
}));
}
json!({ "resourceType": "Parameters", "parameter": parameters })
}
fn variable_parameter(variable: &TraceVariable) -> Value {
let mut parts = vec![
json!({ "name": "name", "valueString": variable.name }),
json!({ "name": "contextType", "valueString": variable.context_type }),
];
let data_type = variable.data_type.as_deref();
let value = &variable.value;
let is_resource = value.get("resourceType").is_some();
let is_data_type = data_type.is_some_and(|t| !t.contains('.') && t != "BackboneElement");
if is_resource {
parts.push(json!({ "name": "resource", "resource": value }));
} else if value.is_null() {
} else if let (Some(type_name), true) = (data_type, is_data_type) {
let mut part = Map::new();
part.insert("name".into(), "value".into());
part.insert(format!("value{}", capitalize(type_name)), value.clone());
parts.push(Value::Object(part));
} else if !value.is_object() && !value.is_array() {
parts.push(json!({ "name": "value", "valueString": scalar_text(value) }));
} else {
parts.push(json!({ "name": "jsonValue", "valueString": value.to_string() }));
}
if let Some(data_type) = data_type {
parts.push(json!({ "name": "dataType", "valueString": data_type }));
}
json!({ "name": "variable", "part": parts })
}
fn scalar_text(value: &Value) -> String {
match value {
Value::String(text) => text.clone(),
other => other.to_string(),
}
}
fn capitalize(text: &str) -> String {
let mut chars = text.chars();
match chars.next() {
Some(first) => first.to_uppercase().chain(chars).collect(),
None => String::new(),
}
}