mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
feat(admin): 实现系统数据导入导出功能,支持提供商和模型批量配置
This commit is contained in:
@@ -1 +1,126 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::{Map, Value};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum AdminJsonFieldState {
|
||||
Missing,
|
||||
Null,
|
||||
Present,
|
||||
}
|
||||
|
||||
impl AdminJsonFieldState {
|
||||
pub(crate) fn is_present(self) -> bool {
|
||||
!matches!(self, Self::Missing)
|
||||
}
|
||||
|
||||
pub(crate) fn is_null(self) -> bool {
|
||||
matches!(self, Self::Null)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct AdminJsonObjectPatch {
|
||||
field_states: BTreeMap<String, AdminJsonFieldState>,
|
||||
}
|
||||
|
||||
impl AdminJsonObjectPatch {
|
||||
fn from_object(raw_payload: &Map<String, Value>) -> Self {
|
||||
let field_states = raw_payload
|
||||
.iter()
|
||||
.map(|(key, value)| {
|
||||
let state = if value.is_null() {
|
||||
AdminJsonFieldState::Null
|
||||
} else {
|
||||
AdminJsonFieldState::Present
|
||||
};
|
||||
(key.clone(), state)
|
||||
})
|
||||
.collect();
|
||||
Self { field_states }
|
||||
}
|
||||
|
||||
pub(crate) fn state(&self, field: &str) -> AdminJsonFieldState {
|
||||
self.field_states
|
||||
.get(field)
|
||||
.copied()
|
||||
.unwrap_or(AdminJsonFieldState::Missing)
|
||||
}
|
||||
|
||||
pub(crate) fn contains(&self, field: &str) -> bool {
|
||||
self.state(field).is_present()
|
||||
}
|
||||
|
||||
pub(crate) fn is_null(&self, field: &str) -> bool {
|
||||
self.state(field).is_null()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct AdminTypedObjectPatch<T> {
|
||||
fields: AdminJsonObjectPatch,
|
||||
pub(crate) payload: T,
|
||||
}
|
||||
|
||||
impl<T> AdminTypedObjectPatch<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
pub(crate) fn from_object(raw_payload: Map<String, Value>) -> Result<Self, serde_json::Error> {
|
||||
let fields = AdminJsonObjectPatch::from_object(&raw_payload);
|
||||
let payload = serde_json::from_value(Value::Object(raw_payload))?;
|
||||
Ok(Self { fields, payload })
|
||||
}
|
||||
|
||||
pub(crate) fn contains(&self, field: &str) -> bool {
|
||||
self.fields.contains(field)
|
||||
}
|
||||
|
||||
pub(crate) fn is_null(&self, field: &str) -> bool {
|
||||
self.fields.is_null(field)
|
||||
}
|
||||
|
||||
pub(crate) fn into_parts(self) -> (AdminJsonObjectPatch, T) {
|
||||
(self.fields, self.payload)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{AdminJsonFieldState, AdminTypedObjectPatch};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ExamplePatchPayload {
|
||||
#[serde(default)]
|
||||
description: Option<String>,
|
||||
#[serde(default)]
|
||||
enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_typed_object_patch_tracks_field_presence() {
|
||||
let patch = AdminTypedObjectPatch::<ExamplePatchPayload>::from_object(
|
||||
json!({
|
||||
"description": null,
|
||||
"enabled": true,
|
||||
})
|
||||
.as_object()
|
||||
.cloned()
|
||||
.expect("object"),
|
||||
)
|
||||
.expect("patch");
|
||||
|
||||
assert_eq!(patch.payload.description, None);
|
||||
assert_eq!(patch.payload.enabled, Some(true));
|
||||
assert!(patch.contains("description"));
|
||||
assert!(patch.is_null("description"));
|
||||
assert!(patch.contains("enabled"));
|
||||
assert!(!patch.is_null("enabled"));
|
||||
assert_eq!(
|
||||
patch.into_parts().0.state("missing_field"),
|
||||
AdminJsonFieldState::Missing
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user