diff --git a/packages/platform-test-suite/test/functional/platform/DataContract.spec.js b/packages/platform-test-suite/test/functional/platform/DataContract.spec.js index 348740129b2..18cf12f1132 100644 --- a/packages/platform-test-suite/test/functional/platform/DataContract.spec.js +++ b/packages/platform-test-suite/test/functional/platform/DataContract.spec.js @@ -59,6 +59,31 @@ describe('Platform', () => { expect(broadcastError.getCause()).to.be.an.instanceOf(IdentityNotFoundError); }); + it('should expose validation error when document property positions are not contiguous', async () => { + // Additional wait time to mitigate testnet latency + await waitForSTPropagated(); + + const identityNonce = await client.platform.nonceManager + .bumpIdentityNonce(identity.getId()); + const invalidDataContract = await getDataContractFixture(identityNonce, identity.getId()); + + const documentSchema = invalidDataContract.getDocumentSchema('niceDocument'); + documentSchema.properties.name.position = 5; + invalidDataContract.setDocumentSchema('niceDocument', documentSchema, { skipValidation: true }); + + let broadcastError; + + try { + await client.platform.contracts.publish(invalidDataContract, identity); + } catch (e) { + broadcastError = e; + } + + expect(broadcastError).to.be.an.instanceOf(StateTransitionBroadcastError); + expect(broadcastError.getCode()).to.equal(10411); + expect(broadcastError.getMessage()).to.equal('position field is not present for document type "niceDocument"'); + }); + it('should create new data contract with previously created identity as an owner', async () => { // Additional wait time to mitigate testnet latency await waitForSTPropagated(); diff --git a/packages/rs-dapi/src/services/platform_service/error_mapping.rs b/packages/rs-dapi/src/services/platform_service/error_mapping.rs index c2cdf252d9a..ecac28bc54e 100644 --- a/packages/rs-dapi/src/services/platform_service/error_mapping.rs +++ b/packages/rs-dapi/src/services/platform_service/error_mapping.rs @@ -139,9 +139,25 @@ impl From for tonic::Response for StateTransitionBroadcastError { fn from(err: TenderdashStatus) -> Self { + let message = if let Some(msg) = err.message { + msg + } else { + // try to extract from consensus error + if let Some(consensus_error_bytes) = &err.consensus_error + && let Ok(consensus_error) = + ConsensusError::deserialize_from_bytes(consensus_error_bytes).inspect_err(|e| { + tracing::debug!("Failed to deserialize consensus error: {}", e); + }) + { + consensus_error.to_string() + } else { + "Unknown error".to_string() + } + }; + StateTransitionBroadcastError { code: err.code.clamp(0, u32::MAX as i64) as u32, - message: err.message.unwrap_or_else(|| "Unknown error".to_string()), + message, data: err.consensus_error.clone().unwrap_or_default(), } }