Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions bazel/rules/rules_score/docs/user_guide/architectural_design.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,48 @@ package "MySeooc" as MySeooc <<SEooC>> {
@enduml
```

#### Valid PlantUML Definitions

The validator identifies elements by their **stereotype**, not by the PlantUML keyword used. Both `package` and `component` keywords are accepted at each level.

| Stereotype | Valid PlantUML keywords | Meaning | Bazel rule |
|---|---|---|---|
| `<<SEooC>>` | `package`, `component` | Safety Element out of Context boundary | `dependable_element` |
| `<<component>>` | `component`, `package` | Architectural component | `component` |
| `<<unit>>` | `component`, `package` | Leaf implementation unit | `unit` |

#### Ports and Interface Bindings

Elements with stereotype `<<SEooC>>` or `<<component>>` may declare ports and bind them to interfaces. This documents which external interfaces the element requires or provides.

```text
@startuml MySeooc_StaticDesign

package "MySeooc" as MySeooc <<SEooC>> {
component "KvsComponent" as KvsComponent <<component>> {
component "KeyValueStore" as KeyValueStore <<unit>>
}

portin " " as p_storage ' required interface port
portout " " as p_api ' provided interface port
}

interface "score::storage" as storage
interface "kvsapi" as kvsapi

p_storage -( storage : requires
p_api )- kvsapi : provides

@enduml
```

**Rules:**

- `portin` / `portout` are declared inside the `<<SEooC>>` or `<<component>>` element.
- `-(` binds a required (incoming) interface; `)-` binds a provided (outgoing) interface.
- The `--()` lollipop syntax (e.g. `port --() Interface`) is treated as a plain association and does **not** carry interface-binding semantics.
- Plain `package` without a stereotype cannot carry interface bindings.

### Bazel

The PlantUML diagrams capture *intended* structure; the Bazel rules model the *actual* implementation. Using the same example as the diagram above — SEooC `MySeooc` containing component `KvsComponent` with units `KeyValueStore` and `StorageBackend` — the three rules work together like this:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ invalid_interface_binding_non_component.puml:
fields:
from: "ActorA"
to: "IService"
reason: "Decorator binding only allows Component on the left and Interface on the right"
reason: "Decorator binding requires a Component or component-stereotyped element on the left and Interface on the right"
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ invalid_interface_left_decorator.puml:
fields:
from: "IProvided"
to: "A"
reason: "Decorator binding only allows Component on the left and Interface on the right"
reason: "Decorator binding requires a Component or component-stereotyped element on the left and Interface on the right"
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
' *******************************************************************************
' Copyright (c) 2026 Contributors to the Eclipse Foundation
'
' See the NOTICE file(s) distributed with this work for additional
' information regarding copyright ownership.
'
' This program and the accompanying materials are made available under the
' terms of the Apache License Version 2.0 which is available at
' https://www.apache.org/licenses/LICENSE-2.0
'
' SPDX-License-Identifier: Apache-2.0
' *******************************************************************************
@startuml

package "Pkg" as Pkg {
interface "IService" as IService
}

Pkg )- IService : invalid

@enduml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
invalid_package_no_stereotype_binding.puml:
error:
type: "InvalidRelationship"
fields:
from: "Pkg"
to: "IService"
reason: "Decorator binding requires a Component or component-stereotyped element on the left and Interface on the right"
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"package_component_interface_binding.puml": {
"MyComponent": {
"id": "MyComponent",
"name": "MyComponent",
"alias": "MyComponent",
"parent_id": null,
"comp_type": "Package",
"stereotype": "component",
"relations": [
{
"target": "IRequired",
"annotation": "requires",
"relation_type": "InterfaceBinding",
"source_role": "Required"
},
{
"target": "IProvided",
"annotation": "provides",
"relation_type": "InterfaceBinding",
"source_role": "Provided"
}
]
},
"IRequired": {
"id": "IRequired",
"name": "IRequired",
"alias": "IRequired",
"parent_id": null,
"comp_type": "Interface",
"stereotype": null,
"relations": []
},
"IProvided": {
"id": "IProvided",
"name": "IProvided",
"alias": "IProvided",
"parent_id": null,
"comp_type": "Interface",
"stereotype": null,
"relations": []
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
' *******************************************************************************
' Copyright (c) 2026 Contributors to the Eclipse Foundation
'
' See the NOTICE file(s) distributed with this work for additional
' information regarding copyright ownership.
'
' This program and the accompanying materials are made available under the
' terms of the Apache License Version 2.0 which is available at
' https://www.apache.org/licenses/LICENSE-2.0
'
' SPDX-License-Identifier: Apache-2.0
' *******************************************************************************
@startuml

package "MyComponent" as MyComponent <<component>> {
portin " " as p_in
portout " " as p_out
}

interface "IRequired" as IRequired
interface "IProvided" as IProvided

p_in -( IRequired : requires
p_out )- IProvided : provides

@enduml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"package_seooc_interface_binding.puml": {
"MySeooc": {
"id": "MySeooc",
"name": "MySeooc",
"alias": "MySeooc",
"parent_id": null,
"comp_type": "Package",
"stereotype": "SEooC",
"relations": [
{
"target": "IRequired",
"annotation": "requires",
"relation_type": "InterfaceBinding",
"source_role": "Required"
},
{
"target": "IProvided",
"annotation": "provides",
"relation_type": "InterfaceBinding",
"source_role": "Provided"
}
]
},
"IRequired": {
"id": "IRequired",
"name": "IRequired",
"alias": "IRequired",
"parent_id": null,
"comp_type": "Interface",
"stereotype": null,
"relations": []
},
"IProvided": {
"id": "IProvided",
"name": "IProvided",
"alias": "IProvided",
"parent_id": null,
"comp_type": "Interface",
"stereotype": null,
"relations": []
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
' *******************************************************************************
' Copyright (c) 2026 Contributors to the Eclipse Foundation
'
' See the NOTICE file(s) distributed with this work for additional
' information regarding copyright ownership.
'
' This program and the accompanying materials are made available under the
' terms of the Apache License Version 2.0 which is available at
' https://www.apache.org/licenses/LICENSE-2.0
'
' SPDX-License-Identifier: Apache-2.0
' *******************************************************************************
@startuml

package "MySeooc" as MySeooc <<SEooC>> {
portin " " as p_in
portout " " as p_out
}

interface "IRequired" as IRequired
interface "IProvided" as IProvided

p_in -( IRequired : requires
p_out )- IProvided : provides

@enduml
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,5 @@ When interface bindings are used:
- Exactly one endpoint must be an interface.
- Interface-to-interface bindings are not allowed.
- Interface-left decorator forms are rejected.
- Only a `component` element or a `package` with stereotype `<<SEooC>>` or `<<component>>` is accepted on the left side.
- Port role (`portin`/`portout`) must be consistent with decorator role.
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ struct RelationValidationInput<'a> {
has_interface_tokens: bool,
src_is_interface: bool,
tgt_is_interface: bool,
src_is_component: bool,
src_is_component_role: bool,
decor_role: Option<EndpointRole>,
src_port_role: Option<EndpointRole>,
}
Expand Down Expand Up @@ -471,9 +471,15 @@ impl ComponentResolver {
});
}

let decor_role = if line == "-" && left == ")" && middle.is_empty() && right.is_empty() {
// A lollipop line may carry a direction hint, which adds a second dash
// segment: `)-u-` or `-u-(`. The line field then contains `"--"` instead
// of `"-"`. Direction is visual-only and does not affect semantics.
let is_lollipop_line = line.chars().all(|c| c == '-') && !line.is_empty();

let decor_role = if is_lollipop_line && left == ")" && middle.is_empty() && right.is_empty()
{
Some(EndpointRole::Provided)
} else if line == "-"
} else if is_lollipop_line
&& left.is_empty()
&& ((middle == "(" && right.is_empty()) || (middle.is_empty() && right == "("))
{
Expand Down Expand Up @@ -570,13 +576,13 @@ impl ComponentResolver {
) -> Option<ComponentResolverError> {
if input.has_interface_tokens
&& input.decor_role.is_some()
&& (!input.src_is_component || !input.tgt_is_interface)
&& (!input.src_is_component_role || !input.tgt_is_interface)
{
return Some(ComponentResolverError::InvalidRelationship {
from: input.relation.lhs.clone(),
to: input.relation.rhs.clone(),
reason:
"Decorator binding only allows Component on the left and Interface on the right"
"Decorator binding requires a Component or component-stereotyped element on the left and Interface on the right"
.to_string(),
});
}
Expand Down Expand Up @@ -636,14 +642,21 @@ impl ComponentResolver {
let src_is_interface = matches!(src_type, Some(ComponentType::Interface));
let tgt_is_interface = matches!(tgt_type, Some(ComponentType::Interface));
let src_is_component = matches!(src_type, Some(ComponentType::Component));
let src_is_package = matches!(src_type, Some(ComponentType::Package));
let src_stereotype = self
.elements
.get(&src_fqn)
.and_then(|e| e.stereotype.as_deref());
let src_is_component_role = src_is_component
|| (src_is_package && matches!(src_stereotype, Some("SEooC") | Some("component")));

let validation_input = RelationValidationInput {
relation,
has_interface_tokens: parsed_arrow.has_provided_token
|| parsed_arrow.has_required_token,
src_is_interface,
tgt_is_interface,
src_is_component,
src_is_component_role,
decor_role: parsed_arrow.decor_role,
src_port_role,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,21 @@ fn test_port_target_no_decor_no_mismatch() {
run_component_resolver_case("port_target_no_decor_no_mismatch");
}

#[test]
fn test_package_seooc_interface_binding() {
run_component_resolver_case("package_seooc_interface_binding");
}

#[test]
fn test_package_component_interface_binding() {
run_component_resolver_case("package_component_interface_binding");
}

#[test]
fn test_invalid_package_no_stereotype_binding() {
run_component_resolver_case("invalid_package_no_stereotype_binding");
}

#[test]
fn test_deployment_diagram() {
run_deployment_resolver_case("deployment_diagram_it");
Expand Down
34 changes: 34 additions & 0 deletions validation/core/docs/specifications/bazel_component.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,40 @@ immediate enclosing component alias as parent.
| Missing unit in PlantUML | Unit Consistency |
| Extra unit in PlantUML | Unit Consistency |

## PlantUML Stereotype Reference

The validator identifies elements by their **stereotype**, not by the PlantUML keyword. Both `package` and `component` keywords are accepted for each role.

| Stereotype | Valid PlantUML keywords | Meaning | Bazel rule |
|---|---|---|---|
| `<<SEooC>>` | `package`, `component` | Safety Element out of Context boundary; may own `portin`/`portout` ports | `dependable_element` |
| `<<component>>` | `component`, `package` | Architectural component; may own `portin`/`portout` ports | `component` |
| `<<unit>>` | `component`, `package` | Leaf implementation unit | `unit` |

### Port and Interface Binding

Elements with stereotype `<<SEooC>>` or `<<component>>` may declare ports and bind them to interfaces:

```text
package "MySeooc" as MySeooc <<SEooC>> {
portin " " as p_in ' required interface port
portout " " as p_out ' provided interface port
}

interface "IRequired" as IRequired
interface "IProvided" as IProvided

p_in -( IRequired : requires ' required binding
p_out )- IProvided : provides ' provided binding
```

**Rules:**

- `portin` / `portout` must be declared inside the `<<SEooC>>` or `<<component>>` element.
- Use `-(` for required (incoming) and `)-` for provided (outgoing) interface bindings.
- Plain `package` **without** a stereotype cannot carry interface bindings.
- Elements with other stereotypes (e.g. `actor`, `database`) are not valid on the left side of a binding.

## Debug Output

The validator emits debug output containing:
Expand Down
Loading
Loading