dpp/data_contract/methods/validate_document/v0/
mod.rs1use crate::data_contract::accessors::v0::DataContractV0Getters;
2use crate::data_contract::document_type::accessors::DocumentTypeV0Getters;
3use crate::data_contract::document_type::methods::{
4 DocumentTypeBasicMethods, DocumentTypeV0Methods,
5};
6use crate::data_contract::document_type::property_constraints::DocumentSystemValues;
7use crate::data_contract::document_type::DocumentType;
8
9use crate::consensus::basic::document::{
10 DocumentFieldMaxSizeExceededError, InvalidDocumentTypeError,
11};
12use crate::consensus::basic::value_error::ValueError;
13use crate::consensus::basic::BasicError;
14use crate::consensus::ConsensusError;
15use crate::data_contract::schema::DataContractSchemaMethodsV0;
16use crate::data_contract::DataContract;
17use crate::document::{Document, DocumentV0Getters};
18use crate::validation::SimpleConsensusValidationResult;
19use crate::ProtocolError;
20use platform_value::Value;
21use platform_version::version::PlatformVersion;
22use std::ops::Deref;
23
24pub trait DataContractDocumentValidationMethodsV0 {
25 fn validate_document(
26 &self,
27 name: &str,
28 document: &Document,
29 platform_version: &PlatformVersion,
30 ) -> Result<SimpleConsensusValidationResult, ProtocolError>;
31
32 fn validate_document_properties(
40 &self,
41 name: &str,
42 value: Value,
43 system: &DocumentSystemValues,
44 platform_version: &PlatformVersion,
45 ) -> Result<SimpleConsensusValidationResult, ProtocolError>;
46}
47
48impl DataContract {
49 #[inline(always)]
50 pub(super) fn validate_document_properties_v0(
51 &self,
52 name: &str,
53 value: Value,
54 system: &DocumentSystemValues,
55 platform_version: &PlatformVersion,
56 ) -> Result<SimpleConsensusValidationResult, ProtocolError> {
57 let Some(document_type) = self.document_type_optional_for_name(name) else {
58 return Ok(SimpleConsensusValidationResult::new_with_error(
59 InvalidDocumentTypeError::new(name.to_owned(), self.id()).into(),
60 ));
61 };
62
63 if let Some(max_depth) = platform_version.system_limits.max_document_value_depth {
64 let max_depth = max_depth as usize;
65 let excess_depth = match &value {
69 Value::Map(map) => map.iter().find_map(|(key, property_value)| {
70 key.first_depth_exceeding(max_depth)
71 .or_else(|| property_value.first_depth_exceeding(max_depth))
72 }),
73 other => other.first_depth_exceeding(max_depth),
74 };
75 if let Some(actual_depth) = excess_depth {
76 return Ok(SimpleConsensusValidationResult::new_with_error(
77 ConsensusError::BasicError(BasicError::ValueError(
78 ValueError::new_from_string(format!(
79 "document value depth {actual_depth} exceeds system maximum {max_depth}"
80 )),
81 )),
82 ));
83 }
84 }
85
86 let validator = document_type.json_schema_validator_ref().deref();
87
88 if let Some((key, size)) =
89 value.has_data_larger_than(platform_version.system_limits.max_field_value_size)
90 {
91 let field = match key {
92 Some(Value::Text(field)) => field.clone(),
93 _ => "".to_string(),
94 };
95 return Ok(SimpleConsensusValidationResult::new_with_error(
96 ConsensusError::BasicError(BasicError::DocumentFieldMaxSizeExceededError(
97 DocumentFieldMaxSizeExceededError::new(
98 field,
99 size as u64,
100 platform_version.system_limits.max_field_value_size as u64,
101 ),
102 )),
103 ));
104 }
105
106 let max_bytes_result =
112 document_type.validate_max_bytes_properties(&value, platform_version)?;
113
114 let generated_from_result =
121 document_type.validate_generated_from_properties(&value, platform_version)?;
122
123 let property_constraints_result =
132 document_type.validate_property_constraints(&value, system, platform_version)?;
133
134 let json_value = match value.try_into_validating_json() {
135 Ok(json_value) => json_value,
136 Err(e) => {
137 return Ok(SimpleConsensusValidationResult::new_with_error(
138 ConsensusError::BasicError(BasicError::ValueError(e.into())),
139 ))
140 }
141 };
142
143 let schema_result = if !validator.is_compiled(platform_version)? {
145 let root_schema = DocumentType::enrich_with_base_schema(
148 document_type.schema().clone(),
151 self.schema_defs().map(|defs| Value::from(defs.clone())),
152 platform_version,
153 )?;
154
155 let root_json_schema = root_schema
156 .try_to_validating_json()
157 .map_err(ProtocolError::ValueError)?;
158
159 validator.compile_and_validate(&root_json_schema, &json_value, platform_version)?
160 } else {
161 validator.validate(&json_value, platform_version)?
162 };
163 if !schema_result.is_valid() {
164 return Ok(schema_result);
165 }
166 if !max_bytes_result.is_valid() {
167 return Ok(max_bytes_result);
168 }
169 if !generated_from_result.is_valid() {
170 return Ok(generated_from_result);
171 }
172
173 Ok(property_constraints_result)
174 }
175
176 #[inline(always)]
177 pub(super) fn validate_document_v0(
178 &self,
179 name: &str,
180 document: &Document,
181 platform_version: &PlatformVersion,
182 ) -> Result<SimpleConsensusValidationResult, ProtocolError> {
183 self.validate_document_properties_v0(
185 name,
186 document.properties().into(),
187 &DocumentSystemValues::of_document(document),
188 platform_version,
189 )
190 }
191}
192
193#[cfg(all(test, feature = "fixtures-and-mocks"))]
194mod tests {
195 use super::DataContractDocumentValidationMethodsV0;
196 use crate::consensus::basic::value_error::ValueError;
197 use crate::consensus::basic::BasicError;
198 use crate::consensus::ConsensusError;
199 use crate::data_contract::created_data_contract::CreatedDataContract;
200 use crate::data_contract::document_type::property_constraints::DocumentSystemValues;
201 use crate::tests::fixtures::get_data_contract_fixture;
202 use platform_value::Value;
203 use platform_version::version::PlatformVersion;
204
205 fn data_contract() -> CreatedDataContract {
206 let platform_version = PlatformVersion::get(13).expect("protocol version 13 exists");
207 get_data_contract_fixture(None, 0, platform_version.protocol_version)
208 }
209
210 fn nested_document_value(container_count: usize, leaf: Value) -> Value {
211 let nested = (0..container_count).fold(leaf, |value, depth| {
212 if depth % 2 == 0 {
213 Value::Array(vec![value])
214 } else {
215 Value::Map(vec![(Value::Text("nested".to_owned()), value)])
216 }
217 });
218
219 Value::Map(vec![(Value::Text("name".to_owned()), nested)])
220 }
221
222 #[test]
223 fn should_reject_excessive_document_value_depth_before_field_size_validation() {
224 let platform_version = PlatformVersion::get(13).expect("protocol version 13 exists");
225 let data_contract = data_contract().data_contract_owned();
226 let max_depth = platform_version
227 .system_limits
228 .max_document_value_depth
229 .expect("protocol version 13 should enforce document value depth");
230 let value = nested_document_value(
231 max_depth as usize + 1,
232 Value::Text(
233 "x".repeat(platform_version.system_limits.max_field_value_size as usize + 1),
234 ),
235 );
236
237 let result = data_contract
238 .validate_document_properties(
239 "noTimeDocument",
240 value,
241 &DocumentSystemValues::default(),
242 platform_version,
243 )
244 .expect("validation should return a consensus result");
245
246 let Some(ConsensusError::BasicError(BasicError::ValueError(ValueError { .. }))) =
247 result.first_error()
248 else {
249 panic!("expected document value depth error, got {result:?}");
250 };
251 assert_eq!(
252 result.first_error().expect("expected an error").to_string(),
253 format!(
254 "document value depth {} exceeds system maximum {max_depth}",
255 max_depth + 1
256 )
257 );
258 }
259
260 #[test]
261 fn should_allow_document_value_depth_at_the_limit() {
262 let platform_version = PlatformVersion::get(13).expect("protocol version 13 exists");
263 let data_contract = data_contract().data_contract_owned();
264 let max_depth = platform_version
265 .system_limits
266 .max_document_value_depth
267 .expect("protocol version 13 should enforce document value depth");
268 let value = nested_document_value(
269 max_depth as usize,
270 Value::Text(
271 "x".repeat(platform_version.system_limits.max_field_value_size as usize + 1),
272 ),
273 );
274
275 let result = data_contract
276 .validate_document_properties(
277 "noTimeDocument",
278 value,
279 &DocumentSystemValues::default(),
280 platform_version,
281 )
282 .expect("validation should return a consensus result");
283
284 assert!(matches!(
285 result.first_error(),
286 Some(ConsensusError::BasicError(
287 BasicError::DocumentFieldMaxSizeExceededError(_)
288 ))
289 ));
290 }
291
292 #[test]
293 fn should_preserve_valid_document_properties() {
294 let platform_version = PlatformVersion::get(13).expect("protocol version 13 exists");
295 let data_contract = data_contract().data_contract_owned();
296 let value = Value::Map(vec![(
297 Value::Text("name".to_owned()),
298 Value::Text("Alice".to_owned()),
299 )]);
300
301 let result = data_contract
302 .validate_document_properties(
303 "noTimeDocument",
304 value,
305 &DocumentSystemValues::default(),
306 platform_version,
307 )
308 .expect("validation should return a consensus result");
309
310 assert!(
311 result.is_valid(),
312 "expected valid properties, got {result:?}"
313 );
314 }
315}