icydb-core 0.147.3

IcyDB — A schema-first typed query engine and persistence runtime for Internet Computer canisters
Documentation
use crate::{
    db::data::persisted_row::codec::{
        decode_runtime_value_option_with_strategy, decode_with_strategy,
        encode_runtime_value_with_strategy, encode_with_strategy, require_decoded,
        strategy::StorageStrategy,
    },
    error::InternalError,
    model::field::FieldKind,
    traits::{FieldTypeMeta, PersistedFieldMetaCodec, RuntimeValueDecode, RuntimeValueEncode},
    value::Value,
};

// The field-meta lane is entirely selected by `FieldTypeMeta`, so one blanket
// impl keeps generated/runtime/container owners on the same storage contract
// instead of re-emitting identical per-type forwarding bodies.
impl<T> PersistedFieldMetaCodec for T
where
    T: FieldTypeMeta + RuntimeValueEncode + RuntimeValueDecode,
{
    fn encode_persisted_slot_payload_by_meta(
        &self,
        field_name: &'static str,
    ) -> Result<Vec<u8>, InternalError> {
        encode_meta(Some(self), field_name)
    }

    fn decode_persisted_slot_payload_by_meta(
        bytes: &[u8],
        field_name: &'static str,
    ) -> Result<Self, InternalError> {
        decode_meta(bytes, field_name)
    }

    fn encode_persisted_option_slot_payload_by_meta(
        value: &Option<Self>,
        field_name: &'static str,
    ) -> Result<Vec<u8>, InternalError> {
        encode_meta(value.as_ref(), field_name)
    }

    fn decode_persisted_option_slot_payload_by_meta(
        bytes: &[u8],
        field_name: &'static str,
    ) -> Result<Option<Self>, InternalError> {
        decode_meta_option(bytes, field_name)
    }
}

/// Decode one persisted slot payload using the field type's own runtime field
/// metadata.
pub fn decode_persisted_slot_payload_by_meta<T>(
    bytes: &[u8],
    field_name: &'static str,
) -> Result<T, InternalError>
where
    T: PersistedFieldMetaCodec,
{
    T::decode_persisted_slot_payload_by_meta(bytes, field_name)
}

/// Decode one optional persisted slot payload using the inner field type's own
/// runtime field metadata.
pub fn decode_persisted_option_slot_payload_by_meta<T>(
    bytes: &[u8],
    field_name: &'static str,
) -> Result<Option<T>, InternalError>
where
    T: PersistedFieldMetaCodec,
{
    T::decode_persisted_option_slot_payload_by_meta(bytes, field_name)
}

/// Encode one persisted slot payload using the field type's own runtime field
/// metadata.
pub fn encode_persisted_slot_payload_by_meta<T>(
    value: &T,
    field_name: &'static str,
) -> Result<Vec<u8>, InternalError>
where
    T: PersistedFieldMetaCodec,
{
    value.encode_persisted_slot_payload_by_meta(field_name)
}

/// Encode one optional persisted slot payload using the inner field type's own
/// runtime field metadata.
pub fn encode_persisted_option_slot_payload_by_meta<T>(
    value: &Option<T>,
    field_name: &'static str,
) -> Result<Vec<u8>, InternalError>
where
    T: PersistedFieldMetaCodec,
{
    T::encode_persisted_option_slot_payload_by_meta(value, field_name)
}

/// Decode one repeated persisted slot payload using the item field type's own
/// runtime field metadata.
pub fn decode_persisted_many_slot_payload_by_meta<T>(
    bytes: &[u8],
    field_name: &'static str,
) -> Result<Vec<T>, InternalError>
where
    T: FieldTypeMeta + RuntimeValueDecode,
{
    let runtime_value = require_decoded(
        decode_meta_many_value_option::<T>(bytes, field_name)?,
        || {
            InternalError::persisted_row_field_decode_failed(
                field_name,
                "unexpected null for non-nullable field",
            )
        },
    )?;

    let Value::List(items) = runtime_value else {
        return Err(InternalError::persisted_row_field_decode_failed(
            field_name,
            format!("payload does not match Vec<{}>", std::any::type_name::<T>()),
        ));
    };

    items
        .iter()
        .map(|value| decode_runtime_value_as_meta::<T>(value, field_name))
        .collect()
}

/// Encode one repeated persisted slot payload using the item field type's own
/// runtime field metadata.
pub fn encode_persisted_many_slot_payload_by_meta<T>(
    values: &[T],
    field_name: &'static str,
) -> Result<Vec<u8>, InternalError>
where
    T: FieldTypeMeta + RuntimeValueEncode,
{
    let runtime_values = values.iter().map(RuntimeValueEncode::to_value).collect();
    let runtime_value = Value::List(runtime_values);
    let strategy = meta_many_storage_strategy::<T>();

    encode_with_strategy(
        strategy,
        Some(&runtime_value),
        field_name,
        encode_runtime_value_with_strategy,
    )
}

pub(super) fn encode_meta<T>(
    value: Option<&T>,
    field_name: &'static str,
) -> Result<Vec<u8>, InternalError>
where
    T: FieldTypeMeta + RuntimeValueEncode,
{
    let runtime_value = value.map(RuntimeValueEncode::to_value);
    let strategy = StorageStrategy::from_field_storage(T::STORAGE_DECODE, T::KIND);

    encode_with_strategy(
        strategy,
        runtime_value.as_ref(),
        field_name,
        encode_runtime_value_with_strategy,
    )
}

pub(super) fn decode_meta<T>(bytes: &[u8], field_name: &'static str) -> Result<T, InternalError>
where
    T: FieldTypeMeta + RuntimeValueDecode,
{
    let runtime_value = require_decoded(decode_meta_value_option::<T>(bytes, field_name)?, || {
        InternalError::persisted_row_field_decode_failed(
            field_name,
            "unexpected null for non-nullable field",
        )
    })?;

    decode_runtime_value_as_meta(&runtime_value, field_name)
}

pub(super) fn decode_meta_option<T>(
    bytes: &[u8],
    field_name: &'static str,
) -> Result<Option<T>, InternalError>
where
    T: FieldTypeMeta + RuntimeValueDecode,
{
    let Some(runtime_value) = decode_meta_value_option::<T>(bytes, field_name)? else {
        return Ok(None);
    };

    decode_runtime_value_as_meta(&runtime_value, field_name).map(Some)
}

fn decode_meta_value_option<T>(
    bytes: &[u8],
    field_name: &'static str,
) -> Result<Option<Value>, InternalError>
where
    T: FieldTypeMeta,
{
    let strategy = StorageStrategy::from_field_storage(T::STORAGE_DECODE, T::KIND);

    decode_with_strategy(
        strategy,
        bytes,
        field_name,
        decode_runtime_value_option_with_strategy,
    )
}

fn decode_runtime_value_as_meta<T>(
    value: &Value,
    field_name: &'static str,
) -> Result<T, InternalError>
where
    T: FieldTypeMeta + RuntimeValueDecode,
{
    T::from_value(value).ok_or_else(|| {
        InternalError::persisted_row_field_decode_failed(
            field_name,
            format!("payload does not match {}", std::any::type_name::<T>()),
        )
    })
}

fn decode_meta_many_value_option<T>(
    bytes: &[u8],
    field_name: &'static str,
) -> Result<Option<Value>, InternalError>
where
    T: FieldTypeMeta,
{
    let strategy = meta_many_storage_strategy::<T>();

    decode_with_strategy(
        strategy,
        bytes,
        field_name,
        decode_runtime_value_option_with_strategy,
    )
}

const fn meta_many_storage_strategy<T>() -> StorageStrategy
where
    T: FieldTypeMeta,
{
    // Repeated `item(is = "...")` fields are modeled as a list whose item
    // payload contract comes from the item type. Using `Vec<T>` metadata here
    // would incorrectly force value-storage for by-kind item newtypes.
    let kind = FieldKind::List(&T::KIND);

    StorageStrategy::from_field_storage(T::STORAGE_DECODE, kind)
}