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,586 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ module Core
6
+ # Text type generator with locale support
7
+ #
8
+ # Generates random text strings with optional length constraints and locale support.
9
+ # Uses Faker for locale-specific text generation when available.
10
+ #
11
+ # @example Basic usage
12
+ # text(50..100) # 50-100 characters
13
+ # text(100) # Exactly 100 characters
14
+ # text(min: 50, max: 200) # Named arguments
15
+ #
16
+ # @example With locale
17
+ # text(ar) # Arabic text (default length)
18
+ # text(50, language: :ar) # Arabic text, ~50 chars
19
+ # text(locale: :es) # Spanish text
20
+ #
21
+ # @example Multiple args: locale + length (order-independent)
22
+ # text(ar, 1..10) # Arabic text, 1-10 characters
23
+ # text(1..10, ar) # Same: 1-10 chars, Arabic
24
+ # text(en, 5..50) # English text, 5-50 characters
25
+ #
26
+ class Text < Base
27
+ # Generate random text
28
+ #
29
+ # @param rng [Generator::RNG] random number generator
30
+ # @param context [Generator::Context] generation context
31
+ # @param args [Hash] type arguments
32
+ # @option args [Integer] :min minimum length
33
+ # @option args [Integer] :max maximum length
34
+ # @option args [Range] :range length range
35
+ # @option args [Hash] :range hash with :min and :max keys
36
+ # @option args [Symbol, String] :language locale alias (:en, :ar, :es, etc.)
37
+ # @option args [Symbol, String] :locale locale identifier
38
+ # @return [String] generated text
39
+ #
40
+ def generate_random(rng, context, args)
41
+ # Check for locale in various argument formats:
42
+ # 1. Explicit: text(locale: :ar), text(language: :ar), text(lang: :ar)
43
+ # 2. Shorthand: text(ar) - parser should store this as args[:locale] since Synthra 1.x
44
+ # 3. Fallback: text(ar) - older versions stored as args[:value]
45
+ # An Integer or Range :value is a LENGTH (e.g. text(50), text(50..200)), not a locale,
46
+ # so it must not be fed to normalize_locale (Integer#to_sym would raise NoMethodError).
47
+ locale_arg = args[:value]
48
+ locale_arg = nil if locale_arg.is_a?(Integer) || locale_arg.is_a?(Range)
49
+ locale_value = args[:locale] || args[:language] || args[:lang] || locale_arg
50
+ locale = normalize_locale(locale_value)
51
+
52
+ # Prefer readable sentences/words over random character strings
53
+ generate_readable_text(rng, context, args, locale)
54
+ end
55
+
56
+ # Generate edge case text
57
+ #
58
+ # @param rng [Generator::RNG] random number generator
59
+ # @param context [Generator::Context] generation context
60
+ # @param args [Hash] type arguments
61
+ # @return [String] edge case text
62
+ #
63
+ def generate_edge(rng, context, args)
64
+ rng.sample(Generator::Modes::EDGE_STRINGS)
65
+ end
66
+
67
+ # Generate invalid text values
68
+ #
69
+ # @param rng [Generator::RNG] random number generator
70
+ # @param context [Generator::Context] generation context
71
+ # @param args [Hash] type arguments
72
+ # @return [Object] invalid value (nil, number, etc.)
73
+ #
74
+ def generate_invalid(rng, context, args)
75
+ rng.sample([nil, 123, [], {}, true, false])
76
+ end
77
+
78
+ # Generate hostile/malicious text for security testing
79
+ #
80
+ # Returns attack vectors including SQLi, XSS, path traversal,
81
+ # buffer overflows, and unicode edge cases.
82
+ #
83
+ # @param rng [Generator::RNG] random number generator
84
+ # @param context [Generator::Context] generation context
85
+ # @param args [Hash] type arguments
86
+ # @return [String] hostile payload
87
+ #
88
+ def generate_hostile(rng, context, args)
89
+ require_relative "../hostile_payloads"
90
+ rng.sample(HostilePayloads::ALL)
91
+ end
92
+
93
+ private
94
+
95
+ # Extract length constraints from arguments
96
+ #
97
+ # @param args [Hash] type arguments
98
+ # @return [Array<Integer>] [min_length, max_length]
99
+ #
100
+ def extract_length(args)
101
+ if args[:range].is_a?(Range)
102
+ args[:range].minmax
103
+ elsif args[:range].is_a?(Hash)
104
+ [args[:range][:min] || args[:range]["min"] || 10, args[:range][:max] || args[:range]["max"] || 100]
105
+ elsif args[:min] || args[:max]
106
+ [args[:min] || 10, args[:max] || 100]
107
+ elsif args[:value].is_a?(Integer)
108
+ [args[:value], args[:value]]
109
+ elsif args[:value].is_a?(Range)
110
+ args[:value].minmax
111
+ else
112
+ [10, 100] # Default range
113
+ end
114
+ end
115
+
116
+ # Normalize locale identifier
117
+ #
118
+ # @param locale [Symbol, String] locale identifier
119
+ # @return [Symbol] normalized locale
120
+ #
121
+ def normalize_locale(locale)
122
+ return :en if locale.nil?
123
+
124
+ locale_str = locale.to_s.downcase
125
+ case locale_str
126
+ when "en", "english" then :en
127
+ when "ar", "arabic" then :ar
128
+ when "es", "spanish" then :es
129
+ when "fr", "french" then :fr
130
+ when "de", "german" then :de
131
+ when "it", "italian" then :it
132
+ when "pt", "portuguese" then :pt
133
+ when "ru", "russian" then :ru
134
+ when "zh", "chinese" then :zh_CN
135
+ when "ja", "japanese" then :ja
136
+ when "ko", "korean" then :ko
137
+ when "hi", "hindi" then :hi
138
+ else
139
+ locale.to_sym
140
+ end
141
+ end
142
+
143
+ # Generate readable text (words/sentences) instead of random character strings.
144
+ # Respects character length when args include :range, :min/:max, or :value.
145
+ #
146
+ # @param rng [Generator::RNG] random number generator
147
+ # @param context [Generator::Context] generation context
148
+ # @param args [Hash] type arguments
149
+ # @param locale [Symbol] locale identifier
150
+ # @return [String] generated readable text
151
+ #
152
+ def generate_readable_text(rng, context, args, locale)
153
+ min_len, max_len = extract_length(args)
154
+ word_count = rng.int([min_len / 8, 2].max, [max_len / 5, 25].max)
155
+
156
+ text = begin
157
+ with_seeded_faker(rng) do
158
+ with_faker_locale(locale) do
159
+ if english_locale?(locale)
160
+ faker_english_words(word_count)
161
+ else
162
+ faker_localized_words(word_count, locale)
163
+ end
164
+ end
165
+ end
166
+ rescue StandardError
167
+ with_seeded_faker(rng) do
168
+ with_faker_locale(locale) { Faker::Lorem.words(number: [word_count, 10].min).join(" ") }
169
+ end
170
+ # :nocov: sibling rescue cannot catch errors raised by the rescue above it (dead code)
171
+ rescue StandardError
172
+ "Sample text"
173
+ # :nocov:
174
+ end
175
+
176
+ truncate_to_length(text, rng, min_len, max_len, args)
177
+ end
178
+
179
+ # Seed Faker deterministically from rng so word/sentence CONTENT — not just the word
180
+ # COUNT — is reproducible by seed. Without this, Faker::Books::Lovecraft.words (and the
181
+ # other word providers below) use Faker's process-global RNG and vary across runs even
182
+ # for the same generation seed. Derives a fresh per-call seed from rng (which advances,
183
+ # so each record still differs) and restores the previous Faker::Config.random after.
184
+ def with_seeded_faker(rng)
185
+ return yield unless defined?(Faker::Config) && Faker::Config.respond_to?(:random)
186
+
187
+ previous = Faker::Config.random
188
+ Faker::Config.random = Random.new(rng.rand(2**31))
189
+ begin
190
+ yield
191
+ ensure
192
+ Faker::Config.random = previous
193
+ end
194
+ end
195
+
196
+ def with_faker_locale(locale)
197
+ return yield unless Faker::Config.respond_to?(:locale=)
198
+
199
+ original = Faker::Config.locale
200
+ Faker::Config.locale = locale
201
+ yield
202
+ ensure
203
+ Faker::Config.locale = original
204
+ end
205
+
206
+ def english_locale?(locale)
207
+ locale.to_s.downcase.start_with?("en")
208
+ end
209
+
210
+ # Truncate or slice text to fit within [min_len, max_len] when length was explicitly specified.
211
+ # Preserves locale (e.g. Arabic text stays Arabic).
212
+ #
213
+ def truncate_to_length(text, rng, min_len, max_len, args)
214
+ has_explicit_length = args.key?(:range) || args.key?(:min) || args.key?(:max) ||
215
+ (args[:value].is_a?(Integer) || args[:value].is_a?(Range))
216
+ return text unless has_explicit_length
217
+ return "" if max_len <= 0 # zero-length requested → empty string (clamp(1, 0) would raise)
218
+
219
+ target_len = rng.int(min_len.clamp(1, max_len), max_len)
220
+ text.length > target_len ? text[0, target_len] : text
221
+ end
222
+
223
+ # English words for text/string — Faker::Hipster reads as English (e.g. "artisan kale tote
224
+ # bag"), matching the `words` type. (Was Faker::Books::Lovecraft, which leaked horror
225
+ # vocabulary "stygian eldritch cyclopean tentacles" into post content, excerpts and tags.)
226
+ def faker_english_words(word_count)
227
+ word_count = word_count.clamp(1, 25)
228
+ Faker::Hipster.words(number: word_count).join(" ")
229
+ end
230
+
231
+ # Localized words via Faker (for non-English locales)
232
+ # Uses locale-aware Faker providers that actually generate text in the target language
233
+ def faker_localized_words(word_count, locale)
234
+ word_count = word_count.clamp(1, 25)
235
+
236
+ # Build text from locale-aware providers
237
+ words = []
238
+ attempts = 0
239
+ max_attempts = word_count * 3
240
+
241
+ while words.length < word_count && attempts < max_attempts
242
+ attempts += 1
243
+
244
+ # Try various localized Faker providers
245
+ # :nocov: each `if defined?(Faker::X)` guard's false arm is unreachable (Faker is a hard
246
+ # dependency, always loaded); the case arm chosen by rand(6) is also non-deterministic.
247
+ text = case rand(6)
248
+ when 0
249
+ Faker::Name.name if defined?(Faker::Name)
250
+ when 1
251
+ Faker::Address.city if defined?(Faker::Address)
252
+ when 2
253
+ Faker::Company.name if defined?(Faker::Company)
254
+ when 3
255
+ Faker::Address.country if defined?(Faker::Address)
256
+ when 4
257
+ Faker::Address.street_name if defined?(Faker::Address)
258
+ else
259
+ Faker::Company.industry if defined?(Faker::Company) && Faker::Company.respond_to?(:industry)
260
+ end
261
+ # :nocov:
262
+
263
+ words += text.to_s.split(/\s+/) if text
264
+ end
265
+
266
+ words.first(word_count).join(" ")
267
+ rescue StandardError
268
+ # Fallback to Lorem if localized providers fail
269
+ Faker::Lorem.words(number: word_count).join(" ")
270
+ end
271
+
272
+ # Generate text with locale support (character-based; used when explicit length needed)
273
+ #
274
+ def generate_text_with_locale(rng, context, length, locale)
275
+ adapter = faker_adapter(context)
276
+
277
+ if adapter
278
+ adapter.characters(number: length, locale: locale)
279
+
280
+ else
281
+ original_locale = Faker::Config.locale
282
+ begin
283
+ Faker::Config.locale = locale if Faker::Config.respond_to?(:locale=)
284
+ Faker::Lorem.characters(number: length)
285
+ rescue StandardError
286
+ generate_random_alphanumeric(rng, length)
287
+ ensure
288
+ Faker::Config.locale = original_locale if Faker::Config.respond_to?(:locale=)
289
+ end
290
+ end
291
+ rescue StandardError
292
+ generate_random_alphanumeric(rng, length)
293
+ end
294
+
295
+ # Generate random alphanumeric text (fallback)
296
+ #
297
+ # @param rng [Generator::RNG] random number generator
298
+ # @param length [Integer] desired text length
299
+ # @return [String] generated alphanumeric text
300
+ #
301
+ def generate_random_alphanumeric(rng, length)
302
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
303
+ length.times.map { rng.sample(chars) }.join
304
+ end
305
+ end
306
+
307
+ # Number type generator (integer or float)
308
+ #
309
+ # Generates numeric values with optional range constraints. Automatically
310
+ # detects whether to generate integers or floats based on the range.
311
+ #
312
+ # @example Integer range
313
+ # number(1..100) # Integer between 1 and 100
314
+ #
315
+ # @example Float range
316
+ # number(0.0..1.0) # Float between 0.0 and 1.0
317
+ #
318
+ # @example Named arguments
319
+ # number(min: 10, max: 100)
320
+ #
321
+ class Number < Base
322
+ # Generate a random number
323
+ #
324
+ # @param rng [Generator::RNG] random number generator
325
+ # @param context [Generator::Context] generation context
326
+ # @param args [Hash] type arguments
327
+ # @option args [Range] :range numeric range
328
+ # @option args [Integer, Float] :min minimum value
329
+ # @option args [Integer, Float] :max maximum value
330
+ # @return [Integer, Float] generated number
331
+ #
332
+ def generate_random(rng, context, args)
333
+ range = extract_range(args)
334
+ if range
335
+ # Detect if we need floats based on range values
336
+ if range.first.is_a?(Float) || range.last.is_a?(Float)
337
+ (rng.rand * (range.last - range.first)) + range.first
338
+ else
339
+ rng.int(range.first, range.last)
340
+ end
341
+ else
342
+ # Default: integer from 0 to 1000
343
+ rng.int(0, 1000)
344
+ end
345
+ end
346
+
347
+ # Generate edge case numbers
348
+ #
349
+ # @param rng [Generator::RNG] random number generator
350
+ # @param context [Generator::Context] generation context
351
+ # @param args [Hash] type arguments
352
+ # @return [Integer, Float] edge case number
353
+ #
354
+ def generate_edge(rng, context, args)
355
+ range = extract_range(args)
356
+ if range
357
+ # Return boundary values
358
+ candidates = [range.first, range.last]
359
+ rng.sample(candidates)
360
+ else
361
+ rng.sample(Generator::Modes::EDGE_NUMBERS)
362
+ end
363
+ end
364
+
365
+ # Generate invalid number values
366
+ #
367
+ # @param rng [Generator::RNG] random number generator
368
+ # @param context [Generator::Context] generation context
369
+ # @param args [Hash] type arguments
370
+ # @return [Object] invalid value
371
+ #
372
+ def generate_invalid(rng, context, args)
373
+ rng.sample(["not a number", nil, "NaN", Float::INFINITY, "", []])
374
+ end
375
+
376
+ # Generate hostile numeric values for security testing
377
+ #
378
+ # Returns extreme values designed to cause overflows,
379
+ # underflows, or unexpected behavior.
380
+ #
381
+ # @param rng [Generator::RNG] random number generator
382
+ # @param context [Generator::Context] generation context
383
+ # @param args [Hash] type arguments
384
+ # @return [Numeric] hostile numeric value
385
+ #
386
+ def generate_hostile(rng, context, args)
387
+ hostile_numbers = [
388
+ -2**63, # MIN_INT64
389
+ (2**63) - 1, # MAX_INT64
390
+ -2**31, # MIN_INT32
391
+ (2**31) - 1, # MAX_INT32
392
+ Float::INFINITY,
393
+ -Float::INFINITY,
394
+ Float::NAN,
395
+ 0.0 / 0.0,
396
+ 1.0 / 0.0,
397
+ -1.0 / 0.0,
398
+ 2**128, # Extreme large number
399
+ -2**128,
400
+ 0.0000000000001, # Very small float
401
+ 9_999_999_999_999_999_999_999_999_999_999_999_999,
402
+ -9_999_999_999_999_999_999_999_999_999_999_999_999
403
+ ]
404
+ rng.sample(hostile_numbers)
405
+ end
406
+
407
+ private
408
+
409
+ # Extract range from arguments
410
+ #
411
+ # @param args [Hash] type arguments
412
+ # @return [Range, nil] extracted range
413
+ #
414
+ def extract_range(args)
415
+ if args[:range].is_a?(Range)
416
+ args[:range]
417
+ elsif args[:min] || args[:max]
418
+ min = args[:min] || 0
419
+ max = args[:max] || 100
420
+ min..max
421
+ elsif args[:value].is_a?(Range)
422
+ args[:value] # fallback
423
+ else
424
+ nil
425
+ end
426
+ end
427
+ end
428
+
429
+ # Boolean type generator
430
+ #
431
+ # Generates true/false values with optional probability weights.
432
+ #
433
+ # @example Default (50/50)
434
+ # boolean
435
+ #
436
+ # @example With probability
437
+ # boolean(true: 80%) # 80% chance of true
438
+ # boolean(false: 30%) # 30% chance of false
439
+ #
440
+ class Boolean < Base
441
+ # Generate a random boolean
442
+ #
443
+ # @param rng [Generator::RNG] random number generator
444
+ # @param context [Generator::Context] generation context
445
+ # @param args [Hash] type arguments
446
+ # @option args [Integer] :true_probability probability of true (0-100)
447
+ # @option args [Integer] :false_probability probability of false (0-100)
448
+ # @return [Boolean] generated boolean value
449
+ #
450
+ def generate_random(rng, context, args)
451
+ true_prob = extract_probability(args[:true_probability] || args["true_probability"])
452
+ false_prob = extract_probability(args[:false_probability] || args["false_probability"])
453
+
454
+ threshold = if true_prob && true_prob > 0
455
+ true_prob
456
+ elsif false_prob && false_prob > 0
457
+ 100 - false_prob
458
+ else
459
+ 50 # Default 50/50
460
+ end
461
+
462
+ rng.int(1, 100) <= threshold
463
+ end
464
+
465
+ # Generate edge case boolean
466
+ #
467
+ # @param rng [Generator::RNG] random number generator
468
+ # @param context [Generator::Context] generation context
469
+ # @param args [Hash] type arguments
470
+ # @return [Boolean] edge case boolean
471
+ #
472
+ def generate_edge(rng, context, args)
473
+ rng.sample([true, false])
474
+ end
475
+
476
+ # Generate invalid boolean values
477
+ #
478
+ # @param rng [Generator::RNG] random number generator
479
+ # @param context [Generator::Context] generation context
480
+ # @param args [Hash] type arguments
481
+ # @return [Object] invalid value
482
+ #
483
+ def generate_invalid(rng, context, args)
484
+ rng.sample([nil, "true", "false", 0, 1, "", []])
485
+ end
486
+
487
+ private
488
+
489
+ # Extract probability from argument
490
+ #
491
+ # @param prob [Integer, String, nil] probability value
492
+ # @return [Integer, nil] extracted probability (0-100)
493
+ #
494
+ def extract_probability(prob)
495
+ return nil if prob.nil?
496
+
497
+ case prob
498
+ when Integer
499
+ prob.clamp(0, 100)
500
+ when String
501
+ prob.to_i.clamp(0, 100) # fallback
502
+ else
503
+ nil
504
+ end
505
+ end
506
+ end
507
+
508
+ # Const type generator - returns a literal constant value
509
+ #
510
+ # Always returns the exact value provided, regardless of generation mode.
511
+ # Useful for fields that should always have the same value, like
512
+ # discriminator fields or fixed identifiers.
513
+ #
514
+ # @example Fixed string value
515
+ # type: const("message_create")
516
+ #
517
+ # @example Fixed number value
518
+ # version: const(1)
519
+ #
520
+ # @example As discriminator
521
+ # eventType: const("new_twitter_direct_message")
522
+ #
523
+ class Const < Base
524
+ # Generate the constant value
525
+ #
526
+ # @param rng [Generator::RNG] random number generator (not used)
527
+ # @param context [Generator::Context] generation context (not used)
528
+ # @param args [Hash] type arguments
529
+ # @option args [Object] :value the constant value to return
530
+ # @return [Object] the constant value
531
+ #
532
+ def generate(rng, context, args, mode)
533
+ extract_value(args)
534
+ end
535
+
536
+ # Generate random constant (same as generate)
537
+ #
538
+ # @param rng [Generator::RNG] random number generator
539
+ # @param context [Generator::Context] generation context
540
+ # @param args [Hash] type arguments
541
+ # @return [Object] the constant value
542
+ #
543
+ def generate_random(rng, context, args)
544
+ extract_value(args)
545
+ end
546
+
547
+ # Generate edge case constant (same as generate)
548
+ #
549
+ # @param rng [Generator::RNG] random number generator
550
+ # @param context [Generator::Context] generation context
551
+ # @param args [Hash] type arguments
552
+ # @return [Object] the constant value
553
+ #
554
+ def generate_edge(rng, context, args)
555
+ extract_value(args)
556
+ end
557
+
558
+ # Generate invalid constant (same as generate)
559
+ #
560
+ # For const types, even "invalid" mode returns the constant value,
561
+ # since the purpose is to always return the same value.
562
+ #
563
+ # @param rng [Generator::RNG] random number generator
564
+ # @param context [Generator::Context] generation context
565
+ # @param args [Hash] type arguments
566
+ # @return [Object] the constant value
567
+ #
568
+ def generate_invalid(rng, context, args)
569
+ extract_value(args)
570
+ end
571
+
572
+ private
573
+
574
+ # Extract the constant value from arguments
575
+ #
576
+ # @param args [Hash] type arguments
577
+ # @return [Object] the extracted value
578
+ #
579
+ def extract_value(args)
580
+ # Support multiple argument formats
581
+ args[:value] || args["value"] || args[:path] || args["path"] || args.values.first
582
+ end
583
+ end
584
+ end
585
+ end
586
+ end