use serde::{Deserialize, Deserializer, Serialize, de::DeserializeOwned};
use crate::{
Blob, EntityFragment, EntityMigration, EntitySourceKey, FieldFragment, FieldSourceKey,
MAX_PROPOSAL_LITERAL_BYTES, MAX_SCHEMA_FRAGMENT_BYTES, MAX_SCHEMA_MIGRATION_PLAN_BYTES,
MAX_SCHEMA_PROPOSAL_BYTES, NamedTypeFragment, SchemaContractError, SchemaFragment,
SchemaMigrationPlan, SchemaProposal,
};
const FRAGMENT_HEADER: [u8; 5] = *b"ICWF\x01";
const PROPOSAL_HEADER: [u8; 5] = *b"ICWP\x01";
const MIGRATION_HEADER: [u8; 5] = *b"ICWM\x01";
const SOURCE_MEANING_HEADER: [u8; 5] = *b"ICWS\x01";
pub fn encode_schema_fragment(fragment: &SchemaFragment) -> Result<Vec<u8>, SchemaContractError> {
fragment.validate()?;
encode(FRAGMENT_HEADER, fragment, MAX_SCHEMA_FRAGMENT_BYTES)
}
pub fn decode_schema_fragment(bytes: &[u8]) -> Result<SchemaFragment, SchemaContractError> {
let fragment: SchemaFragment = decode(FRAGMENT_HEADER, bytes, MAX_SCHEMA_FRAGMENT_BYTES)?;
fragment.validate()?;
ensure_canonical(bytes, encode_schema_fragment(&fragment)?)?;
Ok(fragment)
}
pub fn encode_schema_proposal(proposal: &SchemaProposal) -> Result<Vec<u8>, SchemaContractError> {
proposal.validate_current()?;
encode(PROPOSAL_HEADER, proposal, MAX_SCHEMA_PROPOSAL_BYTES)
}
pub fn decode_schema_proposal(bytes: &[u8]) -> Result<SchemaProposal, SchemaContractError> {
let proposal: SchemaProposal = decode(PROPOSAL_HEADER, bytes, MAX_SCHEMA_PROPOSAL_BYTES)?;
proposal.validate_current()?;
ensure_canonical(bytes, encode_schema_proposal(&proposal)?)?;
Ok(proposal)
}
pub fn encode_schema_migration_plan(
plan: &SchemaMigrationPlan,
) -> Result<Vec<u8>, SchemaContractError> {
plan.validate()?;
encode(MIGRATION_HEADER, plan, MAX_SCHEMA_MIGRATION_PLAN_BYTES)
}
pub fn decode_schema_migration_plan(
bytes: &[u8],
) -> Result<SchemaMigrationPlan, SchemaContractError> {
let plan: SchemaMigrationPlan =
decode(MIGRATION_HEADER, bytes, MAX_SCHEMA_MIGRATION_PLAN_BYTES)?;
plan.validate()?;
ensure_canonical(bytes, encode_schema_migration_plan(&plan)?)?;
Ok(plan)
}
pub(crate) fn encode_migration_transitions_for_digest(
transitions: &[EntityMigration],
) -> Result<Vec<u8>, SchemaContractError> {
encode(
MIGRATION_HEADER,
&(
crate::migration::MIGRATION_PROGRAM_VERSION_CURRENT,
transitions,
),
MAX_SCHEMA_MIGRATION_PLAN_BYTES,
)
}
pub(crate) fn encode_entity_source_meaning(
entity: &EntityFragment,
relation_targets: &[(EntitySourceKey, Vec<(FieldSourceKey, FieldFragment)>)],
reachable_types: &[NamedTypeFragment],
) -> Result<Vec<u8>, SchemaContractError> {
encode(
SOURCE_MEANING_HEADER,
&(entity, relation_targets, reachable_types),
MAX_SCHEMA_PROPOSAL_BYTES,
)
}
fn encode<T: Serialize + ?Sized>(
header: [u8; 5],
value: &T,
max: usize,
) -> Result<Vec<u8>, SchemaContractError> {
let framed = (header, value);
let len = ic_wire::native::encoded_len(&framed, crate::wire::sizing_limits())
.map_err(|_| SchemaContractError::Encode)?;
ensure_input_bound(len, max)?;
ic_wire::native::encode(&framed, crate::wire::limits(max))
.map_err(|_| SchemaContractError::Encode)
}
fn decode<T: DeserializeOwned>(
header: [u8; 5],
bytes: &[u8],
max: usize,
) -> Result<T, SchemaContractError> {
ensure_input_bound(bytes.len(), max)?;
if !bytes.starts_with(&header) {
return Err(SchemaContractError::Decode);
}
let (_, value): ([u8; 5], T) = ic_wire::native::decode(bytes, crate::wire::limits(max))
.map_err(|_| SchemaContractError::Decode)?;
Ok(value)
}
const fn ensure_input_bound(len: usize, max: usize) -> Result<(), SchemaContractError> {
if len > max {
Err(SchemaContractError::EncodedTooLarge { len, max })
} else {
Ok(())
}
}
fn ensure_canonical(bytes: &[u8], canonical: Vec<u8>) -> Result<(), SchemaContractError> {
if canonical == bytes {
Ok(())
} else {
Err(SchemaContractError::NonCanonical)
}
}
pub(crate) fn deserialize_list<'de, D, T, const N: usize>(
deserializer: D,
) -> Result<Vec<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
ic_wire::List::<T, N>::deserialize(deserializer).map(ic_wire::List::into_vec)
}
pub(crate) fn deserialize_literal_text<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<String, D::Error> {
if deserializer.is_human_readable() {
let value = String::deserialize(deserializer)?;
if value.len() > MAX_PROPOSAL_LITERAL_BYTES {
return Err(serde::de::Error::custom("proposal text limit"));
}
Ok(value)
} else {
ic_wire::Text::<MAX_PROPOSAL_LITERAL_BYTES>::deserialize(deserializer)
.map(ic_wire::Text::into_string)
}
}
pub(crate) fn deserialize_literal_blob<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Blob, D::Error> {
if deserializer.is_human_readable() {
let value = Blob::deserialize(deserializer)?;
if value.len() > MAX_PROPOSAL_LITERAL_BYTES {
return Err(serde::de::Error::custom("proposal blob limit"));
}
Ok(value)
} else {
ic_wire::Bytes::<MAX_PROPOSAL_LITERAL_BYTES>::deserialize(deserializer)
.map(|bytes| Blob::from(bytes.into_vec()))
}
}
pub(crate) fn deserialize_key<'de, D, T, const N: usize>(d: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: DeserializeOwned,
{
if d.is_human_readable() {
return T::deserialize(d);
}
let text = ic_wire::Text::<N>::deserialize(d)?.into_string();
T::deserialize(serde::de::value::StringDeserializer::<D::Error>::new(text))
}
pub(crate) fn deserialize_optional_key<'de, D, T, const N: usize>(
d: D,
) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: DeserializeOwned,
{
if d.is_human_readable() {
return Option::<T>::deserialize(d);
}
Option::<ic_wire::Text<N>>::deserialize(d)?
.map(|text| {
T::deserialize(serde::de::value::StringDeserializer::<D::Error>::new(
text.into_string(),
))
})
.transpose()
}
pub(crate) fn deserialize_keys<'de, D, T, const N: usize>(d: D) -> Result<Vec<T>, D::Error>
where
D: Deserializer<'de>,
T: DeserializeOwned,
{
if d.is_human_readable() {
return deserialize_list::<D, T, N>(d);
}
let keys = ic_wire::List::<ic_wire::Text<{ crate::MAX_SOURCE_KEY_BYTES }>, N>::deserialize(d)?;
let mut values = Vec::new();
values
.try_reserve_exact(keys.as_slice().len())
.map_err(serde::de::Error::custom)?;
for key in keys.into_vec() {
values.push(T::deserialize(serde::de::value::StringDeserializer::<
D::Error,
>::new(key.into_string()))?);
}
Ok(values)
}
pub(crate) fn deserialize_literal_decimal<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<crate::Decimal, D::Error> {
const MAX_BYTES: usize = 1
+ crate::decimal::DECIMAL_DIGIT_BUFFER_LEN
+ 1
+ crate::decimal::MAX_SUPPORTED_SCALE as usize;
let text = if deserializer.is_human_readable() {
let text = String::deserialize(deserializer)?;
if text.len() > MAX_BYTES {
return Err(serde::de::Error::custom("decimal text limit"));
}
text
} else {
ic_wire::Text::<MAX_BYTES>::deserialize(deserializer)?.into_string()
};
text.parse().map_err(serde::de::Error::custom)
}
#[cfg(test)]
mod tests;