poststation_api_icd/
rest.rs

1//! Rest API version of types
2//!
3//! This uses Schemars instead of postcard-schema, and avoid types like `u64` that
4//! will make JSON/JS sad.
5//!
6//! At some point in the future we will publish an OpenAPI spec for all available requests.
7//! For now, here is a listing of all endpoints and an example CURL request for each of them.
8//!
9//! ## "Get Devices"
10//!
11//! ```sh
12//! curl http://localhost:4444/api/devices -q -H "Accept: application/json"
13//! ```
14//!
15//! ```json
16//! [
17//!   {
18//!     "serial": "3836363937050630",
19//!     "name": "XRAY-013",
20//!     "is_connected": false,
21//!     "manufacturer": "OneVariable",
22//!     "product": "poststation-pico"
23//!   },
24//!   {
25//!     "serial": "6E43B25479AC185C",
26//!     "name": "YACHTY-312",
27//!     "is_connected": true,
28//!     "manufacturer": "Simulator",
29//!     "product": "Product"
30//!   },
31//! ]
32//! ```
33//!
34//! ## "Get Schemas"
35//!
36//! ```sh
37//!  curl http://localhost:4444/api/devices/CA9FF06E058FF9A6/schemas -q -H "Accept: application/json"
38//! ```
39//!
40//! Output: <https://gist.github.com/jamesmunns/0a533d8ed8ffbbc34c282da848a162fd>
41//!
42//! ## "Get Logs"
43//!
44//! ```sh
45//! curl 'http://localhost:4444/api/devices/3836363937050630/logs?serial=3836363937050630&count=2' \
46//! -q -H "Accept: application/json"
47//! ```
48//!
49//! ```json
50//! [
51//!   {
52//!     "uuidv7": "01936033-6231-71f2-9200-49361527b270",
53//!     "msg": "Uptime: Duration { ticks: 1347000000 } freq: 125000000"
54//!   },
55//!   {
56//!     "uuidv7": "01936033-6de8-7db0-8a7c-00563efac872",
57//!     "msg": "Uptime: Duration { ticks: 1350000000 } freq: 125000000"
58//!   }
59//! ]
60//! ```
61//!
62//! ## "Get Range of logs"
63//!
64//! This API is used as a paginated version of "Get Logs". You can use either a UTC millisecond timestamp
65//! or the UUIDv7 of a log item as the "anchor" of the request, and then request N logs "Before" or "After" the
66//! anchor (excluding the anchor itself).
67//!
68//! ### Using a UUIDv7 of a log entry as the anchor
69//!
70//! ```sh
71//! curl 'http://localhost:4444/api/devices/3836363937050630/logs/range?serial=3836363937050630&count=4&uuid=01936032-e149-7e92-b4ca-f7e8a30e11cb&direction=After' \
72//!   -q -H "Accept: application/json" | jq
73//! ```
74//!
75//! ```json
76//! [
77//!   {
78//!     "uuidv7": "01936033-1029-7e32-8b45-dc4595c98ee8",
79//!     "msg": "Uptime: Duration { ticks: 1326000000 } freq: 125000000"
80//!   },
81//!   {
82//!     "uuidv7": "01936033-0471-7912-9eaa-f3db32a47387",
83//!     "msg": "Uptime: Duration { ticks: 1323000000 } freq: 125000000"
84//!   },
85//!   {
86//!     "uuidv7": "01936032-f8b9-78e1-929e-99051b2bba64",
87//!     "msg": "Uptime: Duration { ticks: 1320000000 } freq: 125000000"
88//!   },
89//!   {
90//!     "uuidv7": "01936032-ed01-7ca0-99ad-f9ccac6c7e22",
91//!     "msg": "Uptime: Duration { ticks: 1317000000 } freq: 125000000"
92//!   }
93//! ]
94//! ```
95//!
96//! ### Using a unix millisecond timestamp as the anchor
97//!
98//! ```sh
99//! curl 'http://localhost:4444/api/devices/3836363937050630/logs/range?serial=3836363937050630&count=4&unix_ms_ts=1732485767497&direction=After' \
100//! -q -H "Accept: application/json" | jq
101//!
102//! ```json
103//! [
104//!   {
105//!     "uuidv7": "01936033-0471-7912-9eaa-f3db32a47387",
106//!     "msg": "Uptime: Duration { ticks: 1323000000 } freq: 125000000"
107//!   },
108//!   {
109//!     "uuidv7": "01936032-f8b9-78e1-929e-99051b2bba64",
110//!     "msg": "Uptime: Duration { ticks: 1320000000 } freq: 125000000"
111//!   },
112//!   {
113//!     "uuidv7": "01936032-ed01-7ca0-99ad-f9ccac6c7e22",
114//!     "msg": "Uptime: Duration { ticks: 1317000000 } freq: 125000000"
115//!   },
116//!   {
117//!     "uuidv7": "01936032-e149-7e92-b4ca-f7e8a30e11cb",
118//!     "msg": "Uptime: Duration { ticks: 1314000000 } freq: 125000000"
119//!   }
120//! ]
121//! ```
122//!
123//! ## "Get Topic Messages"
124//!
125//! ```sh
126//! curl 'http://localhost:4444/api/devices/CA9FF06E058FF9A6/topics?path=simulator/temperature&key=583A352440D70716&count=3' \
127//!     -H "Accept: application/json"
128//! ```
129//!
130//! ```json
131//! [
132//!   {
133//!     "uuidv7": "01938dff-2bad-7ae1-9e3f-0ce6e2805ec0",
134//!     "msg": {
135//!       "temp": 3207.5660993151787
136//!     }
137//!   },
138//!   {
139//!     "uuidv7": "01938dff-2da1-7301-9654-7e3d338cf1eb",
140//!     "msg": {
141//!       "temp": 3210.7076919687684
142//!     }
143//!   },
144//!   {
145//!     "uuidv7": "01938dff-2f95-7b22-b71b-16dcd0c34f5a",
146//!     "msg": {
147//!       "temp": 3213.8492846223585
148//!     }
149//!   }
150//! ]
151//! ```
152//!
153//! ## "Proxy an endpoint request"
154//!
155//! ```sh
156//! curl \
157//!     -X POST \
158//!     -H 'Content-Type: application/json' \
159//!     -H "Accept: application/json" \
160//!     'http://localhost:4444/api/devices/CA9FF06E058FF9A6/proxy' \
161//!     -d '{
162//!         "path": "postcard-rpc/ping",
163//!         "req_key": "E8EDEF24F26C7C91",
164//!         "resp_key": "E8EDEF24F26C7C91",
165//!         "seq_no": 0,
166//!         "body": 123
167//!     }'
168//! ```
169//!
170//! ```json
171//! {
172//!   "resp_key": "E8EDEF24F26C7C91",
173//!   "seq_no": 0,
174//!   "body": 123
175//! }
176//! ```
177//!
178//! ## "Proxy a topic publish"
179//!
180//! ```sh
181//! curl \
182//!     -X POST \
183//!     -H 'Content-Type: application/json' \
184//!     -H "Accept: application/json" \
185//!     'http://localhost:4444/api/devices/CA9FF06E058FF9A6/publish' \
186//!     -d '{
187//!         "path": "some/topic/into/server",
188//!         "topic_key": "E8EDEF24F26C7C91",
189//!         "seq_no": 0,
190//!         "body": { "some": "payload" }
191//!     }'
192//! ```
193//!
194//! ```json
195//! {}
196//! ```
197//!
198//! # "Subscribe to a stream of topic_out messages"
199//!
200//! This is a **WebSocket** endpoint, which gives you a live feed of a specific topic from a
201//! specific device.
202//!
203//! ```sh
204//! websocat "ws://localhost:4444/api/devices/CA9FF06E058FF9A6/listen?path=simulator/temperature&key=583A352440D70716" | jq
205//! ```
206//!
207//! ```json
208//! {
209//!   "msg": {
210//!     "temp": 2726.9024233159403
211//!   },
212//!   "seq_no": 868
213//! }
214//! {
215//!   "msg": {
216//!     "temp": 2730.0440159695304
217//!   },
218//!   "seq_no": 869
219//! }
220//! {
221//!   "msg": {
222//!     "temp": 2733.18560862312
223//!   },
224//!   "seq_no": 870
225//! }
226//! ```
227
228use schemars::JsonSchema;
229use serde::{Deserialize, Serialize};
230use uuid::Uuid;
231
232#[derive(Debug, PartialEq, Serialize, Deserialize, Hash, JsonSchema)]
233pub struct DeviceData {
234    pub serial: String,
235    pub name: String,
236    pub is_connected: bool,
237    pub manufacturer: Option<String>,
238    pub product: Option<String>,
239}
240
241#[derive(Debug, PartialEq, Serialize, Deserialize, Hash, JsonSchema)]
242pub struct LogRequest {
243    pub count: u32,
244}
245
246#[derive(Debug, PartialEq, Serialize, Deserialize, Hash, JsonSchema)]
247pub struct Log {
248    pub uuidv7: Uuid,
249    pub msg: String,
250}
251
252#[derive(Debug, PartialEq, Serialize, Deserialize, Hash, JsonSchema)]
253pub struct LogRangeRequest {
254    pub uuid: Option<Uuid>,
255    pub unix_ms_ts: Option<u64>,
256    pub direction: Direction,
257    pub count: u32,
258}
259
260#[derive(Debug, PartialEq, Serialize, Deserialize, Hash, JsonSchema)]
261pub enum Direction {
262    Before,
263    After,
264}
265
266#[derive(Debug, PartialEq, Serialize, Deserialize, Hash, JsonSchema)]
267pub struct TopicRequest {
268    pub path: String,
269    pub key: foreign::Key,
270    pub count: u32,
271}
272
273#[derive(Debug, PartialEq, Serialize, Deserialize, Hash, JsonSchema)]
274pub struct TopicMsg {
275    pub uuidv7: Uuid,
276    pub msg: serde_json::Value,
277}
278
279#[derive(Debug, PartialEq, Serialize, Deserialize, Hash, JsonSchema)]
280pub struct TopicStreamRequest {
281    pub path: String,
282    pub key: foreign::Key,
283}
284
285#[derive(Debug, PartialEq, Serialize, Deserialize, Hash, JsonSchema)]
286pub struct TopicStreamMsg {
287    pub stream_id: Uuid,
288    pub msg: serde_json::Value,
289}
290
291#[derive(Debug, PartialEq, Serialize, Deserialize, Hash, JsonSchema)]
292pub enum TopicStreamResult {
293    Started(Uuid),
294    NoDeviceKnown,
295    DeviceDisconnected,
296    NoSuchTopic,
297}
298
299#[derive(Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
300pub struct ProxyRequest {
301    pub path: String,
302    pub req_key: foreign::Key,
303    pub resp_key: foreign::Key,
304    pub seq_no: u32,
305    pub body: serde_json::Value,
306}
307
308#[derive(Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
309pub struct ProxyResponseOk {
310    pub resp_key: foreign::Key,
311    pub seq_no: u32,
312    pub body: serde_json::Value,
313}
314
315#[derive(Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
316pub enum ProxyResponseError {
317    WireErr {
318        resp_key: foreign::Key,
319        seq_no: u32,
320        body: foreign::WireError,
321    },
322    OtherErr(String),
323}
324
325#[derive(Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
326pub struct PublishRequest {
327    pub path: String,
328    pub topic_key: foreign::Key,
329    pub seq_no: u32,
330    pub body: serde_json::Value,
331}
332
333/// These are types from other crates I'm pasting here just so I can impl JsonSchema on it
334pub mod foreign {
335    use std::collections::HashSet;
336
337    use schema::OwnedNamedType;
338    use schemars::JsonSchema;
339    use serde::{Deserialize, Serialize};
340
341    impl From<postcard_rpc::Key> for Key {
342        fn from(value: postcard_rpc::Key) -> Self {
343            Self(format!("{:016X}", u64::from_le_bytes(value.to_bytes())))
344        }
345    }
346
347    impl TryFrom<Key> for postcard_rpc::Key {
348        type Error = String;
349        fn try_from(value: Key) -> Result<Self, Self::Error> {
350            let Ok(val) = u64::from_str_radix(&value.0, 16) else {
351                return Err(value.0);
352            };
353            unsafe { Ok(postcard_rpc::Key::from_bytes(val.to_le_bytes())) }
354        }
355    }
356
357    #[derive(
358        Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize, Hash, JsonSchema,
359    )]
360    pub struct Key(String);
361
362    /// The given frame was too long
363    #[derive(Serialize, Deserialize, Debug, PartialEq, JsonSchema)]
364    pub struct FrameTooLong {
365        /// The length of the too-long frame
366        pub len: u32,
367        /// The maximum frame length supported
368        pub max: u32,
369    }
370
371    /// The given frame was too short
372    #[derive(Serialize, Deserialize, Debug, PartialEq, JsonSchema)]
373    pub struct FrameTooShort {
374        /// The length of the too-short frame
375        pub len: u32,
376    }
377
378    /// A protocol error that is handled outside of the normal request type, usually
379    /// indicating a protocol-level error
380    #[derive(Serialize, Deserialize, Debug, PartialEq, JsonSchema)]
381    pub enum WireError {
382        /// The frame exceeded the buffering capabilities of the server
383        FrameTooLong(FrameTooLong),
384        /// The frame was shorter than the minimum frame size and was rejected
385        FrameTooShort(FrameTooShort),
386        /// Deserialization of a message failed
387        DeserFailed,
388        /// Serialization of a message failed, usually due to a lack of space to
389        /// buffer the serialized form
390        SerFailed,
391        /// The key associated with this request was unknown
392        UnknownKey,
393        /// The server was unable to spawn the associated handler, typically due
394        /// to an exhaustion of resources
395        FailedToSpawn,
396        /// The provided key is below the minimum key size calculated to avoid hash
397        /// collisions, and was rejected to avoid potential misunderstanding
398        KeyTooSmall,
399    }
400
401    /// A report describing the schema spoken by the connected device
402    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)]
403    pub struct SchemaReport {
404        /// All custom types spoken by the device (on any endpoint or topic),
405        /// as well as all primitive types. In the future, primitive types may
406        /// be removed.
407        pub types: HashSet<OwnedNamedType>,
408        /// All incoming (client to server) topics reported by the device
409        pub topics_in: Vec<TopicReport>,
410        /// All outgoing (server to client) topics reported by the device
411        pub topics_out: Vec<TopicReport>,
412        /// All endpoints reported by the device
413        pub endpoints: Vec<EndpointReport>,
414    }
415
416    impl From<postcard_rpc::host_client::SchemaReport> for SchemaReport {
417        fn from(value: postcard_rpc::host_client::SchemaReport) -> Self {
418            Self {
419                types: value.types.iter().map(Into::into).collect(),
420                topics_in: value.topics_in.into_iter().map(Into::into).collect(),
421                topics_out: value.topics_out.into_iter().map(Into::into).collect(),
422                endpoints: value.endpoints.into_iter().map(Into::into).collect(),
423            }
424        }
425    }
426
427    impl From<postcard_rpc::host_client::TopicReport> for TopicReport {
428        fn from(value: postcard_rpc::host_client::TopicReport) -> Self {
429            Self {
430                path: value.path,
431                key: value.key.into(),
432                ty: (&value.ty).into(),
433            }
434        }
435    }
436
437    /// A description of a single Topic
438    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)]
439    pub struct TopicReport {
440        /// The human readable path of the topic
441        pub path: String,
442        /// The Key of the topic (which hashes the path and type)
443        pub key: Key,
444        /// The schema of the type of the message
445        pub ty: OwnedNamedType,
446    }
447
448    impl From<postcard_rpc::host_client::EndpointReport> for EndpointReport {
449        fn from(value: postcard_rpc::host_client::EndpointReport) -> Self {
450            Self {
451                path: value.path,
452                req_key: value.req_key.into(),
453                req_ty: (&value.req_ty).into(),
454                resp_key: value.resp_key.into(),
455                resp_ty: (&value.resp_ty).into(),
456            }
457        }
458    }
459
460    /// A description of a single Endpoint
461    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)]
462    pub struct EndpointReport {
463        /// The human readable path of the endpoint
464        pub path: String,
465        /// The Key of the request (which hashes the path and type)
466        pub req_key: Key,
467        /// The schema of the request type
468        pub req_ty: OwnedNamedType,
469        /// The Key of the response (which hashes the path and type)
470        pub resp_key: Key,
471        /// The schema of the response type
472        pub resp_ty: OwnedNamedType,
473    }
474
475    pub mod schema {
476        //! Owned + JSON friendly Schema version
477
478        use postcard_schema::schema::owned as real;
479        use schemars::JsonSchema;
480        use serde::{Deserialize, Serialize};
481        use std::{boxed::Box, ops::Deref, string::String, vec::Vec};
482
483        // ---
484
485        /// The owned version of [`NamedType`]
486        #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
487        pub struct OwnedNamedType {
488            /// The name of this type
489            pub name: String,
490            /// The type
491            pub ty: OwnedDataModelType,
492        }
493
494        impl From<&real::OwnedNamedType> for OwnedNamedType {
495            fn from(value: &real::OwnedNamedType) -> Self {
496                Self {
497                    name: value.name.to_string(),
498                    ty: (&value.ty).into(),
499                }
500            }
501        }
502
503        // ---
504
505        /// The owned version of [`DataModelType`]
506        #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
507        pub enum OwnedDataModelType {
508            /// The `bool` Serde Data Model Type
509            Bool,
510
511            /// The `i8` Serde Data Model Type
512            I8,
513
514            /// The `u8` Serde Data Model Type
515            U8,
516
517            /// A variably encoded i16
518            I16,
519
520            /// A variably encoded i32
521            I32,
522
523            /// A variably encoded i64
524            I64,
525
526            /// A variably encoded i128
527            I128,
528
529            /// A variably encoded u16
530            U16,
531
532            /// A variably encoded u32
533            U32,
534
535            /// A variably encoded u64
536            U64,
537
538            /// A variably encoded u128
539            U128,
540
541            /// A variably encoded usize
542            Usize,
543
544            /// A variably encoded isize
545            Isize,
546
547            /// The `f32` Serde Data Model Type
548            F32,
549
550            /// The `f64 Serde Data Model Type
551            F64,
552
553            /// The `char` Serde Data Model Type
554            Char,
555
556            /// The `String` Serde Data Model Type
557            String,
558
559            /// The `&[u8]` Serde Data Model Type
560            ByteArray,
561
562            /// The `Option<T>` Serde Data Model Type
563            Option(Box<OwnedNamedType>),
564
565            /// The `()` Serde Data Model Type
566            Unit,
567
568            /// The "unit struct" Serde Data Model Type
569            UnitStruct,
570
571            /// The "newtype struct" Serde Data Model Type
572            NewtypeStruct(Box<OwnedNamedType>),
573
574            /// The "Sequence" Serde Data Model Type
575            Seq(Box<OwnedNamedType>),
576
577            /// The "Tuple" Serde Data Model Type
578            Tuple(Vec<OwnedNamedType>),
579
580            /// The "Tuple Struct" Serde Data Model Type
581            TupleStruct(Vec<OwnedNamedType>),
582
583            /// The "Map" Serde Data Model Type
584            Map {
585                /// The map "Key" type
586                key: Box<OwnedNamedType>,
587                /// The map "Value" type
588                val: Box<OwnedNamedType>,
589            },
590
591            /// The "Struct" Serde Data Model Type
592            Struct(Vec<OwnedNamedValue>),
593
594            /// The "Enum" Serde Data Model Type (which contains any of the "Variant" types)
595            Enum(Vec<OwnedNamedVariant>),
596
597            /// A NamedType/OwnedNamedType
598            Schema,
599        }
600
601        impl From<&real::OwnedDataModelType> for OwnedDataModelType {
602            fn from(other: &real::OwnedDataModelType) -> Self {
603                match other {
604                    real::OwnedDataModelType::Bool => Self::Bool,
605                    real::OwnedDataModelType::I8 => Self::I8,
606                    real::OwnedDataModelType::U8 => Self::U8,
607                    real::OwnedDataModelType::I16 => Self::I16,
608                    real::OwnedDataModelType::I32 => Self::I32,
609                    real::OwnedDataModelType::I64 => Self::I64,
610                    real::OwnedDataModelType::I128 => Self::I128,
611                    real::OwnedDataModelType::U16 => Self::U16,
612                    real::OwnedDataModelType::U32 => Self::U32,
613                    real::OwnedDataModelType::U64 => Self::U64,
614                    real::OwnedDataModelType::U128 => Self::U128,
615                    real::OwnedDataModelType::Usize => Self::Usize,
616                    real::OwnedDataModelType::Isize => Self::Isize,
617                    real::OwnedDataModelType::F32 => Self::F32,
618                    real::OwnedDataModelType::F64 => Self::F64,
619                    real::OwnedDataModelType::Char => Self::Char,
620                    real::OwnedDataModelType::String => Self::String,
621                    real::OwnedDataModelType::ByteArray => Self::ByteArray,
622                    real::OwnedDataModelType::Option(o) => Self::Option(Box::new(o.deref().into())),
623                    real::OwnedDataModelType::Unit => Self::Unit,
624                    real::OwnedDataModelType::UnitStruct => Self::UnitStruct,
625                    real::OwnedDataModelType::NewtypeStruct(nts) => {
626                        Self::NewtypeStruct(Box::new(nts.deref().into()))
627                    }
628                    real::OwnedDataModelType::Seq(s) => Self::Seq(Box::new(s.deref().into())),
629                    real::OwnedDataModelType::Tuple(t) => {
630                        Self::Tuple(t.iter().map(|i| i.into()).collect())
631                    }
632                    real::OwnedDataModelType::TupleStruct(ts) => {
633                        Self::TupleStruct(ts.iter().map(|i| i.into()).collect())
634                    }
635                    real::OwnedDataModelType::Map { key, val } => Self::Map {
636                        key: Box::new(key.deref().into()),
637                        val: Box::new(val.deref().into()),
638                    },
639                    real::OwnedDataModelType::Struct(s) => {
640                        Self::Struct(s.iter().map(|i| i.into()).collect())
641                    }
642                    real::OwnedDataModelType::Enum(e) => {
643                        Self::Enum(e.iter().map(|i| i.into()).collect())
644                    }
645                    real::OwnedDataModelType::Schema => Self::Schema,
646                }
647            }
648        }
649
650        // ---
651
652        /// The owned version of [`DataModelVariant`]
653        #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
654        pub enum OwnedDataModelVariant {
655            /// The "unit variant" Serde Data Model Type
656            UnitVariant,
657            /// The "newtype variant" Serde Data Model Type
658            NewtypeVariant(Box<OwnedNamedType>),
659            /// The "Tuple Variant" Serde Data Model Type
660            TupleVariant(Vec<OwnedNamedType>),
661            /// The "Struct Variant" Serde Data Model Type
662            StructVariant(Vec<OwnedNamedValue>),
663        }
664
665        impl From<&real::OwnedDataModelVariant> for OwnedDataModelVariant {
666            fn from(value: &real::OwnedDataModelVariant) -> Self {
667                match value {
668                    real::OwnedDataModelVariant::UnitVariant => Self::UnitVariant,
669                    real::OwnedDataModelVariant::NewtypeVariant(d) => {
670                        Self::NewtypeVariant(Box::new(d.deref().into()))
671                    }
672                    real::OwnedDataModelVariant::TupleVariant(d) => {
673                        Self::TupleVariant(d.iter().map(|i| i.into()).collect())
674                    }
675                    real::OwnedDataModelVariant::StructVariant(d) => {
676                        Self::StructVariant(d.iter().map(|i| i.into()).collect())
677                    }
678                }
679            }
680        }
681
682        // ---
683
684        /// The owned version of [`NamedValue`]
685        #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
686        pub struct OwnedNamedValue {
687            /// The name of this value
688            pub name: String,
689            /// The type of this value
690            pub ty: OwnedNamedType,
691        }
692
693        impl From<&real::OwnedNamedValue> for OwnedNamedValue {
694            fn from(value: &real::OwnedNamedValue) -> Self {
695                Self {
696                    name: value.name.to_string(),
697                    ty: (&value.ty).into(),
698                }
699            }
700        }
701
702        // ---
703
704        /// The owned version of [`NamedVariant`]
705        #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
706        pub struct OwnedNamedVariant {
707            /// The name of this variant
708            pub name: String,
709            /// The type of this variant
710            pub ty: OwnedDataModelVariant,
711        }
712
713        impl From<&real::OwnedNamedVariant> for OwnedNamedVariant {
714            fn from(value: &real::OwnedNamedVariant) -> Self {
715                Self {
716                    name: value.name.to_string(),
717                    ty: (&value.ty).into(),
718                }
719            }
720        }
721    }
722}