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,248 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ # Regex type generator.
6
+ #
7
+ # Generates a string that MATCHES a supplied regular-expression pattern.
8
+ # The generator is small and dependency-free; it understands the common
9
+ # subset of regex syntax used in fake-data schemas:
10
+ #
11
+ # - character classes: \d \w \s \D \W \S and [a-z] [A-Z0-9] [^abc]
12
+ # - quantifiers: {n} {n,m} {n,} + * ?
13
+ # - literals: any other character is emitted verbatim
14
+ # - anchors: ^ and $ are stripped (treated as match boundaries)
15
+ # - groups: (...) are flattened (their contents are generated)
16
+ # - alternation: a|b picks one branch at random (seeded)
17
+ #
18
+ # Anything it cannot understand degrades gracefully to a reasonable string
19
+ # rather than crashing or returning "".
20
+ class Regex < Base
21
+ DEFAULT_REPEAT_MAX = 8 # cap for unbounded quantifiers (+ and *)
22
+
23
+ def generate_random(rng, context, args)
24
+ pattern = args[:pattern]
25
+ return "" unless pattern
26
+
27
+ source = pattern.is_a?(Regexp) ? pattern.source : pattern.to_s
28
+ return "" if source.empty?
29
+
30
+ build_from_pattern(source, rng)
31
+ rescue StandardError
32
+ # Pattern too complex / malformed — never crash, never return "".
33
+ "generated_string"
34
+ end
35
+
36
+ def generate_edge(rng, context, args)
37
+ rng.sample(["", "0", "a"])
38
+ end
39
+
40
+ def generate_invalid(rng, context, args)
41
+ rng.sample([nil, 123, [], {}])
42
+ end
43
+
44
+ private
45
+
46
+ # Generate a string matching +source+.
47
+ #
48
+ # @param source [String] regex source (no surrounding slashes)
49
+ # @param rng [Generator::RNG] seeded RNG
50
+ # @return [String]
51
+ def build_from_pattern(source, rng)
52
+ source = strip_anchors(source)
53
+ source = pick_alternation(source, rng)
54
+ tokens = tokenize(source)
55
+ tokens.map { |tok| render_token(tok, rng) }.join
56
+ end
57
+
58
+ # Strip leading ^ and trailing $ anchors (they constrain position, not content).
59
+ def strip_anchors(source)
60
+ source.sub(/\A\^/, "").sub(/\$\z/, "")
61
+ end
62
+
63
+ # If the (top-level) pattern uses alternation, pick one branch deterministically.
64
+ # Only splits on bars that are not inside a group or char class.
65
+ def pick_alternation(source, rng)
66
+ branches = split_top_level_alternation(source)
67
+ return source if branches.length <= 1
68
+
69
+ rng.sample(branches)
70
+ end
71
+
72
+ def split_top_level_alternation(source)
73
+ branches = []
74
+ current = +""
75
+ depth = 0
76
+ in_class = false
77
+ i = 0
78
+ while i < source.length
79
+ ch = source[i]
80
+ case ch
81
+ when "\\"
82
+ current << ch << (source[i + 1] || "")
83
+ i += 2
84
+ next
85
+ when "["
86
+ in_class = true
87
+ current << ch
88
+ when "]"
89
+ in_class = false
90
+ current << ch
91
+ when "("
92
+ depth += 1 unless in_class
93
+ current << ch
94
+ when ")"
95
+ depth -= 1 unless in_class
96
+ current << ch
97
+ when "|"
98
+ if depth.zero? && !in_class
99
+ branches << current
100
+ current = +""
101
+ else
102
+ current << ch
103
+ end
104
+ else
105
+ current << ch
106
+ end
107
+ i += 1
108
+ end
109
+ branches << current
110
+ branches
111
+ end
112
+
113
+ # Break the pattern into (atom, quantifier) tokens.
114
+ #
115
+ # @return [Array<Hash>] each token: { atom:, min:, max: }
116
+ def tokenize(source)
117
+ tokens = []
118
+ i = 0
119
+ while i < source.length
120
+ atom, i = read_atom(source, i)
121
+ min, max, i = read_quantifier(source, i)
122
+ tokens << { atom: atom, min: min, max: max }
123
+ end
124
+ tokens
125
+ end
126
+
127
+ # Read a single atom starting at +i+. Returns [atom_descriptor, next_index].
128
+ # atom_descriptor is one of:
129
+ # { kind: :class, chars: [...] } - a character set to pick from
130
+ # { kind: :literal, char: "x" } - a literal character
131
+ def read_atom(source, i)
132
+ ch = source[i]
133
+ case ch
134
+ when "\\"
135
+ esc = source[i + 1]
136
+ [{ kind: :class, chars: escape_class(esc) }, i + 2]
137
+ when "["
138
+ chars, next_i = read_char_class(source, i + 1)
139
+ [{ kind: :class, chars: chars }, next_i]
140
+ when "."
141
+ [{ kind: :class, chars: printable_chars }, i + 1]
142
+ when "(", ")"
143
+ # Flatten groups: skip the paren, the contents are read as normal atoms.
144
+ [{ kind: :empty }, i + 1]
145
+ else
146
+ [{ kind: :literal, char: ch }, i + 1]
147
+ end
148
+ end
149
+
150
+ # Expand an escaped class character (\d \w \s and negations) to a char list.
151
+ def escape_class(esc)
152
+ case esc
153
+ when "d" then ("0".."9").to_a
154
+ when "w" then ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + ["_"]
155
+ when "s" then [" ", "\t"]
156
+ when "D" then ("a".."z").to_a + ("A".."Z").to_a
157
+ when "W" then [" ", "-", ".", "!"]
158
+ when "S" then ("a".."z").to_a + ("0".."9").to_a
159
+ else [esc] # escaped literal, e.g. \. \- \/
160
+ end
161
+ end
162
+
163
+ # Read a [...] character class starting after the opening bracket.
164
+ # Returns [chars, next_index].
165
+ def read_char_class(source, i)
166
+ negate = false
167
+ if source[i] == "^"
168
+ negate = true
169
+ i += 1
170
+ end
171
+
172
+ chars = []
173
+ while i < source.length && source[i] != "]"
174
+ if source[i] == "\\"
175
+ chars.concat(escape_class(source[i + 1]))
176
+ i += 2
177
+ next
178
+ end
179
+
180
+ if source[i + 1] == "-" && source[i + 2] && source[i + 2] != "]"
181
+ chars.concat((source[i]..source[i + 2]).to_a)
182
+ i += 3
183
+ else
184
+ chars << source[i]
185
+ i += 1
186
+ end
187
+ end
188
+ i += 1 # consume closing ]
189
+
190
+ chars = printable_chars - chars if negate
191
+ [chars, i]
192
+ end
193
+
194
+ # Read a quantifier at +i+. Returns [min, max, next_index].
195
+ # No quantifier => [1, 1, i].
196
+ def read_quantifier(source, i)
197
+ case source[i]
198
+ when "+" then [1, DEFAULT_REPEAT_MAX, i + 1]
199
+ when "*" then [0, DEFAULT_REPEAT_MAX, i + 1]
200
+ when "?" then [0, 1, i + 1]
201
+ when "{" then read_brace_quantifier(source, i)
202
+ else [1, 1, i]
203
+ end
204
+ end
205
+
206
+ def read_brace_quantifier(source, i)
207
+ close = source.index("}", i)
208
+ return [1, 1, i] unless close
209
+
210
+ body = source[(i + 1)...close]
211
+ if body.include?(",")
212
+ lo, hi = body.split(",", 2)
213
+ min = lo.to_i
214
+ max = hi.empty? ? min + DEFAULT_REPEAT_MAX : hi.to_i
215
+ else
216
+ min = max = body.to_i
217
+ end
218
+ [min, max, close + 1]
219
+ end
220
+
221
+ def render_token(token, rng)
222
+ atom = token[:atom]
223
+ return "" if atom[:kind] == :empty
224
+
225
+ count = token[:min] == token[:max] ? token[:min] : rng.int(token[:min], token[:max])
226
+ Array.new(count) { render_atom(atom, rng) }.join
227
+ end
228
+
229
+ def render_atom(atom, rng)
230
+ case atom[:kind]
231
+ when :class
232
+ chars = atom[:chars]
233
+ chars.empty? ? "" : rng.sample(chars)
234
+ when :literal
235
+ atom[:char]
236
+ else
237
+ # :nocov: render_token short-circuits :empty atoms, so this arm is a defensive fallback only
238
+ ""
239
+ # :nocov:
240
+ end
241
+ end
242
+
243
+ def printable_chars
244
+ @printable_chars ||= ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ # =============================================================================
4
+ # Synthra Types Registry
5
+ # =============================================================================
6
+ #
7
+ # The Types::Registry manages registration and lookup of type generator classes.
8
+ # Built-in types are registered automatically at gem load time.
9
+ #
10
+ # @example Register a custom type
11
+ # Synthra::Types::Registry.register("my_type", MyTypeClass)
12
+ #
13
+ # @example Register with a block
14
+ # Synthra::Types::Registry.register("my_type") do |rng, context, args, mode|
15
+ # "custom_value"
16
+ # end
17
+ #
18
+ # =============================================================================
19
+
20
+ module Synthra
21
+ module Types
22
+ # Registry for type generator classes
23
+ #
24
+ # Thread-safe registry for storing and retrieving type implementations.
25
+ # Supports both class-based and block-based type registration.
26
+ #
27
+ # @example Look up a type
28
+ # type = Registry.lookup("uuid")
29
+ # value = type.generate(rng, context, {}, :random)
30
+ #
31
+ class Registry
32
+ # Internal storage for registered types
33
+ # @api private
34
+ @types = {}
35
+
36
+ # Mutex for thread-safe access
37
+ # @api private
38
+ @mutex = Mutex.new
39
+
40
+ class << self
41
+ # Register a type generator
42
+ #
43
+ # @param name [String] type name (used in DSL)
44
+ # @param klass [Class, nil] type class (must inherit from Base)
45
+ # @param force [Boolean] if true, overwrites existing type
46
+ # @yield [rng, context, args, mode] block for generating values
47
+ # @yieldparam rng [Generator::RNG] random number generator
48
+ # @yieldparam context [Generator::Context] generation context
49
+ # @yieldparam args [Hash] type arguments
50
+ # @yieldparam mode [Symbol] generation mode (:random, :edge, :invalid)
51
+ # @yieldreturn [Object] generated value
52
+ # @return [void]
53
+ # @raise [ArgumentError] if type exists and force is false
54
+ #
55
+ # @example Register with a class
56
+ # Registry.register("uuid", Types::Uuid)
57
+ #
58
+ # @example Register with a block
59
+ # Registry.register("ssn") do |rng, context, args, mode|
60
+ # "#{rng.int(100, 999)}-#{rng.int(10, 99)}-#{rng.int(1000, 9999)}"
61
+ # end
62
+ #
63
+ def register(name, klass = nil, force: false, &block)
64
+ @mutex.synchronize do
65
+ key = name.to_s
66
+ if @types.key?(key) && !force
67
+ raise ArgumentError, "Type '#{name}' already registered. Use force: true to override."
68
+ end
69
+
70
+ if block
71
+ # Create a wrapper class for block-based types
72
+ wrapper = Class.new(Base) do
73
+ define_method(:generate_random) do |rng, context, args|
74
+ block.call(rng, context, args, :random)
75
+ end
76
+
77
+ define_method(:generate_edge) do |rng, context, args|
78
+ block.call(rng, context, args, :edge)
79
+
80
+ rescue StandardError
81
+ block.call(rng, context, args, :random)
82
+ end
83
+
84
+
85
+ define_method(:generate_invalid) do |rng, context, args|
86
+ block.call(rng, context, args, :invalid)
87
+
88
+ rescue StandardError
89
+ nil
90
+ end
91
+
92
+ end
93
+ @types[key] = wrapper.new
94
+ elsif klass
95
+ @types[key] = klass.new
96
+ else
97
+ raise ArgumentError, "Must provide either a class or a block"
98
+ end
99
+ end
100
+ end
101
+
102
+ # Look up a registered type
103
+ #
104
+ # @param name [String] type name (may include ? for nullable)
105
+ # @return [Base] type generator instance
106
+ # @raise [UnknownTypeError] if type not found (includes "did you mean" suggestions)
107
+ #
108
+ # @example
109
+ # type = Registry.lookup("uuid")
110
+ # value = type.generate(rng, context, {}, :random)
111
+ #
112
+ def lookup(name)
113
+ @mutex.synchronize do
114
+ # Strip nullable suffix if present
115
+ key = name.to_s.sub(/\?$/, "")
116
+ @types.fetch(key) do
117
+ # Find similar type names for "did you mean" suggestions
118
+ suggestions = find_similar_types(key)
119
+ raise Errors::UnknownTypeError.new(name, suggestions: suggestions)
120
+ end
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ # Find type names similar to the given name
127
+ #
128
+ # @param name [String] the type name to match
129
+ # @return [Array<String>] similar type names
130
+ #
131
+ def find_similar_types(name)
132
+ Utils::StringDistance.similar_strings(name, @types.keys, max_distance: 3, limit: 3)
133
+ end
134
+
135
+ public
136
+
137
+ # Check if a type is registered
138
+ #
139
+ # @param name [String] type name
140
+ # @return [Boolean] true if registered
141
+ #
142
+ def registered?(name)
143
+ @mutex.synchronize do
144
+ key = name.to_s.sub(/\?$/, "")
145
+ @types.key?(key)
146
+ end
147
+ end
148
+
149
+ # Get all registered type names
150
+ #
151
+ # @return [Array<String>] list of type names
152
+ #
153
+ def names
154
+ @mutex.synchronize do
155
+ @types.keys.sort
156
+ end
157
+ end
158
+
159
+ # Clear all registered types
160
+ #
161
+ # @return [void]
162
+ #
163
+ def clear
164
+ @mutex.synchronize do
165
+ @types.clear
166
+ end
167
+ end
168
+
169
+ # Get the number of registered types
170
+ #
171
+ # @return [Integer] number of types
172
+ #
173
+ def size
174
+ @mutex.synchronize do
175
+ @types.size
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ # Repeating element type generator (stub implementation)
6
+ class RepeatingElement < Base
7
+ def generate_random(rng, context, args)
8
+ value = args[:value]
9
+ count = clamp_count(args[:count] || 1)
10
+ type_name = args[:type]
11
+
12
+ if type_name
13
+ generate_from_type(rng, context, type_name, count)
14
+ elsif value
15
+ value.to_s * count
16
+ else
17
+ ""
18
+ end
19
+ end
20
+
21
+ def generate_edge(rng, context, args)
22
+ rng.sample(["", "-", "---"])
23
+ end
24
+
25
+ def generate_invalid(rng, context, args)
26
+ rng.sample([nil, 123, [], {}])
27
+ end
28
+
29
+ private
30
+
31
+ # Bound the repeat count to the same ceiling as the other array-producing
32
+ # types (schema_ref, array). Without this, a tenant-supplied `count:` allocates
33
+ # an unbounded Array/String — a denial-of-service vector. Floors at 0.
34
+ def clamp_count(count)
35
+ max = Synthra.configuration&.limits&.max_array_size || 1000
36
+ [[count.to_i, 0].max, max].min
37
+ end
38
+
39
+ def generate_from_type(rng, context, type_name, count)
40
+ type = Types::Registry.lookup(type_name)
41
+ Array.new(count) { type.generate_random(rng, context, {}) }
42
+
43
+ rescue StandardError
44
+ []
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ # Scenario type generator (stub implementation)
6
+ class Scenario < Base
7
+ SCENARIOS = {
8
+ user_roles: ["admin", "user", "guest"],
9
+ status: ["active", "inactive", "pending"],
10
+ default: ["scenario1", "scenario2", "scenario3"]
11
+ }.freeze
12
+
13
+ def generate_random(rng, context, args)
14
+ name = (args[:name] || :default).to_sym
15
+ scenarios = SCENARIOS[name] || SCENARIOS[:default]
16
+ rng.sample(scenarios)
17
+ end
18
+
19
+ def generate_edge(rng, context, args)
20
+ rng.sample(["", "edge", "scenario"])
21
+ end
22
+
23
+ def generate_invalid(rng, context, args)
24
+ rng.sample([nil, 123, [], {}])
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Types
5
+ module TechnologyInternet
6
+ # AppBundleId type
7
+ class AppBundleId < Base
8
+ def generate_random(rng, context, args)
9
+ # Format: com.company.appname
10
+ companies = ["company", "corp", "inc", "llc", "tech", "app", "mobile", "digital"]
11
+ apps = ["app", "application", "mobile", "ios", "android", "game", "tool", "utility"]
12
+ company = rng.sample(companies)
13
+ app = rng.sample(apps)
14
+ number = rng.int(1, 999)
15
+ "com.#{company}.#{app}#{number}"
16
+ end
17
+
18
+ def generate_edge(rng, context, args)
19
+ ["", "com.a.b", "com.verylongcompanyname.verylongappname"].sample(random: rng.instance_variable_get(:@random))
20
+ end
21
+
22
+ def generate_invalid(rng, context, args)
23
+ [nil, 123, [], {}].sample(random: rng.instance_variable_get(:@random))
24
+ end
25
+ end
26
+
27
+ # AppName type
28
+ class AppName < Base
29
+ APP_NAMES = [
30
+ "MyApp", "SuperApp", "AwesomeApp", "BestApp", "CoolApp",
31
+ "QuickApp", "SmartApp", "FastApp", "EasyApp", "ProApp"
32
+ ].freeze
33
+
34
+ def generate_random(rng, context, args)
35
+ rng.sample(APP_NAMES)
36
+ end
37
+
38
+ def generate_edge(rng, context, args)
39
+ ["", "MyApp", "A"].sample(random: rng.instance_variable_get(:@random))
40
+ end
41
+
42
+ def generate_invalid(rng, context, args)
43
+ [nil, 123, [], {}].sample(random: rng.instance_variable_get(:@random))
44
+ end
45
+ end
46
+
47
+ # AppVersion type
48
+ class AppVersion < Base
49
+ def generate_random(rng, context, args)
50
+ # Semantic versioning: major.minor.patch
51
+ major = rng.int(0, 10)
52
+ minor = rng.int(0, 99)
53
+ patch = rng.int(0, 999)
54
+ "#{major}.#{minor}.#{patch}"
55
+ end
56
+
57
+ def generate_edge(rng, context, args)
58
+ ["0.0.0", "10.99.999", "1.0.0"].sample(random: rng.instance_variable_get(:@random))
59
+ end
60
+
61
+ def generate_invalid(rng, context, args)
62
+ [nil, "not a version", "invalid", [], {}].sample(random: rng.instance_variable_get(:@random))
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end