Skip to content

split component sequence validator#291

Open
xiangu-batw wants to merge 3 commits into
eclipse-score:mainfrom
xiangu-batw:xian_split_validator
Open

split component sequence validator#291
xiangu-batw wants to merge 3 commits into
eclipse-score:mainfrom
xiangu-batw:xian_split_validator

Conversation

@xiangu-batw

Copy link
Copy Markdown
Contributor

No description provided.

&mut errors,
validate_component_sequence(
component,
validate_component_sequence(component, sequence, Errors::default()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the idea of this Errors::default() ? Do we need it?

// validators: change signature from
pub fn validate_component_internal_api(
component_diagram: &ComponentDiagramArchitecture,
internal_api_diagram: &InternalApiIndex,
errors: Errors, // ← remove this
) -> Errors

// to
pub fn validate_component_internal_api(
component_diagram: &ComponentDiagramArchitecture,
internal_api_diagram: &InternalApiIndex,
) -> Errors

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

);
ran_validator = true;
}
if let (Some(component), Some(internal_api)) = (component.as_ref(), internal_api.as_ref()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change it here also from:

// before
merge_errors(
&mut errors,
validate_component_internal_api(component, internal_api, Errors::default()),
);

// after — intent is now explicit
merge_errors(&mut errors, validate_component_internal_api(component, internal_api));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

)
}

fn format_name_list(names: &BTreeSet<String>) -> String {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use this snipped in each validator, should we move it into a format_helper?

// validation/core/src/validators/format_helpers.rs
use std::collections::BTreeSet;

pub(super) fn format_name_list(names: &BTreeSet) -> String {
if names.is_empty() {
return "".to_string();
}
names.iter().map(|n| format!(""{n}"")).collect::<Vec<_>>().join(", ")
}

pub(super) fn extract_method_name(method: &str) -> &str {
method.split('(').next().unwrap_or(method).trim()
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the common functions are now in shared/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 3 test files in the test folder include nearly the same fixtures, would it make sense to create a common fixture folder? e.g.

// test/fixtures.rs (only compiled under #[cfg(test)])
pub fn unit(alias: &str, interface_targets: &[&str]) -> LogicComponent { ... }
pub fn interface(alias: &str) -> LogicComponent { ... }
pub fn component_diagrams_with_entities(entities: Vec) -> ComponentDiagramInputs { ... }
pub fn internal_api_index(interfaces: Vec<(&str, Vec<&str>)>) -> InternalApiIndex {
let mut errors = Errors::default();
let idx = InternalApiIndex::build_index(&raw, &mut errors);
assert!(errors.is_empty(), "test fixture construction failed: {:?}", errors.messages);
idx
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extracted the fixtures in fixtures.rs

match relation.source_role.as_deref() {
Some("Required") => {
if relation.relation_type != ComponentRelationType::InterfaceBinding {
continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hides inconsistencies, maybe add an debug output?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it filters out the irrelevant cases

.find(|candidate| candidate.is_interface() && candidate.id == relation.target)
.map(|candidate| candidate.id.clone())
else {
continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hides inconsistencies, maybe add debut output?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it filters out the irrelevant cases

}
}

fn build_unit_bindings(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nearly same function is declared in component_sequence_validator as well (only different return type)
Can we move UnitInterfaces move to a shared module:
// shared: fn interface_entity_ids(diagram: &ComponentDiagramArchitecture) -> BTreeSet<&str>
// each validator calls it, builds its own map with correct return type

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the common functions are now in shared/

Comment on lines 54 to 77
if let (Some(component), Some(sequence)) = (component.as_ref(), sequence.as_ref()) {
merge_errors(
&mut errors,
validate_component_sequence(
component,
validate_component_sequence(component, sequence, Errors::default()),
);
ran_validator = true;
}
if let (Some(component), Some(internal_api)) = (component.as_ref(), internal_api.as_ref()) {
merge_errors(
&mut errors,
validate_component_internal_api(component, internal_api, Errors::default()),
);
ran_validator = true;
}
if let (Some(sequence), Some(internal_api)) = (sequence.as_ref(), internal_api.as_ref()) {
merge_errors(
&mut errors,
validate_sequence_internal_api(
sequence,
internal_api.as_ref(),
internal_api,
component.as_ref(),
Errors::default(),
),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we use the registry pattern here? So basically each validator registers itself and we don´t have to adapt the logic every time a new validator is added? Maybe something like:

// architectural_design.rs
let validators: &[Box<dyn Fn() -> Option>] = &[
Box::new(|| {
let (c, s) = (component.as_ref()?, sequence.as_ref()?);
Some(validate_component_sequence(c, s))
}),
Box::new(|| {
let (c, i) = (component.as_ref()?, internal_api.as_ref()?);
Some(validate_component_internal_api(c, i))
}),
Box::new(|| {
let (s, i) = (sequence.as_ref()?, internal_api.as_ref()?);
Some(validate_sequence_internal_api(s, i, component.as_ref()))
}),
];

let mut ran_validator = false;
for validator in validators {
if let Some(errs) = validator() {
merge_errors(&mut errors, errs);
ran_validator = true;
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated with registry pattern.

Comment on lines 351 to 387
_ => {}
EndpointRole::None => {}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nested loop, also used in the sequence_internal_api_validation. We could think of creating an "ID Map" (pre-built BTreeSet of interface IDs)

so maybe something like:

// Built once before outer loop — O(E):
let interface_ids: BTreeSet<&str> = diagram.entities.iter()
.filter(|e| e.is_interface())
.map(|e| e.id.as_str())
.collect();
// Contains: {"pkg.InterfaceA", "pkg.InterfaceB"}

// Inside per-unit, per-relation loop — O(log E):
if !interface_ids.contains(relation.target.as_str()) {
continue;
}
// relation.target is already the interface ID — use directly, no .find() needed
bindings.insert(relation.target.clone()); // single clone, not double

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

@xiangu-batw xiangu-batw force-pushed the xian_split_validator branch 4 times, most recently from b0c50bc to 6b0ec47 Compare July 2, 2026 02:46
)
.is_empty()
{
eprintln!("Warning: sequence call between units \"{}\" and \"{}\" for method \"{}\" has shared interface(s) {:?} but no matching required/provided roles in the component diagram",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

route through log::

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

) {
if let Some(component_context) = component_context.as_ref() {
diagnostics.debug(|| {
"DEBUG: Sequence calls checked against related internal API interfaces:".to_string()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double Debug (debug already prints debug)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment on lines 295 to 314
@@ -649,177 +313,10 @@ fn build_observed_call_contexts<'a>(
.collect()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate to sequence_internal_api_validator:403 -> Move to shared utils?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

) {
diagnostics.debug(|| "Component interfaces checked against internal API:".to_string());
for interface_id in component_interface_ids {
diagnostics.debug(|| format!(" {interface_id}\n"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is trainling\n working here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.


diagnostics.debug(|| "Internal API interfaces available for component interfaces:".to_string());
for interface_id in internal_api_interface_ids {
diagnostics.debug(|| format!(" {interface_id}\n"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is trainling\n working here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

use crate::models::{
ComponentDiagramArchitecture, InternalApiIndex, InternalApiInterface, SequenceDiagramIndex,
use super::shared::{
all_interfaces_for_alias, build_unit_bindings, format_name_list, intersect_interfaces,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all_interfaces_for_alias clones BTreeSet on every call:
could we return &BTreeSet with lifetime of UnitBindigs, and work with references only?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. This would require changing SequenceCallContext to borrow interface sets instead of owning them, which in turn requires restructuring the current data model rather than a local optimization. Otherwise it will end up with a self-referential ownership pattern, which isn't supported by ordinary Rust ownership. I'd prefer to keep this PR as it is, and refactor the data model when adding public API support.

@xiangu-batw xiangu-batw force-pushed the xian_split_validator branch from 6b0ec47 to 8f19bd1 Compare July 3, 2026 05:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants