Skip to main content

spin_world/wasi_otel/
common_conversions.rs

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