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,466 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ module Core
6
+ # Copy type generator - copies value from another field in current or parent schema
7
+ #
8
+ # Allows referencing fields that were already generated in the same or parent schema.
9
+ # Useful for linking IDs across nested objects.
10
+ #
11
+ # @example Copy a nested field value
12
+ # sender_id: copy("users.sender.id")
13
+ #
14
+ class Copy < Base
15
+ # Generate by copying from another field
16
+ #
17
+ # @param rng [Generator::RNG] random number generator (not used)
18
+ # @param context [Generator::Context] generation context with field values
19
+ # @param args [Hash] type arguments
20
+ # @option args [String] :path dot-separated path to field (e.g., "users.sender.id")
21
+ # @return [Object, nil] copied value
22
+ #
23
+ def generate(rng, context, args, mode)
24
+ path = args[:path] || args["path"] || args.values.first
25
+ return nil unless path && context
26
+
27
+ # Try current context first
28
+ result = context.get(path.to_s)
29
+ return result if result
30
+
31
+ # Walk up parent contexts to find the value
32
+ parent = context.parent_context
33
+ while parent && result.nil?
34
+ result = parent.get(path.to_s)
35
+ parent = parent.parent_context
36
+ end
37
+
38
+ result
39
+ end
40
+
41
+ def generate_random(rng, context, args)
42
+ generate(rng, context, args, :random)
43
+ end
44
+
45
+ def generate_edge(rng, context, args)
46
+ generate(rng, context, args, :edge)
47
+ end
48
+
49
+ def generate_invalid(rng, context, args)
50
+ nil
51
+ end
52
+ end
53
+
54
+ # Reference type generator (Ref(Schema.field))
55
+ #
56
+ # Generates values by resolving references to fields in other schemas.
57
+ # Uses the Resolver to look up previously generated values or generate
58
+ # new ones on-demand.
59
+ #
60
+ # @example Generate reference value
61
+ # ref_type = Reference.new
62
+ # ref_type.generate(rng, context, { ref: RefNode.new("User", "id") }, :random)
63
+ #
64
+ class Reference < Base
65
+ # Generate a reference value
66
+ #
67
+ # @param rng [Generator::RNG] random number generator
68
+ # @param context [Generator::Context] generation context
69
+ # @param args [Hash] type arguments
70
+ # @option args [ReferenceNode, Hash] :ref reference node or hash with :schema and :field
71
+ # @return [Object, nil] resolved reference value
72
+ #
73
+ def generate(rng, context, args, mode)
74
+ ref = args[:ref] || args["ref"]
75
+ return nil unless ref
76
+
77
+ # Extract schema name and field path
78
+ if ref.respond_to?(:schema_name) && ref.respond_to?(:field_path)
79
+ schema_name = ref.schema_name
80
+ field_path = ref.field_path
81
+ elsif ref.is_a?(Hash)
82
+ schema_name = ref[:schema] || ref["schema"]
83
+ field_path = ref[:field] || ref["field"]
84
+ else
85
+ return nil
86
+ end
87
+
88
+ return nil unless schema_name && field_path
89
+
90
+ # Get resolver from context
91
+ resolver = context&.resolver
92
+ return nil unless resolver
93
+
94
+ # :nocov: context is non-nil here (the `return nil unless resolver` guard above
95
+ # exits when context — and thus resolver — is nil), so this `&.` else is unreachable.
96
+ depth = context&.depth || 0
97
+ # :nocov:
98
+ # Derive a deterministic seed from the (seeded) generation RNG so the referenced record —
99
+ # and thus this FK value — is reproducible by seed, mirroring how Shared seeds its
100
+ # sub-generation. Without it the resolver generated the referenced schema with a random
101
+ # seed, so ref(User.id) varied across runs with the same parent seed.
102
+ resolver.resolve(schema_name, field_path, depth: depth, seed: rng.int(0, 2_147_483_647))
103
+
104
+ rescue StandardError
105
+ nil
106
+ end
107
+
108
+
109
+ # Generate a random reference value
110
+ #
111
+ # @param rng [Generator::RNG] random number generator
112
+ # @param context [Generator::Context] generation context
113
+ # @param args [Hash] type arguments
114
+ # @return [Object, nil] resolved reference value
115
+ #
116
+ def generate_random(rng, context, args)
117
+ generate(rng, context, args, :random)
118
+ end
119
+
120
+ # Generate edge case reference values
121
+ #
122
+ # @param rng [Generator::RNG] random number generator
123
+ # @param context [Generator::Context] generation context
124
+ # @param args [Hash] type arguments
125
+ # @return [Object, nil] edge case value
126
+ #
127
+ def generate_edge(rng, context, args)
128
+ nil
129
+ end
130
+
131
+ # Generate invalid reference values
132
+ #
133
+ # @param rng [Generator::RNG] random number generator
134
+ # @param context [Generator::Context] generation context
135
+ # @param args [Hash] type arguments
136
+ # @return [Object] invalid reference value
137
+ #
138
+ def generate_invalid(rng, context, args)
139
+ rng.sample([
140
+ nil,
141
+ "invalid-ref",
142
+ 123,
143
+ []
144
+ ])
145
+ end
146
+ end
147
+
148
+ # Shared type generator - generates once and reuses across batch
149
+ #
150
+ # When used with generate_many, generates the wrapped type once per batch
151
+ # and reuses the same value for all records. Useful for scenarios like
152
+ # "all DMs go to the same recipient" or "all orders from same customer".
153
+ #
154
+ # @example All records share the same recipient
155
+ # users: map_by_field("id", sender: TwitterUser, recipient: shared(TwitterUser))
156
+ #
157
+ class Shared < Base
158
+ # Track whether we've already warned about misuse (to avoid spam)
159
+ @warned_about_misuse = false
160
+
161
+ class << self
162
+ attr_accessor :warned_about_misuse
163
+ end
164
+
165
+ # Generate a shared value (cached per batch)
166
+ #
167
+ # @param rng [Generator::RNG] random number generator
168
+ # @param context [Generator::Context] generation context with shared_context
169
+ # @param args [Hash] type arguments
170
+ # @option args [String] :type_name the wrapped type to generate
171
+ # @option args [Hash] :type_args arguments for the wrapped type
172
+ # @return [Object] generated or cached value
173
+ #
174
+ def generate(rng, context, args, mode)
175
+ type_name = args[:type_name] || args["type_name"]
176
+ type_args = args[:type_args] || args["type_args"] || {}
177
+ shared_key = args[:shared_key] || args["shared_key"] || "#{type_name}_#{type_args.hash}"
178
+
179
+ # Get or create shared_context from context
180
+ shared_context = context&.shared_context
181
+
182
+ # Warn if shared_context was not initialized by generate_many
183
+ if shared_context.nil? && should_warn_about_misuse?
184
+ emit_misuse_warning(type_name)
185
+ shared_context = {}
186
+ end
187
+
188
+ shared_context ||= {}
189
+
190
+ # Return cached value if exists
191
+ return shared_context[shared_key] if shared_context.key?(shared_key)
192
+
193
+ # Generate new value
194
+ registry = context&.registry
195
+
196
+ value = if registry&.schema?(type_name)
197
+ # It's a schema reference
198
+ schema = registry.schema(type_name)
199
+ new_seed = rng.int(0, 2_147_483_647)
200
+ schema.generate(seed: new_seed, registry: registry, mode: mode, parent_context: context)
201
+ else
202
+ # It's a regular type
203
+ type_gen = Types::Registry.lookup(type_name)
204
+ type_gen.generate(rng, context, type_args, mode)
205
+ end
206
+
207
+ # Cache the value
208
+ shared_context[shared_key] = value
209
+ context.shared_context = shared_context if context
210
+
211
+ value
212
+ end
213
+
214
+ def generate_random(rng, context, args)
215
+ generate(rng, context, args, :random)
216
+ end
217
+
218
+ def generate_edge(rng, context, args)
219
+ generate(rng, context, args, :edge)
220
+ end
221
+
222
+ def generate_invalid(rng, context, args)
223
+ nil
224
+ end
225
+
226
+ private
227
+
228
+ # Check if we should warn about shared() misuse
229
+ #
230
+ # @return [Boolean] true if warning should be emitted
231
+ #
232
+ def should_warn_about_misuse?
233
+ return false if self.class.warned_about_misuse
234
+
235
+ Synthra.configuration.warn_on_shared_misuse
236
+ end
237
+
238
+ # Emit warning about shared() being used outside generate_many()
239
+ #
240
+ # @param type_name [String] the type name being shared
241
+ # @return [void]
242
+ #
243
+ def emit_misuse_warning(type_name)
244
+ self.class.warned_about_misuse = true
245
+
246
+ message = "[Synthra] Warning: shared(#{type_name}) used outside generate_many() - " \
247
+ "value will not be shared across calls. " \
248
+ "Use generate_many() for batch generation with shared values."
249
+
250
+ logger = Synthra.configuration.logger
251
+ if logger
252
+ logger.warn(message)
253
+ else
254
+ warn message
255
+ end
256
+ end
257
+ end
258
+
259
+ # Schema reference type generator (schema(:Name))
260
+ #
261
+ # Generates values by instantiating another schema. This is the explicit
262
+ # way to reference schemas in the new Simulyra DSL syntax.
263
+ #
264
+ # @example Generate schema reference
265
+ # schema_ref = SchemaRef.new
266
+ # schema_ref.generate(rng, context, { schema_name: "User", array: false }, :random)
267
+ #
268
+ # @example Generate array of schemas
269
+ # schema_ref.generate(rng, context, { schema_name: "User", array: true, array_range: 1..10 }, :random)
270
+ #
271
+ class SchemaRef < Base
272
+ # Generate a schema reference value
273
+ #
274
+ # @param rng [Generator::RNG] random number generator
275
+ # @param context [Generator::Context] generation context
276
+ # @param args [Hash] type arguments
277
+ # @option args [String] :schema_name name of the schema to generate
278
+ # @option args [Boolean] :array whether to generate an array (default: false)
279
+ # @option args [Range, nil] :array_range size range for arrays
280
+ # @return [Object, Array<Object>, nil] generated schema instance(s)
281
+ #
282
+ def generate(rng, context, args, mode)
283
+ schema_name = args[:schema_name] || args["schema_name"]
284
+ return nil unless schema_name
285
+
286
+ registry = context&.registry
287
+ return nil unless registry
288
+
289
+ # Check if schema exists
290
+ unless registry.schema?(schema_name)
291
+ return nil
292
+ end
293
+
294
+ schema = registry.schema(schema_name)
295
+ is_array = args[:array] || args["array"] || false
296
+ array_range = args[:array_range] || args["array_range"]
297
+
298
+ if is_array
299
+ generate_array(rng, context, schema, registry, mode, array_range)
300
+ else
301
+ generate_single(rng, context, schema, registry, mode)
302
+ end
303
+ end
304
+
305
+ def generate_random(rng, context, args)
306
+ generate(rng, context, args, :random)
307
+ end
308
+
309
+ def generate_edge(rng, context, args)
310
+ generate(rng, context, args, :edge)
311
+ end
312
+
313
+ def generate_invalid(rng, context, args)
314
+ rng.sample([
315
+ nil,
316
+ "invalid-schema-ref",
317
+ 123,
318
+ []
319
+ ])
320
+ end
321
+
322
+ private
323
+
324
+ # Generate a single schema instance
325
+ #
326
+ # @param rng [Generator::RNG] random number generator
327
+ # @param context [Generator::Context] generation context
328
+ # @param schema [Schema] the schema to generate
329
+ # @param registry [Registry] schema registry
330
+ # @param mode [Symbol] generation mode
331
+ # @return [Object] generated schema instance
332
+ #
333
+ def generate_single(rng, context, schema, registry, mode)
334
+ new_seed = rng.int(0, 2_147_483_647)
335
+ # :nocov: every caller reaches here only past `return nil unless registry`
336
+ # (registry derives from context), so context is non-nil — `&.` else unreachable.
337
+ depth = (context&.depth || 0) + 1
338
+ # :nocov:
339
+
340
+ # Prevent infinite recursion
341
+ # :nocov: Synthra.configuration and #limits are memoized (`||= …`) and never nil,
342
+ # so the `&.` nil-receiver else-arms on this chain are unreachable.
343
+ max_depth = Synthra.configuration&.limits&.max_recursion || 10
344
+ # :nocov:
345
+ return nil if depth > max_depth
346
+
347
+ schema.generate(
348
+ seed: new_seed,
349
+ registry: registry,
350
+ mode: mode,
351
+ parent_context: context,
352
+ depth: depth
353
+ )
354
+ end
355
+
356
+ # Generate an array of schema instances
357
+ #
358
+ # @param rng [Generator::RNG] random number generator
359
+ # @param context [Generator::Context] generation context
360
+ # @param schema [Schema] the schema to generate
361
+ # @param registry [Registry] schema registry
362
+ # @param mode [Symbol] generation mode
363
+ # @param size_range [Range, nil] size range for the array
364
+ # @return [Array<Object>] array of generated schema instances
365
+ #
366
+ def generate_array(rng, context, schema, registry, mode, size_range)
367
+ # Determine array size
368
+ size = if size_range.is_a?(Range)
369
+ rng.int(size_range.min, size_range.max)
370
+ elsif size_range.is_a?(Integer)
371
+ size_range
372
+ elsif size_range.is_a?(Hash)
373
+ min = size_range[:min] || 0
374
+ max = size_range[:max] || 10
375
+ rng.int(min, max)
376
+ else
377
+ rng.int(1, 10) # Default size
378
+ end
379
+
380
+ # Enforce array size limits
381
+ # :nocov: Synthra.configuration and #limits are memoized (`||= …`) and never nil,
382
+ # so the `&.` nil-receiver else-arms on this chain are unreachable.
383
+ max_size = Synthra.configuration&.limits&.max_array_size || 1000
384
+ # :nocov:
385
+ size = [size, max_size].min
386
+
387
+ Array.new(size) do
388
+ generate_single(rng, context, schema, registry, mode)
389
+ end.compact
390
+ end
391
+ end
392
+
393
+ # Custom type generator (calls registered functions)
394
+ #
395
+ # Generates values by invoking a registered function with the current
396
+ # generation context. Functions can access previously generated fields
397
+ # to compute derived values.
398
+ #
399
+ # @example Generate using custom function
400
+ # custom_type = Custom.new
401
+ # custom_type.generate(rng, context, { function: :full_name }, :random)
402
+ #
403
+ class Custom < Base
404
+ # Generate a value using a custom function
405
+ #
406
+ # @param rng [Generator::RNG] random number generator (not used)
407
+ # @param context [Generator::Context] generation context
408
+ # @param args [Hash] type arguments
409
+ # @option args [Symbol, String] :function function name to call
410
+ # @return [Object] function result
411
+ # @raise [MissingFunctionError] if function not found
412
+ #
413
+ def generate(rng, context, args, mode)
414
+ function_name = args[:function] || args["function"] || args.values.first
415
+ raise Errors::MissingFunctionError.new(function_name) unless function_name
416
+
417
+ Functions::Registry.call(function_name.to_sym, context)
418
+ end
419
+
420
+ # Generate a random value using a custom function
421
+ #
422
+ # @param rng [Generator::RNG] random number generator (not used)
423
+ # @param context [Generator::Context] generation context
424
+ # @param args [Hash] type arguments
425
+ # @return [Object] function result
426
+ # @raise [MissingFunctionError] if function not found
427
+ #
428
+ def generate_random(rng, context, args)
429
+ generate(rng, context, args, :random)
430
+ end
431
+
432
+ # Generate edge case custom values
433
+ #
434
+ # @param rng [Generator::RNG] random number generator
435
+ # @param context [Generator::Context] generation context
436
+ # @param args [Hash] type arguments
437
+ # @return [Object] edge case value
438
+ #
439
+ def generate_edge(rng, context, args)
440
+ generate(rng, context, args, :edge)
441
+
442
+ rescue StandardError
443
+ nil
444
+ end
445
+
446
+
447
+ # Generate invalid custom values
448
+ #
449
+ # @param rng [Generator::RNG] random number generator
450
+ # @param context [Generator::Context] generation context
451
+ # @param args [Hash] type arguments
452
+ # @return [Object] invalid value
453
+ #
454
+ def generate_invalid(rng, context, args)
455
+ rng.sample([
456
+ nil,
457
+ "invalid-custom",
458
+ 123,
459
+ []
460
+ ])
461
+ end
462
+ end
463
+ end
464
+ end
465
+ end
466
+