1use serde_json::{Map, Value, json};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Phase {
9 Enter,
10 Step,
11 Exit,
12}
13
14impl Phase {
15 pub fn code(self) -> &'static str {
16 match self {
17 Phase::Enter => "enter",
18 Phase::Step => "step",
19 Phase::Exit => "exit",
20 }
21 }
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct TraceVariable {
27 pub name: String,
28 pub context_type: String,
30 pub value: Value,
32 pub data_type: Option<String>,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct TraceEvent {
39 pub phase: Phase,
40 pub depth: usize,
41 pub source: Option<String>,
43 pub position: String,
45 pub variables: Vec<TraceVariable>,
46 pub messages: Vec<String>,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum TraceStatus {
52 Complete,
54 Stopped,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct Trace {
61 pub status: TraceStatus,
62 pub events: Vec<TraceEvent>,
63}
64
65pub fn debug_parameters(result: Option<&Value>, trace: &Trace, error: Option<&str>) -> Value {
68 let mut parameters = Vec::new();
69 if let Some(result) = result {
70 parameters.push(json!({ "name": "return", "resource": result }));
71 }
72 parameters.push(json!({
73 "name": "traceStatus",
74 "valueCode": match trace.status {
75 TraceStatus::Complete => "complete",
76 TraceStatus::Stopped => "stopped",
77 }
78 }));
79 for event in &trace.events {
80 let mut parts = Vec::new();
81 if let Some(source) = &event.source {
82 parts.push(json!({ "name": "source", "valueString": source }));
83 }
84 parts.push(json!({ "name": "expressionPosition", "valueString": event.position }));
85 parts.push(json!({ "name": "phase", "valueCode": event.phase.code() }));
86 parts.push(json!({ "name": "depth", "valueInteger": event.depth }));
87 for variable in &event.variables {
88 parts.push(variable_parameter(variable));
89 }
90 for message in &event.messages {
91 parts.push(json!({
92 "name": "message",
93 "part": [{ "name": "text", "valueString": message }]
94 }));
95 }
96 parameters.push(json!({ "name": "trace", "part": parts }));
97 }
98 if let Some(error) = error {
99 parameters.push(json!({
100 "name": "outcome",
101 "resource": {
102 "resourceType": "OperationOutcome",
103 "issue": [{ "severity": "error", "code": "processing", "diagnostics": error }]
104 }
105 }));
106 }
107 json!({ "resourceType": "Parameters", "parameter": parameters })
108}
109
110fn variable_parameter(variable: &TraceVariable) -> Value {
113 let mut parts = vec![
114 json!({ "name": "name", "valueString": variable.name }),
115 json!({ "name": "contextType", "valueString": variable.context_type }),
116 ];
117 let data_type = variable.data_type.as_deref();
118 let value = &variable.value;
119 let is_resource = value.get("resourceType").is_some();
120 let is_data_type = data_type.is_some_and(|t| !t.contains('.') && t != "BackboneElement");
121 if is_resource {
122 parts.push(json!({ "name": "resource", "resource": value }));
123 } else if value.is_null() {
124 } else if let (Some(type_name), true) = (data_type, is_data_type) {
126 let mut part = Map::new();
127 part.insert("name".into(), "value".into());
128 part.insert(format!("value{}", capitalize(type_name)), value.clone());
129 parts.push(Value::Object(part));
130 } else if !value.is_object() && !value.is_array() {
131 parts.push(json!({ "name": "value", "valueString": scalar_text(value) }));
132 } else {
133 parts.push(json!({ "name": "jsonValue", "valueString": value.to_string() }));
134 }
135 if let Some(data_type) = data_type {
136 parts.push(json!({ "name": "dataType", "valueString": data_type }));
137 }
138 json!({ "name": "variable", "part": parts })
139}
140
141fn scalar_text(value: &Value) -> String {
142 match value {
143 Value::String(text) => text.clone(),
144 other => other.to_string(),
145 }
146}
147
148fn capitalize(text: &str) -> String {
149 let mut chars = text.chars();
150 match chars.next() {
151 Some(first) => first.to_uppercase().chain(chars).collect(),
152 None => String::new(),
153 }
154}