futu_backend/auth/commconfig/
store.rs1use 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}