Skip to main content

spin_world/wasi_otel/
common_conversions.rs

1use crate::wasi::{self, clocks0_2_0::wall_clock};
2use serde::{
3    Deserialize,
4    de::{self, SeqAccess, Visitor},
5};
6use std::{
7    fmt,
8    time::{Duration, SystemTime, UNIX_EPOCH},
9};
10
11impl From<wasi::otel::types::KeyValue> for opentelemetry::KeyValue {
12    fn from(kv: wasi::otel::types::KeyValue) -> Self {
13        let owned: OwnedValue = from_json(&kv.value);
14        let value: opentelemetry::Value = owned.into();
15        opentelemetry::KeyValue::new(kv.key, value)
16    }
17}
18
19impl From<OwnedValue> for opentelemetry::Value {
20    fn from(value: OwnedValue) -> Self {
21        match value {
22            OwnedValue::String(s) => opentelemetry::Value::String(s.into()),
23            OwnedValue::Bool(v) => opentelemetry::Value::Bool(v),
24            OwnedValue::F64(v) => opentelemetry::Value::F64(v),
25            OwnedValue::I64(v) => opentelemetry::Value::I64(v),
26            OwnedValue::Array(arr) => opentelemetry::Value::Array(match arr {
27                OwnedArray::Bool(v) => opentelemetry::Array::Bool(v),
28                OwnedArray::F64(v) => opentelemetry::Array::F64(v),
29                OwnedArray::I64(v) => opentelemetry::Array::I64(v),
30                OwnedArray::String(v) => opentelemetry::Array::String(
31                    v.into_iter()
32                        .map(opentelemetry::StringValue::from)
33                        .collect(),
34                ),
35            }),
36        }
37    }
38}
39
40enum OwnedValue {
41    Bool(bool),
42    I64(i64),
43    F64(f64),
44    String(String),
45    Array(OwnedArray),
46}
47
48enum OwnedArray {
49    Bool(Vec<bool>),
50    I64(Vec<i64>),
51    F64(Vec<f64>),
52    String(Vec<String>),
53}
54
55impl<'de> Deserialize<'de> for OwnedValue {
56    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
57    where
58        D: serde::Deserializer<'de>,
59    {
60        struct ValueVisitor;
61
62        impl<'de> Visitor<'de> for ValueVisitor {
63            type Value = OwnedValue;
64
65            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
66                formatter.write_str("a boolean, number, string, or array")
67            }
68
69            fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
70            where
71                E: de::Error,
72            {
73                Ok(OwnedValue::Bool(value))
74            }
75
76            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
77            where
78                E: de::Error,
79            {
80                Ok(OwnedValue::I64(value))
81            }
82
83            fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
84            where
85                E: de::Error,
86            {
87                Ok(OwnedValue::F64(value))
88            }
89
90            /// u64 isn't an option in the OpenTelemetry Rust SDK; however, Serde may interpret a JSON number as u64.
91            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
92            where
93                E: de::Error,
94            {
95                i64::try_from(value)
96                    .map(|v| Ok(OwnedValue::I64(v)))
97                    .map_err(|_| de::Error::custom("Integer too large for i64"))?
98            }
99
100            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
101            where
102                E: de::Error,
103            {
104                Ok(OwnedValue::String(value.to_owned()))
105            }
106
107            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
108            where
109                A: SeqAccess<'de>,
110            {
111                let mut elements = Vec::new();
112
113                // Determine the type by looking at the first element
114                if let Some(first) = seq.next_element::<serde_json::Value>()? {
115                    elements.push(first);
116
117                    // Collect remaining elements
118                    while let Some(elem) = seq.next_element::<serde_json::Value>()? {
119                        elements.push(elem);
120                    }
121
122                    if elements.is_empty() {
123                        return Ok(OwnedValue::Array(OwnedArray::Bool(vec![])));
124                    }
125
126                    match &elements[0] {
127                        serde_json::Value::Bool(_) => {
128                            let bools: Result<Vec<bool>, _> = elements
129                                .iter()
130                                .map(|v| {
131                                    v.as_bool()
132                                        .ok_or_else(|| de::Error::custom("Mixed types in array"))
133                                })
134                                .collect();
135                            Ok(OwnedValue::Array(OwnedArray::Bool(bools?)))
136                        }
137                        serde_json::Value::Number(n) if n.is_i64() => {
138                            let ints: Result<Vec<i64>, _> = elements
139                                .iter()
140                                .map(|v| {
141                                    v.as_i64()
142                                        .ok_or_else(|| de::Error::custom("Mixed types in array"))
143                                })
144                                .collect();
145                            Ok(OwnedValue::Array(OwnedArray::I64(ints?)))
146                        }
147                        serde_json::Value::Number(n) if n.is_f64() => {
148                            let nums: Result<Vec<f64>, _> = elements
149                                .iter()
150                                .map(|v| {
151                                    v.as_f64()
152                                        .ok_or_else(|| de::Error::custom("Mixed types in array"))
153                                })
154                                .collect();
155                            Ok(OwnedValue::Array(OwnedArray::F64(nums?)))
156                        }
157                        serde_json::Value::String(_) => {
158                            let strings: Result<Vec<String>, _> = elements
159                                .iter()
160                                .map(|v| match v.as_str() {
161                                    Some(s) => Ok(s.to_string()),
162                                    None => Err(de::Error::custom("Mixed types in array")),
163                                })
164                                .collect();
165                            Ok(OwnedValue::Array(OwnedArray::String(strings?)))
166                        }
167                        _ => Err(de::Error::custom("Unsupported array element type")),
168                    }
169                } else {
170                    // Empty array using bool as the default
171                    Ok(OwnedValue::Array(OwnedArray::Bool(vec![])))
172                }
173            }
174        }
175
176        deserializer.deserialize_any(ValueVisitor)
177    }
178}
179
180// Deserialize a JSON string to a Serde-serializable struct
181pub(crate) fn from_json<T: for<'de> Deserialize<'de>>(json: &str) -> T {
182    serde_json::from_str(json).unwrap_or_else(|e| {
183        panic!(
184            "Failed to deserialize JSON to {}\
185             \n Input: {}\
186             \n Error: {}",
187            std::any::type_name::<T>(),
188            json,
189            e
190        )
191    })
192}
193
194impl From<wasi::otel::types::InstrumentationScope> for opentelemetry::InstrumentationScope {
195    fn from(value: wasi::otel::types::InstrumentationScope) -> Self {
196        let builder =
197            Self::builder(value.name).with_attributes(value.attributes.into_iter().map(Into::into));
198        match (value.version, value.schema_url) {
199            (Some(version), Some(schema_url)) => builder
200                .with_version(version)
201                .with_schema_url(schema_url)
202                .build(),
203            (Some(version), None) => builder.with_version(version).build(),
204            (None, Some(schema_url)) => builder.with_schema_url(schema_url).build(),
205            (None, None) => builder.build(),
206        }
207    }
208}
209
210impl From<wall_clock::Datetime> for SystemTime {
211    fn from(timestamp: wall_clock::Datetime) -> Self {
212        UNIX_EPOCH
213            + Duration::from_secs(timestamp.seconds)
214            + Duration::from_nanos(timestamp.nanoseconds as u64)
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    macro_rules! compare_json_and_literal {
223        ($json:expr, $literal:expr) => {{
224            let left: serde_json::Value = from_json($json);
225            let right: serde_json::Value = serde_json::json!($literal);
226            assert_eq!(left, right);
227        }};
228    }
229
230    #[test]
231    fn deserialize_from_json_to_otel_value() {
232        compare_json_and_literal!("false", false);
233        compare_json_and_literal!("[false,true,true]", vec![false, true, true]);
234        compare_json_and_literal!("6", 6);
235        compare_json_and_literal!("[1,2,3,4]", vec![1, 2, 3, 4]);
236        compare_json_and_literal!("-6", -6);
237        compare_json_and_literal!("[-1,-2,-3,-4]", vec![-1, -2, -3, -4]);
238        compare_json_and_literal!("123.456", 123.456);
239        compare_json_and_literal!("[1.0,2.1,3.2,4.3]", vec![1.0, 2.1, 3.2, 4.3]);
240        compare_json_and_literal!("\"test\"", "test");
241        compare_json_and_literal!(
242            "[\"Hello, world!\",\"Goodnight, moon.\"]",
243            vec!["Hello, world!", "Goodnight, moon."]
244        );
245    }
246}