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,513 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ module Core
6
+ # Array type generator
7
+ #
8
+ # Generates arrays of elements using a specified type generator.
9
+ # Supports both positional and named argument syntax.
10
+ #
11
+ # @example Generate array of text
12
+ # array_type = ArrayType.new
13
+ # array_type.generate(rng, context, { element: "text", size: 1..5 }, :random)
14
+ #
15
+ class ArrayType < Base
16
+ # Generate a random array
17
+ #
18
+ # @param rng [Generator::RNG] random number generator
19
+ # @param context [Generator::Context] generation context
20
+ # @param args [Hash] type arguments
21
+ # @option args [String, Object] :element element type name or generator
22
+ # @option args [Range, Integer, Hash] :size size specification (default: 0..10)
23
+ # @return [Array] generated array
24
+ #
25
+ def generate(rng, context, args, mode)
26
+ element_type = resolve_type(args[:element] || args[:element_type] || "text")
27
+ size_range = extract_size_range(args)
28
+ size = rng.int(size_range.min, size_range.max)
29
+
30
+ size.times.map do
31
+ element_type.generate(rng, context, {}, mode)
32
+ end
33
+ end
34
+
35
+ # Generate a random array
36
+ #
37
+ # @param rng [Generator::RNG] random number generator
38
+ # @param context [Generator::Context] generation context
39
+ # @param args [Hash] type arguments
40
+ # @return [Array] generated array
41
+ #
42
+ def generate_random(rng, context, args)
43
+ generate(rng, context || Generator::Context.new, args, :random)
44
+ end
45
+
46
+ # Generate edge case array values
47
+ #
48
+ # @param rng [Generator::RNG] random number generator
49
+ # @param context [Generator::Context] generation context
50
+ # @param args [Hash] type arguments
51
+ # @return [Array, nil, Integer] edge case value
52
+ #
53
+ def generate_edge(rng, context, args)
54
+ rng.sample([
55
+ [],
56
+ [nil],
57
+ [""],
58
+ [0],
59
+ [1, 2, 3]
60
+ ])
61
+ end
62
+
63
+ # Generate invalid array values
64
+ #
65
+ # @param rng [Generator::RNG] random number generator
66
+ # @param context [Generator::Context] generation context
67
+ # @param args [Hash] type arguments
68
+ # @return [Object] invalid array value
69
+ #
70
+ def generate_invalid(rng, context, args)
71
+ rng.sample([
72
+ nil,
73
+ "not an array",
74
+ 123,
75
+ {}
76
+ ])
77
+ end
78
+
79
+ private
80
+
81
+ # Resolve element type to a type generator
82
+ #
83
+ # Looks up the type generator for the element type. If not found
84
+ # in the registry, assumes it's a schema reference and creates
85
+ # an ObjectType generator.
86
+ #
87
+ # @param element_type [String, Object] element type name or generator
88
+ # @return [Base] type generator instance
89
+ #
90
+ def resolve_type(element_type)
91
+ case element_type
92
+ when String
93
+ begin
94
+ Types::Registry.lookup(element_type)
95
+ rescue Errors::UnknownTypeError
96
+ # Assume it's a schema reference
97
+ Core::ObjectType.new(element_type)
98
+ end
99
+ when Base
100
+ element_type
101
+ else
102
+ # Default to Text if element_type is not a string or Base instance
103
+ Types::Registry.lookup("text")
104
+ end
105
+ end
106
+
107
+ # Extract size range from arguments
108
+ #
109
+ # Converts various size specifications into a Range object.
110
+ #
111
+ # @param args [Hash] type arguments
112
+ # @return [Range] size range
113
+ #
114
+ def extract_size_range(args)
115
+ case args[:size]
116
+ when Range
117
+ args[:size]
118
+ when Integer
119
+ args[:size]..args[:size]
120
+ when Hash
121
+ min = args[:size][:min] || args[:size]["min"] || 0
122
+ max = args[:size][:max] || args[:size]["max"] || 10
123
+ min..max
124
+ else
125
+ 0..10 # Default range
126
+ end
127
+ end
128
+ end
129
+
130
+ # Object type generator (nested schema reference)
131
+ #
132
+ # Generates a complete object by referencing another schema definition.
133
+ # The referenced schema is looked up in the registry and generated
134
+ # recursively.
135
+ #
136
+ # @example Generate nested object
137
+ # object_type = ObjectType.new("Address")
138
+ # object_type.generate(rng, context, {}, :random)
139
+ #
140
+ class ObjectType < Base
141
+ attr_reader :schema_name
142
+
143
+ # Create a new ObjectType generator
144
+ #
145
+ # @param schema_name [String, nil] name of the schema to reference
146
+ #
147
+ def initialize(schema_name = nil)
148
+ @schema_name = schema_name
149
+ end
150
+
151
+ # Generate a nested schema object
152
+ #
153
+ # Looks up the referenced schema in the registry and generates
154
+ # a complete object from it.
155
+ #
156
+ # @param rng [Generator::RNG] random number generator
157
+ # @param context [Generator::Context] generation context
158
+ # @param args [Hash] type arguments
159
+ # @option args [String] :schema schema name (if not set in initialize)
160
+ # @return [Hash] generated object
161
+ #
162
+ def generate(rng, context, args, mode)
163
+ case mode
164
+ when :invalid
165
+ return generate_invalid(rng, context, args)
166
+ when :edge
167
+ return generate_edge(rng, context, args)
168
+ end
169
+
170
+ schema_name = @schema_name || args[:schema] || args["schema"]
171
+ return {} unless schema_name
172
+
173
+ registry = context&.registry
174
+ return {} unless registry
175
+
176
+ schema = registry.schema(schema_name)
177
+ return {} unless schema
178
+
179
+ # Get depth and resolver from context for proper recursion handling
180
+ # :nocov: context is guaranteed non-nil here (the `return {} unless registry`
181
+ # guard above already exits when context — and thus registry — is nil), so the
182
+ # `&.` nil-receiver else-arms below are unreachable.
183
+ depth = context&.depth || 0
184
+ resolver = context&.resolver
185
+ # :nocov:
186
+
187
+ # Generate new seed from current rng for unique nested values
188
+ # This ensures each nested schema generates different data
189
+ nested_seed = rng.int(0, 2**31 - 1)
190
+
191
+ # Pass current context as parent_context so nested schemas can access parent fields via copy()
192
+ schema.generate(seed: nested_seed, registry: registry, mode: mode, depth: depth + 1, resolver: resolver, parent_context: context)
193
+
194
+ rescue StandardError
195
+ {}
196
+ end
197
+
198
+
199
+ # Generate a random nested object
200
+ #
201
+ # @param rng [Generator::RNG] random number generator
202
+ # @param context [Generator::Context] generation context
203
+ # @param args [Hash] type arguments
204
+ # @return [Hash] generated object
205
+ #
206
+ def generate_random(rng, context, args)
207
+ generate(rng, context, args, :random)
208
+ end
209
+
210
+ # Generate edge case object values
211
+ #
212
+ # @param rng [Generator::RNG] random number generator
213
+ # @param context [Generator::Context] generation context
214
+ # @param args [Hash] type arguments
215
+ # @return [Hash] edge case value
216
+ #
217
+ def generate_edge(rng, context, args)
218
+ {}
219
+ end
220
+
221
+ # Generate invalid object values
222
+ #
223
+ # @param rng [Generator::RNG] random number generator
224
+ # @param context [Generator::Context] generation context
225
+ # @param args [Hash] type arguments
226
+ # @return [Object] invalid object value
227
+ #
228
+ def generate_invalid(rng, context, args)
229
+ rng.sample([
230
+ nil,
231
+ [],
232
+ "not an object",
233
+ 123
234
+ ])
235
+ end
236
+ end
237
+
238
+ # Special hash for map_by_field that supports both ID keys and entry name access
239
+ # This allows copy("users.sender.id") to work while JSON serializes only ID keys
240
+ class MapByFieldResult < Hash
241
+ def initialize(id_keyed_data, entries_by_name)
242
+ super()
243
+ merge!(id_keyed_data)
244
+ @entries_by_name = entries_by_name
245
+ end
246
+
247
+ def [](key)
248
+ # First check entry names (for copy() access)
249
+ return @entries_by_name[key] if @entries_by_name&.key?(key)
250
+ return @entries_by_name[key.to_s] if @entries_by_name&.key?(key.to_s)
251
+ super
252
+ end
253
+
254
+ # JSON serialization only includes the ID-keyed data
255
+ def to_json(*args)
256
+ Hash[self].to_json(*args)
257
+ end
258
+
259
+ def as_json(*args)
260
+ Hash[self]
261
+ end
262
+ end
263
+
264
+ # MapByField type - creates objects with dynamic keys from a field value
265
+ #
266
+ # Generates multiple objects and uses a specified field as the key.
267
+ # Perfect for Twitter-style user maps where ID is the key.
268
+ #
269
+ # @example DSL usage
270
+ # users: map_by_field("id", sender: TwitterUser, recipient: TwitterUser)
271
+ # # Generates: { "123": { id: "123", ... }, "456": { id: "456", ... } }
272
+ #
273
+ class MapByField < Base
274
+ # Generate a map with dynamic keys
275
+ #
276
+ # @param rng [Generator::RNG] random number generator
277
+ # @param context [Generator::Context] generation context
278
+ # @param args [Hash] type arguments with :key_field and schema entries
279
+ # @return [Hash] map with field values as keys
280
+ #
281
+ def generate(rng, context, args, mode)
282
+ case mode
283
+ when :invalid
284
+ return generate_invalid(rng, context, args)
285
+ when :edge
286
+ return generate_edge(rng, context, args)
287
+ end
288
+
289
+ key_field = args[:key_field] || args["key_field"] || "id"
290
+ entries = args[:entries] || args["entries"] || {}
291
+
292
+ registry = context&.registry
293
+ return {} unless registry
294
+
295
+ result = {}
296
+ entries_by_name = {} # For copy() access via users.sender.id
297
+ # :nocov: context is guaranteed non-nil here (the `return {} unless registry`
298
+ # guard above exits when context — and thus registry — is nil), so the `&.`
299
+ # nil-receiver else-arms on these three reads are unreachable.
300
+ depth = context&.depth || 0
301
+ resolver = context&.resolver
302
+
303
+ # Get shared context for batch-level caching
304
+ shared_context = context&.shared_context || {}
305
+ # :nocov:
306
+
307
+ entries.each do |entry_name, entry_config|
308
+ # Handle both simple schema name and { schema:, shared: } hash
309
+ is_shared = false
310
+ if entry_config.is_a?(Hash)
311
+ schema_name = entry_config[:schema] || entry_config["schema"]
312
+ is_shared = entry_config[:shared] || entry_config["shared"]
313
+ else
314
+ schema_name = entry_config.to_s
315
+ end
316
+
317
+ schema = registry.schema(schema_name) rescue nil
318
+ next unless schema
319
+
320
+ # Check if this entry should be shared across batch
321
+ shared_key = "map_by_field_#{entry_name}"
322
+ if is_shared && shared_context.key?(shared_key)
323
+ # Use cached value from shared context
324
+ obj = shared_context[shared_key]
325
+ else
326
+ # Generate new value
327
+ nested_seed = rng.int(0, 2**31 - 1)
328
+ obj = schema.generate(
329
+ seed: nested_seed,
330
+ registry: registry,
331
+ mode: mode,
332
+ depth: depth + 1,
333
+ resolver: resolver,
334
+ parent_context: context
335
+ )
336
+
337
+ # Cache in shared context if marked as shared
338
+ if is_shared
339
+ shared_context[shared_key] = obj
340
+ # :nocov: context is non-nil here (guarded by `return {} unless registry`
341
+ # earlier, where registry derives from context), so `if context` is always true.
342
+ context.shared_context = shared_context if context
343
+ # :nocov:
344
+ end
345
+ end
346
+
347
+ # Store by entry name for copy() to access
348
+ entries_by_name[entry_name.to_s] = obj
349
+
350
+ # Use the specified field as the key for the result
351
+ key = obj[key_field] || obj[key_field.to_sym]
352
+ result[key] = obj if key
353
+ end
354
+
355
+ # Create a special hash that has both ID keys and entry name access
356
+ MapByFieldResult.new(result, entries_by_name)
357
+ end
358
+
359
+ def generate_random(rng, context, args)
360
+ generate(rng, context, args, :random)
361
+ end
362
+
363
+ def generate_edge(rng, context, args)
364
+ {}
365
+ end
366
+
367
+ def generate_invalid(rng, context, args)
368
+ rng.sample([nil, [], "not a map", 123])
369
+ end
370
+ end
371
+
372
+ # OneOf type - randomly selects one of multiple schemas
373
+ #
374
+ # Generates data from one randomly chosen schema from a list.
375
+ # Perfect for polymorphic types like message payloads, events, etc.
376
+ #
377
+ # @example DSL usage
378
+ # payload: one_of(MessageCreatePayload, TypingPayload, ReadReceiptPayload)
379
+ # # Randomly generates one of the three payload types
380
+ #
381
+ # @example With weights
382
+ # payload: one_of(CommonPayload:80%, RarePayload:20%)
383
+ # # 80% chance of CommonPayload, 20% chance of RarePayload
384
+ #
385
+ class OneOf < Base
386
+ # Generate data from one of the specified schemas
387
+ #
388
+ # @param rng [Generator::RNG] random number generator
389
+ # @param context [Generator::Context] generation context
390
+ # @param args [Hash] type arguments with :schemas array
391
+ # @return [Hash] generated data from the selected schema
392
+ #
393
+ def generate(rng, context, args, mode)
394
+ case mode
395
+ when :invalid
396
+ return generate_invalid(rng, context, args)
397
+ when :edge
398
+ return generate_edge(rng, context, args)
399
+ end
400
+
401
+ schemas = args[:schemas] || args["schemas"] || []
402
+ return nil if schemas.empty?
403
+
404
+ registry = context&.registry
405
+ return nil unless registry
406
+
407
+ # Select a schema based on weights (if provided)
408
+ selected = select_schema(rng, schemas)
409
+ return nil unless selected
410
+
411
+ # Get schema name (could be string or hash with weight)
412
+ schema_name = selected.is_a?(Hash) ? (selected[:schema] || selected["schema"]) : selected.to_s
413
+
414
+ schema = registry.schema(schema_name) rescue nil
415
+ return nil unless schema
416
+
417
+ # Generate from the selected schema
418
+ # :nocov: context is guaranteed non-nil here (the `return nil unless registry`
419
+ # guard above exits when context — and thus registry — is nil), so the `&.`
420
+ # nil-receiver else-arms on these three reads are unreachable.
421
+ depth = context&.depth || 0
422
+ resolver = context&.resolver
423
+ shared_context = context&.shared_context
424
+ # :nocov:
425
+
426
+ nested_seed = rng.int(0, 2**31 - 1)
427
+ schema.generate(
428
+ seed: nested_seed,
429
+ registry: registry,
430
+ mode: mode,
431
+ depth: depth + 1,
432
+ resolver: resolver,
433
+ parent_context: context,
434
+ shared_context: shared_context
435
+ )
436
+ end
437
+
438
+ def generate_random(rng, context, args)
439
+ generate(rng, context, args, :random)
440
+ end
441
+
442
+ def generate_edge(rng, context, args)
443
+ # In edge mode, return first schema's edge case
444
+ schemas = args[:schemas] || args["schemas"] || []
445
+ return nil if schemas.empty?
446
+
447
+ registry = context&.registry
448
+ return nil unless registry
449
+
450
+ first_schema = schemas.first
451
+ schema_name = first_schema.is_a?(Hash) ? (first_schema[:schema] || first_schema["schema"]) : first_schema.to_s
452
+
453
+ schema = registry.schema(schema_name) rescue nil
454
+ return {} unless schema
455
+
456
+ # :nocov: context is non-nil here (the `return nil unless registry` guard above
457
+ # exits when context — and thus registry — is nil), so this `&.` else is unreachable.
458
+ depth = context&.depth || 0
459
+ # :nocov:
460
+ nested_seed = rng.int(0, 2**31 - 1)
461
+ schema.generate(seed: nested_seed, registry: registry, mode: :edge, depth: depth + 1)
462
+ end
463
+
464
+ def generate_invalid(rng, context, args)
465
+ rng.sample([nil, "not-an-object", 123, [], {}])
466
+ end
467
+
468
+ private
469
+
470
+ # Select a schema based on weights
471
+ #
472
+ # @param rng [Generator::RNG] random number generator
473
+ # @param schemas [Array] list of schemas (strings or hashes with weights)
474
+ # @return [Object] selected schema entry
475
+ #
476
+ def select_schema(rng, schemas)
477
+ # Check if any schema has weights
478
+ has_weights = schemas.any? { |s| s.is_a?(Hash) && (s[:weight] || s["weight"]) }
479
+
480
+ if has_weights
481
+ # Weighted selection
482
+ total_weight = schemas.sum do |s|
483
+ if s.is_a?(Hash)
484
+ s[:weight] || s["weight"] || 1
485
+ else
486
+ 1
487
+ end
488
+ end
489
+
490
+ roll = rng.int(1, total_weight)
491
+ cumulative = 0
492
+
493
+ schemas.each do |s|
494
+ weight = if s.is_a?(Hash)
495
+ s[:weight] || s["weight"] || 1
496
+ else
497
+ 1
498
+ end
499
+ cumulative += weight
500
+ return s if roll <= cumulative
501
+ end
502
+
503
+ schemas.last # Fallback
504
+ else
505
+ # Equal probability
506
+ rng.sample(schemas)
507
+ end
508
+ end
509
+ end
510
+ end
511
+ end
512
+ end
513
+
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ module Core
6
+ # Default value type generator
7
+ #
8
+ # Returns a default value, optionally with a probability of being used.
9
+ # If probability is less than 1.0, returns nil some of the time.
10
+ #
11
+ # @example Generate default value
12
+ # default = DefaultValue.new
13
+ # default.generate_random(rng, context, { value: "Anonymous" }, :random)
14
+ # # => "Anonymous"
15
+ #
16
+ # @example Default value with probability
17
+ # default.generate_random(rng, context, { value: "Guest", probability: 0.8 }, :random)
18
+ # # => "Guest" (80% of the time) or nil (20% of the time)
19
+ #
20
+ class DefaultValue < Base
21
+ # Generate a default value
22
+ #
23
+ # @param rng [Generator::RNG] random number generator
24
+ # @param context [Generator::Context] generation context (not used)
25
+ # @param args [Hash] type arguments
26
+ # @option args [Object] :value default value to return
27
+ # @option args [Float] :probability probability of using default (0.0-1.0, default: 1.0)
28
+ # @return [Object, nil] default value or nil
29
+ #
30
+ def generate_random(rng, context, args)
31
+ value = args[:value] || args["value"]
32
+ probability = args[:probability] || args["probability"] || 1.0
33
+
34
+ # If probability is less than 1.0, randomly return nil
35
+ if probability < 1.0
36
+ rng.rand < probability ? value : nil
37
+ else
38
+ value
39
+ end
40
+ end
41
+
42
+ # Generate edge case default values
43
+ #
44
+ # @param rng [Generator::RNG] random number generator
45
+ # @param context [Generator::Context] generation context
46
+ # @param args [Hash] type arguments
47
+ # @return [Object, nil] edge case value
48
+ #
49
+ def generate_edge(rng, context, args)
50
+ value = args[:value] || args["value"]
51
+ rng.sample([value, nil])
52
+ end
53
+
54
+ # Generate invalid default values
55
+ #
56
+ # @param rng [Generator::RNG] random number generator
57
+ # @param context [Generator::Context] generation context
58
+ # @param args [Hash] type arguments
59
+ # @return [Object] invalid value
60
+ #
61
+ def generate_invalid(rng, context, args)
62
+ value = args[:value] || args["value"]
63
+ case value
64
+ when String
65
+ rng.sample([nil, 123, [], {}])
66
+ when Integer
67
+ rng.sample([nil, "not a number", [], {}])
68
+ else
69
+ nil
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ module Core
6
+ # Enum type generator
7
+ #
8
+ # Generates values from a predefined list, optionally with probability weights.
9
+ #
10
+ # @example Simple enum
11
+ # enum(active, inactive)
12
+ #
13
+ # @example With probabilities
14
+ # enum(active: 80%, inactive: 20%)
15
+ #
16
+ class Enum < Base
17
+ # Generate a random enum value
18
+ #
19
+ # @param rng [Generator::RNG] random number generator
20
+ # @param context [Generator::Context] generation context
21
+ # @param args [Hash] type arguments
22
+ # @option args [Array] :values array of enum values (with probabilities)
23
+ # @option args [Array<String>] :values simple string array
24
+ # @return [Object] selected enum value
25
+ #
26
+ def generate_random(rng, context, args)
27
+ values = args[:values] || args["values"] || []
28
+
29
+ if values.empty?
30
+ return nil
31
+ end
32
+
33
+ # Check if values are simple strings/objects or have probabilities
34
+ if values.first.respond_to?(:probability)
35
+ # Weighted selection
36
+ weighted_choice(rng, values)
37
+ else
38
+ # Simple random selection
39
+ rng.sample(values)
40
+ end
41
+ end
42
+
43
+ # Generate edge case enum value
44
+ #
45
+ # @param rng [Generator::RNG] random number generator
46
+ # @param context [Generator::Context] generation context
47
+ # @param args [Hash] type arguments
48
+ # @return [Object] edge case enum value
49
+ #
50
+ def generate_edge(rng, context, args)
51
+ values = args[:values] || args["values"] || []
52
+ return nil if values.empty?
53
+
54
+ # Return first or last value
55
+ if values.first.respond_to?(:value)
56
+ candidates = [values.first.value, values.last.value].compact
57
+ else
58
+ candidates = [values.first, values.last].compact
59
+ end
60
+
61
+ rng.sample(candidates)
62
+ end
63
+
64
+ # Generate invalid enum value
65
+ #
66
+ # @param rng [Generator::RNG] random number generator
67
+ # @param context [Generator::Context] generation context
68
+ # @param args [Hash] type arguments
69
+ # @return [Object] invalid value
70
+ #
71
+ def generate_invalid(rng, context, args)
72
+ rng.sample([nil, "invalid_enum", [], {}, 123])
73
+ end
74
+
75
+ private
76
+
77
+ # Select a value using weighted probabilities
78
+ #
79
+ # @param rng [Generator::RNG] random number generator
80
+ # @param values [Array] array of value objects with probabilities
81
+ # @return [Object] selected value
82
+ #
83
+ def weighted_choice(rng, values)
84
+ total = values.sum { |v| v.probability || 0 }
85
+ return values.first.value if total.zero?
86
+
87
+ random = rng.int(1, total)
88
+ cumulative = 0
89
+
90
+ values.each do |value|
91
+ cumulative += (value.probability || 0)
92
+ return value.value if random <= cumulative
93
+ end
94
+
95
+ # Fallback to first value
96
+ values.first.value
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+