hecks 3.13.0 → 4.0.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 (440) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/adapters/fake_processor.rb +34 -0
  3. data/examples/pizzas/adapters/payment_signature.rb +66 -0
  4. data/examples/pizzas/adapters/payment_webhook.rb +87 -0
  5. data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
  6. data/examples/pizzas/bluebook/pizzas.bluebook +4 -4
  7. data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
  8. data/examples/pizzas/config.ru +22 -0
  9. data/lib/hecks/adapters/driven/prism.rb +0 -2
  10. data/lib/hecks/adapters/driven.rb +0 -2
  11. data/lib/hecks/adapters/driving/json.rb +1 -7
  12. data/lib/hecks/bluebook/dsl/attribute_collector.rb +15 -5
  13. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
  14. data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
  15. data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
  16. data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
  17. data/lib/hecks/bluebook/hecksagon.rb +1 -1
  18. data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
  19. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
  20. data/lib/hecks/bluebook/meta_validator.rb +0 -11
  21. data/lib/hecks/bluebook/named_pattern.rb +33 -0
  22. data/lib/hecks/bluebook/translation.rb +18 -10
  23. data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
  24. data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
  25. data/lib/hecks/bluebook/translation_expression.rb +39 -0
  26. data/lib/hecks/bluebook/translation_plan.rb +70 -0
  27. data/lib/hecks/bluebook.rb +3 -0
  28. data/lib/hecks/chapters.rb +3 -3
  29. data/lib/hecks/cli/domain_stub.rb +1 -1
  30. data/lib/hecks/cli/interview_aggregate.rb +4 -4
  31. data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
  32. data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
  33. data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
  34. data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
  35. data/lib/hecks/composer/adapters/jev.adapter +3 -0
  36. data/lib/hecks/composer/adapters/jev.rb +66 -0
  37. data/lib/hecks/composer/agent.rb +113 -0
  38. data/lib/hecks/composer/assistant.rb +110 -0
  39. data/lib/hecks/composer/behavior_run.rb +53 -0
  40. data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
  41. data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
  42. data/lib/hecks/composer/board.rb +65 -0
  43. data/lib/hecks/composer/board_edit/command_text.rb +39 -0
  44. data/lib/hecks/composer/board_edit/source.rb +84 -0
  45. data/lib/hecks/composer/board_edit.rb +144 -0
  46. data/lib/hecks/composer/check.rb +89 -0
  47. data/lib/hecks/composer/domains.rb +81 -0
  48. data/lib/hecks/composer/jobs.rb +134 -0
  49. data/lib/hecks/composer/layout.rb +37 -0
  50. data/lib/hecks/composer/live.rb +150 -0
  51. data/lib/hecks/composer/outline.rb +48 -0
  52. data/lib/hecks/composer/page.html +989 -0
  53. data/lib/hecks/composer/panels.rb +142 -0
  54. data/lib/hecks/composer/parse.rb +83 -0
  55. data/lib/hecks/composer/plain.rb +28 -0
  56. data/lib/hecks/composer/ports/composer_agent.port +7 -0
  57. data/lib/hecks/composer/session.rb +141 -0
  58. data/lib/hecks/composer/toolbox.rb +166 -0
  59. data/lib/hecks/composer/web.rb +156 -0
  60. data/lib/hecks/composer/workbench.rb +51 -0
  61. data/lib/hecks/composer/workspace.rb +81 -0
  62. data/lib/hecks/composer.rb +17 -0
  63. data/lib/hecks/corpus/adapters.rb +22 -0
  64. data/lib/hecks/corpus/rust.rb +1 -1
  65. data/lib/hecks/corpus/worlds.rb +19 -0
  66. data/lib/hecks/corpus.rb +5 -0
  67. data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -34
  68. data/lib/hecks/forms/app/commands.rb +2 -3
  69. data/lib/hecks/forms/app/queries.rb +2 -3
  70. data/lib/hecks/forms/app/records.rb +17 -5
  71. data/lib/hecks/forms/command_form_renderer.rb +42 -16
  72. data/lib/hecks/forms/field_controls.rb +2 -2
  73. data/lib/hecks/forms/field_renderer.rb +1 -1
  74. data/lib/hecks/forms/field_shape.rb +36 -17
  75. data/lib/hecks/forms/index_renderer.rb +2 -1
  76. data/lib/hecks/forms/page.rb +1 -1
  77. data/lib/hecks/forms/params.rb +0 -13
  78. data/lib/hecks/forms/query_form_renderer.rb +7 -6
  79. data/lib/hecks/forms/record_renderer.rb +11 -7
  80. data/lib/hecks/forms/rule_words.rb +100 -0
  81. data/lib/hecks/forms/value_object_shape.rb +2 -1
  82. data/lib/hecks/framework/bluebook/change.bluebook +2 -2
  83. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
  84. data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
  85. data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
  86. data/lib/hecks/fuzzing/replay/filters.rb +2 -6
  87. data/lib/hecks/gate/stages.yml +25 -0
  88. data/lib/hecks/grammar/evolve/seeds.rb +0 -6
  89. data/lib/hecks/grammar/evolve.rb +0 -3
  90. data/lib/hecks/grammar/translation.bluebook +1 -1
  91. data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
  92. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
  93. data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
  94. data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
  95. data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
  96. data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
  97. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
  98. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
  99. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
  100. data/lib/hecks/hecks/codebase.behaviors +6 -0
  101. data/lib/hecks/hecks/codebase.bluebook +58 -37
  102. data/lib/hecks/hecks/composer.behaviors +68 -0
  103. data/lib/hecks/hecks/context_map.hecksagon +1 -1
  104. data/lib/hecks/hecks/custodian.behaviors +0 -6
  105. data/lib/hecks/hecks/custodian.bluebook +15 -92
  106. data/lib/hecks/hecks/hecks.bluebook +2 -2
  107. data/lib/hecks/hecks/hecks.hecksagon +2 -2
  108. data/lib/hecks/hecks/hecks.world +1 -1
  109. data/lib/hecks/hecks/rehearsal.bluebook +10 -16
  110. data/lib/hecks/hecks/tooling.bluebook +22 -22
  111. data/lib/hecks/kernel_capabilities/templates.rb +4 -4
  112. data/lib/hecks/kernel_capabilities.rb +1 -1
  113. data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
  114. data/lib/hecks/language/bluebook/command.bluebook +1 -0
  115. data/lib/hecks/language/bluebook/entity.bluebook +1 -0
  116. data/lib/hecks/language/bluebook/query.bluebook +1 -0
  117. data/lib/hecks/language/bluebook/shape.bluebook +8 -7
  118. data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
  119. data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
  120. data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
  121. data/lib/hecks/ports/access_control.rb +2 -2
  122. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
  123. data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
  124. data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
  125. data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
  126. data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
  127. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
  128. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
  129. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
  130. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
  131. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
  132. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
  133. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
  134. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
  135. data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
  136. data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
  137. data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
  138. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
  139. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
  140. data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
  141. data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
  142. data/lib/hecks/ports.rb +0 -1
  143. data/lib/hecks/projection_files.rb +3 -1
  144. data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
  145. data/lib/hecks/projections/glossary/entries.rb +10 -4
  146. data/lib/hecks/projections/glossary/groups.rb +42 -0
  147. data/lib/hecks/projections/glossary/html/page.rb +7 -2
  148. data/lib/hecks/projections/glossary/html/parser.rb +1 -1
  149. data/lib/hecks/projections/glossary/html/sections.rb +31 -11
  150. data/lib/hecks/projections/glossary/html.rb +1 -2
  151. data/lib/hecks/projections/glossary/markdown.rb +31 -28
  152. data/lib/hecks/projections/glossary/mermaid.rb +3 -0
  153. data/lib/hecks/projections/glossary/page.css +143 -153
  154. data/lib/hecks/projections/glossary/page.js +63 -3
  155. data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
  156. data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
  157. data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
  158. data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
  159. data/lib/hecks/projections/glossary/sentences.rb +13 -3
  160. data/lib/hecks/projections/glossary/slugs.rb +4 -1
  161. data/lib/hecks/projections/glossary.rb +2 -1
  162. data/lib/hecks/projections/plain_english.rb +97 -0
  163. data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
  164. data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
  165. data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
  166. data/lib/hecks/projections/rust_vocabulary.rb +14 -6
  167. data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
  168. data/lib/hecks/projections/statements/actions.rb +99 -0
  169. data/lib/hecks/projections/statements/relationships.rb +86 -0
  170. data/lib/hecks/projections/statements.rb +15 -24
  171. data/lib/hecks/projector/exporter/adapters.rb +112 -0
  172. data/lib/hecks/projector/exporter/translations.rb +18 -35
  173. data/lib/hecks/projector/exporter/worlds.rb +103 -0
  174. data/lib/hecks/projector/exporter.rb +5 -1
  175. data/lib/hecks/quality_control/adapters/agent.rb +6 -7
  176. data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
  177. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
  178. data/lib/hecks/quality_control/quality_control.bluebook +51 -51
  179. data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
  180. data/lib/hecks/runtime/outbox.rb +2 -9
  181. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
  182. data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
  183. data/lib/hecks/runtime/value.rb +13 -0
  184. data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
  185. data/lib/hecks/rust_build/wasm.rb +0 -12
  186. data/lib/hecks/rust_build/write_if_changed.rb +12 -12
  187. data/lib/hecks/rust_format.rb +77 -0
  188. data/lib/hecks/site/bluebook/site.bluebook +5 -5
  189. data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
  190. data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
  191. data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
  192. data/lib/hecks/tools/fuzz_sweep.rb +1 -3
  193. data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
  194. data/lib/hecks/tools/translation_audit.rb +8 -10
  195. data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
  196. data/lib/hecks/version.rb +1 -1
  197. data/lib/hecks/vocabulary.rb +2 -1
  198. data/lib/hecks.rb +3 -0
  199. data/prebuilt/{58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
  200. data/prebuilt/{2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
  201. data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +1 -0
  202. data/rust/Cargo.toml +40 -0
  203. data/rust/build/Cargo.toml +36 -0
  204. data/rust/build/clippy.toml +4 -0
  205. data/rust/build/src/adapters_pass.rs +196 -0
  206. data/rust/build/src/binding_pass.rs +54 -13
  207. data/rust/build/src/build_artifact.rs +23 -5
  208. data/rust/build/src/cargo_sync.rs +69 -26
  209. data/rust/build/src/lineage_pass.rs +101 -87
  210. data/rust/build/src/main.rs +11 -2
  211. data/rust/build/src/pipeline.rs +80 -19
  212. data/rust/build/src/reserved_names.rs +6 -50
  213. data/rust/build/src/resolve.rs +82 -35
  214. data/rust/build/src/rustfmt.rs +100 -0
  215. data/rust/build/src/seams_pass.rs +85 -16
  216. data/rust/build/src/subprocess.rs +30 -6
  217. data/rust/build/src/tmp.rs +5 -2
  218. data/rust/build/src/translation_pass.rs +72 -22
  219. data/rust/build/src/world_pass.rs +109 -0
  220. data/rust/clippy.toml +4 -0
  221. data/rust/codegen/Cargo.toml +36 -0
  222. data/rust/codegen/clippy.toml +4 -0
  223. data/rust/codegen/src/asks.rs +40 -14
  224. data/rust/codegen/src/attr.rs +2 -2
  225. data/rust/codegen/src/bridging.rs +676 -117
  226. data/rust/codegen/src/commands.rs +979 -373
  227. data/rust/codegen/src/constraints.rs +88 -25
  228. data/rust/codegen/src/dependency_planning.rs +128 -34
  229. data/rust/codegen/src/domain_generator.rs +300 -170
  230. data/rust/codegen/src/exemplar.rs +87 -32
  231. data/rust/codegen/src/expr_emitter.rs +54 -6
  232. data/rust/codegen/src/fielded.rs +193 -41
  233. data/rust/codegen/src/hecks_naming.rs +124 -16
  234. data/rust/codegen/src/hecks_naming_tests.rs +81 -0
  235. data/rust/codegen/src/json.rs +80 -44
  236. data/rust/codegen/src/json_codec.rs +585 -149
  237. data/rust/codegen/src/literal.rs +104 -138
  238. data/rust/codegen/src/main.rs +145 -52
  239. data/rust/codegen/src/manifest.rs +76 -14
  240. data/rust/codegen/src/mutations.rs +1212 -239
  241. data/rust/codegen/src/naming.rs +111 -60
  242. data/rust/codegen/src/optional_pass.rs +90 -28
  243. data/rust/codegen/src/ports.rs +5 -4
  244. data/rust/codegen/src/prelude.rs +139 -49
  245. data/rust/codegen/src/queries.rs +601 -226
  246. data/rust/codegen/src/reactions.rs +268 -74
  247. data/rust/codegen/src/read_models.rs +606 -205
  248. data/rust/codegen/src/reference_specs.rs +16 -3
  249. data/rust/codegen/src/registry.rs +128 -79
  250. data/rust/codegen/src/reserved_names.rs +6 -50
  251. data/rust/codegen/src/shared.rs +71 -22
  252. data/rust/codegen/src/sidecars.rs +15 -5
  253. data/rust/codegen/src/skip_reason.rs +8 -2
  254. data/rust/codegen/src/types.rs +306 -108
  255. data/rust/host/Cargo.lock +1 -0
  256. data/rust/host/Cargo.toml +40 -4
  257. data/rust/host/HECKS_RELEASE +1 -1
  258. data/rust/host/clippy.toml +4 -0
  259. data/rust/host/src/api.rs +698 -218
  260. data/rust/host/src/auth.rs +491 -214
  261. data/rust/host/src/b64.rs +91 -0
  262. data/rust/host/src/bin/expr_harness.rs +6 -2
  263. data/rust/host/src/bin/lineage_harness.rs +47 -11
  264. data/rust/host/src/bin/mint_harness.rs +70 -37
  265. data/rust/host/src/bin/validate_harness.rs +16 -8
  266. data/rust/host/src/boot.rs +144 -68
  267. data/rust/host/src/boundary_fuzz/api.rs +130 -32
  268. data/rust/host/src/boundary_fuzz/auth.rs +108 -23
  269. data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
  270. data/rust/host/src/boundary_fuzz/ir.rs +164 -12
  271. data/rust/host/src/boundary_fuzz/server.rs +283 -36
  272. data/rust/host/src/boundary_fuzz/web.rs +56 -8
  273. data/rust/host/src/checkout.rs +78 -32
  274. data/rust/host/src/clock.rs +8 -0
  275. data/rust/host/src/commerce_ir.rs +333 -95
  276. data/rust/host/src/crypto.rs +33 -0
  277. data/rust/host/src/dispatch.rs +990 -331
  278. data/rust/host/src/expr_json.rs +1021 -273
  279. data/rust/host/src/extension.rs +24 -4
  280. data/rust/host/src/field_hints.rs +11 -7
  281. data/rust/host/src/fuzz_support.rs +85 -13
  282. data/rust/host/src/host.rs +100 -0
  283. data/rust/host/src/http.rs +22 -0
  284. data/rust/host/src/ir.rs +326 -16
  285. data/rust/host/src/journal.rs +277 -176
  286. data/rust/host/src/lambda_client.rs +219 -53
  287. data/rust/host/src/lib.rs +16 -4
  288. data/rust/host/src/log.rs +62 -11
  289. data/rust/host/src/mint.rs +1153 -326
  290. data/rust/host/src/needs.rs +185 -44
  291. data/rust/host/src/payments/keystore.rs +197 -45
  292. data/rust/host/src/payments/roles.rs +191 -0
  293. data/rust/host/src/payments/tests.rs +1445 -275
  294. data/rust/host/src/payments.rs +409 -121
  295. data/rust/host/src/platform_signin/attack_tests.rs +5 -5
  296. data/rust/host/src/platform_signin/config.rs +11 -11
  297. data/rust/host/src/platform_signin/cookie.rs +2 -2
  298. data/rust/host/src/platform_signin/extension.rs +2 -1
  299. data/rust/host/src/platform_signin/handler.rs +9 -8
  300. data/rust/host/src/platform_signin/jti.rs +4 -2
  301. data/rust/host/src/platform_signin/jwks.rs +2 -1
  302. data/rust/host/src/platform_signin/jws.rs +2 -37
  303. data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
  304. data/rust/host/src/platform_signin/mode_tests.rs +5 -3
  305. data/rust/host/src/platform_signin/pages.rs +20 -20
  306. data/rust/host/src/platform_signin/session.rs +8 -1
  307. data/rust/host/src/platform_signin/settings.rs +6 -6
  308. data/rust/host/src/platform_signin/strict_json.rs +12 -9
  309. data/rust/host/src/platform_signin/verify.rs +41 -41
  310. data/rust/host/src/platform_signin/wiring_support.rs +13 -18
  311. data/rust/host/src/presentation.rs +207 -63
  312. data/rust/host/src/presentation_write.rs +502 -187
  313. data/rust/host/src/query_step.rs +69 -18
  314. data/rust/host/src/rate_limit.rs +541 -142
  315. data/rust/host/src/reference_transform.rs +77 -25
  316. data/rust/host/src/reference_validate.rs +443 -104
  317. data/rust/host/src/resend.rs +190 -55
  318. data/rust/host/src/secrets.rs +116 -21
  319. data/rust/host/src/server.rs +483 -104
  320. data/rust/host/src/storage_shape.rs +88 -21
  321. data/rust/host/src/test_pg.rs +17 -7
  322. data/rust/host/src/ui_schema.rs +495 -207
  323. data/rust/host/src/wasm_runner.rs +21 -6
  324. data/rust/host/src/web/commerce.rs +101 -23
  325. data/rust/host/src/web/newsletter/cooldown.rs +10 -3
  326. data/rust/host/src/web/newsletter/template.rs +26 -7
  327. data/rust/host/src/web/newsletter/tests.rs +112 -23
  328. data/rust/host/src/web/newsletter.rs +209 -59
  329. data/rust/host/src/web/newsletter_send.rs +251 -79
  330. data/rust/host/src/web/registration_receipt/tests.rs +16 -4
  331. data/rust/host/src/web/registration_receipt.rs +100 -24
  332. data/rust/host/src/web/registrations.rs +1384 -332
  333. data/rust/host/src/web/resend_webhook.rs +138 -80
  334. data/rust/host/src/web.rs +2416 -508
  335. data/rust/lsp/Cargo.toml +36 -0
  336. data/rust/lsp/clippy.toml +4 -0
  337. data/rust/lsp/src/json.rs +29 -22
  338. data/rust/lsp/src/main.rs +81 -31
  339. data/rust/lsp/src/outline.rs +23 -7
  340. data/rust/lsp/src/rpc.rs +17 -4
  341. data/rust/parser/Cargo.toml +36 -0
  342. data/rust/parser/clippy.toml +4 -0
  343. data/rust/parser/src/build/closed_sets.rs +6 -1
  344. data/rust/parser/src/build/identity.rs +4 -3
  345. data/rust/parser/src/build/mod.rs +1 -0
  346. data/rust/parser/src/build/named_pattern.rs +26 -0
  347. data/rust/parser/src/build/naming.rs +2 -80
  348. data/rust/parser/src/build/pattern_subset.rs +6 -8
  349. data/rust/parser/src/build/query_derive.rs +3 -1
  350. data/rust/parser/src/build/query_inference.rs +14 -5
  351. data/rust/parser/src/build/read_model.rs +4 -1
  352. data/rust/parser/src/build/references.rs +4 -3
  353. data/rust/parser/src/canonical.rs +34 -15
  354. data/rust/parser/src/emit.rs +187 -105
  355. data/rust/parser/src/expr/ast_json.rs +134 -42
  356. data/rust/parser/src/expr/evaluator.rs +27 -9
  357. data/rust/parser/src/expr/mod.rs +54 -13
  358. data/rust/parser/src/expr/resolver.rs +299 -92
  359. data/rust/parser/src/ir.rs +35 -3
  360. data/rust/parser/src/keywords.rs +7168 -510
  361. data/rust/parser/src/lex.rs +11 -25
  362. data/rust/parser/src/lib.rs +1 -0
  363. data/rust/parser/src/main.rs +65 -6
  364. data/rust/parser/src/parse/adapter.rs +157 -0
  365. data/rust/parser/src/parse/aggregate.rs +19 -10
  366. data/rust/parser/src/parse/chapter.rs +16 -17
  367. data/rust/parser/src/parse/command.rs +35 -16
  368. data/rust/parser/src/parse/domain_port.rs +4 -7
  369. data/rust/parser/src/parse/entity.rs +21 -11
  370. data/rust/parser/src/parse/hecksagon.rs +10 -4
  371. data/rust/parser/src/parse/lifecycle.rs +9 -5
  372. data/rust/parser/src/parse/mod.rs +43 -91
  373. data/rust/parser/src/parse/needs.rs +21 -12
  374. data/rust/parser/src/parse/policy.rs +2 -2
  375. data/rust/parser/src/parse/process_manager.rs +9 -11
  376. data/rust/parser/src/parse/query.rs +8 -4
  377. data/rust/parser/src/parse/read_model.rs +57 -16
  378. data/rust/parser/src/parse/value_object.rs +7 -2
  379. data/rust/parser/src/parse/world.rs +322 -0
  380. data/rust/parser/src/ruby_value.rs +7 -6
  381. data/rust/src/exemplar/json.rs +4 -4
  382. data/rust/src/exemplar/mutations.rs +18 -0
  383. data/rust/src/exemplar/read_models.rs +1 -10
  384. data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
  385. data/rust/src/kernel/attribute_shapes/list.rs +2 -2
  386. data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
  387. data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
  388. data/rust/src/kernel/cli.rs +229 -112
  389. data/rust/src/kernel/dispatch.rs +323 -115
  390. data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
  391. data/rust/src/kernel/expr.rs +203 -86
  392. data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
  393. data/rust/src/kernel/expression_operators/comparison.rs +87 -2
  394. data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
  395. data/rust/src/kernel/expression_operators/logical.rs +9 -3
  396. data/rust/src/kernel/expression_operators/membership.rs +3 -1
  397. data/rust/src/kernel/expression_operators/mod.rs +34 -21
  398. data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
  399. data/rust/src/kernel/expression_operators/positional.rs +84 -30
  400. data/rust/src/kernel/expression_operators/presence.rs +52 -13
  401. data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
  402. data/rust/src/kernel/expression_operators/sized.rs +24 -8
  403. data/rust/src/kernel/expression_operators/text.rs +120 -27
  404. data/rust/src/kernel/expression_operators/to_string.rs +10 -4
  405. data/rust/src/kernel/json.rs +230 -148
  406. data/rust/src/kernel/mod.rs +46 -33
  407. data/rust/src/kernel/named_query.rs +63 -15
  408. data/rust/src/kernel/needs.rs +112 -25
  409. data/rust/src/kernel/orchestrate.rs +980 -581
  410. data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
  411. data/rust/src/kernel/pattern.rs +107 -34
  412. data/rust/src/kernel/query_comparators.rs +118 -41
  413. data/rust/src/kernel/query_ordering.rs +13 -5
  414. data/rust/src/kernel/read_model.rs +479 -181
  415. data/rust/src/kernel/reference_lookup.rs +122 -26
  416. data/rust/src/kernel/repository.rs +293 -66
  417. data/rust/src/kernel/routing.rs +58 -42
  418. data/rust/src/kernel/ruby_float.rs +139 -0
  419. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
  420. data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
  421. data/rust/src/kernel/vocab/field_hint.rs +19 -25
  422. data/rust/src/kernel/vocab/mod.rs +6 -6
  423. data/rust/src/kernel/vocab/query_comparator.rs +23 -24
  424. data/rust/src/kernel/vocab/refusal_template.rs +501 -297
  425. data/rust/src/lib.rs +50 -0
  426. data/rust/src/main.rs +4 -1
  427. data/rust/web/Cargo.toml +36 -0
  428. data/rust/web/clippy.toml +4 -0
  429. metadata +85 -15
  430. data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
  431. data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
  432. data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
  433. data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
  434. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
  435. data/lib/hecks/ports/authentication.port +0 -6
  436. data/lib/hecks/ports/authentication.rb +0 -67
  437. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
  438. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
  439. data/prebuilt/verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json +0 -1
  440. data/rust/host/src/approval.rs +0 -652
@@ -5,43 +5,75 @@ use crate::exemplar::Exemplar;
5
5
  use crate::fielded;
6
6
  use crate::json::Json;
7
7
  use crate::naming;
8
+ use crate::shared::ruby_to_i;
8
9
  use std::collections::HashMap;
9
10
 
10
- pub fn emit_check_invariants(exemplar: &Exemplar, vo: &Json, value_objects_by_name: &HashMap<String, &Json>, aggregates_by_name: &HashMap<String, &Json>) -> String {
11
+ #[allow(
12
+ clippy::panic,
13
+ reason = "a drifted exemplar or invalid IR aborts generation on purpose"
14
+ )]
15
+ pub fn emit_check_invariants(
16
+ exemplar: &Exemplar,
17
+ vo: &Json,
18
+ value_objects_by_name: &HashMap<String, &Json>,
19
+ aggregates_by_name: &HashMap<String, &Json>,
20
+ ) -> String {
11
21
  let name = naming::rust_ident(vo.get("name").and_then(Json::as_str).unwrap_or(""));
12
- let type_name = vo.get("name").and_then(Json::as_str).unwrap_or("").to_string();
13
- let invariants = vo.get("invariants").map(Json::each).unwrap_or(&[]);
22
+ let type_name = vo
23
+ .get("name")
24
+ .and_then(Json::as_str)
25
+ .unwrap_or("")
26
+ .to_string();
27
+ let invariants = vo.get("invariants").map_or_default(Json::each);
14
28
 
15
29
  // The block is flush left to match the Ruby heredoc's dedent; re-indenting it mismatches.
16
30
  // Order: nested value objects, then `admits`/`pattern`, then invariants.
17
31
  let mut body: Vec<String> = Vec::new();
18
- let attributes = vo.get("attributes").map(Json::each).unwrap_or(&[]);
32
+ let attributes = vo.get("attributes").map_or_default(Json::each);
19
33
 
20
34
  for attr in attributes {
21
35
  if naming::effective_scalar_type(crate::attr::type_name(attr)).is_some() {
22
36
  continue;
23
37
  }
24
- let is_plain_nested = matches!(value_objects_by_name.get(crate::attr::type_name(attr)), Some(v) if !v.get("closed_set").map(Json::as_bool).unwrap_or(false));
38
+ let is_plain_nested = matches!(value_objects_by_name.get(crate::attr::type_name(attr)), Some(v) if !v.get("closed_set").is_some_and(Json::as_bool));
25
39
  if !is_plain_nested {
26
40
  continue;
27
41
  }
28
42
  let field = naming::rust_ident_field(crate::attr::name(attr));
29
- body.push(nested_invariant_line(&field, crate::attr::list(attr), crate::attr::optional(attr)));
43
+ body.push(nested_invariant_line(
44
+ &field,
45
+ crate::attr::list(attr),
46
+ crate::attr::optional(attr),
47
+ ));
30
48
  }
31
49
 
32
50
  for attr in attributes {
33
51
  if crate::attr::list(attr) {
34
52
  let field = format!("self.{}", naming::rust_ident_field(crate::attr::name(attr)));
35
- if let Some(line) = crate::constraints::emit_list_pattern_check(exemplar, &field, attr, &name) {
53
+ if let Some(line) =
54
+ crate::constraints::emit_list_pattern_check(exemplar, &field, attr, &name)
55
+ {
36
56
  body.push(format!(" {line}"));
37
57
  }
38
58
  continue;
39
59
  }
40
60
  let field = format!("self.{}", naming::rust_ident_field(crate::attr::name(attr)));
41
- if let Some(line) = crate::constraints::emit_admits_check(exemplar, &field, attr, aggregates_by_name, value_objects_by_name) {
61
+ if let Some(line) = crate::constraints::emit_admits_check(
62
+ exemplar,
63
+ &field,
64
+ attr,
65
+ aggregates_by_name,
66
+ value_objects_by_name,
67
+ ) {
42
68
  body.push(format!(" {line}"));
43
69
  }
44
- if let Some(line) = crate::constraints::emit_pattern_check(exemplar, &field, attr, &name, value_objects_by_name) {
70
+ if let Some(line) = crate::constraints::emit_pattern_check(
71
+ exemplar,
72
+ &field,
73
+ attr,
74
+ &name,
75
+ value_objects_by_name,
76
+ ) {
45
77
  body.push(format!(" {line}"));
46
78
  }
47
79
  }
@@ -78,7 +110,7 @@ fn nested_invariant_line(field: &str, list: bool, optional: bool) -> String {
78
110
 
79
111
  pub fn emit_closed_set_table(exemplar: &Exemplar, vo: &Json) -> String {
80
112
  let name = naming::rust_ident(vo.get("name").and_then(Json::as_str).unwrap_or(""));
81
- let attributes = vo.get("attributes").map(Json::each).unwrap_or(&[]);
113
+ let attributes = vo.get("attributes").map_or_default(Json::each);
82
114
 
83
115
  let field_subs_list: Vec<Vec<(&str, String)>> = attributes
84
116
  .iter()
@@ -87,12 +119,24 @@ pub fn emit_closed_set_table(exemplar: &Exemplar, vo: &Json) -> String {
87
119
  if ty == "String" {
88
120
  ty = "&'static str".to_string();
89
121
  }
90
- vec![("TmplFieldType", ty), ("tmpl_field", naming::rust_ident_field(crate::attr::name(attr)))]
122
+ vec![
123
+ ("TmplFieldType", ty),
124
+ (
125
+ "tmpl_field",
126
+ naming::rust_ident_field(crate::attr::name(attr)),
127
+ ),
128
+ ]
91
129
  })
92
130
  .collect();
93
- let struct_part = exemplar.compose("plain_struct", &[("TmplType", name.clone())], "struct_field", &field_subs_list, "\n");
94
-
95
- let members = vo.get("members").map(Json::each).unwrap_or(&[]);
131
+ let struct_part = exemplar.compose(
132
+ "plain_struct",
133
+ &[("TmplType", name.clone())],
134
+ "struct_field",
135
+ &field_subs_list,
136
+ "\n",
137
+ );
138
+
139
+ let members = vo.get("members").map_or_default(Json::each);
96
140
  let member_literals: Vec<String> = members
97
141
  .iter()
98
142
  .map(|row| {
@@ -111,44 +155,36 @@ pub fn emit_closed_set_table(exemplar: &Exemplar, vo: &Json) -> String {
111
155
  .iter()
112
156
  .map(|attr| {
113
157
  let field_name = crate::attr::name(attr).to_string();
114
- let raw_str: String = match present.get(&field_name) {
115
- Some(v) => v.to_s(),
116
- None => match crate::attr::default(attr) {
117
- Some(d) => d.to_s(),
118
- None => String::new(),
158
+ let raw_str: String = present.get(&field_name).map_or_else(
159
+ || {
160
+ crate::attr::default(attr)
161
+ .map_or_else(String::new, super::json::Json::to_s)
119
162
  },
120
- };
163
+ |v| v.to_s(),
164
+ );
121
165
  let literal = match crate::attr::type_name(attr) {
122
166
  "Integer" => ruby_to_i(&raw_str).to_string(),
123
167
  "Float" => format!("{}f64", ruby_to_f(&raw_str)),
124
168
  _ => naming::ruby_inspect_string(&raw_str),
125
169
  };
126
- exemplar.render("closed_set_table_row_field", &[("tmpl_field", naming::rust_ident_field(&field_name)), ("tmpl_value_placeholder()", literal)])
170
+ exemplar.render(
171
+ "closed_set_table_row_field",
172
+ &[
173
+ ("tmpl_field", naming::rust_ident_field(&field_name)),
174
+ ("tmpl_value_placeholder()", literal),
175
+ ],
176
+ )
127
177
  })
128
178
  .collect();
129
179
  format!(" {name} {{ {} }},", fields.join(", "))
130
180
  })
131
181
  .collect();
132
182
 
133
- format!("{struct_part}\n\npub const {}: &[{name}] = &[\n{}\n];", naming::screaming_snake(vo.get("name").and_then(Json::as_str).unwrap_or("")), member_literals.join("\n"))
134
- }
135
-
136
- /// Ruby's `String#to_i`: parses a leading integer prefix, 0 when there is none.
137
- fn ruby_to_i(s: &str) -> i64 {
138
- let trimmed = s.trim_start();
139
- let mut end = 0;
140
- let bytes = trimmed.as_bytes();
141
- if end < bytes.len() && (bytes[end] == b'-' || bytes[end] == b'+') {
142
- end += 1;
143
- }
144
- let digits_start = end;
145
- while end < bytes.len() && bytes[end].is_ascii_digit() {
146
- end += 1;
147
- }
148
- if end == digits_start {
149
- return 0;
150
- }
151
- trimmed[..end].parse().unwrap_or(0)
183
+ format!(
184
+ "{struct_part}\n\npub const {}: &[{name}] = &[\n{}\n];",
185
+ naming::screaming_snake(vo.get("name").and_then(Json::as_str).unwrap_or("")),
186
+ member_literals.join("\n")
187
+ )
152
188
  }
153
189
 
154
190
  /// Ruby's `String#to_f`: parses a leading float prefix, 0.0 when there is none.
@@ -182,17 +218,22 @@ fn ruby_to_f(s: &str) -> f64 {
182
218
  trimmed[..end].parse().unwrap_or(0.0)
183
219
  }
184
220
 
185
- pub fn emit_value_object(exemplar: &Exemplar, vo: &Json, value_objects_by_name: &HashMap<String, &Json>, aggregates_by_name: &HashMap<String, &Json>) -> String {
221
+ pub fn emit_value_object(
222
+ exemplar: &Exemplar,
223
+ vo: &Json,
224
+ value_objects_by_name: &HashMap<String, &Json>,
225
+ aggregates_by_name: &HashMap<String, &Json>,
226
+ ) -> String {
186
227
  let name = naming::rust_ident(vo.get("name").and_then(Json::as_str).unwrap_or(""));
187
- let closed_set = vo.get("closed_set").map(Json::as_bool).unwrap_or(false);
188
- let attributes = vo.get("attributes").map(Json::each).unwrap_or(&[]);
228
+ let closed_set = vo.get("closed_set").is_some_and(Json::as_bool);
229
+ let attributes = vo.get("attributes").map_or_default(Json::each);
189
230
 
190
231
  if closed_set {
191
232
  if attributes.len() > 1 {
192
233
  return emit_closed_set_table(exemplar, vo);
193
234
  }
194
235
 
195
- let members = vo.get("members").map(Json::each).unwrap_or(&[]);
236
+ let members = vo.get("members").map_or_default(Json::each);
196
237
  let variants: Vec<String> = members
197
238
  .iter()
198
239
  .map(|row| {
@@ -202,9 +243,21 @@ pub fn emit_value_object(exemplar: &Exemplar, vo: &Json, value_objects_by_name:
202
243
  naming::closed_set_variant(&value)
203
244
  })
204
245
  .collect();
205
- let field_subs_list: Vec<Vec<(&str, String)>> = variants.iter().map(|v| vec![("TmplMemberA", v.clone())]).collect();
206
- let enum_part = exemplar.compose("closed_set_enum", &[("TmplKind", name)], "closed_set_enum:VARIANT", &field_subs_list, "\n");
207
- return format!("{enum_part}\n\n{}", naming::emit_closed_set_fielded_impl(vo));
246
+ let field_subs_list: Vec<Vec<(&str, String)>> = variants
247
+ .iter()
248
+ .map(|v| vec![("TmplMemberA", v.clone())])
249
+ .collect();
250
+ let enum_part = exemplar.compose(
251
+ "closed_set_enum",
252
+ &[("TmplKind", name)],
253
+ "closed_set_enum:VARIANT",
254
+ &field_subs_list,
255
+ "\n",
256
+ );
257
+ return format!(
258
+ "{enum_part}\n\n{}",
259
+ naming::emit_closed_set_fielded_impl(vo)
260
+ );
208
261
  }
209
262
 
210
263
  let field_subs_list: Vec<Vec<(&str, String)>> = attributes
@@ -214,24 +267,48 @@ pub fn emit_value_object(exemplar: &Exemplar, vo: &Json, value_objects_by_name:
214
267
  if crate::attr::optional(attr) {
215
268
  ty = format!("Option<{ty}>");
216
269
  }
217
- vec![("TmplFieldType", ty), ("tmpl_field", naming::rust_ident_field(crate::attr::name(attr)))]
270
+ vec![
271
+ ("TmplFieldType", ty),
272
+ (
273
+ "tmpl_field",
274
+ naming::rust_ident_field(crate::attr::name(attr)),
275
+ ),
276
+ ]
218
277
  })
219
278
  .collect();
220
- let struct_part = exemplar.compose("plain_struct", &[("TmplType", name.clone())], "struct_field", &field_subs_list, "\n");
221
-
222
- let fielded_part = fielded::emit_fielded_flat(exemplar, &name, attributes, value_objects_by_name, &[]);
223
- let invariants_part = emit_check_invariants(exemplar, vo, value_objects_by_name, aggregates_by_name);
279
+ let struct_part = exemplar.compose(
280
+ "plain_struct",
281
+ &[("TmplType", name.clone())],
282
+ "struct_field",
283
+ &field_subs_list,
284
+ "\n",
285
+ );
286
+
287
+ let fielded_part =
288
+ fielded::emit_fielded_flat(exemplar, &name, attributes, value_objects_by_name, &[]);
289
+ let invariants_part =
290
+ emit_check_invariants(exemplar, vo, value_objects_by_name, aggregates_by_name);
224
291
 
225
292
  format!("{struct_part}\n\n{fielded_part}\n\n{invariants_part}")
226
293
  }
227
294
 
228
295
  /// Attribute type names on `aggregate` that have no Rust mapping.
229
- pub fn unsupported_attribute_types(aggregate: &Json, value_objects_by_name: &HashMap<String, &Json>) -> Vec<String> {
230
- let entity_names: Vec<&str> = aggregate.get("entities").map(Json::each).unwrap_or(&[]).iter().map(|e| e.get("name").and_then(Json::as_str).unwrap_or("")).collect();
296
+ pub fn unsupported_attribute_types(
297
+ aggregate: &Json,
298
+ value_objects_by_name: &HashMap<String, &Json>,
299
+ ) -> Vec<String> {
300
+ let entity_names: Vec<&str> = aggregate
301
+ .get("entities")
302
+ .map_or_default(Json::each)
303
+ .iter()
304
+ .map(|e| e.get("name").and_then(Json::as_str).unwrap_or(""))
305
+ .collect();
231
306
  let mut seen = Vec::new();
232
- for attr in aggregate.get("attributes").map(Json::each).unwrap_or(&[]) {
307
+ for attr in aggregate.get("attributes").map_or_default(Json::each) {
233
308
  let ty = crate::attr::type_name(attr);
234
- let supported = naming::effective_scalar_type(ty).is_some() || value_objects_by_name.contains_key(ty) || (crate::attr::list(attr) && entity_names.contains(&ty));
309
+ let supported = naming::effective_scalar_type(ty).is_some()
310
+ || value_objects_by_name.contains_key(ty)
311
+ || (crate::attr::list(attr) && entity_names.contains(&ty));
235
312
  if !supported && !seen.iter().any(|s| s == ty) {
236
313
  seen.push(ty.to_string());
237
314
  }
@@ -239,9 +316,13 @@ pub fn unsupported_attribute_types(aggregate: &Json, value_objects_by_name: &Has
239
316
  seen
240
317
  }
241
318
 
242
- pub fn emit_entity(exemplar: &Exemplar, entity: &Json, value_objects_by_name: &HashMap<String, &Json>) -> String {
319
+ pub fn emit_entity(
320
+ exemplar: &Exemplar,
321
+ entity: &Json,
322
+ value_objects_by_name: &HashMap<String, &Json>,
323
+ ) -> String {
243
324
  let name = naming::rust_ident(entity.get("name").and_then(Json::as_str).unwrap_or(""));
244
- let attributes = entity.get("attributes").map(Json::each).unwrap_or(&[]);
325
+ let attributes = entity.get("attributes").map_or_default(Json::each);
245
326
 
246
327
  let mut field_subs_list: Vec<Vec<(&str, String)>> = attributes
247
328
  .iter()
@@ -250,49 +331,96 @@ pub fn emit_entity(exemplar: &Exemplar, entity: &Json, value_objects_by_name: &H
250
331
  if crate::attr::optional(attr) {
251
332
  ty = format!("Option<{ty}>");
252
333
  }
253
- vec![("TmplFieldType", ty), ("tmpl_field", naming::rust_ident_field(crate::attr::name(attr)))]
334
+ vec![
335
+ ("TmplFieldType", ty),
336
+ (
337
+ "tmpl_field",
338
+ naming::rust_ident_field(crate::attr::name(attr)),
339
+ ),
340
+ ]
254
341
  })
255
342
  .collect();
256
343
 
257
344
  let mut lifecycle_arm: Vec<String> = Vec::new();
258
345
  if let Some(lifecycle) = entity.get("lifecycle") {
259
346
  let field = lifecycle.get("field").and_then(Json::as_str).unwrap_or("");
260
- field_subs_list.push(vec![("TmplFieldType", "String".to_string()), ("tmpl_field", naming::rust_ident_field(field))]);
347
+ field_subs_list.push(vec![
348
+ ("TmplFieldType", "String".to_string()),
349
+ ("tmpl_field", naming::rust_ident_field(field)),
350
+ ]);
261
351
  let ident = naming::rust_ident_field(field);
262
- lifecycle_arm.push(format!(" \"{field}\" => Some(Field::Value(Value::Str(self.{ident}.clone()))),"));
352
+ lifecycle_arm.push(format!(
353
+ " \"{field}\" => Some(Field::Value(Value::Str(self.{ident}.clone()))),"
354
+ ));
263
355
  }
264
356
 
265
- let struct_part = exemplar.compose("plain_struct", &[("TmplType", name.clone())], "struct_field", &field_subs_list, "\n");
266
- let fielded_part = fielded::emit_fielded_flat(exemplar, &name, attributes, value_objects_by_name, &lifecycle_arm);
357
+ let struct_part = exemplar.compose(
358
+ "plain_struct",
359
+ &[("TmplType", name.clone())],
360
+ "struct_field",
361
+ &field_subs_list,
362
+ "\n",
363
+ );
364
+ let fielded_part = fielded::emit_fielded_flat(
365
+ exemplar,
366
+ &name,
367
+ attributes,
368
+ value_objects_by_name,
369
+ &lifecycle_arm,
370
+ );
267
371
 
268
372
  format!("{struct_part}\n\n{fielded_part}")
269
373
  }
270
374
 
271
- pub fn emit_record(exemplar: &Exemplar, aggregate: &Json, value_objects_by_name: &HashMap<String, &Json>) -> String {
375
+ pub fn emit_record(
376
+ exemplar: &Exemplar,
377
+ aggregate: &Json,
378
+ value_objects_by_name: &HashMap<String, &Json>,
379
+ ) -> String {
272
380
  let name = naming::rust_ident(aggregate.get("name").and_then(Json::as_str).unwrap_or(""));
273
- let attributes = aggregate.get("attributes").map(Json::each).unwrap_or(&[]);
381
+ let attributes = aggregate.get("attributes").map_or_default(Json::each);
274
382
 
275
383
  let mut field_subs_list: Vec<Vec<(&str, String)>> = attributes
276
384
  .iter()
277
385
  .map(|attr| {
278
386
  let list = crate::attr::list(attr);
279
387
  let mut ty = naming::rust_type(crate::attr::type_name(attr), list);
280
- if !list || crate::shared::list_attr_creation_optional(aggregate, crate::attr::name(attr), value_objects_by_name) {
388
+ if !list
389
+ || crate::shared::list_attr_creation_optional(aggregate, crate::attr::name(attr))
390
+ {
281
391
  ty = format!("Option<{ty}>");
282
392
  }
283
- vec![("TmplFieldType", ty), ("tmpl_field", naming::rust_ident_field(crate::attr::name(attr)))]
393
+ vec![
394
+ ("TmplFieldType", ty),
395
+ (
396
+ "tmpl_field",
397
+ naming::rust_ident_field(crate::attr::name(attr)),
398
+ ),
399
+ ]
284
400
  })
285
401
  .collect();
286
402
 
287
403
  if let Some(lifecycle) = aggregate.get("lifecycle") {
288
404
  let field = lifecycle.get("field").and_then(Json::as_str).unwrap_or("");
289
- field_subs_list.push(vec![("TmplFieldType", "String".to_string()), ("tmpl_field", naming::rust_ident_field(field))]);
405
+ field_subs_list.push(vec![
406
+ ("TmplFieldType", "String".to_string()),
407
+ ("tmpl_field", naming::rust_ident_field(field)),
408
+ ]);
290
409
  }
291
410
  for ev in crate::bridging::correctable_event_names(aggregate) {
292
- field_subs_list.push(vec![("TmplFieldType", "bool".to_string()), ("tmpl_field", crate::bridging::corrects_flag_field(&ev))]);
411
+ field_subs_list.push(vec![
412
+ ("TmplFieldType", "bool".to_string()),
413
+ ("tmpl_field", crate::bridging::corrects_flag_field(&ev)),
414
+ ]);
293
415
  }
294
416
 
295
- let struct_part = exemplar.compose("plain_struct", &[("TmplType", name)], "struct_field", &field_subs_list, "\n");
417
+ let struct_part = exemplar.compose(
418
+ "plain_struct",
419
+ &[("TmplType", name)],
420
+ "struct_field",
421
+ &field_subs_list,
422
+ "\n",
423
+ );
296
424
  let fielded_part = fielded::emit_fielded_record(exemplar, aggregate, value_objects_by_name);
297
425
 
298
426
  format!("{struct_part}\n\n{fielded_part}")
@@ -304,12 +432,15 @@ pub fn emit_record(exemplar: &Exemplar, aggregate: &Json, value_objects_by_name:
304
432
  /// field that resolves to nothing stays `String`.
305
433
  pub fn with_resolved_projection_types(ir: &Json) -> Json {
306
434
  let mut resolved = ir.clone();
307
- let aggregates: Vec<Json> = ir.get("aggregates").map(Json::each).unwrap_or(&[]).to_vec();
435
+ let aggregates: Vec<Json> = ir.get("aggregates").map_or_default(Json::each).to_vec();
308
436
  let Some(list) = resolved.get_mut("aggregates").and_then(Json::as_array_mut) else {
309
437
  return resolved;
310
438
  };
311
439
  for (aggregate, original) in list.iter_mut().zip(aggregates.iter()) {
312
- let Some(fields) = aggregate.get_mut("projected_fields").and_then(Json::as_array_mut) else {
440
+ let Some(fields) = aggregate
441
+ .get_mut("projected_fields")
442
+ .and_then(Json::as_array_mut)
443
+ else {
313
444
  continue;
314
445
  };
315
446
  for field in fields.iter_mut() {
@@ -325,26 +456,45 @@ const PROJECTION_CHAIN_LIMIT: usize = 8;
325
456
  // The scalar type a projection of `field` copies, following a chain of projections.
326
457
  fn projected_scalar_type(aggregates: &[Json], owner: &Json, field: &Json, depth: usize) -> String {
327
458
  let reference = field.get("reference").and_then(Json::as_str).unwrap_or("");
328
- let remote = field.get("remote_field").and_then(Json::as_str).unwrap_or("");
459
+ let remote = field
460
+ .get("remote_field")
461
+ .and_then(Json::as_str)
462
+ .unwrap_or("");
329
463
  let target = owner
330
464
  .get("attributes")
331
- .map(Json::each)
332
- .unwrap_or(&[])
465
+ .map_or_default(Json::each)
333
466
  .iter()
334
467
  .find(|a| crate::attr::name(a) == reference)
335
468
  .and_then(|a| naming::reference_target(crate::attr::type_name(a)))
336
- .and_then(|name| aggregates.iter().find(|a| a.get("name").and_then(Json::as_str) == Some(name)));
337
- target.map(|t| remote_scalar_type(aggregates, t, remote, depth)).unwrap_or_else(|| "String".to_string())
469
+ .and_then(|name| {
470
+ aggregates
471
+ .iter()
472
+ .find(|a| a.get("name").and_then(Json::as_str) == Some(name))
473
+ });
474
+ target.map_or_else(
475
+ || "String".to_string(),
476
+ |t| remote_scalar_type(aggregates, t, remote, depth),
477
+ )
338
478
  }
339
479
 
340
480
  fn remote_scalar_type(aggregates: &[Json], target: &Json, remote: &str, depth: usize) -> String {
341
- let declared = target.get("attributes").map(Json::each).unwrap_or(&[]).iter().find(|a| crate::attr::name(a) == remote);
481
+ let declared = target
482
+ .get("attributes")
483
+ .map_or_default(Json::each)
484
+ .iter()
485
+ .find(|a| crate::attr::name(a) == remote);
342
486
  if let Some(attr) = declared {
343
487
  return unwrapped_scalar_type(target, crate::attr::type_name(attr));
344
488
  }
345
- let chained = target.get("projected_fields").map(Json::each).unwrap_or(&[]).iter().find(|f| f.get("name").and_then(Json::as_str) == Some(remote));
489
+ let chained = target
490
+ .get("projected_fields")
491
+ .map_or_default(Json::each)
492
+ .iter()
493
+ .find(|f| f.get("name").and_then(Json::as_str) == Some(remote));
346
494
  match chained {
347
- Some(next) if depth < PROJECTION_CHAIN_LIMIT => projected_scalar_type(aggregates, target, next, depth + 1),
495
+ Some(next) if depth < PROJECTION_CHAIN_LIMIT => {
496
+ projected_scalar_type(aggregates, target, next, depth + 1)
497
+ }
348
498
  _ => "String".to_string(),
349
499
  }
350
500
  }
@@ -353,16 +503,21 @@ fn remote_scalar_type(aggregates: &[Json], target: &Json, remote: &str, depth: u
353
503
  fn unwrapped_scalar_type(aggregate: &Json, type_name: &str) -> String {
354
504
  let sole = aggregate
355
505
  .get("value_objects")
356
- .map(Json::each)
357
- .unwrap_or(&[])
506
+ .map_or_default(Json::each)
358
507
  .iter()
359
508
  .find(|vo| vo.get("name").and_then(Json::as_str) == Some(type_name))
360
- .map(|vo| vo.get("attributes").map(Json::each).unwrap_or(&[]))
509
+ .map(|vo| vo.get("attributes").map_or_default(Json::each))
361
510
  .filter(|attrs| attrs.len() == 1 && !crate::attr::list(&attrs[0]))
362
511
  .map(|attrs| crate::attr::type_name(&attrs[0]).to_string());
363
512
  let resolved = sole.unwrap_or_else(|| type_name.to_string());
364
- let resolved = if resolved == "Boolean" { "TrueClass".to_string() } else { resolved };
365
- naming::effective_scalar_type(&resolved).unwrap_or("String").to_string()
513
+ let resolved = if resolved == "Boolean" {
514
+ "TrueClass".to_string()
515
+ } else {
516
+ resolved
517
+ };
518
+ naming::effective_scalar_type(&resolved)
519
+ .unwrap_or("String")
520
+ .to_string()
366
521
  }
367
522
 
368
523
  // The `Value` reader a generated setter narrows with, by scalar type.
@@ -379,12 +534,19 @@ fn projection_reader(scalar: &str) -> &'static str {
379
534
  pub fn projected_field_pseudo_attributes(aggregate: &Json) -> Vec<Json> {
380
535
  aggregate
381
536
  .get("projected_fields")
382
- .map(Json::each)
383
- .unwrap_or(&[])
537
+ .map_or_default(Json::each)
384
538
  .iter()
385
539
  .map(|field| {
386
- let name = field.get("name").and_then(Json::as_str).unwrap_or("").to_string();
387
- let scalar = field.get("type").and_then(Json::as_str).unwrap_or("String").to_string();
540
+ let name = field
541
+ .get("name")
542
+ .and_then(Json::as_str)
543
+ .unwrap_or("")
544
+ .to_string();
545
+ let scalar = field
546
+ .get("type")
547
+ .and_then(Json::as_str)
548
+ .unwrap_or("String")
549
+ .to_string();
388
550
  Json::Object(vec![
389
551
  ("name".to_string(), Json::String(name)),
390
552
  ("type".to_string(), Json::String(scalar)),
@@ -398,10 +560,17 @@ pub fn projected_field_pseudo_attributes(aggregate: &Json) -> Vec<Json> {
398
560
  /// Copy of `aggregate` whose `attributes` also list the projected-field pseudo-attributes, so
399
561
  /// `emit_record` and `emit_fielded_record` see them (`domain_generator.rb#record_for_struct`).
400
562
  pub fn with_projected_field_pseudo_attributes(aggregate: &Json) -> Json {
401
- let mut attributes: Vec<Json> = aggregate.get("attributes").map(Json::each).unwrap_or(&[]).to_vec();
563
+ let mut attributes: Vec<Json> = aggregate
564
+ .get("attributes")
565
+ .map_or_default(Json::each)
566
+ .to_vec();
402
567
  attributes.extend(projected_field_pseudo_attributes(aggregate));
403
568
  let mut pairs: Vec<(String, Json)> = match aggregate {
404
- Json::Object(pairs) => pairs.iter().filter(|(k, _)| k != "attributes").cloned().collect(),
569
+ Json::Object(pairs) => pairs
570
+ .iter()
571
+ .filter(|(k, _)| k != "attributes")
572
+ .cloned()
573
+ .collect(),
405
574
  _ => Vec::new(),
406
575
  };
407
576
  pairs.push(("attributes".to_string(), Json::Array(attributes)));
@@ -413,14 +582,19 @@ pub fn emit_set_projected_field(aggregate: &Json) -> String {
413
582
  let name = naming::rust_ident(aggregate.get("name").and_then(Json::as_str).unwrap_or(""));
414
583
  let arms: Vec<String> = aggregate
415
584
  .get("projected_fields")
416
- .map(Json::each)
417
- .unwrap_or(&[])
585
+ .map_or_default(Json::each)
418
586
  .iter()
419
587
  .map(|field| {
420
588
  let field_name = field.get("name").and_then(Json::as_str).unwrap_or("");
421
589
  let ident = naming::rust_ident_field(field_name);
422
- let reader = projection_reader(field.get("type").and_then(Json::as_str).unwrap_or("String"));
423
- format!(" {} => self.{} = value.and_then(crate::kernel::Value::{}),", naming::ruby_inspect_string(field_name), ident, reader)
590
+ let reader =
591
+ projection_reader(field.get("type").and_then(Json::as_str).unwrap_or("String"));
592
+ format!(
593
+ " {} => self.{} = value.and_then(crate::kernel::Value::{}),",
594
+ naming::ruby_inspect_string(field_name),
595
+ ident,
596
+ reader
597
+ )
424
598
  })
425
599
  .collect();
426
600
  format!(
@@ -434,8 +608,7 @@ pub fn emit_projected_field_table(aggregate: &Json) -> String {
434
608
  let name = naming::screaming_snake(aggregate.get("name").and_then(Json::as_str).unwrap_or(""));
435
609
  let rows: Vec<String> = aggregate
436
610
  .get("projected_fields")
437
- .map(Json::each)
438
- .unwrap_or(&[])
611
+ .map_or_default(Json::each)
439
612
  .iter()
440
613
  .map(|field| {
441
614
  let field_name = field.get("name").and_then(Json::as_str).unwrap_or("");
@@ -449,7 +622,10 @@ pub fn emit_projected_field_table(aggregate: &Json) -> String {
449
622
  )
450
623
  })
451
624
  .collect();
452
- format!("pub static {name}_PROJECTED_FIELDS: &[crate::kernel::ProjectedFieldSpec] = &[\n{}\n];\n", rows.join("\n"))
625
+ format!(
626
+ "pub static {name}_PROJECTED_FIELDS: &[crate::kernel::ProjectedFieldSpec] = &[\n{}\n];\n",
627
+ rows.join("\n")
628
+ )
453
629
  }
454
630
 
455
631
  #[cfg(test)]
@@ -485,11 +661,14 @@ mod tests {
485
661
  #[test]
486
662
  fn boolean_attributes_round_trip_through_json_as_bool() {
487
663
  let vo = flag_value_object();
488
- let attributes = vo.get("attributes").map(Json::each).unwrap_or(&[]);
664
+ let attributes = vo.get("attributes").map_or_default(Json::each);
489
665
  let by_name = HashMap::new();
490
666
  let exemplar = Exemplar::load();
491
- let from_json = crate::json_codec::emit_from_json_flat(&exemplar, "Entry", attributes, &by_name, None, None, false, false, None);
492
- let to_json = crate::json_codec::emit_to_json_flat(&exemplar, "Entry", attributes, &by_name, false, &[], None);
667
+ let from_json = crate::json_codec::emit_from_json_flat(
668
+ &exemplar, "Entry", attributes, &by_name, None, None, false, false, None, None,
669
+ );
670
+ let to_json =
671
+ crate::json_codec::emit_to_json_flat(&exemplar, "Entry", attributes, false, &[], None);
493
672
 
494
673
  assert!(from_json.contains("x.as_bool()"));
495
674
  assert!(!from_json.contains("TrueClass::from_json"));
@@ -510,12 +689,18 @@ mod tests {
510
689
  }
511
690
 
512
691
  fn resolved_booking() -> Json {
513
- with_resolved_projection_types(&event_and_booking()).get("aggregates").map(Json::each).unwrap_or(&[])[1].clone()
692
+ with_resolved_projection_types(&event_and_booking())
693
+ .get("aggregates")
694
+ .map_or_default(Json::each)[1]
695
+ .clone()
514
696
  }
515
697
 
516
698
  #[test]
517
699
  fn a_projection_through_a_single_field_value_object_takes_its_scalar_type() {
518
- let field = resolved_booking().get("projected_fields").map(Json::each).unwrap_or(&[])[0].clone();
700
+ let field = resolved_booking()
701
+ .get("projected_fields")
702
+ .map_or_default(Json::each)[0]
703
+ .clone();
519
704
 
520
705
  assert_eq!(field.get("type").and_then(Json::as_str), Some("Integer"));
521
706
  }
@@ -531,7 +716,9 @@ mod tests {
531
716
  fn the_projected_setter_narrows_with_the_remote_types_reader() {
532
717
  let setter = emit_set_projected_field(&resolved_booking());
533
718
 
534
- assert!(setter.contains("\"starts_at\" => self.starts_at = value.and_then(crate::kernel::Value::into_i64),"));
719
+ assert!(setter.contains(
720
+ "\"starts_at\" => self.starts_at = value.and_then(crate::kernel::Value::into_i64),"
721
+ ));
535
722
  }
536
723
 
537
724
  /// A value object holding a required and an optional value object, and a required and an
@@ -555,11 +742,22 @@ mod tests {
555
742
  fn a_nested_value_object_checks_its_invariants_only_when_it_is_present() {
556
743
  let note = Json::parse(r#"{"name":"Note","attributes":[{"name":"text","type":"String","list":false,"optional":false}]}"#).unwrap();
557
744
  let by_name: HashMap<String, &Json> = HashMap::from([("Note".to_string(), &note)]);
558
- let generated = emit_check_invariants(&Exemplar::load(), &holder_value_object(), &by_name, &HashMap::new());
745
+ let generated = emit_check_invariants(
746
+ &Exemplar::load(),
747
+ &holder_value_object(),
748
+ &by_name,
749
+ &HashMap::new(),
750
+ );
559
751
 
560
752
  assert!(generated.contains("self.required_note.check_invariants()?;"));
561
- assert!(generated.contains("if let Some(value) = &self.note { value.check_invariants()?; }"));
562
- assert!(generated.contains("for item in &self.required_notes { item.check_invariants()?; }"));
563
- assert!(generated.contains("if let Some(items) = &self.notes { for item in items { item.check_invariants()?; } }"));
753
+ assert!(
754
+ generated.contains("if let Some(value) = &self.note { value.check_invariants()?; }")
755
+ );
756
+ assert!(
757
+ generated.contains("for item in &self.required_notes { item.check_invariants()?; }")
758
+ );
759
+ assert!(generated.contains(
760
+ "if let Some(items) = &self.notes { for item in items { item.check_invariants()?; } }"
761
+ ));
564
762
  }
565
763
  }