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
|
@@ -98,7 +98,8 @@ pub fn parse_body(
|
|
|
98
98
|
// An inline type-position `one_of(...)` set is returned beside the value object (see
|
|
99
99
|
// build/closed_sets.rs); `ValueObject` has no nested-value-objects field.
|
|
100
100
|
"attribute" => {
|
|
101
|
-
let (attribute, one_of, closed_set) =
|
|
101
|
+
let (attribute, one_of, closed_set) =
|
|
102
|
+
build_value_object_attribute(file, &gated.args)?;
|
|
102
103
|
synthesized.extend(closed_set);
|
|
103
104
|
if let Some(values) = one_of {
|
|
104
105
|
let vo_name = vo.name.clone();
|
|
@@ -140,7 +141,11 @@ pub fn parse_body(
|
|
|
140
141
|
fn build_value_object_attribute(
|
|
141
142
|
file: &str,
|
|
142
143
|
args: &super::ArgumentGateResult,
|
|
143
|
-
) -> ParseResult<(
|
|
144
|
+
) -> ParseResult<(
|
|
145
|
+
ir::Attribute,
|
|
146
|
+
Option<Vec<ruby_value::Value>>,
|
|
147
|
+
Option<ir::ValueObject>,
|
|
148
|
+
)> {
|
|
144
149
|
let (attribute, closed_set) = super::build_attribute(file, 0, "attribute", args)?;
|
|
145
150
|
let one_of = super::named_raw(args, "one_of").map(|raw| {
|
|
146
151
|
let trimmed = raw.trim();
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
//! The `World` construct: a `.world` file's `Hecks.world "Name" do ... end` blocks, each holding
|
|
2
|
+
//! the scalar words `realm`, `latest`, `default_adapter`, `default_database` and any number of
|
|
3
|
+
//! open-vocabulary bindings (`persisted_by("Heki") do ... end`, `launcher "Launcher", k: v`).
|
|
4
|
+
//! Printed by `hecks-parse world`; worlds never enter `ir.json`.
|
|
5
|
+
//!
|
|
6
|
+
//! The settings bag is open, so a binding verb has no keyword row: it is shape-matched, and only
|
|
7
|
+
//! its verb, adapter and setting names are recorded. Setting values are never read into the IR
|
|
8
|
+
//! (they can be secrets) and nothing is ever evaluated.
|
|
9
|
+
|
|
10
|
+
use crate::diag::{Diagnostic, ParseResult};
|
|
11
|
+
use crate::ir;
|
|
12
|
+
use crate::keywords;
|
|
13
|
+
use crate::lex::{self, LineShape, Opener, SourceLine};
|
|
14
|
+
use crate::ruby_value::{self, Value};
|
|
15
|
+
|
|
16
|
+
/// Ruby methods that do something rather than name a binding. The settings bag is open, so these
|
|
17
|
+
/// are the words refused by name: a `.world` file only declares, it never runs anything.
|
|
18
|
+
const RUBY_KERNEL_WORDS: &[&str] = &[
|
|
19
|
+
"puts",
|
|
20
|
+
"print",
|
|
21
|
+
"p",
|
|
22
|
+
"pp",
|
|
23
|
+
"require",
|
|
24
|
+
"require_relative",
|
|
25
|
+
"load",
|
|
26
|
+
"eval",
|
|
27
|
+
"instance_eval",
|
|
28
|
+
"system",
|
|
29
|
+
"exec",
|
|
30
|
+
"spawn",
|
|
31
|
+
"raise",
|
|
32
|
+
"fail",
|
|
33
|
+
"abort",
|
|
34
|
+
"exit",
|
|
35
|
+
"sleep",
|
|
36
|
+
"send",
|
|
37
|
+
"public_send",
|
|
38
|
+
"binding",
|
|
39
|
+
"at_exit",
|
|
40
|
+
"warn",
|
|
41
|
+
"gets",
|
|
42
|
+
"open",
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/// Parses every top-level `world` block of one `.world` file, in file order, unmerged.
|
|
46
|
+
pub fn parse_file(path: &str, source: &str) -> ParseResult<Vec<ir::World>> {
|
|
47
|
+
let joined = lex::join_continuations(source);
|
|
48
|
+
let lines = lex::lines(&joined);
|
|
49
|
+
let mut pos = 0usize;
|
|
50
|
+
let mut worlds = Vec::new();
|
|
51
|
+
|
|
52
|
+
while pos < lines.len() {
|
|
53
|
+
let (row, call, header_line) = super::file::parse_header(path, &lines, &mut pos)?;
|
|
54
|
+
if row.inner != "World" {
|
|
55
|
+
return Err(Diagnostic::new(
|
|
56
|
+
path,
|
|
57
|
+
header_line,
|
|
58
|
+
format!(
|
|
59
|
+
"a .world file's own top-level blocks must all be 'world'; got '{}'",
|
|
60
|
+
call.word
|
|
61
|
+
),
|
|
62
|
+
));
|
|
63
|
+
}
|
|
64
|
+
let domain = super::file::header_name(&call)
|
|
65
|
+
.filter(|name| !name.is_empty())
|
|
66
|
+
.ok_or_else(|| {
|
|
67
|
+
Diagnostic::new(path, header_line, "'world' needs a non-empty quoted name")
|
|
68
|
+
})?;
|
|
69
|
+
worlds.push(parse_body(path, &lines, &mut pos, domain)?);
|
|
70
|
+
}
|
|
71
|
+
Ok(worlds)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Folds blocks into one world per distinct domain, in order of first appearance, as the Ruby
|
|
75
|
+
/// registry does: a later scalar replaces an earlier one; a later binding replaces the earlier
|
|
76
|
+
/// binding with the same key wholesale, in the earlier slot; new keys append.
|
|
77
|
+
pub fn merge(blocks: Vec<ir::World>) -> Vec<ir::World> {
|
|
78
|
+
let mut merged: Vec<ir::World> = Vec::new();
|
|
79
|
+
for block in blocks {
|
|
80
|
+
let Some(existing) = merged.iter_mut().find(|w| w.domain == block.domain) else {
|
|
81
|
+
merged.push(block);
|
|
82
|
+
continue;
|
|
83
|
+
};
|
|
84
|
+
if block.realm.is_some() {
|
|
85
|
+
existing.realm = block.realm;
|
|
86
|
+
}
|
|
87
|
+
if block.latest.is_some() {
|
|
88
|
+
existing.latest = block.latest;
|
|
89
|
+
}
|
|
90
|
+
if block.default_adapter.is_some() {
|
|
91
|
+
existing.default_adapter = block.default_adapter;
|
|
92
|
+
}
|
|
93
|
+
existing.default_database |= block.default_database;
|
|
94
|
+
for binding in block.bindings {
|
|
95
|
+
upsert(&mut existing.bindings, binding);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
merged
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// A repeated (verb, adapter) key keeps its first position with the last contents.
|
|
102
|
+
fn upsert(bindings: &mut Vec<ir::Binding>, binding: ir::Binding) {
|
|
103
|
+
let same = |b: &ir::Binding| {
|
|
104
|
+
b.verb == binding.verb && b.adapter.to_lowercase() == binding.adapter.to_lowercase()
|
|
105
|
+
};
|
|
106
|
+
match bindings.iter_mut().find(|b| same(b)) {
|
|
107
|
+
Some(slot) => *slot = binding,
|
|
108
|
+
None => bindings.push(binding),
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn parse_body(
|
|
113
|
+
file: &str,
|
|
114
|
+
lines: &[SourceLine],
|
|
115
|
+
pos: &mut usize,
|
|
116
|
+
domain: String,
|
|
117
|
+
) -> ParseResult<ir::World> {
|
|
118
|
+
let mut world = ir::World {
|
|
119
|
+
domain,
|
|
120
|
+
..ir::World::default()
|
|
121
|
+
};
|
|
122
|
+
loop {
|
|
123
|
+
let line = *lines.get(*pos).ok_or_else(|| {
|
|
124
|
+
let last = lines.last().map_or(0, |l| l.number);
|
|
125
|
+
Diagnostic::new(file, last, "unexpected end of file — still inside World")
|
|
126
|
+
})?;
|
|
127
|
+
let LineShape::Call(call) = lex::classify(file, &line)? else {
|
|
128
|
+
*pos += 1;
|
|
129
|
+
return Ok(world);
|
|
130
|
+
};
|
|
131
|
+
match call.word.as_str() {
|
|
132
|
+
"default_database" => {
|
|
133
|
+
*pos += 1;
|
|
134
|
+
world.default_database = default_database_set(file, line.number, &call)?;
|
|
135
|
+
}
|
|
136
|
+
"realm" | "latest" | "default_adapter" => {
|
|
137
|
+
let gated = super::next_line(file, lines, pos, "World")?.ok_or_else(|| {
|
|
138
|
+
Diagnostic::new(file, line.number, "unexpected 'end' inside World")
|
|
139
|
+
})?;
|
|
140
|
+
let value = scalar_text(file, line.number, &call.word, &gated.args)?;
|
|
141
|
+
match call.word.as_str() {
|
|
142
|
+
"realm" => world.realm = Some(value),
|
|
143
|
+
"latest" => world.latest = Some(value),
|
|
144
|
+
_ => world.default_adapter = Some(value),
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
_ => {
|
|
148
|
+
*pos += 1;
|
|
149
|
+
let binding = parse_binding(file, lines, pos, line.number, &call)?;
|
|
150
|
+
upsert(&mut world.bindings, binding);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// A non-empty string literal, as Ruby's world language requires of these words.
|
|
157
|
+
fn scalar_text(
|
|
158
|
+
file: &str,
|
|
159
|
+
line: usize,
|
|
160
|
+
word: &str,
|
|
161
|
+
args: &super::ArgumentGateResult,
|
|
162
|
+
) -> ParseResult<String> {
|
|
163
|
+
let raw = args.positional.first().map_or("", |(_, v)| v.trim());
|
|
164
|
+
if !raw.starts_with('"') {
|
|
165
|
+
return Err(Diagnostic::new(
|
|
166
|
+
file,
|
|
167
|
+
line,
|
|
168
|
+
format!("'{word}' takes a quoted string"),
|
|
169
|
+
));
|
|
170
|
+
}
|
|
171
|
+
let text = super::positional_text(file, line, word, args, 1)?;
|
|
172
|
+
if text.is_empty() {
|
|
173
|
+
return Err(Diagnostic::new(
|
|
174
|
+
file,
|
|
175
|
+
line,
|
|
176
|
+
format!("'{word}' must not be empty"),
|
|
177
|
+
));
|
|
178
|
+
}
|
|
179
|
+
Ok(text)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// `default_database` takes a string or any Ruby expression (an environment lookup). The expression
|
|
183
|
+
/// is neither evaluated nor kept: only that it was set. A literal empty string or `nil` refuses.
|
|
184
|
+
fn default_database_set(file: &str, line: usize, call: &lex::Call) -> ParseResult<bool> {
|
|
185
|
+
let items = ruby_value::split_items(&call.args);
|
|
186
|
+
let single = items.len() == 1 && call.opener == Opener::None;
|
|
187
|
+
if !single || super::as_named(&items[0]).is_some() {
|
|
188
|
+
return Err(Diagnostic::new(
|
|
189
|
+
file,
|
|
190
|
+
line,
|
|
191
|
+
"'default_database' takes exactly one value and no block",
|
|
192
|
+
));
|
|
193
|
+
}
|
|
194
|
+
if matches!(ruby_value::read(&items[0]), Value::Str(ref s) if s.is_empty()) || items[0] == "nil"
|
|
195
|
+
{
|
|
196
|
+
return Err(Diagnostic::new(
|
|
197
|
+
file,
|
|
198
|
+
line,
|
|
199
|
+
"'default_database' must not be empty",
|
|
200
|
+
));
|
|
201
|
+
}
|
|
202
|
+
Ok(true)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/// One binding call: `verb("Adapter") do ... end`, or `verb "Adapter", k: v, ...` with no block.
|
|
206
|
+
/// Records the verb, the first positional string, the keyword names, then the body's top-level
|
|
207
|
+
/// keys. A verb that is a grammar word elsewhere is refused, as Ruby's word gate refuses it.
|
|
208
|
+
fn parse_binding(
|
|
209
|
+
file: &str,
|
|
210
|
+
lines: &[SourceLine],
|
|
211
|
+
pos: &mut usize,
|
|
212
|
+
number: usize,
|
|
213
|
+
call: &lex::Call,
|
|
214
|
+
) -> ParseResult<ir::Binding> {
|
|
215
|
+
if RUBY_KERNEL_WORDS.contains(&call.word.as_str()) {
|
|
216
|
+
return Err(Diagnostic::new(
|
|
217
|
+
file,
|
|
218
|
+
number,
|
|
219
|
+
format!(
|
|
220
|
+
"'{}' is a Ruby method, not a binding verb; a .world file only declares",
|
|
221
|
+
call.word
|
|
222
|
+
),
|
|
223
|
+
));
|
|
224
|
+
}
|
|
225
|
+
if keywords::KEYWORDS
|
|
226
|
+
.iter()
|
|
227
|
+
.any(|k| k.live() && k.word == call.word)
|
|
228
|
+
{
|
|
229
|
+
return Err(Diagnostic::new(
|
|
230
|
+
file,
|
|
231
|
+
number,
|
|
232
|
+
format!(
|
|
233
|
+
"'{}' is a grammar word, not a binding verb, and World does not admit it",
|
|
234
|
+
call.word
|
|
235
|
+
),
|
|
236
|
+
));
|
|
237
|
+
}
|
|
238
|
+
let mut adapter = None;
|
|
239
|
+
let mut settings: Vec<String> = Vec::new();
|
|
240
|
+
for item in ruby_value::split_items(&call.args) {
|
|
241
|
+
if let Some((name, _value)) = super::as_named(&item) {
|
|
242
|
+
push_unique(&mut settings, name.to_string());
|
|
243
|
+
} else if adapter.is_none() && item.starts_with('"') {
|
|
244
|
+
adapter = Some(match ruby_value::read(&item) {
|
|
245
|
+
Value::Str(s) => s,
|
|
246
|
+
other => ruby_value::to_s(&other),
|
|
247
|
+
});
|
|
248
|
+
} else {
|
|
249
|
+
return Err(Diagnostic::new(
|
|
250
|
+
file,
|
|
251
|
+
number,
|
|
252
|
+
format!(
|
|
253
|
+
"'{}' takes one quoted adapter name, then keyword arguments",
|
|
254
|
+
call.word
|
|
255
|
+
),
|
|
256
|
+
));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
let adapter = adapter.ok_or_else(|| {
|
|
260
|
+
Diagnostic::new(
|
|
261
|
+
file,
|
|
262
|
+
number,
|
|
263
|
+
format!("'{}' needs a quoted adapter name", call.word),
|
|
264
|
+
)
|
|
265
|
+
})?;
|
|
266
|
+
match call.opener {
|
|
267
|
+
Opener::None => {}
|
|
268
|
+
Opener::DoBlock { .. } => body_keys(file, lines, pos, &mut settings)?,
|
|
269
|
+
Opener::BraceBlock { .. } => {
|
|
270
|
+
return Err(Diagnostic::new(
|
|
271
|
+
file,
|
|
272
|
+
number,
|
|
273
|
+
format!("'{}' takes a `do ... end` block, not braces", call.word),
|
|
274
|
+
))
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
Ok(ir::Binding {
|
|
278
|
+
verb: call.word.clone(),
|
|
279
|
+
adapter,
|
|
280
|
+
settings,
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/// Collects a binding block's top-level setting names, tracking nested blocks through the shape
|
|
285
|
+
/// gate so a malformed line inside still refuses. Values are not read.
|
|
286
|
+
fn body_keys(
|
|
287
|
+
file: &str,
|
|
288
|
+
lines: &[SourceLine],
|
|
289
|
+
pos: &mut usize,
|
|
290
|
+
settings: &mut Vec<String>,
|
|
291
|
+
) -> ParseResult<()> {
|
|
292
|
+
let mut depth = 1;
|
|
293
|
+
while depth > 0 {
|
|
294
|
+
let line = *lines.get(*pos).ok_or_else(|| {
|
|
295
|
+
let last = lines.last().map_or(0, |l| l.number);
|
|
296
|
+
Diagnostic::new(
|
|
297
|
+
file,
|
|
298
|
+
last,
|
|
299
|
+
"unexpected end of file — still inside a binding block",
|
|
300
|
+
)
|
|
301
|
+
})?;
|
|
302
|
+
*pos += 1;
|
|
303
|
+
match lex::classify(file, &line)? {
|
|
304
|
+
LineShape::End => depth -= 1,
|
|
305
|
+
LineShape::Call(call) => {
|
|
306
|
+
if depth == 1 {
|
|
307
|
+
push_unique(settings, call.word.clone());
|
|
308
|
+
}
|
|
309
|
+
if matches!(call.opener, Opener::DoBlock { .. }) {
|
|
310
|
+
depth += 1;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
Ok(())
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
fn push_unique(names: &mut Vec<String>, name: String) {
|
|
319
|
+
if !names.contains(&name) {
|
|
320
|
+
names.push(name);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -10,8 +10,8 @@ pub enum Value {
|
|
|
10
10
|
Float(f64),
|
|
11
11
|
Str(String),
|
|
12
12
|
Symbol(String),
|
|
13
|
-
Hash(Vec<(String,
|
|
14
|
-
Array(Vec<
|
|
13
|
+
Hash(Vec<(String, Self)>),
|
|
14
|
+
Array(Vec<Self>),
|
|
15
15
|
/// A bare word `Literal.read` tolerates; kept apart from `Str` so `render` adds no quotes.
|
|
16
16
|
Bare(String),
|
|
17
17
|
}
|
|
@@ -306,9 +306,7 @@ fn scan_adjacent_strings(raw: &str) -> Option<(String, usize)> {
|
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
// Defensive: return `None` rather than panic on an empty or odd `raw`.
|
|
309
|
-
let
|
|
310
|
-
return None;
|
|
311
|
-
};
|
|
309
|
+
let "e = chars.get(i).filter(|c| **c == '"' || **c == '\'')?;
|
|
312
310
|
|
|
313
311
|
let start = i;
|
|
314
312
|
i += 1;
|
|
@@ -487,7 +485,10 @@ mod tests {
|
|
|
487
485
|
#[test]
|
|
488
486
|
fn double_quoted_escapes_read_as_ruby_reads_them() {
|
|
489
487
|
assert_eq!(read(r#""a\tb\nc""#), Value::Str("a\tb\nc".to_string()));
|
|
490
|
-
assert_eq!(
|
|
488
|
+
assert_eq!(
|
|
489
|
+
read(r#""\e\s\a\0""#),
|
|
490
|
+
Value::Str("\u{1b} \u{7}\0".to_string())
|
|
491
|
+
);
|
|
491
492
|
assert_eq!(
|
|
492
493
|
read(r#""\x41\101é\u{1F4A5 41}""#),
|
|
493
494
|
Value::Str("AA\u{e9}\u{1F4A5}A".to_string())
|
data/rust/src/exemplar/json.rs
CHANGED
|
@@ -10,7 +10,7 @@ impl TmplKind {
|
|
|
10
10
|
pub fn to_json(&self) -> crate::kernel::Json {
|
|
11
11
|
let member = match self {
|
|
12
12
|
// TMPL:closed_set_codec:TO_JSON_ARM BEGIN
|
|
13
|
-
|
|
13
|
+
Self::TmplMemberA => "tmpl_member_a",
|
|
14
14
|
// TMPL:closed_set_codec:TO_JSON_ARM END
|
|
15
15
|
};
|
|
16
16
|
crate::kernel::Json::obj(vec![("tmpl_field_name", crate::kernel::Json::str(member))])
|
|
@@ -21,7 +21,7 @@ impl TmplKind {
|
|
|
21
21
|
// does in `validate!`: before a missing field, and before membership.
|
|
22
22
|
let unknown = v.unknown_keys(&["tmpl_field_name"]);
|
|
23
23
|
if !unknown.is_empty() {
|
|
24
|
-
let unknown: Vec<&str> = unknown.iter().map(
|
|
24
|
+
let unknown: Vec<&str> = unknown.iter().map(String::as_str).collect();
|
|
25
25
|
return Err(crate::kernel::Refusal::UnknownArgument(
|
|
26
26
|
crate::kernel::refusal_wording::UnknownArgumentUnknownArgsArgs {
|
|
27
27
|
command: "tmpl_closed_set_type",
|
|
@@ -72,7 +72,7 @@ impl TmplKind {
|
|
|
72
72
|
}
|
|
73
73
|
match candidate.ruby_to_s().as_str() {
|
|
74
74
|
// TMPL:closed_set_codec:FROM_JSON_ARM BEGIN
|
|
75
|
-
"tmpl_member_a" => Ok(
|
|
75
|
+
"tmpl_member_a" => Ok(Self::TmplMemberA),
|
|
76
76
|
// TMPL:closed_set_codec:FROM_JSON_ARM END
|
|
77
77
|
_ => Err(crate::kernel::Refusal::InvariantViolation(
|
|
78
78
|
crate::kernel::refusal_wording::InvariantViolationClosedSetMemberArgs {
|
|
@@ -134,7 +134,7 @@ tmpl_to_json_fields_block()
|
|
|
134
134
|
// membership of the whole row.
|
|
135
135
|
let unknown = v.unknown_keys(&TMPL_DECLARED_NAMES);
|
|
136
136
|
if !unknown.is_empty() {
|
|
137
|
-
let unknown: Vec<&str> = unknown.iter().map(
|
|
137
|
+
let unknown: Vec<&str> = unknown.iter().map(String::as_str).collect();
|
|
138
138
|
return Err(crate::kernel::Refusal::UnknownArgument(
|
|
139
139
|
crate::kernel::refusal_wording::UnknownArgumentUnknownArgsArgs {
|
|
140
140
|
command: "tmpl_table_type",
|
|
@@ -78,3 +78,21 @@ fn tmpl_mutation_remove_host(record: &mut TmplRemoveHost) {
|
|
|
78
78
|
record.tmpl_field.retain(|item| item.tmpl_id_field != tmpl_remove_match_placeholder());
|
|
79
79
|
// TMPL:mutation_remove END
|
|
80
80
|
}
|
|
81
|
+
|
|
82
|
+
// `remove:` on a value-object list drops every element equal by value to the argument, so
|
|
83
|
+
// the element type derives `PartialEq` and the retained ones are those that differ.
|
|
84
|
+
#[derive(PartialEq)]
|
|
85
|
+
struct TmplRemoveValueElement {
|
|
86
|
+
tmpl_value: i64,
|
|
87
|
+
}
|
|
88
|
+
struct TmplRemoveValueHost {
|
|
89
|
+
tmpl_field: Vec<TmplRemoveValueElement>,
|
|
90
|
+
}
|
|
91
|
+
fn tmpl_remove_value_placeholder() -> TmplRemoveValueElement {
|
|
92
|
+
TmplRemoveValueElement { tmpl_value: 0 }
|
|
93
|
+
}
|
|
94
|
+
fn tmpl_mutation_remove_value_host(record: &mut TmplRemoveValueHost) {
|
|
95
|
+
// TMPL:mutation_remove_value BEGIN
|
|
96
|
+
record.tmpl_field.retain(|item| *item != tmpl_remove_value_placeholder());
|
|
97
|
+
// TMPL:mutation_remove_value END
|
|
98
|
+
}
|
|
@@ -33,16 +33,7 @@ crate::kernel::read_model::ReadModelDef {
|
|
|
33
33
|
],
|
|
34
34
|
authorization: Some(crate::kernel::named_query::TenantAuth { query_name: "tmpl_query_name", tenant_field: "tmpl_tenant_field", policy: "tmpl_policy" }),
|
|
35
35
|
group_by: None,
|
|
36
|
-
|
|
37
|
-
median_field: None,
|
|
38
|
-
sum_field: None,
|
|
39
|
-
avg_field: None,
|
|
40
|
-
min_field: None,
|
|
41
|
-
max_field: None,
|
|
42
|
-
percentile_field: None,
|
|
43
|
-
percentile_at: None,
|
|
44
|
-
any_field: None,
|
|
45
|
-
all_field: None,
|
|
36
|
+
reduction: None,
|
|
46
37
|
},
|
|
47
38
|
];
|
|
48
39
|
// TMPL:read_model_table END
|
|
@@ -5,9 +5,11 @@ use crate::kernel::Refusal;
|
|
|
5
5
|
|
|
6
6
|
/// A scalar (or `Nil`) with segments left resolves to `Nil`, like Ruby's `walk_path`.
|
|
7
7
|
/// This makes `flag.nil?` read as a value instead of a refusal.
|
|
8
|
-
pub fn step<'a>(current: Field<'a>, seg: &str, head: &str) -> Result<Field<'a>, Refusal> {
|
|
8
|
+
pub fn step<'a>(current: &Field<'a>, seg: &str, head: &str) -> Result<Field<'a>, Refusal> {
|
|
9
9
|
match current {
|
|
10
|
-
Field::Nested(obj) => obj
|
|
10
|
+
Field::Nested(obj) => (*obj)
|
|
11
|
+
.field(seg)
|
|
12
|
+
.ok_or_else(|| eval_error(format!("cannot resolve {seg:?} on {head:?}"))),
|
|
11
13
|
Field::Value(_) => Ok(Field::Value(Value::Nil)),
|
|
12
14
|
}
|
|
13
15
|
}
|
|
@@ -16,7 +18,9 @@ pub fn step<'a>(current: Field<'a>, seg: &str, head: &str) -> Result<Field<'a>,
|
|
|
16
18
|
pub fn finish(current: Field<'_>, path: &str) -> Result<Value, Refusal> {
|
|
17
19
|
match current {
|
|
18
20
|
Field::Value(v) => Ok(v),
|
|
19
|
-
Field::Nested(obj) => obj
|
|
21
|
+
Field::Nested(obj) => obj
|
|
22
|
+
.as_scalar()
|
|
23
|
+
.ok_or_else(|| eval_error(format!("{path} resolved to an object, not a scalar"))),
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
|
|
@@ -39,50 +43,86 @@ mod tests {
|
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
// `Field` derives neither `Debug` nor `PartialEq`; assertions compare the `finish`ed `Value`.
|
|
42
|
-
fn stepped_value(current: Field<'_>, seg: &str, head: &str) -> Value {
|
|
46
|
+
fn stepped_value(current: &Field<'_>, seg: &str, head: &str) -> Value {
|
|
43
47
|
finish(step(current, seg, head).unwrap(), "test.path").unwrap()
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
#[test]
|
|
47
51
|
fn a_further_segment_on_a_bool_scalar_resolves_to_nil_not_a_refusal() {
|
|
48
52
|
// `.nil?` has no dedicated node, so it arrives as a trailing segment on a resolved scalar.
|
|
49
|
-
assert_eq!(
|
|
53
|
+
assert_eq!(
|
|
54
|
+
stepped_value(
|
|
55
|
+
&Field::Value(Value::Bool(true)),
|
|
56
|
+
"nil?",
|
|
57
|
+
"previous_sessions"
|
|
58
|
+
),
|
|
59
|
+
Value::Nil
|
|
60
|
+
);
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
#[test]
|
|
53
64
|
fn this_holds_for_a_false_scalar_too() {
|
|
54
|
-
assert_eq!(
|
|
65
|
+
assert_eq!(
|
|
66
|
+
stepped_value(
|
|
67
|
+
&Field::Value(Value::Bool(false)),
|
|
68
|
+
"nil?",
|
|
69
|
+
"previous_sessions"
|
|
70
|
+
),
|
|
71
|
+
Value::Nil
|
|
72
|
+
);
|
|
55
73
|
}
|
|
56
74
|
|
|
57
75
|
#[test]
|
|
58
76
|
fn this_holds_for_every_scalar_shape_not_just_bool() {
|
|
59
|
-
for value in [
|
|
60
|
-
|
|
77
|
+
for value in [
|
|
78
|
+
Value::Int(0),
|
|
79
|
+
Value::Float(1.5),
|
|
80
|
+
Value::Str("x".to_string()),
|
|
81
|
+
Value::List(3),
|
|
82
|
+
] {
|
|
83
|
+
assert_eq!(
|
|
84
|
+
stepped_value(&Field::Value(value.clone()), "nil?", "head"),
|
|
85
|
+
Value::Nil,
|
|
86
|
+
"expected Nil for {value:?}"
|
|
87
|
+
);
|
|
61
88
|
}
|
|
62
89
|
}
|
|
63
90
|
|
|
64
91
|
#[test]
|
|
65
92
|
fn a_further_segment_on_an_already_nil_value_stays_nil() {
|
|
66
93
|
// An unset optional field already reads as `Nil` before `step` runs.
|
|
67
|
-
assert_eq!(
|
|
94
|
+
assert_eq!(
|
|
95
|
+
stepped_value(&Field::Value(Value::Nil), "nil?", "superseded_by"),
|
|
96
|
+
Value::Nil
|
|
97
|
+
);
|
|
68
98
|
}
|
|
69
99
|
|
|
70
100
|
#[test]
|
|
71
101
|
fn a_real_nested_field_still_walks_normally() {
|
|
72
|
-
let customer = Customer {
|
|
73
|
-
|
|
102
|
+
let customer = Customer {
|
|
103
|
+
status: "active".to_string(),
|
|
104
|
+
};
|
|
105
|
+
assert_eq!(
|
|
106
|
+
stepped_value(&Field::Nested(&customer), "status", "customer"),
|
|
107
|
+
Value::Str("active".to_string())
|
|
108
|
+
);
|
|
74
109
|
}
|
|
75
110
|
|
|
76
111
|
#[test]
|
|
77
112
|
fn an_unknown_nested_field_still_refuses() {
|
|
78
113
|
// A generated `field()` is exhaustive, so an unknown name is a codegen bug: keep refusing.
|
|
79
|
-
let customer = Customer {
|
|
80
|
-
|
|
114
|
+
let customer = Customer {
|
|
115
|
+
status: "active".to_string(),
|
|
116
|
+
};
|
|
117
|
+
let stepped = step(&Field::Nested(&customer), "not_a_real_field", "customer");
|
|
81
118
|
assert!(stepped.is_err());
|
|
82
119
|
}
|
|
83
120
|
|
|
84
121
|
#[test]
|
|
85
122
|
fn finish_unwraps_a_resolved_nil_to_a_plain_value() {
|
|
86
|
-
assert_eq!(
|
|
123
|
+
assert_eq!(
|
|
124
|
+
finish(Field::Value(Value::Nil), "previous_sessions.nil?").unwrap(),
|
|
125
|
+
Value::Nil
|
|
126
|
+
);
|
|
87
127
|
}
|
|
88
128
|
}
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
use crate::kernel::expr::Value;
|
|
5
5
|
|
|
6
|
-
pub fn is_empty(v: &Value) -> Option<bool> {
|
|
6
|
+
pub const fn is_empty(v: &Value) -> Option<bool> {
|
|
7
7
|
match v {
|
|
8
8
|
Value::List(n) => Some(*n == 0),
|
|
9
9
|
_ => None,
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
pub fn size(v: &Value) -> Option<i64> {
|
|
13
|
+
pub const fn size(v: &Value) -> Option<i64> {
|
|
14
14
|
match v {
|
|
15
15
|
Value::List(n) => Some(*n as i64),
|
|
16
16
|
_ => None,
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
// `for_attribute` itself takes, read directly rather than
|
|
7
7
|
// re-derived here.
|
|
8
8
|
|
|
9
|
-
pub mod
|
|
9
|
+
pub mod composite;
|
|
10
10
|
pub mod list;
|
|
11
11
|
pub mod optional;
|
|
12
|
-
pub mod
|
|
12
|
+
pub mod scalar;
|
|
13
13
|
|
|
14
14
|
/// One variant per real attribute shape the Ruby
|
|
15
15
|
/// grammar admits, in the order Coercion::SHAPES declares them. Every match
|
|
@@ -31,14 +31,14 @@ impl AttributeShape {
|
|
|
31
31
|
/// variant names — `hecks measure_kernel_coverage`'s own existence
|
|
32
32
|
/// check reads this, so the file-name spelling here is the ONE
|
|
33
33
|
/// place that ever needs to change if a name is renamed.
|
|
34
|
-
pub fn module_name(&self) -> &'static str {
|
|
34
|
+
pub const fn module_name(&self) -> &'static str {
|
|
35
35
|
match self {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
Self::Scalar => "scalar",
|
|
37
|
+
Self::List => "list",
|
|
38
|
+
Self::Optional => "optional",
|
|
39
|
+
Self::Composite => "composite",
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
pub const ALL: &'static [
|
|
43
|
+
pub const ALL: &'static [Self] = &[Self::Scalar, Self::List, Self::Optional, Self::Composite];
|
|
44
44
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
use crate::kernel::expr::Value;
|
|
5
5
|
|
|
6
6
|
// Mirrors `Resolver.numeric`: `Int` and `Float` mix freely; any other `Value` is not a number.
|
|
7
|
-
pub fn numeric(v: &Value) -> Option<f64> {
|
|
7
|
+
pub const fn numeric(v: &Value) -> Option<f64> {
|
|
8
8
|
match v {
|
|
9
9
|
Value::Int(i) => Some(*i as f64),
|
|
10
10
|
Value::Float(f) => Some(*f),
|
|
@@ -24,7 +24,7 @@ pub fn to_s(v: &Value) -> Option<Value> {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// Only `Str` has a length; `Int`/`Float`/`Bool` answer `None` so the caller refuses.
|
|
27
|
-
pub fn is_empty(v: &Value) -> Option<bool> {
|
|
27
|
+
pub const fn is_empty(v: &Value) -> Option<bool> {
|
|
28
28
|
match v {
|
|
29
29
|
Value::Str(s) => Some(s.is_empty()),
|
|
30
30
|
_ => None,
|