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
|
@@ -7,17 +7,43 @@ use std::collections::{HashMap, HashSet};
|
|
|
7
7
|
|
|
8
8
|
/// `use` lines for value objects this aggregate's commands reference but never declare locally.
|
|
9
9
|
/// A locally declared type name is never foreign.
|
|
10
|
-
pub fn cross_aggregate_vo_imports(
|
|
11
|
-
|
|
10
|
+
pub fn cross_aggregate_vo_imports(
|
|
11
|
+
aggregate: &Json,
|
|
12
|
+
domain_value_object_owner: &HashMap<String, String>,
|
|
13
|
+
mod_name: &str,
|
|
14
|
+
) -> Vec<String> {
|
|
15
|
+
let local_names: HashSet<String> = aggregate
|
|
16
|
+
.get("value_objects")
|
|
17
|
+
.map_or_default(Json::each)
|
|
18
|
+
.iter()
|
|
19
|
+
.map(|vo| {
|
|
20
|
+
vo.get("name")
|
|
21
|
+
.and_then(Json::as_str)
|
|
22
|
+
.unwrap_or("")
|
|
23
|
+
.to_string()
|
|
24
|
+
})
|
|
25
|
+
.collect();
|
|
12
26
|
|
|
13
|
-
let commands = aggregate.get("commands").
|
|
14
|
-
let entity_commands = aggregate
|
|
15
|
-
|
|
27
|
+
let commands = aggregate.get("commands").map_or_default(Json::each);
|
|
28
|
+
let entity_commands = aggregate
|
|
29
|
+
.get("entities")
|
|
30
|
+
.map_or_default(Json::each)
|
|
31
|
+
.iter()
|
|
32
|
+
.flat_map(|e| e.get("commands").map_or_default(Json::each).iter());
|
|
33
|
+
let port_operations = aggregate
|
|
34
|
+
.get("ports")
|
|
35
|
+
.map_or_default(Json::each)
|
|
36
|
+
.iter()
|
|
37
|
+
.flat_map(|p| p.get("operations").map_or_default(Json::each).iter());
|
|
16
38
|
|
|
17
39
|
let mut foreign_types: Vec<String> = Vec::new();
|
|
18
40
|
let mut seen: HashSet<String> = HashSet::new();
|
|
19
|
-
for c in commands
|
|
20
|
-
|
|
41
|
+
for c in commands
|
|
42
|
+
.iter()
|
|
43
|
+
.chain(entity_commands)
|
|
44
|
+
.chain(port_operations)
|
|
45
|
+
{
|
|
46
|
+
for attr in c.get("attributes").map_or_default(Json::each) {
|
|
21
47
|
let type_name = crate::attr::type_name(attr).to_string();
|
|
22
48
|
if local_names.contains(&type_name) || seen.contains(&type_name) {
|
|
23
49
|
continue;
|
|
@@ -27,39 +53,70 @@ pub fn cross_aggregate_vo_imports(aggregate: &Json, domain_value_object_owner: &
|
|
|
27
53
|
}
|
|
28
54
|
}
|
|
29
55
|
|
|
30
|
-
let mut pairs: Vec<(String, String)> = foreign_types
|
|
56
|
+
let mut pairs: Vec<(String, String)> = foreign_types
|
|
57
|
+
.into_iter()
|
|
58
|
+
.filter_map(|type_name| {
|
|
59
|
+
domain_value_object_owner
|
|
60
|
+
.get(&type_name)
|
|
61
|
+
.map(|owner| (type_name, owner.clone()))
|
|
62
|
+
})
|
|
63
|
+
.collect();
|
|
31
64
|
pairs.sort_by(|(a_type, a_owner), (b_type, b_owner)| (a_owner, a_type).cmp(&(b_owner, b_type)));
|
|
32
|
-
pairs
|
|
65
|
+
pairs
|
|
66
|
+
.into_iter()
|
|
67
|
+
.map(|(type_name, owner)| {
|
|
68
|
+
format!(
|
|
69
|
+
"use crate::generated::{mod_name}::{}::{};",
|
|
70
|
+
naming::aggregate_module(&owner),
|
|
71
|
+
naming::rust_ident(&type_name)
|
|
72
|
+
)
|
|
73
|
+
})
|
|
74
|
+
.collect()
|
|
33
75
|
}
|
|
34
76
|
|
|
35
77
|
/// True when a value object rebuilds into a differently-named one by matching field names.
|
|
36
78
|
pub fn vo_field_bridgeable(source_vo: Option<&Json>, target_vo: Option<&Json>) -> bool {
|
|
37
|
-
let (Some(source_vo), Some(target_vo)) = (source_vo, target_vo) else {
|
|
38
|
-
|
|
79
|
+
let (Some(source_vo), Some(target_vo)) = (source_vo, target_vo) else {
|
|
80
|
+
return false;
|
|
81
|
+
};
|
|
82
|
+
if source_vo.get("closed_set").is_some_and(Json::as_bool)
|
|
83
|
+
|| target_vo.get("closed_set").is_some_and(Json::as_bool)
|
|
84
|
+
{
|
|
39
85
|
return closed_set_bridge_members(source_vo, target_vo).is_some();
|
|
40
86
|
}
|
|
41
87
|
|
|
42
|
-
let source_attrs = source_vo.get("attributes").
|
|
43
|
-
let target_attrs = target_vo.get("attributes").
|
|
88
|
+
let source_attrs = source_vo.get("attributes").map_or_default(Json::each);
|
|
89
|
+
let target_attrs = target_vo.get("attributes").map_or_default(Json::each);
|
|
44
90
|
target_attrs.iter().all(|t_attr| {
|
|
45
91
|
let t_name = crate::attr::name(t_attr);
|
|
46
|
-
source_attrs
|
|
92
|
+
source_attrs
|
|
93
|
+
.iter()
|
|
94
|
+
.any(|s_attr| crate::attr::name(s_attr) == t_name)
|
|
95
|
+
|| crate::attr::default(t_attr).is_some()
|
|
47
96
|
})
|
|
48
97
|
}
|
|
49
98
|
|
|
50
99
|
/// Closed-set to closed-set bridge, admitted only when every source member is a target member.
|
|
51
100
|
fn closed_set_bridge_members<'a>(source_vo: &'a Json, target_vo: &Json) -> Option<&'a [Json]> {
|
|
52
|
-
let closed = |vo: &Json| vo.get("closed_set").
|
|
101
|
+
let closed = |vo: &Json| vo.get("closed_set").is_some_and(Json::as_bool);
|
|
53
102
|
if !closed(source_vo) || !closed(target_vo) {
|
|
54
103
|
return None;
|
|
55
104
|
}
|
|
56
|
-
let single = |vo: &Json| vo.get("attributes").
|
|
105
|
+
let single = |vo: &Json| vo.get("attributes").map_or_default(Json::each).len() == 1;
|
|
57
106
|
if !single(source_vo) || !single(target_vo) {
|
|
58
107
|
return None;
|
|
59
108
|
}
|
|
60
|
-
let target_values: Vec<String> = target_vo
|
|
61
|
-
|
|
62
|
-
|
|
109
|
+
let target_values: Vec<String> = target_vo
|
|
110
|
+
.get("members")
|
|
111
|
+
.map_or_default(Json::each)
|
|
112
|
+
.iter()
|
|
113
|
+
.map(closed_set_row_value)
|
|
114
|
+
.collect();
|
|
115
|
+
let source_rows = source_vo.get("members").map_or_default(Json::each);
|
|
116
|
+
if source_rows
|
|
117
|
+
.iter()
|
|
118
|
+
.all(|row| target_values.contains(&closed_set_row_value(row)))
|
|
119
|
+
{
|
|
63
120
|
Some(source_rows)
|
|
64
121
|
} else {
|
|
65
122
|
None
|
|
@@ -72,39 +129,73 @@ fn closed_set_row_value(row: &Json) -> String {
|
|
|
72
129
|
first.get(1).map(Json::to_s).unwrap_or_default()
|
|
73
130
|
}
|
|
74
131
|
|
|
75
|
-
|
|
132
|
+
#[allow(
|
|
133
|
+
clippy::unwrap_used,
|
|
134
|
+
reason = "the value was checked just before; None would mean invalid IR"
|
|
135
|
+
)]
|
|
136
|
+
pub fn vo_field_rhs(
|
|
137
|
+
source_expr: &str,
|
|
138
|
+
source_vo: &Json,
|
|
139
|
+
target_type: &str,
|
|
140
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
141
|
+
) -> String {
|
|
76
142
|
let target_vo = value_objects_by_name[target_type];
|
|
77
143
|
if let Some(rows) = closed_set_bridge_members(source_vo, target_vo) {
|
|
78
144
|
let source_type = source_vo.get("name").map(Json::to_s).unwrap_or_default();
|
|
79
145
|
let arms: Vec<String> = rows
|
|
80
146
|
.iter()
|
|
81
|
-
.map(|row|
|
|
147
|
+
.map(|row| {
|
|
148
|
+
format!(
|
|
149
|
+
"{}::{} => {}::{}",
|
|
150
|
+
naming::rust_ident(&source_type),
|
|
151
|
+
closed_set_variant_of_row(row),
|
|
152
|
+
naming::rust_ident(target_type),
|
|
153
|
+
closed_set_variant_of_row(row)
|
|
154
|
+
)
|
|
155
|
+
})
|
|
82
156
|
.collect();
|
|
83
157
|
return format!("match &{source_expr} {{ {} }}", arms.join(", "));
|
|
84
158
|
}
|
|
85
|
-
let source_attrs = source_vo.get("attributes").
|
|
86
|
-
let target_attrs = target_vo.get("attributes").
|
|
159
|
+
let source_attrs = source_vo.get("attributes").map_or_default(Json::each);
|
|
160
|
+
let target_attrs = target_vo.get("attributes").map_or_default(Json::each);
|
|
87
161
|
|
|
88
162
|
let fields: Vec<String> = target_attrs
|
|
89
163
|
.iter()
|
|
90
164
|
.map(|t_attr| {
|
|
91
165
|
let field = naming::rust_ident_field(crate::attr::name(t_attr));
|
|
92
166
|
let t_name = crate::attr::name(t_attr);
|
|
93
|
-
if source_attrs
|
|
167
|
+
if source_attrs
|
|
168
|
+
.iter()
|
|
169
|
+
.any(|s_attr| crate::attr::name(s_attr) == t_name)
|
|
170
|
+
{
|
|
94
171
|
format!("{field}: {source_expr}.{field}.clone()")
|
|
95
172
|
} else {
|
|
96
|
-
format!(
|
|
173
|
+
format!(
|
|
174
|
+
"{field}: {}",
|
|
175
|
+
naming::literal_rhs(crate::attr::default(t_attr).unwrap())
|
|
176
|
+
)
|
|
97
177
|
}
|
|
98
178
|
})
|
|
99
179
|
.collect();
|
|
100
|
-
format!(
|
|
180
|
+
format!(
|
|
181
|
+
"{} {{ {} }}",
|
|
182
|
+
naming::rust_ident(target_type),
|
|
183
|
+
fields.join(", ")
|
|
184
|
+
)
|
|
101
185
|
}
|
|
102
186
|
|
|
103
|
-
pub fn bridgeable_value_types(
|
|
187
|
+
pub fn bridgeable_value_types(
|
|
188
|
+
source_type: &str,
|
|
189
|
+
target_type: &str,
|
|
190
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
191
|
+
) -> bool {
|
|
104
192
|
if source_type == target_type {
|
|
105
193
|
return true;
|
|
106
194
|
}
|
|
107
|
-
if let (Some(s), Some(t)) = (
|
|
195
|
+
if let (Some(s), Some(t)) = (
|
|
196
|
+
naming::effective_scalar_type(source_type),
|
|
197
|
+
naming::effective_scalar_type(target_type),
|
|
198
|
+
) {
|
|
108
199
|
if s == t {
|
|
109
200
|
return true;
|
|
110
201
|
}
|
|
@@ -118,8 +209,8 @@ pub fn bridgeable_value_types(source_type: &str, target_type: &str, value_object
|
|
|
118
209
|
|
|
119
210
|
match source_vo {
|
|
120
211
|
Some(vo) => {
|
|
121
|
-
let closed = vo.get("closed_set").
|
|
122
|
-
let attrs = vo.get("attributes").
|
|
212
|
+
let closed = vo.get("closed_set").is_some_and(Json::as_bool);
|
|
213
|
+
let attrs = vo.get("attributes").map_or_default(Json::each);
|
|
123
214
|
if closed || attrs.len() != 1 {
|
|
124
215
|
return false;
|
|
125
216
|
}
|
|
@@ -134,11 +225,24 @@ pub fn bridgeable_value_types(source_type: &str, target_type: &str, value_object
|
|
|
134
225
|
}
|
|
135
226
|
}
|
|
136
227
|
|
|
137
|
-
|
|
228
|
+
#[allow(
|
|
229
|
+
clippy::expect_used,
|
|
230
|
+
clippy::panic,
|
|
231
|
+
reason = "a drifted exemplar or invalid IR aborts generation on purpose; the message names an invariant an earlier check in this generator established"
|
|
232
|
+
)]
|
|
233
|
+
pub fn value_rhs(
|
|
234
|
+
source_expr: &str,
|
|
235
|
+
source_type: &str,
|
|
236
|
+
target_type: &str,
|
|
237
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
238
|
+
) -> String {
|
|
138
239
|
if source_type == target_type {
|
|
139
240
|
return format!("{source_expr}.clone()");
|
|
140
241
|
}
|
|
141
|
-
if let (Some(s), Some(t)) = (
|
|
242
|
+
if let (Some(s), Some(t)) = (
|
|
243
|
+
naming::effective_scalar_type(source_type),
|
|
244
|
+
naming::effective_scalar_type(target_type),
|
|
245
|
+
) {
|
|
142
246
|
if s == t {
|
|
143
247
|
return format!("{source_expr}.clone()");
|
|
144
248
|
}
|
|
@@ -147,15 +251,21 @@ pub fn value_rhs(source_expr: &str, source_type: &str, target_type: &str, value_
|
|
|
147
251
|
let source_vo = value_objects_by_name.get(source_type).copied();
|
|
148
252
|
let target_vo = value_objects_by_name.get(target_type).copied();
|
|
149
253
|
if target_vo.is_some() {
|
|
150
|
-
return vo_field_rhs(
|
|
254
|
+
return vo_field_rhs(
|
|
255
|
+
source_expr,
|
|
256
|
+
source_vo.expect("bridgeable_value_types? should have caught this"),
|
|
257
|
+
target_type,
|
|
258
|
+
value_objects_by_name,
|
|
259
|
+
);
|
|
151
260
|
}
|
|
152
261
|
|
|
153
262
|
let source_vo = source_vo.unwrap_or_else(|| panic!("unsupported coercion {source_type} -> {target_type} — bridgeable_value_types? should have caught this"));
|
|
154
|
-
let attrs = source_vo.get("attributes").
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
263
|
+
let attrs = source_vo.get("attributes").map_or_default(Json::each);
|
|
264
|
+
assert!(!(source_vo.get("closed_set").is_some_and(Json::as_bool) || attrs.len() != 1), "unsupported coercion {source_type} -> {target_type} — bridgeable_value_types? should have caught this");
|
|
265
|
+
format!(
|
|
266
|
+
"{source_expr}.{}.clone()",
|
|
267
|
+
naming::rust_ident_field(crate::attr::name(&attrs[0]))
|
|
268
|
+
)
|
|
159
269
|
}
|
|
160
270
|
|
|
161
271
|
/// True when a list-to-list `:set` needs a per-element rebuild rather than one `.clone()`.
|
|
@@ -171,40 +281,67 @@ pub fn list_bridge_requires_element_mapping(source_type: &str, target_type: &str
|
|
|
171
281
|
}
|
|
172
282
|
|
|
173
283
|
/// `value_rhs` applied per element, for list-to-list `:set` whose element types differ.
|
|
174
|
-
pub fn list_value_rhs(
|
|
175
|
-
|
|
284
|
+
pub fn list_value_rhs(
|
|
285
|
+
source_expr: &str,
|
|
286
|
+
source_type: &str,
|
|
287
|
+
target_type: &str,
|
|
288
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
289
|
+
) -> String {
|
|
290
|
+
format!(
|
|
291
|
+
"{source_expr}.iter().map(|item| {}).collect()",
|
|
292
|
+
value_rhs("item", source_type, target_type, value_objects_by_name)
|
|
293
|
+
)
|
|
176
294
|
}
|
|
177
295
|
|
|
178
|
-
pub fn literal_set_bridgeable(
|
|
296
|
+
pub fn literal_set_bridgeable(
|
|
297
|
+
value: &Literal,
|
|
298
|
+
target_type: Option<&str>,
|
|
299
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
300
|
+
) -> bool {
|
|
179
301
|
match value {
|
|
180
|
-
Literal::Hash(_) =>
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
},
|
|
302
|
+
Literal::Hash(_) => target_type.is_some_and(|target_type| {
|
|
303
|
+
literal_hash_bridgeable(value, target_type, value_objects_by_name)
|
|
304
|
+
}),
|
|
184
305
|
Literal::Str(_) | Literal::Int(_) | Literal::Float(_) | Literal::Bool(_) => {
|
|
185
|
-
let Some(target_type) = target_type else {
|
|
306
|
+
let Some(target_type) = target_type else {
|
|
307
|
+
return true;
|
|
308
|
+
};
|
|
186
309
|
if !value_objects_by_name.contains_key(target_type) {
|
|
187
310
|
return true;
|
|
188
311
|
}
|
|
189
312
|
// A scalar literal into a single-field value object is that field's value.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
313
|
+
crate::json_codec::sole_field_of(target_type, value_objects_by_name).is_some_and(
|
|
314
|
+
|sole| {
|
|
315
|
+
literal_hash_bridgeable(
|
|
316
|
+
&Literal::Hash(vec![(sole, value.clone())]),
|
|
317
|
+
target_type,
|
|
318
|
+
value_objects_by_name,
|
|
319
|
+
)
|
|
320
|
+
},
|
|
321
|
+
)
|
|
194
322
|
}
|
|
195
323
|
_ => false,
|
|
196
324
|
}
|
|
197
325
|
}
|
|
198
326
|
|
|
199
327
|
/// Renders a literal as the right-hand side for a target type.
|
|
200
|
-
pub fn literal_rhs_for(
|
|
328
|
+
pub fn literal_rhs_for(
|
|
329
|
+
value: &Literal,
|
|
330
|
+
target_type: Option<&str>,
|
|
331
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
332
|
+
) -> String {
|
|
201
333
|
if let Literal::Hash(_) = value {
|
|
202
334
|
return literal_hash_rhs(value, target_type.unwrap_or(""), value_objects_by_name);
|
|
203
335
|
}
|
|
204
336
|
if let Some(target_type) = target_type {
|
|
205
337
|
if value_objects_by_name.contains_key(target_type) {
|
|
206
|
-
if let Some(sole) = crate::json_codec::sole_field_of(target_type, value_objects_by_name)
|
|
207
|
-
|
|
338
|
+
if let Some(sole) = crate::json_codec::sole_field_of(target_type, value_objects_by_name)
|
|
339
|
+
{
|
|
340
|
+
return literal_hash_rhs(
|
|
341
|
+
&Literal::Hash(vec![(sole, value.clone())]),
|
|
342
|
+
target_type,
|
|
343
|
+
value_objects_by_name,
|
|
344
|
+
);
|
|
208
345
|
}
|
|
209
346
|
}
|
|
210
347
|
}
|
|
@@ -212,15 +349,25 @@ pub fn literal_rhs_for(value: &Literal, target_type: Option<&str>, value_objects
|
|
|
212
349
|
}
|
|
213
350
|
|
|
214
351
|
/// True when a literal Hash fills an ordinary value object or matches one closed-set member row.
|
|
215
|
-
pub fn literal_hash_bridgeable(
|
|
216
|
-
|
|
352
|
+
pub fn literal_hash_bridgeable(
|
|
353
|
+
hash: &Literal,
|
|
354
|
+
target_type: &str,
|
|
355
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
356
|
+
) -> bool {
|
|
357
|
+
let Some(vo) = value_objects_by_name.get(target_type) else {
|
|
358
|
+
return false;
|
|
359
|
+
};
|
|
217
360
|
|
|
218
|
-
if vo.get("closed_set").
|
|
219
|
-
let members = vo.get("members").
|
|
220
|
-
members
|
|
361
|
+
if vo.get("closed_set").is_some_and(Json::as_bool) {
|
|
362
|
+
let members = vo.get("members").map_or_default(Json::each);
|
|
363
|
+
members
|
|
364
|
+
.iter()
|
|
365
|
+
.any(|member| member_matches_hash(member, hash))
|
|
221
366
|
} else {
|
|
222
|
-
let attrs = vo.get("attributes").
|
|
223
|
-
attrs
|
|
367
|
+
let attrs = vo.get("attributes").map_or_default(Json::each);
|
|
368
|
+
attrs
|
|
369
|
+
.iter()
|
|
370
|
+
.all(|attr| hash.get(crate::attr::name(attr)).is_some())
|
|
224
371
|
}
|
|
225
372
|
}
|
|
226
373
|
|
|
@@ -230,11 +377,11 @@ fn member_matches_hash(member: &Json, hash: &Literal) -> bool {
|
|
|
230
377
|
let pairs = member.as_array().unwrap_or(&[]);
|
|
231
378
|
pairs.iter().all(|pair| {
|
|
232
379
|
let kv = pair.as_array().unwrap_or(&[]);
|
|
233
|
-
let (Some(field), Some(value)) = (kv.first().and_then(Json::as_str), kv.get(1)) else {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
380
|
+
let (Some(field), Some(value)) = (kv.first().and_then(Json::as_str), kv.get(1)) else {
|
|
381
|
+
return false;
|
|
382
|
+
};
|
|
383
|
+
hash.get(field)
|
|
384
|
+
.is_some_and(|held| literal_to_s(held) == value.to_s())
|
|
238
385
|
})
|
|
239
386
|
}
|
|
240
387
|
|
|
@@ -251,20 +398,32 @@ fn literal_to_s(lit: &Literal) -> String {
|
|
|
251
398
|
}
|
|
252
399
|
}
|
|
253
400
|
|
|
254
|
-
|
|
401
|
+
#[allow(
|
|
402
|
+
clippy::panic,
|
|
403
|
+
reason = "a drifted exemplar or invalid IR aborts generation on purpose"
|
|
404
|
+
)]
|
|
405
|
+
pub fn literal_hash_rhs(
|
|
406
|
+
hash: &Literal,
|
|
407
|
+
target_type: &str,
|
|
408
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
409
|
+
) -> String {
|
|
255
410
|
let vo = value_objects_by_name
|
|
256
411
|
.get(target_type)
|
|
257
412
|
.unwrap_or_else(|| panic!("unsupported literal hash source {hash:?} -> {target_type} — literal_hash_bridgeable? should have caught this"));
|
|
258
413
|
|
|
259
|
-
if vo.get("closed_set").
|
|
260
|
-
let members = vo.get("members").
|
|
414
|
+
if vo.get("closed_set").is_some_and(Json::as_bool) {
|
|
415
|
+
let members = vo.get("members").map_or_default(Json::each);
|
|
261
416
|
let row = members
|
|
262
417
|
.iter()
|
|
263
418
|
.find(|member| member_matches_hash(member, hash))
|
|
264
419
|
.unwrap_or_else(|| panic!("literal {hash:?} matches no member of {target_type} — literal_hash_bridgeable? should have caught this"));
|
|
265
|
-
format!(
|
|
420
|
+
format!(
|
|
421
|
+
"{}::{}",
|
|
422
|
+
naming::rust_ident(target_type),
|
|
423
|
+
closed_set_variant_of_row(row)
|
|
424
|
+
)
|
|
266
425
|
} else {
|
|
267
|
-
let attrs = vo.get("attributes").
|
|
426
|
+
let attrs = vo.get("attributes").map_or_default(Json::each);
|
|
268
427
|
let fields: Vec<String> = attrs
|
|
269
428
|
.iter()
|
|
270
429
|
.map(|attr| {
|
|
@@ -275,7 +434,11 @@ pub fn literal_hash_rhs(hash: &Literal, target_type: &str, value_objects_by_name
|
|
|
275
434
|
format!("{}: {}", naming::rust_ident_field(name), crate::literal::literal_rhs(value))
|
|
276
435
|
})
|
|
277
436
|
.collect();
|
|
278
|
-
format!(
|
|
437
|
+
format!(
|
|
438
|
+
"{} {{ {} }}",
|
|
439
|
+
naming::rust_ident(target_type),
|
|
440
|
+
fields.join(", ")
|
|
441
|
+
)
|
|
279
442
|
}
|
|
280
443
|
}
|
|
281
444
|
|
|
@@ -287,28 +450,97 @@ fn closed_set_variant_of_row(row: &Json) -> String {
|
|
|
287
450
|
}
|
|
288
451
|
|
|
289
452
|
pub fn integer_field_of(vo: Option<&Json>) -> Option<String> {
|
|
453
|
+
scalar_field_of(vo, "Integer")
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
fn scalar_field_of(vo: Option<&Json>, type_name: &str) -> Option<String> {
|
|
290
457
|
let vo = vo?;
|
|
291
|
-
if vo.get("closed_set").
|
|
458
|
+
if vo.get("closed_set").is_some_and(Json::as_bool) {
|
|
292
459
|
return None;
|
|
293
460
|
}
|
|
294
|
-
let attrs = vo.get("attributes").
|
|
295
|
-
attrs
|
|
461
|
+
let attrs = vo.get("attributes").map_or_default(Json::each);
|
|
462
|
+
attrs
|
|
463
|
+
.iter()
|
|
464
|
+
.find(|a| crate::attr::type_name(a) == type_name)
|
|
465
|
+
.map(|a| crate::attr::name(a).to_string())
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/// Which machine number a value object's arithmetic field is.
|
|
469
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
470
|
+
pub enum NumericKind {
|
|
471
|
+
Integer,
|
|
472
|
+
Float,
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/// The field arithmetic acts on: the first Integer field, else the first Float field.
|
|
476
|
+
pub fn numeric_field_of(vo: Option<&Json>) -> Option<(String, NumericKind)> {
|
|
477
|
+
integer_field_of(vo)
|
|
478
|
+
.map(|field| (field, NumericKind::Integer))
|
|
479
|
+
.or_else(|| scalar_field_of(vo, "Float").map(|field| (field, NumericKind::Float)))
|
|
296
480
|
}
|
|
297
481
|
|
|
298
|
-
|
|
299
|
-
pub fn arithmetic_target_field<'a>(mutation: &Json, aggregate: &'a Json, value_objects_by_name: &HashMap<String, &Json>) -> Option<(&'a Json, String)> {
|
|
482
|
+
fn target_attribute<'a>(mutation: &Json, aggregate: &'a Json) -> Option<&'a Json> {
|
|
300
483
|
let target = mutation.get("target").map(Json::to_s).unwrap_or_default();
|
|
301
|
-
let attrs = aggregate.get("attributes").
|
|
484
|
+
let attrs = aggregate.get("attributes").map_or_default(Json::each);
|
|
302
485
|
let target_attr = attrs.iter().find(|a| crate::attr::name(a) == target)?;
|
|
303
486
|
if crate::attr::list(target_attr) {
|
|
304
487
|
return None;
|
|
305
488
|
}
|
|
306
|
-
|
|
489
|
+
Some(target_attr)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/// The target half of an `:increment`/`:decrement`/`:multiply` mutation.
|
|
493
|
+
pub fn arithmetic_target_field<'a>(
|
|
494
|
+
mutation: &Json,
|
|
495
|
+
aggregate: &'a Json,
|
|
496
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
497
|
+
) -> Option<(&'a Json, String, NumericKind)> {
|
|
498
|
+
let target_attr = target_attribute(mutation, aggregate)?;
|
|
499
|
+
let (field, kind) = numeric_field_of(
|
|
500
|
+
value_objects_by_name
|
|
501
|
+
.get(crate::attr::type_name(target_attr))
|
|
502
|
+
.copied(),
|
|
503
|
+
)?;
|
|
504
|
+
Some((target_attr, field, kind))
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/// The target of a `:clamp`, whose bounds are integers: Integer-field value objects only.
|
|
508
|
+
pub fn integer_target_field<'a>(
|
|
509
|
+
mutation: &Json,
|
|
510
|
+
aggregate: &'a Json,
|
|
511
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
512
|
+
) -> Option<(&'a Json, String)> {
|
|
513
|
+
let target_attr = target_attribute(mutation, aggregate)?;
|
|
514
|
+
let field = integer_field_of(
|
|
515
|
+
value_objects_by_name
|
|
516
|
+
.get(crate::attr::type_name(target_attr))
|
|
517
|
+
.copied(),
|
|
518
|
+
)?;
|
|
307
519
|
Some((target_attr, field))
|
|
308
520
|
}
|
|
309
521
|
|
|
310
|
-
/// The amount half — resolved to a raw Rust
|
|
311
|
-
pub fn arithmetic_amount_expr(
|
|
522
|
+
/// The amount half — resolved to a raw Rust expression of the target field's own type.
|
|
523
|
+
pub fn arithmetic_amount_expr(
|
|
524
|
+
source: &Json,
|
|
525
|
+
command: &Json,
|
|
526
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
527
|
+
target_field: &str,
|
|
528
|
+
kind: NumericKind,
|
|
529
|
+
) -> Option<String> {
|
|
530
|
+
match kind {
|
|
531
|
+
NumericKind::Integer => {
|
|
532
|
+
integer_amount_expr(source, command, value_objects_by_name, target_field)
|
|
533
|
+
}
|
|
534
|
+
NumericKind::Float => float_amount_expr(source, command, value_objects_by_name),
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
fn integer_amount_expr(
|
|
539
|
+
source: &Json,
|
|
540
|
+
command: &Json,
|
|
541
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
542
|
+
target_integer_field: &str,
|
|
543
|
+
) -> Option<String> {
|
|
312
544
|
let kind = source.get("kind").map(Json::to_s).unwrap_or_default();
|
|
313
545
|
if kind == "literal" {
|
|
314
546
|
let value = source.get("value")?;
|
|
@@ -326,17 +558,131 @@ pub fn arithmetic_amount_expr(source: &Json, command: &Json, value_objects_by_na
|
|
|
326
558
|
}
|
|
327
559
|
if kind == "argument" {
|
|
328
560
|
let name = source.get("name").map(Json::to_s).unwrap_or_default();
|
|
329
|
-
let attrs = command.get("attributes").
|
|
561
|
+
let attrs = command.get("attributes").map_or_default(Json::each);
|
|
330
562
|
let arg_attr = attrs.iter().find(|a| crate::attr::name(a) == name)?;
|
|
331
563
|
if crate::attr::type_name(arg_attr) == "Integer" {
|
|
332
|
-
return Some(format!(
|
|
564
|
+
return Some(format!(
|
|
565
|
+
"args.{}",
|
|
566
|
+
naming::rust_ident_field(crate::attr::name(arg_attr))
|
|
567
|
+
));
|
|
333
568
|
}
|
|
334
|
-
let field = integer_field_of(
|
|
335
|
-
|
|
569
|
+
let field = integer_field_of(
|
|
570
|
+
value_objects_by_name
|
|
571
|
+
.get(crate::attr::type_name(arg_attr))
|
|
572
|
+
.copied(),
|
|
573
|
+
)?;
|
|
574
|
+
return Some(format!(
|
|
575
|
+
"args.{}.{}",
|
|
576
|
+
naming::rust_ident_field(crate::attr::name(arg_attr)),
|
|
577
|
+
naming::rust_ident_field(&field)
|
|
578
|
+
));
|
|
336
579
|
}
|
|
337
580
|
None
|
|
338
581
|
}
|
|
339
582
|
|
|
583
|
+
/// The Rust expression for a text operand that can never be added to the target's Integer field.
|
|
584
|
+
///
|
|
585
|
+
/// Ruby wraps the amount as the target's own type before combining, so a required command
|
|
586
|
+
/// argument carrying text refuses `TypeMismatch` at the mutation step (C4.4). The rule is exact:
|
|
587
|
+
/// the argument is a single, required, non-list value and is either a bare `String` or a value
|
|
588
|
+
/// object whose only field is a `String` named as the target's Integer field (so the rewrap
|
|
589
|
+
/// lands on that field and no other refusal intervenes); the target is a required scalar. Any
|
|
590
|
+
/// other shape keeps its own refusal and so stays ungenerated. The expression yields the `String`.
|
|
591
|
+
pub fn text_operand_expr(
|
|
592
|
+
mutation: &Json,
|
|
593
|
+
aggregate: &Json,
|
|
594
|
+
command: &Json,
|
|
595
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
596
|
+
target_integer_field: &str,
|
|
597
|
+
) -> Option<String> {
|
|
598
|
+
let target = mutation.get("target").map(Json::to_s).unwrap_or_default();
|
|
599
|
+
let aggregate_attrs = aggregate.get("attributes").map_or_default(Json::each);
|
|
600
|
+
let target_attr = aggregate_attrs
|
|
601
|
+
.iter()
|
|
602
|
+
.find(|a| crate::attr::name(a) == target)?;
|
|
603
|
+
if crate::attr::optional(target_attr) {
|
|
604
|
+
return None;
|
|
605
|
+
}
|
|
606
|
+
let source = mutation.get("source")?;
|
|
607
|
+
if source.get("kind").map(Json::to_s).unwrap_or_default() != "argument" {
|
|
608
|
+
return None;
|
|
609
|
+
}
|
|
610
|
+
let name = source.get("name").map(Json::to_s).unwrap_or_default();
|
|
611
|
+
let command_attrs = command.get("attributes").map_or_default(Json::each);
|
|
612
|
+
let arg_attr = command_attrs
|
|
613
|
+
.iter()
|
|
614
|
+
.find(|a| crate::attr::name(a) == name)?;
|
|
615
|
+
if crate::attr::list(arg_attr) || crate::attr::optional(arg_attr) {
|
|
616
|
+
return None;
|
|
617
|
+
}
|
|
618
|
+
let arg = format!("args.{}", naming::rust_ident_field(&name));
|
|
619
|
+
if crate::attr::type_name(arg_attr) == "String" {
|
|
620
|
+
return Some(arg);
|
|
621
|
+
}
|
|
622
|
+
let vo = value_objects_by_name
|
|
623
|
+
.get(crate::attr::type_name(arg_attr))
|
|
624
|
+
.copied()?;
|
|
625
|
+
if vo.get("closed_set").is_some_and(Json::as_bool) {
|
|
626
|
+
return None;
|
|
627
|
+
}
|
|
628
|
+
let fields = vo.get("attributes").map_or_default(Json::each);
|
|
629
|
+
match fields {
|
|
630
|
+
[only]
|
|
631
|
+
if crate::attr::type_name(only) == "String"
|
|
632
|
+
&& !crate::attr::list(only)
|
|
633
|
+
&& !crate::attr::optional(only)
|
|
634
|
+
&& crate::attr::name(only) == target_integer_field =>
|
|
635
|
+
{
|
|
636
|
+
Some(format!(
|
|
637
|
+
"{arg}.{}",
|
|
638
|
+
naming::rust_ident_field(target_integer_field)
|
|
639
|
+
))
|
|
640
|
+
}
|
|
641
|
+
_ => None,
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// A Float target takes a Float or Integer amount, an Integer widened to `f64` as Ruby coerces it.
|
|
646
|
+
fn float_amount_expr(
|
|
647
|
+
source: &Json,
|
|
648
|
+
command: &Json,
|
|
649
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
650
|
+
) -> Option<String> {
|
|
651
|
+
let kind = source.get("kind").map(Json::to_s).unwrap_or_default();
|
|
652
|
+
if kind == "literal" {
|
|
653
|
+
let value = source.get("value")?;
|
|
654
|
+
return match value.number()? {
|
|
655
|
+
Number::Int(n) => Some(format!("({n} as f64)")),
|
|
656
|
+
Number::Float(_) => Some(naming::literal_rhs(value)),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
if kind != "argument" {
|
|
660
|
+
return None;
|
|
661
|
+
}
|
|
662
|
+
let name = source.get("name").map(Json::to_s).unwrap_or_default();
|
|
663
|
+
let attrs = command.get("attributes").map_or_default(Json::each);
|
|
664
|
+
let arg_attr = attrs.iter().find(|a| crate::attr::name(a) == name)?;
|
|
665
|
+
let arg = format!(
|
|
666
|
+
"args.{}",
|
|
667
|
+
naming::rust_ident_field(crate::attr::name(arg_attr))
|
|
668
|
+
);
|
|
669
|
+
match crate::attr::type_name(arg_attr) {
|
|
670
|
+
"Float" => return Some(arg),
|
|
671
|
+
"Integer" => return Some(format!("({arg} as f64)")),
|
|
672
|
+
_ => {}
|
|
673
|
+
}
|
|
674
|
+
let (field, field_kind) = numeric_field_of(
|
|
675
|
+
value_objects_by_name
|
|
676
|
+
.get(crate::attr::type_name(arg_attr))
|
|
677
|
+
.copied(),
|
|
678
|
+
)?;
|
|
679
|
+
let access = format!("{arg}.{}", naming::rust_ident_field(&field));
|
|
680
|
+
Some(match field_kind {
|
|
681
|
+
NumericKind::Float => access,
|
|
682
|
+
NumericKind::Integer => format!("({access} as f64)"),
|
|
683
|
+
})
|
|
684
|
+
}
|
|
685
|
+
|
|
340
686
|
/// `clamp:` bounds as `[min, max]` integers; `None` for anything else.
|
|
341
687
|
pub fn clamp_bounds_ints(source: &Json) -> Option<(i64, i64)> {
|
|
342
688
|
if source.get("kind").map(Json::to_s).unwrap_or_default() != "literal" {
|
|
@@ -347,38 +693,70 @@ pub fn clamp_bounds_ints(source: &Json) -> Option<(i64, i64)> {
|
|
|
347
693
|
if items.len() != 2 {
|
|
348
694
|
return None;
|
|
349
695
|
}
|
|
350
|
-
let Some(Number::Int(min)) = items[0].number() else {
|
|
351
|
-
|
|
696
|
+
let Some(Number::Int(min)) = items[0].number() else {
|
|
697
|
+
return None;
|
|
698
|
+
};
|
|
699
|
+
let Some(Number::Int(max)) = items[1].number() else {
|
|
700
|
+
return None;
|
|
701
|
+
};
|
|
352
702
|
Some((min, max))
|
|
353
703
|
}
|
|
354
704
|
|
|
355
705
|
/// An aggregate attribute a creating command's arguments never mention.
|
|
356
|
-
|
|
706
|
+
#[allow(
|
|
707
|
+
clippy::unwrap_used,
|
|
708
|
+
reason = "the value was checked just before; None would mean invalid IR"
|
|
709
|
+
)]
|
|
710
|
+
pub fn creation_default_rhs(
|
|
711
|
+
attr: &Json,
|
|
712
|
+
value_objects_by_name: &HashMap<String, &Json>,
|
|
713
|
+
) -> Option<String> {
|
|
357
714
|
if let Some(default) = crate::attr::default(attr) {
|
|
358
715
|
return Some(if let Json::Object(_) = default {
|
|
359
|
-
literal_hash_rhs(
|
|
716
|
+
literal_hash_rhs(
|
|
717
|
+
&Literal::from_json(default),
|
|
718
|
+
crate::attr::type_name(attr),
|
|
719
|
+
value_objects_by_name,
|
|
720
|
+
)
|
|
360
721
|
} else {
|
|
361
722
|
naming::literal_rhs(default)
|
|
362
723
|
});
|
|
363
724
|
}
|
|
364
725
|
|
|
365
726
|
let vo = value_objects_by_name.get(crate::attr::type_name(attr))?;
|
|
366
|
-
if vo.get("closed_set").
|
|
727
|
+
if vo.get("closed_set").is_some_and(Json::as_bool) {
|
|
367
728
|
return None;
|
|
368
729
|
}
|
|
369
|
-
let attrs = vo.get("attributes").
|
|
730
|
+
let attrs = vo.get("attributes").map_or_default(Json::each);
|
|
370
731
|
// Vacuously true on an empty list, as Ruby's `all?`; only a present field without a default
|
|
371
732
|
// fails.
|
|
372
733
|
if !attrs.iter().all(|f| crate::attr::default(f).is_some()) {
|
|
373
734
|
return None;
|
|
374
735
|
}
|
|
375
|
-
let fields: Vec<String> = attrs
|
|
376
|
-
|
|
736
|
+
let fields: Vec<String> = attrs
|
|
737
|
+
.iter()
|
|
738
|
+
.map(|f| {
|
|
739
|
+
format!(
|
|
740
|
+
"{}: {}",
|
|
741
|
+
naming::rust_ident_field(crate::attr::name(f)),
|
|
742
|
+
naming::literal_rhs(crate::attr::default(f).unwrap())
|
|
743
|
+
)
|
|
744
|
+
})
|
|
745
|
+
.collect();
|
|
746
|
+
Some(format!(
|
|
747
|
+
"{} {{ {} }}",
|
|
748
|
+
naming::rust_ident(crate::attr::type_name(attr)),
|
|
749
|
+
fields.join(", ")
|
|
750
|
+
))
|
|
377
751
|
}
|
|
378
752
|
|
|
379
753
|
/// The `corrects "EventName"` mutation, if this command declares one.
|
|
380
754
|
pub fn corrects_of(command: &Json) -> Option<&Json> {
|
|
381
|
-
command
|
|
755
|
+
command
|
|
756
|
+
.get("mutations")
|
|
757
|
+
.map_or_default(Json::each)
|
|
758
|
+
.iter()
|
|
759
|
+
.find(|m| m.get("op").map(Json::to_s).unwrap_or_default() == "corrects")
|
|
382
760
|
}
|
|
383
761
|
|
|
384
762
|
/// True when a `corrects` mutation carries `reverses: true`.
|
|
@@ -389,8 +767,7 @@ pub fn corrects_reverses(mutation: &Json) -> bool {
|
|
|
389
767
|
.get("source")
|
|
390
768
|
.and_then(|s| s.get("value"))
|
|
391
769
|
.and_then(|v| v.get("reverses"))
|
|
392
|
-
.
|
|
393
|
-
.unwrap_or(false)
|
|
770
|
+
.is_some_and(Json::as_bool)
|
|
394
771
|
}
|
|
395
772
|
|
|
396
773
|
/// Synthetic flag field name, e.g. `emitted_fee_applied` from `"FeeApplied"`; must match Ruby's.
|
|
@@ -412,8 +789,8 @@ pub fn corrects_flag_field(event_name: &str) -> String {
|
|
|
412
789
|
/// Every event name any command on this aggregate, entities included, names in a `corrects`
|
|
413
790
|
/// mutation. The flag field lives on the parent record whichever level declares it.
|
|
414
791
|
pub fn correctable_event_names(aggregate: &Json) -> Vec<String> {
|
|
415
|
-
let mut names = corrects_targets_of(aggregate.get("commands").
|
|
416
|
-
for entity in aggregate.get("entities").
|
|
792
|
+
let mut names = corrects_targets_of(aggregate.get("commands").map_or_default(Json::each));
|
|
793
|
+
for entity in aggregate.get("entities").map_or_default(Json::each) {
|
|
417
794
|
for name in entity_correctable_event_names(entity) {
|
|
418
795
|
if !names.contains(&name) {
|
|
419
796
|
names.push(name);
|
|
@@ -425,8 +802,8 @@ pub fn correctable_event_names(aggregate: &Json) -> Vec<String> {
|
|
|
425
802
|
|
|
426
803
|
/// The entity-recursive half of `correctable_event_names`.
|
|
427
804
|
fn entity_correctable_event_names(entity: &Json) -> Vec<String> {
|
|
428
|
-
let mut names = corrects_targets_of(entity.get("commands").
|
|
429
|
-
for nested in entity.get("entities").
|
|
805
|
+
let mut names = corrects_targets_of(entity.get("commands").map_or_default(Json::each));
|
|
806
|
+
for nested in entity.get("entities").map_or_default(Json::each) {
|
|
430
807
|
for name in entity_correctable_event_names(nested) {
|
|
431
808
|
if !names.contains(&name) {
|
|
432
809
|
names.push(name);
|
|
@@ -439,7 +816,7 @@ fn entity_correctable_event_names(entity: &Json) -> Vec<String> {
|
|
|
439
816
|
fn corrects_targets_of(commands: &[Json]) -> Vec<String> {
|
|
440
817
|
let mut names: Vec<String> = Vec::new();
|
|
441
818
|
for c in commands {
|
|
442
|
-
for m in c.get("mutations").
|
|
819
|
+
for m in c.get("mutations").map_or_default(Json::each) {
|
|
443
820
|
if m.get("op").map(Json::to_s).unwrap_or_default() == "corrects" {
|
|
444
821
|
let name = m.get("target").map(Json::to_s).unwrap_or_default();
|
|
445
822
|
if !names.contains(&name) {
|
|
@@ -453,7 +830,9 @@ fn corrects_targets_of(commands: &[Json]) -> Vec<String> {
|
|
|
453
830
|
|
|
454
831
|
/// The synthetic `GivenSpec` prepended to a `corrects`-declaring command.
|
|
455
832
|
pub fn corrects_given_specs(command: &Json) -> Vec<String> {
|
|
456
|
-
let Some(corrects) = corrects_of(command) else {
|
|
833
|
+
let Some(corrects) = corrects_of(command) else {
|
|
834
|
+
return Vec::new();
|
|
835
|
+
};
|
|
457
836
|
let event_name = corrects.get("target").map(Json::to_s).unwrap_or_default();
|
|
458
837
|
vec![format!(
|
|
459
838
|
" crate::kernel::GivenSpec {{ description: \"\", expr: crate::kernel::Expr::Lookup({:?}), corrects_event: Some({:?}) }},",
|
|
@@ -462,20 +841,200 @@ pub fn corrects_given_specs(command: &Json) -> Vec<String> {
|
|
|
462
841
|
)]
|
|
463
842
|
}
|
|
464
843
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
844
|
+
#[cfg(test)]
|
|
845
|
+
mod text_operand_tests {
|
|
846
|
+
use super::*;
|
|
847
|
+
|
|
848
|
+
fn aggregate() -> Json {
|
|
849
|
+
Json::parse(
|
|
850
|
+
r#"{"attributes":[
|
|
851
|
+
{"name":"count","type":"Count","list":false,"optional":false},
|
|
852
|
+
{"name":"limit","type":"Count","list":false,"optional":true}]}"#,
|
|
853
|
+
)
|
|
854
|
+
.unwrap()
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
fn vos() -> Vec<Json> {
|
|
858
|
+
vec![
|
|
859
|
+
Json::parse(
|
|
860
|
+
r#"{"name":"Count","attributes":[{"name":"value","type":"Integer","list":false}]}"#,
|
|
861
|
+
)
|
|
862
|
+
.unwrap(),
|
|
863
|
+
Json::parse(
|
|
864
|
+
r#"{"name":"Label","attributes":[{"name":"value","type":"String","list":false}]}"#,
|
|
865
|
+
)
|
|
866
|
+
.unwrap(),
|
|
867
|
+
Json::parse(
|
|
868
|
+
r#"{"name":"Note","attributes":[{"name":"text","type":"String","list":false}]}"#,
|
|
869
|
+
)
|
|
870
|
+
.unwrap(),
|
|
871
|
+
]
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
fn by_name(vos: &[Json]) -> HashMap<String, &Json> {
|
|
875
|
+
vos.iter()
|
|
876
|
+
.map(|vo| (vo.get("name").map(Json::to_s).unwrap_or_default(), vo))
|
|
877
|
+
.collect()
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
fn operand(target: &str, arg_type: &str, arg_flags: &str) -> Option<String> {
|
|
881
|
+
let mutation = Json::parse(&format!(
|
|
882
|
+
r#"{{"target":"{target}","op":"increment","source":{{"kind":"argument","name":"by"}}}}"#
|
|
883
|
+
))
|
|
884
|
+
.unwrap();
|
|
885
|
+
let command = Json::parse(&format!(
|
|
886
|
+
r#"{{"attributes":[{{"name":"by","type":"{arg_type}","list":false{arg_flags}}}]}}"#
|
|
887
|
+
))
|
|
888
|
+
.unwrap();
|
|
889
|
+
let vos = vos();
|
|
890
|
+
text_operand_expr(&mutation, &aggregate(), &command, &by_name(&vos), "value")
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
#[test]
|
|
894
|
+
fn a_value_object_holding_text_in_the_target_field_name_is_a_text_operand() {
|
|
895
|
+
assert_eq!(
|
|
896
|
+
operand("count", "Label", "").as_deref(),
|
|
897
|
+
Some("args.by.value")
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
#[test]
|
|
902
|
+
fn a_bare_string_argument_is_a_text_operand() {
|
|
903
|
+
assert_eq!(operand("count", "String", "").as_deref(), Some("args.by"));
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
#[test]
|
|
907
|
+
fn a_numeric_operand_is_not_a_text_operand() {
|
|
908
|
+
assert_eq!(operand("count", "Count", ""), None);
|
|
909
|
+
assert_eq!(operand("count", "Integer", ""), None);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
#[test]
|
|
913
|
+
fn a_text_field_named_otherwise_keeps_its_own_refusal() {
|
|
914
|
+
assert_eq!(operand("count", "Note", ""), None);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
#[test]
|
|
918
|
+
fn an_optional_target_or_argument_keeps_its_own_behaviour() {
|
|
919
|
+
assert_eq!(operand("limit", "Label", ""), None);
|
|
920
|
+
assert_eq!(operand("count", "Label", r#","optional":true"#), None);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
#[cfg(test)]
|
|
925
|
+
mod float_arithmetic_tests {
|
|
926
|
+
use super::*;
|
|
927
|
+
|
|
928
|
+
fn vos() -> Vec<Json> {
|
|
929
|
+
["Count:Integer", "Weight:Float", "Label:String"]
|
|
930
|
+
.iter()
|
|
931
|
+
.map(|pair| {
|
|
932
|
+
let (name, ty) = pair.split_once(':').unwrap();
|
|
933
|
+
Json::parse(&format!(
|
|
934
|
+
r#"{{"name":"{name}","attributes":[{{"name":"value","type":"{ty}","list":false}}]}}"#
|
|
935
|
+
))
|
|
936
|
+
.unwrap()
|
|
937
|
+
})
|
|
938
|
+
.collect()
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
fn by_name(vos: &[Json]) -> HashMap<String, &Json> {
|
|
942
|
+
vos.iter()
|
|
943
|
+
.map(|vo| (vo.get("name").map(Json::to_s).unwrap_or_default(), vo))
|
|
944
|
+
.collect()
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
fn aggregate() -> Json {
|
|
948
|
+
Json::parse(r#"{"attributes":[{"name":"weight","type":"Weight","list":false},{"name":"count","type":"Count","list":false},{"name":"label","type":"Label","list":false}]}"#).unwrap()
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
fn command() -> Json {
|
|
952
|
+
Json::parse(r#"{"attributes":[{"name":"more","type":"Weight"},{"name":"n","type":"Integer"},{"name":"f","type":"Float"},{"name":"c","type":"Count"}]}"#).unwrap()
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
fn mutation(target: &str) -> Json {
|
|
956
|
+
Json::parse(&format!(r#"{{"target":"{target}","op":"increment"}}"#)).unwrap()
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
fn amount(source: &str, kind: NumericKind) -> Option<String> {
|
|
960
|
+
let vos = vos();
|
|
961
|
+
arithmetic_amount_expr(
|
|
962
|
+
&Json::parse(source).unwrap(),
|
|
963
|
+
&command(),
|
|
964
|
+
&by_name(&vos),
|
|
965
|
+
"value",
|
|
966
|
+
kind,
|
|
967
|
+
)
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
#[test]
|
|
971
|
+
fn a_float_field_value_object_is_a_float_target() {
|
|
972
|
+
let vos = vos();
|
|
973
|
+
let aggregate = aggregate();
|
|
974
|
+
let (_, field, kind) =
|
|
975
|
+
arithmetic_target_field(&mutation("weight"), &aggregate, &by_name(&vos)).unwrap();
|
|
976
|
+
assert_eq!((field.as_str(), kind), ("value", NumericKind::Float));
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
#[test]
|
|
980
|
+
fn an_integer_field_value_object_stays_an_integer_target() {
|
|
981
|
+
let vos = vos();
|
|
982
|
+
let aggregate = aggregate();
|
|
983
|
+
let (_, _, kind) =
|
|
984
|
+
arithmetic_target_field(&mutation("count"), &aggregate, &by_name(&vos)).unwrap();
|
|
985
|
+
assert_eq!(kind, NumericKind::Integer);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
#[test]
|
|
989
|
+
fn a_text_field_value_object_is_no_arithmetic_target() {
|
|
990
|
+
let vos = vos();
|
|
991
|
+
let aggregate = aggregate();
|
|
992
|
+
assert!(arithmetic_target_field(&mutation("label"), &aggregate, &by_name(&vos)).is_none());
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
#[test]
|
|
996
|
+
fn clamp_still_takes_only_integer_targets() {
|
|
997
|
+
let vos = vos();
|
|
998
|
+
let aggregate = aggregate();
|
|
999
|
+
assert!(integer_target_field(&mutation("weight"), &aggregate, &by_name(&vos)).is_none());
|
|
1000
|
+
assert!(integer_target_field(&mutation("count"), &aggregate, &by_name(&vos)).is_some());
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
#[test]
|
|
1004
|
+
fn a_float_target_reads_a_float_value_object_argument() {
|
|
1005
|
+
let source = r#"{"kind":"argument","name":"more"}"#;
|
|
1006
|
+
assert_eq!(
|
|
1007
|
+
amount(source, NumericKind::Float).as_deref(),
|
|
1008
|
+
Some("args.more.value")
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
#[test]
|
|
1013
|
+
fn a_float_target_widens_an_integer_argument_and_integer_value_object() {
|
|
1014
|
+
assert_eq!(
|
|
1015
|
+
amount(r#"{"kind":"argument","name":"n"}"#, NumericKind::Float).as_deref(),
|
|
1016
|
+
Some("(args.n as f64)")
|
|
1017
|
+
);
|
|
1018
|
+
assert_eq!(
|
|
1019
|
+
amount(r#"{"kind":"argument","name":"c"}"#, NumericKind::Float).as_deref(),
|
|
1020
|
+
Some("(args.c.value as f64)")
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
#[test]
|
|
1025
|
+
fn a_float_target_takes_float_and_integer_literals() {
|
|
1026
|
+
assert_eq!(
|
|
1027
|
+
amount(r#"{"kind":"literal","value":2.5}"#, NumericKind::Float).as_deref(),
|
|
1028
|
+
Some("2.5")
|
|
1029
|
+
);
|
|
1030
|
+
assert_eq!(
|
|
1031
|
+
amount(r#"{"kind":"literal","value":3}"#, NumericKind::Float).as_deref(),
|
|
1032
|
+
Some("(3 as f64)")
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
#[test]
|
|
1037
|
+
fn an_integer_target_still_refuses_a_float_literal() {
|
|
1038
|
+
assert!(amount(r#"{"kind":"literal","value":2.5}"#, NumericKind::Integer).is_none());
|
|
1039
|
+
}
|
|
481
1040
|
}
|