use std::collections::BTreeMap;
use crate::{
ConstraintDetailCounts, ConstraintDetailInput, EngineInputV2, ExpressionDomainCandidateV0,
ExpressionDomainCandidatesV0, ExpressionDomainCanonicalCandidateBundleV0,
ExpressionDomainCanonicalProducerSignalV0, ExpressionDomainEvaluatorCandidatePayloadV0,
ExpressionDomainEvaluatorCandidateV0, ExpressionDomainEvaluatorCandidatesV0,
ExpressionDomainFragmentV0, ExpressionDomainFragmentsV0, ExpressionDomainPlanSummaryV0,
collect_constraint_detail_counts, map_reduced_expression_value_domain_derivation,
map_reduced_expression_value_domain_kind,
};
struct ExpressionDomainInputRows {
plan_summary: ExpressionDomainPlanSummaryV0,
fragments: Vec<ExpressionDomainFragmentV0>,
candidates: Vec<ExpressionDomainCandidateV0>,
evaluator_candidates: Vec<ExpressionDomainEvaluatorCandidateV0>,
}
fn collect_expression_domain_input_rows(input: &EngineInputV2) -> ExpressionDomainInputRows {
let mut planned_expression_ids = Vec::new();
let mut value_domain_kinds = BTreeMap::new();
let mut value_constraint_kinds = BTreeMap::new();
let mut constraint_detail_counts = ConstraintDetailCounts::default();
let mut finite_value_count = 0usize;
let mut fragments = Vec::new();
let mut candidates = Vec::new();
let mut evaluator_candidates = Vec::new();
for entry in &input.type_facts {
planned_expression_ids.push(entry.expression_id.clone());
*value_domain_kinds
.entry(entry.facts.kind.clone())
.or_insert(0) += 1;
if let Some(values) = &entry.facts.values {
finite_value_count += values.len();
}
if let Some(constraint_kind) = &entry.facts.constraint_kind {
*value_constraint_kinds
.entry(constraint_kind.clone())
.or_insert(0) += 1;
}
collect_constraint_detail_counts(
&mut constraint_detail_counts,
ConstraintDetailInput {
prefix: entry.facts.prefix.as_ref(),
suffix: entry.facts.suffix.as_ref(),
min_len: entry.facts.min_len,
max_len: entry.facts.max_len,
char_must: entry.facts.char_must.as_ref(),
char_may: entry.facts.char_may.as_ref(),
may_include_other_chars: entry.facts.may_include_other_chars,
},
);
let fragment = ExpressionDomainFragmentV0 {
expression_id: entry.expression_id.clone(),
file_path: entry.file_path.clone(),
value_domain_kind: entry.facts.kind.clone(),
value_constraint_kind: entry.facts.constraint_kind.clone(),
value_prefix: entry.facts.prefix.clone(),
value_suffix: entry.facts.suffix.clone(),
value_min_len: entry.facts.min_len,
value_max_len: entry.facts.max_len,
value_char_must: entry.facts.char_must.clone(),
value_char_may: entry.facts.char_may.clone(),
value_may_include_other_chars: entry.facts.may_include_other_chars,
finite_value_count: entry.facts.values.as_ref().map_or(0, Vec::len),
};
fragments.push(fragment.clone());
candidates.push(ExpressionDomainCandidateV0 {
expression_id: fragment.expression_id,
file_path: fragment.file_path,
value_domain_kind: fragment.value_domain_kind,
value_constraint_kind: fragment.value_constraint_kind,
value_prefix: fragment.value_prefix,
value_suffix: fragment.value_suffix,
value_min_len: fragment.value_min_len,
value_max_len: fragment.value_max_len,
value_char_must: fragment.value_char_must,
value_char_may: fragment.value_char_may,
value_may_include_other_chars: fragment.value_may_include_other_chars,
finite_value_count: fragment.finite_value_count,
});
evaluator_candidates.push(ExpressionDomainEvaluatorCandidateV0 {
kind: "expression-domain",
file_path: entry.file_path.clone(),
query_id: entry.expression_id.clone(),
payload: ExpressionDomainEvaluatorCandidatePayloadV0 {
expression_id: entry.expression_id.clone(),
value_domain_kind: map_reduced_expression_value_domain_kind(&entry.facts),
value_constraint_kind: entry.facts.constraint_kind.clone(),
value_prefix: entry.facts.prefix.clone(),
value_suffix: entry.facts.suffix.clone(),
value_min_len: entry.facts.min_len,
value_max_len: entry.facts.max_len,
value_char_must: entry.facts.char_must.clone(),
value_char_may: entry.facts.char_may.clone(),
value_may_include_other_chars: entry.facts.may_include_other_chars,
finite_value_count: entry.facts.values.as_ref().map_or(0, Vec::len),
value_domain_derivation: map_reduced_expression_value_domain_derivation(
&entry.facts,
),
},
});
}
fragments.sort_by(|a, b| a.expression_id.cmp(&b.expression_id));
candidates.sort_by(|a, b| a.expression_id.cmp(&b.expression_id));
evaluator_candidates.sort_by(|a, b| a.query_id.cmp(&b.query_id));
ExpressionDomainInputRows {
plan_summary: ExpressionDomainPlanSummaryV0 {
schema_version: "0",
input_version: input.version.clone(),
planned_expression_ids,
value_domain_kinds,
value_constraint_kinds,
constraint_detail_counts,
finite_value_count,
},
fragments,
candidates,
evaluator_candidates,
}
}
pub fn summarize_expression_domain_plan_input(
input: &EngineInputV2,
) -> ExpressionDomainPlanSummaryV0 {
collect_expression_domain_input_rows(input).plan_summary
}
pub fn summarize_expression_domain_fragments_input(
input: &EngineInputV2,
) -> ExpressionDomainFragmentsV0 {
let rows = collect_expression_domain_input_rows(input);
ExpressionDomainFragmentsV0 {
schema_version: "0",
input_version: input.version.clone(),
fragments: rows.fragments,
}
}
pub fn summarize_expression_domain_candidates_input(
input: &EngineInputV2,
) -> ExpressionDomainCandidatesV0 {
let rows = collect_expression_domain_input_rows(input);
ExpressionDomainCandidatesV0 {
schema_version: "0",
input_version: input.version.clone(),
candidates: rows.candidates,
}
}
pub fn summarize_expression_domain_canonical_candidate_bundle_input(
input: &EngineInputV2,
) -> ExpressionDomainCanonicalCandidateBundleV0 {
let rows = collect_expression_domain_input_rows(input);
ExpressionDomainCanonicalCandidateBundleV0 {
schema_version: "0",
input_version: input.version.clone(),
plan_summary: rows.plan_summary,
fragments: rows.fragments,
candidates: rows.candidates,
}
}
pub fn summarize_expression_domain_evaluator_candidates_input(
input: &EngineInputV2,
) -> ExpressionDomainEvaluatorCandidatesV0 {
let rows = collect_expression_domain_input_rows(input);
ExpressionDomainEvaluatorCandidatesV0 {
schema_version: "0",
input_version: input.version.clone(),
results: rows.evaluator_candidates,
}
}
pub fn summarize_expression_domain_canonical_producer_signal_input(
input: &EngineInputV2,
) -> ExpressionDomainCanonicalProducerSignalV0 {
let rows = collect_expression_domain_input_rows(input);
let input_version = input.version.clone();
ExpressionDomainCanonicalProducerSignalV0 {
schema_version: "0",
input_version: input_version.clone(),
canonical_bundle: ExpressionDomainCanonicalCandidateBundleV0 {
schema_version: "0",
input_version: input_version.clone(),
plan_summary: rows.plan_summary,
fragments: rows.fragments,
candidates: rows.candidates,
},
evaluator_candidates: ExpressionDomainEvaluatorCandidatesV0 {
schema_version: "0",
input_version,
results: rows.evaluator_candidates,
},
}
}
#[cfg(test)]
mod tests {
use super::{
summarize_expression_domain_candidates_input,
summarize_expression_domain_canonical_candidate_bundle_input,
summarize_expression_domain_canonical_producer_signal_input,
summarize_expression_domain_evaluator_candidates_input,
summarize_expression_domain_fragments_input, summarize_expression_domain_plan_input,
};
use crate::{StringTypeFactsV2, TypeFactEntryV2, test_support::sample_input};
#[test]
fn summarizes_expression_domain_counts() {
let summary = summarize_expression_domain_plan_input(&sample_input());
assert_eq!(
summary.planned_expression_ids,
vec!["expr-1".to_string(), "expr-2".to_string()]
);
assert_eq!(summary.value_domain_kinds.get("constrained"), Some(&1));
assert_eq!(summary.value_domain_kinds.get("finiteSet"), Some(&1));
assert_eq!(summary.value_constraint_kinds.get("prefixSuffix"), Some(&1));
assert_eq!(summary.constraint_detail_counts.prefix_count, 1);
assert_eq!(summary.constraint_detail_counts.suffix_count, 1);
assert_eq!(summary.constraint_detail_counts.min_len_count, 1);
assert_eq!(summary.finite_value_count, 2);
}
#[test]
fn summarizes_expression_domain_fragments() {
let summary = summarize_expression_domain_fragments_input(&sample_input());
assert_eq!(summary.fragments.len(), 2);
let first = &summary.fragments[0];
assert_eq!(first.expression_id, "expr-1");
assert_eq!(first.file_path, "/tmp/App.tsx");
assert_eq!(first.value_domain_kind, "constrained");
assert_eq!(first.value_constraint_kind.as_deref(), Some("prefixSuffix"));
assert_eq!(first.value_prefix.as_deref(), Some("btn-"));
assert_eq!(first.value_suffix.as_deref(), Some("-active"));
assert_eq!(first.value_min_len, Some(10));
assert_eq!(first.finite_value_count, 0);
let second = &summary.fragments[1];
assert_eq!(second.expression_id, "expr-2");
assert_eq!(second.value_domain_kind, "finiteSet");
assert_eq!(second.finite_value_count, 2);
}
#[test]
fn summarizes_expression_domain_candidates() {
let summary = summarize_expression_domain_candidates_input(&sample_input());
assert_eq!(summary.candidates.len(), 2);
assert_eq!(summary.candidates[0].expression_id, "expr-1");
assert_eq!(summary.candidates[0].value_domain_kind, "constrained");
assert_eq!(
summary.candidates[0].value_constraint_kind.as_deref(),
Some("prefixSuffix")
);
assert_eq!(summary.candidates[1].expression_id, "expr-2");
assert_eq!(summary.candidates[1].finite_value_count, 2);
}
#[test]
fn summarizes_expression_domain_canonical_candidate_bundle() {
let summary = summarize_expression_domain_canonical_candidate_bundle_input(&sample_input());
assert_eq!(summary.plan_summary.planned_expression_ids.len(), 2);
assert_eq!(summary.fragments.len(), 2);
assert_eq!(summary.candidates.len(), 2);
}
#[test]
fn summarizes_expression_domain_evaluator_candidates() {
let summary = summarize_expression_domain_evaluator_candidates_input(&sample_input());
assert_eq!(summary.schema_version, "0");
assert_eq!(summary.input_version, "2");
assert_eq!(summary.results.len(), 2);
assert_eq!(summary.results[0].kind, "expression-domain");
assert_eq!(summary.results[0].query_id, "expr-1");
assert_eq!(summary.results[0].payload.value_domain_kind, "prefixSuffix");
assert_eq!(
summary.results[0].payload.value_constraint_kind.as_deref(),
Some("prefixSuffix")
);
assert_eq!(summary.results[1].payload.finite_value_count, 2);
}
#[test]
fn expression_domain_evaluator_reports_reduced_value_domain_kind() {
let mut input = sample_input();
input.type_facts.push(TypeFactEntryV2 {
file_path: "/tmp/App.tsx".to_string(),
expression_id: "expr-3".to_string(),
facts: StringTypeFactsV2 {
kind: "finiteSet".to_string(),
constraint_kind: Some("prefix".to_string()),
values: Some(vec!["btn-active".to_string(), "card".to_string()]),
prefix: Some("btn-".to_string()),
suffix: None,
min_len: None,
max_len: None,
char_must: None,
char_may: None,
may_include_other_chars: None,
},
});
let fragments = summarize_expression_domain_fragments_input(&input);
let candidates = summarize_expression_domain_candidates_input(&input);
let evaluator_candidates = summarize_expression_domain_evaluator_candidates_input(&input);
assert_eq!(fragments.fragments[2].expression_id, "expr-3");
assert_eq!(fragments.fragments[2].value_domain_kind, "finiteSet");
assert_eq!(candidates.candidates[2].expression_id, "expr-3");
assert_eq!(candidates.candidates[2].value_domain_kind, "finiteSet");
assert_eq!(evaluator_candidates.results[2].query_id, "expr-3");
assert_eq!(
evaluator_candidates.results[2].payload.value_domain_kind,
"exact"
);
assert_eq!(
evaluator_candidates.results[2]
.payload
.value_domain_derivation
.reduced_kind,
"exact"
);
assert_eq!(
evaluator_candidates.results[2]
.payload
.value_domain_derivation
.steps[1]
.operation,
"intersectConstraint"
);
}
#[test]
fn summarizes_expression_domain_canonical_producer_signal() {
let summary = summarize_expression_domain_canonical_producer_signal_input(&sample_input());
assert_eq!(summary.schema_version, "0");
assert_eq!(summary.input_version, "2");
assert_eq!(
summary
.canonical_bundle
.plan_summary
.planned_expression_ids
.len(),
2
);
assert_eq!(summary.canonical_bundle.fragments.len(), 2);
assert_eq!(summary.canonical_bundle.candidates.len(), 2);
assert_eq!(summary.evaluator_candidates.results.len(), 2);
}
}