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,1169 @@
1
+ # frozen_string_literal: true
2
+
3
+ # =============================================================================
4
+
5
+ # Synthra Error Classes
6
+ # =============================================================================
7
+ #
8
+ # This file defines all error classes used throughout Synthra.
9
+ # Errors are organized into categories:
10
+ #
11
+ # 1. **Parsing Errors** - Raised during DSL parsing (syntax, type, indentation)
12
+ # 2. **Generation Errors** - Raised during data generation (uniqueness, references)
13
+ # 3. **Resource Limit Errors** - Raised when configured limits are exceeded
14
+ # 4. **Simulated Errors** - Intentionally raised by behaviors for testing
15
+ #
16
+ # All errors inherit from Synthra::Errors::Error which inherits from StandardError.
17
+ #
18
+ # =============================================================================
19
+
20
+
21
+ module Synthra
22
+
23
+ # Container module for all Synthra error classes
24
+ #
25
+ # This module provides a namespace for error organization. All errors
26
+ # are also aliased at the Synthra module level for convenience.
27
+ #
28
+ # @example Catching any Synthra error
29
+ # begin
30
+ # schema.generate
31
+ # rescue Synthra::Error => e
32
+ # puts "Synthra error: #{e.message}"
33
+ # end
34
+ #
35
+ # @example Catching specific error types
36
+ # begin
37
+ # registry.load_file("schema.dsl")
38
+ # rescue Synthra::ParseError => e
39
+ # puts "Parse error at line #{e.line}: #{e.message}"
40
+ # rescue Synthra::GenerationError => e
41
+ # puts "Generation error: #{e.message}"
42
+ # end
43
+ #
44
+
45
+ module Errors
46
+
47
+ # Base error class for all Synthra errors
48
+ #
49
+ # All Synthra errors inherit from this class, allowing you to
50
+ # catch all gem-related errors with a single rescue clause.
51
+ #
52
+ # @example Catch all Synthra errors
53
+ # begin
54
+ # Synthra.load("schema.dsl").generate
55
+ # rescue Synthra::Errors::Error => e
56
+ # logger.error("Synthra error: #{e.message}")
57
+ # end
58
+ #
59
+
60
+ class Error < StandardError
61
+
62
+ # Machine-readable error code for programmatic error handling
63
+ ERROR_CODE = "SYNTHRA_ERROR"
64
+ end
65
+
66
+ # ============================================================================
67
+
68
+ # Parsing Errors
69
+ # ============================================================================
70
+ #
71
+ # These errors are raised when there are problems parsing DSL files.
72
+ # They include location information (line, column) to help identify
73
+ # the source of the problem.
74
+
75
+
76
+
77
+ # Base class for parsing errors with location information
78
+ #
79
+ # ParseError and its subclasses include line and column numbers
80
+ # to help locate the source of syntax problems in DSL files.
81
+ # The error message is automatically formatted to include this
82
+ # location information.
83
+ #
84
+ # @!attribute [r] line
85
+ # @return [Integer, nil] the line number where the error occurred (1-based)
86
+ #
87
+ # @!attribute [r] column
88
+ # @return [Integer, nil] the column number where the error occurred (1-based)
89
+ #
90
+ # @!attribute [r] source_line
91
+ # @return [String, nil] the content of the source line with the error
92
+ #
93
+ # @!attribute [r] source_context
94
+ # @return [Array<String>, nil] lines before/after for context
95
+ #
96
+ # @!attribute [r] suggestions
97
+ # @return [Array<String>, nil] "did you mean" suggestions
98
+ #
99
+ # @example Error with location
100
+ # begin
101
+ # Synthra.load("bad.dsl")
102
+ # rescue Synthra::ParseError => e
103
+ # puts "Error at line #{e.line}, column #{e.column}"
104
+ # puts e.message
105
+ # # Output:
106
+ # # Error at line 5, column 10
107
+ # #
108
+ # # 3 | User:
109
+ # # 4 | id: uuid
110
+ # # > 5 | name: strng
111
+ # # ^^^^^
112
+ # # 6 | email: email
113
+ # #
114
+ # # Unknown type 'strng'. Did you mean: string, text?
115
+ # end
116
+ #
117
+
118
+ # Raised when a schema references an unknown mixin. Was referenced in mixin.rb but never
119
+ # defined, so the unknown-mixin path raised NameError instead of this clean error.
120
+ class MixinError < Error
121
+ ERROR_CODE = "MIXIN_ERROR"
122
+ end
123
+
124
+ class ParseError < Error
125
+ ERROR_CODE = "PARSE_ERROR"
126
+ attr_reader :line, :column, :source_line, :source_context, :suggestions
127
+
128
+
129
+ # Create a new ParseError with location information
130
+ #
131
+ # @param message [String] the error message describing the problem
132
+ # @param line [Integer, nil] line number where error occurred (1-based)
133
+ # @param column [Integer, nil] column number where error occurred (1-based)
134
+ # @param source_line [String, nil] the source line content for context
135
+ # @param source_context [Array<String>, nil] surrounding lines for context
136
+ # @param suggestions [Array<String>, nil] "did you mean" suggestions
137
+ #
138
+ # @example
139
+ # raise ParseError.new("Unexpected token", line: 5, column: 10, source_line: " bad: ^invalid")
140
+ #
141
+
142
+ def initialize(message, line: nil, column: nil, source_line: nil, source_context: nil, suggestions: nil)
143
+ @line = line
144
+ @column = column
145
+ @source_line = source_line
146
+ @source_context = source_context
147
+ @suggestions = suggestions
148
+
149
+ full_message = build_message(message)
150
+ super(full_message)
151
+ end
152
+
153
+ private
154
+
155
+
156
+ # Build a formatted error message with location context
157
+ #
158
+ # Creates a user-friendly error message that includes:
159
+ # - The base error message
160
+ # - Line and column numbers
161
+ # - Source context with line numbers and pointer
162
+ # - "Did you mean" suggestions
163
+ #
164
+ # @param message [String] the base error message
165
+ # @return [String] the formatted message with location context
166
+ #
167
+
168
+ def build_message(message)
169
+ parts = [message]
170
+ parts << "at line #{line}" if line
171
+ parts << "column #{column}" if column
172
+
173
+ # Add source context if available
174
+ if source_context&.any?
175
+ parts << "\n\n#{format_source_context}"
176
+ elsif source_line
177
+ parts << "\n #{source_line}"
178
+ parts << "\n #{" " * ([column.to_i - 1, 0].max)}^" if column
179
+ end
180
+
181
+ # Add suggestions if available
182
+ if suggestions&.any?
183
+ parts << "\n\n#{Utils::StringDistance.format_suggestions(suggestions)}"
184
+ end
185
+
186
+ parts.join(" ").strip
187
+ end
188
+
189
+
190
+ # Format source context with line numbers
191
+ #
192
+ # @return [String] formatted source context
193
+ #
194
+ # :nocov:
195
+ def format_source_context
196
+ return "" unless source_context&.any?
197
+
198
+ lines = []
199
+ context_start_line = [line.to_i - source_context.length / 2, 1].max
200
+
201
+ source_context.each_with_index do |content, idx|
202
+ current_line = context_start_line + idx
203
+ line_num = current_line.to_s.rjust(5)
204
+
205
+ if current_line == line
206
+ lines << " > #{line_num} | #{content}"
207
+ if column && column > 0
208
+ pointer = " " * (10 + [column - 1, 0].max) + "^"
209
+ lines << pointer
210
+ end
211
+ else
212
+ lines << " #{line_num} | #{content}"
213
+ end
214
+ end
215
+
216
+ lines.join("\n")
217
+ end
218
+ # :nocov:
219
+ end
220
+
221
+
222
+ # Syntax error in DSL file
223
+ #
224
+ # Raised when the parser encounters invalid syntax that doesn't
225
+ # match the expected DSL grammar.
226
+ #
227
+ # @example
228
+ # # Missing colon after field name
229
+ # # User:
230
+ # # name text <- SyntaxError: expected ':'
231
+ #
232
+
233
+ class SyntaxError < ParseError
234
+ ERROR_CODE = "SYNTAX_ERROR"
235
+ end
236
+
237
+
238
+ # Unknown type referenced in DSL
239
+ #
240
+ # Raised when a field uses a type that hasn't been registered.
241
+ # This could be a typo or a custom type that wasn't defined.
242
+ # Includes "did you mean" suggestions for similar type names.
243
+ #
244
+ # @!attribute [r] type_name
245
+ # @return [String] the unknown type name that was referenced
246
+ #
247
+ # @example
248
+ # # User:
249
+ # # name: strng <- UnknownTypeError: Unknown type 'strng'. Did you mean: string, text?
250
+ #
251
+
252
+ class UnknownTypeError < ParseError
253
+ ERROR_CODE = "UNKNOWN_TYPE_ERROR"
254
+ attr_reader :type_name
255
+
256
+
257
+ # Create a new UnknownTypeError
258
+ #
259
+ # @param type_name [String] the type name that wasn't found
260
+ # @param suggestions [Array<String>] similar type names
261
+ # @param kwargs [Hash] location information (line, column, source_line)
262
+ #
263
+
264
+ def initialize(type_name, suggestions: nil, **kwargs)
265
+ @type_name = type_name
266
+ super("Unknown type '#{type_name}'", suggestions: suggestions, **kwargs)
267
+ end
268
+ end
269
+
270
+
271
+ # Invalid path in copy() reference
272
+ #
273
+ # Raised when a copy() type references a path that doesn't exist
274
+ # in the schema structure. Includes "did you mean" suggestions
275
+ # for similar paths.
276
+ #
277
+ # @!attribute [r] path
278
+ # @return [String] the invalid path that was referenced
279
+ #
280
+ # @!attribute [r] available_paths
281
+ # @return [Array<String>] list of valid paths available
282
+ #
283
+ # @example
284
+ # # copy("users.snder.id") <- PathError: Path 'users.snder.id' not found.
285
+ # # Did you mean: users.sender.id?
286
+ #
287
+
288
+ class PathError < ParseError
289
+ ERROR_CODE = "PATH_ERROR"
290
+ attr_reader :path, :available_paths
291
+
292
+
293
+ # Create a new PathError
294
+ #
295
+ # @param path [String] the path that wasn't found
296
+ # @param available_paths [Array<String>] list of valid paths
297
+ # @param suggestions [Array<String>] similar paths
298
+ # @param kwargs [Hash] location information (line, column, source_line)
299
+ #
300
+
301
+ def initialize(path, available_paths: [], suggestions: nil, **kwargs)
302
+ @path = path
303
+ @available_paths = available_paths
304
+ super("Path '#{path}' not found in schema", suggestions: suggestions, **kwargs)
305
+ end
306
+ end
307
+
308
+
309
+ # Circular schema reference detected
310
+ #
311
+ # Raised when schemas reference each other in a cycle that would cause
312
+ # infinite recursion during data generation.
313
+ #
314
+ # @!attribute [r] cycle
315
+ # @return [Array<String>] the schemas involved in the cycle
316
+ #
317
+ # @example Circular reference
318
+ # # Parent:
319
+ # # child: Child
320
+ # # Child:
321
+ # # parent: Parent <- Circular!
322
+ #
323
+
324
+ class CycleError < ParseError
325
+ ERROR_CODE = "CYCLE_ERROR"
326
+ attr_reader :cycle
327
+
328
+
329
+ # Create a new CycleError
330
+ #
331
+ # @param cycle [Array<String>] list of schema names in the cycle
332
+ # @param kwargs [Hash] additional options
333
+ #
334
+
335
+ def initialize(cycle, **kwargs)
336
+ @cycle = cycle
337
+ cycle_path = cycle.join(" -> ")
338
+ super("Circular schema reference detected: #{cycle_path}", **kwargs)
339
+ end
340
+ end
341
+
342
+
343
+ # Field order error for copy() references
344
+ #
345
+ # Raised when a copy() field references another field that is defined
346
+ # after it in the schema. Since fields are generated in definition order,
347
+ # this would result in nil values.
348
+ #
349
+ # @!attribute [r] field_name
350
+ # @return [String] the name of the copy field
351
+ #
352
+ # @!attribute [r] copy_path
353
+ # @return [String] the path being copied
354
+ #
355
+ # @!attribute [r] schema_name
356
+ # @return [String] the schema containing the error
357
+ #
358
+ # @example Field order problem
359
+ # # Order:
360
+ # # user_id: copy("user.id") <- ERROR: user not defined yet!
361
+ # # user: User
362
+ #
363
+
364
+ class FieldOrderError < ParseError
365
+ ERROR_CODE = "FIELD_ORDER_ERROR"
366
+ attr_reader :field_name, :copy_path, :schema_name
367
+
368
+
369
+ # Create a new FieldOrderError
370
+ #
371
+ # @param field_name [String] the copy field name
372
+ # @param copy_path [String] the path being copied
373
+ # @param schema_name [String] the schema name
374
+ # @param suggestion [String] how to fix the error
375
+ # @param kwargs [Hash] additional options
376
+ #
377
+
378
+ def initialize(field_name:, copy_path:, schema_name:, suggestion: nil, **kwargs)
379
+ @field_name = field_name
380
+ @copy_path = copy_path
381
+ @schema_name = schema_name
382
+
383
+ message = "Field order error in '#{schema_name}': " \
384
+ "'#{field_name}' uses copy(\"#{copy_path}\") but the source field is defined after it"
385
+
386
+ super(message, suggestions: suggestion ? [suggestion] : nil, **kwargs)
387
+ end
388
+ end
389
+
390
+
391
+ # Invalid probability specification
392
+ #
393
+ # Raised when enum probabilities exceed 100% total.
394
+ #
395
+ # @!attribute [r] total
396
+ # @return [Numeric] the total probability that exceeded 100%
397
+ #
398
+ # @example
399
+ # # status: enum(active:60%, inactive:60%) <- total 120% > 100%
400
+ #
401
+
402
+ class InvalidProbabilityError < ParseError
403
+ ERROR_CODE = "INVALID_PROBABILITY_ERROR"
404
+ attr_reader :total
405
+
406
+
407
+ # Create a new InvalidProbabilityError
408
+ #
409
+ # @param total [Numeric] the total probability percentage
410
+ # @param kwargs [Hash] location information (line, column, source_line)
411
+ #
412
+
413
+ def initialize(total, **kwargs)
414
+ @total = total
415
+ super("Probability total #{total}% exceeds 100%", **kwargs)
416
+ end
417
+ end
418
+
419
+
420
+ # Indentation error in DSL file
421
+ #
422
+ # Raised when indentation doesn't match the expected pattern.
423
+ # Synthra uses 2-space indentation consistently.
424
+ #
425
+ # @!attribute [r] expected
426
+ # @return [Integer] the expected number of spaces
427
+ #
428
+ # @!attribute [r] actual
429
+ # @return [Integer] the actual number of spaces found
430
+ #
431
+ # @example
432
+ # # User:
433
+ # # name: text <- 3 spaces instead of 2
434
+ #
435
+
436
+ class IndentationError < ParseError
437
+ ERROR_CODE = "INDENTATION_ERROR"
438
+ attr_reader :expected, :actual
439
+
440
+
441
+ # Create a new IndentationError
442
+ #
443
+ # @param expected [Integer] expected number of spaces
444
+ # @param actual [Integer] actual number of spaces found
445
+ # @param kwargs [Hash] location information (line, column, source_line)
446
+ #
447
+
448
+ def initialize(expected:, actual:, **kwargs)
449
+ @expected = expected
450
+ @actual = actual
451
+ super("Invalid indentation: expected #{expected} spaces, got #{actual}", **kwargs)
452
+ end
453
+ end
454
+
455
+ # ============================================================================
456
+
457
+ # Simulyra DSL Validation Errors
458
+ # ============================================================================
459
+ #
460
+ # These errors are raised when validating Simulyra DSL structure.
461
+ # They enforce the strict separation between Schema, API, and Scenario layers.
462
+
463
+
464
+ # Base class for Simulyra DSL validation errors
465
+ #
466
+ # ValidationError provides structured error messages with suggestions
467
+ # for how to fix the problem.
468
+ #
469
+ # @!attribute [r] suggestion
470
+ # @return [String, nil] suggestion for how to fix the error
471
+ #
472
+
473
+ class ValidationError < ParseError
474
+ ERROR_CODE = "VALIDATION_ERROR"
475
+ attr_reader :suggestion
476
+
477
+ # Create a new ValidationError
478
+ #
479
+ # @param message [String] error description
480
+ # @param line [Integer, nil] line number where error occurred
481
+ # @param suggestion [String, nil] suggestion for fixing the error
482
+ #
483
+ def initialize(message, line: nil, suggestion: nil, **kwargs)
484
+ @suggestion = suggestion
485
+ full_message = message
486
+ full_message += " -> #{suggestion}" if suggestion
487
+ super(full_message, line: line, **kwargs)
488
+ end
489
+ end
490
+
491
+
492
+ # Schema-level validation error
493
+ #
494
+ # Raised when there are problems with schema definitions.
495
+ #
496
+ # @example Duplicate schema names
497
+ # # SchemaError: Duplicate schema "User" -> Defined again at line 15
498
+ #
499
+
500
+ class SchemaError < ValidationError
501
+ ERROR_CODE = "SCHEMA_ERROR"
502
+ end
503
+
504
+
505
+ # API-level validation error
506
+ #
507
+ # Raised when there are problems with API definitions.
508
+ #
509
+ # @example API without scenarios
510
+ # # ApiError: API GET /users must define at least one scenario
511
+ #
512
+
513
+ class ApiError < ValidationError
514
+ ERROR_CODE = "API_ERROR"
515
+ end
516
+
517
+
518
+ # Scenario-level validation error
519
+ #
520
+ # Raised when there are problems with scenario definitions.
521
+ #
522
+ # @example Missing @status directive
523
+ # # ScenarioError: Scenario "Success" must define @status
524
+ #
525
+
526
+ class ScenarioError < ValidationError
527
+ ERROR_CODE = "SCENARIO_ERROR"
528
+ end
529
+
530
+
531
+ # Reference resolution error
532
+ #
533
+ # Raised when a schema(:Name) reference cannot be resolved.
534
+ #
535
+ # @example Unknown schema reference
536
+ # # ReferenceError: Schema "Users" is not defined -> Did you mean: User?
537
+ #
538
+
539
+ class ReferenceError < ValidationError
540
+ ERROR_CODE = "REFERENCE_ERROR"
541
+ end
542
+
543
+
544
+ # Context separation error
545
+ #
546
+ # Raised when DSL elements are used in the wrong context.
547
+ #
548
+ # @example HTTP directive in schema
549
+ # # ContextError: HTTP directives are not allowed inside schema definitions
550
+ #
551
+
552
+ class ContextError < ValidationError
553
+ ERROR_CODE = "CONTEXT_ERROR"
554
+ end
555
+
556
+
557
+ # Type-level validation error
558
+ #
559
+ # Raised when type definitions have invalid syntax or unknown types.
560
+ #
561
+ # @example Invalid array syntax
562
+ # # TypeError: Arrays can only be created from schema references
563
+ #
564
+
565
+ class TypeError < ValidationError
566
+ ERROR_CODE = "TYPE_ERROR"
567
+ end
568
+
569
+ # ============================================================================
570
+
571
+ # Generation Errors
572
+ # ============================================================================
573
+ #
574
+ # These errors are raised during data generation when something goes wrong.
575
+ # They indicate problems with schema definitions, references, or constraints.
576
+
577
+
578
+
579
+ # Base class for errors that occur during data generation
580
+ #
581
+ # GenerationError and its subclasses are raised when the schema is valid
582
+ # but something goes wrong during the actual data generation process.
583
+ #
584
+ # @example
585
+ # begin
586
+ # schema.generate(count: 1000)
587
+ # rescue Synthra::GenerationError => e
588
+ # puts "Could not generate data: #{e.message}"
589
+ # end
590
+ #
591
+
592
+ class GenerationError < Error
593
+ ERROR_CODE = "GENERATION_ERROR"
594
+ end
595
+
596
+ # Raised when a DSL feature needs a dangerous opt-in that is disabled by
597
+ # default — e.g. the `formula` type's eval. Default-safe: surfaces a clear
598
+ # error instead of silently executing tenant-supplied Ruby. Inherits from
599
+ # Error (not GenerationError) so a generic `rescue` in a type generator
600
+ # cannot swallow it back into a silent value.
601
+ class SecurityError < Error
602
+ ERROR_CODE = "SECURITY_ERROR"
603
+ end
604
+
605
+
606
+ # Uniqueness constraint could not be satisfied
607
+ #
608
+ # Raised when the generator cannot produce a unique value after
609
+ # multiple attempts. This typically happens when generating many
610
+ # records with unique fields that have limited possible values.
611
+ #
612
+ # @!attribute [r] field
613
+ # @return [String] the field name that requires uniqueness
614
+ #
615
+ # @!attribute [r] attempts
616
+ # @return [Integer] number of attempts made before giving up
617
+ #
618
+ # @!attribute [r] last_values
619
+ # @return [Array] the last few values that were rejected as duplicates
620
+ #
621
+ # @example
622
+ # # When generating 1000 users with unique emails from small domain
623
+ # # UniquenessError: Could not generate unique value for 'email' after 100 attempts
624
+ #
625
+
626
+ class UniquenessError < GenerationError
627
+ ERROR_CODE = "UNIQUENESS_ERROR"
628
+ attr_reader :field, :attempts, :last_values, :schema_name
629
+
630
+
631
+ # Create a new UniquenessError
632
+ #
633
+ # @param field [String] the field name that requires uniqueness
634
+ # @param attempts [Integer] number of generation attempts made
635
+ # @param last_values [Array] the last values attempted (for debugging)
636
+ # @param schema_name [String, nil] the schema name where the error occurred
637
+ #
638
+
639
+ def initialize(field:, attempts: nil, last_values: nil, schema_name: nil)
640
+ @field = field
641
+ @attempts = attempts || Synthra.configuration.max_unique_retries
642
+ @last_values = last_values || []
643
+ @schema_name = schema_name
644
+ message = "Could not generate unique value for '#{field}'"
645
+ message += " in schema '#{schema_name}'" if schema_name
646
+ message += " after #{@attempts} attempts"
647
+ message += ". Last values: #{@last_values.first(5).inspect}" unless @last_values.empty?
648
+ super(message)
649
+ end
650
+ end
651
+
652
+
653
+ # Cyclic reference detected between schemas
654
+ #
655
+ # Raised when schemas reference each other in a cycle, which would
656
+ # cause infinite recursion during generation.
657
+ #
658
+ # @!attribute [r] cycle
659
+ # @return [Array<String>] the schema names forming the cycle
660
+ #
661
+ # @example
662
+ # # User references Order, Order references User
663
+ # # CyclicReferenceError: Cyclic reference detected: User -> Order -> User
664
+ #
665
+
666
+ class CyclicReferenceError < GenerationError
667
+ ERROR_CODE = "CYCLIC_REFERENCE_ERROR"
668
+ attr_reader :cycle
669
+
670
+
671
+ # Create a new CyclicReferenceError
672
+ #
673
+ # @param cycle [Array<String>] the schema names in the reference cycle
674
+ #
675
+
676
+ def initialize(cycle)
677
+ @cycle = cycle
678
+ cycle_str = cycle.join(" -> ")
679
+ super("Cyclic reference detected: #{cycle_str}")
680
+ end
681
+ end
682
+
683
+
684
+ # Referenced schema or field not found
685
+ #
686
+ # Raised when a Ref() references a schema or field that doesn't exist.
687
+ #
688
+ # @!attribute [r] schema_name
689
+ # @return [String] the schema name that was referenced
690
+ #
691
+ # @!attribute [r] field_path
692
+ # @return [String, nil] the field path within the schema (if applicable)
693
+ #
694
+ # @example
695
+ # # user_id: Ref(NonExistent.id)
696
+ # # MissingReferenceError: Reference not found: NonExistent.id
697
+ #
698
+
699
+ class MissingReferenceError < GenerationError
700
+ ERROR_CODE = "MISSING_REFERENCE_ERROR"
701
+ attr_reader :schema_name, :field_path
702
+
703
+
704
+ # Create a new MissingReferenceError
705
+ #
706
+ # @param schema_name [String] the schema name that wasn't found
707
+ # @param field_path [String, nil] the field path if referencing a specific field
708
+ #
709
+
710
+ def initialize(schema_name:, field_path: nil)
711
+ @schema_name = schema_name
712
+ @field_path = field_path
713
+
714
+ message = if field_path
715
+ "Reference not found: #{schema_name}.#{field_path}"
716
+ else
717
+ "Schema not found: #{schema_name}"
718
+ end
719
+ super(message)
720
+ end
721
+ end
722
+
723
+
724
+ # Custom function not registered
725
+ #
726
+ # Raised when a custom() type references a function that hasn't
727
+ # been registered with Synthra.register_function.
728
+ #
729
+ # @!attribute [r] function_name
730
+ # @return [Symbol] the function name that wasn't found
731
+ #
732
+ # @example
733
+ # # full_name: custom(:compute_name)
734
+ # # MissingFunctionError: Custom function not registered: compute_name
735
+ #
736
+
737
+ class MissingFunctionError < GenerationError
738
+ ERROR_CODE = "MISSING_FUNCTION_ERROR"
739
+ attr_reader :function_name
740
+
741
+
742
+ # Create a new MissingFunctionError
743
+ #
744
+ # @param function_name [Symbol, String] the function name that wasn't registered
745
+ #
746
+
747
+ def initialize(function_name)
748
+ @function_name = function_name
749
+ super("Custom function not registered: #{function_name}")
750
+ end
751
+ end
752
+
753
+
754
+ # Error evaluating a conditional field
755
+ #
756
+ # Raised when the condition expression for a conditional field
757
+ # cannot be evaluated, typically due to referencing a non-existent field.
758
+ #
759
+ # @!attribute [r] condition
760
+ # @return [String] the condition expression that failed
761
+ #
762
+ # @!attribute [r] original_error
763
+ # @return [Exception, nil] the underlying error if one occurred
764
+ #
765
+ # @example
766
+ # # admin_note: text if is_admin
767
+ # # ConditionError: Error evaluating condition 'is_admin'
768
+ #
769
+
770
+ class ConditionError < GenerationError
771
+ ERROR_CODE = "CONDITION_ERROR"
772
+ attr_reader :condition, :original_error
773
+
774
+
775
+ # Create a new ConditionError
776
+ #
777
+ # @param condition [String] the condition expression that failed
778
+ # @param original_error [Exception, nil] the underlying error
779
+ #
780
+
781
+ def initialize(condition:, original_error: nil)
782
+ @condition = condition
783
+ @original_error = original_error
784
+
785
+ message = "Error evaluating condition '#{condition}'"
786
+ message += ": #{original_error.message}" if original_error
787
+ super(message)
788
+ end
789
+ end
790
+
791
+ # ============================================================================
792
+
793
+ # Resource Limit Errors
794
+ # ============================================================================
795
+ #
796
+ # These errors are raised when configured resource limits are exceeded.
797
+ # They protect against runaway generation (infinite loops, huge arrays, etc.)
798
+
799
+
800
+
801
+ # Base class for resource limit exceeded errors
802
+ #
803
+ # ResourceLimitError and its subclasses are raised when generation
804
+ # would exceed configured safety limits. These limits prevent accidental
805
+ # resource exhaustion during testing.
806
+ #
807
+ # @example Handling limit errors
808
+ # begin
809
+ # schema.generate
810
+ # rescue Synthra::ResourceLimitError => e
811
+ # puts "Limit exceeded: #{e.message}"
812
+ # puts "Configure limits via Synthra.configure"
813
+ # end
814
+ #
815
+
816
+ class ResourceLimitError < Error
817
+ ERROR_CODE = "RESOURCE_LIMIT_ERROR"
818
+ end
819
+
820
+
821
+ # Latency exceeds configured maximum
822
+ #
823
+ # Raised when the @latency behavior specifies a delay that exceeds
824
+ # the configured max_latency_ms limit.
825
+ #
826
+ # @!attribute [r] requested
827
+ # @return [Integer] the requested latency in milliseconds
828
+ #
829
+ # @!attribute [r] max_allowed
830
+ # @return [Integer] the maximum allowed latency in milliseconds
831
+ #
832
+ # @example
833
+ # # @latency 60000 <- 60 seconds exceeds default 10 second max
834
+ # # LatencyLimitError: Latency 60000ms exceeds maximum allowed 10000ms
835
+ #
836
+
837
+ class LatencyLimitError < ResourceLimitError
838
+ ERROR_CODE = "LATENCY_LIMIT_ERROR"
839
+ attr_reader :requested, :max_allowed
840
+
841
+
842
+ # Create a new LatencyLimitError
843
+ #
844
+ # @param requested [Integer] the requested latency in milliseconds
845
+ # @param max_allowed [Integer] the configured maximum latency
846
+ #
847
+
848
+ def initialize(requested:, max_allowed:)
849
+ @requested = requested
850
+ @max_allowed = max_allowed
851
+ super(
852
+ "Latency #{requested}ms exceeds maximum allowed #{max_allowed}ms. " \
853
+ "Configure via Synthra.configure { |c| c.limits.max_latency_ms = N }"
854
+ )
855
+ end
856
+ end
857
+
858
+
859
+ # Recursion depth exceeds configured maximum
860
+ #
861
+ # Raised when nested schema generation exceeds the maximum recursion
862
+ # depth, which could indicate a circular reference or overly deep nesting.
863
+ #
864
+ # @!attribute [r] depth
865
+ # @return [Integer] the current recursion depth
866
+ #
867
+ # @!attribute [r] max_allowed
868
+ # @return [Integer] the maximum allowed recursion depth
869
+ #
870
+ # @example
871
+ # # Deeply nested: User -> Profile -> Settings -> Preferences -> ...
872
+ # # RecursionLimitError: Recursion depth 11 exceeds maximum allowed 10
873
+ #
874
+
875
+ class RecursionLimitError < ResourceLimitError
876
+ ERROR_CODE = "RECURSION_LIMIT_ERROR"
877
+ attr_reader :depth, :max_allowed
878
+
879
+
880
+ # Create a new RecursionLimitError
881
+ #
882
+ # @param depth [Integer] the current recursion depth
883
+ # @param max_allowed [Integer] the configured maximum depth
884
+ #
885
+
886
+ def initialize(depth:, max_allowed:)
887
+ @depth = depth
888
+ @max_allowed = max_allowed
889
+ super(
890
+ "Recursion depth #{depth} exceeds maximum allowed #{max_allowed}. " \
891
+ "Configure via Synthra.configure { |c| c.limits.max_recursion = N }"
892
+ )
893
+ end
894
+ end
895
+
896
+
897
+ # Array size exceeds configured maximum
898
+ #
899
+ # Raised when generating an array with more elements than the
900
+ # configured max_array_size limit.
901
+ #
902
+ # @!attribute [r] size
903
+ # @return [Integer] the requested array size
904
+ #
905
+ # @!attribute [r] max_allowed
906
+ # @return [Integer] the maximum allowed array size
907
+ #
908
+ # @example
909
+ # # items: array(Item, 10000) <- exceeds default 1000 max
910
+ # # ArraySizeLimitError: Array size 10000 exceeds maximum allowed 1000
911
+ #
912
+
913
+ class ArraySizeLimitError < ResourceLimitError
914
+ ERROR_CODE = "ARRAY_SIZE_LIMIT_ERROR"
915
+ attr_reader :size, :max_allowed
916
+
917
+
918
+ # Create a new ArraySizeLimitError
919
+ #
920
+ # @param size [Integer] the requested array size
921
+ # @param max_allowed [Integer] the configured maximum size
922
+ #
923
+
924
+ def initialize(size:, max_allowed:)
925
+ @size = size
926
+ @max_allowed = max_allowed
927
+ super(
928
+ "Array size #{size} exceeds maximum allowed #{max_allowed}. " \
929
+ "Configure via Synthra.configure { |c| c.limits.max_array_size = N }"
930
+ )
931
+ end
932
+ end
933
+
934
+
935
+ # Text length exceeds configured maximum
936
+ #
937
+ # Raised when generating text with more characters than the
938
+ # configured max_text_length limit.
939
+ #
940
+ # @!attribute [r] length
941
+ # @return [Integer] the requested text length
942
+ #
943
+ # @!attribute [r] max_allowed
944
+ # @return [Integer] the maximum allowed text length
945
+ #
946
+ # @example
947
+ # # body: text(1..50000) <- exceeds default 10000 max
948
+ # # TextLengthLimitError: Text length 50000 exceeds maximum allowed 10000
949
+ #
950
+
951
+ class TextLengthLimitError < ResourceLimitError
952
+ ERROR_CODE = "TEXT_LENGTH_LIMIT_ERROR"
953
+ attr_reader :length, :max_allowed
954
+
955
+
956
+ # Create a new TextLengthLimitError
957
+ #
958
+ # @param length [Integer] the requested text length
959
+ # @param max_allowed [Integer] the maximum allowed text length
960
+ #
961
+
962
+ def initialize(length:, max_allowed:)
963
+ @length = length
964
+ @max_allowed = max_allowed
965
+ super(
966
+ "Text length #{length} exceeds maximum allowed #{max_allowed}. " \
967
+ "Configure via Synthra.configure { |c| c.limits.max_text_length = N }"
968
+ )
969
+ end
970
+ end
971
+
972
+ # ============================================================================
973
+
974
+ # Simulated Errors (Expected behavior for testing)
975
+ # ============================================================================
976
+ #
977
+ # These errors are intentionally raised by behaviors to simulate failure
978
+ # scenarios. They are expected errors, not bugs, and are useful for testing
979
+ # error handling in applications that consume generated data.
980
+
981
+
982
+
983
+ # Base class for simulated behavior errors
984
+ #
985
+ # Simulated errors are raised intentionally by behaviors like @failure,
986
+ # @close_connection, and @simulate_error. They help test how consuming
987
+ # applications handle various failure scenarios.
988
+ #
989
+ # @example Testing error handling
990
+ # begin
991
+ # api_mock.generate # Schema has @failure 10%
992
+ # rescue Synthra::SimulatedBehaviorError => e
993
+ # # This is expected - test that your app handles it correctly
994
+ # expect(app.last_error).to eq("Service unavailable")
995
+ # end
996
+ #
997
+
998
+ class SimulatedBehaviorError < Error
999
+ ERROR_CODE = "SIMULATED_BEHAVIOR_ERROR"
1000
+ end
1001
+
1002
+
1003
+ # Simulated failure (service unavailable)
1004
+ #
1005
+ # Raised by the @failure behavior to simulate a service being unavailable.
1006
+ # Useful for testing retry logic and error handling.
1007
+ #
1008
+ # @!attribute [r] probability
1009
+ # @return [Integer, nil] the configured failure probability percentage
1010
+ #
1011
+ # @example DSL usage
1012
+ # # API:
1013
+ # # @failure 10% <- 10% chance of SimulatedFailure
1014
+ # # id: uuid
1015
+ #
1016
+
1017
+ class SimulatedFailure < SimulatedBehaviorError
1018
+ ERROR_CODE = "SIMULATED_FAILURE"
1019
+ attr_reader :probability
1020
+
1021
+
1022
+ # Create a new SimulatedFailure
1023
+ #
1024
+ # @param probability [Integer, nil] the probability percentage if configured
1025
+ #
1026
+
1027
+ def initialize(probability: nil)
1028
+ @probability = probability
1029
+ message = "Simulated failure"
1030
+ message += " (#{probability}% probability)" if probability
1031
+ super(message)
1032
+ end
1033
+ end
1034
+
1035
+
1036
+ # Simulated connection drop
1037
+ #
1038
+ # Raised by the @close_connection behavior to simulate the connection
1039
+ # being closed unexpectedly. Useful for testing reconnection logic.
1040
+ #
1041
+ # @!attribute [r] probability
1042
+ # @return [Integer, nil] the configured probability percentage
1043
+ #
1044
+ # @example DSL usage
1045
+ # # Stream:
1046
+ # # @close_connection 5% <- 5% chance of SimulatedConnectionDrop
1047
+ # # data: text
1048
+ #
1049
+
1050
+ class SimulatedConnectionDrop < SimulatedBehaviorError
1051
+ ERROR_CODE = "SIMULATED_CONNECTION_DROP"
1052
+ attr_reader :probability
1053
+
1054
+
1055
+ # Create a new SimulatedConnectionDrop
1056
+ #
1057
+ # @param probability [Integer, nil] the probability percentage if configured
1058
+ #
1059
+
1060
+ def initialize(probability: nil)
1061
+ @probability = probability
1062
+ message = "Simulated connection drop"
1063
+ message += " (#{probability}% probability)" if probability
1064
+ super(message)
1065
+ end
1066
+ end
1067
+
1068
+
1069
+ # Simulated HTTP error
1070
+ #
1071
+ # Raised by the @simulate_error behavior to simulate specific HTTP
1072
+ # error responses. Useful for testing how applications handle various
1073
+ # HTTP status codes.
1074
+ #
1075
+ # @!attribute [r] code
1076
+ # @return [Integer] the HTTP status code being simulated
1077
+ #
1078
+ # @!attribute [r] probability
1079
+ # @return [Integer, nil] the configured probability percentage
1080
+ #
1081
+ # @example DSL usage
1082
+ # # API:
1083
+ # # @simulate_error 429:5% <- 5% chance of 429 Too Many Requests
1084
+ # # @simulate_error 500:1% <- 1% chance of 500 Internal Server Error
1085
+ # # id: uuid
1086
+ #
1087
+
1088
+ class SimulatedError < SimulatedBehaviorError
1089
+ ERROR_CODE = "SIMULATED_ERROR"
1090
+ attr_reader :code, :probability
1091
+
1092
+
1093
+ # Create a new SimulatedError
1094
+ #
1095
+ # @param code [Integer] the HTTP status code to simulate
1096
+ # @param probability [Integer, nil] the probability percentage if configured
1097
+ #
1098
+
1099
+ def initialize(code:, probability: nil)
1100
+ @code = code
1101
+ @probability = probability
1102
+ message = "Simulated HTTP error: #{code}"
1103
+ message += " (#{probability}% probability)" if probability
1104
+ super(message)
1105
+ end
1106
+ end
1107
+ end
1108
+
1109
+ # ============================================================================
1110
+
1111
+ # Module-Level Aliases
1112
+ # ============================================================================
1113
+ #
1114
+ # These aliases allow you to reference error classes directly from the
1115
+ # Synthra module without the Errors:: namespace prefix.
1116
+ #
1117
+ # @example Both of these work
1118
+ # rescue Synthra::Errors::ParseError => e
1119
+ # rescue Synthra::ParseError => e # Preferred
1120
+
1121
+
1122
+
1123
+ # @!parse
1124
+ # # Base error class (alias of Errors::Error)
1125
+ # class Error < StandardError; end
1126
+
1127
+ Error = Errors::Error
1128
+
1129
+
1130
+ # Parsing errors
1131
+ ParseError = Errors::ParseError
1132
+ SyntaxError = Errors::SyntaxError
1133
+ UnknownTypeError = Errors::UnknownTypeError
1134
+ PathError = Errors::PathError
1135
+ InvalidProbabilityError = Errors::InvalidProbabilityError
1136
+ IndentationError = Errors::IndentationError
1137
+
1138
+ # Simulyra DSL validation errors
1139
+ ValidationError = Errors::ValidationError
1140
+ SchemaError = Errors::SchemaError
1141
+ ApiError = Errors::ApiError
1142
+ ScenarioError = Errors::ScenarioError
1143
+ ReferenceError = Errors::ReferenceError
1144
+ ContextError = Errors::ContextError
1145
+ # Note: Don't alias TypeError as it conflicts with Ruby's built-in TypeError
1146
+
1147
+ # Generation errors
1148
+ GenerationError = Errors::GenerationError
1149
+ UniquenessError = Errors::UniquenessError
1150
+ CyclicReferenceError = Errors::CyclicReferenceError
1151
+ MissingReferenceError = Errors::MissingReferenceError
1152
+ MissingFunctionError = Errors::MissingFunctionError
1153
+ ConditionError = Errors::ConditionError
1154
+
1155
+
1156
+ # Resource limit errors
1157
+ ResourceLimitError = Errors::ResourceLimitError
1158
+ LatencyLimitError = Errors::LatencyLimitError
1159
+ RecursionLimitError = Errors::RecursionLimitError
1160
+ ArraySizeLimitError = Errors::ArraySizeLimitError
1161
+ TextLengthLimitError = Errors::TextLengthLimitError
1162
+
1163
+
1164
+ # Simulated behavior errors
1165
+ SimulatedBehaviorError = Errors::SimulatedBehaviorError
1166
+ SimulatedFailure = Errors::SimulatedFailure
1167
+ SimulatedConnectionDrop = Errors::SimulatedConnectionDrop
1168
+ SimulatedError = Errors::SimulatedError
1169
+ end