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.
Files changed (302) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.rubocop.yml +40 -0
  4. data/.yardopts +18 -0
  5. data/ACCESS_DOCS.md +116 -0
  6. data/ADVANCED_FEATURES_SUMMARY.md +245 -0
  7. data/CHANGELOG.md +498 -0
  8. data/CODE_OF_CONDUCT.md +122 -0
  9. data/CONTRIBUTING.md +307 -0
  10. data/DOCUMENTATION.md +204 -0
  11. data/DOCUMENTATION_GUIDE.md +194 -0
  12. data/LICENSE +22 -0
  13. data/README.md +1458 -0
  14. data/Rakefile +128 -0
  15. data/benchmark/README.md +591 -0
  16. data/benchmark/batch_generation.rb +63 -0
  17. data/benchmark/engine_comparison.rb +295 -0
  18. data/benchmark/single_record.rb +108 -0
  19. data/benchmark/streaming.rb +74 -0
  20. data/docs/ACTIVERECORD_INFERENCE.md +524 -0
  21. data/docs/API_SERVER.md +278 -0
  22. data/docs/CONFIG_FILE.md +315 -0
  23. data/docs/DATA_CONTRACTS.md +312 -0
  24. data/docs/ENHANCED_REPL.md +304 -0
  25. data/docs/FACTORY_BOT.md +271 -0
  26. data/docs/GITHUB_ACTION.md +420 -0
  27. data/docs/GRAPHQL_EXPORT.md +303 -0
  28. data/docs/GRAPHQL_FEDERATION.md +651 -0
  29. data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
  30. data/docs/LSP.md +189 -0
  31. data/docs/MIGRATION_GENERATOR.md +547 -0
  32. data/docs/MOCK_SERVER.md +280 -0
  33. data/docs/NATIVE_ENGINE.md +298 -0
  34. data/docs/OPENAPI_EXPORT.md +252 -0
  35. data/docs/PERFORMANCE_MODE.md +228 -0
  36. data/docs/PERSONAS.md +547 -0
  37. data/docs/PROPERTY_TESTING.md +284 -0
  38. data/docs/PROTOBUF_EXPORT.md +276 -0
  39. data/docs/QUALITY_METRICS.md +555 -0
  40. data/docs/QUICK_REFERENCE.md +88 -0
  41. data/docs/RAILS_ENGINE.md +404 -0
  42. data/docs/RAILS_INTEGRATION.md +454 -0
  43. data/docs/README.md +175 -0
  44. data/docs/REPL_QUICK_REFERENCE.md +149 -0
  45. data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
  46. data/docs/SCENARIOS.md +576 -0
  47. data/docs/SECURITY_TESTING.md +318 -0
  48. data/docs/SNAPSHOT_TESTING.md +308 -0
  49. data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
  50. data/docs/TERRAFORM_EXPORT.md +447 -0
  51. data/docs/TIME_TRAVEL.md +507 -0
  52. data/docs/USAGE_GUIDE.md +980 -0
  53. data/docs/WEBHOOK_SIMULATOR.md +579 -0
  54. data/docs/advanced_features.md +183 -0
  55. data/docs/api_reference.md +153 -0
  56. data/docs/behaviors.md +138 -0
  57. data/docs/best_practices.md +191 -0
  58. data/docs/dsl_reference.md +172 -0
  59. data/docs/examples.md +162 -0
  60. data/docs/generation_modes.md +125 -0
  61. data/docs/getting_started.md +92 -0
  62. data/docs/index.html +177 -0
  63. data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
  64. data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
  65. data/docs/troubleshooting.md +215 -0
  66. data/docs/type_reference.md +224 -0
  67. data/examples/hostile_mode_demo.rb +64 -0
  68. data/exe/synthra +6 -0
  69. data/ext/synthra_native/Cargo.lock +972 -0
  70. data/ext/synthra_native/Cargo.toml +46 -0
  71. data/ext/synthra_native/extconf.rb +41 -0
  72. data/ext/synthra_native/src/generator.rs +397 -0
  73. data/ext/synthra_native/src/lib.rs +229 -0
  74. data/ext/synthra_native/src/types.rs +1485 -0
  75. data/lib/generators/synthra/install_generator.rb +86 -0
  76. data/lib/generators/synthra/templates/api_response.dsl +15 -0
  77. data/lib/generators/synthra/templates/fake_data.rake +116 -0
  78. data/lib/generators/synthra/templates/synthra.yml +31 -0
  79. data/lib/generators/synthra/templates/synthra_support.rb +19 -0
  80. data/lib/generators/synthra/templates/user.dsl +11 -0
  81. data/lib/synthra/activerecord_inference.rb +394 -0
  82. data/lib/synthra/api.rb +381 -0
  83. data/lib/synthra/api_server.rb +718 -0
  84. data/lib/synthra/behaviors/applicator.rb +165 -0
  85. data/lib/synthra/behaviors/base.rb +155 -0
  86. data/lib/synthra/behaviors/close_connection.rb +60 -0
  87. data/lib/synthra/behaviors/deprecated.rb +100 -0
  88. data/lib/synthra/behaviors/failure.rb +69 -0
  89. data/lib/synthra/behaviors/latency.rb +103 -0
  90. data/lib/synthra/behaviors/partial_data.rb +83 -0
  91. data/lib/synthra/behaviors/randomize_order.rb +74 -0
  92. data/lib/synthra/behaviors/registry.rb +238 -0
  93. data/lib/synthra/behaviors/simulate_error.rb +79 -0
  94. data/lib/synthra/cli/commands/base.rb +105 -0
  95. data/lib/synthra/cli/commands/diff.rb +227 -0
  96. data/lib/synthra/cli/commands/docs.rb +67 -0
  97. data/lib/synthra/cli/commands/export.rb +150 -0
  98. data/lib/synthra/cli/commands/generate.rb +72 -0
  99. data/lib/synthra/cli/commands/import.rb +80 -0
  100. data/lib/synthra/cli/commands/lint.rb +53 -0
  101. data/lib/synthra/cli/commands/live.rb +57 -0
  102. data/lib/synthra/cli/commands/seed.rb +99 -0
  103. data/lib/synthra/cli/commands/validate.rb +28 -0
  104. data/lib/synthra/cli.rb +2471 -0
  105. data/lib/synthra/config_file.rb +129 -0
  106. data/lib/synthra/configuration.rb +281 -0
  107. data/lib/synthra/contracts_registry.rb +408 -0
  108. data/lib/synthra/database_seeder.rb +268 -0
  109. data/lib/synthra/deterministic_ids.rb +218 -0
  110. data/lib/synthra/documentation_generator.rb +414 -0
  111. data/lib/synthra/engine.rb +251 -0
  112. data/lib/synthra/errors.rb +1169 -0
  113. data/lib/synthra/export/base.rb +85 -0
  114. data/lib/synthra/export/csv.rb +101 -0
  115. data/lib/synthra/export/graphql.rb +266 -0
  116. data/lib/synthra/export/graphql_federation.rb +377 -0
  117. data/lib/synthra/export/graphviz.rb +258 -0
  118. data/lib/synthra/export/javascript.rb +327 -0
  119. data/lib/synthra/export/json_data.rb +61 -0
  120. data/lib/synthra/export/json_schema.rb +290 -0
  121. data/lib/synthra/export/openapi.rb +514 -0
  122. data/lib/synthra/export/protobuf.rb +483 -0
  123. data/lib/synthra/export/python.rb +560 -0
  124. data/lib/synthra/export/sql.rb +381 -0
  125. data/lib/synthra/export/sql_insert.rb +152 -0
  126. data/lib/synthra/export/terraform.rb +501 -0
  127. data/lib/synthra/export/type_mapping.rb +389 -0
  128. data/lib/synthra/export/typescript.rb +300 -0
  129. data/lib/synthra/export/xml_data.rb +104 -0
  130. data/lib/synthra/export/yaml_data.rb +56 -0
  131. data/lib/synthra/export.rb +404 -0
  132. data/lib/synthra/factory_bot_integration.rb +157 -0
  133. data/lib/synthra/field.rb +440 -0
  134. data/lib/synthra/functions/registry.rb +104 -0
  135. data/lib/synthra/generator/context.rb +372 -0
  136. data/lib/synthra/generator/engine.rb +336 -0
  137. data/lib/synthra/generator/faker_adapter.rb +425 -0
  138. data/lib/synthra/generator/modes.rb +444 -0
  139. data/lib/synthra/generator/resolver.rb +256 -0
  140. data/lib/synthra/generator/rng.rb +273 -0
  141. data/lib/synthra/generator/streamer.rb +63 -0
  142. data/lib/synthra/generator/uniqueness.rb +118 -0
  143. data/lib/synthra/initializer.rb +97 -0
  144. data/lib/synthra/limits.rb +279 -0
  145. data/lib/synthra/live_preview.rb +518 -0
  146. data/lib/synthra/loader_config.rb +78 -0
  147. data/lib/synthra/lsp/server.rb +689 -0
  148. data/lib/synthra/migration_generator.rb +301 -0
  149. data/lib/synthra/mixin.rb +174 -0
  150. data/lib/synthra/mock_server.rb +458 -0
  151. data/lib/synthra/native_engine.rb +304 -0
  152. data/lib/synthra/openapi_importer.rb +228 -0
  153. data/lib/synthra/output/json_formatter.rb +40 -0
  154. data/lib/synthra/output/ndjson_formatter.rb +55 -0
  155. data/lib/synthra/parser/ast.rb +1287 -0
  156. data/lib/synthra/parser/lexer.rb +1152 -0
  157. data/lib/synthra/parser/parser.rb +1664 -0
  158. data/lib/synthra/parser/tokens.rb +461 -0
  159. data/lib/synthra/performance_mode.rb +364 -0
  160. data/lib/synthra/personas.rb +397 -0
  161. data/lib/synthra/property_testing.rb +172 -0
  162. data/lib/synthra/quality_metrics.rb +405 -0
  163. data/lib/synthra/rails_test_helper.rb +248 -0
  164. data/lib/synthra/registry.rb +533 -0
  165. data/lib/synthra/relationships.rb +193 -0
  166. data/lib/synthra/repl/enhanced_repl.rb +607 -0
  167. data/lib/synthra/repl/formatter.rb +191 -0
  168. data/lib/synthra/scenarios.rb +423 -0
  169. data/lib/synthra/schema.rb +605 -0
  170. data/lib/synthra/schema_inheritance.rb +104 -0
  171. data/lib/synthra/schema_versioning.rb +212 -0
  172. data/lib/synthra/snapshot_testing.rb +199 -0
  173. data/lib/synthra/time_travel.rb +338 -0
  174. data/lib/synthra/type_definitions.rb +274 -0
  175. data/lib/synthra/types/address_location/addresses.rb +125 -0
  176. data/lib/synthra/types/address_location/airports.rb +200 -0
  177. data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
  178. data/lib/synthra/types/address_location/locations.rb +406 -0
  179. data/lib/synthra/types/base.rb +48 -0
  180. data/lib/synthra/types/commerce_products/commerce.rb +119 -0
  181. data/lib/synthra/types/commerce_products/companies.rb +71 -0
  182. data/lib/synthra/types/commerce_products/construction.rb +126 -0
  183. data/lib/synthra/types/commerce_products/products.rb +205 -0
  184. data/lib/synthra/types/core/collections.rb +513 -0
  185. data/lib/synthra/types/core/defaults.rb +76 -0
  186. data/lib/synthra/types/core/enums.rb +102 -0
  187. data/lib/synthra/types/core/identifiers.rb +445 -0
  188. data/lib/synthra/types/core/primitives.rb +586 -0
  189. data/lib/synthra/types/core/references.rb +466 -0
  190. data/lib/synthra/types/core/sequences.rb +304 -0
  191. data/lib/synthra/types/crypto/crypto.rb +162 -0
  192. data/lib/synthra/types/date_time/dates.rb +387 -0
  193. data/lib/synthra/types/finance_banking/banking.rb +424 -0
  194. data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
  195. data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
  196. data/lib/synthra/types/formula.rb +184 -0
  197. data/lib/synthra/types/health_medical/medical.rb +150 -0
  198. data/lib/synthra/types/hostile_payloads.rb +122 -0
  199. data/lib/synthra/types/json_array.rb +51 -0
  200. data/lib/synthra/types/media_entertainment/media.rb +62 -0
  201. data/lib/synthra/types/naughty_string.rb +30 -0
  202. data/lib/synthra/types/personal_names/chinese.rb +41 -0
  203. data/lib/synthra/types/personal_names/identifiers.rb +187 -0
  204. data/lib/synthra/types/personal_names/names.rb +246 -0
  205. data/lib/synthra/types/personal_names/national_id.rb +89 -0
  206. data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
  207. data/lib/synthra/types/regex.rb +248 -0
  208. data/lib/synthra/types/registry.rb +182 -0
  209. data/lib/synthra/types/repeating_element.rb +50 -0
  210. data/lib/synthra/types/scenario.rb +29 -0
  211. data/lib/synthra/types/technology_internet/apps.rb +67 -0
  212. data/lib/synthra/types/technology_internet/communication.rb +162 -0
  213. data/lib/synthra/types/technology_internet/devices.rb +80 -0
  214. data/lib/synthra/types/technology_internet/formats.rb +139 -0
  215. data/lib/synthra/types/technology_internet/networking.rb +143 -0
  216. data/lib/synthra/types/template.rb +92 -0
  217. data/lib/synthra/types/text_content/business.rb +128 -0
  218. data/lib/synthra/types/text_content/colors.rb +70 -0
  219. data/lib/synthra/types/text_content/misc.rb +237 -0
  220. data/lib/synthra/types/text_content/security.rb +130 -0
  221. data/lib/synthra/types/text_content/text_generation.rb +532 -0
  222. data/lib/synthra/types/travel/travel.rb +150 -0
  223. data/lib/synthra/utils/string_distance.rb +133 -0
  224. data/lib/synthra/validator/dsl_validator.rb +339 -0
  225. data/lib/synthra/validator/path_validator.rb +615 -0
  226. data/lib/synthra/version.rb +35 -0
  227. data/lib/synthra/webhook_simulator.rb +341 -0
  228. data/lib/synthra.rb +259 -0
  229. data/schemas/address.dsl +16 -0
  230. data/schemas/api_response.dsl +8 -0
  231. data/schemas/error_payload.dsl +9 -0
  232. data/schemas/order.dsl +10 -0
  233. data/schemas/order_item.dsl +8 -0
  234. data/schemas/payment.dsl +11 -0
  235. data/schemas/social_post.dsl +13 -0
  236. data/schemas/user.dsl +10 -0
  237. data/scripts/batch_fix_all.rb +96 -0
  238. data/scripts/delete_old_files.rb +32 -0
  239. data/scripts/fix_all_grouped_files.rb +137 -0
  240. data/scripts/fix_all_indentation.rb +48 -0
  241. data/scripts/fix_all_syntax.rb +124 -0
  242. data/scripts/fix_grouped_files.rb +184 -0
  243. data/scripts/fix_syntax_errors.rb +108 -0
  244. data/scripts/group_domain_types.rb +112 -0
  245. data/scripts/group_domain_types_fixed.rb +150 -0
  246. data/scripts/merge_domains_to_one_file.rb +68 -0
  247. data/scripts/move_existing_types.rb +130 -0
  248. data/scripts/split_grouped_types.rb +142 -0
  249. data/tech_docs/README.md +134 -0
  250. data/tech_docs/advanced/streaming.md +405 -0
  251. data/tech_docs/advanced/thread_safety.md +253 -0
  252. data/tech_docs/api/overview.md +485 -0
  253. data/tech_docs/appendices/type_chart.md +193 -0
  254. data/tech_docs/basic_concepts.md +383 -0
  255. data/tech_docs/behaviors/overview.md +346 -0
  256. data/tech_docs/dsl/complex_types.md +790 -0
  257. data/tech_docs/dsl/core_types.md +464 -0
  258. data/tech_docs/dsl/datetime_types.md +325 -0
  259. data/tech_docs/dsl/field_modifiers.md +414 -0
  260. data/tech_docs/dsl/grammar.md +431 -0
  261. data/tech_docs/dsl/schema_definition.md +399 -0
  262. data/tech_docs/export/README.md +276 -0
  263. data/tech_docs/installation.md +273 -0
  264. data/tech_docs/integration/ci_cd.md +707 -0
  265. data/tech_docs/integration/ci_cd_guide.md +579 -0
  266. data/tech_docs/integration/factory_bot.md +485 -0
  267. data/tech_docs/integration/rails.md +630 -0
  268. data/tech_docs/integration/rspec.md +449 -0
  269. data/tech_docs/modes/overview.md +350 -0
  270. data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
  271. data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
  272. data/tech_docs/quick_start.md +256 -0
  273. data/tech_docs/templates/README.md +805 -0
  274. data/tech_docs/tutorials/advanced.md +371 -0
  275. data/tech_docs/tutorials/getting_started.md +189 -0
  276. data/tech_docs/tutorials/intermediate.md +231 -0
  277. data/tech_docs/tutorials/template_gallery.md +569 -0
  278. data/tech_example/01_basic_usage.rb +238 -0
  279. data/tech_example/02_types_demo.rb +336 -0
  280. data/tech_example/04_cli_usage.md +429 -0
  281. data/tech_example/05_database_seeding.rb +283 -0
  282. data/tech_example/07_rspec_integration.rb +359 -0
  283. data/tech_example/10_custom_types.rb +387 -0
  284. data/tech_example/12_behaviors.rb +372 -0
  285. data/tech_example/13_twitter_dm_example.rb +120 -0
  286. data/tech_example/14_exact_json_structure.rb +253 -0
  287. data/tech_example/16_lsp_server.rb +56 -0
  288. data/tech_example/17_property_testing.rb +92 -0
  289. data/tech_example/18_enhanced_repl.rb +120 -0
  290. data/tech_example/19_security_fuzzing.rb +145 -0
  291. data/tech_example/NEW_FEATURES.md +169 -0
  292. data/tech_example/README.md +118 -0
  293. data/tech_example/schemas/api_response.dsl +123 -0
  294. data/tech_example/schemas/ecommerce.dsl +121 -0
  295. data/tech_example/schemas/twitter_dm.dsl +109 -0
  296. data/tech_example/schemas/user.dsl +36 -0
  297. data/vscode-extension/README.md +246 -0
  298. data/vscode-extension/language-configuration.json +31 -0
  299. data/vscode-extension/package.json +55 -0
  300. data/vscode-extension/snippets/fakedatadsl.json +198 -0
  301. data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
  302. 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
+ }