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,
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            client_created_at: None,
63            idempotency_key: None,
64        };
65        let json = serde_json::to_string(&req).unwrap();
66        assert!(json.contains(r#""content":"hi""#));
67        assert!(json.contains(r#""source":"remote:host""#));
68        assert!(json.contains(r#""byte_size":2"#));
69        assert!(!json.contains("content_type"));
70        assert!(!json.contains("ttl"));
71        assert!(!json.contains("encrypted"));
72        assert!(!json.contains("target_device_id"));
73    }
74
75    #[test]
76    fn push_request_serializes_encrypted() {
77        let req = PushRequest {
78            content: "ciphertext".into(),
79            content_type: ContentType::Image.as_wire().into(),
80            label: "logo".into(),
81            source: "remote:host".into(),
82            media_path: None,
83            byte_size: 1234,
84            encrypted: true,
85            target_device_id: Some("dev1".into()),
86            client_created_at: None,
87            idempotency_key: None,
88        };
89        let json = serde_json::to_string(&req).unwrap();
90        assert!(json.contains(r#""content_type":"image""#));
91        assert!(json.contains(r#""label":"logo""#));
92        assert!(!json.contains("ttl"));
93        assert!(json.contains(r#""encrypted":true"#));
94        assert!(json.contains(r#""target_device_id":"dev1""#));
95    }
96
97    #[test]
98    fn push_response_deserializes() {
99        let json = r#"{"clip_id":"01HABC","byte_size":42}"#;
100        let resp: PushResponse = serde_json::from_str(json).unwrap();
101        assert_eq!(resp.clip_id, "01HABC");
102        assert_eq!(resp.byte_size, 42);
103    }
104
105    #[test]
106    fn content_type_lowercase() {
107        let s = serde_json::to_string(&ContentType::Text).unwrap();
108        assert_eq!(s, r#""text""#);
109        let s = serde_json::to_string(&ContentType::Image).unwrap();
110        assert_eq!(s, r#""image""#);
111    }
112}