Skip to main content

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}