mod metadata;
mod reverse_index;
mod save_validate;
mod validate;
use crate::{
db::{
Db,
data::{DataKey, RawDataKey, StorageKey, StorageKeyEncodeError},
identity::{EntityName, EntityNameError},
},
error::InternalError,
types::EntityTag,
value::Value,
};
use std::{collections::BTreeSet, fmt::Display};
pub(in crate::db) use metadata::{
model_has_any_strong_relations as model_has_strong_relation_targets,
model_has_strong_relations_to_target,
};
pub(crate) use reverse_index::{
ReverseRelationSourceInfo, prepare_reverse_relation_index_mutations_for_source_slot_readers,
};
pub(in crate::db) use save_validate::validate_save_strong_relations;
pub(in crate::db) use validate::validate_delete_strong_relations_for_source;
pub(crate) type StrongRelationDeleteValidateFn<C> =
fn(&Db<C>, &str, &BTreeSet<RawDataKey>) -> Result<(), InternalError>;
#[derive(Clone, Copy, Debug)]
enum RelationTargetDecodeContext {
DeleteValidation,
ReverseIndexPrepare,
}
#[derive(Clone, Copy, Debug)]
enum RelationTargetMismatchPolicy {
Skip,
Reject,
}
#[derive(Debug)]
pub(super) enum RelationTargetRawKeyError {
StorageKeyEncode(StorageKeyEncodeError),
TargetEntityName(EntityNameError),
}
impl InternalError {
#[expect(clippy::too_many_arguments)]
pub(in crate::db) fn relation_target_raw_key_error(
err: RelationTargetRawKeyError,
source_path: &'static str,
field_name: &str,
target_path: &str,
target_entity_name: &str,
value: &Value,
storage_compat_message: &'static str,
invalid_target_message: &'static str,
) -> Self {
match err {
RelationTargetRawKeyError::StorageKeyEncode(err) => {
Self::executor_unsupported(format!(
"{storage_compat_message}: source={source_path} field={field_name} target={target_path} value={value:?} ({err})",
))
}
RelationTargetRawKeyError::TargetEntityName(err) => Self::executor_internal(format!(
"{invalid_target_message}: source={source_path} field={field_name} target={target_path} name={target_entity_name} ({err})",
)),
}
}
pub(crate) fn strong_relation_target_missing(
source_path: &'static str,
field_name: &str,
target_path: &str,
value: &Value,
) -> Self {
Self::executor_unsupported(format!(
"strong relation missing: source={source_path} field={field_name} target={target_path} key={value:?}",
))
}
pub(crate) fn strong_relation_target_store_missing(
source_path: &'static str,
field_name: &str,
target_path: &str,
target_store_path: &str,
value: &Value,
err: impl Display,
) -> Self {
Self::executor_internal(format!(
"strong relation target store missing: source={source_path} field={field_name} target={target_path} store={target_store_path} key={value:?} ({err})",
))
}
}
pub(super) fn raw_relation_target_key_from_value(
target_entity_tag: EntityTag,
target_entity_name: &str,
value: &Value,
) -> Result<RawDataKey, RelationTargetRawKeyError> {
let storage_key =
StorageKey::try_from_value(value).map_err(RelationTargetRawKeyError::StorageKeyEncode)?;
let _ = EntityName::try_from_str(target_entity_name)
.map_err(RelationTargetRawKeyError::TargetEntityName)?;
DataKey::raw_from_parts(target_entity_tag, storage_key)
.map_err(RelationTargetRawKeyError::StorageKeyEncode)
}
pub(super) fn for_each_relation_target_value(
value: &Value,
mut visit: impl FnMut(&Value) -> Result<(), InternalError>,
) -> Result<(), InternalError> {
match value {
Value::List(items) => {
for item in items {
if matches!(item, Value::Null) {
continue;
}
visit(item)?;
}
}
Value::Null => {}
_ => visit(value)?,
}
Ok(())
}