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,406 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ module AddressLocation
6
+ # City type
7
+ class City < Base
8
+ # Generate random city name
9
+ #
10
+ # @param rng [Generator::RNG] random number generator (not directly used)
11
+ # @param context [Generator::Context] generation context
12
+ # @param args [Hash] type arguments (not used)
13
+ # @return [String] generated city name
14
+ #
15
+ def generate_random(rng, context, args)
16
+ adapter = faker_adapter(context)
17
+ if adapter
18
+ adapter.city
19
+
20
+ else
21
+ Faker::Address.city
22
+ end
23
+
24
+ rescue StandardError
25
+ rng.sample(["New York", "Los Angeles", "Chicago", "Houston", "Phoenix"])
26
+ end
27
+
28
+
29
+ def generate_edge(rng, context, args)
30
+ ["", "A", "Very Long City Name That Exceeds Normal Limits"].sample(random: rng.instance_variable_get(:@random))
31
+ end
32
+
33
+ def generate_invalid(rng, context, args)
34
+ [nil, 123, [], {}].sample(random: rng.instance_variable_get(:@random))
35
+ end
36
+ end
37
+
38
+ # State type
39
+ class State < Base
40
+ # Generate random state/province name
41
+ #
42
+ # @param rng [Generator::RNG] random number generator (not directly used)
43
+ # @param context [Generator::Context] generation context
44
+ # @param args [Hash] type arguments
45
+ # @option args [Symbol, String] :country country code (:us, :ca, etc.)
46
+ # @return [String] generated state/province name
47
+ #
48
+ def generate_random(rng, context, args)
49
+ country = (args[:country] || :us).to_sym
50
+
51
+ adapter = faker_adapter(context)
52
+ if adapter
53
+ case country
54
+ when :us
55
+ adapter.state
56
+ when :ca
57
+ adapter.state_abbr # Faker uses state_abbr for Canada
58
+
59
+ else
60
+ adapter.state
61
+ end
62
+
63
+ else
64
+ # Fallback to Faker with default locale
65
+ case country
66
+ when :us
67
+ Faker::Address.state
68
+ when :ca
69
+ Faker::Address.state_abbr
70
+ else
71
+ Faker::Address.state
72
+ end
73
+
74
+ end
75
+
76
+ rescue StandardError
77
+ rng.sample(["California", "New York", "Texas", "Florida", "Illinois"])
78
+ end
79
+
80
+
81
+ def generate_edge(rng, context, args)
82
+ ["", "CA", "Very Long State Name That Exceeds Normal Limits"].sample(random: rng.instance_variable_get(:@random))
83
+ end
84
+
85
+ def generate_invalid(rng, context, args)
86
+ [nil, 123, [], {}].sample(random: rng.instance_variable_get(:@random))
87
+ end
88
+ end
89
+
90
+ # Country type
91
+ class Country < Base
92
+ # Generate random country name
93
+ #
94
+ # @param rng [Generator::RNG] random number generator (not directly used)
95
+ # @param context [Generator::Context] generation context
96
+ # @param args [Hash] type arguments (not used)
97
+ # @return [String] generated country name
98
+ #
99
+ def generate_random(rng, context, args)
100
+ adapter = faker_adapter(context)
101
+ if adapter
102
+ adapter.country
103
+
104
+ else
105
+ Faker::Address.country
106
+ end
107
+
108
+ rescue StandardError
109
+ rng.sample(["United States", "Canada", "Mexico", "Germany", "France"])
110
+ end
111
+
112
+
113
+ def generate_edge(rng, context, args)
114
+ ["", "A", "Very Long Country Name That Exceeds Normal Limits"].sample(random: rng.instance_variable_get(:@random))
115
+ end
116
+
117
+ def generate_invalid(rng, context, args)
118
+ [nil, 123, [], {}].sample(random: rng.instance_variable_get(:@random))
119
+ end
120
+ end
121
+
122
+ # CountryCode type
123
+ class CountryCode < Base
124
+ # Fallback country codes if Faker is unavailable
125
+ #
126
+ # ISO 3166-1 alpha-2 country codes used when Faker cannot generate codes.
127
+ #
128
+ # @return [Array<String>] list of 2-letter country codes
129
+ #
130
+ FALLBACK_CODES = %w[
131
+ US GB CA AU DE FR JP CN IN BR
132
+ MX ES IT NL SE NO DK FI CH AT
133
+ BE IE NZ SG KR TW HK MY PH ID
134
+ TH VN PL CZ HU RO BG GR PT EG
135
+ ZA NG KE MA AE SA IL TR UA RU
136
+ ].freeze
137
+
138
+ # Generate a random country code
139
+ #
140
+ # Uses Faker::Address.country_code for realistic ISO 3166-1 alpha-2 codes.
141
+ #
142
+ # @param rng [Generator::RNG] random number generator
143
+ # @param args [Hash] type arguments (not used)
144
+ # @return [String] two-letter country code
145
+ #
146
+ # @example
147
+ # generate_random(rng, {})
148
+ # # => "DE"
149
+ #
150
+ def generate_random(rng, context, args)
151
+ adapter = faker_adapter(context)
152
+ if adapter
153
+ adapter.country_code
154
+
155
+ else
156
+ Faker::Address.country_code
157
+ end
158
+
159
+ rescue StandardError
160
+ rng.sample(FALLBACK_CODES)
161
+ end
162
+
163
+
164
+ def generate_edge(rng, context, args)
165
+ ["", "A", "USA"].sample(random: rng.instance_variable_get(:@random))
166
+ end
167
+
168
+ def generate_invalid(rng, context, args)
169
+ [nil, 123, [], {}].sample(random: rng.instance_variable_get(:@random))
170
+ end
171
+ end
172
+
173
+ # PostalCode type
174
+ class PostalCode < Base
175
+ # Generate a random postal code
176
+ #
177
+ # Uses Faker::Address.zip_code or postcode for realistic values.
178
+ # Falls back to manual generation if needed.
179
+ #
180
+ # @param rng [Generator::RNG] random number generator
181
+ # @param args [Hash] type arguments
182
+ # @option args [String] :country country code for format hint (US, GB, CA, etc.)
183
+ # @return [String] postal code
184
+ #
185
+ # @example
186
+ # generate_random(rng, { country: "US" })
187
+ # # => "90210"
188
+ #
189
+ def generate_random(rng, context, args)
190
+ country = args[:country]
191
+ adapter = faker_adapter(context)
192
+
193
+ # Use Faker with locale switching if country specified
194
+ if country
195
+ result = generate_for_country(country, rng, adapter)
196
+ return result if result && !result.empty?
197
+ end
198
+
199
+ # Fallback to country-specific format if Faker didn't work
200
+ if country
201
+ return generate_country_format(country.to_s.upcase, rng)
202
+ end
203
+
204
+ # Default Faker locale
205
+ if adapter
206
+ adapter.zip_code
207
+
208
+ else
209
+ Faker::Address.zip_code
210
+ end
211
+
212
+ rescue StandardError
213
+ # Fallback to a generic format
214
+ country = args[:country]
215
+ if country
216
+ generate_country_format(country.to_s.upcase, rng)
217
+ else
218
+ rng.sample(["12345", "90210", "SW1A 0AA", "M5V 2L9"])
219
+ end
220
+
221
+ end
222
+
223
+ def generate_edge(rng, context, args)
224
+ ["", "00000", "99999", "A1A 1A1"].sample(random: rng.instance_variable_get(:@random))
225
+ end
226
+
227
+ def generate_invalid(rng, context, args)
228
+ ["invalid", nil, 123, "ABCDE"].sample(random: rng.instance_variable_get(:@random))
229
+ end
230
+
231
+ private
232
+
233
+ # Generates a postal code for a specific country, handling Faker locales.
234
+ def generate_for_country(country, rng, adapter)
235
+ original_locale = Faker::Config.locale
236
+ begin
237
+ Faker::Config.locale = country.to_sym
238
+ # :nocov:
239
+ # The :us then-arm is unreachable to completion in the bundled Faker
240
+ # version: :us is not a valid I18n locale for Faker::Address, so both
241
+ # zip_code calls raise I18n::InvalidLocale before returning, and the
242
+ # rescue below references an undefined constant. (latent-bug, do not
243
+ # "fix" here)
244
+ if Faker::Config.locale == :us
245
+ adapter&.zip_code || Faker::Address.zip_code
246
+ # :nocov:
247
+ else
248
+ adapter&.postcode || Faker::Address.postcode
249
+ end
250
+ # :nocov:
251
+ # Unreachable in the bundled Faker version: an invalid locale raises
252
+ # I18n::InvalidLocale (not Faker::UnresolvedLocaleError, which is not
253
+ # even defined here), so this rescue clause itself would raise NameError
254
+ # and never bind. See coverage-spec note. (latent-bug, do not "fix" here)
255
+ rescue Faker::UnresolvedLocaleError
256
+ # Fallback to default locale if specific locale is not found
257
+ Faker::Config.locale = original_locale
258
+ adapter&.zip_code || Faker::Address.zip_code
259
+ # :nocov:
260
+ ensure
261
+ Faker::Config.locale = original_locale
262
+ end
263
+ end
264
+
265
+ # Generate postal code in country-specific format
266
+ def generate_country_format(country_code, rng)
267
+ case country_code
268
+ when "US"
269
+ "#{rng.int(10000, 99999)}"
270
+ when "DE"
271
+ "#{rng.int(10000, 99999)}"
272
+ when "JP"
273
+ "#{rng.int(100, 999)}-#{rng.int(1000, 9999)}"
274
+ when "AU"
275
+ "#{rng.int(1000, 9999)}"
276
+ when "GB", "UK"
277
+ rng.sample(["SW1A 0AA", "M1 1AA", "B33 8TH", "W1A 0AX"])
278
+ when "CA"
279
+ "#{rng.sample(['A', 'B', 'C', 'E', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'V', 'X', 'Y'])}#{rng.int(0, 9)}#{rng.sample(['A', 'B', 'C', 'E', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z'])} #{rng.int(0, 9)}#{rng.sample(['A', 'B', 'C', 'E', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z'])}#{rng.int(0, 9)}"
280
+ else
281
+ "#{rng.int(10000, 99999)}"
282
+ end
283
+ end
284
+ end
285
+
286
+ # Latitude type
287
+ class Latitude < Base
288
+ # Generate random latitude
289
+ #
290
+ # @param rng [Generator::RNG] random number generator
291
+ # @param context [Generator::Context] generation context
292
+ # @param args [Hash] type arguments
293
+ # @option args [Range] :range latitude range (default: -90..90)
294
+ # @option args [Integer] :precision decimal precision (default: 6)
295
+ # @return [Float] generated latitude
296
+ #
297
+ def generate_random(rng, context, args)
298
+ range = args[:range] || (-90.0..90.0)
299
+ precision = args[:precision] || 6
300
+
301
+ min = [range.min, -90.0].max
302
+ max = [range.max, 90.0].min
303
+
304
+ value = rng.rand * (max - min) + min
305
+ value.round(precision)
306
+ end
307
+
308
+ def generate_edge(rng, context, args)
309
+ [-90.0, 90.0, 0.0].sample(random: rng.instance_variable_get(:@random))
310
+ end
311
+
312
+ def generate_invalid(rng, context, args)
313
+ [nil, "not a number", [], {}].sample(random: rng.instance_variable_get(:@random))
314
+ end
315
+ end
316
+
317
+ # Longitude type
318
+ class Longitude < Base
319
+ # Generate random longitude
320
+ #
321
+ # @param rng [Generator::RNG] random number generator
322
+ # @param context [Generator::Context] generation context
323
+ # @param args [Hash] type arguments
324
+ # @option args [Range] :range longitude range (default: -180..180)
325
+ # @option args [Integer] :precision decimal precision (default: 6)
326
+ # @return [Float] generated longitude
327
+ #
328
+ def generate_random(rng, context, args)
329
+ range = args[:range] || (-180.0..180.0)
330
+ precision = args[:precision] || 6
331
+
332
+ min = [range.min, -180.0].max
333
+ max = [range.max, 180.0].min
334
+
335
+ value = rng.rand * (max - min) + min
336
+ value.round(precision)
337
+ end
338
+
339
+ def generate_edge(rng, context, args)
340
+ [-180.0, 180.0, 0.0].sample(random: rng.instance_variable_get(:@random))
341
+ end
342
+
343
+ def generate_invalid(rng, context, args)
344
+ [nil, "not a number", [], {}].sample(random: rng.instance_variable_get(:@random))
345
+ end
346
+ end
347
+
348
+ # Timezone type
349
+ class Timezone < Base
350
+ # Common IANA timezone identifiers
351
+ #
352
+ # @return [Array<String>] list of timezone identifiers
353
+ #
354
+ TIMEZONES = [
355
+ "America/New_York",
356
+ "America/Chicago",
357
+ "America/Denver",
358
+ "America/Los_Angeles",
359
+ "Europe/London",
360
+ "Europe/Paris",
361
+ "Europe/Berlin",
362
+ "Asia/Tokyo",
363
+ "Asia/Shanghai",
364
+ "Asia/Dubai",
365
+ "Australia/Sydney",
366
+ "Australia/Melbourne",
367
+ "America/Toronto",
368
+ "America/Mexico_City",
369
+ "America/Sao_Paulo",
370
+ "Africa/Johannesburg",
371
+ "Pacific/Auckland"
372
+ ].freeze
373
+
374
+ # Generate random timezone
375
+ #
376
+ # @param rng [Generator::RNG] random number generator
377
+ # @param context [Generator::Context] generation context
378
+ # @param args [Hash] type arguments (not used)
379
+ # @return [String] generated timezone identifier
380
+ #
381
+ def generate_random(rng, context, args)
382
+ # Try Faker first
383
+ adapter = faker_adapter(context)
384
+ if adapter
385
+ adapter.time_zone
386
+
387
+ else
388
+ Faker::Address.time_zone
389
+ end
390
+
391
+ rescue StandardError
392
+ rng.sample(TIMEZONES)
393
+ end
394
+
395
+
396
+ def generate_edge(rng, context, args)
397
+ ["UTC", "GMT", "Z"].sample(random: rng.instance_variable_get(:@random))
398
+ end
399
+
400
+ def generate_invalid(rng, context, args)
401
+ [nil, 123, [], {}].sample(random: rng.instance_variable_get(:@random))
402
+ end
403
+ end
404
+ end
405
+ end
406
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ class Base
6
+ def generate(rng, context, args, mode)
7
+ case mode
8
+ when :random then generate_random(rng, context, args)
9
+ when :edge then generate_edge(rng, context, args)
10
+ when :invalid then generate_invalid(rng, context, args)
11
+ when :hostile then generate_hostile(rng, context, args)
12
+ else generate_random(rng, context, args)
13
+ end
14
+ end
15
+
16
+ def generate_random(rng, context, args)
17
+ raise NotImplementedError, "#{self.class} must implement #generate_random"
18
+ end
19
+
20
+ def generate_edge(rng, context, args)
21
+ generate_random(rng, context, args)
22
+ end
23
+
24
+ def generate_invalid(rng, context, args)
25
+ nil
26
+ end
27
+
28
+ # Generate hostile/malicious data for security testing
29
+ # Override this method in type classes to provide specific attack vectors
30
+ # @param rng [Generator::RNG] random number generator
31
+ # @param context [Generator::Context] generation context
32
+ # @param args [Hash] type arguments
33
+ # @return [Object] hostile data payload
34
+ def generate_hostile(rng, context, args)
35
+ # Default: return edge case value (fallback)
36
+ generate_edge(rng, context, args)
37
+ end
38
+
39
+ protected
40
+
41
+ def faker_adapter(context)
42
+ return nil unless context
43
+
44
+ context.faker_adapter if context.respond_to?(:faker_adapter)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ module CommerceProducts
6
+ # Discount type
7
+ class Discount < Base
8
+ # Generate random discount
9
+ #
10
+ # @param rng [Generator::RNG] random number generator
11
+ # @param context [Generator::Context] generation context
12
+ # @param args [Hash] type arguments
13
+ # @option args [Symbol] :type discount type (:percentage, :amount)
14
+ # @option args [Range] :range discount range (e.g., 0..50 for percentages)
15
+ # @option args [Integer] :min minimum value
16
+ # @option args [Integer, Float] :max maximum value
17
+ # @return [Numeric] generated discount value
18
+ #
19
+ def generate_random(rng, context, args)
20
+ type = (args[:type] || :percentage).to_sym
21
+
22
+ if args[:range].is_a?(Range)
23
+ min = args[:range].min
24
+ max = args[:range].max
25
+ else
26
+ min = args[:min] || 0
27
+ max = args[:max] || (type == :percentage ? 100 : 1000)
28
+ end
29
+
30
+ case type
31
+ when :percentage
32
+ rng.int(min, max)
33
+ when :amount
34
+ (rng.rand * (max - min) + min).round(2)
35
+ else
36
+ rng.int(min, max) # fallback
37
+ end
38
+ end
39
+
40
+ def generate_edge(rng, context, args)
41
+ [0, 100, 0.0, 999.99].sample(random: rng.instance_variable_get(:@random))
42
+ end
43
+
44
+ def generate_invalid(rng, context, args)
45
+ [nil, "abc", -1, [], {}].sample(random: rng.instance_variable_get(:@random))
46
+ end
47
+ end
48
+
49
+ # TaxRate type
50
+ class TaxRate < Base
51
+ # Common tax rates by country (as percentages)
52
+ #
53
+ # Maps country codes to arrays of typical tax rate percentages.
54
+ #
55
+ # @return [Hash<Symbol, Array<Float>>] country code to tax rates mapping
56
+ # @example
57
+ # TAX_RATES[:us] # => [0.0, 4.0, 5.0, 6.0, 7.0, 8.0, 8.5, 9.0, 10.0]
58
+ # TAX_RATES[:uk] # => [0.0, 5.0, 20.0]
59
+ #
60
+ TAX_RATES = {
61
+ us: [0.0, 4.0, 5.0, 6.0, 7.0, 8.0, 8.5, 9.0, 10.0],
62
+ uk: [0.0, 5.0, 20.0],
63
+ ca: [0.0, 5.0, 13.0, 15.0],
64
+ de: [0.0, 7.0, 19.0],
65
+ fr: [0.0, 5.5, 10.0, 20.0],
66
+ general: [0.0, 5.0, 10.0, 15.0, 20.0, 25.0]
67
+ }.freeze
68
+
69
+ # Generate random tax rate
70
+ #
71
+ # @param rng [Generator::RNG] random number generator
72
+ # @param context [Generator::Context] generation context
73
+ # @param args [Hash] type arguments
74
+ # @option args [Symbol, String] :country country code for tax rates
75
+ # @option args [Range] :range tax rate range (e.g., 0..20)
76
+ # @return [Float] generated tax rate percentage
77
+ #
78
+ def generate_random(rng, context, args)
79
+ country = (args[:country] || :general).to_sym
80
+
81
+ if args[:range].is_a?(Range)
82
+ min = args[:range].min
83
+ max = args[:range].max
84
+ value = rng.rand * (max - min) + min
85
+ value.round(2)
86
+ else
87
+ rates = TAX_RATES[country] || TAX_RATES[:general]
88
+ rng.sample(rates)
89
+ end
90
+ end
91
+
92
+ def generate_edge(rng, context, args)
93
+ [0.0, 0.25, 0.50].sample(random: rng.instance_variable_get(:@random))
94
+ end
95
+
96
+ def generate_invalid(rng, context, args)
97
+ [nil, "abc", -0.1, 1.1, [], {}].sample(random: rng.instance_variable_get(:@random))
98
+ end
99
+ end
100
+
101
+ # ShirtSize type
102
+ class ShirtSize < Base
103
+ SIZES = ["XS", "S", "M", "L", "XL", "XXL", "XXXL"].freeze
104
+
105
+ def generate_random(rng, context, args)
106
+ rng.sample(SIZES)
107
+ end
108
+
109
+ def generate_edge(rng, context, args)
110
+ ["XS", "XXXL"].sample(random: rng.instance_variable_get(:@random))
111
+ end
112
+
113
+ def generate_invalid(rng, context, args)
114
+ [nil, 123, [], {}].sample(random: rng.instance_variable_get(:@random))
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ module CommerceProducts
6
+ # CompanyName type
7
+ class CompanyName < Base
8
+ # Generate random company name
9
+ #
10
+ # @param rng [Generator::RNG] random number generator (not directly used)
11
+ # @param context [Generator::Context] generation context
12
+ # @param args [Hash] type arguments (not used)
13
+ # @return [String] generated company name
14
+ #
15
+ def generate_random(rng, context, args)
16
+ adapter = faker_adapter(context)
17
+ if adapter
18
+ adapter.company_name
19
+
20
+ else
21
+ Faker::Company.name
22
+ end
23
+
24
+ rescue StandardError
25
+ rng.sample(["Acme Corp", "Tech Solutions Inc", "Global Industries"])
26
+ end
27
+
28
+
29
+ def generate_edge(rng, context, args)
30
+ ["", "A", "Very Long Company Name That Exceeds Normal Limits"].sample(random: rng.instance_variable_get(:@random))
31
+ end
32
+
33
+ def generate_invalid(rng, context, args)
34
+ [nil, 123, [], {}].sample(random: rng.instance_variable_get(:@random))
35
+ end
36
+ end
37
+
38
+ # JobTitle type
39
+ class JobTitle < Base
40
+ # Generate random job title
41
+ #
42
+ # @param rng [Generator::RNG] random number generator (not directly used)
43
+ # @param context [Generator::Context] generation context
44
+ # @param args [Hash] type arguments (not used)
45
+ # @return [String] generated job title
46
+ #
47
+ def generate_random(rng, context, args)
48
+ adapter = faker_adapter(context)
49
+ if adapter
50
+ adapter.job
51
+
52
+ else
53
+ Faker::Job.title
54
+ end
55
+
56
+ rescue StandardError
57
+ rng.sample(["Software Engineer", "Product Manager", "Sales Representative"])
58
+ end
59
+
60
+
61
+ def generate_edge(rng, context, args)
62
+ ["", "A", "Very Long Job Title That Exceeds Normal Limits"].sample(random: rng.instance_variable_get(:@random))
63
+ end
64
+
65
+ def generate_invalid(rng, context, args)
66
+ [nil, 123, [], {}].sample(random: rng.instance_variable_get(:@random))
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end