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}