Skip to main content

reifydb_codec/
constraint.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_value::value::{
5	constraint::{Constraint, TypeConstraint, bytes::MaxBytes},
6	dictionary::DictionaryId,
7	digest::Digest,
8	sumtype::SumTypeId,
9	value_type::ValueType,
10};
11
12use crate::{
13	error::{DecodeError, EncodeError},
14	tag::{TypeTag, ValueKind, peel_options},
15	unscaled::{decode_params, family_type, params},
16};
17
18#[repr(C)]
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub struct EncodedTypeConstraint {
21	pub base_type: u8,
22
23	pub constraint_type: u8,
24
25	pub constraint_param1: u32,
26
27	pub constraint_param2: u32,
28}
29
30pub fn encode_type_constraint(tc: &TypeConstraint) -> Result<EncodedTypeConstraint, EncodeError> {
31	let ty = tc.get_type();
32	let base_type = TypeTag::of_type(&ty)?.byte();
33	if let (
34		ValueType::Digest {
35			inner,
36			accuracy,
37		},
38		_,
39	) = peel_options(&ty)
40	{
41		if let Some(constraint) = tc.constraint() {
42			return Err(EncodeError::UnsupportedType(format!(
43				"{ty} cannot carry the {constraint:?} constraint"
44			)));
45		}
46		Digest::new((**inner).clone(), *accuracy)
47			.map_err(|error| EncodeError::UnsupportedType(error.to_string()))?;
48		return Ok(EncodedTypeConstraint {
49			base_type,
50			constraint_type: 5,
51			constraint_param1: *accuracy,
52			constraint_param2: u32::from(TypeTag::of_type(inner)?.byte()),
53		});
54	}
55	if let Some((precision, scale)) = params(peel_options(&ty).0) {
56		if let Some(constraint) = tc.constraint() {
57			return Err(EncodeError::UnsupportedType(format!(
58				"{ty} cannot carry the {constraint:?} constraint"
59			)));
60		}
61		return Ok(EncodedTypeConstraint {
62			base_type,
63			constraint_type: 2,
64			constraint_param1: u32::from(precision.value()),
65			constraint_param2: u32::from(scale.value()),
66		});
67	}
68	Ok(match tc.constraint() {
69		None => EncodedTypeConstraint {
70			base_type,
71			constraint_type: 0,
72			constraint_param1: 0,
73			constraint_param2: 0,
74		},
75		Some(Constraint::MaxBytes(max)) => EncodedTypeConstraint {
76			base_type,
77			constraint_type: 1,
78			constraint_param1: max.value(),
79			constraint_param2: 0,
80		},
81		Some(Constraint::Dictionary(dict_id, id_type)) => EncodedTypeConstraint {
82			base_type,
83			constraint_type: 3,
84			constraint_param1: dict_id.to_u64() as u32,
85			constraint_param2: TypeTag::of_type(id_type)?.byte() as u32,
86		},
87		Some(Constraint::SumType(id)) => EncodedTypeConstraint {
88			base_type,
89			constraint_type: 4,
90			constraint_param1: id.to_u64() as u32,
91			constraint_param2: 0,
92		},
93	})
94}
95
96pub fn decode_type_constraint(encoded: &EncodedTypeConstraint) -> Result<TypeConstraint, DecodeError> {
97	let tag = TypeTag::from_byte(encoded.base_type)?;
98	if encoded.constraint_type == 5 {
99		return decode_digest_type(tag, encoded).map(TypeConstraint::unconstrained);
100	}
101	if encoded.constraint_type == 2 {
102		return decode_family_type(tag, encoded).map(TypeConstraint::unconstrained);
103	}
104	let ty = tag.to_type()?;
105	Ok(match encoded.constraint_type {
106		1 => TypeConstraint::with_constraint(
107			ty,
108			Constraint::MaxBytes(MaxBytes::new(encoded.constraint_param1)),
109		),
110		3 => TypeConstraint::with_constraint(
111			ty,
112			Constraint::Dictionary(
113				DictionaryId::from(encoded.constraint_param1 as u64),
114				TypeTag::from_byte(encoded.constraint_param2 as u8)?.to_type()?,
115			),
116		),
117		4 => TypeConstraint::with_constraint(
118			ty,
119			Constraint::SumType(SumTypeId::from(encoded.constraint_param1 as u64)),
120		),
121		_ => TypeConstraint::unconstrained(ty),
122	})
123}
124
125fn decode_family_type(tag: TypeTag, encoded: &EncodedTypeConstraint) -> Result<ValueType, DecodeError> {
126	let kind = tag.kind().ok_or(DecodeError::UnknownTypeCode(encoded.base_type))?;
127	let param = |value: u32| {
128		u8::try_from(value)
129			.map_err(|_| DecodeError::InvalidData(format!("type parameter {value} does not fit in a byte")))
130	};
131	let (precision, scale) = decode_params(param(encoded.constraint_param1)?, param(encoded.constraint_param2)?)?;
132	let base = family_type(kind, precision, scale)?;
133	Ok((0..tag.depth()).fold(base, |ty, _| ValueType::Option(Box::new(ty))))
134}
135
136fn decode_digest_type(tag: TypeTag, encoded: &EncodedTypeConstraint) -> Result<ValueType, DecodeError> {
137	if tag.kind() != Some(ValueKind::Digest) {
138		return Err(DecodeError::InvalidData(format!(
139			"digest constraint on base type tag 0x{:02X}, which is not a digest",
140			encoded.base_type
141		)));
142	}
143	let inner_byte = u8::try_from(encoded.constraint_param2).map_err(|_| {
144		DecodeError::InvalidData(format!(
145			"digest inner type tag {} does not fit in a type tag byte",
146			encoded.constraint_param2
147		))
148	})?;
149	let inner = TypeTag::from_byte(inner_byte)?.to_type()?;
150	Digest::new(inner.clone(), encoded.constraint_param1)
151		.map_err(|error| DecodeError::InvalidData(format!("invalid digest type: {error}")))?;
152	let base = ValueType::Digest {
153		inner: Box::new(inner),
154		accuracy: encoded.constraint_param1,
155	};
156	Ok((0..tag.depth()).fold(base, |ty, _| ValueType::Option(Box::new(ty))))
157}