Skip to main content

client_core/
rest.rs

1//! REST DTOs for the relay's legacy HTTP+JSON endpoints.
2//!
3//! Wire types are generated from `proto/cinch/v1/*.proto` at build time
4//! (see `build.rs`); this module re-exports them under the names the CLI
5//! and desktop already use. The generated types preserve snake_case JSON +
6//! Go-style `omitempty` semantics via per-field `skip_serializing_if`
7//! attribute injection.
8//!
9//! `ContentType` is a thin Rust-side enum kept for the CLI's auto-detection
10//! pipeline. On the wire it round-trips through the proto's `string`
11//! `content_type` field via `From<ContentType> for &'static str` so callers
12//! can keep producing strongly typed values.
13
14use serde::{Deserialize, Serialize};
15
16pub use crate::proto::cinch::v1::{
17    DeviceCodeCompleteRequest, DeviceCodeDenyRequest, DeviceCodePollResponse,
18    DeviceCodeStartRequest as DeviceCodeRequest, DeviceCodeStartResponse as DeviceCodeResponse,
19    ErrorResponse, KeyBundleGetResponse as KeyBundleResponse, KeyBundlePutRequest, PullResponse,
20    PushClipRequest as PushRequest, PushClipResponse as PushResponse,
21    RegisterDevicePublicKeyRequest, RevokeDeviceRequest as DeviceRevokeRequest,
22};
23
24/// Content classification — wire values are lowercase strings (`"text"`,
25/// `"image"`, etc.) matching the Go side's `protocol.ContentType` constants
26/// and the `string content_type` field on the proto messages.
27#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
28#[serde(rename_all = "lowercase")]
29pub enum ContentType {
30    Text,
31    Url,
32    Code,
33    Image,
34}
35
36impl ContentType {
37    pub fn as_wire(self) -> &'static str {
38        match self {
39            ContentType::Text => "text",
40            ContentType::Url => "url",
41            ContentType::Code => "code",
42            ContentType::Image => "image",
43        }
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn push_request_serializes_minimal_fields() {
53        let req = PushRequest {
54            content: "hi".into(),
55            content_type: String::new(),
56            label: String::new(),
57            source: "remote:host".into(),
58            media_path: None,
59            byte_size: 2,
60            encrypted: false,
61            target_device_id: None,
62        };
63        let json = serde_json::to_string(&req).unwrap();
64        assert!(json.contains(r#""content":"hi""#));
65        assert!(json.contains(r#""source":"remote:host""#));
66        assert!(json.contains(r#""byte_size":2"#));
67        assert!(!json.contains("content_type"));
68        assert!(!json.contains("ttl"));
69        assert!(!json.contains("encrypted"));
70        assert!(!json.contains("target_device_id"));
71    }
72
73    #[test]
74    fn push_request_serializes_encrypted() {
75        let req = PushRequest {
76            content: "ciphertext".into(),
77            content_type: ContentType::Image.as_wire().into(),
78            label: "logo".into(),
79            source: "remote:host".into(),
80            media_path: None,
81            byte_size: 1234,
82            encrypted: true,
83            target_device_id: Some("dev1".into()),
84        };
85        let json = serde_json::to_string(&req).unwrap();
86        assert!(json.contains(r#""content_type":"image""#));
87        assert!(json.contains(r#""label":"logo""#));
88        assert!(!json.contains("ttl"));
89        assert!(json.contains(r#""encrypted":true"#));
90        assert!(json.contains(r#""target_device_id":"dev1""#));
91    }
92
93    #[test]
94    fn push_response_deserializes() {
95        let json = r#"{"clip_id":"01HABC","byte_size":42}"#;
96        let resp: PushResponse = serde_json::from_str(json).unwrap();
97        assert_eq!(resp.clip_id, "01HABC");
98        assert_eq!(resp.byte_size, 42);
99    }
100
101    #[test]
102    fn content_type_lowercase() {
103        let s = serde_json::to_string(&ContentType::Text).unwrap();
104        assert_eq!(s, r#""text""#);
105        let s = serde_json::to_string(&ContentType::Image).unwrap();
106        assert_eq!(s, r#""image""#);
107    }
108}