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
@@ -7,40 +7,73 @@ use crate::naming;
7
7
  use std::collections::HashMap;
8
8
 
9
9
  pub fn json_type_error(struct_name: &str, key: &str, expectation: &str) -> String {
10
- format!("crate::kernel::Refusal::TypeMismatch({}.to_string())", naming::ruby_inspect_string(&format!("{struct_name}.{key}: expected {expectation}")))
10
+ format!(
11
+ "crate::kernel::Refusal::TypeMismatch({}.to_string())",
12
+ naming::ruby_inspect_string(&format!("{struct_name}.{key}: expected {expectation}"))
13
+ )
11
14
  }
12
15
 
13
16
  // The refusal for a scalar offered where a list argument goes; worded as Ruby's
14
17
  // `Value.refuse_scalar_list`, with the offered value known only at runtime.
15
- pub fn list_shape_error(struct_name: &str, key: &str, element_type: &str, value_var: &str) -> String {
18
+ pub fn list_shape_error(
19
+ struct_name: &str,
20
+ key: &str,
21
+ element_type: &str,
22
+ value_var: &str,
23
+ ) -> String {
16
24
  let template = format!("{struct_name}.{key} expects list_of({element_type}), got {{}}");
17
- format!("crate::kernel::Refusal::TypeMismatch(format!({}, {value_var}.inspect()))", naming::ruby_inspect_string(&template))
25
+ format!(
26
+ "crate::kernel::Refusal::TypeMismatch(format!({}, {value_var}.inspect()))",
27
+ naming::ruby_inspect_string(&template)
28
+ )
18
29
  }
19
30
 
20
31
  // A String field refuses any value that is not a String, and an Integer or Float field any that
21
32
  // is not a number, as Ruby's `Value::Coercion#check_scalar_shapes` and `check_numeric_item!`
22
33
  // do; the offered shape is only known at runtime, so the message renders it. A whole number
23
34
  // beyond signed 64 bits words as Ruby's `integer_range` site.
24
- pub fn scalar_type_error(struct_name: &str, key: &str, scalar_type: &str, value_var: &str) -> String {
25
- if scalar_type != "Integer" && scalar_type != "Float" && scalar_type != "String" {
35
+ pub fn scalar_type_error(
36
+ struct_name: &str,
37
+ key: &str,
38
+ scalar_type: &str,
39
+ value_var: &str,
40
+ ) -> String {
41
+ if !matches!(
42
+ scalar_type,
43
+ "Integer" | "Float" | "String" | "TrueClass" | "FalseClass"
44
+ ) {
26
45
  return json_type_error(struct_name, key, scalar_type);
27
46
  }
28
47
  let template = format!("{struct_name}.{key} expects {scalar_type}, got {{}}");
29
- let proper = format!("crate::kernel::Refusal::TypeMismatch(format!({}, {value_var}.inspect()))", naming::ruby_inspect_string(&template));
48
+ let proper = format!(
49
+ "crate::kernel::Refusal::TypeMismatch(format!({}, {value_var}.describe()))",
50
+ naming::ruby_inspect_string(&template)
51
+ );
30
52
  if scalar_type != "Integer" {
31
53
  return proper;
32
54
  }
33
55
  let range_template = format!("{struct_name}.{key} must fit in a 64-bit integer, got {{}}");
34
- let range = format!("crate::kernel::Refusal::TypeMismatch(format!({}, {value_var}.inspect()))", naming::ruby_inspect_string(&range_template));
35
- format!("if matches!({value_var}, crate::kernel::Json::Num(n, _) if n.is_finite() && n.fract() == 0.0 && n.abs() >= 9.223372036854775808e18) {{ {range} }} else {{ {proper} }}")
56
+ let range = format!(
57
+ "crate::kernel::Refusal::TypeMismatch(format!({}, {value_var}.describe()))",
58
+ naming::ruby_inspect_string(&range_template)
59
+ );
60
+ format!("if matches!({value_var}, crate::kernel::Json::Num(n, _) if n.is_finite() && n.fract() == 0.0 && n.abs() >= 9.223_372_036_854_776e18) {{ {range} }} else {{ {proper} }}")
36
61
  }
37
62
 
38
63
  /// A missing non-optional field refuses as "{type}.{field} expects {expected}, got nil".
39
64
  pub fn required_field_expr(struct_name: &str, key: &str, expected: &str) -> String {
40
65
  let message = format!("{struct_name}.{key} expects {expected}, got nil");
41
- format!("v.get({}).ok_or_else(|| crate::kernel::Refusal::TypeMismatch({}.to_string()))?", naming::ruby_inspect_string(key), naming::ruby_inspect_string(&message))
66
+ format!(
67
+ "v.get({}).ok_or_else(|| crate::kernel::Refusal::TypeMismatch({}.to_string()))?",
68
+ naming::ruby_inspect_string(key),
69
+ naming::ruby_inspect_string(&message)
70
+ )
42
71
  }
43
72
 
73
+ #[allow(
74
+ clippy::panic,
75
+ reason = "a drifted exemplar or invalid IR aborts generation on purpose"
76
+ )]
44
77
  pub fn scalar_json_accessor(scalar_type: &str) -> &'static str {
45
78
  match scalar_type {
46
79
  "String" => "as_str",
@@ -51,9 +84,18 @@ pub fn scalar_json_accessor(scalar_type: &str) -> &'static str {
51
84
  }
52
85
  }
53
86
 
54
- pub fn scalar_from_json_expr(struct_name: &str, key: &str, scalar_type: &str, default: Option<&Json>) -> String {
87
+ pub fn scalar_from_json_expr(
88
+ struct_name: &str,
89
+ key: &str,
90
+ scalar_type: &str,
91
+ default: Option<&Json>,
92
+ ) -> String {
55
93
  let accessor = scalar_json_accessor(scalar_type);
56
- let wrap = if scalar_type == "String" { ".map(|s| s.to_string())" } else { "" };
94
+ let wrap = if scalar_type == "String" {
95
+ ".map(ToString::to_string)"
96
+ } else {
97
+ ""
98
+ };
57
99
  if let Some(default) = default {
58
100
  let default_rhs = naming::literal_rhs(default);
59
101
  return format!(
@@ -69,12 +111,28 @@ pub fn scalar_from_json_expr(struct_name: &str, key: &str, scalar_type: &str, de
69
111
  )
70
112
  }
71
113
 
72
- pub fn scalar_from_json_value_expr(struct_name: &str, key: &str, scalar_type: &str, value_expr: &str) -> String {
114
+ pub fn scalar_from_json_value_expr(
115
+ struct_name: &str,
116
+ key: &str,
117
+ scalar_type: &str,
118
+ value_expr: &str,
119
+ ) -> String {
73
120
  let accessor = scalar_json_accessor(scalar_type);
74
- let wrap = if scalar_type == "String" { ".map(|s| s.to_string())" } else { "" };
75
- format!("{value_expr}.{accessor}(){wrap}.ok_or_else(|| {})?", scalar_type_error(struct_name, key, scalar_type, value_expr))
121
+ let wrap = if scalar_type == "String" {
122
+ ".map(ToString::to_string)"
123
+ } else {
124
+ ""
125
+ };
126
+ format!(
127
+ "{value_expr}.{accessor}(){wrap}.ok_or_else(|| {})?",
128
+ scalar_type_error(struct_name, key, scalar_type, value_expr)
129
+ )
76
130
  }
77
131
 
132
+ #[allow(
133
+ clippy::panic,
134
+ reason = "a drifted exemplar or invalid IR aborts generation on purpose"
135
+ )]
78
136
  pub fn scalar_to_json_expr(scalar_type: &str, rust_expr: &str) -> String {
79
137
  match scalar_type {
80
138
  "String" => format!("crate::kernel::Json::Str({rust_expr}.clone())"),
@@ -86,9 +144,12 @@ pub fn scalar_to_json_expr(scalar_type: &str, rust_expr: &str) -> String {
86
144
  }
87
145
 
88
146
  /// The attribute name of a single-field value object (Ruby's bare-scalar shorthand), else `None`.
89
- pub fn sole_field_of(type_name: &str, value_objects_by_name: &HashMap<String, &Json>) -> Option<String> {
147
+ pub fn sole_field_of(
148
+ type_name: &str,
149
+ value_objects_by_name: &HashMap<String, &Json>,
150
+ ) -> Option<String> {
90
151
  let vo = value_objects_by_name.get(type_name)?;
91
- let attrs = vo.get("attributes").map(Json::each).unwrap_or(&[]);
152
+ let attrs = vo.get("attributes").map_or_default(Json::each);
92
153
  if attrs.len() == 1 {
93
154
  Some(crate::attr::name(&attrs[0]).to_string())
94
155
  } else {
@@ -98,74 +159,128 @@ pub fn sole_field_of(type_name: &str, value_objects_by_name: &HashMap<String, &J
98
159
 
99
160
  /// `NestedType::from_json(expr)?`, via `coerce_single_field` for a single-field type.
100
161
  /// A multi-field type is shape-checked first: only this site knows the attribute name Ruby quotes.
101
- pub fn composite_from_json_expr(attr: &Json, value_objects_by_name: &HashMap<String, &Json>, value_expr: &str) -> String {
162
+ pub fn composite_from_json_expr(
163
+ attr: &Json,
164
+ value_objects_by_name: &HashMap<String, &Json>,
165
+ value_expr: &str,
166
+ ) -> String {
102
167
  let nested_type = naming::rust_ident(crate::attr::type_name(attr));
103
- match sole_field_of(crate::attr::type_name(attr), value_objects_by_name) {
104
- Some(sole) => format!("{nested_type}::from_json(&{value_expr}.coerce_single_field({}))?", naming::ruby_inspect_string(&sole)),
105
- None => format!(
106
- "{nested_type}::from_json({value_expr}.expect_value_object_shape({}, {})?)?",
107
- naming::ruby_inspect_string(crate::attr::name(attr)),
108
- naming::ruby_inspect_string(crate::attr::type_name(attr))
109
- ),
110
- }
168
+ sole_field_of(crate::attr::type_name(attr), value_objects_by_name).map_or_else(
169
+ || {
170
+ format!(
171
+ "{nested_type}::from_json({value_expr}.expect_value_object_shape({}, {})?)?",
172
+ naming::ruby_inspect_string(crate::attr::name(attr)),
173
+ naming::ruby_inspect_string(crate::attr::type_name(attr))
174
+ )
175
+ },
176
+ |sole| {
177
+ format!(
178
+ "{nested_type}::from_json(&{value_expr}.coerce_single_field({}))?",
179
+ naming::ruby_inspect_string(&sole)
180
+ )
181
+ },
182
+ )
111
183
  }
112
184
 
113
185
  /// Scalar elements (`list_of(String)`, `has_many` references) have no generated `from_json`;
114
186
  /// they read as scalars minus the trailing `?`. Single-field value objects coerce bare scalars.
115
- pub fn list_element_from_json_mapper(struct_name: &str, key: &str, attr: &Json, value_objects_by_name: &HashMap<String, &Json>) -> String {
116
- match naming::effective_scalar_type(crate::attr::type_name(attr)) {
117
- Some(scalar) => {
118
- let body = scalar_from_json_value_expr(struct_name, key, scalar, "item");
119
- let body = body.strip_suffix('?').unwrap_or(&body);
120
- format!("|item| {body}")
121
- }
122
- None => {
123
- let nested_type = naming::rust_ident(crate::attr::type_name(attr));
124
- match sole_field_of(crate::attr::type_name(attr), value_objects_by_name) {
125
- Some(sole) => format!("|item| {nested_type}::from_json(&item.coerce_single_field({}))", naming::ruby_inspect_string(&sole)),
126
- None => format!("{nested_type}::from_json"),
127
- }
128
- }
187
+ #[allow(
188
+ clippy::option_if_let_else,
189
+ reason = "a map_or_else closure would bury this long body"
190
+ )]
191
+ pub fn list_element_from_json_mapper(
192
+ struct_name: &str,
193
+ key: &str,
194
+ attr: &Json,
195
+ value_objects_by_name: &HashMap<String, &Json>,
196
+ ) -> String {
197
+ if let Some(scalar) = naming::effective_scalar_type(crate::attr::type_name(attr)) {
198
+ let body = scalar_from_json_value_expr(struct_name, key, scalar, "item");
199
+ let body = body.strip_suffix('?').unwrap_or(&body);
200
+ format!("|item| {body}")
201
+ } else {
202
+ let nested_type = naming::rust_ident(crate::attr::type_name(attr));
203
+ sole_field_of(crate::attr::type_name(attr), value_objects_by_name).map_or_else(
204
+ || format!("{nested_type}::from_json"),
205
+ |sole| {
206
+ format!(
207
+ "|item| {nested_type}::from_json(&item.coerce_single_field({}))",
208
+ naming::ruby_inspect_string(&sole)
209
+ )
210
+ },
211
+ )
129
212
  }
130
213
  }
131
214
 
132
215
  /// Inverse of `list_element_from_json_mapper`: scalar elements have no `.to_json()` method.
133
216
  pub fn list_element_to_json_expr(attr: &Json) -> String {
134
- match naming::effective_scalar_type(crate::attr::type_name(attr)) {
135
- Some(scalar) => scalar_to_json_expr(scalar, &naming::deref_scalar(scalar, "x")),
136
- None => "x.to_json()".to_string(),
137
- }
217
+ naming::effective_scalar_type(crate::attr::type_name(attr)).map_or_else(
218
+ || "x.to_json()".to_string(),
219
+ |scalar| scalar_to_json_expr(scalar, &naming::deref_scalar(scalar, "x")),
220
+ )
138
221
  }
139
222
 
140
223
  /// A required composite argument offered as `null` reads as an empty object, like an omitted key.
141
224
  ///
142
225
  /// Builds one owned value up front: mixing arm-local temporaries does not borrow-check (E0716).
143
- pub fn required_composite_argument_expr(struct_name: &str, key: &str, attr: &Json, value_objects_by_name: &HashMap<String, &Json>) -> String {
226
+ pub fn required_composite_argument_expr(
227
+ struct_name: &str,
228
+ key: &str,
229
+ attr: &Json,
230
+ value_objects_by_name: &HashMap<String, &Json>,
231
+ ) -> String {
144
232
  let fetch = required_field_expr(struct_name, key, crate::attr::type_name(attr));
145
233
  let nested_type = naming::rust_ident(crate::attr::type_name(attr));
146
234
  let guarded = format!("match {fetch} {{ crate::kernel::Json::Null => crate::kernel::Json::Object(Vec::new()), other => other.clone() }}");
147
- match sole_field_of(crate::attr::type_name(attr), value_objects_by_name) {
148
- Some(sole) => format!("{nested_type}::from_json(&({guarded}).coerce_single_field({}))?", naming::ruby_inspect_string(&sole)),
149
- None => format!(
150
- "{nested_type}::from_json(({guarded}).expect_value_object_shape({}, {})?)?",
151
- naming::ruby_inspect_string(crate::attr::name(attr)),
152
- naming::ruby_inspect_string(crate::attr::type_name(attr))
153
- ),
154
- }
235
+ sole_field_of(crate::attr::type_name(attr), value_objects_by_name).map_or_else(
236
+ || {
237
+ format!(
238
+ "{nested_type}::from_json(({guarded}).expect_value_object_shape({}, {})?)?",
239
+ naming::ruby_inspect_string(crate::attr::name(attr)),
240
+ naming::ruby_inspect_string(crate::attr::type_name(attr))
241
+ )
242
+ },
243
+ |sole| {
244
+ format!(
245
+ "{nested_type}::from_json(&({guarded}).coerce_single_field({}))?",
246
+ naming::ruby_inspect_string(&sole)
247
+ )
248
+ },
249
+ )
155
250
  }
156
251
 
157
252
  /// The names `ArgumentGate#refuse_unknown_arguments` accepts: declared attributes plus the
158
253
  /// reference key, identity heads and saga correlation keys.
159
254
  ///
160
255
  /// `extra_identity_heads` is the entity's own identity head for entity commands, else `&[]`.
161
- pub fn command_argument_allowlist(aggregate: &Json, command: &Json, process_managers: &[Json], extra_identity_heads: &[String]) -> Vec<String> {
162
- let references = command.get("references").map(Json::to_s).unwrap_or_default();
163
- let reference_key = if references.is_empty() { None } else { Some(crate::hecks_naming::reference_key(&references)) };
256
+ pub fn command_argument_allowlist(
257
+ aggregate: &Json,
258
+ command: &Json,
259
+ process_managers: &[Json],
260
+ extra_identity_heads: &[String],
261
+ ) -> Vec<String> {
262
+ let references = command
263
+ .get("references")
264
+ .map(Json::to_s)
265
+ .unwrap_or_default();
266
+ let reference_key = if references.is_empty() {
267
+ None
268
+ } else {
269
+ Some(crate::hecks_naming::reference_key(&references))
270
+ };
164
271
 
165
- let identity_heads: Vec<String> = aggregate.get("identified_by").map(Json::each).unwrap_or(&[]).iter().map(|p| p.to_s().split('.').next().unwrap_or("").to_string()).collect();
272
+ let identity_heads: Vec<String> = aggregate
273
+ .get("identified_by")
274
+ .map_or_default(Json::each)
275
+ .iter()
276
+ .map(|p| p.to_s().split('.').next().unwrap_or("").to_string())
277
+ .collect();
166
278
 
167
- let correlation_keys: Vec<String> =
168
- process_managers.iter().filter_map(|pm| pm.get("correlates_by").map(Json::to_s)).filter_map(|c| c.split('.').next().map(|s| s.to_string())).collect();
279
+ let correlation_keys: Vec<String> = process_managers
280
+ .iter()
281
+ .filter_map(|pm| pm.get("correlates_by").map(Json::to_s))
282
+ .filter_map(|c| c.split('.').next().map(std::string::ToString::to_string))
283
+ .collect();
169
284
 
170
285
  let mut out: Vec<String> = vec!["id".to_string()];
171
286
  if let Some(rk) = reference_key {
@@ -191,14 +306,21 @@ pub fn command_argument_allowlist(aggregate: &Json, command: &Json, process_mana
191
306
  ///
192
307
  /// Goes through the site's `<Variant>Args` struct, so leaving an argument out does not compile.
193
308
  fn emit_absent_argument_check(command_name: &str, attributes: &[Json]) -> String {
194
- let mut required: Vec<String> = attributes.iter().filter(|a| !crate::attr::optional(a)).map(|a| naming::rust_field(crate::attr::name(a))).collect();
309
+ let mut required: Vec<String> = attributes
310
+ .iter()
311
+ .filter(|a| !crate::attr::optional(a))
312
+ .map(|a| naming::rust_field(crate::attr::name(a)))
313
+ .collect();
195
314
  required.sort();
196
315
  if required.is_empty() {
197
316
  return String::new();
198
317
  }
199
- let declared: Vec<String> = attributes.iter().map(|a| crate::attr::name(a).to_string()).collect();
318
+ let declared: Vec<String> = attributes
319
+ .iter()
320
+ .map(|a| crate::attr::name(a).to_string())
321
+ .collect();
200
322
  format!(
201
- "let absent: Vec<&str> = [{}].into_iter().filter(|key| v.get(key).is_none()).collect();\nif !absent.is_empty() {{\n return Err(crate::kernel::Refusal::AbsentArgument(crate::kernel::refusal_wording::AbsentArgumentAbsentArgsArgs {{\n command: {},\n absent: &absent,\n declared: &[{}],\n }}.render_args()));\n}}\n",
323
+ "let required: &[&str] = &[{}];\nlet absent: Vec<&str> = required.iter().copied().filter(|key| v.get(key).is_none()).collect();\nif !absent.is_empty() {{\n return Err(crate::kernel::Refusal::AbsentArgument(crate::kernel::refusal_wording::AbsentArgumentAbsentArgsArgs {{\n command: {},\n absent: &absent,\n declared: &[{}],\n }}.render_args()));\n}}\n",
202
324
  required.iter().map(|k| naming::ruby_inspect_string(k)).collect::<Vec<_>>().join(", "),
203
325
  naming::ruby_inspect_string(command_name),
204
326
  declared.iter().map(|k| naming::ruby_inspect_string(k)).collect::<Vec<_>>().join(", "),
@@ -207,9 +329,13 @@ fn emit_absent_argument_check(command_name: &str, attributes: &[Json]) -> String
207
329
 
208
330
  /// Both name lists go over raw to `UnknownArgumentUnknownArgsArgs::render_args`, which reads
209
331
  /// the same vocabulary rows as Ruby's `RefusalWording.render_site`; no join or "none" here.
210
- fn emit_unknown_argument_check(command_name: &str, known_keys: &[String], declared_names: &[String]) -> String {
332
+ fn emit_unknown_argument_check(
333
+ command_name: &str,
334
+ known_keys: &[String],
335
+ declared_names: &[String],
336
+ ) -> String {
211
337
  format!(
212
- "let unknown = v.unknown_keys(&[{}]);\nif !unknown.is_empty() {{\n let unknown: Vec<&str> = unknown.iter().map(|key| key.as_str()).collect();\n return Err(crate::kernel::Refusal::UnknownArgument(crate::kernel::refusal_wording::UnknownArgumentUnknownArgsArgs {{\n command: {},\n unknown: &unknown,\n declared: &[{}],\n }}.render_args()));\n}}\n",
338
+ "let unknown = v.unknown_keys(&[{}]);\nif !unknown.is_empty() {{\n let unknown: Vec<&str> = unknown.iter().map(String::as_str).collect();\n return Err(crate::kernel::Refusal::UnknownArgument(crate::kernel::refusal_wording::UnknownArgumentUnknownArgsArgs {{\n command: {},\n unknown: &unknown,\n declared: &[{}],\n }}.render_args()));\n}}\n",
213
339
  known_keys.iter().map(|k| naming::ruby_inspect_string(k)).collect::<Vec<_>>().join(", "),
214
340
  naming::ruby_inspect_string(command_name),
215
341
  declared_names.iter().map(|k| naming::ruby_inspect_string(k)).collect::<Vec<_>>().join(", "),
@@ -217,16 +343,31 @@ fn emit_unknown_argument_check(command_name: &str, known_keys: &[String], declar
217
343
  }
218
344
 
219
345
  /// The unknown-argument check then the absent-argument check, in Ruby's DISPATCH_ORDER.
220
- pub fn unknown_and_absent_argument_checks(command_name: &str, attributes: &[Json], unknown_argument_allowlist: Option<&[String]>, absent_argument_check: bool) -> String {
346
+ #[allow(
347
+ clippy::option_if_let_else,
348
+ reason = "a map_or_else closure would bury this long body"
349
+ )]
350
+ pub fn unknown_and_absent_argument_checks(
351
+ command_name: &str,
352
+ attributes: &[Json],
353
+ unknown_argument_allowlist: Option<&[String]>,
354
+ absent_argument_check: bool,
355
+ ) -> String {
221
356
  let mut check = match unknown_argument_allowlist {
222
357
  Some(allowlist) => {
223
- let mut known_keys: Vec<String> = attributes.iter().map(|a| naming::rust_field(crate::attr::name(a))).collect();
358
+ let mut known_keys: Vec<String> = attributes
359
+ .iter()
360
+ .map(|a| naming::rust_field(crate::attr::name(a)))
361
+ .collect();
224
362
  for k in allowlist {
225
363
  if !known_keys.contains(k) {
226
364
  known_keys.push(k.clone());
227
365
  }
228
366
  }
229
- let declared_names: Vec<String> = attributes.iter().map(|a| crate::attr::name(a).to_string()).collect();
367
+ let declared_names: Vec<String> = attributes
368
+ .iter()
369
+ .map(|a| crate::attr::name(a).to_string())
370
+ .collect();
230
371
  emit_unknown_argument_check(command_name, &known_keys, &declared_names)
231
372
  }
232
373
  None => String::new(),
@@ -239,9 +380,19 @@ pub fn unknown_and_absent_argument_checks(command_name: &str, attributes: &[Json
239
380
 
240
381
  /// One generated function per argument-gate step, beside a command's `from_json`, for
241
382
  /// `kernel::decode_aggregate_arguments`/`decode_entity_arguments` to call in step order.
242
- pub fn emit_argument_gates(struct_name: &str, command_name: &str, attributes: &[Json], unknown_argument_allowlist: Option<&[String]>) -> String {
383
+ pub fn emit_argument_gates(
384
+ struct_name: &str,
385
+ command_name: &str,
386
+ attributes: &[Json],
387
+ unknown_argument_allowlist: Option<&[String]>,
388
+ ) -> String {
243
389
  let unknown = match unknown_argument_allowlist {
244
- Some(_) => unknown_and_absent_argument_checks(command_name, attributes, unknown_argument_allowlist, false),
390
+ Some(_) => unknown_and_absent_argument_checks(
391
+ command_name,
392
+ attributes,
393
+ unknown_argument_allowlist,
394
+ false,
395
+ ),
245
396
  None => String::new(),
246
397
  };
247
398
  let absent = emit_absent_argument_check(command_name, attributes);
@@ -264,6 +415,10 @@ fn argument_gate_fn(name: &str, body: &str) -> String {
264
415
  /// before the next field is built, as in Ruby's `coerce_declared_arguments`; this needs
265
416
  /// `aggregates_by_name`. Otherwise the value object is one struct literal.
266
417
  #[allow(clippy::too_many_arguments)]
418
+ #[allow(
419
+ clippy::expect_used,
420
+ reason = "the message names an invariant an earlier check in this generator established"
421
+ )]
267
422
  pub fn emit_from_json_flat(
268
423
  exemplar: &Exemplar,
269
424
  struct_name: &str,
@@ -274,12 +429,23 @@ pub fn emit_from_json_flat(
274
429
  absent_argument_check: bool,
275
430
  interleave_checks: bool,
276
431
  aggregates_by_name: Option<&HashMap<String, &Json>>,
432
+ scalar_owner: Option<&str>,
277
433
  ) -> String {
278
434
  let command_name = command_name.unwrap_or(struct_name);
279
435
  // A refusal names the command (`Write.slugs expects ...`), as Ruby's `Value.for_attribute`
280
436
  // does, not the generated `WriteArgs` struct that holds its arguments.
281
- let error_name = if struct_name == format!("{command_name}Args") { command_name } else { struct_name };
282
- let idents: Vec<String> = attributes.iter().map(|attr| naming::rust_ident_field(crate::attr::name(attr))).collect();
437
+ let error_name = if struct_name == format!("{command_name}Args") {
438
+ command_name
439
+ } else {
440
+ struct_name
441
+ };
442
+ // A mistyped bare primitive names the declaring aggregate (Ruby's `check_bare_primitive`), while
443
+ // the list and shape refusals above keep naming the command.
444
+ let scalar_error_name = scalar_owner.unwrap_or(error_name);
445
+ let idents: Vec<String> = attributes
446
+ .iter()
447
+ .map(|attr| naming::rust_ident_field(crate::attr::name(attr)))
448
+ .collect();
283
449
  let field_exprs: Vec<String> = attributes
284
450
  .iter()
285
451
  .zip(idents.iter())
@@ -305,13 +471,13 @@ pub fn emit_from_json_flat(
305
471
  "match v.get({}) {{ Some(crate::kernel::Json::Null) | None => Vec::new(), Some(x) => x.as_array().ok_or_else(|| {shape_error})?.iter().map({mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?, }}",
306
472
  naming::ruby_inspect_string(&key)
307
473
  )
308
- } else if optional && scalar.is_some() {
474
+ } else if let (true, Some(scalar)) = (optional, scalar) {
309
475
  // An optional argument offered as null is the same absence as an omitted key.
310
- format!("match v.get({}) {{ Some(crate::kernel::Json::Null) | None => None, Some(x) => Some({}) }}", naming::ruby_inspect_string(&key), scalar_from_json_value_expr(error_name, &key, scalar.unwrap(), "x"))
476
+ format!("match v.get({}) {{ Some(crate::kernel::Json::Null) | None => None, Some(x) => Some({}) }}", naming::ruby_inspect_string(&key), scalar_from_json_value_expr(scalar_error_name, &key, scalar, "x"))
311
477
  } else if optional {
312
478
  format!("match v.get({}) {{ Some(crate::kernel::Json::Null) | None => None, Some(x) => Some({}) }}", naming::ruby_inspect_string(&key), composite_from_json_expr(attr, value_objects_by_name, "x"))
313
479
  } else if let Some(scalar) = scalar {
314
- scalar_from_json_expr(error_name, &key, scalar, crate::attr::default(attr))
480
+ scalar_from_json_expr(scalar_error_name, &key, scalar, crate::attr::default(attr))
315
481
  } else if absent_argument_check {
316
482
  // The argument entry point only; see `required_composite_argument_expr`.
317
483
  required_composite_argument_expr(error_name, &key, attr, value_objects_by_name)
@@ -339,10 +505,25 @@ pub fn emit_from_json_flat(
339
505
  })
340
506
  .collect();
341
507
 
342
- let unknown_check = unknown_and_absent_argument_checks(command_name, attributes, unknown_argument_allowlist, absent_argument_check);
508
+ let unknown_check = unknown_and_absent_argument_checks(
509
+ command_name,
510
+ attributes,
511
+ unknown_argument_allowlist,
512
+ absent_argument_check,
513
+ );
343
514
 
344
- let shorthand_fields = if interleave_checks { Some(idents.as_slice()) } else { None };
345
- emit_from_json_skeleton(exemplar, struct_name, &field_exprs, &unknown_check, shorthand_fields)
515
+ let shorthand_fields = if interleave_checks {
516
+ Some(idents.as_slice())
517
+ } else {
518
+ None
519
+ };
520
+ emit_from_json_skeleton(
521
+ exemplar,
522
+ struct_name,
523
+ &field_exprs,
524
+ &unknown_check,
525
+ shorthand_fields,
526
+ )
346
527
  }
347
528
 
348
529
  fn emit_object_shape_check(struct_name: &str) -> String {
@@ -353,7 +534,17 @@ fn emit_object_shape_check(struct_name: &str) -> String {
353
534
 
354
535
  /// `shorthand_fields: None` folds `ident: rhs,` lines into the struct literal; `Some(idents)`
355
536
  /// puts the `let`+check blocks in the preamble and closes the literal with shorthand init.
356
- fn emit_from_json_skeleton(exemplar: &Exemplar, struct_name: &str, field_exprs: &[String], unknown_check: &str, shorthand_fields: Option<&[String]>) -> String {
537
+ #[allow(
538
+ clippy::option_if_let_else,
539
+ reason = "a map_or_else closure would bury this long body"
540
+ )]
541
+ fn emit_from_json_skeleton(
542
+ exemplar: &Exemplar,
543
+ struct_name: &str,
544
+ field_exprs: &[String],
545
+ unknown_check: &str,
546
+ shorthand_fields: Option<&[String]>,
547
+ ) -> String {
357
548
  let mut preamble = format!("{}{unknown_check}", emit_object_shape_check(struct_name));
358
549
  let field_block = match shorthand_fields {
359
550
  Some(idents) => {
@@ -361,9 +552,17 @@ fn emit_from_json_skeleton(exemplar: &Exemplar, struct_name: &str, field_exprs:
361
552
  preamble.push_str(f);
362
553
  preamble.push('\n');
363
554
  }
364
- idents.iter().map(|f| format!(" {f},")).collect::<Vec<_>>().join("\n")
555
+ idents
556
+ .iter()
557
+ .map(|f| format!(" {f},"))
558
+ .collect::<Vec<_>>()
559
+ .join("\n")
365
560
  }
366
- None => field_exprs.iter().map(|f| format!(" {f}")).collect::<Vec<_>>().join("\n"),
561
+ None => field_exprs
562
+ .iter()
563
+ .map(|f| format!(" {f}"))
564
+ .collect::<Vec<_>>()
565
+ .join("\n"),
367
566
  };
368
567
  format!(
369
568
  "{}\n",
@@ -371,14 +570,28 @@ fn emit_from_json_skeleton(exemplar: &Exemplar, struct_name: &str, field_exprs:
371
570
  "from_json_flat",
372
571
  &[
373
572
  ("TmplFlatType2", struct_name.to_string()),
374
- ("let _tmpl_unknown_check_placeholder = ();\n Ok(Self {", format!("{preamble} Ok(Self {{")),
573
+ (
574
+ "let _tmpl_unknown_check_placeholder = ();\n Ok(Self {",
575
+ format!("{preamble} Ok(Self {{")
576
+ ),
375
577
  ("tmpl_ident: tmpl_rhs_placeholder(),", field_block),
376
578
  ],
377
579
  )
378
580
  )
379
581
  }
380
582
 
381
- pub fn emit_to_json_flat(exemplar: &Exemplar, struct_name: &str, attributes: &[Json], value_objects_by_name: &HashMap<String, &Json>, optional: bool, extra_fields: &[(String, String)], aggregate: Option<&Json>) -> String {
583
+ #[allow(
584
+ clippy::unwrap_used,
585
+ reason = "the value was checked just before; None would mean invalid IR"
586
+ )]
587
+ pub fn emit_to_json_flat(
588
+ exemplar: &Exemplar,
589
+ struct_name: &str,
590
+ attributes: &[Json],
591
+ optional: bool,
592
+ extra_fields: &[(String, String)],
593
+ aggregate: Option<&Json>,
594
+ ) -> String {
382
595
  let mut field_exprs: Vec<String> = attributes
383
596
  .iter()
384
597
  .map(|attr| {
@@ -387,19 +600,19 @@ pub fn emit_to_json_flat(exemplar: &Exemplar, struct_name: &str, attributes: &[J
387
600
  let scalar = naming::effective_scalar_type(crate::attr::type_name(attr));
388
601
  let list = crate::attr::list(attr);
389
602
 
390
- let record_optional_list = list && aggregate.map(|a| crate::shared::list_attr_creation_optional(a, crate::attr::name(attr), value_objects_by_name)).unwrap_or(false);
603
+ let record_optional_list = list && aggregate.is_some_and(|a| crate::shared::list_attr_creation_optional(a, crate::attr::name(attr)));
391
604
  let field_optional = optional || crate::attr::optional(attr);
392
605
  let list_is_optional = if aggregate.is_some() { record_optional_list } else { crate::attr::optional(attr) };
393
606
 
394
607
  let elem_to_json = if list { Some(list_element_to_json_expr(attr)) } else { None };
395
608
  let value_expr = if list && list_is_optional {
396
- format!("self.{ident}.as_ref().map(|v| crate::kernel::Json::Array(v.iter().map(|x| {}).collect())).unwrap_or(crate::kernel::Json::Null)", elem_to_json.as_deref().unwrap())
609
+ format!("self.{ident}.as_ref().map_or(crate::kernel::Json::Null, |v| crate::kernel::Json::Array(v.iter().map(|x| {}).collect()))", elem_to_json.as_deref().unwrap())
397
610
  } else if list {
398
611
  format!("crate::kernel::Json::Array(self.{ident}.iter().map(|x| {}).collect())", elem_to_json.as_deref().unwrap())
399
- } else if field_optional && scalar.is_some() {
400
- format!("self.{ident}.as_ref().map(|v| {}).unwrap_or(crate::kernel::Json::Null)", scalar_to_json_expr(scalar.unwrap(), &naming::deref_scalar(scalar.unwrap(), "v")))
612
+ } else if let (true, Some(scalar)) = (field_optional, scalar) {
613
+ format!("self.{ident}.as_ref().map_or(crate::kernel::Json::Null, |v| {})", scalar_to_json_expr(scalar, &naming::deref_scalar(scalar, "v")))
401
614
  } else if field_optional {
402
- format!("self.{ident}.as_ref().map(|v| v.to_json()).unwrap_or(crate::kernel::Json::Null)")
615
+ format!("self.{ident}.as_ref().map_or(crate::kernel::Json::Null, |v| v.to_json())")
403
616
  } else if let Some(scalar) = scalar {
404
617
  scalar_to_json_expr(scalar, &format!("self.{ident}"))
405
618
  } else {
@@ -409,28 +622,85 @@ pub fn emit_to_json_flat(exemplar: &Exemplar, struct_name: &str, attributes: &[J
409
622
  })
410
623
  .collect();
411
624
 
412
- field_exprs.extend(extra_fields.iter().map(|(key, expr)| exemplar.render("to_json_field", &[("\"tmpl_field_name\"", naming::ruby_inspect_string(key)), ("tmpl_json_value_placeholder()", expr.clone())])));
625
+ field_exprs.extend(extra_fields.iter().map(|(key, expr)| {
626
+ exemplar.render(
627
+ "to_json_field",
628
+ &[
629
+ ("\"tmpl_field_name\"", naming::ruby_inspect_string(key)),
630
+ ("tmpl_json_value_placeholder()", expr.clone()),
631
+ ],
632
+ )
633
+ }));
413
634
  // `corrects` flag fields are read off `aggregate`, not `extra_fields` (String-only).
414
635
  if let Some(aggregate) = aggregate {
415
- field_exprs.extend(crate::bridging::correctable_event_names(aggregate).iter().map(|ev| {
416
- let field = crate::bridging::corrects_flag_field(ev);
417
- exemplar.render("to_json_field", &[("\"tmpl_field_name\"", naming::ruby_inspect_string(&field)), ("tmpl_json_value_placeholder()", format!("crate::kernel::Json::Bool(self.{field})"))])
418
- }));
636
+ field_exprs.extend(
637
+ crate::bridging::correctable_event_names(aggregate)
638
+ .iter()
639
+ .map(|ev| {
640
+ let field = crate::bridging::corrects_flag_field(ev);
641
+ exemplar.render(
642
+ "to_json_field",
643
+ &[
644
+ ("\"tmpl_field_name\"", naming::ruby_inspect_string(&field)),
645
+ (
646
+ "tmpl_json_value_placeholder()",
647
+ format!("crate::kernel::Json::Bool(self.{field})"),
648
+ ),
649
+ ],
650
+ )
651
+ }),
652
+ );
419
653
  }
420
- let field_block = field_exprs.iter().map(|f| format!(" {f}")).collect::<Vec<_>>().join("\n");
654
+ let field_block = field_exprs
655
+ .iter()
656
+ .map(|f| format!(" {f}"))
657
+ .collect::<Vec<_>>()
658
+ .join("\n");
421
659
 
422
- format!("{}\n", exemplar.render("to_json_flat", &[("TmplFlatType2", struct_name.to_string()), ("tmpl_to_json_field_block()", field_block)]))
660
+ format!(
661
+ "{}\n",
662
+ exemplar.render(
663
+ "to_json_flat",
664
+ &[
665
+ ("TmplFlatType2", struct_name.to_string()),
666
+ ("tmpl_to_json_field_block()", field_block)
667
+ ]
668
+ )
669
+ )
423
670
  }
424
671
 
425
672
  /// Command args structs only: an absent optional argument is absent from the event payload, as in
426
673
  /// Ruby. Re-reads the dense field block so the two forms cannot disagree about a field.
427
- pub fn emit_to_json_flat_sparse(exemplar: &Exemplar, struct_name: &str, attributes: &[Json], value_objects_by_name: &HashMap<String, &Json>) -> String {
428
- let dense = emit_to_json_flat(exemplar, struct_name, attributes, value_objects_by_name, false, &[], None);
674
+ #[allow(
675
+ clippy::expect_used,
676
+ reason = "the message names an invariant an earlier check in this generator established"
677
+ )]
678
+ pub fn emit_to_json_flat_sparse(
679
+ exemplar: &Exemplar,
680
+ struct_name: &str,
681
+ attributes: &[Json],
682
+ ) -> String {
683
+ let dense = emit_to_json_flat(exemplar, struct_name, attributes, false, &[], None);
429
684
  let open = "vec![\n";
430
- let start = dense.find(open).map(|i| i + open.len()).expect("to_json_flat renders a vec! literal");
431
- let end = dense[start..].find(" ])").map(|i| start + i).expect("to_json_flat closes its vec! literal");
685
+ let start = dense
686
+ .find(open)
687
+ .map(|i| i + open.len())
688
+ .expect("to_json_flat renders a vec! literal");
689
+ let end = dense[start..]
690
+ .find(" ])")
691
+ .map(|i| start + i)
692
+ .expect("to_json_flat closes its vec! literal");
432
693
  let field_block = dense[start..end].trim_end_matches('\n').to_string();
433
- format!("{}\n", exemplar.render("to_json_flat_sparse", &[("TmplFlatType3", struct_name.to_string()), ("tmpl_to_json_field_block_sparse()", field_block)]))
694
+ format!(
695
+ "{}\n",
696
+ exemplar.render(
697
+ "to_json_flat_sparse",
698
+ &[
699
+ ("TmplFlatType3", struct_name.to_string()),
700
+ ("tmpl_to_json_field_block_sparse()", field_block)
701
+ ]
702
+ )
703
+ )
434
704
  }
435
705
 
436
706
  pub fn emit_from_json_state(
@@ -450,7 +720,7 @@ pub fn emit_from_json_state(
450
720
  let scalar = naming::effective_scalar_type(crate::attr::type_name(attr));
451
721
  let list = crate::attr::list(attr);
452
722
 
453
- let record_optional_list = list && aggregate.map(|a| crate::shared::list_attr_creation_optional(a, crate::attr::name(attr), value_objects_by_name)).unwrap_or(false);
723
+ let record_optional_list = list && aggregate.is_some_and(|a| crate::shared::list_attr_creation_optional(a, crate::attr::name(attr)));
454
724
  let list_is_optional = if aggregate.is_some() { record_optional_list } else { crate::attr::optional(attr) };
455
725
  let field_optional = optional || crate::attr::optional(attr);
456
726
 
@@ -467,11 +737,11 @@ pub fn emit_from_json_state(
467
737
  "match v.get({}).and_then(crate::kernel::Json::as_array) {{ Some(items) => items.iter().map({mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?, None => Vec::new(), }}",
468
738
  naming::ruby_inspect_string(&key)
469
739
  )
470
- } else if field_optional && scalar.is_some() {
740
+ } else if let (true, Some(scalar)) = (field_optional, scalar) {
471
741
  format!(
472
742
  "match v.get({}) {{ Some(&crate::kernel::Json::Null) | None => None, Some(x) => Some({}), }}",
473
743
  naming::ruby_inspect_string(&key),
474
- scalar_from_json_value_expr(struct_name, &key, scalar.unwrap(), "x")
744
+ scalar_from_json_value_expr(struct_name, &key, scalar, "x")
475
745
  )
476
746
  } else if field_optional {
477
747
  format!(
@@ -490,8 +760,16 @@ pub fn emit_from_json_state(
490
760
 
491
761
  for (key, _serialize_expr) in extra_fields {
492
762
  let ident = naming::rust_ident_field(key);
493
- let rhs = format!("v.require({}, {})?.as_str().ok_or_else(|| {})?.to_string()", naming::ruby_inspect_string(key), naming::ruby_inspect_string(struct_name), json_type_error(struct_name, key, "a string"));
494
- field_exprs.push(exemplar.render("field_assignment", &[("tmpl_ident", ident), ("tmpl_rhs_placeholder()", rhs)]));
763
+ let rhs = format!(
764
+ "v.require({}, {})?.as_str().ok_or_else(|| {})?.to_string()",
765
+ naming::ruby_inspect_string(key),
766
+ naming::ruby_inspect_string(struct_name),
767
+ json_type_error(struct_name, key, "a string")
768
+ );
769
+ field_exprs.push(exemplar.render(
770
+ "field_assignment",
771
+ &[("tmpl_ident", ident), ("tmpl_rhs_placeholder()", rhs)],
772
+ ));
495
773
  }
496
774
  // `corrects` flag fields: see `emit_to_json_flat`.
497
775
  if let Some(aggregate) = aggregate {
@@ -504,7 +782,10 @@ pub fn emit_from_json_state(
504
782
  naming::ruby_inspect_string(struct_name),
505
783
  json_type_error(struct_name, &field, "a boolean")
506
784
  );
507
- field_exprs.push(exemplar.render("field_assignment", &[("tmpl_ident", ident), ("tmpl_rhs_placeholder()", rhs)]));
785
+ field_exprs.push(exemplar.render(
786
+ "field_assignment",
787
+ &[("tmpl_ident", ident), ("tmpl_rhs_placeholder()", rhs)],
788
+ ));
508
789
  }
509
790
  }
510
791
 
@@ -513,12 +794,15 @@ pub fn emit_from_json_state(
513
794
 
514
795
  pub fn emit_closed_set_codec(exemplar: &Exemplar, vo: &Json) -> String {
515
796
  let name = naming::rust_ident(vo.get("name").and_then(Json::as_str).unwrap_or(""));
516
- let attributes = vo.get("attributes").map(Json::each).unwrap_or(&[]);
797
+ let attributes = vo.get("attributes").map_or_default(Json::each);
517
798
  let sole_attribute = &attributes[0];
518
799
  let sole_attribute_name = crate::attr::name(sole_attribute);
519
- let sole_attribute_type = sole_attribute.get("type").and_then(Json::as_str).unwrap_or("");
800
+ let sole_attribute_type = sole_attribute
801
+ .get("type")
802
+ .and_then(Json::as_str)
803
+ .unwrap_or("");
520
804
  let field_name = naming::rust_field(sole_attribute_name);
521
- let members = vo.get("members").map(Json::each).unwrap_or(&[]);
805
+ let members = vo.get("members").map_or_default(Json::each);
522
806
 
523
807
  let rows: Vec<(String, String)> = members
524
808
  .iter()
@@ -532,35 +816,71 @@ pub fn emit_closed_set_codec(exemplar: &Exemplar, vo: &Json) -> String {
532
816
 
533
817
  let row_subs: Vec<Vec<(&str, String)>> = rows
534
818
  .iter()
535
- .map(|(variant, raw)| vec![("TmplKind", name.clone()), ("TmplMemberA", variant.clone()), ("\"tmpl_member_a\"", naming::ruby_inspect_string(raw))])
819
+ .map(|(variant, raw)| {
820
+ vec![
821
+ ("TmplMemberA", variant.clone()),
822
+ ("\"tmpl_member_a\"", naming::ruby_inspect_string(raw)),
823
+ ]
824
+ })
536
825
  .collect();
537
826
 
538
- let type_name = vo.get("name").and_then(Json::as_str).unwrap_or("").to_string();
827
+ let type_name = vo
828
+ .get("name")
829
+ .and_then(Json::as_str)
830
+ .unwrap_or("")
831
+ .to_string();
539
832
  // The member list goes over raw; `render_args` quotes and joins it.
540
- let admitted = format!("[{}]", rows.iter().map(|(_v, raw)| naming::ruby_inspect_string(raw)).collect::<Vec<_>>().join(", "));
833
+ let admitted = format!(
834
+ "[{}]",
835
+ rows.iter()
836
+ .map(|(_v, raw)| naming::ruby_inspect_string(raw))
837
+ .collect::<Vec<_>>()
838
+ .join(", ")
839
+ );
541
840
  // Same wording as `required_field_expr`, resolved at codegen time from the sole field.
542
- let null_message = format!("{type_name}.{sole_attribute_name} expects {sole_attribute_type}, got nil");
841
+ let null_message =
842
+ format!("{type_name}.{sole_attribute_name} expects {sole_attribute_type}, got nil");
543
843
 
544
844
  exemplar.assemble(
545
845
  "closed_set_codec",
546
846
  &[
547
847
  ("TmplKind", name),
548
- ("\"tmpl_field_name\"", naming::ruby_inspect_string(&field_name)),
549
- ("\"tmpl_closed_set_type\"", naming::ruby_inspect_string(&type_name)),
848
+ (
849
+ "\"tmpl_field_name\"",
850
+ naming::ruby_inspect_string(&field_name),
851
+ ),
852
+ (
853
+ "\"tmpl_closed_set_type\"",
854
+ naming::ruby_inspect_string(&type_name),
855
+ ),
550
856
  ("[\"tmpl_closed_set_member_a\"]", admitted),
551
- ("\"tmpl_null_field_message\"", naming::ruby_inspect_string(&null_message)),
857
+ (
858
+ "\"tmpl_null_field_message\"",
859
+ naming::ruby_inspect_string(&null_message),
860
+ ),
552
861
  ],
553
862
  &[
554
- ("closed_set_codec:TO_JSON_ARM", exemplar.render_each("closed_set_codec:TO_JSON_ARM", &row_subs, "\n")),
555
- ("closed_set_codec:FROM_JSON_ARM", exemplar.render_each("closed_set_codec:FROM_JSON_ARM", &row_subs, "\n")),
863
+ (
864
+ "closed_set_codec:TO_JSON_ARM",
865
+ exemplar.render_each("closed_set_codec:TO_JSON_ARM", &row_subs, "\n"),
866
+ ),
867
+ (
868
+ "closed_set_codec:FROM_JSON_ARM",
869
+ exemplar.render_each("closed_set_codec:FROM_JSON_ARM", &row_subs, "\n"),
870
+ ),
556
871
  ],
557
872
  )
558
873
  }
559
874
 
875
+ #[allow(
876
+ clippy::unwrap_used,
877
+ clippy::panic,
878
+ reason = "the value was checked just before; None would mean invalid IR, which aborts generation on purpose"
879
+ )]
560
880
  pub fn emit_closed_set_table_codec(exemplar: &Exemplar, vo: &Json) -> String {
561
881
  let name = naming::rust_ident(vo.get("name").and_then(Json::as_str).unwrap_or(""));
562
882
  let const_name = naming::screaming_snake(vo.get("name").and_then(Json::as_str).unwrap_or(""));
563
- let attributes = vo.get("attributes").map(Json::each).unwrap_or(&[]);
883
+ let attributes = vo.get("attributes").map_or_default(Json::each);
564
884
 
565
885
  let to_json_fields: Vec<String> = attributes
566
886
  .iter()
@@ -572,29 +892,57 @@ pub fn emit_closed_set_table_codec(exemplar: &Exemplar, vo: &Json) -> String {
572
892
  Some("String") => format!("crate::kernel::Json::Str(self.{ident}.to_string())"),
573
893
  Some("Integer") => format!("crate::kernel::Json::int(self.{ident})"),
574
894
  Some("Float") => format!("crate::kernel::Json::Float(self.{ident})"),
575
- Some("TrueClass" | "FalseClass") => format!("crate::kernel::Json::Bool(self.{ident})"),
576
- _ => String::new(),
895
+ Some("TrueClass" | "FalseClass") => {
896
+ format!("crate::kernel::Json::Bool(self.{ident})")
897
+ }
898
+ other => panic!("no to_json expr for closed-set attribute scalar {other:?}"),
577
899
  };
578
- exemplar.render("to_json_field", &[("\"tmpl_field_name\"", naming::ruby_inspect_string(&key)), ("tmpl_json_value_placeholder()", value_expr)])
900
+ exemplar.render(
901
+ "to_json_field",
902
+ &[
903
+ ("\"tmpl_field_name\"", naming::ruby_inspect_string(&key)),
904
+ ("tmpl_json_value_placeholder()", value_expr),
905
+ ],
906
+ )
579
907
  })
580
908
  .collect();
581
- let to_json_fields_block = to_json_fields.iter().map(|f| format!(" {f}")).collect::<Vec<_>>().join("\n");
909
+ let to_json_fields_block = to_json_fields
910
+ .iter()
911
+ .map(|f| format!(" {f}"))
912
+ .collect::<Vec<_>>()
913
+ .join("\n");
582
914
 
583
915
  let match_conditions: Vec<String> = attributes
584
916
  .iter()
585
917
  .map(|attr| {
586
918
  let key = naming::rust_field(crate::attr::name(attr));
587
919
  let ident = naming::rust_ident_field(crate::attr::name(attr));
588
- let accessor = scalar_json_accessor(naming::effective_scalar_type(crate::attr::type_name(attr)).unwrap());
920
+ let accessor = scalar_json_accessor(
921
+ naming::effective_scalar_type(crate::attr::type_name(attr)).unwrap(),
922
+ );
589
923
  exemplar.render(
590
924
  "closed_set_table_from_json_condition",
591
- &[("\"tmpl_field_name\"", naming::ruby_inspect_string(&key)), ("tmpl_accessor_fn", format!("crate::kernel::Json::{accessor}")), ("tmpl_field", ident)],
925
+ &[
926
+ ("\"tmpl_field_name\"", naming::ruby_inspect_string(&key)),
927
+ (
928
+ "tmpl_accessor_fn",
929
+ format!("crate::kernel::Json::{accessor}"),
930
+ ),
931
+ ("tmpl_field", ident),
932
+ ],
592
933
  )
593
934
  })
594
935
  .collect();
595
936
 
596
- let type_name = vo.get("name").and_then(Json::as_str).unwrap_or("").to_string();
597
- let field_names: Vec<String> = attributes.iter().map(|attr| naming::ruby_inspect_string(&naming::rust_field(crate::attr::name(attr)))).collect();
937
+ let type_name = vo
938
+ .get("name")
939
+ .and_then(Json::as_str)
940
+ .unwrap_or("")
941
+ .to_string();
942
+ let field_names: Vec<String> = attributes
943
+ .iter()
944
+ .map(|attr| naming::ruby_inspect_string(&naming::rust_field(crate::attr::name(attr))))
945
+ .collect();
598
946
  // A field left out (or null) refuses as "{type}.{field} expects {expected}, got nil", before
599
947
  // membership is judged.
600
948
  let absent_checks: Vec<String> = attributes
@@ -612,15 +960,22 @@ pub fn emit_closed_set_table_codec(exemplar: &Exemplar, vo: &Json) -> String {
612
960
  // The refusal quotes the first column of every row, as Ruby's `refuse_non_member!` does.
613
961
  let admitted: Vec<String> = vo
614
962
  .get("members")
615
- .map(Json::each)
616
- .unwrap_or(&[])
963
+ .map_or_default(Json::each)
617
964
  .iter()
618
965
  .map(|row| {
619
- let first = row.as_array().unwrap_or(&[]).first().and_then(Json::as_array).unwrap_or(&[]);
966
+ let first = row
967
+ .as_array()
968
+ .unwrap_or(&[])
969
+ .first()
970
+ .and_then(Json::as_array)
971
+ .unwrap_or(&[]);
620
972
  naming::ruby_inspect_string(&first.get(1).map(Json::to_s).unwrap_or_default())
621
973
  })
622
974
  .collect();
623
- let first_field = attributes.first().map(|attr| naming::rust_field(crate::attr::name(attr))).unwrap_or_default();
975
+ let first_field = attributes
976
+ .first()
977
+ .map(|attr| naming::rust_field(crate::attr::name(attr)))
978
+ .unwrap_or_default();
624
979
 
625
980
  exemplar.render(
626
981
  "closed_set_table_codec",
@@ -629,17 +984,26 @@ pub fn emit_closed_set_table_codec(exemplar: &Exemplar, vo: &Json) -> String {
629
984
  ("tmpl_to_json_fields_block()", to_json_fields_block),
630
985
  ("TMPL_TABLE", const_name),
631
986
  ("tmpl_from_json_conditions()", match_conditions.join(" && ")),
632
- ("\"tmpl_table_type\"", naming::ruby_inspect_string(&type_name)),
633
- ("TMPL_DECLARED_NAMES", format!("[{}]", field_names.join(", "))),
987
+ (
988
+ "\"tmpl_table_type\"",
989
+ naming::ruby_inspect_string(&type_name),
990
+ ),
991
+ (
992
+ "TMPL_DECLARED_NAMES",
993
+ format!("[{}]", field_names.join(", ")),
994
+ ),
634
995
  ("TMPL_ADMITTED", format!("[{}]", admitted.join(", "))),
635
996
  ("tmpl_absent_field_checks();", absent_checks.join("\n")),
636
- ("\"tmpl_first_field\"", naming::ruby_inspect_string(&first_field)),
997
+ (
998
+ "\"tmpl_first_field\"",
999
+ naming::ruby_inspect_string(&first_field),
1000
+ ),
637
1001
  ],
638
1002
  )
639
1003
  }
640
1004
 
641
1005
  pub fn extract_id_supported(aggregate: &Json) -> bool {
642
- let identified_by = aggregate.get("identified_by").map(Json::each).unwrap_or(&[]);
1006
+ let identified_by = aggregate.get("identified_by").map_or_default(Json::each);
643
1007
  identified_by.iter().all(|path| {
644
1008
  let path = path.as_str().unwrap_or("");
645
1009
  let mut parts = path.split('.');
@@ -656,26 +1020,54 @@ pub fn emit_extract_id(exemplar: &Exemplar, aggregate: &Json) -> String {
656
1020
  /// `extract_id` minted as `extract_id_lenient` on `to_id_component_lenient`; entity addressing
657
1021
  /// only, never a root aggregate's hydrate.
658
1022
  pub fn emit_extract_id_lenient(exemplar: &Exemplar, entity: &Json) -> String {
659
- emit_extract_id_shaped(exemplar, entity, "extract_id_lenient", "to_id_component_lenient")
1023
+ emit_extract_id_shaped(
1024
+ exemplar,
1025
+ entity,
1026
+ "extract_id_lenient",
1027
+ "to_id_component_lenient",
1028
+ )
660
1029
  }
661
1030
 
662
- fn emit_extract_id_shaped(exemplar: &Exemplar, aggregate: &Json, method_name: &str, coercion: &str) -> String {
1031
+ fn emit_extract_id_shaped(
1032
+ exemplar: &Exemplar,
1033
+ aggregate: &Json,
1034
+ method_name: &str,
1035
+ coercion: &str,
1036
+ ) -> String {
663
1037
  let name = naming::rust_ident(aggregate.get("name").and_then(Json::as_str).unwrap_or(""));
664
- let identified_by: Vec<String> = aggregate.get("identified_by").map(Json::each).unwrap_or(&[]).iter().map(Json::to_s).collect();
665
- let reference_key = crate::hecks_naming::snake(aggregate.get("name").and_then(Json::as_str).unwrap_or(""));
1038
+ let identified_by: Vec<String> = aggregate
1039
+ .get("identified_by")
1040
+ .map_or_default(Json::each)
1041
+ .iter()
1042
+ .map(Json::to_s)
1043
+ .collect();
1044
+ let reference_key =
1045
+ crate::hecks_naming::snake(aggregate.get("name").and_then(Json::as_str).unwrap_or(""));
666
1046
 
667
1047
  // `tmpl_id_coercion` is also set per tier1 entry: `compose` checks the nested `TIER1_LINE`
668
1048
  // slot for leftover placeholders before the outer text is substituted.
669
1049
  let tier1_subs: Vec<Vec<(&str, String)>> = identified_by
670
1050
  .iter()
671
1051
  .enumerate()
672
- .map(|(i, path)| vec![("\"tmpl_path\"", naming::ruby_inspect_string(path)), ("c0", format!("c{i}")), ("tmpl_id_coercion", coercion.to_string())])
1052
+ .map(|(i, path)| {
1053
+ vec![
1054
+ ("\"tmpl_path\"", naming::ruby_inspect_string(path)),
1055
+ ("c0", format!("c{i}")),
1056
+ ("tmpl_id_coercion", coercion.to_string()),
1057
+ ]
1058
+ })
673
1059
  .collect();
674
1060
 
675
1061
  let tier1_join = if identified_by.len() == 1 {
676
1062
  "c0".to_string()
677
1063
  } else {
678
- format!("vec![{}].join(\":\")", (0..identified_by.len()).map(|i| format!("c{i}")).collect::<Vec<_>>().join(", "))
1064
+ format!(
1065
+ "[{}].join(\":\")",
1066
+ (0..identified_by.len())
1067
+ .map(|i| format!("c{i}"))
1068
+ .collect::<Vec<_>>()
1069
+ .join(", ")
1070
+ )
679
1071
  };
680
1072
 
681
1073
  let mut tried = identified_by.clone();
@@ -689,9 +1081,15 @@ fn emit_extract_id_shaped(exemplar: &Exemplar, aggregate: &Json, method_name: &s
689
1081
  ("TmplExtractIdType", name.clone()),
690
1082
  ("tmpl_extract_id_name", method_name.to_string()),
691
1083
  ("tmpl_id_coercion", coercion.to_string()),
692
- ("\"tmpl_reference_key\"", naming::ruby_inspect_string(&reference_key)),
1084
+ (
1085
+ "\"tmpl_reference_key\"",
1086
+ naming::ruby_inspect_string(&reference_key),
1087
+ ),
693
1088
  ("tmpl_tier1_join_placeholder()", tier1_join),
694
- ("\"tmpl_error_text\"", naming::ruby_inspect_string(&format!("{name}: no identity found (tried {tried})"))),
1089
+ (
1090
+ "\"tmpl_error_text\"",
1091
+ naming::ruby_inspect_string(&format!("{name}: no identity found (tried {tried})")),
1092
+ ),
695
1093
  ],
696
1094
  "extract_id:TIER1_LINE",
697
1095
  &tier1_subs,
@@ -701,23 +1099,51 @@ fn emit_extract_id_shaped(exemplar: &Exemplar, aggregate: &Json, method_name: &s
701
1099
 
702
1100
  pub fn emit_extract_wants(exemplar: &Exemplar, entity: &Json) -> String {
703
1101
  let name = naming::rust_ident(entity.get("name").and_then(Json::as_str).unwrap_or(""));
704
- let identified_by: Vec<String> = entity.get("identified_by").map(Json::each).unwrap_or(&[]).iter().map(Json::to_s).collect();
1102
+ let identified_by: Vec<String> = entity
1103
+ .get("identified_by")
1104
+ .map_or_default(Json::each)
1105
+ .iter()
1106
+ .map(Json::to_s)
1107
+ .collect();
705
1108
 
706
- let tier1_subs: Vec<Vec<(&str, String)>> =
707
- identified_by.iter().enumerate().map(|(i, path)| vec![("\"tmpl_path\"", naming::ruby_inspect_string(path)), ("c0", format!("c{i}"))]).collect();
1109
+ let tier1_subs: Vec<Vec<(&str, String)>> = identified_by
1110
+ .iter()
1111
+ .enumerate()
1112
+ .map(|(i, path)| {
1113
+ vec![
1114
+ ("\"tmpl_path\"", naming::ruby_inspect_string(path)),
1115
+ ("c0", format!("c{i}")),
1116
+ ]
1117
+ })
1118
+ .collect();
708
1119
 
709
1120
  let wants_join = if identified_by.len() == 1 {
710
1121
  "c0".to_string()
711
1122
  } else {
712
- format!("vec![{}].join(\", \")", (0..identified_by.len()).map(|i| format!("c{i}")).collect::<Vec<_>>().join(", "))
1123
+ format!(
1124
+ "vec![{}].join(\", \")",
1125
+ (0..identified_by.len())
1126
+ .map(|i| format!("c{i}"))
1127
+ .collect::<Vec<_>>()
1128
+ .join(", ")
1129
+ )
713
1130
  };
714
1131
 
715
- exemplar.compose("extract_wants", &[("TmplExtractWantsType", name), ("tmpl_wants_join_placeholder()", wants_join)], "extract_wants:TIER1_LINE", &tier1_subs, "\n")
1132
+ exemplar.compose(
1133
+ "extract_wants",
1134
+ &[
1135
+ ("TmplExtractWantsType", name),
1136
+ ("tmpl_wants_join_placeholder()", wants_join),
1137
+ ],
1138
+ "extract_wants:TIER1_LINE",
1139
+ &tier1_subs,
1140
+ "\n",
1141
+ )
716
1142
  }
717
1143
 
718
1144
  pub fn emit_self_identity(exemplar: &Exemplar, entity: &Json) -> String {
719
1145
  let name = naming::rust_ident(entity.get("name").and_then(Json::as_str).unwrap_or(""));
720
- let identified_by = entity.get("identified_by").map(Json::each).unwrap_or(&[]);
1146
+ let identified_by = entity.get("identified_by").map_or_default(Json::each);
721
1147
 
722
1148
  let components: Vec<String> = identified_by
723
1149
  .iter()
@@ -733,7 +1159,17 @@ pub fn emit_self_identity(exemplar: &Exemplar, entity: &Json) -> String {
733
1159
  format!("{out}.to_string()")
734
1160
  })
735
1161
  .collect();
736
- let body = if components.len() == 1 { components[0].clone() } else { format!("vec![{}].join(\":\")", components.join(", ")) };
1162
+ let body = if components.len() == 1 {
1163
+ components[0].clone()
1164
+ } else {
1165
+ format!("[{}].join(\":\")", components.join(", "))
1166
+ };
737
1167
 
738
- exemplar.render("self_identity", &[("TmplSelfIdentityType", name), ("tmpl_identity_body_placeholder()", body)])
1168
+ exemplar.render(
1169
+ "self_identity",
1170
+ &[
1171
+ ("TmplSelfIdentityType", name),
1172
+ ("tmpl_identity_body_placeholder()", body),
1173
+ ],
1174
+ )
739
1175
  }