Skip to content

Conversation

@raphael-goetz
Copy link
Member

Resolves: #91

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 14 changed files in this pull request and generated 19 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +76 to +79
Err(ReaderError::ReadFeatureError {
path: self.path.to_string(),
source: Box::new(err),
})
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

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

[nitpick] Potential information loss in error propagation. When an error occurs in read_feature_content, it's wrapped in another ReadFeatureError, creating nested error wrapping. This can make the original error harder to diagnose. Consider either:

  1. Returning the error directly without additional wrapping
  2. Flattening the error structure
  3. Providing more context about which specific operation failed

The current structure creates double-logging and redundant error wrapping.

Suggested change
Err(ReaderError::ReadFeatureError {
path: self.path.to_string(),
source: Box::new(err),
})
Err(err)

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +74
pub async fn send(&self) {
self.update_data_types().await;
self.update_runtime_definitions().await;
self.update_flow_types().await;
}
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

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

The send method silently ignores all errors from the individual update methods. This makes it difficult for callers to know if the updates succeeded or failed. Consider:

  1. Returning a Result type to indicate success/failure
  2. Collecting errors and returning them
  3. At minimum, add a return value indicating overall success

Example:

pub async fn send(&self) -> Result<(), Vec<String>> {
    let mut errors = Vec::new();
    // Track errors from each update method
    // Return Err(errors) if any occurred
    if errors.is_empty() {
        Ok(())
    } else {
        Err(errors)
    }
}

Copilot uses AI. Check for mistakes.
Comment on lines +126 to +140
None => continue,
};

let flow_types =
match self.load_definitions_for_feature::<FlowType>(&path, "flow_type")? {
Some(v) => v,
None => continue,
};

let functions = match self.load_definitions_for_feature::<RuntimeFunctionDefinition>(
&path,
"runtime_definition",
)? {
Some(v) => v,
None => continue,
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

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

Inconsistent error handling strategy. In load_definitions_for_feature, when should_break is false and an error occurs, the method returns Ok(None). However, this means that if one of the three definition types (data_types, flow_types, or functions) fails to load, the entire feature is skipped (lines 126, 132, 140 all have continue when None is returned).

This could lead to silent data loss - if only data types fail to load but flow types succeed, the entire feature is skipped. Consider either:

  1. Collecting partial features with whatever loaded successfully
  2. Logging warnings more explicitly about which definition types failed
  3. Returning more granular error information
Suggested change
None => continue,
};
let flow_types =
match self.load_definitions_for_feature::<FlowType>(&path, "flow_type")? {
Some(v) => v,
None => continue,
};
let functions = match self.load_definitions_for_feature::<RuntimeFunctionDefinition>(
&path,
"runtime_definition",
)? {
Some(v) => v,
None => continue,
None => {
log::warn!(
"Failed to load data_types for feature '{}', path: {}. Using empty list.",
feature_name,
path.display()
);
Vec::new()
}
};
let flow_types = match self
.load_definitions_for_feature::<FlowType>(&path, "flow_type")?
{
Some(v) => v,
None => {
log::warn!(
"Failed to load flow_types for feature '{}', path: {}. Using empty list.",
feature_name,
path.display()
);
Vec::new()
}
};
let functions = match self
.load_definitions_for_feature::<RuntimeFunctionDefinition>(
&path,
"runtime_definition",
)?
{
Some(v) => v,
None => {
log::warn!(
"Failed to load functions for feature '{}', path: {}. Using empty list.",
feature_name,
path.display()
);
Vec::new()
}

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +10

#[derive(Deserialize, Debug, Clone)]
pub struct Feature {
pub name: String,
pub data_types: Vec<DefinitionDataType>,
pub flow_types: Vec<FlowType>,
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

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

The Feature struct lacks documentation. Public structs should have doc comments explaining their purpose and fields:

/// Represents a feature containing related flow definitions.
///
/// A feature groups together data types, flow types, and runtime function
/// definitions that belong to the same logical feature or module.
#[derive(Deserialize, Debug, Clone)]
pub struct Feature {
    /// The name of the feature
    pub name: String,
    /// Data type definitions associated with this feature
    pub data_types: Vec<DefinitionDataType>,
    /// Flow type definitions associated with this feature
    pub flow_types: Vec<FlowType>,
    /// Runtime function definitions associated with this feature
    pub functions: Vec<RuntimeFunctionDefinition>,
}
Suggested change
#[derive(Deserialize, Debug, Clone)]
pub struct Feature {
pub name: String,
pub data_types: Vec<DefinitionDataType>,
pub flow_types: Vec<FlowType>,
/// Represents a feature containing related flow definitions.
///
/// A feature groups together data types, flow types, and runtime function
/// definitions that belong to the same logical feature or module.
#[derive(Deserialize, Debug, Clone)]
pub struct Feature {
/// The name of the feature.
pub name: String,
/// Data type definitions associated with this feature.
pub data_types: Vec<DefinitionDataType>,
/// Flow type definitions associated with this feature.
pub flow_types: Vec<FlowType>,
/// Runtime function definitions associated with this feature.

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +184
pub struct FlowUpdateService {
aquila_url: String,
data_types: Vec<DataType>,
runtime_definitions: Vec<RuntimeFunctionDefinition>,
flow_types: Vec<FlowType>,
}

impl FlowUpdateService {
/// Create a new FlowUpdateService instance from an Aquila URL and a definition path.
///
/// This will read the definition files from the given path and initialize the service with the data types, runtime definitions, and flow types.
pub fn from_url(aquila_url: String, definition_path: &str) -> Self {
let mut data_types = Vec::new();
let mut runtime_definitions = Vec::new();
let mut flow_types = Vec::new();

let reader = Reader::configure(definition_path.to_string(), true, vec![], None);

let features = match reader.read_features() {
Ok(features) => features,
Err(error) => {
log::error!("Error occurred while reading definitions: {:?}", error);
panic!("Error occurred while reading definitions")
}
};

for feature in features {
data_types.append(&mut feature.data_types.clone());
flow_types.append(&mut feature.flow_types.clone());
runtime_definitions.append(&mut feature.functions.clone());
}

Self {
aquila_url,
data_types,
runtime_definitions,
flow_types,
}
}

pub fn with_flow_types(mut self, flow_types: Vec<FlowType>) -> Self {
self.flow_types = flow_types;
self
}

pub fn with_data_types(mut self, data_types: Vec<DataType>) -> Self {
self.data_types = data_types;
self
}

pub fn with_runtime_definitions(
mut self,
runtime_definitions: Vec<RuntimeFunctionDefinition>,
) -> Self {
self.runtime_definitions = runtime_definitions;
self
}

pub async fn send(&self) {
self.update_data_types().await;
self.update_runtime_definitions().await;
self.update_flow_types().await;
}

async fn update_data_types(&self) {
if self.data_types.is_empty() {
log::info!("No data types to update");
return;
}

log::info!("Updating the current DataTypes!");
let mut client = match DataTypeServiceClient::connect(self.aquila_url.clone()).await {
Ok(client) => {
log::info!("Successfully connected to the DataTypeService");
client
}
Err(err) => {
log::error!("Failed to connect to the DataTypeService: {:?}", err);
return;
}
};

let request = DataTypeUpdateRequest {
data_types: self.data_types.clone(),
};

match client.update(request).await {
Ok(response) => {
log::info!(
"Was the update of the DataTypes accepted by Sagittarius? {}",
response.into_inner().success
);
}
Err(err) => {
log::error!("Failed to update data types: {:?}", err);
}
}
}

async fn update_runtime_definitions(&self) {
if self.runtime_definitions.is_empty() {
log::info!("No runtime definitions to update");
return;
}

log::info!("Updating the current RuntimeDefinitions!");
let mut client =
match RuntimeFunctionDefinitionServiceClient::connect(self.aquila_url.clone()).await {
Ok(client) => {
log::info!("Connected to RuntimeFunctionDefinitionService");
client
}
Err(err) => {
log::error!(
"Failed to connect to RuntimeFunctionDefinitionService: {:?}",
err
);
return;
}
};

let request = RuntimeFunctionDefinitionUpdateRequest {
runtime_functions: self.runtime_definitions.clone(),
};

match client.update(request).await {
Ok(response) => {
log::info!(
"Was the update of the RuntimeFunctionDefinitions accepted by Sagittarius? {}",
response.into_inner().success
);
}
Err(err) => {
log::error!("Failed to update runtime function definitions: {:?}", err);
}
}
}

async fn update_flow_types(&self) {
if self.flow_types.is_empty() {
log::info!("No FlowTypes to update!");
return;
}

log::info!("Updating the current FlowTypes!");
let mut client = match FlowTypeServiceClient::connect(self.aquila_url.clone()).await {
Ok(client) => {
log::info!("Connected to FlowTypeService!");
client
}
Err(err) => {
log::error!("Failed to connect to FlowTypeService: {:?}", err);
return;
}
};

let request = FlowTypeUpdateRequest {
flow_types: self.flow_types.clone(),
};

match client.update(request).await {
Ok(response) => {
log::info!(
"Was the update of the FlowTypes accepted by Sagittarius? {}",
response.into_inner().success
);
}
Err(err) => {
log::error!("Failed to update flow types: {:?}", err);
}
}
}
}
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

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

The FlowUpdateService lacks test coverage. Given that other modules in this repository have comprehensive test suites, this new service should have tests covering:

  • Successful initialization from a definition path
  • Handling errors during definition reading
  • Builder pattern methods (with_flow_types, with_data_types, with_runtime_definitions)
  • The send method and individual update methods
  • Connection failures and update failures

Consider adding tests using mock gRPC clients or integration tests.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +50
pub trait HasVersion {
fn version(&self) -> &Option<Version>;
fn version_mut(&mut self) -> &mut Option<Version>;

fn normalize_version(&mut self) {
self.version_mut().get_or_insert(Version {
major: 0,
minor: 0,
patch: 0,
});
}

fn is_accepted(&self, filter: &Option<Version>) -> bool {
filter
.as_ref()
.is_none_or(|v| self.version().as_ref() == Some(v))
}
}

impl HasVersion for DefinitionDataType {
fn version(&self) -> &Option<Version> {
&self.version
}

fn version_mut(&mut self) -> &mut Option<Version> {
&mut self.version
}
}

impl HasVersion for FlowType {
fn version(&self) -> &Option<Version> {
&self.version
}

fn version_mut(&mut self) -> &mut Option<Version> {
&mut self.version
}
}

impl HasVersion for RuntimeFunctionDefinition {
fn version(&self) -> &Option<Version> {
&self.version
}

fn version_mut(&mut self) -> &mut Option<Version> {
&mut self.version
}
}
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

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

The HasVersion trait and its implementations lack test coverage. Consider adding tests to verify:

  • normalize_version correctly sets default version (0.0.0) when None
  • normalize_version doesn't overwrite existing versions
  • is_accepted correctly filters based on version
  • is_accepted returns true when filter is None
  • All three implementations work correctly for their respective types

Add tests in a #[cfg(test)] module.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to 18
pub struct Reader {
should_break: bool,
accepted_features: Vec<String>,
accepted_version: Option<Version>,
path: String,
}
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

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

The Reader struct and its public methods lack documentation. Public APIs should have doc comments explaining their purpose, parameters, and behavior. Consider adding:

/// A reader for parsing flow definition files from a directory structure.
///
/// The Reader walks through a directory containing feature definitions and
/// parses JSON files for data types, flow types, and runtime function definitions.
pub struct Reader {
    // ...
}

Also add documentation for the public methods configure and read_features.

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +26
pub fn configure(
path: String,
should_break: bool,
accepted_features: Vec<String>,
accepted_version: Option<Version>,
) -> Self {
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

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

The configure method lacks documentation explaining its parameters. Add doc comments:

/// Creates a new Reader instance with the specified configuration.
///
/// # Parameters
/// * `path` - The root path to the directory containing feature definitions
/// * `should_break` - If true, stops on first error; if false, skips errors and continues
/// * `accepted_features` - If not empty, only features in this list will be loaded
/// * `accepted_version` - If specified, only definitions matching this version will be loaded
pub fn configure(

Copilot uses AI. Check for mistakes.
@raphael-goetz raphael-goetz merged commit e010490 into main Nov 27, 2025
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Move Reader into Code0-Flow

2 participants