use crate::providers::ResourceProvider;
use crate::resource::Resource;
use crate::resource::value_objects::Meta;
use crate::resource::version::RawVersion;
use chrono::{DateTime, Utc};
pub trait ScimMetadataManager: ResourceProvider {
fn add_creation_metadata(
&self,
resource: &mut Resource,
base_url: &str,
) -> Result<(), Self::Error> {
let now = Utc::now();
let resource_type = &resource.resource_type;
let resource_id = resource
.get_id()
.map(|id| id.to_string())
.unwrap_or_else(|| "unknown".to_string());
let location = format!("{}/{}/{}", base_url, resource_type, resource_id);
let version = self.compute_resource_version(resource);
let meta = Meta::new(
resource_type.clone(),
now,
now,
Some(location),
Some(version.as_str().to_string()),
)
.map_err(|e| self.metadata_error(&format!("Failed to create metadata: {}", e)))?;
resource.set_meta(meta);
Ok(())
}
fn update_modification_metadata(&self, resource: &mut Resource) -> Result<(), Self::Error> {
let now = Utc::now();
let new_version = self.compute_resource_version(resource);
if let Some(existing_meta) = resource.get_meta() {
let updated_meta = Meta::new(
existing_meta.resource_type.clone(),
existing_meta.created,
now,
existing_meta.location.clone(),
Some(new_version.as_str().to_string()),
)
.map_err(|e| self.metadata_error(&format!("Failed to update metadata: {}", e)))?;
resource.set_meta(updated_meta);
}
Ok(())
}
fn compute_resource_version(&self, resource: &Resource) -> RawVersion {
match resource.to_json() {
Ok(resource_json) => {
let content = resource_json.to_string();
RawVersion::from_content(content.as_bytes())
}
Err(_) => {
let timestamp = Utc::now().timestamp_millis();
RawVersion::from_hash(&format!("timestamp-{}", timestamp))
}
}
}
fn generate_location_uri(&self, base_url: &str, resource_type: &str, id: &str) -> String {
format!(
"{}/{}/{}",
base_url.trim_end_matches('/'),
resource_type,
id
)
}
fn extract_resource_version(&self, resource: &Resource) -> Option<String> {
resource
.get_meta()
.and_then(|meta| meta.version.as_ref())
.map(|s| s.to_string())
}
fn has_valid_metadata(&self, resource: &Resource) -> bool {
if let Some(meta) = resource.get_meta() {
let has_resource_type = !meta.resource_type.is_empty();
let has_location = meta
.location
.as_ref()
.map(|s| !s.is_empty())
.unwrap_or(false);
has_resource_type && has_location
} else {
false
}
}
fn refresh_metadata(
&self,
resource: &mut Resource,
base_url: &str,
preserve_created: bool,
) -> Result<(), Self::Error> {
let now = Utc::now();
let resource_type = &resource.resource_type;
let resource_id = resource
.get_id()
.map(|id| id.to_string())
.unwrap_or_else(|| "unknown".to_string());
let created = if preserve_created {
resource.get_meta().map(|meta| meta.created).unwrap_or(now)
} else {
now
};
let location = self.generate_location_uri(base_url, resource_type, &resource_id);
let version = self.compute_resource_version(resource);
let meta = Meta::new(
resource_type.clone(),
created,
now,
Some(location),
Some(version.as_str().to_string()),
)
.map_err(|e| self.metadata_error(&format!("Failed to refresh metadata: {}", e)))?;
resource.set_meta(meta);
Ok(())
}
fn strip_metadata(&self, resource: &mut Resource) {
resource.meta = None;
}
fn get_creation_time(&self, resource: &Resource) -> Option<DateTime<Utc>> {
resource.get_meta().map(|meta| meta.created)
}
fn get_modification_time(&self, resource: &Resource) -> Option<DateTime<Utc>> {
resource.get_meta().map(|meta| meta.last_modified)
}
fn metadata_error(&self, message: &str) -> Self::Error;
}
impl<T> ScimMetadataManager for T
where
T: ResourceProvider,
T::Error: From<String>,
{
fn metadata_error(&self, message: &str) -> Self::Error {
Self::Error::from(message.to_string())
}
}