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,387 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ module DateTime
6
+ # DateType type
7
+ class DateType < Base
8
+ def generate_random(rng, context, args)
9
+ # When a TimeTravel context is active, re-base the "N days back from today"
10
+ # distribution onto the anchor date instead of the real Date.today. Same
11
+ # shape (uniform over the last 2 years), just relative to a different origin.
12
+ anchor = TimeTravel.anchor_date
13
+ return anchor - rng.int(0, 730) if anchor
14
+
15
+ adapter = faker_adapter(context)
16
+ if adapter
17
+ adapter.date_backward(days: 365 * 2)
18
+
19
+ else
20
+ Faker::Date.backward(days: 365 * 2)
21
+ end
22
+
23
+ rescue StandardError
24
+ Date.today - rng.int(0, 730)
25
+ end
26
+
27
+
28
+ def generate_edge(rng, context, args)
29
+ anchor = TimeTravel.anchor_date || Date.today
30
+ [anchor, anchor - 730].sample(random: rng.instance_variable_get(:@random))
31
+ end
32
+
33
+ def generate_invalid(rng, context, args)
34
+ [nil, "not a date", 123, [], {}].sample(random: rng.instance_variable_get(:@random))
35
+ end
36
+ end
37
+
38
+ # DatetimeType type
39
+ class DatetimeType < Base
40
+ # Generate random datetime
41
+ #
42
+ # @param rng [Generator::RNG] random number generator
43
+ # @param context [Generator::Context] generation context
44
+ # @param args [Hash] type arguments
45
+ # @option args [Symbol] :format format type (:standard, :iso8601, :rfc3339)
46
+ # @option args [Range] :date_range date range for generation
47
+ # @return [String] generated datetime string
48
+ #
49
+ def generate_random(rng, context, args)
50
+ format_type = args[:format] || :standard
51
+ reference_date = TimeTravel.anchor_date || Date.today
52
+ date_range = args[:date_range] || (reference_date - 365..reference_date + 365)
53
+
54
+ # Generate date
55
+ date = rng.sample(date_range.to_a)
56
+
57
+ # Generate time
58
+ hour = rng.int(0, 23)
59
+ minute = rng.int(0, 59)
60
+ second = rng.int(0, 59)
61
+
62
+ case format_type
63
+ when :iso8601, :rfc3339
64
+ "#{date}T#{format('%02d:%02d:%02d', hour, minute, second)}Z"
65
+ else # :standard
66
+ "#{date} #{format('%02d:%02d:%02d', hour, minute, second)}"
67
+ end
68
+ end
69
+
70
+ def generate_edge(rng, context, args)
71
+ ["1970-01-01T00:00:00Z", "2099-12-31T23:59:59Z"].sample(random: rng.instance_variable_get(:@random))
72
+ end
73
+
74
+ def generate_invalid(rng, context, args)
75
+ [nil, "not a datetime", 123, [], {}].sample(random: rng.instance_variable_get(:@random))
76
+ end
77
+ end
78
+
79
+ # TimeType type
80
+ class TimeType < Base
81
+ # Generate random time
82
+ #
83
+ # @param rng [Generator::RNG] random number generator
84
+ # @param context [Generator::Context] generation context
85
+ # @param args [Hash] type arguments
86
+ # @option args [Symbol] :format time format (:full, :hour_minute, :hour_only)
87
+ # @return [String] generated time string
88
+ #
89
+ def generate_random(rng, context, args)
90
+ format_type = args[:format] || :full
91
+
92
+ hour = rng.int(0, 23)
93
+ minute = rng.int(0, 59)
94
+ second = rng.int(0, 59)
95
+
96
+ case format_type
97
+ when :hour_only
98
+ format("%02d:00:00", hour)
99
+ when :hour_minute
100
+ format("%02d:%02d:00", hour, minute)
101
+ else # :full
102
+ format("%02d:%02d:%02d", hour, minute, second)
103
+ end
104
+ end
105
+
106
+ def generate_edge(rng, context, args)
107
+ ["00:00:00", "23:59:59", "12:00:00"].sample(random: rng.instance_variable_get(:@random))
108
+ end
109
+
110
+ def generate_invalid(rng, context, args)
111
+ [nil, "not a time", 123, [], {}].sample(random: rng.instance_variable_get(:@random))
112
+ end
113
+ end
114
+
115
+ # Timestamp type
116
+ class Timestamp < Base
117
+ # Generate a random timestamp
118
+ #
119
+ # Generates a Time value in the past (up to 365 days ago).
120
+ #
121
+ # @param rng [Generator::RNG] random number generator
122
+ # @param context [Generator::Context] generation context
123
+ # @param args [Hash] type arguments (not used)
124
+ # @return [Time] time value in the past
125
+ #
126
+ def generate_random(rng, context, args)
127
+ # "timestamp" anchors exactly to the TimeTravel point-in-time (or a random
128
+ # point within it, if a range context is active) rather than a random past
129
+ # offset — the caller asked for data as it existed AT that moment.
130
+ return TimeTravel.current_time.current_time if TimeTravel.current_time
131
+
132
+ adapter = faker_adapter(context)
133
+ if adapter
134
+ adapter.time_backward(days: 365)
135
+
136
+ else
137
+ Faker::Time.backward(days: 365)
138
+ end
139
+
140
+ rescue StandardError
141
+ Time.now - rng.int(0, 365 * 86400) # fallback
142
+ end
143
+
144
+
145
+ def generate_edge(rng, context, args)
146
+ reference = TimeTravel.current_time&.current_time || Time.now
147
+ [Time.at(0), reference].sample(random: rng.instance_variable_get(:@random))
148
+ end
149
+
150
+ def generate_invalid(rng, context, args)
151
+ [nil, "not a timestamp", 123, [], {}].sample(random: rng.instance_variable_get(:@random))
152
+ end
153
+ end
154
+
155
+ # Now type
156
+ class Now < Base
157
+ # Generate a "current" date value
158
+ #
159
+ # Uses a fixed base date (2020-01-01) plus a random offset
160
+ # determined by the RNG for deterministic output.
161
+ #
162
+ # @param rng [Generator::RNG] random number generator
163
+ # @param context [Generator::Context] generation context (not used)
164
+ # @param args [Hash] type arguments (not used)
165
+ # @return [Date] date value representing "now"
166
+ #
167
+ # @example
168
+ # generate_random(rng, context, {})
169
+ # # => Date.new(2020, 1, 15) # Base + random days
170
+ #
171
+ def generate_random(rng, context, args)
172
+ # "now" anchors exactly to the TimeTravel point-in-time when one is set —
173
+ # it represents "the current moment", so under time travel that moment
174
+ # IS the anchor (or a random point within an anchored range).
175
+ if TimeTravel.current_time
176
+ anchored = TimeTravel.current_time.current_time
177
+ return anchored.respond_to?(:to_date) ? anchored.to_date : anchored
178
+ end
179
+
180
+ # Otherwise: use RNG to generate deterministic timestamps.
181
+ # Base time is 2020-01-01, add random offset up to 5 years
182
+ base_time = Date.new(2020, 1, 1)
183
+ days_offset = rng.int(0, 365 * 5)
184
+ base_time + days_offset
185
+ end
186
+
187
+ def generate_edge(rng, context, args)
188
+ if TimeTravel.current_time
189
+ anchored = TimeTravel.current_time.current_time
190
+ return anchored.respond_to?(:to_date) ? anchored.to_date : anchored
191
+ end
192
+
193
+ [Date.new(2020, 1, 1), Date.new(2025, 1, 1)].sample(random: rng.instance_variable_get(:@random))
194
+ end
195
+
196
+ def generate_invalid(rng, context, args)
197
+ [nil, "not a date", 123, [], {}].sample(random: rng.instance_variable_get(:@random))
198
+ end
199
+ end
200
+
201
+ # PastDate type
202
+ class PastDate < Base
203
+ # Generate a random past date
204
+ #
205
+ # Generates a date in the past using the specified duration.
206
+ #
207
+ # @param rng [Generator::RNG] random number generator
208
+ # @param context [Generator::Context] generation context
209
+ # @param args [Hash] type arguments
210
+ # @option args [Integer, Hash] :value duration value
211
+ # @option args [String] :suffix duration unit (d, m, y, h)
212
+ # @return [Date] date in the past
213
+ #
214
+ def generate_random(rng, context, args)
215
+ duration = parse_duration(args)
216
+ anchor = TimeTravel.anchor_date
217
+ return anchor - duration if anchor
218
+
219
+ adapter = faker_adapter(context)
220
+ if adapter
221
+ adapter.date_backward(days: duration)
222
+
223
+ else
224
+ Faker::Date.backward(days: duration)
225
+ end
226
+
227
+ rescue StandardError
228
+ Date.today - duration
229
+ end
230
+
231
+
232
+ def generate_edge(rng, context, args)
233
+ anchor = TimeTravel.anchor_date || Date.today
234
+ [anchor - 1, anchor - 365].sample(random: rng.instance_variable_get(:@random))
235
+ end
236
+
237
+ def generate_invalid(rng, context, args)
238
+ [nil, "not a date", 123, [], {}].sample(random: rng.instance_variable_get(:@random))
239
+ end
240
+
241
+ private
242
+
243
+ def parse_duration(args)
244
+ # Handle nested value hash like {value: {value: 2, suffix: "y"}}
245
+ if args[:value].is_a?(Hash)
246
+ value = args[:value][:value] || 1
247
+ suffix = args[:value][:suffix] || "d"
248
+ else
249
+ value = args[:value] || 365
250
+ suffix = args[:suffix] || "d"
251
+ end
252
+
253
+ case suffix.to_s.downcase
254
+ when "y", "year", "years"
255
+ value * 365
256
+ when "m", "month", "months"
257
+ value * 30
258
+ when "d", "day", "days"
259
+ value
260
+ when "h", "hour", "hours"
261
+ (value / 24.0).ceil
262
+ else
263
+ value
264
+ end
265
+ end
266
+ end
267
+
268
+ # FutureDate type
269
+ class FutureDate < Base
270
+ # Generate a random future date
271
+ #
272
+ # Generates a date in the future using the specified duration.
273
+ #
274
+ # @param rng [Generator::RNG] random number generator
275
+ # @param context [Generator::Context] generation context
276
+ # @param args [Hash] type arguments
277
+ # @option args [Integer, Hash] :value duration value
278
+ # @option args [String] :suffix duration unit (d, m, y)
279
+ # @return [Date] date in the future
280
+ #
281
+ def generate_random(rng, context, args)
282
+ duration = parse_duration(args)
283
+ anchor = TimeTravel.anchor_date
284
+ return anchor + duration if anchor
285
+
286
+ adapter = faker_adapter(context)
287
+ if adapter
288
+ adapter.date_forward(days: duration)
289
+
290
+ else
291
+ Faker::Date.forward(days: duration)
292
+ end
293
+
294
+ rescue StandardError
295
+ Date.today + duration
296
+ end
297
+
298
+
299
+ def generate_edge(rng, context, args)
300
+ anchor = TimeTravel.anchor_date || Date.today
301
+ [anchor + 1, anchor + 365].sample(random: rng.instance_variable_get(:@random))
302
+ end
303
+
304
+ def generate_invalid(rng, context, args)
305
+ [nil, "not a date", 123, [], {}].sample(random: rng.instance_variable_get(:@random))
306
+ end
307
+
308
+ private
309
+
310
+ def parse_duration(args)
311
+ # Handle nested value hash like {value: {value: 7, suffix: "d"}}
312
+ if args[:value].is_a?(Hash)
313
+ value = args[:value][:value] || 1
314
+ suffix = args[:value][:suffix] || "d"
315
+ else
316
+ value = args[:value] || 365
317
+ suffix = args[:suffix] || "d"
318
+ end
319
+
320
+ case suffix.to_s.downcase
321
+ when "y", "year", "years"
322
+ value * 365
323
+ when "m", "month", "months"
324
+ value * 30
325
+ when "d", "day", "days"
326
+ value
327
+ else
328
+ value
329
+ end
330
+ end
331
+ end
332
+
333
+ # DateBetween type
334
+ class DateBetween < Base
335
+ # Generate random date between range
336
+ #
337
+ # @param rng [Generator::RNG] random number generator
338
+ # @param context [Generator::Context] generation context
339
+ # @param args [Hash] type arguments
340
+ # @option args [String, Date] :start start date
341
+ # @option args [String, Date] :from alias for :start
342
+ # @option args [String, Date] :end end date
343
+ # @option args [String, Date] :to alias for :end
344
+ # @return [Date] generated date
345
+ #
346
+ def generate_random(rng, context, args)
347
+ reference_date = TimeTravel.anchor_date || Date.today
348
+ start_date = parse_date(args[:start] || args[:from] || (reference_date - 365))
349
+ end_date = parse_date(args[:end] || args[:to] || reference_date)
350
+
351
+ # Ensure valid range
352
+ start_date, end_date = end_date, start_date if start_date > end_date
353
+
354
+ # Generate random date in range
355
+ days = (end_date - start_date).to_i
356
+ start_date + rng.int(0, days)
357
+ end
358
+
359
+ def generate_edge(rng, context, args)
360
+ anchor = TimeTravel.anchor_date || Date.today
361
+ [anchor - 365, anchor].sample(random: rng.instance_variable_get(:@random))
362
+ end
363
+
364
+ def generate_invalid(rng, context, args)
365
+ [nil, "not a date", 123, [], {}].sample(random: rng.instance_variable_get(:@random))
366
+ end
367
+
368
+ private
369
+
370
+ def parse_date(date)
371
+ case date
372
+ when Date
373
+ date
374
+ when String
375
+ Date.parse(date)
376
+ else
377
+ TimeTravel.anchor_date || Date.today
378
+ end
379
+
380
+ rescue StandardError
381
+ TimeTravel.anchor_date || Date.today
382
+ end
383
+
384
+ end
385
+ end
386
+ end
387
+ end