hecks 3.2.1 → 3.4.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/lib/hecks/bluebook/assembly/contracts.rb +2 -1
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -39
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +21 -23
- data/lib/hecks/bluebook/dsl/need_word.rb +50 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +6 -1
- data/lib/hecks/bluebook/query.rb +8 -5
- data/lib/hecks/cli/interview_draft.rb +88 -33
- data/lib/hecks/cli/interview_session.rb +77 -26
- data/lib/hecks/cli/interview_shape.rb +198 -0
- data/lib/hecks/corpus.rb +3 -5
- data/lib/hecks/doors/cli_runner.rb +14 -1
- data/lib/hecks/doors/launcher_options.rb +13 -3
- data/lib/hecks/doors/mcp_door_scope.rb +2 -1
- data/lib/hecks/doors.rb +0 -17
- data/lib/hecks/embryonaut_bluebook/manifest.rb +117 -0
- data/lib/hecks/embryonaut_bluebook/registry.rb +209 -0
- data/lib/hecks/embryonaut_bluebook/vendor.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +2 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +1 -4
- data/lib/hecks/fuzzing/qa_settings.rb +15 -34
- data/lib/hecks/fuzzing/target_capabilities.rb +1 -1
- data/lib/hecks/gate/stages.yml +113 -8
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +10 -1
- data/lib/hecks/hecks/adapters/first_admin.rb +108 -0
- data/lib/hecks/hecks/adapters/git.rb +59 -0
- data/lib/hecks/hecks/adapters/in_process_access.rb +25 -0
- data/lib/hecks/hecks/adapters/in_process_boot.rb +2 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +18 -3
- data/lib/hecks/hecks/adapters/site_toolchain.rb +67 -0
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +21 -0
- data/lib/hecks/hecks/custodian.behaviors +6 -0
- data/lib/hecks/hecks/custodian.bluebook +217 -0
- data/lib/hecks/hecks/hecks.hecksagon +24 -0
- data/lib/hecks/hecks/hecks.world +8 -3
- data/lib/hecks/hecks/site.world +5 -2
- data/lib/hecks/language/bluebook/query.bluebook +25 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +213 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +0 -5
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/projections/deploy/box/hosting.rb +170 -0
- data/lib/hecks/projections/deploy/box/hosting_settings.rb +123 -0
- data/lib/hecks/projections/deploy/box/settings.rb +17 -4
- data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +33 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +79 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +77 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +12 -0
- data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +24 -0
- data/lib/hecks/projections/deploy/box/templates/smoke-after-deploy.sh.tmpl +271 -0
- data/lib/hecks/projections/deploy/box.rb +15 -2
- data/lib/hecks/projections/deploy/fargate.rb +3 -3
- data/lib/hecks/projections/deploy/lambda.rb +8 -8
- data/lib/hecks/projections/model/deviations.rb +1 -1
- data/lib/hecks/projections/site/live_edge.rb +162 -0
- data/lib/hecks/quality_control/adapters/git_pr.rb +0 -14
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +32 -16
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_seed_targets.rb +1 -2
- data/lib/hecks/quality_control/quality_control.bluebook +76 -124
- data/lib/hecks/runtime/interpreting.rb +3 -22
- data/lib/hecks/runtime/loader.rb +6 -7
- data/lib/hecks/runtime/needs.rb +48 -0
- data/lib/hecks/runtime/query_interpreter.rb +3 -0
- data/lib/hecks/runtime/registry/verification.rb +3 -3
- data/lib/hecks/runtime.rb +6 -11
- data/lib/hecks/rust_build/host.rb +147 -0
- data/lib/hecks/rust_build/kernel_input.rb +40 -18
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +0 -18
- data/lib/hecks/rust_build/project_rust.rb +3 -10
- data/lib/hecks/rust_build.rb +1 -0
- data/lib/hecks/site/bluebook/site.bluebook +53 -1
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/sme/bluebook/sme.bluebook +128 -1
- data/lib/hecks/tools/gate.rb +1 -1
- data/lib/hecks/tools/rust_comment_style.rb +1 -3
- data/lib/hecks/tools/tools_doc.rb +204 -0
- data/lib/hecks/tools.rb +1 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +151 -0
- data/lib/hecks.rb +6 -12
- data/qa/settings.yml +1 -2
- data/rust/build/src/lineage_pass.rs +30 -15
- data/rust/build/src/main.rs +4 -2
- data/rust/build/src/pipeline.rs +8 -19
- data/rust/codegen/src/bridging.rs +7 -7
- data/rust/codegen/src/json.rs +214 -23
- data/rust/codegen/src/literal.rs +6 -3
- data/rust/codegen/src/main.rs +20 -9
- data/rust/codegen/src/naming.rs +4 -2
- data/rust/codegen/src/optional_pass.rs +226 -0
- data/rust/{build → codegen}/src/sidecars.rs +9 -5
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +1 -1
- data/rust/host/src/dispatch.rs +5 -0
- data/rust/host/src/needs.rs +99 -2
- data/rust/host/src/resend.rs +4 -4
- data/rust/host/src/ui_schema.rs +1 -1
- data/rust/parser/src/emit.rs +12 -0
- data/rust/parser/src/ir.rs +2 -0
- data/rust/parser/src/keywords.rs +2 -4
- data/rust/parser/src/lex.rs +1 -1
- data/rust/parser/src/main.rs +1 -0
- data/rust/parser/src/parse/command.rs +2 -44
- data/rust/parser/src/parse/hecksagon.rs +15 -24
- data/rust/parser/src/parse/mod.rs +1 -0
- data/rust/parser/src/parse/needs.rs +60 -0
- data/rust/parser/src/parse/query.rs +3 -0
- data/rust/src/kernel/cli.rs +16 -6
- data/rust/src/kernel/needs.rs +104 -9
- metadata +22 -9
- data/lib/hecks/facade.rb +0 -31
- data/lib/hecks/rust_build/append_optionals.rb +0 -79
- data/lib/hecks/rust_build/rust_literal.rb +0 -33
- data/lib/hecks/three_zero/forms.yml +0 -127
- data/lib/hecks/three_zero.rb +0 -12
- data/rust/build/src/json.rs +0 -421
- data/rust/build/src/optional_pass.rs +0 -150
data/rust/codegen/src/main.rs
CHANGED
|
@@ -19,6 +19,7 @@ mod literal;
|
|
|
19
19
|
mod manifest;
|
|
20
20
|
mod mutations;
|
|
21
21
|
mod naming;
|
|
22
|
+
mod optional_pass;
|
|
22
23
|
mod ports;
|
|
23
24
|
mod prelude;
|
|
24
25
|
mod queries;
|
|
@@ -28,6 +29,7 @@ mod reference_specs;
|
|
|
28
29
|
mod registry;
|
|
29
30
|
mod reserved_names;
|
|
30
31
|
mod shared;
|
|
32
|
+
mod sidecars;
|
|
31
33
|
mod skip_reason;
|
|
32
34
|
mod types;
|
|
33
35
|
|
|
@@ -85,22 +87,29 @@ fn run_prelude(args: &[String]) -> Result<(), String> {
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
/// `hecks-codegen domain <ir.json> <source_label> <mod_name> <out_dir>` — writes one chapter's
|
|
88
|
-
/// `<aggregate>.rs`, `registry.rs`, `mod.rs
|
|
89
|
-
///
|
|
90
|
-
/// `metadata.rs` and `ir.json` are left to `hecks project_rust`.
|
|
90
|
+
/// `<aggregate>.rs`, `registry.rs`, `mod.rs`, `manifest.json` and its `ir.json`/`metadata.rs`
|
|
91
|
+
/// sidecars into `out_dir`.
|
|
91
92
|
fn run_domain(args: &[String]) -> Result<(), String> {
|
|
92
93
|
let [ir_path, source_label, mod_name, out_dir] = args else {
|
|
93
94
|
return Err("usage: hecks-codegen domain <ir.json> <source_label> <mod_name> <out_dir>".to_string());
|
|
94
95
|
};
|
|
95
96
|
|
|
96
|
-
let
|
|
97
|
-
let ir = Json::parse(&text).map_err(|e| format!("parsing {ir_path}: {e}"))?;
|
|
97
|
+
let ir = read_ir(ir_path)?;
|
|
98
98
|
|
|
99
99
|
let ex = exemplar::Exemplar::load();
|
|
100
100
|
write_domain(&ex, &ir, source_label, mod_name, out_dir)?;
|
|
101
101
|
Ok(())
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/// Reads one chapter's IR and prepares it for generation: marks the fields an `append` binds to an
|
|
105
|
+
/// optional argument. What it generates from and what it writes to `ir.json` are this same IR.
|
|
106
|
+
fn read_ir(path: &str) -> Result<Json, String> {
|
|
107
|
+
let text = std::fs::read_to_string(path).map_err(|e| format!("reading {path}: {e}"))?;
|
|
108
|
+
let mut ir = Json::parse(&text).map_err(|e| format!("parsing {path}: {e}"))?;
|
|
109
|
+
optional_pass::run(&mut ir);
|
|
110
|
+
Ok(ir)
|
|
111
|
+
}
|
|
112
|
+
|
|
104
113
|
/// Writes one chapter's files into `out_dir` and returns the `GeneratedDomain`, so `run_full`
|
|
105
114
|
/// can union the registry, query and read-model definitions across chapters.
|
|
106
115
|
fn write_domain(
|
|
@@ -147,6 +156,10 @@ fn write_domain(
|
|
|
147
156
|
std::fs::write(&manifest_path, &generated.manifest_json).map_err(|e| format!("writing {manifest_path}: {e}"))?;
|
|
148
157
|
println!("wrote {manifest_path}");
|
|
149
158
|
|
|
159
|
+
let (ir_json_path, metadata_path) = sidecars::write(std::path::Path::new(out_dir), ir, source_label)?;
|
|
160
|
+
println!("wrote {}", ir_json_path.display());
|
|
161
|
+
println!("wrote {}", metadata_path.display());
|
|
162
|
+
|
|
150
163
|
Ok(generated)
|
|
151
164
|
}
|
|
152
165
|
|
|
@@ -181,8 +194,7 @@ fn run_full(args: &[String]) -> Result<(), String> {
|
|
|
181
194
|
|
|
182
195
|
let ex = exemplar::Exemplar::load();
|
|
183
196
|
|
|
184
|
-
let
|
|
185
|
-
let target_ir = Json::parse(&target_text).map_err(|e| format!("parsing {target_ir_path}: {e}"))?;
|
|
197
|
+
let target_ir = read_ir(target_ir_path)?;
|
|
186
198
|
let target_domain_name = target_ir.get("name").and_then(Json::as_str).unwrap_or("").to_string();
|
|
187
199
|
|
|
188
200
|
let target_out_dir = format!("{out_root}/{target_mod_name}");
|
|
@@ -209,8 +221,7 @@ fn run_full(args: &[String]) -> Result<(), String> {
|
|
|
209
221
|
let chapter_ir_path = &args[i + 2];
|
|
210
222
|
i += 3;
|
|
211
223
|
|
|
212
|
-
let
|
|
213
|
-
let chapter_ir = Json::parse(&text).map_err(|e| format!("parsing {chapter_ir_path}: {e}"))?;
|
|
224
|
+
let chapter_ir = read_ir(chapter_ir_path)?;
|
|
214
225
|
let chapter_domain_name = chapter_ir.get("name").and_then(Json::as_str).unwrap_or("").to_string();
|
|
215
226
|
|
|
216
227
|
let chapter_out_dir = format!("{out_root}/{chapter_mod_name}");
|
data/rust/codegen/src/naming.rs
CHANGED
|
@@ -285,8 +285,10 @@ pub fn literal_rhs(literal: &crate::json::Json) -> String {
|
|
|
285
285
|
match literal {
|
|
286
286
|
crate::json::Json::String(s) => format!("{}.to_string()", ruby_inspect_string(s)),
|
|
287
287
|
// Separate arms so a whole-number Float renders as `0.0`, not an integer-typed `0`.
|
|
288
|
-
crate::json::Json::
|
|
289
|
-
|
|
288
|
+
crate::json::Json::Number(_) => match literal.number() {
|
|
289
|
+
Some(crate::json::Number::Int(n)) => n.to_string(),
|
|
290
|
+
_ => literal.to_s(),
|
|
291
|
+
},
|
|
290
292
|
crate::json::Json::Bool(b) => b.to_string(),
|
|
291
293
|
other => panic!("unsupported literal mutation source {other:?} — not one of String/Integer/Float/Boolean"),
|
|
292
294
|
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
//! Marks value-object and entity fields `optional` when an `append` mutation binds them to an
|
|
2
|
+
//! optional command argument, so the generated struct field is an `Option`.
|
|
3
|
+
//!
|
|
4
|
+
//! Resolves the appended element as `Hecks::RustBuild::AppendOptionals` does in Ruby: a local
|
|
5
|
+
//! entity wins over any value object of the same name, a local value object over a domain-wide one,
|
|
6
|
+
//! and an `append` into another aggregate's value object marks that value object.
|
|
7
|
+
|
|
8
|
+
use std::collections::HashMap;
|
|
9
|
+
|
|
10
|
+
use crate::json::Json;
|
|
11
|
+
|
|
12
|
+
/// Where an appended element lives: its aggregate's index, then its own index in that aggregate.
|
|
13
|
+
#[derive(Clone, Copy)]
|
|
14
|
+
enum Element {
|
|
15
|
+
ValueObject(usize, usize),
|
|
16
|
+
Entity(usize, usize),
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
struct Mark {
|
|
20
|
+
element: Element,
|
|
21
|
+
field: String,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub fn run(ir: &mut Json) {
|
|
25
|
+
let marks = collect_marks(ir);
|
|
26
|
+
apply_marks(ir, &marks);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fn list<'a>(node: &'a Json, key: &str) -> &'a [Json] {
|
|
30
|
+
node.get(key).map(Json::each).unwrap_or(&[])
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn name_of(node: &Json) -> Option<&str> {
|
|
34
|
+
node.get("name").and_then(Json::as_str)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Every value object in the domain by name; the last one declared wins, as a Ruby `to_h` does.
|
|
38
|
+
fn domain_wide_value_objects(aggregates: &[Json]) -> HashMap<&str, (usize, usize)> {
|
|
39
|
+
let mut by_name = HashMap::new();
|
|
40
|
+
for (a, aggregate) in aggregates.iter().enumerate() {
|
|
41
|
+
for (v, value_object) in list(aggregate, "value_objects").iter().enumerate() {
|
|
42
|
+
if let Some(name) = name_of(value_object) {
|
|
43
|
+
by_name.insert(name, (a, v));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
by_name
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fn find_element(aggregates: &[Json], a: usize, domain_wide: &HashMap<&str, (usize, usize)>, type_name: &str) -> Option<Element> {
|
|
51
|
+
let aggregate = &aggregates[a];
|
|
52
|
+
if let Some(e) = list(aggregate, "entities").iter().position(|entity| name_of(entity) == Some(type_name)) {
|
|
53
|
+
return Some(Element::Entity(a, e));
|
|
54
|
+
}
|
|
55
|
+
if let Some(v) = list(aggregate, "value_objects").iter().rposition(|value_object| name_of(value_object) == Some(type_name)) {
|
|
56
|
+
return Some(Element::ValueObject(a, v));
|
|
57
|
+
}
|
|
58
|
+
domain_wide.get(type_name).map(|&(owner, v)| Element::ValueObject(owner, v))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Read first, write after: the commands are read while the elements they mark are mutated.
|
|
62
|
+
fn collect_marks(ir: &Json) -> Vec<Mark> {
|
|
63
|
+
let aggregates = list(ir, "aggregates");
|
|
64
|
+
let domain_wide = domain_wide_value_objects(aggregates);
|
|
65
|
+
let mut marks = Vec::new();
|
|
66
|
+
|
|
67
|
+
for (a, aggregate) in aggregates.iter().enumerate() {
|
|
68
|
+
for attribute in list(aggregate, "attributes") {
|
|
69
|
+
let (Some(attr_name), Some(type_name)) = (name_of(attribute), attribute.get("type").and_then(Json::as_str)) else { continue };
|
|
70
|
+
let Some(element) = find_element(aggregates, a, &domain_wide, type_name) else { continue };
|
|
71
|
+
|
|
72
|
+
for field in collect_fields_to_mark(aggregate, attr_name) {
|
|
73
|
+
marks.push(Mark { element, field });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
marks
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn collect_fields_to_mark(aggregate: &Json, target_attr_name: &str) -> Vec<String> {
|
|
81
|
+
let mut out = Vec::new();
|
|
82
|
+
|
|
83
|
+
for command in list(aggregate, "commands") {
|
|
84
|
+
let command_attrs = list(command, "attributes");
|
|
85
|
+
|
|
86
|
+
for mutation in list(command, "mutations") {
|
|
87
|
+
let op = mutation.get("op").and_then(Json::as_str).unwrap_or("");
|
|
88
|
+
let target = mutation.get("target").and_then(Json::as_str).unwrap_or("");
|
|
89
|
+
if op != "append" || target != target_attr_name {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let Some(fields) = mutation.get("fields").and_then(Json::as_object) else { continue };
|
|
94
|
+
for (field_name, source) in fields {
|
|
95
|
+
// Only a Symbol (an argument name) renders with a leading `:` in `Literal.render`.
|
|
96
|
+
let Some(source_text) = source.as_str() else { continue };
|
|
97
|
+
let Some(arg_name) = source_text.strip_prefix(':') else { continue };
|
|
98
|
+
|
|
99
|
+
let source_is_optional = command_attrs
|
|
100
|
+
.iter()
|
|
101
|
+
.any(|a| name_of(a) == Some(arg_name) && a.get("optional").map(Json::as_bool).unwrap_or(false));
|
|
102
|
+
if source_is_optional {
|
|
103
|
+
out.push(field_name.clone());
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
out
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn apply_marks(ir: &mut Json, marks: &[Mark]) {
|
|
113
|
+
let Some(aggregates) = ir.get_mut("aggregates").and_then(Json::as_array_mut) else { return };
|
|
114
|
+
|
|
115
|
+
for mark in marks {
|
|
116
|
+
let (a, key, index) = match mark.element {
|
|
117
|
+
Element::ValueObject(a, v) => (a, "value_objects", v),
|
|
118
|
+
Element::Entity(a, e) => (a, "entities", e),
|
|
119
|
+
};
|
|
120
|
+
let element = aggregates.get_mut(a).and_then(|aggregate| aggregate.get_mut(key)).and_then(Json::as_array_mut).and_then(|elements| elements.get_mut(index));
|
|
121
|
+
let Some(attrs) = element.and_then(|element| element.get_mut("attributes")).and_then(Json::as_array_mut) else { continue };
|
|
122
|
+
|
|
123
|
+
if let Some(field_attr) = attrs.iter_mut().find(|a| name_of(a) == Some(mark.field.as_str())) {
|
|
124
|
+
field_attr.set_bool("optional", true);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[cfg(test)]
|
|
130
|
+
mod tests {
|
|
131
|
+
use super::*;
|
|
132
|
+
|
|
133
|
+
fn aggregate_with(entities: &str, value_objects: &str, attribute_type: &str) -> String {
|
|
134
|
+
format!(
|
|
135
|
+
r#"{{
|
|
136
|
+
"attributes": [
|
|
137
|
+
{{"name": "ledger", "type": "{attribute_type}", "list": true, "default": null, "optional": false, "pattern": null, "admits": null}}
|
|
138
|
+
],
|
|
139
|
+
"value_objects": [{value_objects}],
|
|
140
|
+
"entities": [{entities}],
|
|
141
|
+
"commands": [
|
|
142
|
+
{{
|
|
143
|
+
"name": "Credit",
|
|
144
|
+
"attributes": [
|
|
145
|
+
{{"name": "amount", "type": "PositiveMoney", "list": false, "default": null, "optional": true, "pattern": null, "admits": null}}
|
|
146
|
+
],
|
|
147
|
+
"mutations": [
|
|
148
|
+
{{"target": "ledger", "op": "append", "fields": {{"amount": ":amount", "direction": "{{value: \"credit\"}}"}}}}
|
|
149
|
+
]
|
|
150
|
+
}}
|
|
151
|
+
]
|
|
152
|
+
}}"#
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn element(amount_optional: bool) -> String {
|
|
157
|
+
format!(
|
|
158
|
+
r#"{{"name": "LedgerEntry", "attributes": [
|
|
159
|
+
{{"name": "amount", "type": "Money", "list": false, "default": null, "optional": {amount_optional}, "pattern": null, "admits": null}},
|
|
160
|
+
{{"name": "direction", "type": "Direction", "list": false, "default": null, "optional": false, "pattern": null, "admits": null}}
|
|
161
|
+
]}}"#
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fn domain(aggregates: &[String]) -> Json {
|
|
166
|
+
Json::parse(&format!(r#"{{"aggregates": [{}]}}"#, aggregates.join(","))).expect("fixture parses")
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fn amount_optional(ir: &Json, aggregate: usize, key: &str) -> bool {
|
|
170
|
+
let element = &list(&list(ir, "aggregates")[aggregate], key)[0];
|
|
171
|
+
let amount = list(element, "attributes").iter().find(|a| name_of(a) == Some("amount")).unwrap();
|
|
172
|
+
amount.get("optional").unwrap().as_bool()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// The input and expectation `spec/rust_build_generation_helpers_spec.rb` holds the Ruby pass to,
|
|
176
|
+
// recorded from it. Read at test time, not compiled in: an installed gem has no `spec/`.
|
|
177
|
+
#[test]
|
|
178
|
+
fn marks_what_the_ruby_pass_marks_on_the_shared_fixture() {
|
|
179
|
+
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../spec/fixtures/append_optionals");
|
|
180
|
+
let read = |name: &str| Json::parse(&std::fs::read_to_string(dir.join(name)).expect("reads the fixture")).expect("parses");
|
|
181
|
+
|
|
182
|
+
let mut ir = read("input.json");
|
|
183
|
+
run(&mut ir);
|
|
184
|
+
|
|
185
|
+
assert_eq!(ir, read("expected.json"));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Banking's `Account.Credit` shape: entity list `ledger`, one `append` binding `amount` from
|
|
189
|
+
// an optional argument and `direction` from a literal Hash.
|
|
190
|
+
#[test]
|
|
191
|
+
fn marks_the_argument_sourced_field_optional() {
|
|
192
|
+
let mut ir = domain(&[aggregate_with(&element(false), "", "LedgerEntry")]);
|
|
193
|
+
run(&mut ir);
|
|
194
|
+
|
|
195
|
+
assert!(amount_optional(&ir, 0, "entities"));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#[test]
|
|
199
|
+
fn marks_a_value_object_another_aggregate_declares() {
|
|
200
|
+
let owner = r#"{"attributes": [], "value_objects": [LEDGER], "entities": [], "commands": []}"#.replace("LEDGER", &element(false));
|
|
201
|
+
let appender = aggregate_with("", "", "LedgerEntry");
|
|
202
|
+
let mut ir = domain(&[appender, owner]);
|
|
203
|
+
run(&mut ir);
|
|
204
|
+
|
|
205
|
+
assert!(amount_optional(&ir, 1, "value_objects"), "the value object lives in the second aggregate");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#[test]
|
|
209
|
+
fn prefers_a_local_entity_over_a_value_object_of_the_same_name() {
|
|
210
|
+
let local = aggregate_with(&element(false), "", "LedgerEntry");
|
|
211
|
+
let owner = r#"{"attributes": [], "value_objects": [LEDGER], "entities": [], "commands": []}"#.replace("LEDGER", &element(false));
|
|
212
|
+
let mut ir = domain(&[local, owner]);
|
|
213
|
+
run(&mut ir);
|
|
214
|
+
|
|
215
|
+
assert!(amount_optional(&ir, 0, "entities"), "the local entity is the one marked");
|
|
216
|
+
assert!(!amount_optional(&ir, 1, "value_objects"), "the domain-wide value object is left alone");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#[test]
|
|
220
|
+
fn leaves_a_field_alone_when_its_argument_is_required() {
|
|
221
|
+
let mut ir = domain(&[aggregate_with(&element(false), "", "LedgerEntry").replace("\"optional\": true", "\"optional\": false")]);
|
|
222
|
+
run(&mut ir);
|
|
223
|
+
|
|
224
|
+
assert!(!amount_optional(&ir, 0, "entities"));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
//! Writes each chapter's `ir.json` and `metadata.rs` sidecars.
|
|
1
|
+
//! Writes each chapter's `ir.json` and `metadata.rs` sidecars, from the IR as generated (marked).
|
|
2
|
+
|
|
3
|
+
use crate::json::{self, Json};
|
|
2
4
|
|
|
3
5
|
/// Renders `text` as a Rust string literal. Not Ruby's `String#inspect`, whose `\#{` and bare
|
|
4
6
|
/// `\uXXXX` escapes are not valid Rust.
|
|
@@ -22,22 +24,24 @@ pub fn rust_string_literal(text: &str) -> String {
|
|
|
22
24
|
out
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
/// `ir.json` is what `rust/host` reads at runtime, and `metadata.rs` embeds the same text.
|
|
28
|
+
pub fn write(mod_dir: &std::path::Path, ir: &Json, source_label: &str) -> Result<(std::path::PathBuf, std::path::PathBuf), String> {
|
|
26
29
|
std::fs::create_dir_all(mod_dir).map_err(|e| format!("creating {}: {e}", mod_dir.display()))?;
|
|
27
30
|
|
|
31
|
+
let ir_text = json::write(ir);
|
|
28
32
|
let ir_json_path = mod_dir.join("ir.json");
|
|
29
|
-
std::fs::write(&ir_json_path, ir_text).map_err(|e| format!("writing {}: {e}", ir_json_path.display()))?;
|
|
33
|
+
std::fs::write(&ir_json_path, &ir_text).map_err(|e| format!("writing {}: {e}", ir_json_path.display()))?;
|
|
30
34
|
|
|
31
35
|
let metadata_path = mod_dir.join("metadata.rs");
|
|
32
36
|
let mut contents = String::new();
|
|
33
|
-
//
|
|
37
|
+
// The header every generated `metadata.rs` carries; `regenerate_corpus --check` diffs it.
|
|
34
38
|
contents.push_str("// GENERATED by hecks project_rust — ");
|
|
35
39
|
contents.push_str(source_label);
|
|
36
40
|
contents.push_str("'s own canonical IR,\n");
|
|
37
41
|
contents.push_str("// embedded for runtime self-description. Not read by any dispatch\n");
|
|
38
42
|
contents.push_str("// function in this module — introspection only.\n");
|
|
39
43
|
contents.push_str("pub const IR_JSON: &str = ");
|
|
40
|
-
contents.push_str(&rust_string_literal(ir_text));
|
|
44
|
+
contents.push_str(&rust_string_literal(&ir_text));
|
|
41
45
|
contents.push_str(";\n");
|
|
42
46
|
std::fs::write(&metadata_path, contents).map_err(|e| format!("writing {}: {e}", metadata_path.display()))?;
|
|
43
47
|
|
data/rust/host/HECKS_RELEASE
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.4.0
|
data/rust/host/src/api.rs
CHANGED
|
@@ -823,7 +823,7 @@ mod tests {
|
|
|
823
823
|
}
|
|
824
824
|
|
|
825
825
|
// app.rb: `get "/api/me" { json(session[:member] || {}) }`, where
|
|
826
|
-
// session[:member] is
|
|
826
|
+
// session[:member] is the access-control module's own
|
|
827
827
|
// `{ "email" =>, "name" =>, "identity_id" =>, "role" => }`.
|
|
828
828
|
#[test]
|
|
829
829
|
fn me_is_the_ruby_engines_own_member_hash() {
|
data/rust/host/src/dispatch.rs
CHANGED
|
@@ -611,6 +611,11 @@ pub async fn query(
|
|
|
611
611
|
question: &str,
|
|
612
612
|
args: serde_json::Value,
|
|
613
613
|
) -> anyhow::Result<serde_json::Value> {
|
|
614
|
+
// A fact the query `needs` is answered before the kernel checks its arguments (ADR 0081).
|
|
615
|
+
let mut args = args;
|
|
616
|
+
if let Some(domain_ir) = crate::ir::ir() {
|
|
617
|
+
crate::needs::fill_query_needs(domain_ir, question, &mut args, &crate::needs::ProcessClock);
|
|
618
|
+
}
|
|
614
619
|
let state = read(client, wasm_path).await?;
|
|
615
620
|
let seed = state.get("instances").cloned().unwrap_or_else(|| serde_json::json!({}));
|
|
616
621
|
let input = serde_json::json!({ "seed": seed, "steps": [{ "query": question, "args": args }] }).to_string();
|
data/rust/host/src/needs.rs
CHANGED
|
@@ -186,9 +186,11 @@ fn facts_mut(args: &mut Value) -> Option<&mut serde_json::Map<String, Value>> {
|
|
|
186
186
|
|
|
187
187
|
fn answer(fact: &str, attribute_type: Option<&str>, clock: &dyn Clock) -> Option<Value> {
|
|
188
188
|
match fact {
|
|
189
|
-
|
|
189
|
+
// `now` is epoch seconds; `today` the day those fall in, whole days since the epoch (UTC).
|
|
190
|
+
"now" | "today" => {
|
|
190
191
|
let secs = clock.now_secs();
|
|
191
|
-
|
|
192
|
+
let answer = if fact == "today" { secs.div_euclid(86_400) } else { secs };
|
|
193
|
+
Some(if attribute_type == Some("Integer") { json!(answer) } else { json!({ "value": answer }) })
|
|
192
194
|
}
|
|
193
195
|
_ => None,
|
|
194
196
|
}
|
|
@@ -212,6 +214,49 @@ pub fn fill_needs(domain_ir: &Value, verb: &str, args: &mut Value, clock: &dyn C
|
|
|
212
214
|
}
|
|
213
215
|
}
|
|
214
216
|
|
|
217
|
+
/// The facts the query `question` (`Domain::Aggregate.Query`) needs, each with the type of the
|
|
218
|
+
/// argument of the same name. An entity's query is not looked up here.
|
|
219
|
+
fn declared_query_needs<'a>(domain_ir: &'a Value, question: &str) -> Vec<(&'a str, Option<&'a str>)> {
|
|
220
|
+
let Some((qualified_aggregate, query_name)) = question.rsplit_once('.') else { return Vec::new() };
|
|
221
|
+
let aggregate = qualified_aggregate.rsplit("::").next().unwrap_or(qualified_aggregate);
|
|
222
|
+
let Some(query) = list(domain_ir, "aggregates")
|
|
223
|
+
.iter()
|
|
224
|
+
.find(|a| named(a, aggregate))
|
|
225
|
+
.and_then(|a| list(a, "queries").iter().find(|q| named(q, query_name)))
|
|
226
|
+
else {
|
|
227
|
+
return Vec::new();
|
|
228
|
+
};
|
|
229
|
+
let attributes = list(query, "attributes");
|
|
230
|
+
list(query, "needs")
|
|
231
|
+
.iter()
|
|
232
|
+
.filter_map(|n| n.get("fact").and_then(Value::as_str))
|
|
233
|
+
.map(|fact| {
|
|
234
|
+
let attribute_type =
|
|
235
|
+
attributes.iter().find(|a| named(a, fact)).and_then(|a| a.get("type")).and_then(Value::as_str);
|
|
236
|
+
(fact, attribute_type)
|
|
237
|
+
})
|
|
238
|
+
.collect()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// Fills each fact the query `question` needs that `args` leaves out, before the kernel checks the
|
|
242
|
+
/// arguments. A key the caller supplied (even a null) is kept; a query that needs nothing, or whose
|
|
243
|
+
/// arguments are not an object, is untouched.
|
|
244
|
+
pub fn fill_query_needs(domain_ir: &Value, question: &str, args: &mut Value, clock: &dyn Clock) {
|
|
245
|
+
let needs = declared_query_needs(domain_ir, question);
|
|
246
|
+
if needs.is_empty() {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
let Some(facts) = args.as_object_mut() else { return };
|
|
250
|
+
for (fact, attribute_type) in needs {
|
|
251
|
+
if facts.contains_key(fact) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if let Some(value) = answer(fact, attribute_type, clock) {
|
|
255
|
+
facts.insert(fact.to_string(), value);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
215
260
|
/// Gives each argument `verb` declares a default for, and `args` leaves out, that default, after
|
|
216
261
|
/// `fill_needs` and before any gate reads the arguments. A key the caller supplied (even a null)
|
|
217
262
|
/// is kept, as is a fact `fill_needs` already answered.
|
|
@@ -408,6 +453,58 @@ mod tests {
|
|
|
408
453
|
assert_eq!(args, json!({"number": 1}));
|
|
409
454
|
}
|
|
410
455
|
|
|
456
|
+
// An aggregate with a query that needs `now` and one that needs `today`.
|
|
457
|
+
fn query_ir() -> Value {
|
|
458
|
+
json!({"name": "Lease", "aggregates": [{"name": "Slot", "commands": [], "queries": [
|
|
459
|
+
{"name": "Expired", "attributes": [{"name": "now", "type": "LeaseInstant"}],
|
|
460
|
+
"needs": [{"fact": "now"}]},
|
|
461
|
+
{"name": "IssuedToday", "attributes": [{"name": "today", "type": "Integer"}],
|
|
462
|
+
"needs": [{"fact": "today"}]},
|
|
463
|
+
{"name": "Plain", "attributes": [{"name": "now", "type": "LeaseInstant"}]}
|
|
464
|
+
]}]})
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
#[test]
|
|
468
|
+
fn a_needed_query_fact_the_caller_left_out_is_filled_from_the_clock() {
|
|
469
|
+
let mut args = json!({});
|
|
470
|
+
fill_query_needs(&query_ir(), "Lease::Slot.Expired", &mut args, &FixedClock(1_700_000_000));
|
|
471
|
+
assert_eq!(args["now"], json!({"value": 1_700_000_000}));
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
#[test]
|
|
475
|
+
fn a_query_value_the_caller_supplied_is_kept_even_when_null() {
|
|
476
|
+
let mut args = json!({"now": null});
|
|
477
|
+
fill_query_needs(&query_ir(), "Lease::Slot.Expired", &mut args, &FixedClock(5));
|
|
478
|
+
assert!(args["now"].is_null());
|
|
479
|
+
let mut named = json!({"now": {"value": 9}});
|
|
480
|
+
fill_query_needs(&query_ir(), "Lease::Slot.Expired", &mut named, &FixedClock(5));
|
|
481
|
+
assert_eq!(named["now"], json!({"value": 9}));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
#[test]
|
|
485
|
+
fn a_query_that_needs_nothing_or_names_nothing_is_untouched() {
|
|
486
|
+
let mut args = json!({});
|
|
487
|
+
fill_query_needs(&query_ir(), "Lease::Slot.Plain", &mut args, &FixedClock(5));
|
|
488
|
+
fill_query_needs(&query_ir(), "Lease::Slot.Missing", &mut args, &FixedClock(5));
|
|
489
|
+
fill_query_needs(&query_ir(), "Lease::Shelf.Expired", &mut args, &FixedClock(5));
|
|
490
|
+
assert_eq!(args, json!({}));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
#[test]
|
|
494
|
+
fn today_is_whole_days_since_the_epoch_shaped_for_its_argument() {
|
|
495
|
+
let mut args = json!({});
|
|
496
|
+
fill_query_needs(&query_ir(), "Lease::Slot.IssuedToday", &mut args, &FixedClock((19_000 * 86_400) + 86_399));
|
|
497
|
+
assert_eq!(args["today"], json!(19_000));
|
|
498
|
+
|
|
499
|
+
let mut command = json!({"with": {}});
|
|
500
|
+
let ir = json!({"name": "D", "aggregates": [{"name": "A", "commands": [
|
|
501
|
+
{"name": "Open", "attributes": [{"name": "day", "type": "Day"}], "needs": [{"fact": "day"}]},
|
|
502
|
+
{"name": "Take", "attributes": [{"name": "today", "type": "Day"}], "needs": [{"fact": "today"}]}
|
|
503
|
+
]}]});
|
|
504
|
+
fill_needs(&ir, "D::A.Take", &mut command, &FixedClock(19_001 * 86_400));
|
|
505
|
+
assert_eq!(command["with"]["today"], json!({"value": 19_001}));
|
|
506
|
+
}
|
|
507
|
+
|
|
411
508
|
#[test]
|
|
412
509
|
fn the_kernel_table_lists_entity_commands_under_their_full_path() {
|
|
413
510
|
assert_eq!(
|
data/rust/host/src/resend.rs
CHANGED
|
@@ -256,8 +256,8 @@ mod tests {
|
|
|
256
256
|
|
|
257
257
|
#[test]
|
|
258
258
|
fn a_reply_to_address_rides_in_the_payload_and_is_absent_without_one() {
|
|
259
|
-
let with = payload("f", Some("
|
|
260
|
-
assert_eq!(with["reply_to"], "
|
|
259
|
+
let with = payload("f", Some("replies@example.com"), &email("a@b.com", "x"));
|
|
260
|
+
assert_eq!(with["reply_to"], "replies@example.com");
|
|
261
261
|
assert!(payload("f", None, &email("a@b.com", "x")).get("reply_to").is_none());
|
|
262
262
|
}
|
|
263
263
|
|
|
@@ -273,11 +273,11 @@ mod tests {
|
|
|
273
273
|
#[tokio::test]
|
|
274
274
|
async fn a_live_send_carries_the_reply_to_address_to_resend() {
|
|
275
275
|
let fake = FakeResend::start(vec![("200 OK", "", r#"{"id":"em_9"}"#)]);
|
|
276
|
-
let mailer = Mailer::live("re_x", "News <n@mail.example.com>", &fake.base).replying_to("
|
|
276
|
+
let mailer = Mailer::live("re_x", "News <n@mail.example.com>", &fake.base).replying_to("replies@example.com");
|
|
277
277
|
|
|
278
278
|
mailer.deliver(&email("a@b.com", "x")).await;
|
|
279
279
|
|
|
280
|
-
assert!(fake.requests()[0].contains(r#""reply_to":"
|
|
280
|
+
assert!(fake.requests()[0].contains(r#""reply_to":"replies@example.com""#), "{}", fake.requests()[0]);
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
#[test]
|
data/rust/host/src/ui_schema.rs
CHANGED
|
@@ -860,7 +860,7 @@ fn humanize(key: &str) -> String {
|
|
|
860
860
|
mod tests {
|
|
861
861
|
use super::*;
|
|
862
862
|
|
|
863
|
-
//
|
|
863
|
+
// Shaped like a real member-gated app, the kind the Ruby engine
|
|
864
864
|
// actually serves. Each test below pins one rule of ui_schema.rb.
|
|
865
865
|
|
|
866
866
|
fn domain() -> Value {
|
data/rust/parser/src/emit.rs
CHANGED
|
@@ -463,6 +463,18 @@ fn query_json(q: &ir::Query) -> JsonValue {
|
|
|
463
463
|
if let Some(returns) = &q.returns {
|
|
464
464
|
pairs.push(("returns".to_string(), JsonValue::str(returns.clone())));
|
|
465
465
|
}
|
|
466
|
+
// `needs` follows `returns`, only when declared, so a query that needs nothing keeps its shape.
|
|
467
|
+
if !q.needs.is_empty() {
|
|
468
|
+
pairs.push((
|
|
469
|
+
"needs".to_string(),
|
|
470
|
+
JsonValue::Array(
|
|
471
|
+
q.needs
|
|
472
|
+
.iter()
|
|
473
|
+
.map(|fact| JsonValue::Object(vec![("fact".to_string(), JsonValue::str(fact.clone()))]))
|
|
474
|
+
.collect(),
|
|
475
|
+
),
|
|
476
|
+
));
|
|
477
|
+
}
|
|
466
478
|
JsonValue::Object(pairs)
|
|
467
479
|
}
|
|
468
480
|
|
data/rust/parser/src/ir.rs
CHANGED
|
@@ -133,6 +133,8 @@ pub struct Query {
|
|
|
133
133
|
pub options: QueryOptions,
|
|
134
134
|
/// `returns Name` or `returns list_of(Name)`, spelled as `Query#returns` holds it.
|
|
135
135
|
pub returns: Option<String>,
|
|
136
|
+
/// Outside facts the runtime supplies before the filter reads the arguments (`needs :now`).
|
|
137
|
+
pub needs: Vec<String>,
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
/// `AuthorizationSpec#to_h`: both fields are bare `.to_s`, never Literal-rendered.
|
data/rust/parser/src/keywords.rs
CHANGED
|
@@ -161,6 +161,7 @@ pub static KEYWORDS: &[KeywordRow] = &[
|
|
|
161
161
|
KeywordRow { word: "authorize", context: "Query", body: "none", inner: "", opens: "", fills: "options", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
162
162
|
KeywordRow { word: "inspect_query", context: "Query", body: "none", inner: "", opens: "", fills: "options", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
163
163
|
KeywordRow { word: "returns", context: "Query", body: "none", inner: "", opens: "", fills: "returns", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
164
|
+
KeywordRow { word: "needs", context: "Query", body: "none", inner: "", opens: "", fills: "needs", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
164
165
|
KeywordRow { word: "attribute", context: "ValueObject", body: "none", inner: "", opens: "", fills: "attributes", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
165
166
|
KeywordRow { word: "one_of", context: "ValueObject", body: "rows", inner: "OneOf", opens: "", fills: "rows", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
166
167
|
KeywordRow { word: "invariant", context: "ValueObject", body: "source", inner: "", opens: "", fills: "invariants", status: "admitted", was: "", resolves_via: "sibling_scan", disambiguator: "" },
|
|
@@ -183,8 +184,6 @@ pub static KEYWORDS: &[KeywordRow] = &[
|
|
|
183
184
|
KeywordRow { word: "hecksagon", context: "File", body: "keywords", inner: "Hecksagon", opens: "Hecksagon", fills: "", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
184
185
|
KeywordRow { word: "subscribe", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "subscriptions", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
185
186
|
KeywordRow { word: "attaches", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "attachments", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
186
|
-
KeywordRow { word: "uses_framework", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "attachments", status: "deprecated", was: "", resolves_via: "", disambiguator: "" },
|
|
187
|
-
KeywordRow { word: "uses_embryonaut_bluebook", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "attachments", status: "deprecated", was: "", resolves_via: "", disambiguator: "" },
|
|
188
187
|
KeywordRow { word: "port", context: "Hecksagon", body: "keywords", inner: "DomainPort", opens: "DomainPort", fills: "", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
189
188
|
KeywordRow { word: "translates", context: "Hecksagon", body: "keywords", inner: "Policy", opens: "Policy", fills: "", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
190
189
|
KeywordRow { word: "bounded", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "", status: "admitted", was: "", resolves_via: "", disambiguator: "" },
|
|
@@ -437,6 +436,7 @@ pub static ARGUMENTS: &[ArgumentRow] = &[
|
|
|
437
436
|
ArgumentRow { keyword: "authorize", context: "Query", at: "", named: "tenant", kind: "symbol", required: "false", fills: "tenant", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
438
437
|
ArgumentRow { keyword: "inspect_query", context: "Query", at: "1", named: "", kind: "symbol", required: "false", fills: "mode", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
439
438
|
ArgumentRow { keyword: "returns", context: "Query", at: "1", named: "", kind: "constant", required: "true", fills: "type", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
439
|
+
ArgumentRow { keyword: "needs", context: "Query", at: "1", named: "", kind: "symbol", required: "true", fills: "fact", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
440
440
|
ArgumentRow { keyword: "attribute", context: "ValueObject", at: "1", named: "", kind: "symbol", required: "true", fills: "name", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
441
441
|
ArgumentRow { keyword: "attribute", context: "ValueObject", at: "2", named: "", kind: "constant", required: "true", fills: "type", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
442
442
|
ArgumentRow { keyword: "attribute", context: "ValueObject", at: "", named: "default", kind: "literal", required: "false", fills: "default", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
@@ -468,8 +468,6 @@ pub static ARGUMENTS: &[ArgumentRow] = &[
|
|
|
468
468
|
ArgumentRow { keyword: "subscribe", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "subscriptions", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
469
469
|
ArgumentRow { keyword: "attaches", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "attachments", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
470
470
|
ArgumentRow { keyword: "attaches", context: "Hecksagon", at: "", named: "from", kind: "symbol", required: "false", fills: "source", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
471
|
-
ArgumentRow { keyword: "uses_framework", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "attachments", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
472
|
-
ArgumentRow { keyword: "uses_embryonaut_bluebook", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "attachments", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
473
471
|
ArgumentRow { keyword: "port", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "name", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
474
472
|
ArgumentRow { keyword: "translates", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "name", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
|
475
473
|
ArgumentRow { keyword: "reference_to", context: "PortOperation", at: "1", named: "", kind: "constant", required: "true", fills: "type", selects: "", pair_key_fills: "", pair_value_fills: "", pairs_shape: "", status: "admitted", variadic: "", minimum: "" },
|
data/rust/parser/src/lex.rs
CHANGED
|
@@ -567,7 +567,7 @@ mod tests {
|
|
|
567
567
|
#[test]
|
|
568
568
|
fn does_not_strip_a_bare_lowercase_call() {
|
|
569
569
|
assert_eq!(
|
|
570
|
-
strip_aggregate_receiver("
|
|
570
|
+
strip_aggregate_receiver("attaches \"Governance\""),
|
|
571
571
|
None
|
|
572
572
|
);
|
|
573
573
|
assert_eq!(strip_aggregate_receiver("subscribe \"Deposited\""), None);
|
data/rust/parser/src/main.rs
CHANGED