hap_model/database.rs
1//! A thin typed-access layer over a caller-supplied request executor.
2//!
3//! `hap-model` stays transport-agnostic: [`AccessoryDatabase`] does not know
4//! about sockets or sessions. The caller provides a [`RequestExecutor`] that
5//! actually performs an HTTP request over a secure session; the database turns
6//! that into typed `/accessories` and `/characteristics` operations.
7
8use crate::error::Result;
9use crate::format::CharValue;
10use crate::tree::Accessory;
11use crate::{
12 build_read_request, build_subscribe_request, build_write_request, parse_accessories,
13 parse_read_response,
14};
15
16/// One HAP request the executor must perform, described abstractly.
17#[derive(Debug, Clone, PartialEq)]
18#[non_exhaustive]
19pub enum Request {
20 /// `GET <path>` (used for `/accessories` and `/characteristics?...`).
21 Get {
22 /// Request path including any query string.
23 path: String,
24 },
25 /// `PUT /characteristics` with a JSON body.
26 Put {
27 /// Request path.
28 path: String,
29 /// Request body bytes.
30 body: Vec<u8>,
31 },
32}
33
34/// Something that can perform a HAP [`Request`] and return the response body.
35///
36/// M7 implements this over a `SecureSession`; tests implement it in memory.
37pub trait RequestExecutor {
38 /// Perform `req`, returning the raw response body bytes.
39 ///
40 /// # Errors
41 /// Implementations return a [`crate::ModelError::Executor`] (or map their
42 /// own transport error into one) on failure.
43 fn execute(&mut self, req: Request) -> Result<Vec<u8>>;
44}
45
46/// Typed access to an accessory's attribute database over an executor.
47pub struct AccessoryDatabase<E: RequestExecutor> {
48 executor: E,
49 accessories: Vec<Accessory>,
50}
51
52impl<E: RequestExecutor> AccessoryDatabase<E> {
53 /// Fetch `/accessories` through `executor` and build the typed tree.
54 ///
55 /// # Errors
56 /// Propagates executor and parse errors.
57 pub fn fetch(mut executor: E) -> Result<Self> {
58 let body = executor.execute(Request::Get {
59 path: "/accessories".to_string(),
60 })?;
61 let accessories = parse_accessories(&body)?;
62 Ok(Self {
63 executor,
64 accessories,
65 })
66 }
67
68 /// The cached accessory tree from the last fetch.
69 pub fn accessories(&self) -> &[Accessory] {
70 &self.accessories
71 }
72
73 /// Read the current values of the given `(aid, iid)` characteristics.
74 ///
75 /// # Errors
76 /// Propagates executor and parse errors, including a non-zero per-
77 /// characteristic HAP status as [`crate::ModelError::CharacteristicStatus`].
78 pub fn read(&mut self, ids: &[(u64, u64)]) -> Result<Vec<((u64, u64), CharValue)>> {
79 let path = build_read_request(ids);
80 let body = self.executor.execute(Request::Get { path })?;
81 parse_read_response(&body)
82 }
83
84 /// Write values to characteristics.
85 ///
86 /// # Errors
87 /// Propagates executor errors.
88 pub fn write(&mut self, writes: &[((u64, u64), CharValue)]) -> Result<()> {
89 let body = build_write_request(writes);
90 self.executor.execute(Request::Put {
91 path: "/characteristics".to_string(),
92 body,
93 })?;
94 Ok(())
95 }
96
97 /// Subscribe or unsubscribe to event notifications for characteristics.
98 ///
99 /// # Errors
100 /// Propagates executor errors.
101 pub fn subscribe(&mut self, ids: &[(u64, u64)], enable: bool) -> Result<()> {
102 let body = build_subscribe_request(ids, enable);
103 self.executor.execute(Request::Put {
104 path: "/characteristics".to_string(),
105 body,
106 })?;
107 Ok(())
108 }
109}