Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::consensus::basic::data_contract::DocumentTypesAreMissingError;
use crate::data_contract::config::DataContractConfig;
use crate::data_contract::document_type::class_methods::consensus_or_protocol_data_contract_error;
use crate::data_contract::document_type::DocumentType;
use crate::data_contract::errors::DataContractError;
use crate::data_contract::{DocumentName, TokenConfiguration, TokenContractPosition};
use crate::validation::operations::ProtocolValidationOperation;
use crate::version::PlatformVersion;
Expand All @@ -28,7 +29,7 @@ impl DocumentType {

if document_schemas.is_empty() && !has_tokens {
return Err(consensus_or_protocol_data_contract_error(
DocumentTypesAreMissingError::new(data_contract_id).into(),
DataContractError::from(DocumentTypesAreMissingError::new(data_contract_id)).into(),
));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::data_contract::errors::DataContractError;
use crate::ProtocolError;

mod create_document_types_from_document_schemas;
mod parse_property_reference;
mod should_use_creator_id;
mod system_properties;
mod try_from_schema;
Expand All @@ -26,6 +27,25 @@ pub(crate) fn consensus_or_protocol_data_contract_error(
}
}

#[inline]
pub(crate) fn consensus_or_protocol_data_contract_error_from_wrapped_protocol_error(
data_contract_error: ProtocolError,
) -> ProtocolError {
#[cfg(feature = "validation")]
{
match data_contract_error {
ProtocolError::DataContractError(err) => ProtocolError::ConsensusError(
ConsensusError::BasicError(BasicError::ContractError(err)).into(),
),
err => err,
}
}
#[cfg(not(feature = "validation"))]
{
data_contract_error
}
}

#[inline]
pub(crate) fn consensus_or_protocol_value_error(
platform_value_error: platform_value::Error,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
use crate::data_contract::document_type::reference::DocumentPropertyReference;
use crate::data_contract::document_type::{DocumentPropertyType, DocumentType};
use crate::ProtocolError;
use platform_value::Value;
use platform_version::version::PlatformVersion;
use std::collections::BTreeMap;

pub mod v0;
impl DocumentType {
pub(super) fn parse_property_reference(
data_contract_system_version: u16,
inner_properties: &BTreeMap<String, &Value>,
property_type: &DocumentPropertyType,
platform_version: &PlatformVersion,
) -> Result<Option<DocumentPropertyReference>, ProtocolError> {
match platform_version
.dpp
.contract_versions
.document_type_versions
.class_method_versions
.parse_property_reference
{
None => Ok(None),
Some(0) => DocumentType::parse_property_reference_v0(
data_contract_system_version,
inner_properties,
property_type,
),
Some(version) => Err(ProtocolError::UnknownVersionMismatch {
method: "try_from_schema".to_string(),
known_versions: vec![0],
received: version,
}),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::data_contract::config::DataContractConfig;
use crate::data_contract::document_type::accessors::DocumentTypeV0Getters;
use crate::data_contract::document_type::reference::DocumentPropertyReferenceTarget;
use assert_matches::assert_matches;
use platform_value::Identifier;
use platform_version::version::PlatformVersion;
use serde_json::json;
use std::collections::BTreeMap;

#[test]
fn should_parse_refers_to_on_identifier_property() {
let platform_version = PlatformVersion::latest();
let config =
DataContractConfig::default_for_version(platform_version).expect("config should build");

let schema = json!({
"type": "object",
"properties": {
"toUserId": {
"type": "array",
"byteArray": true,
"minItems": 32,
"maxItems": 32,
"contentMediaType": "application/x.dash.dpp.identifier",
"position": 0,
"refersTo": {
"type": "identity"
}
}
},
"required": [],
"additionalProperties": false
});

let value = platform_value::to_value(schema).expect("schema should convert");

let document_type = DocumentType::try_from_schema(
Identifier::random(),
2,
config.version(),
"msg",
value,
None,
&BTreeMap::new(),
&config,
false,
&mut vec![],
platform_version,
)
.expect("should parse");

let reference = document_type
.as_ref()
.flattened_properties()
.get("toUserId")
.and_then(|p| p.reference.clone())
.expect("reference should be present");

assert_matches!(
reference.target,
DocumentPropertyReferenceTarget::IdentityReferenceTarget
);
assert!(reference.must_exist);
}

#[test]
fn should_not_parse_refers_to_reference_on_contracts_before_system_version_2() {
let platform_version = PlatformVersion::latest();
let config =
DataContractConfig::default_for_version(platform_version).expect("config should build");

let schema = json!({
"type": "object",
"properties": {
"toUserId": {
"type": "array",
"byteArray": true,
"minItems": 32,
"maxItems": 32,
"contentMediaType": "application/x.dash.dpp.identifier",
"position": 0,
"refersTo": {
"type": "identity"
}
}
},
"required": [],
"additionalProperties": false
});

let value = platform_value::to_value(schema).expect("schema should convert");

let document_type = DocumentType::try_from_schema(
Identifier::random(),
0,
config.version(),
"msg",
value,
None,
&BTreeMap::new(),
&config,
false,
&mut vec![],
platform_version,
)
.expect("should parse");

let reference = document_type
.as_ref()
.flattened_properties()
.get("toUserId")
.and_then(|p| p.reference.clone());

assert_matches!(reference, None);
}

#[test]
fn should_reject_refers_to_on_non_identifier_property() {
let platform_version = PlatformVersion::latest();
let config =
DataContractConfig::default_for_version(platform_version).expect("config should build");

let schema = json!({
"type": "object",
"properties": {
"name": {
"type": "string",
"position": 0,
"refersTo": { "type": "identity" }
}
},
"required": [],
"additionalProperties": false
});

let value = platform_value::to_value(schema).expect("schema should convert");

let err = DocumentType::try_from_schema(
Identifier::random(),
2,
config.version(),
"msg",
value,
None,
&BTreeMap::new(),
&config,
false,
&mut vec![],
platform_version,
)
.expect_err("should fail");

let message = err.to_string();
assert!(
message.contains("refersTo is only allowed on identifier properties"),
"unexpected error: {message}"
);
}

#[test]
fn should_parse_refers_to_with_must_exist_false() {
let platform_version = PlatformVersion::latest();
let config =
DataContractConfig::default_for_version(platform_version).expect("config should build");

let schema = json!({
"type": "object",
"properties": {
"toUserId": {
"type": "array",
"byteArray": true,
"minItems": 32,
"maxItems": 32,
"contentMediaType": "application/x.dash.dpp.identifier",
"position": 0,
"refersTo": {
"type": "identity",
"mustExist": false
}
}
},
"required": [],
"additionalProperties": false
});

let value = platform_value::to_value(schema).expect("schema should convert");

let document_type = DocumentType::try_from_schema(
Identifier::random(),
2,
config.version(),
"msg",
value,
None,
&BTreeMap::new(),
&config,
false,
&mut vec![],
platform_version,
)
.expect("should parse");

let reference = document_type
.as_ref()
.flattened_properties()
.get("toUserId")
.and_then(|p| p.reference.clone())
.expect("reference should be present");

assert_matches!(
reference.target,
DocumentPropertyReferenceTarget::IdentityReferenceTarget
);
assert!(!reference.must_exist);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::data_contract::document_type::reference::{
DocumentPropertyReference, DocumentPropertyReferenceTarget,
};
use crate::data_contract::document_type::{property_names, DocumentPropertyType, DocumentType};
use crate::data_contract::errors::DataContractError;
use crate::ProtocolError;
use platform_value::btreemap_extensions::BTreeValueMapHelper;
use platform_value::Value;
use std::collections::BTreeMap;

impl DocumentType {
pub(super) fn parse_property_reference_v0(
data_contract_system_version: u16,
inner_properties: &BTreeMap<String, &Value>,
property_type: &DocumentPropertyType,
) -> Result<Option<DocumentPropertyReference>, ProtocolError> {
if data_contract_system_version < 2 {
// refers_to only available on data contracts after system version 2.
// This is because prior to system version 2 we could have arbitrary fields.
// If someone had an arbitrary field refers_to it would not have been validated
// properly to exist and could lead things into an incoherent state.
return Ok(None);
}
let Some(refers_to_value) = inner_properties.get(property_names::REFERS_TO) else {
return Ok(None);
};

if !matches!(property_type, DocumentPropertyType::Identifier) {
return Err(DataContractError::InvalidContractStructure(
"refersTo is only allowed on identifier properties".to_string(),
)
.into());
}

let refers_to_map = refers_to_value.to_btree_ref_string_map()?;

let target = match refers_to_map
.get_str(property_names::TYPE)
.map_err(|e| DataContractError::ValueWrongType(e.to_string()))?
{
"identity" => DocumentPropertyReferenceTarget::IdentityReferenceTarget,
"contract" => DocumentPropertyReferenceTarget::ContractReferenceTarget,
other => {
return Err(DataContractError::InvalidContractStructure(format!(
"invalid refersTo type {other}"
))
.into());
}
};

let must_exist = refers_to_map
.get_optional_bool("mustExist")
.map_err(|e| DataContractError::ValueWrongType(e.to_string()))?
.unwrap_or(true);

Ok(Some(DocumentPropertyReference { target, must_exist }))
}
}
Loading
Loading