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
@@ -1,9 +1,21 @@
1
- //! Port of the subset of `lib/hecks/naming.rb` that codegen calls:
2
- //! `snake`, `demodulise` and `reference_key`.
1
+ //! The one Rust port of `lib/hecks/naming.rb`: `demodulise`, `reference_key`, `snake`, `pascal`,
2
+ //! `plural` and `singularize`, std-only so every Cargo project here can compile this same file
3
+ //! with `#[path = "..."] mod hecks_naming;` instead of keeping a copy that can drift.
4
+ //!
5
+ //! Each function follows the Ruby rule for rule. The Ruby patterns are ASCII-only while
6
+ //! `String#downcase` and `String#upcase` use the full Unicode mapping a character at a time, so
7
+ //! the boundaries below test ASCII and the case folds do not.
8
+ //! `spec/fixtures/naming_vectors.json` is asserted by both runtimes.
3
9
 
4
- /// `type.to_s.split("::").last.to_s`
10
+ /// `type.to_s.split("::").last.to_s`. Ruby's `split` drops trailing empty parts, so a trailing
11
+ /// `::` names the segment before it.
5
12
  pub fn demodulise(type_name: &str) -> String {
6
- type_name.rsplit("::").next().unwrap_or(type_name).to_string()
13
+ type_name
14
+ .split("::")
15
+ .filter(|part| !part.is_empty())
16
+ .last()
17
+ .unwrap_or("")
18
+ .to_string()
7
19
  }
8
20
 
9
21
  /// `snake(demodulise(type)).to_sym`
@@ -11,23 +23,119 @@ pub fn reference_key(type_name: &str) -> String {
11
23
  snake(&demodulise(type_name))
12
24
  }
13
25
 
14
- /// CamelCase to snake_case, matching Ruby's two-gsub `Naming.snake` in a single pass.
26
+ /// `Naming.snake`: PascalCase or camelCase to snake_case.
15
27
  ///
16
- /// Inserts `_` before an uppercase letter that follows a lowercase letter or digit, or that
17
- /// ends an acronym (uppercase before, lowercase after).
18
- pub fn snake(text: &str) -> String {
19
- let chars: Vec<char> = text.chars().collect();
28
+ /// The two Ruby `gsub` passes (`([A-Z]+)([A-Z][a-z])` then `([a-z\d])([A-Z])`), then the
29
+ /// per-character Unicode downcase.
30
+ pub fn snake(name: &str) -> String {
31
+ ruby_downcase(&word_boundary(&acronym_boundary(name)))
32
+ }
33
+
34
+ /// Lowercases the way Ruby's `String#downcase` does: each character on its own, with the full
35
+ /// Unicode mapping. `str::to_lowercase` differs in one place, a word-final capital sigma, which
36
+ /// it turns into the final form `ς` where Ruby gives `σ`; and `to_ascii_lowercase` leaves every
37
+ /// non-ASCII capital alone. Names derived from a bluebook must come out the same in both runtimes.
38
+ pub fn ruby_downcase(text: &str) -> String {
39
+ text.chars().flat_map(char::to_lowercase).collect()
40
+ }
41
+
42
+ /// `Naming.pascal`: snake_case to PascalCase. Each `_`-separated part has its first character
43
+ /// upcased with the full Unicode mapping, as `String#upcase` does.
44
+ pub fn pascal(text: &str) -> String {
45
+ text.split('_')
46
+ .map(|part| {
47
+ let mut chars = part.chars();
48
+ chars.next().map_or_else(String::new, |first| {
49
+ first.to_uppercase().collect::<String>() + chars.as_str()
50
+ })
51
+ })
52
+ .collect()
53
+ }
54
+
55
+ /// `Naming.plural`: "y" after a consonant becomes "ies", a sibilant takes "es", else "s".
56
+ pub fn plural(word: &str) -> String {
57
+ let mut tail = word.chars().rev();
58
+ if tail.next() == Some('y') && tail.next().is_some_and(|c| !"aeiou".contains(c)) {
59
+ return format!("{}ies", &word[..word.len() - 1]);
60
+ }
61
+ if ends_in_sibilant(word) {
62
+ return format!("{word}es");
63
+ }
64
+ format!("{word}s")
65
+ }
66
+
67
+ /// `Naming.singularize`: the crude inverse of `plural` ("ies" to "y", a sibilant's "es" and
68
+ /// then a trailing "s" dropped); it only has to find a name someone already wrote.
69
+ pub fn singularize(word: &str) -> String {
70
+ let length = word.chars().count();
71
+ if length > 3 {
72
+ if let Some(stem) = word.strip_suffix("ies") {
73
+ return format!("{stem}y");
74
+ }
75
+ // Undo `plural`'s "es" after s/x/z/ch/sh before the bare-"s" rule, so "Boxes" gives
76
+ // "Box" while "Invoices" still gives "Invoice".
77
+ if let Some(stem) = word.strip_suffix("es") {
78
+ if ends_in_sibilant(stem) {
79
+ return stem.to_string();
80
+ }
81
+ }
82
+ }
83
+ if length > 1 {
84
+ if let Some(stem) = word.strip_suffix('s') {
85
+ return stem.to_string();
86
+ }
87
+ }
88
+ word.to_string()
89
+ }
90
+
91
+ /// `/(s|x|z|ch|sh)\z/`
92
+ fn ends_in_sibilant(word: &str) -> bool {
93
+ ["s", "x", "z", "ch", "sh"]
94
+ .iter()
95
+ .any(|suffix| word.ends_with(suffix))
96
+ }
97
+
98
+ /// The first `gsub`: `([A-Z]+)([A-Z][a-z])` becomes `\1_\2`.
99
+ fn acronym_boundary(s: &str) -> String {
100
+ let chars: Vec<char> = s.chars().collect();
101
+ let mut out = String::new();
102
+ let mut i = 0;
103
+ while i < chars.len() {
104
+ if chars[i].is_ascii_uppercase() {
105
+ let mut j = i;
106
+ while j < chars.len() && chars[j].is_ascii_uppercase() {
107
+ j += 1;
108
+ }
109
+ let run_len = j - i;
110
+ if run_len >= 2 && j < chars.len() && chars[j].is_ascii_lowercase() {
111
+ out.extend(&chars[i..j - 1]);
112
+ out.push('_');
113
+ out.push(chars[j - 1]);
114
+ i = j;
115
+ continue;
116
+ }
117
+ out.extend(&chars[i..j]);
118
+ i = j;
119
+ continue;
120
+ }
121
+ out.push(chars[i]);
122
+ i += 1;
123
+ }
124
+ out
125
+ }
126
+
127
+ /// The second `gsub`: `([a-z\d])([A-Z])` becomes `\1_\2`, with `\d` ASCII as in Ruby.
128
+ fn word_boundary(s: &str) -> String {
129
+ let chars: Vec<char> = s.chars().collect();
20
130
  let mut out = String::new();
21
- for (i, &c) in chars.iter().enumerate() {
22
- if c.is_ascii_uppercase() && i > 0 {
23
- let prev = chars[i - 1];
24
- let next = chars.get(i + 1).copied();
25
- let insert = (prev.is_ascii_lowercase() || prev.is_ascii_digit()) || (prev.is_ascii_uppercase() && next.map(|n| n.is_ascii_lowercase()).unwrap_or(false));
26
- if insert {
131
+ for (idx, &c) in chars.iter().enumerate() {
132
+ if idx > 0 && c.is_ascii_uppercase() {
133
+ let prev = chars[idx - 1];
134
+ if prev.is_ascii_lowercase() || prev.is_ascii_digit() {
27
135
  out.push('_');
28
136
  }
29
137
  }
30
138
  out.push(c);
31
139
  }
32
- out.to_lowercase()
140
+ out
33
141
  }
@@ -0,0 +1,81 @@
1
+ //! Pins `hecks_naming` (the one Rust port of `lib/hecks/naming.rb`, compiled into several Cargo
2
+ //! projects) against `spec/fixtures/naming_vectors.json`. `spec/naming_vectors_spec.rb` asserts
3
+ //! the same tables against Ruby, so a drift in either runtime fails a test on its own side.
4
+ //! Lives beside the shared file, not in it, because the file is std-only and the tests read JSON.
5
+
6
+ use crate::hecks_naming::{
7
+ demodulise, pascal, plural, reference_key, ruby_downcase, singularize, snake,
8
+ };
9
+ use crate::json::Json;
10
+
11
+ fn rows(section: &str) -> Vec<(String, String)> {
12
+ let table = Json::parse(include_str!("../../../spec/fixtures/naming_vectors.json")).unwrap();
13
+ let rows: Vec<(String, String)> = table
14
+ .get(section)
15
+ .unwrap_or_else(|| panic!("{section} table went missing"))
16
+ .each()
17
+ .iter()
18
+ .map(|row| {
19
+ (
20
+ row.get("input").unwrap().to_s(),
21
+ row.get("expected").unwrap().to_s(),
22
+ )
23
+ })
24
+ .collect();
25
+ assert!(!rows.is_empty(), "{section} rows went missing");
26
+ rows
27
+ }
28
+
29
+ fn assert_every_row(section: &str, rule: fn(&str) -> String) {
30
+ for (input, expected) in rows(section) {
31
+ assert_eq!(rule(&input), expected, "{section}({input:?})");
32
+ }
33
+ }
34
+
35
+ #[test]
36
+ fn snake_matches_every_shared_row() {
37
+ assert_every_row("snake", snake);
38
+ }
39
+
40
+ #[test]
41
+ fn storage_name_is_snake_of_the_demodulised_name() {
42
+ assert_every_row("storage_name", |input| snake(&demodulise(input)));
43
+ }
44
+
45
+ #[test]
46
+ fn pascal_matches_every_shared_row() {
47
+ assert_every_row("pascal", pascal);
48
+ }
49
+
50
+ #[test]
51
+ fn plural_matches_every_shared_row() {
52
+ assert_every_row("plural", plural);
53
+ }
54
+
55
+ #[test]
56
+ fn singularize_matches_every_shared_row() {
57
+ assert_every_row("singularize", singularize);
58
+ }
59
+
60
+ #[test]
61
+ fn demodulise_matches_every_shared_row() {
62
+ assert_every_row("demodulise", demodulise);
63
+ }
64
+
65
+ #[test]
66
+ fn reference_key_matches_every_shared_row() {
67
+ assert_every_row("reference_key", reference_key);
68
+ }
69
+
70
+ #[test]
71
+ fn ruby_downcase_folds_unicode_capitals_one_character_at_a_time() {
72
+ assert_eq!(ruby_downcase("ÉCOLE"), "école");
73
+ assert_eq!(ruby_downcase("ΟΔΥΣΣΕΑΣ"), "οδυσσεασ");
74
+ assert_eq!(ruby_downcase("ascii"), "ascii");
75
+ }
76
+
77
+ #[test]
78
+ fn a_boxes_collection_names_the_box_aggregate_again() {
79
+ assert_eq!(singularize(&plural("Box")), "Box");
80
+ assert_eq!(singularize("Invoices"), "Invoice");
81
+ }
@@ -12,10 +12,10 @@ pub enum Json {
12
12
  /// Raw literal text as scanned, so numbers round-trip unchanged; `number` reads it as Ruby wrote it.
13
13
  Number(String),
14
14
  String(String),
15
- Array(Vec<Json>),
15
+ Array(Vec<Self>),
16
16
  /// Insertion-ordered pairs, matching the declaration order of the Ruby Hash; byte-exact
17
17
  /// re-emission depends on key order.
18
- Object(Vec<(String, Json)>),
18
+ Object(Vec<(String, Self)>),
19
19
  }
20
20
 
21
21
  /// A `Json::Number` as Ruby wrote it: a `.` or an exponent means a Float, anything else an
@@ -27,8 +27,11 @@ pub enum Number {
27
27
  }
28
28
 
29
29
  impl Json {
30
- pub fn parse(text: &str) -> Result<Json, String> {
31
- let mut p = Parser { bytes: text.as_bytes(), pos: 0 };
30
+ pub fn parse(text: &str) -> Result<Self, String> {
31
+ let mut p = Parser {
32
+ bytes: text.as_bytes(),
33
+ pos: 0,
34
+ };
32
35
  p.skip_ws();
33
36
  let value = p.parse_value()?;
34
37
  p.skip_ws();
@@ -39,74 +42,80 @@ impl Json {
39
42
  }
40
43
 
41
44
  /// `ir[:key]`: `None` for a missing key or a `Json::Null` value.
42
- pub fn get(&self, key: &str) -> Option<&Json> {
45
+ pub fn get(&self, key: &str) -> Option<&Self> {
43
46
  match self {
44
- Json::Object(pairs) => pairs.iter().find(|(k, v)| k == key && !matches!(v, Json::Null)).map(|(_, v)| v),
47
+ Self::Object(pairs) => pairs
48
+ .iter()
49
+ .find(|(k, v)| k == key && !matches!(v, Self::Null))
50
+ .map(|(_, v)| v),
45
51
  _ => None,
46
52
  }
47
53
  }
48
54
 
49
55
  /// Like `get`, but distinguishes an absent key from one present as null.
50
- pub fn get_raw(&self, key: &str) -> Option<&Json> {
56
+ pub fn get_raw(&self, key: &str) -> Option<&Self> {
51
57
  match self {
52
- Json::Object(pairs) => pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v),
58
+ Self::Object(pairs) => pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v),
53
59
  _ => None,
54
60
  }
55
61
  }
56
62
 
57
- pub fn get_mut(&mut self, key: &str) -> Option<&mut Json> {
63
+ pub fn get_mut(&mut self, key: &str) -> Option<&mut Self> {
58
64
  match self {
59
- Json::Object(pairs) => pairs.iter_mut().find(|(k, _)| k == key).map(|(_, v)| v),
65
+ Self::Object(pairs) => pairs.iter_mut().find(|(k, _)| k == key).map(|(_, v)| v),
60
66
  _ => None,
61
67
  }
62
68
  }
63
69
 
64
- pub fn as_str(&self) -> Option<&str> {
70
+ pub const fn as_str(&self) -> Option<&str> {
65
71
  match self {
66
- Json::String(s) => Some(s.as_str()),
72
+ Self::String(s) => Some(s.as_str()),
67
73
  _ => None,
68
74
  }
69
75
  }
70
76
 
71
- pub fn as_array(&self) -> Option<&[Json]> {
77
+ pub const fn as_array(&self) -> Option<&[Self]> {
72
78
  match self {
73
- Json::Array(items) => Some(items.as_slice()),
79
+ Self::Array(items) => Some(items.as_slice()),
74
80
  _ => None,
75
81
  }
76
82
  }
77
83
 
78
- pub fn as_array_mut(&mut self) -> Option<&mut Vec<Json>> {
84
+ pub const fn as_array_mut(&mut self) -> Option<&mut Vec<Self>> {
79
85
  match self {
80
- Json::Array(items) => Some(items),
86
+ Self::Array(items) => Some(items),
81
87
  _ => None,
82
88
  }
83
89
  }
84
90
 
85
- pub fn as_object(&self) -> Option<&[(String, Json)]> {
91
+ pub const fn as_object(&self) -> Option<&[(String, Self)]> {
86
92
  match self {
87
- Json::Object(pairs) => Some(pairs.as_slice()),
93
+ Self::Object(pairs) => Some(pairs.as_slice()),
88
94
  _ => None,
89
95
  }
90
96
  }
91
97
 
92
- pub fn as_bool(&self) -> bool {
98
+ pub const fn as_bool(&self) -> bool {
93
99
  // Ruby truthiness: only `false` and `nil` are falsy, so `0` and `""` are truthy.
94
- !matches!(self, Json::Bool(false) | Json::Null)
100
+ !matches!(self, Self::Bool(false) | Self::Null)
95
101
  }
96
102
 
97
103
  /// The number as Ruby wrote it, or `None` for any other value. An integer too large for `i64`
98
104
  /// reads as a Float.
99
105
  pub fn number(&self) -> Option<Number> {
100
- let Json::Number(text) = self else { return None };
101
- if text.contains(|c| matches!(c, '.' | 'e' | 'E')) {
106
+ let Self::Number(text) = self else {
107
+ return None;
108
+ };
109
+ if text.contains(['.', 'e', 'E']) {
102
110
  return text.parse::<f64>().ok().map(Number::Float);
103
111
  }
104
- match text.parse::<i64>() {
105
- Ok(n) => Some(Number::Int(n)),
106
- Err(_) => text.parse::<f64>().ok().map(Number::Float),
107
- }
112
+ text.parse::<i64>().map_or_else(
113
+ |_| text.parse::<f64>().ok().map(Number::Float),
114
+ |n| Some(Number::Int(n)),
115
+ )
108
116
  }
109
117
 
118
+ #[cfg(test)]
110
119
  pub fn as_f64(&self) -> Option<f64> {
111
120
  self.number().map(|n| match n {
112
121
  Number::Int(n) => n as f64,
@@ -114,6 +123,7 @@ impl Json {
114
123
  })
115
124
  }
116
125
 
126
+ #[cfg(test)]
117
127
  pub fn as_i64(&self) -> Option<i64> {
118
128
  self.number().map(|n| match n {
119
129
  Number::Int(n) => n,
@@ -124,26 +134,26 @@ impl Json {
124
134
  /// `#to_s` on a scalar; non-scalars render as the empty string rather than panicking.
125
135
  pub fn to_s(&self) -> String {
126
136
  match self {
127
- Json::Null => String::new(),
128
- Json::Bool(b) => b.to_string(),
129
- Json::Number(text) => match self.number() {
137
+ Self::Null => String::new(),
138
+ Self::Bool(b) => b.to_string(),
139
+ Self::Number(text) => match self.number() {
130
140
  Some(Number::Int(n)) => n.to_string(),
131
141
  Some(Number::Float(n)) => format_number(n),
132
142
  None => text.clone(),
133
143
  },
134
- Json::String(s) => s.clone(),
144
+ Self::String(s) => s.clone(),
135
145
  _ => String::new(),
136
146
  }
137
147
  }
138
148
 
139
149
  /// Array items, or an empty slice for any other value (Ruby's `Array(ir[:key])`).
140
- pub fn each(&self) -> &[Json] {
150
+ pub fn each(&self) -> &[Self] {
141
151
  self.as_array().unwrap_or(&[])
142
152
  }
143
153
 
144
154
  /// Sets `key` to `value`, overwriting in place or else appending last.
145
- pub fn set(&mut self, key: &str, value: Json) {
146
- if let Json::Object(pairs) = self {
155
+ pub fn set(&mut self, key: &str, value: Self) {
156
+ if let Self::Object(pairs) = self {
147
157
  if let Some((_, existing)) = pairs.iter_mut().find(|(k, _)| k == key) {
148
158
  *existing = value;
149
159
  return;
@@ -153,7 +163,7 @@ impl Json {
153
163
  }
154
164
 
155
165
  pub fn set_bool(&mut self, key: &str, value: bool) {
156
- self.set(key, Json::Bool(value));
166
+ self.set(key, Self::Bool(value));
157
167
  }
158
168
  }
159
169
 
@@ -178,7 +188,7 @@ struct Parser<'a> {
178
188
  pos: usize,
179
189
  }
180
190
 
181
- impl<'a> Parser<'a> {
191
+ impl Parser<'_> {
182
192
  fn peek(&self) -> Option<u8> {
183
193
  self.bytes.get(self.pos).copied()
184
194
  }
@@ -198,7 +208,12 @@ impl<'a> Parser<'a> {
198
208
  self.pos += 1;
199
209
  Ok(())
200
210
  } else {
201
- Err(format!("expected {:?} at byte {}, found {:?}", b as char, self.pos, self.peek().map(|c| c as char)))
211
+ Err(format!(
212
+ "expected {:?} at byte {}, found {:?}",
213
+ b as char,
214
+ self.pos,
215
+ self.peek().map(|c| c as char)
216
+ ))
202
217
  }
203
218
  }
204
219
 
@@ -221,7 +236,11 @@ impl<'a> Parser<'a> {
221
236
  Ok(Json::Null)
222
237
  }
223
238
  Some(c) if c == b'-' || c.is_ascii_digit() => self.parse_number(),
224
- other => Err(format!("unexpected byte {:?} at {}", other.map(|c| c as char), self.pos)),
239
+ other => Err(format!(
240
+ "unexpected byte {:?} at {}",
241
+ other.map(|c| c as char),
242
+ self.pos
243
+ )),
225
244
  }
226
245
  }
227
246
 
@@ -259,7 +278,13 @@ impl<'a> Parser<'a> {
259
278
  self.pos += 1;
260
279
  break;
261
280
  }
262
- other => return Err(format!("expected ',' or '}}' at byte {}, found {:?}", self.pos, other.map(|c| c as char))),
281
+ other => {
282
+ return Err(format!(
283
+ "expected ',' or '}}' at byte {}, found {:?}",
284
+ self.pos,
285
+ other.map(|c| c as char)
286
+ ))
287
+ }
263
288
  }
264
289
  }
265
290
  Ok(Json::Object(pairs))
@@ -285,7 +310,13 @@ impl<'a> Parser<'a> {
285
310
  self.pos += 1;
286
311
  break;
287
312
  }
288
- other => return Err(format!("expected ',' or ']' at byte {}, found {:?}", self.pos, other.map(|c| c as char))),
313
+ other => {
314
+ return Err(format!(
315
+ "expected ',' or ']' at byte {}, found {:?}",
316
+ self.pos,
317
+ other.map(|c| c as char)
318
+ ))
319
+ }
289
320
  }
290
321
  }
291
322
  Ok(Json::Array(items))
@@ -338,7 +369,8 @@ impl<'a> Parser<'a> {
338
369
  }
339
370
  Some(b'u') => {
340
371
  self.pos += 1;
341
- let hex = std::str::from_utf8(&self.bytes[self.pos..self.pos + 4]).map_err(|e| e.to_string())?;
372
+ let hex = std::str::from_utf8(&self.bytes[self.pos..self.pos + 4])
373
+ .map_err(|e| e.to_string())?;
342
374
  let code = u32::from_str_radix(hex, 16).map_err(|e| e.to_string())?;
343
375
  self.pos += 4;
344
376
  if let Some(c) = char::from_u32(code) {
@@ -351,7 +383,8 @@ impl<'a> Parser<'a> {
351
383
  Some(_) => {
352
384
  // One UTF-8 char at a time so multi-byte text survives intact.
353
385
  let start = self.pos;
354
- let rest = std::str::from_utf8(&self.bytes[start..]).map_err(|e| e.to_string())?;
386
+ let rest =
387
+ std::str::from_utf8(&self.bytes[start..]).map_err(|e| e.to_string())?;
355
388
  let ch = rest.chars().next().ok_or("empty remainder")?;
356
389
  out.push(ch);
357
390
  self.pos += ch.len_utf8();
@@ -376,9 +409,9 @@ impl<'a> Parser<'a> {
376
409
  self.pos += 1;
377
410
  }
378
411
  }
379
- if matches!(self.peek(), Some(b'e') | Some(b'E')) {
412
+ if matches!(self.peek(), Some(b'e' | b'E')) {
380
413
  self.pos += 1;
381
- if matches!(self.peek(), Some(b'+') | Some(b'-')) {
414
+ if matches!(self.peek(), Some(b'+' | b'-')) {
382
415
  self.pos += 1;
383
416
  }
384
417
  while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
@@ -498,7 +531,10 @@ mod tests {
498
531
  assert_eq!(numbers[1], Some(Number::Int(0)));
499
532
  assert_eq!(numbers[2], Some(Number::Float(1000.0)));
500
533
  assert_eq!(numbers[3], Some(Number::Int(-7)));
501
- assert!(matches!(numbers[4], Some(Number::Float(_))), "an integer past i64 reads as a Float");
534
+ assert!(
535
+ matches!(numbers[4], Some(Number::Float(_))),
536
+ "an integer past i64 reads as a Float"
537
+ );
502
538
  assert_eq!(value.each()[0].to_s(), "0.0");
503
539
  assert_eq!(value.each()[1].to_s(), "0");
504
540
  }