1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9pub use crate::ipc_types::*;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct SdkExtensionMetadata {
19 pub id: String,
21 pub name: String,
23 pub version: String,
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub description: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub author: Option<String>,
31 #[serde(default)]
33 pub sdk_version: Option<String>,
34 #[serde(default = "default_extension_type")]
36 pub extension_type: String,
37}
38
39fn default_extension_type() -> String {
40 "native".to_string()
41}
42
43impl SdkExtensionMetadata {
44 pub fn new(id: impl Into<String>, name: impl Into<String>, version: impl Into<String>) -> Self {
46 Self {
47 id: id.into(),
48 name: name.into(),
49 version: version.into(),
50 description: None,
51 author: None,
52 sdk_version: Some(env!("CARGO_PKG_VERSION").to_string()),
53 extension_type: "native".to_string(),
54 }
55 }
56
57 pub fn with_description(mut self, description: impl Into<String>) -> Self {
59 self.description = Some(description.into());
60 self
61 }
62
63 pub fn with_author(mut self, author: impl Into<String>) -> Self {
65 self.author = Some(author.into());
66 self
67 }
68
69 pub fn with_type(mut self, extension_type: impl Into<String>) -> Self {
71 self.extension_type = extension_type.into();
72 self
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(rename_all = "lowercase")]
83#[derive(Default)]
84pub enum SdkMetricDataType {
85 Float,
86 Integer,
87 Boolean,
88 #[default]
89 String,
90 Binary,
91 Enum {
93 options: Vec<String>,
94 },
95}
96
97impl From<SdkMetricDataType> for MetricDataType {
98 fn from(dt: SdkMetricDataType) -> Self {
99 match dt {
100 SdkMetricDataType::Float => MetricDataType::Float,
101 SdkMetricDataType::Integer => MetricDataType::Integer,
102 SdkMetricDataType::Boolean => MetricDataType::Boolean,
103 SdkMetricDataType::String => MetricDataType::String,
104 SdkMetricDataType::Binary => MetricDataType::Binary,
105 SdkMetricDataType::Enum { options } => MetricDataType::Enum { options },
106 }
107 }
108}
109
110impl From<MetricDataType> for SdkMetricDataType {
111 fn from(dt: MetricDataType) -> Self {
112 match dt {
113 MetricDataType::Float => SdkMetricDataType::Float,
114 MetricDataType::Integer => SdkMetricDataType::Integer,
115 MetricDataType::Boolean => SdkMetricDataType::Boolean,
116 MetricDataType::String => SdkMetricDataType::String,
117 MetricDataType::Binary => SdkMetricDataType::Binary,
118 MetricDataType::Enum { options } => SdkMetricDataType::Enum { options },
119 }
120 }
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct SdkMetricDefinition {
126 pub name: String,
128 pub display_name: String,
130 pub data_type: SdkMetricDataType,
132 #[serde(default)]
134 pub unit: String,
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub min: Option<f64>,
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub max: Option<f64>,
141 #[serde(default)]
143 pub required: bool,
144}
145
146impl SdkMetricDefinition {
147 pub fn new(
149 name: impl Into<String>,
150 display_name: impl Into<String>,
151 data_type: SdkMetricDataType,
152 ) -> Self {
153 Self {
154 name: name.into(),
155 display_name: display_name.into(),
156 data_type,
157 unit: String::new(),
158 min: None,
159 max: None,
160 required: false,
161 }
162 }
163
164 pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
166 self.unit = unit.into();
167 self
168 }
169
170 pub fn with_min(mut self, min: f64) -> Self {
172 self.min = Some(min);
173 self
174 }
175
176 pub fn with_max(mut self, max: f64) -> Self {
178 self.max = Some(max);
179 self
180 }
181
182 pub fn with_required(mut self, required: bool) -> Self {
184 self.required = required;
185 self
186 }
187}
188
189impl From<SdkMetricDefinition> for MetricDescriptor {
190 fn from(def: SdkMetricDefinition) -> Self {
191 Self {
192 name: def.name,
193 display_name: def.display_name,
194 data_type: def.data_type.into(),
195 unit: def.unit,
196 min: def.min,
197 max: def.max,
198 required: def.required,
199 }
200 }
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205#[serde(untagged)]
206#[derive(Default)]
207pub enum SdkMetricValue {
208 Float(f64),
209 Integer(i64),
210 Boolean(bool),
211 String(String),
212 Binary(Vec<u8>),
213 #[default]
214 Null,
215}
216
217impl From<SdkMetricValue> for MetricValue {
218 fn from(v: SdkMetricValue) -> Self {
219 match v {
220 SdkMetricValue::Float(f) => MetricValue::Float(f),
221 SdkMetricValue::Integer(i) => MetricValue::Integer(i),
222 SdkMetricValue::Boolean(b) => MetricValue::Boolean(b),
223 SdkMetricValue::String(s) => MetricValue::String(s),
224 SdkMetricValue::Binary(b) => MetricValue::Binary(b),
225 SdkMetricValue::Null => MetricValue::Null,
226 }
227 }
228}
229
230impl From<MetricValue> for SdkMetricValue {
231 fn from(v: MetricValue) -> Self {
232 match v {
233 MetricValue::Float(f) => SdkMetricValue::Float(f),
234 MetricValue::Integer(i) => SdkMetricValue::Integer(i),
235 MetricValue::Boolean(b) => SdkMetricValue::Boolean(b),
236 MetricValue::String(s) => SdkMetricValue::String(s),
237 MetricValue::Binary(b) => SdkMetricValue::Binary(b),
238 MetricValue::Null => SdkMetricValue::Null,
239 }
240 }
241}
242
243impl From<f64> for SdkMetricValue {
244 fn from(v: f64) -> Self {
245 Self::Float(v)
246 }
247}
248
249impl From<i64> for SdkMetricValue {
250 fn from(v: i64) -> Self {
251 Self::Integer(v)
252 }
253}
254
255impl From<bool> for SdkMetricValue {
256 fn from(v: bool) -> Self {
257 Self::Boolean(v)
258 }
259}
260
261impl From<String> for SdkMetricValue {
262 fn from(v: String) -> Self {
263 Self::String(v)
264 }
265}
266
267impl From<&str> for SdkMetricValue {
268 fn from(v: &str) -> Self {
269 Self::String(v.to_string())
270 }
271}
272
273impl From<Vec<u8>> for SdkMetricValue {
274 fn from(v: Vec<u8>) -> Self {
275 Self::Binary(v)
276 }
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct SdkExtensionMetricValue {
286 pub name: String,
288 pub value: SdkMetricValue,
290 pub timestamp: i64,
292}
293
294impl SdkExtensionMetricValue {
295 pub fn new(name: impl Into<String>, value: SdkMetricValue) -> Self {
297 Self {
298 name: name.into(),
299 value,
300 timestamp: {
301 #[cfg(not(target_arch = "wasm32"))]
302 {
303 crate::ipc_types::current_timestamp_ms()
304 }
305 #[cfg(target_arch = "wasm32")]
306 {
307 crate::wasm::bindings::invoke_capability_raw(
309 "system_timestamp",
310 &serde_json::json!({}),
311 )
312 .ok()
313 .and_then(|v| v.get("timestamp_ms").and_then(|t| t.as_i64()))
314 .unwrap_or(0)
315 }
316 },
317 }
318 }
319
320 pub fn with_timestamp(name: impl Into<String>, value: SdkMetricValue, timestamp: i64) -> Self {
322 Self {
323 name: name.into(),
324 value,
325 timestamp,
326 }
327 }
328}
329
330impl From<SdkExtensionMetricValue> for ExtensionMetricValue {
331 fn from(v: SdkExtensionMetricValue) -> Self {
332 Self {
333 name: v.name,
334 value: v.value.into(),
335 timestamp: v.timestamp,
336 }
337 }
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct SdkParameterDefinition {
347 pub name: String,
349 #[serde(default)]
351 pub display_name: String,
352 #[serde(default)]
354 pub description: String,
355 #[serde(default)]
357 pub param_type: SdkMetricDataType,
358 #[serde(default)]
360 pub required: bool,
361 #[serde(skip_serializing_if = "Option::is_none")]
363 pub default_value: Option<SdkMetricValue>,
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub min: Option<f64>,
367 #[serde(skip_serializing_if = "Option::is_none")]
369 pub max: Option<f64>,
370 #[serde(default)]
372 pub options: Vec<String>,
373}
374
375impl SdkParameterDefinition {
376 pub fn new(name: impl Into<String>, param_type: SdkMetricDataType) -> Self {
378 Self {
379 name: name.into(),
380 display_name: String::new(),
381 description: String::new(),
382 param_type,
383 required: true,
384 default_value: None,
385 min: None,
386 max: None,
387 options: Vec::new(),
388 }
389 }
390
391 pub fn with_display_name(mut self, display_name: impl Into<String>) -> Self {
393 self.display_name = display_name.into();
394 self
395 }
396
397 pub fn with_description(mut self, description: impl Into<String>) -> Self {
399 self.description = description.into();
400 self
401 }
402
403 pub fn optional(mut self) -> Self {
405 self.required = false;
406 self
407 }
408
409 pub fn with_default(mut self, default: SdkMetricValue) -> Self {
411 self.default_value = Some(default);
412 self.required = false;
413 self
414 }
415}
416
417impl From<SdkParameterDefinition> for ParameterDefinition {
418 fn from(p: SdkParameterDefinition) -> Self {
419 Self {
420 name: p.name,
421 display_name: p.display_name,
422 description: p.description,
423 param_type: p.param_type.into(),
424 required: p.required,
425 default_value: p.default_value.map(|v| v.into()),
426 min: p.min,
427 max: p.max,
428 options: p.options,
429 }
430 }
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize, Default)]
435pub struct SdkCommandDefinition {
436 pub name: String,
438 #[serde(default)]
440 pub display_name: String,
441 #[serde(default)]
443 pub payload_template: String,
444 #[serde(default)]
446 pub description: String,
447 #[serde(default)]
449 pub parameters: Vec<SdkParameterDefinition>,
450 #[serde(default)]
452 pub fixed_values: std::collections::HashMap<String, serde_json::Value>,
453 #[serde(default)]
455 pub samples: Vec<serde_json::Value>,
456 #[serde(default)]
458 pub parameter_groups: Vec<SdkParameterGroup>,
459}
460
461#[derive(Debug, Clone, Serialize, Deserialize)]
463pub struct SdkParameterGroup {
464 pub name: String,
466 #[serde(default)]
468 pub display_name: String,
469 #[serde(default)]
471 pub description: String,
472 #[serde(default)]
474 pub parameters: Vec<String>,
475}
476
477impl SdkCommandDefinition {
478 pub fn new(name: impl Into<String>) -> Self {
480 Self {
481 name: name.into(),
482 display_name: String::new(),
483 payload_template: String::new(),
484 description: String::new(),
485 parameters: Vec::new(),
486 fixed_values: std::collections::HashMap::new(),
487 samples: Vec::new(),
488 parameter_groups: Vec::new(),
489 }
490 }
491
492 pub fn with_description(mut self, description: impl Into<String>) -> Self {
494 self.description = description.into();
495 self
496 }
497
498 pub fn param(mut self, param: SdkParameterDefinition) -> Self {
500 self.parameters.push(param);
501 self
502 }
503}
504
505impl From<SdkCommandDefinition> for CommandDescriptor {
506 fn from(c: SdkCommandDefinition) -> Self {
507 Self {
508 name: c.name,
509 display_name: c.display_name,
510 description: c.description,
511 payload_template: c.payload_template,
512 parameters: c.parameters.into_iter().map(|p| p.into()).collect(),
513 fixed_values: c.fixed_values,
514 samples: c.samples,
515 parameter_groups: c
516 .parameter_groups
517 .into_iter()
518 .map(|g| ParameterGroup {
519 name: g.name,
520 display_name: g.display_name,
521 description: g.description,
522 parameters: g.parameters,
523 })
524 .collect(),
525 }
526 }
527}
528
529#[derive(Debug, Clone, Serialize, Deserialize)]
535pub enum SdkExtensionError {
536 CommandNotFound(String),
538 InvalidArguments(String),
540 ExecutionFailed(String),
542 Timeout(String),
544 NotFound(String),
546 InvalidFormat(String),
548 LoadFailed(String),
550 SecurityError(String),
552 NotSupported(String),
554 ConfigurationError(String),
556 InternalError(String),
558 Other(String),
560}
561
562impl std::fmt::Display for SdkExtensionError {
563 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
564 match self {
565 Self::CommandNotFound(cmd) => write!(f, "Command not found: {}", cmd),
566 Self::InvalidArguments(msg) => write!(f, "Invalid arguments: {}", msg),
567 Self::ExecutionFailed(msg) => write!(f, "Execution failed: {}", msg),
568 Self::Timeout(msg) => write!(f, "Timeout: {}", msg),
569 Self::NotFound(msg) => write!(f, "Not found: {}", msg),
570 Self::InvalidFormat(msg) => write!(f, "Invalid format: {}", msg),
571 Self::LoadFailed(msg) => write!(f, "Load failed: {}", msg),
572 Self::SecurityError(msg) => write!(f, "Security error: {}", msg),
573 Self::NotSupported(msg) => write!(f, "Not supported: {}", msg),
574 Self::ConfigurationError(msg) => write!(f, "Configuration error: {}", msg),
575 Self::InternalError(msg) => write!(f, "Internal error: {}", msg),
576 Self::Other(msg) => write!(f, "Error: {}", msg),
577 }
578 }
579}
580
581impl std::error::Error for SdkExtensionError {}
582
583impl From<serde_json::Error> for SdkExtensionError {
584 fn from(e: serde_json::Error) -> Self {
585 Self::InvalidFormat(e.to_string())
586 }
587}
588
589impl From<SdkExtensionError> for ExtensionError {
590 fn from(e: SdkExtensionError) -> Self {
591 match e {
592 SdkExtensionError::CommandNotFound(s) => ExtensionError::CommandNotFound(s),
593 SdkExtensionError::InvalidArguments(s) => ExtensionError::InvalidArguments(s),
594 SdkExtensionError::ExecutionFailed(s) => ExtensionError::ExecutionFailed(s),
595 SdkExtensionError::Timeout(s) => ExtensionError::Timeout(s),
596 SdkExtensionError::NotFound(s) => ExtensionError::NotFound(s),
597 SdkExtensionError::InvalidFormat(s) => ExtensionError::InvalidFormat(s),
598 SdkExtensionError::LoadFailed(s) => ExtensionError::LoadFailed(s),
599 SdkExtensionError::SecurityError(s) => ExtensionError::SecurityError(s),
600 SdkExtensionError::NotSupported(s) => ExtensionError::NotSupported(s),
601 SdkExtensionError::ConfigurationError(s) => ExtensionError::ConfigurationError(s),
602 SdkExtensionError::InternalError(s) => ExtensionError::InternalError(s),
603 SdkExtensionError::Other(s) => ExtensionError::Other(s),
604 }
605 }
606}
607
608pub type SdkResult<T> = std::result::Result<T, SdkExtensionError>;
610
611#[derive(Debug, Clone, Serialize, Deserialize)]
617pub struct FrontendManifest {
618 pub id: String,
620 pub version: String,
622 #[serde(default = "default_entrypoint")]
624 pub entrypoint: String,
625 pub style_entrypoint: Option<String>,
627 pub components: Vec<FrontendComponent>,
629 pub i18n: Option<I18nConfig>,
631 #[serde(default)]
633 pub dependencies: HashMap<String, String>,
634}
635
636fn default_entrypoint() -> String {
637 "index.js".to_string()
638}
639
640#[derive(Debug, Clone, Serialize, Deserialize)]
642pub struct FrontendComponent {
643 pub name: String,
645 #[serde(rename = "type")]
647 pub component_type: FrontendComponentType,
648 pub display_name: String,
650 #[serde(skip_serializing_if = "Option::is_none")]
652 pub description: Option<String>,
653 #[serde(skip_serializing_if = "Option::is_none")]
655 pub icon: Option<String>,
656 #[serde(skip_serializing_if = "Option::is_none")]
658 pub default_size: Option<ComponentSize>,
659 #[serde(skip_serializing_if = "Option::is_none")]
661 pub min_size: Option<ComponentSize>,
662 #[serde(skip_serializing_if = "Option::is_none")]
664 pub max_size: Option<ComponentSize>,
665 #[serde(skip_serializing_if = "Option::is_none")]
667 pub config_schema: Option<serde_json::Value>,
668 #[serde(default = "default_true")]
670 pub refreshable: bool,
671 #[serde(default)]
673 pub refresh_interval: u64,
674}
675
676fn default_true() -> bool {
677 true
678}
679
680#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
682#[serde(rename_all = "lowercase")]
683pub enum FrontendComponentType {
684 Card,
686 Widget,
688 Panel,
690 Dialog,
692 Settings,
694}
695
696#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
698pub struct ComponentSize {
699 pub width: u32,
701 pub height: u32,
703}
704
705impl ComponentSize {
706 pub fn new(width: u32, height: u32) -> Self {
708 Self { width, height }
709 }
710}
711
712#[derive(Debug, Clone, Serialize, Deserialize)]
714pub struct I18nConfig {
715 #[serde(default = "default_language")]
717 pub default_language: String,
718 pub supported_languages: Vec<String>,
720 #[serde(skip_serializing_if = "Option::is_none")]
722 pub resources_path: Option<String>,
723}
724
725fn default_language() -> String {
726 "en".to_string()
727}
728
729pub struct FrontendManifestBuilder {
735 manifest: FrontendManifest,
736}
737
738impl FrontendManifestBuilder {
739 pub fn new(id: impl Into<String>, version: impl Into<String>) -> Self {
741 Self {
742 manifest: FrontendManifest {
743 id: id.into(),
744 version: version.into(),
745 entrypoint: default_entrypoint(),
746 style_entrypoint: None,
747 components: Vec::new(),
748 i18n: None,
749 dependencies: HashMap::new(),
750 },
751 }
752 }
753
754 pub fn entrypoint(mut self, path: impl Into<String>) -> Self {
756 self.manifest.entrypoint = path.into();
757 self
758 }
759
760 pub fn style_entrypoint(mut self, path: impl Into<String>) -> Self {
762 self.manifest.style_entrypoint = Some(path.into());
763 self
764 }
765
766 pub fn component(mut self, component: FrontendComponent) -> Self {
768 self.manifest.components.push(component);
769 self
770 }
771
772 pub fn card(mut self, name: impl Into<String>, display_name: impl Into<String>) -> Self {
774 self.manifest.components.push(FrontendComponent {
775 name: name.into(),
776 component_type: FrontendComponentType::Card,
777 display_name: display_name.into(),
778 description: None,
779 icon: None,
780 default_size: None,
781 min_size: None,
782 max_size: None,
783 config_schema: None,
784 refreshable: true,
785 refresh_interval: 0,
786 });
787 self
788 }
789
790 pub fn widget(mut self, name: impl Into<String>, display_name: impl Into<String>) -> Self {
792 self.manifest.components.push(FrontendComponent {
793 name: name.into(),
794 component_type: FrontendComponentType::Widget,
795 display_name: display_name.into(),
796 description: None,
797 icon: None,
798 default_size: None,
799 min_size: None,
800 max_size: None,
801 config_schema: None,
802 refreshable: true,
803 refresh_interval: 0,
804 });
805 self
806 }
807
808 pub fn panel(mut self, name: impl Into<String>, display_name: impl Into<String>) -> Self {
810 self.manifest.components.push(FrontendComponent {
811 name: name.into(),
812 component_type: FrontendComponentType::Panel,
813 display_name: display_name.into(),
814 description: None,
815 icon: None,
816 default_size: None,
817 min_size: None,
818 max_size: None,
819 config_schema: None,
820 refreshable: true,
821 refresh_interval: 0,
822 });
823 self
824 }
825
826 pub fn i18n(mut self, config: I18nConfig) -> Self {
828 self.manifest.i18n = Some(config);
829 self
830 }
831
832 pub fn dependency(mut self, name: impl Into<String>, version: impl Into<String>) -> Self {
834 self.manifest
835 .dependencies
836 .insert(name.into(), version.into());
837 self
838 }
839
840 pub fn build(self) -> FrontendManifest {
842 self.manifest
843 }
844}
845
846pub struct ArgParser<'a> {
852 args: &'a serde_json::Value,
853}
854
855impl<'a> ArgParser<'a> {
856 pub fn new(args: &'a serde_json::Value) -> Self {
858 Self { args }
859 }
860
861 pub fn get_string(&self, name: &str) -> SdkResult<String> {
863 self.args
864 .get(name)
865 .and_then(|v| v.as_str())
866 .map(|s| s.to_string())
867 .ok_or_else(|| {
868 SdkExtensionError::InvalidArguments(format!(
869 "Missing or invalid string argument: {}",
870 name
871 ))
872 })
873 }
874
875 pub fn get_optional_string(&self, name: &str) -> Option<String> {
877 self.args
878 .get(name)
879 .and_then(|v| v.as_str())
880 .map(|s| s.to_string())
881 }
882
883 pub fn get_i64(&self, name: &str) -> SdkResult<i64> {
885 self.args.get(name).and_then(|v| v.as_i64()).ok_or_else(|| {
886 SdkExtensionError::InvalidArguments(format!(
887 "Missing or invalid integer argument: {}",
888 name
889 ))
890 })
891 }
892
893 pub fn get_optional_i64(&self, name: &str) -> Option<i64> {
895 self.args.get(name).and_then(|v| v.as_i64())
896 }
897
898 pub fn get_f64(&self, name: &str) -> SdkResult<f64> {
900 self.args.get(name).and_then(|v| v.as_f64()).ok_or_else(|| {
901 SdkExtensionError::InvalidArguments(format!(
902 "Missing or invalid float argument: {}",
903 name
904 ))
905 })
906 }
907
908 pub fn get_optional_f64(&self, name: &str) -> Option<f64> {
910 self.args.get(name).and_then(|v| v.as_f64())
911 }
912
913 pub fn get_bool(&self, name: &str) -> SdkResult<bool> {
915 self.args
916 .get(name)
917 .and_then(|v| v.as_bool())
918 .ok_or_else(|| {
919 SdkExtensionError::InvalidArguments(format!(
920 "Missing or invalid boolean argument: {}",
921 name
922 ))
923 })
924 }
925
926 pub fn get_optional_bool(&self, name: &str) -> Option<bool> {
928 self.args.get(name).and_then(|v| v.as_bool())
929 }
930
931 pub fn get_object(&self, name: &str) -> SdkResult<&serde_json::Map<String, serde_json::Value>> {
933 self.args
934 .get(name)
935 .and_then(|v| v.as_object())
936 .ok_or_else(|| {
937 SdkExtensionError::InvalidArguments(format!(
938 "Missing or invalid object argument: {}",
939 name
940 ))
941 })
942 }
943
944 pub fn get_array(&self, name: &str) -> SdkResult<&Vec<serde_json::Value>> {
946 self.args
947 .get(name)
948 .and_then(|v| v.as_array())
949 .ok_or_else(|| {
950 SdkExtensionError::InvalidArguments(format!(
951 "Missing or invalid array argument: {}",
952 name
953 ))
954 })
955 }
956
957 pub fn parse<T: for<'de> Deserialize<'de>>(&self) -> SdkResult<T> {
959 serde_json::from_value(self.args.clone()).map_err(Into::into)
960 }
961}
962
963#[cfg(test)]
968mod tests {
969 use super::*;
970
971 #[test]
972 fn test_metric_data_type_serialization() {
973 let types = vec![
974 (SdkMetricDataType::Float, r#""float""#),
975 (SdkMetricDataType::Integer, r#""integer""#),
976 (SdkMetricDataType::Boolean, r#""boolean""#),
977 (SdkMetricDataType::String, r#""string""#),
978 (SdkMetricDataType::Binary, r#""binary""#),
979 ];
980
981 for (dtype, expected) in types {
982 let json = serde_json::to_string(&dtype).unwrap();
983 assert_eq!(json, expected);
984
985 let deserialized: SdkMetricDataType = serde_json::from_str(expected).unwrap();
986 assert_eq!(dtype, deserialized);
987 }
988
989 let enum_type = SdkMetricDataType::Enum {
991 options: vec!["option1".to_string(), "option2".to_string()],
992 };
993 let json = serde_json::to_string(&enum_type).unwrap();
994 assert!(json.contains("enum"));
995 assert!(json.contains("options"));
996 }
997
998 #[test]
999 fn test_metric_definition_serialization() {
1000 let metric = SdkMetricDefinition {
1001 name: "test_metric".to_string(),
1002 display_name: "Test Metric".to_string(),
1003 data_type: SdkMetricDataType::Float,
1004 unit: "°C".to_string(),
1005 min: Some(0.0),
1006 max: Some(100.0),
1007 required: true,
1008 };
1009
1010 let json = serde_json::to_string(&metric).unwrap();
1011 assert!(json.contains("test_metric"));
1012 assert!(json.contains("float"));
1013
1014 let deserialized: SdkMetricDefinition = serde_json::from_str(&json).unwrap();
1015 assert_eq!(metric.name, deserialized.name);
1016 assert_eq!(metric.unit, deserialized.unit);
1017 }
1018
1019 #[test]
1020 fn test_extension_metadata_serialization() {
1021 let meta = SdkExtensionMetadata {
1022 id: "test-ext".to_string(),
1023 name: "Test Extension".to_string(),
1024 version: "1.0.0".to_string(),
1025 description: Some("A test extension".to_string()),
1026 author: Some("Test Author".to_string()),
1027 sdk_version: Some("0.5.11".to_string()),
1028 extension_type: "native".to_string(),
1029 };
1030
1031 let json = serde_json::to_string(&meta).unwrap();
1032 assert!(json.contains("test-ext"));
1033 assert!(json.contains("1.0.0"));
1034
1035 let deserialized: SdkExtensionMetadata = serde_json::from_str(&json).unwrap();
1036 assert_eq!(meta.id, deserialized.id);
1037 assert_eq!(meta.version, deserialized.version);
1038 }
1039
1040 #[test]
1041 fn test_extension_error_serialization() {
1042 let error = SdkExtensionError::InvalidArguments("test error".to_string());
1043 let json = serde_json::to_string(&error).unwrap();
1044 assert!(json.contains("InvalidArguments"));
1045
1046 assert!(error.to_string().contains("test error"));
1048 }
1049
1050 #[test]
1051 fn test_type_conversions() {
1052 let sdk_dt = SdkMetricDataType::Float;
1054 let dt: MetricDataType = sdk_dt.clone().into();
1055 assert!(matches!(dt, MetricDataType::Float));
1056 let back: SdkMetricDataType = dt.into();
1057 assert!(matches!(back, SdkMetricDataType::Float));
1058
1059 let sdk_v = SdkMetricValue::Integer(42);
1061 let v: MetricValue = sdk_v.into();
1062 assert!(matches!(v, MetricValue::Integer(42)));
1063 }
1064}