Skip to main content

futu_backend/auth/commconfig/
store.rs

1//! Versioned last-good CommConfig persistence.
2
3use std::path::{Path, PathBuf};
4
5use serde::{Deserialize, Serialize};
6
7use super::projection::project_common_config;
8use super::transaction::CommonConfigDocument;
9use super::types::{CommConfigSource, CommonConfigSnapshot};
10
11const STORE_SCHEMA_VERSION: u32 = 1;
12const STORE_FILE_NAME: &str = "common-config.json";
13
14#[derive(Debug)]
15pub enum CommConfigStoreError {
16    Directory(String),
17    Read(std::io::Error),
18    Decode(serde_json::Error),
19    UnsupportedSchema(u32),
20    Encode(serde_json::Error),
21    Write(std::io::Error),
22}
23
24impl CommConfigStoreError {
25    pub fn is_not_found(&self) -> bool {
26        matches!(self, Self::Read(error) if error.kind() == std::io::ErrorKind::NotFound)
27    }
28}
29
30impl std::fmt::Display for CommConfigStoreError {
31    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            Self::Directory(message) => write!(formatter, "store directory unavailable: {message}"),
34            Self::Read(error) => write!(formatter, "read last-good: {error}"),
35            Self::Decode(error) => write!(formatter, "decode last-good: {error}"),
36            Self::UnsupportedSchema(version) => {
37                write!(formatter, "unsupported last-good schema version {version}")
38            }
39            Self::Encode(error) => write!(formatter, "encode last-good: {error}"),
40            Self::Write(error) => write!(formatter, "write last-good: {error}"),
41        }
42    }
43}
44
45impl std::error::Error for CommConfigStoreError {}
46
47#[derive(Debug, Serialize, Deserialize)]
48#[serde(deny_unknown_fields)]
49struct StoredCommonConfig {
50    schema_version: u32,
51    conf_info: serde_json::Map<String, serde_json::Value>,
52}
53
54pub(super) fn common_config_path() -> Result<PathBuf, CommConfigStoreError> {
55    super::super::device::try_futu_opend_dir()
56        .map(|dir| dir.join(STORE_FILE_NAME))
57        .map_err(|error| CommConfigStoreError::Directory(error.to_string()))
58}
59
60pub(super) fn load_last_good_from_path(
61    path: &Path,
62) -> Result<CommonConfigDocument, CommConfigStoreError> {
63    let bytes = std::fs::read(path).map_err(CommConfigStoreError::Read)?;
64    let stored: StoredCommonConfig =
65        serde_json::from_slice(&bytes).map_err(CommConfigStoreError::Decode)?;
66    if stored.schema_version != STORE_SCHEMA_VERSION {
67        return Err(CommConfigStoreError::UnsupportedSchema(
68            stored.schema_version,
69        ));
70    }
71    Ok(CommonConfigDocument::from_conf_info(stored.conf_info))
72}
73
74pub(super) fn save_last_good_to_path(
75    path: &Path,
76    document: &CommonConfigDocument,
77) -> Result<(), CommConfigStoreError> {
78    let parent = path.parent().unwrap_or_else(|| Path::new("."));
79    super::super::device::ensure_dir_0700(parent)
80        .map_err(|error| CommConfigStoreError::Directory(error.to_string()))?;
81    let bytes = serde_json::to_vec(&StoredCommonConfig {
82        schema_version: STORE_SCHEMA_VERSION,
83        conf_info: document.conf_info.clone(),
84    })
85    .map_err(CommConfigStoreError::Encode)?;
86    super::super::device::write_secret_file(path, &bytes).map_err(CommConfigStoreError::Write)
87}
88
89pub(super) fn load_last_good_snapshot_from_path(
90    path: &Path,
91) -> Result<CommonConfigSnapshot, CommConfigStoreError> {
92    let document = load_last_good_from_path(path)?;
93    Ok(project_common_config(
94        &document,
95        0,
96        CommConfigSource::Persisted,
97        1,
98    ))
99}
100
101pub fn load_last_good_snapshot() -> Result<CommonConfigSnapshot, CommConfigStoreError> {
102    load_last_good_snapshot_from_path(&common_config_path()?)
103}
104
105pub(super) fn save_last_good(document: &CommonConfigDocument) -> Result<(), CommConfigStoreError> {
106    save_last_good_to_path(&common_config_path()?, document)
107}