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