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.
- checksums.yaml +4 -4
- data/examples/pizzas/adapters/fake_processor.rb +34 -0
- data/examples/pizzas/adapters/payment_signature.rb +66 -0
- data/examples/pizzas/adapters/payment_webhook.rb +87 -0
- data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
- data/examples/pizzas/bluebook/pizzas.bluebook +4 -4
- data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
- data/examples/pizzas/config.ru +22 -0
- data/lib/hecks/adapters/driven/prism.rb +0 -2
- data/lib/hecks/adapters/driven.rb +0 -2
- data/lib/hecks/adapters/driving/json.rb +1 -7
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +15 -5
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
- data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
- data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
- data/lib/hecks/bluebook/hecksagon.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
- data/lib/hecks/bluebook/meta_validator.rb +0 -11
- data/lib/hecks/bluebook/named_pattern.rb +33 -0
- data/lib/hecks/bluebook/translation.rb +18 -10
- data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
- data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
- data/lib/hecks/bluebook/translation_expression.rb +39 -0
- data/lib/hecks/bluebook/translation_plan.rb +70 -0
- data/lib/hecks/bluebook.rb +3 -0
- data/lib/hecks/chapters.rb +3 -3
- data/lib/hecks/cli/domain_stub.rb +1 -1
- data/lib/hecks/cli/interview_aggregate.rb +4 -4
- data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
- data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
- data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
- data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
- data/lib/hecks/composer/adapters/jev.adapter +3 -0
- data/lib/hecks/composer/adapters/jev.rb +66 -0
- data/lib/hecks/composer/agent.rb +113 -0
- data/lib/hecks/composer/assistant.rb +110 -0
- data/lib/hecks/composer/behavior_run.rb +53 -0
- data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
- data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
- data/lib/hecks/composer/board.rb +65 -0
- data/lib/hecks/composer/board_edit/command_text.rb +39 -0
- data/lib/hecks/composer/board_edit/source.rb +84 -0
- data/lib/hecks/composer/board_edit.rb +144 -0
- data/lib/hecks/composer/check.rb +89 -0
- data/lib/hecks/composer/domains.rb +81 -0
- data/lib/hecks/composer/jobs.rb +134 -0
- data/lib/hecks/composer/layout.rb +37 -0
- data/lib/hecks/composer/live.rb +150 -0
- data/lib/hecks/composer/outline.rb +48 -0
- data/lib/hecks/composer/page.html +989 -0
- data/lib/hecks/composer/panels.rb +142 -0
- data/lib/hecks/composer/parse.rb +83 -0
- data/lib/hecks/composer/plain.rb +28 -0
- data/lib/hecks/composer/ports/composer_agent.port +7 -0
- data/lib/hecks/composer/session.rb +141 -0
- data/lib/hecks/composer/toolbox.rb +166 -0
- data/lib/hecks/composer/web.rb +156 -0
- data/lib/hecks/composer/workbench.rb +51 -0
- data/lib/hecks/composer/workspace.rb +81 -0
- data/lib/hecks/composer.rb +17 -0
- data/lib/hecks/corpus/adapters.rb +22 -0
- data/lib/hecks/corpus/rust.rb +1 -1
- data/lib/hecks/corpus/worlds.rb +19 -0
- data/lib/hecks/corpus.rb +5 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -34
- data/lib/hecks/forms/app/commands.rb +2 -3
- data/lib/hecks/forms/app/queries.rb +2 -3
- data/lib/hecks/forms/app/records.rb +17 -5
- data/lib/hecks/forms/command_form_renderer.rb +42 -16
- data/lib/hecks/forms/field_controls.rb +2 -2
- data/lib/hecks/forms/field_renderer.rb +1 -1
- data/lib/hecks/forms/field_shape.rb +36 -17
- data/lib/hecks/forms/index_renderer.rb +2 -1
- data/lib/hecks/forms/page.rb +1 -1
- data/lib/hecks/forms/params.rb +0 -13
- data/lib/hecks/forms/query_form_renderer.rb +7 -6
- data/lib/hecks/forms/record_renderer.rb +11 -7
- data/lib/hecks/forms/rule_words.rb +100 -0
- data/lib/hecks/forms/value_object_shape.rb +2 -1
- data/lib/hecks/framework/bluebook/change.bluebook +2 -2
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
- data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
- data/lib/hecks/fuzzing/replay/filters.rb +2 -6
- data/lib/hecks/gate/stages.yml +25 -0
- data/lib/hecks/grammar/evolve/seeds.rb +0 -6
- data/lib/hecks/grammar/evolve.rb +0 -3
- data/lib/hecks/grammar/translation.bluebook +1 -1
- data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
- data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +58 -37
- data/lib/hecks/hecks/composer.behaviors +68 -0
- data/lib/hecks/hecks/context_map.hecksagon +1 -1
- data/lib/hecks/hecks/custodian.behaviors +0 -6
- data/lib/hecks/hecks/custodian.bluebook +15 -92
- data/lib/hecks/hecks/hecks.bluebook +2 -2
- data/lib/hecks/hecks/hecks.hecksagon +2 -2
- data/lib/hecks/hecks/hecks.world +1 -1
- data/lib/hecks/hecks/rehearsal.bluebook +10 -16
- data/lib/hecks/hecks/tooling.bluebook +22 -22
- data/lib/hecks/kernel_capabilities/templates.rb +4 -4
- data/lib/hecks/kernel_capabilities.rb +1 -1
- data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
- data/lib/hecks/language/bluebook/command.bluebook +1 -0
- data/lib/hecks/language/bluebook/entity.bluebook +1 -0
- data/lib/hecks/language/bluebook/query.bluebook +1 -0
- data/lib/hecks/language/bluebook/shape.bluebook +8 -7
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
- data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
- data/lib/hecks/ports/access_control.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
- data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
- data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
- data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
- data/lib/hecks/ports.rb +0 -1
- data/lib/hecks/projection_files.rb +3 -1
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
- data/lib/hecks/projections/glossary/entries.rb +10 -4
- data/lib/hecks/projections/glossary/groups.rb +42 -0
- data/lib/hecks/projections/glossary/html/page.rb +7 -2
- data/lib/hecks/projections/glossary/html/parser.rb +1 -1
- data/lib/hecks/projections/glossary/html/sections.rb +31 -11
- data/lib/hecks/projections/glossary/html.rb +1 -2
- data/lib/hecks/projections/glossary/markdown.rb +31 -28
- data/lib/hecks/projections/glossary/mermaid.rb +3 -0
- data/lib/hecks/projections/glossary/page.css +143 -153
- data/lib/hecks/projections/glossary/page.js +63 -3
- data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
- data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
- data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
- data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
- data/lib/hecks/projections/glossary/sentences.rb +13 -3
- data/lib/hecks/projections/glossary/slugs.rb +4 -1
- data/lib/hecks/projections/glossary.rb +2 -1
- data/lib/hecks/projections/plain_english.rb +97 -0
- data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
- data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary.rb +14 -6
- data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
- data/lib/hecks/projections/statements/actions.rb +99 -0
- data/lib/hecks/projections/statements/relationships.rb +86 -0
- data/lib/hecks/projections/statements.rb +15 -24
- data/lib/hecks/projector/exporter/adapters.rb +112 -0
- data/lib/hecks/projector/exporter/translations.rb +18 -35
- data/lib/hecks/projector/exporter/worlds.rb +103 -0
- data/lib/hecks/projector/exporter.rb +5 -1
- data/lib/hecks/quality_control/adapters/agent.rb +6 -7
- data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +51 -51
- data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
- data/lib/hecks/runtime/outbox.rb +2 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
- data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
- data/lib/hecks/runtime/value.rb +13 -0
- data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
- data/lib/hecks/rust_build/wasm.rb +0 -12
- data/lib/hecks/rust_build/write_if_changed.rb +12 -12
- data/lib/hecks/rust_format.rb +77 -0
- data/lib/hecks/site/bluebook/site.bluebook +5 -5
- data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
- data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
- data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
- data/lib/hecks/tools/fuzz_sweep.rb +1 -3
- data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
- data/lib/hecks/tools/translation_audit.rb +8 -10
- data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +2 -1
- data/lib/hecks.rb +3 -0
- data/prebuilt/{58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
- data/prebuilt/{2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
- data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +1 -0
- data/rust/Cargo.toml +40 -0
- data/rust/build/Cargo.toml +36 -0
- data/rust/build/clippy.toml +4 -0
- data/rust/build/src/adapters_pass.rs +196 -0
- data/rust/build/src/binding_pass.rs +54 -13
- data/rust/build/src/build_artifact.rs +23 -5
- data/rust/build/src/cargo_sync.rs +69 -26
- data/rust/build/src/lineage_pass.rs +101 -87
- data/rust/build/src/main.rs +11 -2
- data/rust/build/src/pipeline.rs +80 -19
- data/rust/build/src/reserved_names.rs +6 -50
- data/rust/build/src/resolve.rs +82 -35
- data/rust/build/src/rustfmt.rs +100 -0
- data/rust/build/src/seams_pass.rs +85 -16
- data/rust/build/src/subprocess.rs +30 -6
- data/rust/build/src/tmp.rs +5 -2
- data/rust/build/src/translation_pass.rs +72 -22
- data/rust/build/src/world_pass.rs +109 -0
- data/rust/clippy.toml +4 -0
- data/rust/codegen/Cargo.toml +36 -0
- data/rust/codegen/clippy.toml +4 -0
- data/rust/codegen/src/asks.rs +40 -14
- data/rust/codegen/src/attr.rs +2 -2
- data/rust/codegen/src/bridging.rs +676 -117
- data/rust/codegen/src/commands.rs +979 -373
- data/rust/codegen/src/constraints.rs +88 -25
- data/rust/codegen/src/dependency_planning.rs +128 -34
- data/rust/codegen/src/domain_generator.rs +300 -170
- data/rust/codegen/src/exemplar.rs +87 -32
- data/rust/codegen/src/expr_emitter.rs +54 -6
- data/rust/codegen/src/fielded.rs +193 -41
- data/rust/codegen/src/hecks_naming.rs +124 -16
- data/rust/codegen/src/hecks_naming_tests.rs +81 -0
- data/rust/codegen/src/json.rs +80 -44
- data/rust/codegen/src/json_codec.rs +585 -149
- data/rust/codegen/src/literal.rs +104 -138
- data/rust/codegen/src/main.rs +145 -52
- data/rust/codegen/src/manifest.rs +76 -14
- data/rust/codegen/src/mutations.rs +1212 -239
- data/rust/codegen/src/naming.rs +111 -60
- data/rust/codegen/src/optional_pass.rs +90 -28
- data/rust/codegen/src/ports.rs +5 -4
- data/rust/codegen/src/prelude.rs +139 -49
- data/rust/codegen/src/queries.rs +601 -226
- data/rust/codegen/src/reactions.rs +268 -74
- data/rust/codegen/src/read_models.rs +606 -205
- data/rust/codegen/src/reference_specs.rs +16 -3
- data/rust/codegen/src/registry.rs +128 -79
- data/rust/codegen/src/reserved_names.rs +6 -50
- data/rust/codegen/src/shared.rs +71 -22
- data/rust/codegen/src/sidecars.rs +15 -5
- data/rust/codegen/src/skip_reason.rs +8 -2
- data/rust/codegen/src/types.rs +306 -108
- data/rust/host/Cargo.lock +1 -0
- data/rust/host/Cargo.toml +40 -4
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/clippy.toml +4 -0
- data/rust/host/src/api.rs +698 -218
- data/rust/host/src/auth.rs +491 -214
- data/rust/host/src/b64.rs +91 -0
- data/rust/host/src/bin/expr_harness.rs +6 -2
- data/rust/host/src/bin/lineage_harness.rs +47 -11
- data/rust/host/src/bin/mint_harness.rs +70 -37
- data/rust/host/src/bin/validate_harness.rs +16 -8
- data/rust/host/src/boot.rs +144 -68
- data/rust/host/src/boundary_fuzz/api.rs +130 -32
- data/rust/host/src/boundary_fuzz/auth.rs +108 -23
- data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
- data/rust/host/src/boundary_fuzz/ir.rs +164 -12
- data/rust/host/src/boundary_fuzz/server.rs +283 -36
- data/rust/host/src/boundary_fuzz/web.rs +56 -8
- data/rust/host/src/checkout.rs +78 -32
- data/rust/host/src/clock.rs +8 -0
- data/rust/host/src/commerce_ir.rs +333 -95
- data/rust/host/src/crypto.rs +33 -0
- data/rust/host/src/dispatch.rs +990 -331
- data/rust/host/src/expr_json.rs +1021 -273
- data/rust/host/src/extension.rs +24 -4
- data/rust/host/src/field_hints.rs +11 -7
- data/rust/host/src/fuzz_support.rs +85 -13
- data/rust/host/src/host.rs +100 -0
- data/rust/host/src/http.rs +22 -0
- data/rust/host/src/ir.rs +326 -16
- data/rust/host/src/journal.rs +277 -176
- data/rust/host/src/lambda_client.rs +219 -53
- data/rust/host/src/lib.rs +16 -4
- data/rust/host/src/log.rs +62 -11
- data/rust/host/src/mint.rs +1153 -326
- data/rust/host/src/needs.rs +185 -44
- data/rust/host/src/payments/keystore.rs +197 -45
- data/rust/host/src/payments/roles.rs +191 -0
- data/rust/host/src/payments/tests.rs +1445 -275
- data/rust/host/src/payments.rs +409 -121
- data/rust/host/src/platform_signin/attack_tests.rs +5 -5
- data/rust/host/src/platform_signin/config.rs +11 -11
- data/rust/host/src/platform_signin/cookie.rs +2 -2
- data/rust/host/src/platform_signin/extension.rs +2 -1
- data/rust/host/src/platform_signin/handler.rs +9 -8
- data/rust/host/src/platform_signin/jti.rs +4 -2
- data/rust/host/src/platform_signin/jwks.rs +2 -1
- data/rust/host/src/platform_signin/jws.rs +2 -37
- data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
- data/rust/host/src/platform_signin/mode_tests.rs +5 -3
- data/rust/host/src/platform_signin/pages.rs +20 -20
- data/rust/host/src/platform_signin/session.rs +8 -1
- data/rust/host/src/platform_signin/settings.rs +6 -6
- data/rust/host/src/platform_signin/strict_json.rs +12 -9
- data/rust/host/src/platform_signin/verify.rs +41 -41
- data/rust/host/src/platform_signin/wiring_support.rs +13 -18
- data/rust/host/src/presentation.rs +207 -63
- data/rust/host/src/presentation_write.rs +502 -187
- data/rust/host/src/query_step.rs +69 -18
- data/rust/host/src/rate_limit.rs +541 -142
- data/rust/host/src/reference_transform.rs +77 -25
- data/rust/host/src/reference_validate.rs +443 -104
- data/rust/host/src/resend.rs +190 -55
- data/rust/host/src/secrets.rs +116 -21
- data/rust/host/src/server.rs +483 -104
- data/rust/host/src/storage_shape.rs +88 -21
- data/rust/host/src/test_pg.rs +17 -7
- data/rust/host/src/ui_schema.rs +495 -207
- data/rust/host/src/wasm_runner.rs +21 -6
- data/rust/host/src/web/commerce.rs +101 -23
- data/rust/host/src/web/newsletter/cooldown.rs +10 -3
- data/rust/host/src/web/newsletter/template.rs +26 -7
- data/rust/host/src/web/newsletter/tests.rs +112 -23
- data/rust/host/src/web/newsletter.rs +209 -59
- data/rust/host/src/web/newsletter_send.rs +251 -79
- data/rust/host/src/web/registration_receipt/tests.rs +16 -4
- data/rust/host/src/web/registration_receipt.rs +100 -24
- data/rust/host/src/web/registrations.rs +1384 -332
- data/rust/host/src/web/resend_webhook.rs +138 -80
- data/rust/host/src/web.rs +2416 -508
- data/rust/lsp/Cargo.toml +36 -0
- data/rust/lsp/clippy.toml +4 -0
- data/rust/lsp/src/json.rs +29 -22
- data/rust/lsp/src/main.rs +81 -31
- data/rust/lsp/src/outline.rs +23 -7
- data/rust/lsp/src/rpc.rs +17 -4
- data/rust/parser/Cargo.toml +36 -0
- data/rust/parser/clippy.toml +4 -0
- data/rust/parser/src/build/closed_sets.rs +6 -1
- data/rust/parser/src/build/identity.rs +4 -3
- data/rust/parser/src/build/mod.rs +1 -0
- data/rust/parser/src/build/named_pattern.rs +26 -0
- data/rust/parser/src/build/naming.rs +2 -80
- data/rust/parser/src/build/pattern_subset.rs +6 -8
- data/rust/parser/src/build/query_derive.rs +3 -1
- data/rust/parser/src/build/query_inference.rs +14 -5
- data/rust/parser/src/build/read_model.rs +4 -1
- data/rust/parser/src/build/references.rs +4 -3
- data/rust/parser/src/canonical.rs +34 -15
- data/rust/parser/src/emit.rs +187 -105
- data/rust/parser/src/expr/ast_json.rs +134 -42
- data/rust/parser/src/expr/evaluator.rs +27 -9
- data/rust/parser/src/expr/mod.rs +54 -13
- data/rust/parser/src/expr/resolver.rs +299 -92
- data/rust/parser/src/ir.rs +35 -3
- data/rust/parser/src/keywords.rs +7168 -510
- data/rust/parser/src/lex.rs +11 -25
- data/rust/parser/src/lib.rs +1 -0
- data/rust/parser/src/main.rs +65 -6
- data/rust/parser/src/parse/adapter.rs +157 -0
- data/rust/parser/src/parse/aggregate.rs +19 -10
- data/rust/parser/src/parse/chapter.rs +16 -17
- data/rust/parser/src/parse/command.rs +35 -16
- data/rust/parser/src/parse/domain_port.rs +4 -7
- data/rust/parser/src/parse/entity.rs +21 -11
- data/rust/parser/src/parse/hecksagon.rs +10 -4
- data/rust/parser/src/parse/lifecycle.rs +9 -5
- data/rust/parser/src/parse/mod.rs +43 -91
- data/rust/parser/src/parse/needs.rs +21 -12
- data/rust/parser/src/parse/policy.rs +2 -2
- data/rust/parser/src/parse/process_manager.rs +9 -11
- data/rust/parser/src/parse/query.rs +8 -4
- data/rust/parser/src/parse/read_model.rs +57 -16
- data/rust/parser/src/parse/value_object.rs +7 -2
- data/rust/parser/src/parse/world.rs +322 -0
- data/rust/parser/src/ruby_value.rs +7 -6
- data/rust/src/exemplar/json.rs +4 -4
- data/rust/src/exemplar/mutations.rs +18 -0
- data/rust/src/exemplar/read_models.rs +1 -10
- data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
- data/rust/src/kernel/attribute_shapes/list.rs +2 -2
- data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
- data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
- data/rust/src/kernel/cli.rs +229 -112
- data/rust/src/kernel/dispatch.rs +323 -115
- data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
- data/rust/src/kernel/expr.rs +203 -86
- data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
- data/rust/src/kernel/expression_operators/comparison.rs +87 -2
- data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
- data/rust/src/kernel/expression_operators/logical.rs +9 -3
- data/rust/src/kernel/expression_operators/membership.rs +3 -1
- data/rust/src/kernel/expression_operators/mod.rs +34 -21
- data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
- data/rust/src/kernel/expression_operators/positional.rs +84 -30
- data/rust/src/kernel/expression_operators/presence.rs +52 -13
- data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
- data/rust/src/kernel/expression_operators/sized.rs +24 -8
- data/rust/src/kernel/expression_operators/text.rs +120 -27
- data/rust/src/kernel/expression_operators/to_string.rs +10 -4
- data/rust/src/kernel/json.rs +230 -148
- data/rust/src/kernel/mod.rs +46 -33
- data/rust/src/kernel/named_query.rs +63 -15
- data/rust/src/kernel/needs.rs +112 -25
- data/rust/src/kernel/orchestrate.rs +980 -581
- data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
- data/rust/src/kernel/pattern.rs +107 -34
- data/rust/src/kernel/query_comparators.rs +118 -41
- data/rust/src/kernel/query_ordering.rs +13 -5
- data/rust/src/kernel/read_model.rs +479 -181
- data/rust/src/kernel/reference_lookup.rs +122 -26
- data/rust/src/kernel/repository.rs +293 -66
- data/rust/src/kernel/routing.rs +58 -42
- data/rust/src/kernel/ruby_float.rs +139 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
- data/rust/src/kernel/vocab/field_hint.rs +19 -25
- data/rust/src/kernel/vocab/mod.rs +6 -6
- data/rust/src/kernel/vocab/query_comparator.rs +23 -24
- data/rust/src/kernel/vocab/refusal_template.rs +501 -297
- data/rust/src/lib.rs +50 -0
- data/rust/src/main.rs +4 -1
- data/rust/web/Cargo.toml +36 -0
- data/rust/web/clippy.toml +4 -0
- metadata +85 -15
- data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
- data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
- data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
- data/lib/hecks/ports/authentication.port +0 -6
- data/lib/hecks/ports/authentication.rb +0 -67
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
- data/prebuilt/verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json +0 -1
- data/rust/host/src/approval.rs +0 -652
data/rust/codegen/src/naming.rs
CHANGED
|
@@ -19,7 +19,9 @@ pub fn reference_type(type_name: &str) -> bool {
|
|
|
19
19
|
/// `X` out of `Reference<X>` — `None` for anything that isn't a reference
|
|
20
20
|
/// type at all.
|
|
21
21
|
pub fn reference_target(type_name: &str) -> Option<&str> {
|
|
22
|
-
type_name
|
|
22
|
+
type_name
|
|
23
|
+
.strip_prefix("Reference<")
|
|
24
|
+
.and_then(|rest| rest.strip_suffix('>'))
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
pub fn effective_scalar_type(type_name: &str) -> Option<&'static str> {
|
|
@@ -36,12 +38,16 @@ pub fn effective_scalar_type(type_name: &str) -> Option<&'static str> {
|
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
#[allow(
|
|
42
|
+
clippy::unwrap_used,
|
|
43
|
+
reason = "the value was checked just before; None would mean invalid IR"
|
|
44
|
+
)]
|
|
39
45
|
pub fn rust_type(type_name: &str, list: bool) -> String {
|
|
40
46
|
let scalar = effective_scalar_type(type_name);
|
|
41
|
-
let inner =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
let inner = scalar.map_or_else(
|
|
48
|
+
|| rust_ident(type_name),
|
|
49
|
+
|s| scalar_rust_type(s).unwrap().to_string(),
|
|
50
|
+
);
|
|
45
51
|
if list {
|
|
46
52
|
format!("Vec<{inner}>")
|
|
47
53
|
} else {
|
|
@@ -50,7 +56,7 @@ pub fn rust_type(type_name: &str, list: bool) -> String {
|
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
pub fn rust_ident(name: &str) -> String {
|
|
53
|
-
name.chars().filter(
|
|
59
|
+
name.chars().filter(char::is_ascii_alphanumeric).collect()
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
/// `cmd.gsub(/(?<=.)([A-Z])/, '_\1').downcase` — insert `_` before every
|
|
@@ -103,12 +109,18 @@ pub fn legal_aggregate_mod_identifier(name: &str) -> bool {
|
|
|
103
109
|
/// Mirrors `valid_domain_mod_name?` — shape, and neither a Rust keyword nor
|
|
104
110
|
/// a reserved Cargo.toml key.
|
|
105
111
|
pub fn valid_domain_mod_name(name: &str) -> bool {
|
|
106
|
-
plain_lower_identifier(name)
|
|
112
|
+
plain_lower_identifier(name)
|
|
113
|
+
&& !RUST_KEYWORDS.contains(&name)
|
|
114
|
+
&& !CARGO_RESERVED_DOMAIN_NAMES.contains(&name)
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
/// `None` when every aggregate name and the domain's module name are usable, else the
|
|
110
118
|
/// refusal text the Ruby generator raises, byte for byte. Aggregates are checked first.
|
|
111
|
-
pub fn reserved_name_refusal(
|
|
119
|
+
pub fn reserved_name_refusal(
|
|
120
|
+
source_label: &str,
|
|
121
|
+
mod_name: &str,
|
|
122
|
+
aggregate_names: &[&str],
|
|
123
|
+
) -> Option<String> {
|
|
112
124
|
let refused: Vec<&str> = aggregate_names
|
|
113
125
|
.iter()
|
|
114
126
|
.copied()
|
|
@@ -118,7 +130,11 @@ pub fn reserved_name_refusal(source_label: &str, mod_name: &str, aggregate_names
|
|
|
118
130
|
})
|
|
119
131
|
.collect();
|
|
120
132
|
if let Some(first) = refused.first() {
|
|
121
|
-
let names = refused
|
|
133
|
+
let names = refused
|
|
134
|
+
.iter()
|
|
135
|
+
.map(|name| format!("{name:?}"))
|
|
136
|
+
.collect::<Vec<_>>()
|
|
137
|
+
.join(", ");
|
|
122
138
|
return Some(format!(
|
|
123
139
|
"{source_label}: aggregate name(s) {names} can't be used as-is — downcased, each becomes a bare Rust \
|
|
124
140
|
module identifier (`pub mod {};`) and a generated file name, and at least one is not a plain identifier \
|
|
@@ -180,7 +196,11 @@ pub fn unsafe_name_refusal(source_label: &str, ir: &Json) -> Option<String> {
|
|
|
180
196
|
if refused.is_empty() {
|
|
181
197
|
return None;
|
|
182
198
|
}
|
|
183
|
-
let list = refused
|
|
199
|
+
let list = refused
|
|
200
|
+
.iter()
|
|
201
|
+
.map(|name| ruby_inspect_string(name))
|
|
202
|
+
.collect::<Vec<_>>()
|
|
203
|
+
.join(", ");
|
|
184
204
|
Some(format!(
|
|
185
205
|
"{source_label}: declared name(s) {list} can't be used as-is — each is written into the generated Rust as \
|
|
186
206
|
an identifier, and at least one is not a plain identifier (letters, digits and underscores, not starting \
|
|
@@ -194,7 +214,10 @@ pub fn rust_field(name: &str) -> String {
|
|
|
194
214
|
|
|
195
215
|
pub fn rust_ident_field(name: &str) -> String {
|
|
196
216
|
let field = rust_field(name);
|
|
197
|
-
if
|
|
217
|
+
if matches!(field.as_str(), "crate" | "self" | "Self" | "super") {
|
|
218
|
+
// Rust refuses these as raw identifiers, so they take a trailing underscore.
|
|
219
|
+
format!("{field}_")
|
|
220
|
+
} else if RUST_KEYWORDS.contains(&field.as_str()) {
|
|
198
221
|
format!("r#{field}")
|
|
199
222
|
} else {
|
|
200
223
|
field
|
|
@@ -208,45 +231,22 @@ pub fn closed_set_variant(value: &str) -> String {
|
|
|
208
231
|
.split(|c: char| !c.is_ascii_alphanumeric())
|
|
209
232
|
.filter(|s| !s.is_empty())
|
|
210
233
|
.map(capitalize)
|
|
211
|
-
.collect::<
|
|
212
|
-
.join("");
|
|
234
|
+
.collect::<String>();
|
|
213
235
|
if variant != "Self" {
|
|
214
236
|
return variant;
|
|
215
237
|
}
|
|
216
|
-
if value.starts_with('S') {
|
|
238
|
+
if value.starts_with('S') {
|
|
239
|
+
"SelfType".to_string()
|
|
240
|
+
} else {
|
|
241
|
+
"SelfValue".to_string()
|
|
242
|
+
}
|
|
217
243
|
}
|
|
218
244
|
|
|
219
245
|
fn capitalize(word: &str) -> String {
|
|
220
246
|
let mut chars = word.chars();
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/// Port of `Hecks::Naming.snake` —
|
|
228
|
-
/// `gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase`.
|
|
229
|
-
pub fn snake(text: &str) -> String {
|
|
230
|
-
let chars: Vec<char> = text.chars().collect();
|
|
231
|
-
let mut first = String::new();
|
|
232
|
-
for (i, &c) in chars.iter().enumerate() {
|
|
233
|
-
let next_upper = chars.get(i + 1).is_some_and(|n| n.is_ascii_uppercase());
|
|
234
|
-
let then_lower = chars.get(i + 2).is_some_and(|n| n.is_ascii_lowercase());
|
|
235
|
-
first.push(c);
|
|
236
|
-
if c.is_ascii_uppercase() && next_upper && then_lower {
|
|
237
|
-
first.push('_');
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
let chars: Vec<char> = first.chars().collect();
|
|
241
|
-
let mut second = String::new();
|
|
242
|
-
for (i, &c) in chars.iter().enumerate() {
|
|
243
|
-
second.push(c);
|
|
244
|
-
let next_upper = chars.get(i + 1).is_some_and(|n| n.is_ascii_uppercase());
|
|
245
|
-
if (c.is_ascii_lowercase() || c.is_ascii_digit()) && next_upper {
|
|
246
|
-
second.push('_');
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
second.to_lowercase()
|
|
247
|
+
chars.next().map_or_else(String::new, |first| {
|
|
248
|
+
first.to_uppercase().collect::<String>() + &chars.as_str().to_lowercase()
|
|
249
|
+
})
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
/// `name.to_s.gsub(/([a-z0-9])([A-Z])/, '\1_\2').upcase`
|
|
@@ -289,18 +289,36 @@ pub fn scalar_to_value(type_name: &str, rust_expr: &str) -> Option<String> {
|
|
|
289
289
|
/// single-field closed-set enums implement `Fielded`; multi-field closed-set
|
|
290
290
|
/// tables do not.
|
|
291
291
|
pub fn fielded_capable_nested(vo: &crate::json::Json) -> bool {
|
|
292
|
-
!vo.get("closed_set").
|
|
293
|
-
|| vo
|
|
292
|
+
!vo.get("closed_set").is_some_and(crate::json::Json::as_bool)
|
|
293
|
+
|| vo
|
|
294
|
+
.get("attributes")
|
|
295
|
+
.map_or_default(crate::json::Json::each)
|
|
296
|
+
.len()
|
|
297
|
+
== 1
|
|
294
298
|
}
|
|
295
299
|
|
|
296
300
|
/// The `Fielded` impl emitted beside a single-field closed-set enum, exposing `"value"`.
|
|
297
301
|
pub fn emit_closed_set_fielded_impl(vo: &crate::json::Json) -> String {
|
|
298
|
-
let name = rust_ident(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
302
|
+
let name = rust_ident(
|
|
303
|
+
vo.get("name")
|
|
304
|
+
.and_then(crate::json::Json::as_str)
|
|
305
|
+
.unwrap_or(""),
|
|
306
|
+
);
|
|
307
|
+
let arms = vo
|
|
308
|
+
.get("members")
|
|
309
|
+
.map_or_default(crate::json::Json::each)
|
|
310
|
+
.iter()
|
|
311
|
+
.filter_map(|row| {
|
|
312
|
+
let pair = row.as_array()?.first()?.as_array()?;
|
|
313
|
+
let raw = pair.get(1)?.to_s();
|
|
314
|
+
Some(format!(
|
|
315
|
+
"Self::{} => {}.to_string(),",
|
|
316
|
+
closed_set_variant(&raw),
|
|
317
|
+
ruby_inspect_string(&raw)
|
|
318
|
+
))
|
|
319
|
+
})
|
|
320
|
+
.collect::<Vec<_>>()
|
|
321
|
+
.join(" ");
|
|
304
322
|
|
|
305
323
|
format!(
|
|
306
324
|
"impl crate::kernel::Fielded for {name} {{\n fn field(&self, name: &str) -> Option<crate::kernel::Field<'_>> {{\n use crate::kernel::{{Field, Value}};\n match name {{\n \"value\" => Some(Field::Value(Value::Str(match self {{ {arms} }}))),\n _ => None,\n }}\n }}\n fn as_scalar(&self) -> Option<crate::kernel::Value> {{\n match self.field(\"value\") {{ Some(crate::kernel::Field::Value(v)) => Some(v), _ => None }}\n }}\n}}"
|
|
@@ -308,6 +326,10 @@ pub fn emit_closed_set_fielded_impl(vo: &crate::json::Json) -> String {
|
|
|
308
326
|
}
|
|
309
327
|
|
|
310
328
|
/// A literal mutation-source RHS; `literal` is a String/Integer/Float/Bool read from `ir.json`.
|
|
329
|
+
#[allow(
|
|
330
|
+
clippy::panic,
|
|
331
|
+
reason = "a drifted exemplar or invalid IR aborts generation on purpose"
|
|
332
|
+
)]
|
|
311
333
|
pub fn literal_rhs(literal: &crate::json::Json) -> String {
|
|
312
334
|
match literal {
|
|
313
335
|
crate::json::Json::String(s) => format!("{}.to_string()", ruby_inspect_string(s)),
|
|
@@ -346,11 +368,23 @@ mod tests {
|
|
|
346
368
|
#[test]
|
|
347
369
|
fn a_scalar_reached_through_a_reference_is_dereferenced_when_it_is_a_copy_type() {
|
|
348
370
|
// `self.level.as_ref().map(|v| Field::Value(Value::Int(v)))` does not compile: `v` is `&i64`.
|
|
349
|
-
assert_eq!(
|
|
350
|
-
|
|
351
|
-
|
|
371
|
+
assert_eq!(
|
|
372
|
+
scalar_to_value("Integer", &deref_scalar("Integer", "v")).as_deref(),
|
|
373
|
+
Some("Value::Int(*v)")
|
|
374
|
+
);
|
|
375
|
+
assert_eq!(
|
|
376
|
+
scalar_to_value("Float", &deref_scalar("Float", "v")).as_deref(),
|
|
377
|
+
Some("Value::Float(*v)")
|
|
378
|
+
);
|
|
379
|
+
assert_eq!(
|
|
380
|
+
scalar_to_value("TrueClass", &deref_scalar("TrueClass", "v")).as_deref(),
|
|
381
|
+
Some("Value::Bool(*v)")
|
|
382
|
+
);
|
|
352
383
|
// A String is cloned, so the reference is left alone.
|
|
353
|
-
assert_eq!(
|
|
384
|
+
assert_eq!(
|
|
385
|
+
scalar_to_value("String", &deref_scalar("String", "v")).as_deref(),
|
|
386
|
+
Some("Value::Str(v.clone())")
|
|
387
|
+
);
|
|
354
388
|
}
|
|
355
389
|
|
|
356
390
|
#[test]
|
|
@@ -368,8 +402,12 @@ mod tests {
|
|
|
368
402
|
|
|
369
403
|
#[test]
|
|
370
404
|
fn refuses_an_aggregate_whose_module_name_is_a_rust_keyword() {
|
|
371
|
-
let refusal =
|
|
372
|
-
|
|
405
|
+
let refusal =
|
|
406
|
+
reserved_name_refusal("shop", "shop", &["Pizza", "Match", "Type"]).expect("refused");
|
|
407
|
+
assert!(
|
|
408
|
+
refusal.starts_with("shop: aggregate name(s) \"Match\", \"Type\" can't be used as-is"),
|
|
409
|
+
"{refusal}"
|
|
410
|
+
);
|
|
373
411
|
assert!(refusal.contains("`pub mod match;`"), "{refusal}");
|
|
374
412
|
assert!(refusal.contains("Rust keyword"), "{refusal}");
|
|
375
413
|
}
|
|
@@ -381,7 +419,12 @@ mod tests {
|
|
|
381
419
|
)
|
|
382
420
|
.unwrap();
|
|
383
421
|
let refusal = unsafe_name_refusal("shop", &ir).expect("refused");
|
|
384
|
-
assert!(
|
|
422
|
+
assert!(
|
|
423
|
+
refusal.starts_with(
|
|
424
|
+
"shop: declared name(s) \"a: String, pub evil: u8\" can't be used as-is"
|
|
425
|
+
),
|
|
426
|
+
"{refusal}"
|
|
427
|
+
);
|
|
385
428
|
}
|
|
386
429
|
|
|
387
430
|
#[test]
|
|
@@ -412,12 +455,20 @@ mod tests {
|
|
|
412
455
|
fn refuses_a_domain_module_name_that_is_a_keyword_or_a_cargo_key() {
|
|
413
456
|
for name in ["crate", "type", "package", "default", "version"] {
|
|
414
457
|
let refusal = reserved_name_refusal("label", name, &["Widget"]).expect("refused");
|
|
415
|
-
assert!(
|
|
458
|
+
assert!(
|
|
459
|
+
refusal.starts_with(&format!(
|
|
460
|
+
"label: domain module name {name:?} can't be used as-is"
|
|
461
|
+
)),
|
|
462
|
+
"{refusal}"
|
|
463
|
+
);
|
|
416
464
|
}
|
|
417
465
|
}
|
|
418
466
|
|
|
419
467
|
#[test]
|
|
420
468
|
fn accepts_ordinary_names_and_cargo_keys_as_aggregate_names() {
|
|
421
|
-
assert_eq!(
|
|
469
|
+
assert_eq!(
|
|
470
|
+
reserved_name_refusal("shop", "shop", &["Pizza", "Version", "Default"]),
|
|
471
|
+
None
|
|
472
|
+
);
|
|
422
473
|
}
|
|
423
474
|
}
|
|
@@ -27,7 +27,7 @@ pub fn run(ir: &mut Json) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
fn list<'a>(node: &'a Json, key: &str) -> &'a [Json] {
|
|
30
|
-
node.get(key).
|
|
30
|
+
node.get(key).map_or_default(Json::each)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
fn name_of(node: &Json) -> Option<&str> {
|
|
@@ -47,15 +47,28 @@ fn domain_wide_value_objects(aggregates: &[Json]) -> HashMap<&str, (usize, usize
|
|
|
47
47
|
by_name
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
fn find_element(
|
|
50
|
+
fn find_element(
|
|
51
|
+
aggregates: &[Json],
|
|
52
|
+
a: usize,
|
|
53
|
+
domain_wide: &HashMap<&str, (usize, usize)>,
|
|
54
|
+
type_name: &str,
|
|
55
|
+
) -> Option<Element> {
|
|
51
56
|
let aggregate = &aggregates[a];
|
|
52
|
-
if let Some(e) = list(aggregate, "entities")
|
|
57
|
+
if let Some(e) = list(aggregate, "entities")
|
|
58
|
+
.iter()
|
|
59
|
+
.position(|entity| name_of(entity) == Some(type_name))
|
|
60
|
+
{
|
|
53
61
|
return Some(Element::Entity(a, e));
|
|
54
62
|
}
|
|
55
|
-
if let Some(v) = list(aggregate, "value_objects")
|
|
63
|
+
if let Some(v) = list(aggregate, "value_objects")
|
|
64
|
+
.iter()
|
|
65
|
+
.rposition(|value_object| name_of(value_object) == Some(type_name))
|
|
66
|
+
{
|
|
56
67
|
return Some(Element::ValueObject(a, v));
|
|
57
68
|
}
|
|
58
|
-
domain_wide
|
|
69
|
+
domain_wide
|
|
70
|
+
.get(type_name)
|
|
71
|
+
.map(|&(owner, v)| Element::ValueObject(owner, v))
|
|
59
72
|
}
|
|
60
73
|
|
|
61
74
|
// Read first, write after: the commands are read while the elements they mark are mutated.
|
|
@@ -66,8 +79,15 @@ fn collect_marks(ir: &Json) -> Vec<Mark> {
|
|
|
66
79
|
|
|
67
80
|
for (a, aggregate) in aggregates.iter().enumerate() {
|
|
68
81
|
for attribute in list(aggregate, "attributes") {
|
|
69
|
-
let (Some(attr_name), Some(type_name)) = (
|
|
70
|
-
|
|
82
|
+
let (Some(attr_name), Some(type_name)) = (
|
|
83
|
+
name_of(attribute),
|
|
84
|
+
attribute.get("type").and_then(Json::as_str),
|
|
85
|
+
) else {
|
|
86
|
+
continue;
|
|
87
|
+
};
|
|
88
|
+
let Some(element) = find_element(aggregates, a, &domain_wide, type_name) else {
|
|
89
|
+
continue;
|
|
90
|
+
};
|
|
71
91
|
|
|
72
92
|
for field in collect_fields_to_mark(aggregate, attr_name) {
|
|
73
93
|
marks.push(Mark { element, field });
|
|
@@ -90,15 +110,21 @@ fn collect_fields_to_mark(aggregate: &Json, target_attr_name: &str) -> Vec<Strin
|
|
|
90
110
|
continue;
|
|
91
111
|
}
|
|
92
112
|
|
|
93
|
-
let Some(fields) = mutation.get("fields").and_then(Json::as_object) else {
|
|
113
|
+
let Some(fields) = mutation.get("fields").and_then(Json::as_object) else {
|
|
114
|
+
continue;
|
|
115
|
+
};
|
|
94
116
|
for (field_name, source) in fields {
|
|
95
117
|
// Only a Symbol (an argument name) renders with a leading `:` in `Literal.render`.
|
|
96
|
-
let Some(source_text) = source.as_str() else {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
let
|
|
100
|
-
|
|
101
|
-
|
|
118
|
+
let Some(source_text) = source.as_str() else {
|
|
119
|
+
continue;
|
|
120
|
+
};
|
|
121
|
+
let Some(arg_name) = source_text.strip_prefix(':') else {
|
|
122
|
+
continue;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
let source_is_optional = command_attrs.iter().any(|a| {
|
|
126
|
+
name_of(a) == Some(arg_name) && a.get("optional").is_some_and(Json::as_bool)
|
|
127
|
+
});
|
|
102
128
|
if source_is_optional {
|
|
103
129
|
out.push(field_name.clone());
|
|
104
130
|
}
|
|
@@ -110,17 +136,31 @@ fn collect_fields_to_mark(aggregate: &Json, target_attr_name: &str) -> Vec<Strin
|
|
|
110
136
|
}
|
|
111
137
|
|
|
112
138
|
fn apply_marks(ir: &mut Json, marks: &[Mark]) {
|
|
113
|
-
let Some(aggregates) = ir.get_mut("aggregates").and_then(Json::as_array_mut) else {
|
|
139
|
+
let Some(aggregates) = ir.get_mut("aggregates").and_then(Json::as_array_mut) else {
|
|
140
|
+
return;
|
|
141
|
+
};
|
|
114
142
|
|
|
115
143
|
for mark in marks {
|
|
116
144
|
let (a, key, index) = match mark.element {
|
|
117
145
|
Element::ValueObject(a, v) => (a, "value_objects", v),
|
|
118
146
|
Element::Entity(a, e) => (a, "entities", e),
|
|
119
147
|
};
|
|
120
|
-
let element = aggregates
|
|
121
|
-
|
|
148
|
+
let element = aggregates
|
|
149
|
+
.get_mut(a)
|
|
150
|
+
.and_then(|aggregate| aggregate.get_mut(key))
|
|
151
|
+
.and_then(Json::as_array_mut)
|
|
152
|
+
.and_then(|elements| elements.get_mut(index));
|
|
153
|
+
let Some(attrs) = element
|
|
154
|
+
.and_then(|element| element.get_mut("attributes"))
|
|
155
|
+
.and_then(Json::as_array_mut)
|
|
156
|
+
else {
|
|
157
|
+
continue;
|
|
158
|
+
};
|
|
122
159
|
|
|
123
|
-
if let Some(field_attr) = attrs
|
|
160
|
+
if let Some(field_attr) = attrs
|
|
161
|
+
.iter_mut()
|
|
162
|
+
.find(|a| name_of(a) == Some(mark.field.as_str()))
|
|
163
|
+
{
|
|
124
164
|
field_attr.set_bool("optional", true);
|
|
125
165
|
}
|
|
126
166
|
}
|
|
@@ -163,12 +203,16 @@ mod tests {
|
|
|
163
203
|
}
|
|
164
204
|
|
|
165
205
|
fn domain(aggregates: &[String]) -> Json {
|
|
166
|
-
Json::parse(&format!(r#"{{"aggregates": [{}]}}"#, aggregates.join(",")))
|
|
206
|
+
Json::parse(&format!(r#"{{"aggregates": [{}]}}"#, aggregates.join(",")))
|
|
207
|
+
.expect("fixture parses")
|
|
167
208
|
}
|
|
168
209
|
|
|
169
210
|
fn amount_optional(ir: &Json, aggregate: usize, key: &str) -> bool {
|
|
170
211
|
let element = &list(&list(ir, "aggregates")[aggregate], key)[0];
|
|
171
|
-
let amount = list(element, "attributes")
|
|
212
|
+
let amount = list(element, "attributes")
|
|
213
|
+
.iter()
|
|
214
|
+
.find(|a| name_of(a) == Some("amount"))
|
|
215
|
+
.unwrap();
|
|
172
216
|
amount.get("optional").unwrap().as_bool()
|
|
173
217
|
}
|
|
174
218
|
|
|
@@ -176,8 +220,12 @@ mod tests {
|
|
|
176
220
|
// recorded from it. Read at test time, not compiled in: an installed gem has no `spec/`.
|
|
177
221
|
#[test]
|
|
178
222
|
fn marks_what_the_ruby_pass_marks_on_the_shared_fixture() {
|
|
179
|
-
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
180
|
-
|
|
223
|
+
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
224
|
+
.join("../../spec/fixtures/append_optionals");
|
|
225
|
+
let read = |name: &str| {
|
|
226
|
+
Json::parse(&std::fs::read_to_string(dir.join(name)).expect("reads the fixture"))
|
|
227
|
+
.expect("parses")
|
|
228
|
+
};
|
|
181
229
|
|
|
182
230
|
let mut ir = read("input.json");
|
|
183
231
|
run(&mut ir);
|
|
@@ -197,28 +245,42 @@ mod tests {
|
|
|
197
245
|
|
|
198
246
|
#[test]
|
|
199
247
|
fn marks_a_value_object_another_aggregate_declares() {
|
|
200
|
-
let owner =
|
|
248
|
+
let owner =
|
|
249
|
+
r#"{"attributes": [], "value_objects": [LEDGER], "entities": [], "commands": []}"#
|
|
250
|
+
.replace("LEDGER", &element(false));
|
|
201
251
|
let appender = aggregate_with("", "", "LedgerEntry");
|
|
202
252
|
let mut ir = domain(&[appender, owner]);
|
|
203
253
|
run(&mut ir);
|
|
204
254
|
|
|
205
|
-
assert!(
|
|
255
|
+
assert!(
|
|
256
|
+
amount_optional(&ir, 1, "value_objects"),
|
|
257
|
+
"the value object lives in the second aggregate"
|
|
258
|
+
);
|
|
206
259
|
}
|
|
207
260
|
|
|
208
261
|
#[test]
|
|
209
262
|
fn prefers_a_local_entity_over_a_value_object_of_the_same_name() {
|
|
210
263
|
let local = aggregate_with(&element(false), "", "LedgerEntry");
|
|
211
|
-
let owner =
|
|
264
|
+
let owner =
|
|
265
|
+
r#"{"attributes": [], "value_objects": [LEDGER], "entities": [], "commands": []}"#
|
|
266
|
+
.replace("LEDGER", &element(false));
|
|
212
267
|
let mut ir = domain(&[local, owner]);
|
|
213
268
|
run(&mut ir);
|
|
214
269
|
|
|
215
|
-
assert!(
|
|
216
|
-
|
|
270
|
+
assert!(
|
|
271
|
+
amount_optional(&ir, 0, "entities"),
|
|
272
|
+
"the local entity is the one marked"
|
|
273
|
+
);
|
|
274
|
+
assert!(
|
|
275
|
+
!amount_optional(&ir, 1, "value_objects"),
|
|
276
|
+
"the domain-wide value object is left alone"
|
|
277
|
+
);
|
|
217
278
|
}
|
|
218
279
|
|
|
219
280
|
#[test]
|
|
220
281
|
fn leaves_a_field_alone_when_its_argument_is_required() {
|
|
221
|
-
let mut ir = domain(&[aggregate_with(&element(false), "", "LedgerEntry")
|
|
282
|
+
let mut ir = domain(&[aggregate_with(&element(false), "", "LedgerEntry")
|
|
283
|
+
.replace("\"optional\": true", "\"optional\": false")]);
|
|
222
284
|
run(&mut ir);
|
|
223
285
|
|
|
224
286
|
assert!(!amount_optional(&ir, 0, "entities"));
|
data/rust/codegen/src/ports.rs
CHANGED
|
@@ -17,7 +17,7 @@ pub fn port_operation_skip_reason(
|
|
|
17
17
|
_owner_name: &str,
|
|
18
18
|
value_objects_by_name: &HashMap<String, &Json>,
|
|
19
19
|
) -> Option<SkipReason> {
|
|
20
|
-
let attrs = operation.get("attributes").
|
|
20
|
+
let attrs = operation.get("attributes").map_or_default(Json::each);
|
|
21
21
|
let unresolved: Vec<&Json> = attrs
|
|
22
22
|
.iter()
|
|
23
23
|
.filter(|attr| {
|
|
@@ -63,7 +63,7 @@ pub fn emit_port_operation(
|
|
|
63
63
|
naming::rust_ident(operation.get("name").and_then(Json::as_str).unwrap_or(""))
|
|
64
64
|
);
|
|
65
65
|
let qualified = format!("{domain_name}::{owner_name}");
|
|
66
|
-
let attrs = operation.get("attributes").
|
|
66
|
+
let attrs = operation.get("attributes").map_or_default(Json::each);
|
|
67
67
|
// A self-reference on a port operation is a migration-era spelling of
|
|
68
68
|
// its receiver, never an external fact. Keep accepting old IR while
|
|
69
69
|
// excluding that field from the typed args and event payload.
|
|
@@ -109,7 +109,7 @@ pub fn emit_port_operation(
|
|
|
109
109
|
))
|
|
110
110
|
);
|
|
111
111
|
|
|
112
|
-
let emits = operation.get("emits").
|
|
112
|
+
let emits = operation.get("emits").map_or_default(Json::each);
|
|
113
113
|
let events: Vec<String> = emits
|
|
114
114
|
.iter()
|
|
115
115
|
.map(|event_name| {
|
|
@@ -130,7 +130,7 @@ pub fn emit_port_operation(
|
|
|
130
130
|
let operation_name = operation.get("name").and_then(Json::as_str).unwrap_or("");
|
|
131
131
|
[
|
|
132
132
|
format!("#[derive(Debug, Clone)]\n{}", struct_lines.join("\n")),
|
|
133
|
-
crate::json_codec::emit_to_json_flat(exemplar, &args_struct, &fact_attrs,
|
|
133
|
+
crate::json_codec::emit_to_json_flat(exemplar, &args_struct, &fact_attrs, false, &[], None),
|
|
134
134
|
crate::json_codec::emit_from_json_flat(
|
|
135
135
|
exemplar,
|
|
136
136
|
&args_struct,
|
|
@@ -141,6 +141,7 @@ pub fn emit_port_operation(
|
|
|
141
141
|
false,
|
|
142
142
|
true,
|
|
143
143
|
Some(aggregates_by_name),
|
|
144
|
+
None,
|
|
144
145
|
),
|
|
145
146
|
dispatch_fn,
|
|
146
147
|
]
|