devicerail_protocol/
manual.rs1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use uuid::Uuid;
4
5use crate::{DeviceId, EventSequence};
6
7pub const MANUAL_RECORDING_VERSION: u16 = 1;
9
10#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
15#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
16#[serde(rename_all = "camelCase", deny_unknown_fields)]
17pub struct ManualRecording {
18 pub format_version: u16,
19 pub recording_id: Uuid,
20 pub source_device_id: DeviceId,
21 #[cfg_attr(feature = "schema", schemars(length(min = 64, max = 64)))]
22 pub action_space_sha256: String,
23 #[serde(
24 serialize_with = "crate::wire_integer::serialize_js_safe_u64",
25 deserialize_with = "crate::wire_integer::deserialize_js_safe_u64"
26 )]
27 #[cfg_attr(
28 feature = "schema",
29 schemars(range(min = 0_u64, max = 9_007_199_254_740_991_u64))
30 )]
31 pub started_at_ms: u64,
32 #[serde(
33 serialize_with = "crate::wire_integer::serialize_js_safe_u64",
34 deserialize_with = "crate::wire_integer::deserialize_js_safe_u64"
35 )]
36 #[cfg_attr(
37 feature = "schema",
38 schemars(range(min = 0_u64, max = 9_007_199_254_740_991_u64))
39 )]
40 pub ended_at_ms: u64,
41 #[cfg_attr(feature = "schema", schemars(length(max = 10_000)))]
42 pub steps: Vec<ManualActionStep>,
43}
44
45#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
46#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
47#[serde(rename_all = "camelCase", deny_unknown_fields)]
48pub struct ManualActionStep {
49 pub sequence: EventSequence,
50 #[serde(
51 serialize_with = "crate::wire_integer::serialize_js_safe_u64",
52 deserialize_with = "crate::wire_integer::deserialize_js_safe_u64"
53 )]
54 #[cfg_attr(
55 feature = "schema",
56 schemars(range(min = 0_u64, max = 9_007_199_254_740_991_u64))
57 )]
58 pub captured_at_ms: u64,
59 pub call_id: Uuid,
60 #[cfg_attr(feature = "schema", schemars(length(min = 1, max = 128)))]
61 pub name: String,
62 pub arguments: ManualActionArguments,
63}
64
65#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
66#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
67#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)]
68pub enum ManualActionArguments {
69 Captured { value: Value },
71 Protected {
73 #[serde(rename = "secretRef")]
74 #[cfg_attr(feature = "schema", schemars(length(min = 1, max = 128)))]
75 secret_ref: String,
76 },
77}
78
79#[cfg(test)]
80mod tests {
81 use serde_json::json;
82 use uuid::Uuid;
83
84 use super::{
85 MANUAL_RECORDING_VERSION, ManualActionArguments, ManualActionStep, ManualRecording,
86 };
87 use crate::{DeviceId, EventSequence};
88
89 #[test]
90 fn protected_recording_contains_only_an_opaque_reference() {
91 let recording = ManualRecording {
92 format_version: MANUAL_RECORDING_VERSION,
93 recording_id: Uuid::nil(),
94 source_device_id: DeviceId::new("web-1"),
95 action_space_sha256: "a".repeat(64),
96 started_at_ms: 100,
97 ended_at_ms: 101,
98 steps: vec![ManualActionStep {
99 sequence: EventSequence::FIRST,
100 captured_at_ms: 101,
101 call_id: Uuid::nil(),
102 name: "fillSecret".to_owned(),
103 arguments: ManualActionArguments::Protected {
104 secret_ref: "login-password".to_owned(),
105 },
106 }],
107 };
108 let value = serde_json::to_value(recording).expect("serialize recording");
109 assert_eq!(value["formatVersion"], MANUAL_RECORDING_VERSION);
110 assert_eq!(
111 value["steps"][0]["arguments"],
112 json!({ "kind": "protected", "secretRef": "login-password" })
113 );
114 assert!(!value.to_string().contains("password-value"));
115 }
116}