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,483 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ module Export
5
+ # Protocol Buffers / gRPC Export
6
+ #
7
+ # Generates .proto files from Synthra schemas for use with gRPC services.
8
+ #
9
+ # @example Generate proto file
10
+ # exporter = Synthra::Export::Protobuf.new(schema, registry: registry)
11
+ # proto = exporter.export
12
+ #
13
+ # @example CLI usage
14
+ # $ synthra export User -d schemas -f protobuf -o user.proto
15
+ #
16
+ class Protobuf
17
+ # Type mapping from Synthra to Protocol Buffers
18
+ TYPE_MAP = {
19
+ # Strings
20
+ "text" => "string",
21
+ "name" => "string",
22
+ "full_name" => "string",
23
+ "first_name" => "string",
24
+ "last_name" => "string",
25
+ "email" => "string",
26
+ "url" => "string",
27
+ "phone" => "string",
28
+ "address" => "string",
29
+ "city" => "string",
30
+ "state" => "string",
31
+ "country" => "string",
32
+ "country_code" => "string",
33
+ "postal_code" => "string",
34
+ "street" => "string",
35
+ "company" => "string",
36
+ "job_title" => "string",
37
+ "username" => "string",
38
+ "password" => "string",
39
+ "slug" => "string",
40
+ "sentence" => "string",
41
+ "paragraph" => "string",
42
+ "domain" => "string",
43
+ "ip" => "string",
44
+ "ipv6" => "string",
45
+ "mac_address" => "string",
46
+ "user_agent" => "string",
47
+ "color" => "string",
48
+ "hex_color" => "string",
49
+ "file_name" => "string",
50
+ "file_path" => "string",
51
+ "mime_type" => "string",
52
+ "credit_card" => "string",
53
+ "iban" => "string",
54
+ "bic" => "string",
55
+ "ssn" => "string",
56
+ "ein" => "string",
57
+ "license_plate" => "string",
58
+ "vin" => "string",
59
+
60
+ # IDs
61
+ "uuid" => "string",
62
+ "ulid" => "string",
63
+ "nanoid" => "string",
64
+
65
+ # Numbers (integers)
66
+ "number" => "int64",
67
+ "integer" => "int64",
68
+ "int" => "int32",
69
+ "int32" => "int32",
70
+ "int64" => "int64",
71
+ "age" => "int32",
72
+ "airport_elevation" => "int64",
73
+ "discount" => "int64",
74
+ "formula" => "int64",
75
+ "row_number" => "int64",
76
+ "sequence" => "int64",
77
+ "id_sequence" => "int64",
78
+
79
+ # Numbers (floats)
80
+ "float" => "double",
81
+ "double" => "double",
82
+ "money" => "double",
83
+ "decimal" => "string", # Decimal as string for precision
84
+ "percentage" => "double",
85
+ "latitude" => "double",
86
+ "longitude" => "double",
87
+ "airport_latitude" => "double",
88
+ "airport_longitude" => "double",
89
+ "product_price" => "double",
90
+ "tax_rate" => "double",
91
+ "transaction_amount" => "double",
92
+
93
+ # Boolean
94
+ "boolean" => "bool",
95
+ "bool" => "bool",
96
+
97
+ # Timestamps
98
+ "timestamp" => "google.protobuf.Timestamp",
99
+ "date" => "string", # ISO date string
100
+ "date_between" => "string",
101
+ "time" => "string", # ISO time string
102
+ "datetime" => "google.protobuf.Timestamp",
103
+ "now" => "string", # Date value rendered as ISO date string
104
+ "past_date" => "string",
105
+ "future_date" => "string",
106
+ "mobile_device_release_date" => "string",
107
+
108
+ # Binary
109
+ "binary" => "bytes",
110
+ "base64" => "bytes",
111
+
112
+ # Complex
113
+ "object" => "google.protobuf.Struct",
114
+ "json" => "string",
115
+ "array" => "repeated"
116
+ }.freeze
117
+
118
+ attr_reader :schema, :registry, :options
119
+
120
+ # Create a new Protobuf exporter
121
+ #
122
+ # @param schema [Schema] the schema to export
123
+ # @param registry [Registry] registry for resolving references
124
+ # @param options [Hash] export options
125
+ # @option options [String] :package package name (default: "generated")
126
+ # @option options [String] :go_package Go package path
127
+ # @option options [Boolean] :with_service generate gRPC service definition
128
+ # @option options [String] :syntax proto syntax version (default: "proto3")
129
+ #
130
+ def initialize(schema, registry: nil, **options)
131
+ @schema = schema
132
+ @registry = registry
133
+ @options = {
134
+ package: "generated",
135
+ syntax: "proto3",
136
+ with_service: true
137
+ }.merge(options)
138
+ end
139
+
140
+ # Export schema to Protocol Buffers format
141
+ #
142
+ # @return [String] .proto file content
143
+ #
144
+ def export
145
+ lines = []
146
+
147
+ # Syntax declaration
148
+ lines << "syntax = \"#{@options[:syntax]}\";"
149
+ lines << ""
150
+
151
+ # Package
152
+ lines << "package #{@options[:package]};"
153
+ lines << ""
154
+
155
+ # Options
156
+ if @options[:go_package]
157
+ lines << "option go_package = \"#{@options[:go_package]}\";"
158
+ lines << ""
159
+ end
160
+
161
+ # Imports
162
+ imports = collect_imports
163
+ if imports.any?
164
+ imports.each { |imp| lines << "import \"#{imp}\";" }
165
+ lines << ""
166
+ end
167
+
168
+ # Enums (extracted from enum fields)
169
+ enums = extract_enums
170
+ enums.each do |enum|
171
+ lines << generate_enum(enum)
172
+ lines << ""
173
+ end
174
+
175
+ # Message definition
176
+ lines << generate_message(@schema)
177
+
178
+ # Generate referenced schemas
179
+ if @registry
180
+ referenced = collect_referenced_schemas
181
+ referenced.each do |ref_schema|
182
+ lines << ""
183
+ lines << generate_message(ref_schema)
184
+ end
185
+ end
186
+
187
+ # gRPC Service definition
188
+ if @options[:with_service]
189
+ lines << ""
190
+ lines << generate_service
191
+ end
192
+
193
+ lines.join("\n")
194
+ end
195
+
196
+ # Export all schemas from registry
197
+ #
198
+ # @param registry [Registry] schema registry
199
+ # @param options [Hash] export options
200
+ # @return [String] combined .proto file
201
+ #
202
+ def self.export_all(registry, **options)
203
+ lines = []
204
+ package = options[:package] || "generated"
205
+
206
+ lines << "syntax = \"proto3\";"
207
+ lines << ""
208
+ lines << "package #{package};"
209
+ lines << ""
210
+
211
+ if options[:go_package]
212
+ lines << "option go_package = \"#{options[:go_package]}\";"
213
+ lines << ""
214
+ end
215
+
216
+ # Collect all imports
217
+ lines << "import \"google/protobuf/timestamp.proto\";"
218
+ lines << "import \"google/protobuf/struct.proto\";"
219
+ lines << ""
220
+
221
+ # All enums first
222
+ all_enums = []
223
+ registry.names.each do |name|
224
+ schema = registry.schema(name)
225
+ schema.fields.each do |field|
226
+ if field.type_name == "enum" && field.type_args[:values]
227
+ enum_name = "#{schema.name}#{field.name.capitalize}"
228
+ all_enums << { name: enum_name, values: field.type_args[:values] }
229
+ end
230
+ end
231
+ end
232
+
233
+ all_enums.uniq { |e| e[:name] }.each do |enum|
234
+ lines << generate_enum_static(enum)
235
+ lines << ""
236
+ end
237
+
238
+ # All messages
239
+ registry.names.sort.each do |name|
240
+ schema = registry.schema(name)
241
+ exporter = new(schema, registry: registry, **options.merge(with_service: false))
242
+ lines << exporter.generate_message(schema)
243
+ lines << ""
244
+ end
245
+
246
+ # Combined service
247
+ if options[:with_service] != false
248
+ lines << generate_combined_service(registry, package)
249
+ end
250
+
251
+ lines.join("\n")
252
+ end
253
+
254
+ def generate_message(schema)
255
+ lines = []
256
+ lines << "message #{schema.name} {"
257
+
258
+ schema.fields.each_with_index do |field, index|
259
+ field_num = index + 1
260
+ proto_type, field_options = field_to_proto(field)
261
+
262
+ optional = field.optional? ? "optional " : ""
263
+ lines << " #{optional}#{proto_type} #{to_snake_case(field.name)} = #{field_num};"
264
+ end
265
+
266
+ lines << "}"
267
+ lines.join("\n")
268
+ end
269
+
270
+ private
271
+
272
+ def collect_imports
273
+ imports = Set.new
274
+
275
+ @schema.fields.each do |field|
276
+ type = field.type_name
277
+ if %w[timestamp datetime now].include?(type)
278
+ imports.add("google/protobuf/timestamp.proto")
279
+ elsif type == "object"
280
+ imports.add("google/protobuf/struct.proto")
281
+ end
282
+ end
283
+
284
+ imports.to_a.sort
285
+ end
286
+
287
+ def extract_enums
288
+ enums = []
289
+ @schema.fields.each do |field|
290
+ if field.type_name == "enum" && field.type_args[:values]
291
+ enum_name = "#{@schema.name}#{field.name.capitalize}"
292
+ enums << { name: enum_name, values: field.type_args[:values], field: field.name }
293
+ end
294
+ end
295
+ enums
296
+ end
297
+
298
+ def generate_enum(enum)
299
+ lines = []
300
+ lines << "enum #{enum[:name]} {"
301
+ lines << " #{enum[:name].upcase}_UNSPECIFIED = 0;"
302
+
303
+ enum[:values].each_with_index do |value, index|
304
+ value_str = value.respond_to?(:value) ? value.value : value.to_s
305
+ enum_value = "#{enum[:name].upcase}_#{value_str.upcase.gsub(/[^A-Z0-9]/, '_')}"
306
+ lines << " #{enum_value} = #{index + 1};"
307
+ end
308
+
309
+ lines << "}"
310
+ lines.join("\n")
311
+ end
312
+
313
+ def self.generate_enum_static(enum)
314
+ lines = []
315
+ lines << "enum #{enum[:name]} {"
316
+ lines << " #{enum[:name].upcase}_UNSPECIFIED = 0;"
317
+
318
+ enum[:values].each_with_index do |value, index|
319
+ value_str = value.respond_to?(:value) ? value.value : value.to_s
320
+ enum_value = "#{enum[:name].upcase}_#{value_str.upcase.gsub(/[^A-Z0-9]/, '_')}"
321
+ lines << " #{enum_value} = #{index + 1};"
322
+ end
323
+
324
+ lines << "}"
325
+ lines.join("\n")
326
+ end
327
+
328
+ def field_to_proto(field)
329
+ type_name = field.type_name
330
+
331
+ # Handle arrays
332
+ if type_name == "array"
333
+ element_type = field.type_args[:element]
334
+ if element_type && @registry&.schema?(element_type.to_s)
335
+ return ["repeated #{element_type}", {}]
336
+ else
337
+ inner_type = TYPE_MAP[element_type.to_s] || "string"
338
+ return ["repeated #{inner_type}", {}]
339
+ end
340
+ end
341
+
342
+ # Handle enums
343
+ if type_name == "enum"
344
+ enum_name = "#{@schema.name}#{field.name.capitalize}"
345
+ return [enum_name, {}]
346
+ end
347
+
348
+ # Handle schema references
349
+ if @registry&.schema?(type_name)
350
+ return [type_name, {}]
351
+ end
352
+
353
+ # Handle const
354
+ if type_name == "const"
355
+ value = field.type_args[:value]
356
+ case value
357
+ when Integer then return ["int64", {}]
358
+ when Float then return ["double", {}]
359
+ when TrueClass, FalseClass then return ["bool", {}]
360
+ else return ["string", {}]
361
+ end
362
+ end
363
+
364
+ # Standard type mapping
365
+ proto_type = TYPE_MAP[type_name] || "string"
366
+ [proto_type, {}]
367
+ end
368
+
369
+ def collect_referenced_schemas
370
+ return [] unless @registry
371
+
372
+ referenced = Set.new
373
+
374
+ @schema.fields.each do |field|
375
+ type_name = field.type_name
376
+
377
+ if @registry.schema?(type_name)
378
+ referenced.add(@registry.schema(type_name))
379
+ end
380
+
381
+ if type_name == "array"
382
+ element = field.type_args[:element]
383
+ if element && @registry.schema?(element.to_s)
384
+ referenced.add(@registry.schema(element.to_s))
385
+ end
386
+ end
387
+ end
388
+
389
+ referenced.to_a
390
+ end
391
+
392
+ def generate_service
393
+ name = @schema.name
394
+ snake_name = to_snake_case(name)
395
+
396
+ <<~SERVICE
397
+ // gRPC Service for #{name}
398
+ service #{name}Service {
399
+ // Get a single #{name} by ID
400
+ rpc Get#{name}(Get#{name}Request) returns (#{name});
401
+
402
+ // List multiple #{name} records
403
+ rpc List#{name}s(List#{name}sRequest) returns (List#{name}sResponse);
404
+
405
+ // Generate fake #{name} data
406
+ rpc Generate#{name}(Generate#{name}Request) returns (#{name});
407
+
408
+ // Generate multiple fake #{name} records
409
+ rpc GenerateMany#{name}s(GenerateMany#{name}sRequest) returns (stream #{name});
410
+ }
411
+
412
+ message Get#{name}Request {
413
+ string id = 1;
414
+ }
415
+
416
+ message List#{name}sRequest {
417
+ int32 page = 1;
418
+ int32 page_size = 2;
419
+ }
420
+
421
+ message List#{name}sResponse {
422
+ repeated #{name} #{snake_name}s = 1;
423
+ int32 total_count = 2;
424
+ int32 page = 3;
425
+ int32 page_size = 4;
426
+ }
427
+
428
+ message Generate#{name}Request {
429
+ optional int64 seed = 1;
430
+ optional string mode = 2; // random, edge, invalid, hostile
431
+ map<string, string> overrides = 3;
432
+ }
433
+
434
+ message GenerateMany#{name}sRequest {
435
+ int32 count = 1;
436
+ optional int64 seed = 2;
437
+ optional string mode = 3;
438
+ }
439
+ SERVICE
440
+ end
441
+
442
+ def self.generate_combined_service(registry, package)
443
+ lines = []
444
+ lines << "// Combined Data Generation Service"
445
+ lines << "service DataGenerationService {"
446
+
447
+ registry.names.sort.each do |name|
448
+ snake_name = name.gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, '')
449
+ lines << " // Generate #{name}"
450
+ lines << " rpc Generate#{name}(Generate#{name}Request) returns (#{name});"
451
+ lines << " rpc GenerateMany#{name}s(GenerateManyRequest) returns (stream #{name});"
452
+ end
453
+
454
+ lines << "}"
455
+ lines << ""
456
+
457
+ # Common request messages
458
+ lines << "message GenerateManyRequest {"
459
+ lines << " int32 count = 1;"
460
+ lines << " optional int64 seed = 2;"
461
+ lines << " optional string mode = 3;"
462
+ lines << "}"
463
+
464
+ registry.names.sort.each do |name|
465
+ lines << ""
466
+ lines << "message Generate#{name}Request {"
467
+ lines << " optional int64 seed = 1;"
468
+ lines << " optional string mode = 2;"
469
+ lines << " map<string, string> overrides = 3;"
470
+ lines << "}"
471
+ end
472
+
473
+ lines.join("\n")
474
+ end
475
+
476
+ def to_snake_case(str)
477
+ str.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
478
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
479
+ .downcase
480
+ end
481
+ end
482
+ end
483
+ end