Skip to content

Commit

Permalink
refactor: extract document faker to crate
Browse files Browse the repository at this point in the history
  • Loading branch information
shumkov committed Jun 13, 2024
1 parent 2c9368a commit 71475f8
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 216 deletions.
5 changes: 5 additions & 0 deletions .github/package-filters/rs-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,8 @@ dash-sdk:
- packages/rs-sdk/**
- *dapi_client
- *drive

random-document:
- .github/workflows/tests*
- packages/rs-random-document/**
- *dpp
12 changes: 6 additions & 6 deletions .github/workflows/tests-rs-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ jobs:
components: clippy

- name: Setup Node.JS, required by json-schema-faker-rs
if: inputs.package == 'dpp'
if: inputs.package == 'random-document'
uses: ./.github/actions/nodejs

- name: Setup Javy, required by json-schema-faker-rs
if: inputs.package == 'dpp'
if: inputs.package == 'random-document'
uses: ./.github/actions/javy

- uses: clechasseur/rs-clippy-check@v3
Expand Down Expand Up @@ -189,11 +189,11 @@ jobs:
uses: ./.github/actions/rust

- name: Setup Node.JS, required by json-schema-faker-rs
if: inputs.package == 'dpp'
if: inputs.package == 'random-document'
uses: ./.github/actions/nodejs

- name: Setup Javy, required by json-schema-faker-rs
if: inputs.package == 'dpp'
if: inputs.package == 'random-document'
uses: ./.github/actions/javy

- name: Run tests
Expand Down Expand Up @@ -222,11 +222,11 @@ jobs:
uses: ./.github/actions/rust

- name: Setup Node.JS, required by json-schema-faker-rs
if: inputs.package == 'dpp'
if: inputs.package == 'random-document'
uses: ./.github/actions/nodejs

- name: Setup Javy, required by json-schema-faker-rs
if: inputs.package == 'dpp'
if: inputs.package == 'random-document'
uses: ./.github/actions/javy

- name: Get crate ${{ runner.arch }} info
Expand Down
15 changes: 12 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ members = [
"packages/simple-signer",
"packages/rs-json-schema-compatibility-validator",
"packages/check-features",
"packages/rs-random-document",
]

[patch.crates-io]
Expand Down
4 changes: 0 additions & 4 deletions packages/rs-dpp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ itertools = { version = "0.12.1" }
jsonschema = { git = "https://github.com/fominok/jsonschema-rs", branch = "feat-unevaluated-properties", default-features = false, features = [
"draft202012",
], optional = true }
json-schema-faker = { git = "https://github.com/fominok/json-schema-faker-rs", rev = "2dd1cf7", optional = true }
lazy_static = { version = "1.4" }
log = { version = "0.4.6" }
num_enum = "0.5.7"
Expand Down Expand Up @@ -266,6 +265,3 @@ extended-document = [

factories = []
client = ["factories", "state-transitions"]

# Faker feature requires `npm` and `javy` to be installed at build time
documents-faker = ["dep:json-schema-faker", "document-serde-conversion"]
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
#[cfg(feature = "documents-faker")]
use std::collections::BTreeMap;

use bincode::{Decode, Encode};
#[cfg(feature = "documents-faker")]
use platform_value::Value;

use platform_value::{Bytes32, Identifier};
use rand::prelude::StdRng;

Expand Down Expand Up @@ -35,17 +31,6 @@ pub enum DocumentFieldFillSize {
// TODO The factory is used in benchmark and tests. Probably it should be available under the test feature
/// Functions for creating various types of random documents.
pub trait CreateRandomDocument {
#[cfg(feature = "documents-faker")]
/// Create random documents using json-schema-faker-rs
fn random_documents_faker(
&self,
owner_id: Identifier,
entropy: &Bytes32,
count: u32,
platform_version: &PlatformVersion,
substitutions: &BTreeMap<&str, Value>,
) -> Result<Vec<Document>, ProtocolError>;

/// Generates a single random document, employing default behavior for document field
/// filling where fields that are not required will not be filled (`DoNotFillIfNotRequired`) and
/// any fill size that is contractually allowed may be used (`AnyDocumentFillSize`).
Expand Down Expand Up @@ -212,22 +197,6 @@ pub trait CreateRandomDocument {
}

impl CreateRandomDocument for DocumentType {
#[cfg(feature = "documents-faker")]
fn random_documents_faker(
&self,
owner_id: Identifier,
entropy: &Bytes32,
count: u32,
platform_version: &PlatformVersion,
substitutions: &BTreeMap<&str, Value>,
) -> Result<Vec<Document>, ProtocolError> {
match self {
DocumentType::V0(v0) => {
v0.random_documents_faker(owner_id, entropy, count, platform_version, substitutions)
}
}
}

fn random_document(
&self,
seed: Option<u64>,
Expand Down Expand Up @@ -346,22 +315,6 @@ impl CreateRandomDocument for DocumentType {
}

impl<'a> CreateRandomDocument for DocumentTypeRef<'a> {
#[cfg(feature = "documents-faker")]
fn random_documents_faker(
&self,
owner_id: Identifier,
entropy: &Bytes32,
count: u32,
platform_version: &PlatformVersion,
substitutions: &BTreeMap<&str, Value>,
) -> Result<Vec<Document>, ProtocolError> {
match self {
DocumentTypeRef::V0(v0) => {
v0.random_documents_faker(owner_id, entropy, count, platform_version, substitutions)
}
}
}

fn random_document(
&self,
seed: Option<u64>,
Expand Down Expand Up @@ -479,40 +432,3 @@ impl<'a> CreateRandomDocument for DocumentTypeRef<'a> {
}
}
}

#[cfg(all(test, feature = "documents-faker"))]
mod faker_tests {
use data_contracts::SystemDataContract;
use platform_version::TryIntoPlatformVersioned;
use rand::SeedableRng;

use crate::{
data_contract::accessors::v0::DataContractV0Getters,
system_data_contracts::load_system_data_contract,
};

use super::*;

#[test]
fn test_random_document_faker() {
let data_contract =
load_system_data_contract(SystemDataContract::DPNS, &PlatformVersion::latest())
.unwrap();
let mut rng = StdRng::from_entropy();
let entropy = Bytes32::random_with_rng(&mut rng);

let _random_documents = data_contract
.document_types()
.iter()
.next()
.unwrap()
.1
.random_documents_faker(
Identifier::random(),
&entropy,
2,
&PlatformVersion::latest(),
&Default::default(),
);
}
}
119 changes: 1 addition & 118 deletions packages/rs-dpp/src/data_contract/document_type/v0/random_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,10 @@
//!
//!
#[cfg(feature = "documents-faker")]
use std::collections::BTreeMap;
use std::time::{SystemTime, UNIX_EPOCH};

#[cfg(feature = "documents-faker")]
use platform_value::Value;
use platform_value::{Bytes32, Identifier};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use std::time::{SystemTime, UNIX_EPOCH};

use crate::data_contract::document_type::methods::DocumentTypeV0Methods;
use crate::data_contract::document_type::random_document::{
Expand All @@ -32,118 +27,6 @@ use crate::version::PlatformVersion;
use crate::ProtocolError;

impl CreateRandomDocument for DocumentTypeV0 {
/// Create random documents using json-schema-faker-rs
#[cfg(feature = "documents-faker")]
fn random_documents_faker(
&self,
owner_id: Identifier,
entropy: &Bytes32,
count: u32,
platform_version: &PlatformVersion,
substitutions: &BTreeMap<&str, Value>,
) -> Result<Vec<Document>, ProtocolError> {
use anyhow::Context;

use crate::document::{
extended_document_property_names::FEATURE_VERSION,
property_names::{ID, OWNER_ID, REVISION},
serialization_traits::DocumentPlatformValueMethodsV0,
};

let json_schema = &self.schema.clone().try_into()?;
let json_documents = json_schema_faker::generate(json_schema, count as u16)
.context("cannot generate a random document with json-schema-faker-rs")?;

let fix_document = |mut document: platform_value::Value| {
let id = Document::generate_document_id_v0(
&self.data_contract_id,
&owner_id,
self.name.as_str(),
entropy.as_slice(),
);
let now = SystemTime::now();
let duration_since_epoch = now.duration_since(UNIX_EPOCH).expect("Time went backwards");
let time_ms = duration_since_epoch.as_millis() as u64;

if self.documents_mutable {
document.as_map_mut().into_iter().for_each(|d| {
d.push((REVISION.into(), 1.into()));
});
}
document.as_map_mut().into_iter().for_each(|d| {
d.push((ID.into(), id.into()));
});
document.as_map_mut().into_iter().for_each(|d| {
d.push((OWNER_ID.into(), owner_id.into()));
});
if self.required_fields.contains(FEATURE_VERSION) {
document.as_map_mut().into_iter().for_each(|d| {
d.push((FEATURE_VERSION.into(), "0".into()));
});
}
if self.required_fields.contains(CREATED_AT) {
document.as_map_mut().into_iter().for_each(|d| {
d.push((CREATED_AT.into(), time_ms.into()));
});
}
if self.required_fields.contains(UPDATED_AT) {
document.as_map_mut().into_iter().for_each(|d| {
d.push((UPDATED_AT.into(), time_ms.into()));
});
}

document
};

json_documents
.into_iter()
.map(|d| {
let p_value: Value = d.into();
let fixed_value = fix_document(p_value);

// TODO: tl;dr use PlatformDeserialize instead of Deserialize for Documents
//
// `properties` is a `BTreeMap` with `platform_value::Value` as values, since
// `Document::from_platform_value` does deserialization through Serde's data model
// it losts some information like distinction between `Value::Bytes` and `Value::Bytes32`;
// The solution here is to let deserialize a `Document`, but put `properties` unprocessed
// since they were `platform_value::Value` and will be the same type again and no deserialization
// is needed, especially that lossy kind.
let mut properties = fixed_value
.to_map_ref()
.ok()
.and_then(|m| Value::map_into_btree_string_map(m.clone()).ok())
.unwrap_or_default();
let mut document = Document::from_platform_value(fixed_value, platform_version);
if let Ok(Document::V0(d)) = document.as_mut() {
// This moves stored properties back to the document so it could skip unnecessary
// and wrong deserialization part
d.properties.iter_mut().for_each(|(k, v)| {
substitutions
.get(k.as_str())
.cloned()
.or(properties.remove(k))
.into_iter()
.for_each(|prop| {
// TODO: schema and internal DocumentType representations are incompatible
// Properties are tweaked though, because the only integer type supported by
// DPP is i64, while `platform_value::Value` distincts them, and json schema is
// even more permissive; however, we want our proofs to work and proofs use the
// DPP model.
*v = match prop {
Value::U64(x) => Value::I64(x as i64),
Value::U32(x) => Value::I64(x as i64),
Value::I32(x) => Value::I64(x as i64),
x => x,
};
})
});
}
document
})
.collect()
}

/// Creates a random Document using a seed if given, otherwise entropy.
fn random_document(
&self,
Expand Down
18 changes: 18 additions & 0 deletions packages/rs-random-document/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "random-document"
version = "1.0.0-dev.15"
edition = "2021"
rust-version = "1.76"
authors = [
"Anton Suprunchuk <[email protected]>",
"Samuel Westrich <[email protected]>",
"Ivan Shumkov <[email protected]>",
"Djavid Gabibiyan <[email protected]>",
"Igor Markin <[email protected]>",
]

[dependencies]
json-schema-faker = { git = "https://github.com/fominok/json-schema-faker-rs", rev = "2dd1cf7" }
dpp = { path = "../rs-dpp", features = ["system_contracts", "document-value-conversion"] }
platform-value = { path = "../rs-platform-value", features = ["json"]}
anyhow = "1.0.86"
Loading

0 comments on commit 71475f8

Please sign in to comment.