synthra 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +40 -0
- data/.yardopts +18 -0
- data/ACCESS_DOCS.md +116 -0
- data/ADVANCED_FEATURES_SUMMARY.md +245 -0
- data/CHANGELOG.md +498 -0
- data/CODE_OF_CONDUCT.md +122 -0
- data/CONTRIBUTING.md +307 -0
- data/DOCUMENTATION.md +204 -0
- data/DOCUMENTATION_GUIDE.md +194 -0
- data/LICENSE +22 -0
- data/README.md +1458 -0
- data/Rakefile +128 -0
- data/benchmark/README.md +591 -0
- data/benchmark/batch_generation.rb +63 -0
- data/benchmark/engine_comparison.rb +295 -0
- data/benchmark/single_record.rb +108 -0
- data/benchmark/streaming.rb +74 -0
- data/docs/ACTIVERECORD_INFERENCE.md +524 -0
- data/docs/API_SERVER.md +278 -0
- data/docs/CONFIG_FILE.md +315 -0
- data/docs/DATA_CONTRACTS.md +312 -0
- data/docs/ENHANCED_REPL.md +304 -0
- data/docs/FACTORY_BOT.md +271 -0
- data/docs/GITHUB_ACTION.md +420 -0
- data/docs/GRAPHQL_EXPORT.md +303 -0
- data/docs/GRAPHQL_FEDERATION.md +651 -0
- data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
- data/docs/LSP.md +189 -0
- data/docs/MIGRATION_GENERATOR.md +547 -0
- data/docs/MOCK_SERVER.md +280 -0
- data/docs/NATIVE_ENGINE.md +298 -0
- data/docs/OPENAPI_EXPORT.md +252 -0
- data/docs/PERFORMANCE_MODE.md +228 -0
- data/docs/PERSONAS.md +547 -0
- data/docs/PROPERTY_TESTING.md +284 -0
- data/docs/PROTOBUF_EXPORT.md +276 -0
- data/docs/QUALITY_METRICS.md +555 -0
- data/docs/QUICK_REFERENCE.md +88 -0
- data/docs/RAILS_ENGINE.md +404 -0
- data/docs/RAILS_INTEGRATION.md +454 -0
- data/docs/README.md +175 -0
- data/docs/REPL_QUICK_REFERENCE.md +149 -0
- data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
- data/docs/SCENARIOS.md +576 -0
- data/docs/SECURITY_TESTING.md +318 -0
- data/docs/SNAPSHOT_TESTING.md +308 -0
- data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
- data/docs/TERRAFORM_EXPORT.md +447 -0
- data/docs/TIME_TRAVEL.md +507 -0
- data/docs/USAGE_GUIDE.md +980 -0
- data/docs/WEBHOOK_SIMULATOR.md +579 -0
- data/docs/advanced_features.md +183 -0
- data/docs/api_reference.md +153 -0
- data/docs/behaviors.md +138 -0
- data/docs/best_practices.md +191 -0
- data/docs/dsl_reference.md +172 -0
- data/docs/examples.md +162 -0
- data/docs/generation_modes.md +125 -0
- data/docs/getting_started.md +92 -0
- data/docs/index.html +177 -0
- data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
- data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
- data/docs/troubleshooting.md +215 -0
- data/docs/type_reference.md +224 -0
- data/examples/hostile_mode_demo.rb +64 -0
- data/exe/synthra +6 -0
- data/ext/synthra_native/Cargo.lock +972 -0
- data/ext/synthra_native/Cargo.toml +46 -0
- data/ext/synthra_native/extconf.rb +41 -0
- data/ext/synthra_native/src/generator.rs +397 -0
- data/ext/synthra_native/src/lib.rs +229 -0
- data/ext/synthra_native/src/types.rs +1485 -0
- data/lib/generators/synthra/install_generator.rb +86 -0
- data/lib/generators/synthra/templates/api_response.dsl +15 -0
- data/lib/generators/synthra/templates/fake_data.rake +116 -0
- data/lib/generators/synthra/templates/synthra.yml +31 -0
- data/lib/generators/synthra/templates/synthra_support.rb +19 -0
- data/lib/generators/synthra/templates/user.dsl +11 -0
- data/lib/synthra/activerecord_inference.rb +394 -0
- data/lib/synthra/api.rb +381 -0
- data/lib/synthra/api_server.rb +718 -0
- data/lib/synthra/behaviors/applicator.rb +165 -0
- data/lib/synthra/behaviors/base.rb +155 -0
- data/lib/synthra/behaviors/close_connection.rb +60 -0
- data/lib/synthra/behaviors/deprecated.rb +100 -0
- data/lib/synthra/behaviors/failure.rb +69 -0
- data/lib/synthra/behaviors/latency.rb +103 -0
- data/lib/synthra/behaviors/partial_data.rb +83 -0
- data/lib/synthra/behaviors/randomize_order.rb +74 -0
- data/lib/synthra/behaviors/registry.rb +238 -0
- data/lib/synthra/behaviors/simulate_error.rb +79 -0
- data/lib/synthra/cli/commands/base.rb +105 -0
- data/lib/synthra/cli/commands/diff.rb +227 -0
- data/lib/synthra/cli/commands/docs.rb +67 -0
- data/lib/synthra/cli/commands/export.rb +150 -0
- data/lib/synthra/cli/commands/generate.rb +72 -0
- data/lib/synthra/cli/commands/import.rb +80 -0
- data/lib/synthra/cli/commands/lint.rb +53 -0
- data/lib/synthra/cli/commands/live.rb +57 -0
- data/lib/synthra/cli/commands/seed.rb +99 -0
- data/lib/synthra/cli/commands/validate.rb +28 -0
- data/lib/synthra/cli.rb +2471 -0
- data/lib/synthra/config_file.rb +129 -0
- data/lib/synthra/configuration.rb +281 -0
- data/lib/synthra/contracts_registry.rb +408 -0
- data/lib/synthra/database_seeder.rb +268 -0
- data/lib/synthra/deterministic_ids.rb +218 -0
- data/lib/synthra/documentation_generator.rb +414 -0
- data/lib/synthra/engine.rb +251 -0
- data/lib/synthra/errors.rb +1169 -0
- data/lib/synthra/export/base.rb +85 -0
- data/lib/synthra/export/csv.rb +101 -0
- data/lib/synthra/export/graphql.rb +266 -0
- data/lib/synthra/export/graphql_federation.rb +377 -0
- data/lib/synthra/export/graphviz.rb +258 -0
- data/lib/synthra/export/javascript.rb +327 -0
- data/lib/synthra/export/json_data.rb +61 -0
- data/lib/synthra/export/json_schema.rb +290 -0
- data/lib/synthra/export/openapi.rb +514 -0
- data/lib/synthra/export/protobuf.rb +483 -0
- data/lib/synthra/export/python.rb +560 -0
- data/lib/synthra/export/sql.rb +381 -0
- data/lib/synthra/export/sql_insert.rb +152 -0
- data/lib/synthra/export/terraform.rb +501 -0
- data/lib/synthra/export/type_mapping.rb +389 -0
- data/lib/synthra/export/typescript.rb +300 -0
- data/lib/synthra/export/xml_data.rb +104 -0
- data/lib/synthra/export/yaml_data.rb +56 -0
- data/lib/synthra/export.rb +404 -0
- data/lib/synthra/factory_bot_integration.rb +157 -0
- data/lib/synthra/field.rb +440 -0
- data/lib/synthra/functions/registry.rb +104 -0
- data/lib/synthra/generator/context.rb +372 -0
- data/lib/synthra/generator/engine.rb +336 -0
- data/lib/synthra/generator/faker_adapter.rb +425 -0
- data/lib/synthra/generator/modes.rb +444 -0
- data/lib/synthra/generator/resolver.rb +256 -0
- data/lib/synthra/generator/rng.rb +273 -0
- data/lib/synthra/generator/streamer.rb +63 -0
- data/lib/synthra/generator/uniqueness.rb +118 -0
- data/lib/synthra/initializer.rb +97 -0
- data/lib/synthra/limits.rb +279 -0
- data/lib/synthra/live_preview.rb +518 -0
- data/lib/synthra/loader_config.rb +78 -0
- data/lib/synthra/lsp/server.rb +689 -0
- data/lib/synthra/migration_generator.rb +301 -0
- data/lib/synthra/mixin.rb +174 -0
- data/lib/synthra/mock_server.rb +458 -0
- data/lib/synthra/native_engine.rb +304 -0
- data/lib/synthra/openapi_importer.rb +228 -0
- data/lib/synthra/output/json_formatter.rb +40 -0
- data/lib/synthra/output/ndjson_formatter.rb +55 -0
- data/lib/synthra/parser/ast.rb +1287 -0
- data/lib/synthra/parser/lexer.rb +1152 -0
- data/lib/synthra/parser/parser.rb +1664 -0
- data/lib/synthra/parser/tokens.rb +461 -0
- data/lib/synthra/performance_mode.rb +364 -0
- data/lib/synthra/personas.rb +397 -0
- data/lib/synthra/property_testing.rb +172 -0
- data/lib/synthra/quality_metrics.rb +405 -0
- data/lib/synthra/rails_test_helper.rb +248 -0
- data/lib/synthra/registry.rb +533 -0
- data/lib/synthra/relationships.rb +193 -0
- data/lib/synthra/repl/enhanced_repl.rb +607 -0
- data/lib/synthra/repl/formatter.rb +191 -0
- data/lib/synthra/scenarios.rb +423 -0
- data/lib/synthra/schema.rb +605 -0
- data/lib/synthra/schema_inheritance.rb +104 -0
- data/lib/synthra/schema_versioning.rb +212 -0
- data/lib/synthra/snapshot_testing.rb +199 -0
- data/lib/synthra/time_travel.rb +338 -0
- data/lib/synthra/type_definitions.rb +274 -0
- data/lib/synthra/types/address_location/addresses.rb +125 -0
- data/lib/synthra/types/address_location/airports.rb +200 -0
- data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
- data/lib/synthra/types/address_location/locations.rb +406 -0
- data/lib/synthra/types/base.rb +48 -0
- data/lib/synthra/types/commerce_products/commerce.rb +119 -0
- data/lib/synthra/types/commerce_products/companies.rb +71 -0
- data/lib/synthra/types/commerce_products/construction.rb +126 -0
- data/lib/synthra/types/commerce_products/products.rb +205 -0
- data/lib/synthra/types/core/collections.rb +513 -0
- data/lib/synthra/types/core/defaults.rb +76 -0
- data/lib/synthra/types/core/enums.rb +102 -0
- data/lib/synthra/types/core/identifiers.rb +445 -0
- data/lib/synthra/types/core/primitives.rb +586 -0
- data/lib/synthra/types/core/references.rb +466 -0
- data/lib/synthra/types/core/sequences.rb +304 -0
- data/lib/synthra/types/crypto/crypto.rb +162 -0
- data/lib/synthra/types/date_time/dates.rb +387 -0
- data/lib/synthra/types/finance_banking/banking.rb +424 -0
- data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
- data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
- data/lib/synthra/types/formula.rb +184 -0
- data/lib/synthra/types/health_medical/medical.rb +150 -0
- data/lib/synthra/types/hostile_payloads.rb +122 -0
- data/lib/synthra/types/json_array.rb +51 -0
- data/lib/synthra/types/media_entertainment/media.rb +62 -0
- data/lib/synthra/types/naughty_string.rb +30 -0
- data/lib/synthra/types/personal_names/chinese.rb +41 -0
- data/lib/synthra/types/personal_names/identifiers.rb +187 -0
- data/lib/synthra/types/personal_names/names.rb +246 -0
- data/lib/synthra/types/personal_names/national_id.rb +89 -0
- data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
- data/lib/synthra/types/regex.rb +248 -0
- data/lib/synthra/types/registry.rb +182 -0
- data/lib/synthra/types/repeating_element.rb +50 -0
- data/lib/synthra/types/scenario.rb +29 -0
- data/lib/synthra/types/technology_internet/apps.rb +67 -0
- data/lib/synthra/types/technology_internet/communication.rb +162 -0
- data/lib/synthra/types/technology_internet/devices.rb +80 -0
- data/lib/synthra/types/technology_internet/formats.rb +139 -0
- data/lib/synthra/types/technology_internet/networking.rb +143 -0
- data/lib/synthra/types/template.rb +92 -0
- data/lib/synthra/types/text_content/business.rb +128 -0
- data/lib/synthra/types/text_content/colors.rb +70 -0
- data/lib/synthra/types/text_content/misc.rb +237 -0
- data/lib/synthra/types/text_content/security.rb +130 -0
- data/lib/synthra/types/text_content/text_generation.rb +532 -0
- data/lib/synthra/types/travel/travel.rb +150 -0
- data/lib/synthra/utils/string_distance.rb +133 -0
- data/lib/synthra/validator/dsl_validator.rb +339 -0
- data/lib/synthra/validator/path_validator.rb +615 -0
- data/lib/synthra/version.rb +35 -0
- data/lib/synthra/webhook_simulator.rb +341 -0
- data/lib/synthra.rb +259 -0
- data/schemas/address.dsl +16 -0
- data/schemas/api_response.dsl +8 -0
- data/schemas/error_payload.dsl +9 -0
- data/schemas/order.dsl +10 -0
- data/schemas/order_item.dsl +8 -0
- data/schemas/payment.dsl +11 -0
- data/schemas/social_post.dsl +13 -0
- data/schemas/user.dsl +10 -0
- data/scripts/batch_fix_all.rb +96 -0
- data/scripts/delete_old_files.rb +32 -0
- data/scripts/fix_all_grouped_files.rb +137 -0
- data/scripts/fix_all_indentation.rb +48 -0
- data/scripts/fix_all_syntax.rb +124 -0
- data/scripts/fix_grouped_files.rb +184 -0
- data/scripts/fix_syntax_errors.rb +108 -0
- data/scripts/group_domain_types.rb +112 -0
- data/scripts/group_domain_types_fixed.rb +150 -0
- data/scripts/merge_domains_to_one_file.rb +68 -0
- data/scripts/move_existing_types.rb +130 -0
- data/scripts/split_grouped_types.rb +142 -0
- data/tech_docs/README.md +134 -0
- data/tech_docs/advanced/streaming.md +405 -0
- data/tech_docs/advanced/thread_safety.md +253 -0
- data/tech_docs/api/overview.md +485 -0
- data/tech_docs/appendices/type_chart.md +193 -0
- data/tech_docs/basic_concepts.md +383 -0
- data/tech_docs/behaviors/overview.md +346 -0
- data/tech_docs/dsl/complex_types.md +790 -0
- data/tech_docs/dsl/core_types.md +464 -0
- data/tech_docs/dsl/datetime_types.md +325 -0
- data/tech_docs/dsl/field_modifiers.md +414 -0
- data/tech_docs/dsl/grammar.md +431 -0
- data/tech_docs/dsl/schema_definition.md +399 -0
- data/tech_docs/export/README.md +276 -0
- data/tech_docs/installation.md +273 -0
- data/tech_docs/integration/ci_cd.md +707 -0
- data/tech_docs/integration/ci_cd_guide.md +579 -0
- data/tech_docs/integration/factory_bot.md +485 -0
- data/tech_docs/integration/rails.md +630 -0
- data/tech_docs/integration/rspec.md +449 -0
- data/tech_docs/modes/overview.md +350 -0
- data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
- data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
- data/tech_docs/quick_start.md +256 -0
- data/tech_docs/templates/README.md +805 -0
- data/tech_docs/tutorials/advanced.md +371 -0
- data/tech_docs/tutorials/getting_started.md +189 -0
- data/tech_docs/tutorials/intermediate.md +231 -0
- data/tech_docs/tutorials/template_gallery.md +569 -0
- data/tech_example/01_basic_usage.rb +238 -0
- data/tech_example/02_types_demo.rb +336 -0
- data/tech_example/04_cli_usage.md +429 -0
- data/tech_example/05_database_seeding.rb +283 -0
- data/tech_example/07_rspec_integration.rb +359 -0
- data/tech_example/10_custom_types.rb +387 -0
- data/tech_example/12_behaviors.rb +372 -0
- data/tech_example/13_twitter_dm_example.rb +120 -0
- data/tech_example/14_exact_json_structure.rb +253 -0
- data/tech_example/16_lsp_server.rb +56 -0
- data/tech_example/17_property_testing.rb +92 -0
- data/tech_example/18_enhanced_repl.rb +120 -0
- data/tech_example/19_security_fuzzing.rb +145 -0
- data/tech_example/NEW_FEATURES.md +169 -0
- data/tech_example/README.md +118 -0
- data/tech_example/schemas/api_response.dsl +123 -0
- data/tech_example/schemas/ecommerce.dsl +121 -0
- data/tech_example/schemas/twitter_dm.dsl +109 -0
- data/tech_example/schemas/user.dsl +36 -0
- data/vscode-extension/README.md +246 -0
- data/vscode-extension/language-configuration.json +31 -0
- data/vscode-extension/package.json +55 -0
- data/vscode-extension/snippets/fakedatadsl.json +198 -0
- data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
- metadata +478 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "synthra_native"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
authors = ["Synthra Team"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
publish = false
|
|
8
|
+
|
|
9
|
+
[lib]
|
|
10
|
+
crate-type = ["cdylib"]
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
# Ruby bindings
|
|
14
|
+
magnus = "0.8"
|
|
15
|
+
rb-sys = "0.9"
|
|
16
|
+
|
|
17
|
+
# Fake data generation (Rust equivalent of Ruby Faker)
|
|
18
|
+
# Use re-exported rand from fake to avoid version conflicts
|
|
19
|
+
fake = { version = "4", features = [
|
|
20
|
+
"derive",
|
|
21
|
+
"chrono",
|
|
22
|
+
"uuid",
|
|
23
|
+
"random_color",
|
|
24
|
+
"http"
|
|
25
|
+
]}
|
|
26
|
+
|
|
27
|
+
# Parallel processing - use all CPU cores
|
|
28
|
+
rayon = "1.8"
|
|
29
|
+
|
|
30
|
+
# JSON handling
|
|
31
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
32
|
+
serde_json = "1.0"
|
|
33
|
+
|
|
34
|
+
# Date/time
|
|
35
|
+
chrono = "0.4"
|
|
36
|
+
|
|
37
|
+
# UUID
|
|
38
|
+
uuid = { version = "1.0", features = ["v4", "v1", "serde"] }
|
|
39
|
+
|
|
40
|
+
[features]
|
|
41
|
+
default = []
|
|
42
|
+
|
|
43
|
+
[profile.release]
|
|
44
|
+
opt-level = 3
|
|
45
|
+
lto = true
|
|
46
|
+
codegen-units = 1
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mkmf"
|
|
4
|
+
|
|
5
|
+
# Write a no-op Makefile so `gem install` / `bundle install` succeeds WITHOUT building the
|
|
6
|
+
# Rust extension — the gem then runs in pure-Ruby mode. This is what lets the ext source ship
|
|
7
|
+
# in the gem while still installing cleanly where Rust isn't available (CI, plain installs).
|
|
8
|
+
def skip_native_build(reason)
|
|
9
|
+
warn "[synthra] native extension skipped: #{reason} (pure-Ruby mode)"
|
|
10
|
+
File.write("Makefile", <<~MAKEFILE)
|
|
11
|
+
.PHONY: all clean install
|
|
12
|
+
all:
|
|
13
|
+
\t@echo "Native extension skipped (#{reason})"
|
|
14
|
+
clean:
|
|
15
|
+
\t@echo "Nothing to clean"
|
|
16
|
+
install:
|
|
17
|
+
\t@echo "Native extension not installed"
|
|
18
|
+
MAKEFILE
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
cargo_on_path = ENV["PATH"].to_s.split(File::PATH_SEPARATOR).any? do |dir|
|
|
22
|
+
File.executable?(File.join(dir, "cargo"))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if cargo_on_path
|
|
26
|
+
begin
|
|
27
|
+
require "rb_sys/mkmf"
|
|
28
|
+
|
|
29
|
+
create_rust_makefile("synthra/synthra_native") do |r|
|
|
30
|
+
# Use release mode for best performance
|
|
31
|
+
r.profile = ENV.fetch("RB_SYS_CARGO_PROFILE", :release).to_sym
|
|
32
|
+
|
|
33
|
+
# Enable parallel feature if available
|
|
34
|
+
r.extra_cargo_args = ["--features", "parallel"] if ENV["PARALLEL"]
|
|
35
|
+
end
|
|
36
|
+
rescue LoadError
|
|
37
|
+
skip_native_build("rb_sys gem not available")
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
skip_native_build("Rust/cargo not found")
|
|
41
|
+
end
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
//! Generation engine for Synthra
|
|
2
|
+
//!
|
|
3
|
+
//! Handles parsing schema specs from Ruby and generating data efficiently.
|
|
4
|
+
//! Uses Rayon for parallel processing to utilize all CPU cores.
|
|
5
|
+
|
|
6
|
+
use fake::rand::rngs::StdRng;
|
|
7
|
+
use fake::rand::{Rng, SeedableRng};
|
|
8
|
+
use magnus::{Error, IntoValue, RArray, RHash, Ruby, Symbol, TryConvert, Value};
|
|
9
|
+
use rayon::prelude::*;
|
|
10
|
+
use serde_json::Map;
|
|
11
|
+
use std::fs::File;
|
|
12
|
+
use std::io::{BufWriter, Write};
|
|
13
|
+
|
|
14
|
+
use crate::types::{generate_value, Locale};
|
|
15
|
+
|
|
16
|
+
/// Field specification parsed from Ruby
|
|
17
|
+
#[derive(Clone, Debug)]
|
|
18
|
+
pub struct FieldSpec {
|
|
19
|
+
pub name: String,
|
|
20
|
+
pub type_name: String,
|
|
21
|
+
pub args: serde_json::Value,
|
|
22
|
+
pub optional: bool,
|
|
23
|
+
pub nullable: bool,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Native generator engine
|
|
27
|
+
pub struct NativeGenerator {
|
|
28
|
+
fields: Vec<FieldSpec>,
|
|
29
|
+
seed: Option<u64>,
|
|
30
|
+
locale: Locale,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl NativeGenerator {
|
|
34
|
+
/// Create generator from Ruby schema specification
|
|
35
|
+
///
|
|
36
|
+
/// Expected format: Array of [name, type_name, args_json, optional, nullable]
|
|
37
|
+
pub fn from_ruby(schema_spec: RArray, seed: Option<u64>, locale: Option<String>) -> Result<Self, Error> {
|
|
38
|
+
let mut fields = Vec::new();
|
|
39
|
+
|
|
40
|
+
for i in 0..schema_spec.len() {
|
|
41
|
+
let item: Value = schema_spec.entry(i as isize)?;
|
|
42
|
+
let field_array = RArray::try_convert(item)?;
|
|
43
|
+
|
|
44
|
+
let name: String = field_array.entry(0)?;
|
|
45
|
+
let type_name: String = field_array.entry(1)?;
|
|
46
|
+
let args_json: String = field_array.entry(2)?;
|
|
47
|
+
let optional: bool = field_array.entry::<Value>(3)
|
|
48
|
+
.map(|v| bool::try_convert(v).unwrap_or(false))
|
|
49
|
+
.unwrap_or(false);
|
|
50
|
+
let nullable: bool = field_array.entry::<Value>(4)
|
|
51
|
+
.map(|v| bool::try_convert(v).unwrap_or(false))
|
|
52
|
+
.unwrap_or(false);
|
|
53
|
+
|
|
54
|
+
let args: serde_json::Value = serde_json::from_str(&args_json)
|
|
55
|
+
.unwrap_or(serde_json::Value::Object(Map::new()));
|
|
56
|
+
|
|
57
|
+
fields.push(FieldSpec {
|
|
58
|
+
name,
|
|
59
|
+
type_name,
|
|
60
|
+
args,
|
|
61
|
+
optional,
|
|
62
|
+
nullable,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let loc = locale
|
|
67
|
+
.map(|s| Locale::from_string(&s))
|
|
68
|
+
.unwrap_or(Locale::EN);
|
|
69
|
+
|
|
70
|
+
Ok(Self {
|
|
71
|
+
fields,
|
|
72
|
+
seed,
|
|
73
|
+
locale: loc,
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Generate records and return as Ruby Array of Hashes
|
|
78
|
+
/// Uses parallel processing for large batches
|
|
79
|
+
pub fn generate_to_ruby(&self, ruby: &Ruby, count: usize) -> Result<RArray, Error> {
|
|
80
|
+
// For small batches, use single-threaded (less overhead)
|
|
81
|
+
if count < 1000 {
|
|
82
|
+
return self.generate_to_ruby_sequential(ruby, count);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Generate records in parallel
|
|
86
|
+
let records: Vec<_> = (0..count)
|
|
87
|
+
.into_par_iter()
|
|
88
|
+
.map(|i| {
|
|
89
|
+
let mut rng = self.create_rng_for_index(i);
|
|
90
|
+
self.generate_record(&mut rng, i)
|
|
91
|
+
})
|
|
92
|
+
.collect();
|
|
93
|
+
|
|
94
|
+
// Convert to Ruby (must be sequential due to Ruby GIL)
|
|
95
|
+
let result = ruby.ary_new_capa(count);
|
|
96
|
+
for record in records {
|
|
97
|
+
let hash = self.json_map_to_ruby_hash(ruby, &record)?;
|
|
98
|
+
result.push(hash)?;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Ok(result)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Sequential generation for small batches
|
|
105
|
+
fn generate_to_ruby_sequential(&self, ruby: &Ruby, count: usize) -> Result<RArray, Error> {
|
|
106
|
+
let result = ruby.ary_new_capa(count);
|
|
107
|
+
let mut rng = self.create_rng(0);
|
|
108
|
+
|
|
109
|
+
for i in 0..count {
|
|
110
|
+
let record = self.generate_record(&mut rng, i);
|
|
111
|
+
let hash = self.json_map_to_ruby_hash(ruby, &record)?;
|
|
112
|
+
result.push(hash)?;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
Ok(result)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Generate a single record to Ruby Hash
|
|
119
|
+
pub fn generate_one_to_ruby(&self, ruby: &Ruby) -> Result<Value, Error> {
|
|
120
|
+
let mut rng = self.create_rng(0);
|
|
121
|
+
let record = self.generate_record(&mut rng, 0);
|
|
122
|
+
let hash = self.json_map_to_ruby_hash(ruby, &record)?;
|
|
123
|
+
Ok(hash.into_value())
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Generate records directly to file (most efficient)
|
|
127
|
+
/// Uses parallel generation for large batches
|
|
128
|
+
pub fn generate_to_file(
|
|
129
|
+
&self,
|
|
130
|
+
count: usize,
|
|
131
|
+
path: &str,
|
|
132
|
+
format: &str,
|
|
133
|
+
) -> std::io::Result<usize> {
|
|
134
|
+
// For very large batches, use chunked parallel approach
|
|
135
|
+
if count >= 10_000 {
|
|
136
|
+
return self.generate_to_file_parallel(count, path, format);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Small batches: sequential is fine
|
|
140
|
+
let file = File::create(path)?;
|
|
141
|
+
let mut writer = BufWriter::with_capacity(8 * 1024 * 1024, file); // 8MB buffer
|
|
142
|
+
let mut rng = self.create_rng(0);
|
|
143
|
+
|
|
144
|
+
match format {
|
|
145
|
+
"csv" => {
|
|
146
|
+
let headers: Vec<&str> = self.fields.iter().map(|f| f.name.as_str()).collect();
|
|
147
|
+
writeln!(writer, "{}", headers.join(","))?;
|
|
148
|
+
for i in 0..count {
|
|
149
|
+
let record = self.generate_record(&mut rng, i);
|
|
150
|
+
writeln!(writer, "{}", self.record_to_csv_line(&record))?;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
"json" => {
|
|
154
|
+
write!(writer, "[")?;
|
|
155
|
+
for i in 0..count {
|
|
156
|
+
if i > 0 {
|
|
157
|
+
write!(writer, ",")?;
|
|
158
|
+
}
|
|
159
|
+
let record = self.generate_record(&mut rng, i);
|
|
160
|
+
write!(writer, "{}", serde_json::to_string(&record).unwrap())?;
|
|
161
|
+
}
|
|
162
|
+
writeln!(writer, "]")?;
|
|
163
|
+
}
|
|
164
|
+
_ => {
|
|
165
|
+
// Default: JSONL
|
|
166
|
+
for i in 0..count {
|
|
167
|
+
let record = self.generate_record(&mut rng, i);
|
|
168
|
+
writeln!(writer, "{}", serde_json::to_string(&record).unwrap())?;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
writer.flush()?;
|
|
174
|
+
Ok(count)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Parallel file generation for large batches
|
|
178
|
+
/// Uses chunked parallel approach to balance memory and throughput
|
|
179
|
+
fn generate_to_file_parallel(
|
|
180
|
+
&self,
|
|
181
|
+
count: usize,
|
|
182
|
+
path: &str,
|
|
183
|
+
format: &str,
|
|
184
|
+
) -> std::io::Result<usize> {
|
|
185
|
+
let file = File::create(path)?;
|
|
186
|
+
let mut writer = BufWriter::with_capacity(64 * 1024 * 1024, file); // 64MB buffer
|
|
187
|
+
|
|
188
|
+
// Process in manageable chunks to control memory usage
|
|
189
|
+
// Large enough for parallelism, small enough to avoid memory issues
|
|
190
|
+
let num_threads = rayon::current_num_threads();
|
|
191
|
+
let records_per_chunk = 100_000; // 100K records per chunk
|
|
192
|
+
let num_chunks = (count + records_per_chunk - 1) / records_per_chunk;
|
|
193
|
+
|
|
194
|
+
match format {
|
|
195
|
+
"csv" => {
|
|
196
|
+
// Write header
|
|
197
|
+
let headers: Vec<&str> = self.fields.iter().map(|f| f.name.as_str()).collect();
|
|
198
|
+
writeln!(writer, "{}", headers.join(","))?;
|
|
199
|
+
|
|
200
|
+
for chunk_idx in 0..num_chunks {
|
|
201
|
+
let start = chunk_idx * records_per_chunk;
|
|
202
|
+
let end = (start + records_per_chunk).min(count);
|
|
203
|
+
|
|
204
|
+
// Generate this chunk in parallel to a buffer
|
|
205
|
+
let chunk_buffer: String = (start..end)
|
|
206
|
+
.into_par_iter()
|
|
207
|
+
.map(|i| {
|
|
208
|
+
let mut rng = self.create_rng_for_index(i);
|
|
209
|
+
let record = self.generate_record(&mut rng, i);
|
|
210
|
+
format!("{}\n", self.record_to_csv_line(&record))
|
|
211
|
+
})
|
|
212
|
+
.collect();
|
|
213
|
+
|
|
214
|
+
write!(writer, "{}", chunk_buffer)?;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
"json" => {
|
|
218
|
+
write!(writer, "[")?;
|
|
219
|
+
let mut first = true;
|
|
220
|
+
|
|
221
|
+
for chunk_idx in 0..num_chunks {
|
|
222
|
+
let start = chunk_idx * records_per_chunk;
|
|
223
|
+
let end = (start + records_per_chunk).min(count);
|
|
224
|
+
|
|
225
|
+
// Generate this chunk in parallel
|
|
226
|
+
let json_strings: Vec<String> = (start..end)
|
|
227
|
+
.into_par_iter()
|
|
228
|
+
.map(|i| {
|
|
229
|
+
let mut rng = self.create_rng_for_index(i);
|
|
230
|
+
let record = self.generate_record(&mut rng, i);
|
|
231
|
+
serde_json::to_string(&record).unwrap()
|
|
232
|
+
})
|
|
233
|
+
.collect();
|
|
234
|
+
|
|
235
|
+
for json in json_strings {
|
|
236
|
+
if !first {
|
|
237
|
+
write!(writer, ",")?;
|
|
238
|
+
}
|
|
239
|
+
first = false;
|
|
240
|
+
write!(writer, "{}", json)?;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
writeln!(writer, "]")?;
|
|
244
|
+
}
|
|
245
|
+
_ => {
|
|
246
|
+
// JSONL - chunked parallel generation
|
|
247
|
+
for chunk_idx in 0..num_chunks {
|
|
248
|
+
let start = chunk_idx * records_per_chunk;
|
|
249
|
+
let end = (start + records_per_chunk).min(count);
|
|
250
|
+
|
|
251
|
+
// Generate this chunk in parallel to a single buffer string
|
|
252
|
+
let chunk_buffer: String = (start..end)
|
|
253
|
+
.into_par_iter()
|
|
254
|
+
.map(|i| {
|
|
255
|
+
let mut rng = self.create_rng_for_index(i);
|
|
256
|
+
let record = self.generate_record(&mut rng, i);
|
|
257
|
+
format!("{}\n", serde_json::to_string(&record).unwrap())
|
|
258
|
+
})
|
|
259
|
+
.collect();
|
|
260
|
+
|
|
261
|
+
write!(writer, "{}", chunk_buffer)?;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
writer.flush()?;
|
|
267
|
+
Ok(count)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// Convert record to CSV line
|
|
271
|
+
fn record_to_csv_line(&self, record: &Map<String, serde_json::Value>) -> String {
|
|
272
|
+
let values: Vec<String> = self
|
|
273
|
+
.fields
|
|
274
|
+
.iter()
|
|
275
|
+
.map(|f| match record.get(&f.name) {
|
|
276
|
+
Some(serde_json::Value::String(s)) => {
|
|
277
|
+
if s.contains(',') || s.contains('"') || s.contains('\n') {
|
|
278
|
+
format!("\"{}\"", s.replace('"', "\"\""))
|
|
279
|
+
} else {
|
|
280
|
+
s.clone()
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
Some(v) => v.to_string(),
|
|
284
|
+
None => String::new(),
|
|
285
|
+
})
|
|
286
|
+
.collect();
|
|
287
|
+
values.join(",")
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/// Generate a single record as JSON object
|
|
291
|
+
fn generate_record(&self, rng: &mut StdRng, index: usize) -> Map<String, serde_json::Value> {
|
|
292
|
+
let mut record = Map::new();
|
|
293
|
+
|
|
294
|
+
for field in &self.fields {
|
|
295
|
+
// Handle optional fields (skip ~20%)
|
|
296
|
+
if field.optional && rng.random_bool(0.2) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Handle nullable fields (null ~10%)
|
|
301
|
+
if field.nullable && rng.random_bool(0.1) {
|
|
302
|
+
record.insert(field.name.clone(), serde_json::Value::Null);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let value = generate_value(rng, &field.type_name, &field.args, self.locale, index);
|
|
307
|
+
record.insert(field.name.clone(), value);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
record
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/// Create RNG from seed
|
|
314
|
+
fn create_rng(&self, offset: u64) -> StdRng {
|
|
315
|
+
match self.seed {
|
|
316
|
+
Some(seed) => StdRng::seed_from_u64(seed.wrapping_add(offset)),
|
|
317
|
+
None => {
|
|
318
|
+
// Generate random seed from system time
|
|
319
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
320
|
+
let seed = SystemTime::now()
|
|
321
|
+
.duration_since(UNIX_EPOCH)
|
|
322
|
+
.map(|d| d.as_nanos() as u64)
|
|
323
|
+
.unwrap_or(42);
|
|
324
|
+
StdRng::seed_from_u64(seed)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/// Create RNG for a specific record index (used in parallel generation)
|
|
330
|
+
/// Each index gets a deterministic, unique seed based on base seed + index
|
|
331
|
+
fn create_rng_for_index(&self, index: usize) -> StdRng {
|
|
332
|
+
let base_seed = self.seed.unwrap_or_else(|| {
|
|
333
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
334
|
+
SystemTime::now()
|
|
335
|
+
.duration_since(UNIX_EPOCH)
|
|
336
|
+
.map(|d| d.as_nanos() as u64)
|
|
337
|
+
.unwrap_or(42)
|
|
338
|
+
});
|
|
339
|
+
// Mix seed with index to ensure each record is unique but deterministic
|
|
340
|
+
StdRng::seed_from_u64(base_seed.wrapping_mul(31).wrapping_add(index as u64))
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/// Convert serde_json Map to Ruby Hash
|
|
344
|
+
fn json_map_to_ruby_hash(
|
|
345
|
+
&self,
|
|
346
|
+
ruby: &Ruby,
|
|
347
|
+
record: &Map<String, serde_json::Value>,
|
|
348
|
+
) -> Result<RHash, Error> {
|
|
349
|
+
let hash = ruby.hash_new();
|
|
350
|
+
|
|
351
|
+
for (key, value) in record {
|
|
352
|
+
let ruby_key = Symbol::new(key);
|
|
353
|
+
let ruby_value = self.json_to_ruby(ruby, value)?;
|
|
354
|
+
hash.aset(ruby_key, ruby_value)?;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
Ok(hash)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/// Convert serde_json Value to Ruby Value
|
|
361
|
+
fn json_to_ruby(&self, ruby: &Ruby, value: &serde_json::Value) -> Result<Value, Error> {
|
|
362
|
+
match value {
|
|
363
|
+
serde_json::Value::Null => Ok(ruby.qnil().into_value()),
|
|
364
|
+
serde_json::Value::Bool(b) => {
|
|
365
|
+
if *b {
|
|
366
|
+
Ok(ruby.qtrue().into_value())
|
|
367
|
+
} else {
|
|
368
|
+
Ok(ruby.qfalse().into_value())
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
serde_json::Value::Number(n) => {
|
|
372
|
+
if let Some(i) = n.as_i64() {
|
|
373
|
+
Ok(ruby.integer_from_i64(i).into_value())
|
|
374
|
+
} else if let Some(f) = n.as_f64() {
|
|
375
|
+
Ok(ruby.float_from_f64(f).into_value())
|
|
376
|
+
} else {
|
|
377
|
+
Ok(ruby.qnil().into_value())
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
serde_json::Value::String(s) => Ok(ruby.str_new(s).into_value()),
|
|
381
|
+
serde_json::Value::Array(arr) => {
|
|
382
|
+
let ruby_arr = ruby.ary_new_capa(arr.len());
|
|
383
|
+
for item in arr {
|
|
384
|
+
ruby_arr.push(self.json_to_ruby(ruby, item)?)?;
|
|
385
|
+
}
|
|
386
|
+
Ok(ruby_arr.into_value())
|
|
387
|
+
}
|
|
388
|
+
serde_json::Value::Object(obj) => {
|
|
389
|
+
let ruby_hash = ruby.hash_new();
|
|
390
|
+
for (k, v) in obj {
|
|
391
|
+
ruby_hash.aset(Symbol::new(k), self.json_to_ruby(ruby, v)?)?;
|
|
392
|
+
}
|
|
393
|
+
Ok(ruby_hash.into_value())
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|