diff --git a/packages/rs-dpp/src/data_contract/document_type/document_type.rs b/packages/rs-dpp/src/data_contract/document_type/document_type.rs index 71d1e206894..fbfe87598dd 100644 --- a/packages/rs-dpp/src/data_contract/document_type/document_type.rs +++ b/packages/rs-dpp/src/data_contract/document_type/document_type.rs @@ -231,165 +231,6 @@ impl DocumentType { cbor_inner_array_of_strings(document_type_value_map, property_names::REQUIRED) .unwrap_or_default(); - fn insert_values( - document_properties: &mut BTreeMap, - known_required: &mut BTreeSet, - prefix: Option<&str>, - property_key: String, - property_value: &Value, - definition_references: &BTreeMap, - ) -> Result<(), ProtocolError> { - let prefixed_property_key = match prefix { - None => property_key, - Some(prefix) => [prefix, property_key.as_str()].join("."), - }; - - let Some(inner_property_values) = property_value.as_map() else { - return Err(ProtocolError::DataContractError(DataContractError::InvalidContractStructure( - "document property is not a map as expected", - ))); - }; - let base_inner_properties = cbor_map_to_btree_map(inner_property_values); - - let type_value = cbor_inner_text_value(inner_property_values, "type")?; - - let (type_value, inner_properties) = match type_value { - None => { - let ref_value = base_inner_properties - .get_optional_string(property_names::REF)? - .ok_or({ - DataContractError::InvalidContractStructure("cannot find type property") - })?; - let Some(ref_value) = ref_value.strip_prefix("#/$defs/") else { - return Err(ProtocolError::DataContractError( - DataContractError::InvalidContractStructure("malformed reference"), - )); - }; - let inner_properties_map = definition_references - .get_optional_inner_borrowed_map(ref_value)? - .ok_or({ - ProtocolError::DataContractError( - DataContractError::ReferenceDefinitionNotFound( - "document reference not found", - ), - ) - })?; - - let type_value = - cbor_inner_text_value(inner_properties_map, property_names::TYPE)?.ok_or( - { - ProtocolError::DataContractError( - DataContractError::InvalidContractStructure( - "cannot find type property on reference", - ), - ) - }, - )?; - let inner_properties = cbor_map_to_btree_map(inner_properties_map); - Ok::<(&str, BTreeMap), ProtocolError>(( - type_value, - inner_properties, - )) - } - Some(type_value) => Ok((type_value, base_inner_properties)), - }?; - - let required = known_required.contains(&type_value.to_string()); - - let field_type: DocumentFieldType; - - match type_value { - "array" => { - // Only handling bytearrays for v1 - // Return an error if it is not a byte array - field_type = - match inner_properties.get_optional_bool(property_names::BYTE_ARRAY)? { - Some(inner_bool) => { - if inner_bool { - DocumentFieldType::ByteArray( - inner_properties - .get_optional_integer(property_names::MIN_ITEMS)?, - inner_properties - .get_optional_integer(property_names::MAX_ITEMS)?, - ) - } else { - return Err(ProtocolError::DataContractError( - DataContractError::InvalidContractStructure( - "byteArray should always be true if defined", - ), - )); - } - } - // TODO: Contract indices and new encoding format don't support arrays - // but we still can use them as document fields with current cbor encoding - // This is a temporary workaround to bring back v0.22 behavior and should be - // replaced with a proper array support in future versions - None => DocumentFieldType::Array(ArrayFieldType::Boolean), - }; - - document_properties.insert( - prefixed_property_key, - DocumentField { - document_type: field_type, - required, - }, - ); - } - "object" => { - let properties = inner_properties - .get(property_names::PROPERTIES) - .ok_or(ProtocolError::StructureError( - StructureError::KeyValueMustExist("object must have properties"), - ))? - .as_map() - .ok_or(ProtocolError::StructureError( - StructureError::ValueWrongType("properties must be a map"), - ))?; - for (object_property_key, object_property_value) in properties.into_iter() { - let object_property_string = object_property_key - .as_text() - .ok_or(ProtocolError::StructureError(StructureError::KeyWrongType( - "property key must be a string", - )))? - .to_string(); - insert_values( - document_properties, - known_required, - Some(&prefixed_property_key), - object_property_string, - object_property_value, - definition_references, - )? - } - } - "string" => { - field_type = DocumentFieldType::String( - inner_properties.get_optional_integer(property_names::MIN_ITEMS)?, - inner_properties.get_optional_integer(property_names::MAX_ITEMS)?, - ); - document_properties.insert( - prefixed_property_key, - DocumentField { - document_type: field_type, - required, - }, - ); - } - _ => { - field_type = string_to_field_type(type_value) - .ok_or(DataContractError::ValueWrongType("invalid type"))?; - document_properties.insert( - prefixed_property_key, - DocumentField { - document_type: field_type, - required, - }, - ); - } - } - Ok(()) - } - // Based on the property name, determine the type for (property_key, property_value) in property_values { insert_values( @@ -509,3 +350,162 @@ pub fn string_to_field_type(field_type_name: &str) -> Option _ => None, } } + +fn insert_values( + document_properties: &mut BTreeMap, + known_required: &mut BTreeSet, + prefix: Option, + property_key: String, + property_value: &Value, + definition_references: &BTreeMap, +) -> Result<(), ProtocolError> { + let mut to_visit: Vec<(Option, String, &Value)> = + vec![(prefix, property_key, property_value)]; + + while let Some((prefix, property_key, property_value)) = to_visit.pop() { + let prefixed_property_key = match prefix { + None => property_key, + Some(prefix) => [prefix, property_key].join(".").to_owned(), + }; + let (type_value, inner_properties) = + get_type_and_properties(property_value, definition_references)?; + let is_required = known_required.contains(&type_value.to_string()); + let field_type: DocumentFieldType; + + match type_value { + "array" => { + // Only handling bytearrays for v1 + // Return an error if it is not a byte array + field_type = match inner_properties.get_optional_bool(property_names::BYTE_ARRAY)? { + Some(inner_bool) => { + if inner_bool { + DocumentFieldType::ByteArray( + inner_properties.get_optional_integer(property_names::MIN_ITEMS)?, + inner_properties.get_optional_integer(property_names::MAX_ITEMS)?, + ) + } else { + return Err(ProtocolError::DataContractError( + DataContractError::InvalidContractStructure( + "byteArray should always be true if defined", + ), + )); + } + } + // TODO: Contract indices and new encoding format don't support arrays + // but we still can use them as document fields with current cbor encoding + // This is a temporary workaround to bring back v0.22 behavior and should be + // replaced with a proper array support in future versions + None => DocumentFieldType::Array(ArrayFieldType::Boolean), + }; + + document_properties.insert( + prefixed_property_key, + DocumentField { + document_type: field_type, + required: is_required, + }, + ); + } + "object" => { + let properties = inner_properties + .get(property_names::PROPERTIES) + .ok_or(ProtocolError::StructureError( + StructureError::KeyValueMustExist("object must have properties"), + ))? + .as_map() + .ok_or(ProtocolError::StructureError( + StructureError::ValueWrongType("properties must be a map"), + ))?; + + for (object_property_key, object_property_value) in properties.iter() { + let object_property_string = object_property_key + .as_text() + .ok_or(ProtocolError::StructureError(StructureError::KeyWrongType( + "property key must be a string", + )))? + .to_string(); + to_visit.push(( + Some(prefixed_property_key.clone()), + object_property_string, + object_property_value, + )); + } + } + + "string" => { + field_type = DocumentFieldType::String( + inner_properties.get_optional_integer(property_names::MIN_ITEMS)?, + inner_properties.get_optional_integer(property_names::MAX_ITEMS)?, + ); + document_properties.insert( + prefixed_property_key, + DocumentField { + document_type: field_type, + required: is_required, + }, + ); + } + + _ => { + field_type = string_to_field_type(type_value) + .ok_or(DataContractError::ValueWrongType("invalid type"))?; + document_properties.insert( + prefixed_property_key, + DocumentField { + document_type: field_type, + required: is_required, + }, + ); + } + } + } + + Ok(()) +} + +fn get_type_and_properties<'a>( + property_value: &'a Value, + definition_references: &'a BTreeMap, +) -> Result<(&'a str, BTreeMap), ProtocolError> { + let Some(inner_property_values) = property_value.as_map() else { + return Err(ProtocolError::DataContractError(DataContractError::InvalidContractStructure( + "document property is not a map as expected", + ))); + }; + let base_inner_properties = cbor_map_to_btree_map(inner_property_values); + let type_value = cbor_inner_text_value(inner_property_values, "type")?; + let (type_value, inner_properties) = match type_value { + None => { + let ref_value = base_inner_properties + .get_optional_string(property_names::REF)? + .ok_or({ + DataContractError::InvalidContractStructure("cannot find type property") + })?; + let Some(ref_value) = ref_value.strip_prefix("#/$defs/") else { + return Err(ProtocolError::DataContractError( + DataContractError::InvalidContractStructure("malformed reference"), + )); + }; + let inner_properties_map = definition_references + .get_optional_inner_borrowed_map(ref_value)? + .ok_or({ + ProtocolError::DataContractError( + DataContractError::ReferenceDefinitionNotFound( + "document reference not found", + ), + ) + })?; + + let type_value = cbor_inner_text_value(inner_properties_map, property_names::TYPE)? + .ok_or({ + ProtocolError::DataContractError(DataContractError::InvalidContractStructure( + "cannot find type property on reference", + )) + })?; + let inner_properties = cbor_map_to_btree_map(inner_properties_map); + Ok::<(&str, BTreeMap), ProtocolError>((type_value, inner_properties)) + } + Some(type_value) => Ok((type_value, base_inner_properties)), + }?; + Ok((type_value, inner_properties)) +} diff --git a/packages/rs-dpp/src/data_contract/extra/common.rs b/packages/rs-dpp/src/data_contract/extra/common.rs index a1406e4ef16..4a0f90835fd 100644 --- a/packages/rs-dpp/src/data_contract/extra/common.rs +++ b/packages/rs-dpp/src/data_contract/extra/common.rs @@ -274,6 +274,7 @@ pub fn json_document_to_cbor( protocol_version: Option, ) -> Result, ProtocolError> { let file = File::open(path).expect("file not found"); + let reader = BufReader::new(file); let json: serde_json::Value = serde_json::from_reader(reader).expect("expected a valid json"); value_to_cbor(json, protocol_version) diff --git a/packages/rs-drive/src/drive/contract/mod.rs b/packages/rs-drive/src/drive/contract/mod.rs index f195513a590..8a76a88be26 100644 --- a/packages/rs-drive/src/drive/contract/mod.rs +++ b/packages/rs-drive/src/drive/contract/mod.rs @@ -1026,6 +1026,7 @@ mod tests { // let's construct the grovedb structure for the dashpay data contract let contract_cbor = json_document_to_cbor(contract_path, Some(1)).expect("expected to get cbor document"); + let contract = ::from_cbor(&contract_cbor, None) .expect("expected to deserialize the contract"); drive