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,444 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Generator
5
+
6
+ # Generation modes for different types of fake data
7
+ module Modes
8
+
9
+ # Curated edge case strings
10
+
11
+ EDGE_STRINGS = [
12
+ "", # empty
13
+ " ", # single space
14
+ " ", # multiple spaces
15
+ "\t\n\r", # whitespace chars
16
+ "🎉💀🔥👻", # emojis
17
+ "مرحبا", # RTL Arabic
18
+ "日本語", # Japanese
19
+ "SELECT * FROM users;", # SQL injection attempt
20
+ '{"key": "value"}', # JSON in string
21
+ "<script>alert(1)</script>", # XSS attempt
22
+ "a" * 1000, # very long
23
+ "NULL", # string null
24
+ "undefined", # string undefined
25
+ "true", # string boolean
26
+ "123", # string number
27
+ "\x00\x00", # null bytes
28
+ "O'Brien", # apostrophe
29
+ "a\nb\nc" # newlines
30
+ ].freeze
31
+
32
+
33
+ # Curated edge case numbers
34
+
35
+ EDGE_NUMBERS = [
36
+ 0,
37
+ -1,
38
+ 1,
39
+ -2_147_483_648, # INT_MIN
40
+ 2_147_483_647, # INT_MAX
41
+ 9_223_372_036_854_775_807, # BIGINT_MAX
42
+ Float::MIN,
43
+ Float::MAX
44
+ ].freeze
45
+
46
+
47
+ # Curated edge case dates
48
+
49
+ EDGE_DATES = [
50
+ Date.new(1970, 1, 1), # Unix epoch
51
+ Date.new(2000, 2, 29), # Leap day
52
+ Date.new(2038, 1, 19), # Y2038
53
+ Date.new(2020, 1, 1), # Fixed date for determinism (replaces Date.today)
54
+ Date.new(1, 1, 1), # Very old
55
+ Date.new(9999, 12, 31) # Far future
56
+ ].freeze
57
+
58
+
59
+ # Base mode class
60
+ #
61
+ # Abstract base class for all generation modes. Each mode implements
62
+ # the #generate method to produce values in its specific style.
63
+ #
64
+ # @abstract Subclass and implement {#generate}
65
+ #
66
+
67
+ class BaseMode
68
+
69
+ # Create a new mode instance
70
+ #
71
+ # @param rng [Generator::RNG] random number generator
72
+ #
73
+
74
+ def initialize(rng)
75
+ @rng = rng
76
+ end
77
+
78
+
79
+ # Generate a value using this mode
80
+ #
81
+ # @abstract Must be implemented by subclasses
82
+ # @param _type [Base] type generator instance
83
+ # @param _args [Hash] type arguments
84
+ # @return [Object] generated value
85
+ # @raise [NotImplementedError] if not overridden
86
+ #
87
+
88
+ def generate(_type, _args)
89
+ raise NotImplementedError
90
+ end
91
+ end
92
+
93
+
94
+ # Random mode - standard Faker-based generation
95
+ #
96
+ # Generates realistic fake data using Faker library. This is the
97
+ # default mode for normal data generation.
98
+ #
99
+
100
+ class RandomMode < BaseMode
101
+
102
+ # Generate a random value
103
+ #
104
+ # @param type_instance [Base] type generator instance
105
+ # @param args [Hash] type arguments
106
+ # @return [Object] generated value
107
+ #
108
+
109
+ def generate(type_instance, args, context = nil)
110
+ context ||= Generator::Context.new
111
+ type_instance.generate_random(@rng, context, args)
112
+ end
113
+ end
114
+
115
+
116
+ # Edge mode - curated edge cases
117
+ #
118
+ # Generates edge case values (empty strings, boundary values, nulls)
119
+ # useful for testing boundary conditions and error handling.
120
+ #
121
+
122
+ class EdgeMode < BaseMode
123
+
124
+ # Generate an edge case value
125
+ #
126
+ # @param type_instance [Base] type generator instance
127
+ # @param args [Hash] type arguments
128
+ # @return [Object] edge case value
129
+ #
130
+
131
+ def generate(type_instance, args, context = nil)
132
+ context ||= Generator::Context.new
133
+ type_instance.generate_edge(@rng, context, args)
134
+ end
135
+
136
+
137
+ # Get an edge case string
138
+ #
139
+ # Returns a curated edge case string from EDGE_STRINGS.
140
+ #
141
+ # @return [String] edge case string
142
+ #
143
+
144
+ def edge_string
145
+ EDGE_STRINGS.sample(random: @rng.instance_variable_get(:@random))
146
+ end
147
+
148
+
149
+ # Get an edge case number
150
+ #
151
+ # Returns a curated edge case number from EDGE_NUMBERS, optionally
152
+ # filtered by range.
153
+ #
154
+ # @param range [Range, nil] optional range to filter candidates
155
+ # @return [Numeric] edge case number
156
+ #
157
+
158
+ def edge_number(range = nil)
159
+ candidates = EDGE_NUMBERS
160
+ if range
161
+ candidates = candidates.select { |n| range.cover?(n) }
162
+ end
163
+ candidates.empty? ? 0 : candidates.sample(random: @rng.instance_variable_get(:@random))
164
+ end
165
+
166
+
167
+ # Get an edge case date
168
+ #
169
+ # Returns a curated edge case date from EDGE_DATES.
170
+ #
171
+ # @return [Date] edge case date
172
+ #
173
+
174
+ def edge_date
175
+ EDGE_DATES.sample(random: @rng.instance_variable_get(:@random))
176
+ end
177
+ end
178
+
179
+
180
+ # Invalid mode - intentionally wrong data
181
+ #
182
+ # Generates intentionally invalid values (wrong types, malformed data)
183
+ # useful for testing validation logic.
184
+ #
185
+
186
+ class InvalidMode < BaseMode
187
+
188
+ # Generate an invalid value
189
+ #
190
+ # @param type_instance [Base] type generator instance
191
+ # @param args [Hash] type arguments
192
+ # @return [Object] invalid value
193
+ #
194
+
195
+ def generate(type_instance, args, context = nil)
196
+ context ||= Generator::Context.new
197
+ type_instance.generate_invalid(@rng, context, args)
198
+ end
199
+
200
+
201
+ # Get an invalid string value
202
+ #
203
+ # Returns a value that is not a valid string.
204
+ #
205
+ # @return [Object] invalid value (nil, number, array, hash, boolean)
206
+ #
207
+
208
+ def invalid_string
209
+ @rng.sample([nil, 123, [], {}, true])
210
+ end
211
+
212
+
213
+ # Get an invalid number value
214
+ #
215
+ # Returns a value that is not a valid number.
216
+ #
217
+ # @return [Object] invalid value (string, nil, array)
218
+ #
219
+
220
+ def invalid_number
221
+ @rng.sample(["not a number", nil, "NaN", "Infinity", "", []])
222
+ end
223
+
224
+
225
+ # Get an invalid email value
226
+ #
227
+ # Returns a value that is not a valid email address.
228
+ #
229
+ # @return [Object] invalid value (malformed email, nil, number)
230
+ #
231
+
232
+ def invalid_email
233
+ @rng.sample([
234
+ "notanemail",
235
+ "@missing.local",
236
+ "missing@",
237
+ "double@@at.com",
238
+ "spaces in@email.com",
239
+ nil,
240
+ 123
241
+ ])
242
+ end
243
+
244
+
245
+ # Get an invalid UUID value
246
+ #
247
+ # Returns a value that is not a valid UUID.
248
+ #
249
+ # @return [Object] invalid value (string, nil, number)
250
+ #
251
+
252
+ def invalid_uuid
253
+ @rng.sample([
254
+ "not-a-uuid",
255
+ "12345",
256
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
257
+ nil,
258
+ 123
259
+ ])
260
+ end
261
+ end
262
+
263
+
264
+ # Mixed mode - combination of random, edge, and invalid
265
+ #
266
+ # Probabilistically mixes all three modes according to specified ratios.
267
+ # Default: 80% random, 15% edge, 5% invalid.
268
+ #
269
+
270
+ class MixedMode < BaseMode
271
+
272
+ # Default mode ratios
273
+ # @return [Hash] ratio percentages
274
+ DEFAULT_RATIOS = {
275
+ random: 80,
276
+ edge: 15,
277
+ invalid: 5
278
+ }.freeze
279
+
280
+
281
+ # Create a new MixedMode instance
282
+ #
283
+ # @param rng [Generator::RNG] random number generator
284
+ # @param ratios [Hash] mode ratios (default: DEFAULT_RATIOS)
285
+ #
286
+
287
+ def initialize(rng, ratios: DEFAULT_RATIOS)
288
+ super(rng)
289
+ @ratios = ratios
290
+ @random_mode = RandomMode.new(rng)
291
+ @edge_mode = EdgeMode.new(rng)
292
+ @invalid_mode = InvalidMode.new(rng)
293
+ end
294
+
295
+
296
+ # Generate a value using mixed mode
297
+ #
298
+ # Selects a mode probabilistically based on ratios and generates
299
+ # a value using that mode.
300
+ #
301
+ # @param type_instance [Base] type generator instance
302
+ # @param args [Hash] type arguments
303
+ # @return [Object] generated value
304
+ #
305
+
306
+ def generate(type_instance, args, context = nil)
307
+ context ||= Generator::Context.new
308
+ roll = @rng.rand(100)
309
+ cumulative = 0
310
+
311
+ if roll < (cumulative += @ratios[:random])
312
+ @random_mode.generate(type_instance, args, context)
313
+ elsif roll < (cumulative += @ratios[:edge])
314
+ @edge_mode.generate(type_instance, args, context)
315
+ else
316
+ @invalid_mode.generate(type_instance, args, context)
317
+ end
318
+ end
319
+ end
320
+
321
+
322
+ # Hostile mode - security attack payloads
323
+ #
324
+ # Generates security attack payloads (SQL injection, XSS, buffer overflow)
325
+ # for security testing of API endpoints consuming this data.
326
+ #
327
+ class HostileMode < BaseMode
328
+ # Security attack payloads
329
+ SQL_INJECTION_PAYLOADS = [
330
+ "' OR '1'='1",
331
+ "'; DROP TABLE users;--",
332
+ "' UNION SELECT * FROM users--",
333
+ "1' OR '1'='1",
334
+ "admin'--",
335
+ "' OR 1=1;--",
336
+ "1' AND '1'='1",
337
+ "' OR 'x'='x",
338
+ "') OR ('1'='1--",
339
+ "1' OR '1'='1' /*"
340
+ ].freeze
341
+
342
+ XSS_PAYLOADS = [
343
+ "<script>alert(1)</script>",
344
+ "<img src=x onerror=alert(1)>",
345
+ "<svg onload=alert(1)>",
346
+ "javascript:alert(1)",
347
+ "<body onload=alert(1)>",
348
+ "<iframe src=javascript:alert(1)>",
349
+ "<input onfocus=alert(1) autofocus>",
350
+ "<select onfocus=alert(1) autofocus>",
351
+ "<textarea onfocus=alert(1) autofocus>",
352
+ "<keygen onfocus=alert(1) autofocus>"
353
+ ].freeze
354
+
355
+ BUFFER_OVERFLOW_PAYLOADS = [
356
+ "A" * 1000,
357
+ "A" * 10000,
358
+ "A" * 100000,
359
+ "\x00" * 1000,
360
+ "\xFF" * 1000
361
+ ].freeze
362
+
363
+ UNICODE_ATTACKS = [
364
+ "\u202E" + "evil" + "\u202D", # RTL override
365
+ "\uFEFF" + "text", # BOM
366
+ "\u200B" * 100, # Zero-width spaces
367
+ "\u2060" * 100, # Word joiner
368
+ "Zalgo: H\u0301\u0302\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030A\u030B\u030C\u030D\u030E\u030F\u0310\u0311\u0312\u0313\u0314\u0315\u0316\u0317\u0318\u0319\u031A\u031B\u031C\u031D\u031E\u031F\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032A\u032B\u032C\u032D\u032E\u032F\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033A\u033B\u033C\u033D\u033E\u033F\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034A\u034B\u034C\u034D\u034E\u034F\u0350\u0351\u0352\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035A\u035B\u035C\u035D\u035E\u035F\u0360\u0361\u0362\u0363\u0364\u0365\u0366\u0367\u0368\u0369\u036A\u036B\u036C\u036D\u036E\u036F\u0489\u048A\u048B\u048C\u048D\u048E\u048F\u0490\u0491\u0492\u0493\u0494\u0495\u0496\u0497\u0498\u0499\u049A\u049B\u049C\u049D\u049E\u049F\u04A0\u04A1\u04A2\u04A3\u04A4\u04A5\u04A6\u04A7\u04A8\u04A9\u04AA\u04AB\u04AC\u04AD\u04AE\u04AF\u04B0\u04B1\u04B2\u04B3\u04B4\u04B5\u04B6\u04B7\u04B8\u04B9\u04BA\u04BB\u04BC\u04BD\u04BE\u04BF\u04C0\u04C1\u04C2\u04C3\u04C4\u04C5\u04C6\u04C7\u04C8\u04C9\u04CA\u04CB\u04CC\u04CD\u04CE\u04CF\u04D0\u04D1\u04D2\u04D3\u04D4\u04D5\u04D6\u04D7\u04D8\u04D9\u04DA\u04DB\u04DC\u04DD\u04DE\u04DF\u04E0\u04E1\u04E2\u04E3\u04E4\u04E5\u04E6\u04E7\u04E8\u04E9\u04EA\u04EB\u04EC\u04ED\u04EE\u04EF\u04F0\u04F1\u04F2\u04F3\u04F4\u04F5\u04F6\u04F7\u04F8\u04F9\u04FA\u04FB\u04FC\u04FD\u04FE\u04FF"
369
+ ].freeze
370
+
371
+ # Generate a hostile value
372
+ #
373
+ # @param type_instance [Base] type generator instance
374
+ # @param args [Hash] type arguments
375
+ # @return [Object] hostile value
376
+ #
377
+ def generate(type_instance, args, context = nil)
378
+ context ||= Generator::Context.new
379
+ type_instance.generate_hostile(@rng, context, args)
380
+ end
381
+
382
+ # Get SQL injection payload
383
+ #
384
+ # @return [String] SQL injection string
385
+ #
386
+ def sql_injection
387
+ SQL_INJECTION_PAYLOADS.sample(random: @rng.instance_variable_get(:@random))
388
+ end
389
+
390
+ # Get XSS payload
391
+ #
392
+ # @return [String] XSS attack string
393
+ #
394
+ def xss_payload
395
+ XSS_PAYLOADS.sample(random: @rng.instance_variable_get(:@random))
396
+ end
397
+
398
+ # Get buffer overflow payload
399
+ #
400
+ # @return [String] buffer overflow string
401
+ #
402
+ def buffer_overflow
403
+ BUFFER_OVERFLOW_PAYLOADS.sample(random: @rng.instance_variable_get(:@random))
404
+ end
405
+
406
+ # Get Unicode attack payload
407
+ #
408
+ # @return [String] Unicode attack string
409
+ #
410
+ def unicode_attack
411
+ UNICODE_ATTACKS.sample(random: @rng.instance_variable_get(:@random))
412
+ end
413
+ end
414
+
415
+
416
+ # Factory method to create mode instance
417
+ #
418
+ # Creates an appropriate mode instance based on the mode symbol.
419
+ #
420
+ # @param mode [Symbol] mode name (:random, :edge, :invalid, :mixed)
421
+ # @param rng [Generator::RNG] random number generator
422
+ # @return [BaseMode] mode instance
423
+ # @raise [ArgumentError] if mode is unknown
424
+ #
425
+ # @example
426
+ # Modes.create(:edge, rng)
427
+ # # => #<EdgeMode>
428
+ #
429
+
430
+ def self.create(mode, rng)
431
+ case mode
432
+ when :random then RandomMode.new(rng)
433
+ when :edge then EdgeMode.new(rng)
434
+ when :invalid then InvalidMode.new(rng)
435
+ when :hostile then HostileMode.new(rng)
436
+ when :mixed then MixedMode.new(rng)
437
+ else
438
+ raise ArgumentError, "Unknown mode: #{mode}"
439
+ end
440
+ end
441
+ end
442
+ end
443
+ end
444
+
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Generator
5
+
6
+ # Resolves cross-schema references
7
+ class Resolver
8
+ def initialize(registry)
9
+ @registry = registry
10
+ @cache = {}
11
+ @generating = Set.new
12
+ @mutex = Mutex.new
13
+ end
14
+
15
+
16
+ # Resolve a reference to another schema's field
17
+ #
18
+ # @param schema_name [String] the schema name
19
+ # @param field_path [String] dot-separated field path
20
+ # @param depth [Integer] current recursion depth (default: 0)
21
+ # @param seed [Integer, nil] deterministic seed for the referenced schema's generation
22
+ # (derived from the parent RNG by the Reference type) — makes ref() values reproducible
23
+ # @return [Object] the resolved value
24
+ # @raise [RecursionLimitError] if recursion depth exceeds configured limit
25
+
26
+ def resolve(schema_name, field_path, depth: 0, seed: nil)
27
+
28
+ # Validate recursion depth against configured limits (before mutex to avoid deadlock)
29
+ # Check depth + 1 because we're about to generate at that depth
30
+ Synthra.configuration.limits.validate_recursion!(depth + 1)
31
+
32
+
33
+ # Check cache first (thread-safe read)
34
+ cache_key = "#{schema_name}.#{field_path}"
35
+ @mutex.synchronize do
36
+ return @cache[cache_key] if @cache.key?(cache_key)
37
+ end
38
+
39
+
40
+ # Check for cycles BEFORE acquiring mutex to avoid deadlock on recursive calls
41
+ @mutex.synchronize do
42
+ if @generating.include?(schema_name)
43
+ cycle = @generating.to_a + [schema_name]
44
+ raise CyclicReferenceError, cycle
45
+ end
46
+ end
47
+
48
+
49
+ # Generate the referenced schema (outside mutex to allow recursive calls)
50
+ begin
51
+ @mutex.synchronize do
52
+ @generating.add(schema_name)
53
+ end
54
+
55
+ schema = @registry.schema(schema_name)
56
+
57
+ # Pass this resolver instance to ensure cycle detection works
58
+ # The Engine will reuse this resolver instead of creating a new one.
59
+ # `seed` (from the parent RNG) makes the referenced record reproducible by seed.
60
+ record = schema.generate(seed: seed, registry: @registry, depth: depth + 1, resolver: self)
61
+
62
+
63
+ # Cache all fields from this schema
64
+ @mutex.synchronize do
65
+ record.each do |key, val|
66
+ @cache["#{schema_name}.#{key}"] = val
67
+ end
68
+
69
+
70
+ # Extract the specific field
71
+ value = extract_path(record, field_path)
72
+ @cache[cache_key] = value
73
+ value
74
+ end
75
+ rescue KeyError
76
+ raise MissingReferenceError.new(schema_name: schema_name, field_path: field_path)
77
+ ensure
78
+ @mutex.synchronize do
79
+ @generating.delete(schema_name)
80
+ end
81
+ end
82
+ end
83
+
84
+
85
+ # Get schemas in dependency order
86
+ #
87
+ # @return [Array<String>] schema names ordered by dependencies
88
+
89
+ def dependency_order
90
+ dependencies = build_dependency_graph
91
+ topological_sort(dependencies)
92
+ end
93
+
94
+
95
+ # Detect cycles in schema references
96
+ #
97
+ # @return [Array<Array<String>>] array of cycle paths
98
+
99
+ def detect_cycles
100
+ cycles = []
101
+ visited = Set.new
102
+ stack = []
103
+
104
+ @registry.names.each do |name|
105
+ detect_cycles_from(name, visited, stack, cycles)
106
+ end
107
+
108
+ cycles
109
+ end
110
+
111
+
112
+ # Clear the resolution cache
113
+ #
114
+ # @return [void]
115
+
116
+ def clear_cache
117
+ @mutex.synchronize do
118
+ @cache.clear
119
+ @generating.clear
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+
126
+ # Extract value from record using dot-separated path
127
+ #
128
+ # Traverses nested hashes using dot notation (e.g., "address.city").
129
+ #
130
+ # @param record [Hash] record to extract from
131
+ # @param path [String] dot-separated field path
132
+ # @return [Object, nil] extracted value or nil if not found
133
+ #
134
+
135
+ def extract_path(record, path)
136
+ parts = path.to_s.split(".")
137
+ value = record
138
+
139
+ parts.each do |part|
140
+ case value
141
+ when Hash
142
+ value = value[part] || value[part.to_sym]
143
+ else
144
+ return nil
145
+ end
146
+ return nil if value.nil?
147
+ end
148
+
149
+ value
150
+ end
151
+
152
+
153
+ # Build dependency graph from schema references
154
+ #
155
+ # Analyzes all schemas to build a graph of cross-schema dependencies.
156
+ #
157
+ # @return [Hash] dependency graph { schema_name => [dependent_schemas] }
158
+ #
159
+
160
+ def build_dependency_graph
161
+ graph = Hash.new { |h, k| h[k] = [] }
162
+
163
+ @registry.each do |name, schema|
164
+ schema.fields.each do |field|
165
+ if field.type_name == "reference"
166
+ ref = field.type_args[:ref]
167
+ ref_schema = ref.respond_to?(:schema_name) ? ref.schema_name : ref&.dig(:schema_name)
168
+ graph[name] << ref_schema if ref_schema
169
+ end
170
+ end
171
+ end
172
+
173
+ graph
174
+ end
175
+
176
+
177
+ # Topologically sort schemas by dependencies
178
+ #
179
+ # Orders schemas so that dependencies come before dependents.
180
+ # Uses Kahn's algorithm for topological sorting.
181
+ #
182
+ # @param graph [Hash] dependency graph
183
+ # @return [Array<String>] sorted schema names
184
+ #
185
+
186
+ def topological_sort(graph)
187
+ in_degree = Hash.new(0)
188
+ all_nodes = Set.new(@registry.names)
189
+
190
+ graph.each do |node, deps|
191
+ deps.each { |dep| in_degree[dep] += 1 }
192
+ end
193
+
194
+ queue = all_nodes.select { |n| in_degree[n].zero? }
195
+ result = []
196
+
197
+ until queue.empty?
198
+ node = queue.shift
199
+ result << node
200
+
201
+ graph[node].each do |dep|
202
+ in_degree[dep] -= 1
203
+ queue << dep if in_degree[dep].zero?
204
+ end
205
+ end
206
+
207
+
208
+ # If we couldn't sort all, there's a cycle - just return all names
209
+ result.length == all_nodes.length ? result : @registry.names
210
+ end
211
+
212
+
213
+ # Detect cycles starting from a node
214
+ #
215
+ # Recursively traverses the dependency graph to detect cycles.
216
+ # Uses depth-first search with cycle detection.
217
+ #
218
+ # @param node [String] schema name to start from
219
+ # @param visited [Set] set of visited nodes
220
+ # @param stack [Array] current path stack
221
+ # @param cycles [Array] array to collect detected cycles
222
+ # @return [void]
223
+ #
224
+
225
+ def detect_cycles_from(node, visited, stack, cycles)
226
+ return if visited.include?(node)
227
+
228
+ if stack.include?(node)
229
+ cycle_start = stack.index(node)
230
+ cycles << stack[cycle_start..] + [node]
231
+ return
232
+ end
233
+
234
+ stack.push(node)
235
+
236
+ begin
237
+ schema = @registry.schema(node)
238
+ schema.fields.each do |field|
239
+ if field.type_name == "reference"
240
+ ref = field.type_args[:ref]
241
+ ref_schema = ref.respond_to?(:schema_name) ? ref.schema_name : ref&.dig(:schema_name)
242
+ detect_cycles_from(ref_schema, visited, stack, cycles) if ref_schema
243
+ end
244
+ end
245
+ rescue KeyError
246
+
247
+ # Schema not found
248
+ end
249
+
250
+ stack.pop
251
+ visited.add(node)
252
+ end
253
+ end
254
+ end
255
+ end
256
+