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