mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat: Complete target system with C# bindings and resource inference (#458)
* feat: Add Schema Registry and Validation Framework This commit introduces a comprehensive schema registry and validation framework, providing schema-based validation of resources and policy effects. - Thread-safe, in-memory registry for schema storage and management - Global registry patterns for effects and resources - Concurrent access with proper error handling - Unicode schema names support - JSON Schema-compliant validation for all primitive types - Advanced constraint validation (patterns, ranges, length limits) - Discriminated union support with anyOf schemas - Detailed error reporting with nested validation paths - Discriminated subobject validation for polymorphic schemas - **Registry Tests**: All registry operations - **Effect Tests**: Policy effect validation - **Resource Tests**: Resource validation - **Validation Tests**: Core validation engine - Thread-safety, error handling, integration scenarios, edge cases - **Dependencies**: dashmap, once_cell, regex - **Thread Safety**: Minimal locking with Rc<Schema> sharing - **Error Types**: TypeMismatch, OutOfRange, PatternMismatch, etc. - Complete schema registry and validation subsystem - Comprehensive test coverage - Foundation for policy validation in Regorus Benchmarks: - Criterion benchmarks for basic types, effects and Azure resources - Performance range: 3.22ns (string) to 34.74µs (Azure VM resource schema validation) - String withs patterns validation: 30.2µs. Need to explore whether regex caching helps bring this down. - Azure policy effects: 188ns-1.4µs Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> * feat: Complete target system with C# bindings and resource inference - Add comprehensive target system with TargetRegistry and target-aware compilation - Implement resource type inference from policy equality expressions - Create modular C# bindings with separate wrapper classes for each concept - Add thread-safe CompiledPolicy with reference counting for safe disposal - Enhance FFI with detailed error propagation and target functionality - Create TargetExampleApp demonstrating Azure Policy integration - Add CI/CD pipeline testing for all C# applications - Support target definitions with schema validation and resource selectors - Implement PolicyModule struct and target-aware compilation methods - Add comprehensive test coverage for target functionality Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> --------- Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
3c33d31d08
commit
cc917ea75d
58
src/interpreter/error.rs
Normal file
58
src/interpreter/error.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::Rc;
|
||||
use thiserror::Error;
|
||||
|
||||
type String = Rc<str>;
|
||||
|
||||
/// Error type for interpreter target resolution operations.
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum TargetCompileError {
|
||||
/// Multiple different targets specified across modules
|
||||
#[error("Multiple different targets specified: '{existing}' and '{conflicting}'")]
|
||||
ConflictingTargets {
|
||||
existing: String,
|
||||
conflicting: String,
|
||||
},
|
||||
/// Target not found in registry
|
||||
#[error("Target '{0}' not found in registry")]
|
||||
TargetNotFound(String),
|
||||
/// No target specified when one is required
|
||||
#[error("No target specified. When using compile_for_target, at least one module must specify a target using the __target__ annotation")]
|
||||
NoTargetSpecified,
|
||||
/// Modules with targets have different packages
|
||||
#[error("Modules with target '{target}' have different packages: '{existing_package}' and '{conflicting_package}'")]
|
||||
ConflictingPackages {
|
||||
target: String,
|
||||
existing_package: String,
|
||||
conflicting_package: String,
|
||||
},
|
||||
|
||||
/// No effects have rules defined for the target
|
||||
#[error(
|
||||
"Target '{target_name}' requires a rule with name {effect_names} in package '{package}'"
|
||||
)]
|
||||
NoEffectRules {
|
||||
target_name: String,
|
||||
package: String,
|
||||
effect_names: String,
|
||||
},
|
||||
/// Multiple effect rules found for the same effect
|
||||
#[error("Multiple effects have rules defined for target '{target_name}': {effect_names}. Only one effect should have rules defined in package '{path}'")]
|
||||
MultipleEffectRules {
|
||||
target_name: String,
|
||||
effect_names: String,
|
||||
path: String,
|
||||
},
|
||||
|
||||
/// Missing default resource schema error
|
||||
#[error("Missing default resource schema: {0}")]
|
||||
MissingDefaultResourceSchema(String),
|
||||
/// Incompatible default schema error
|
||||
#[error("Incompatible default schema: {0}")]
|
||||
IncompatibleDefaultSchema(String),
|
||||
/// Invalid default schema type error
|
||||
#[error("Invalid default schema type: {0}")]
|
||||
InvalidDefaultSchemaType(String),
|
||||
}
|
||||
278
src/interpreter/target/infer.rs
Normal file
278
src/interpreter/target/infer.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
use super::super::error::TargetCompileError;
|
||||
use super::super::*;
|
||||
use crate::ast::{BoolOp, Expr, Literal, Query, Rule};
|
||||
use crate::compiled_policy::InferredResourceTypes;
|
||||
use crate::value::Value;
|
||||
use crate::{Rc, Schema};
|
||||
|
||||
type String = Rc<str>;
|
||||
|
||||
/// Analyzes policy rules to infer resource types from equality expressions.
|
||||
///
|
||||
/// This function examines the compiled policy rules corresponding to the effect path
|
||||
/// and searches for equality statements that compare the resource selector field with
|
||||
/// string literals. It identifies patterns like:
|
||||
/// - `input.<resource_selector> == "resource_type_name"`
|
||||
/// - `input["resource_selector"] == "resource_type_name"`
|
||||
/// - `"resource_type_name" == input.<resource_selector>`
|
||||
/// - `"resource_type_name" == input["resource_selector"]`
|
||||
///
|
||||
/// The `resource_selector` is determined by the target's resource schema selector
|
||||
/// configuration (e.g., "type", "@odata.type").
|
||||
///
|
||||
/// # Schema Resolution
|
||||
/// For each inferred resource type, the function attempts to find the corresponding
|
||||
/// schema from the target's resource_schema_lookup table. If no specific schema is
|
||||
/// found, it falls back to the default_resource_schema after validating compatibility.
|
||||
///
|
||||
/// # Returns
|
||||
/// An InferredResourceTypes map mapping Query references to ResourceTypeInfo tuples
|
||||
/// containing (resource_type_name, schema).
|
||||
/// The results are also stored in the compiled policy's inferred_resource_types field
|
||||
/// for later use during policy evaluation.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns `TargetCompileError` if:
|
||||
/// - Default resource schema is missing when needed
|
||||
/// - Default schema is incompatible with the resource selector
|
||||
/// - Default schema is not an object type
|
||||
///
|
||||
/// # Examples
|
||||
/// For a policy with rules like:
|
||||
/// ```rego
|
||||
/// effect := "allow" { input.type == "Microsoft.Storage/storageAccounts" }
|
||||
/// effect := "deny" { input["@odata.type"] == "microsoft.graph.user" }
|
||||
/// ```
|
||||
/// This function returns a map with entries for each query containing the resource type
|
||||
/// conditions, mapping queries to their respective type names and schemas.
|
||||
pub fn infer_resource_type(
|
||||
interpreter: &mut Interpreter,
|
||||
) -> Result<InferredResourceTypes, TargetCompileError> {
|
||||
// Check if we have target info
|
||||
if let Some(ref target_info) = interpreter.compiled_policy.target_info {
|
||||
let target = &target_info.target;
|
||||
let effect_path = &target_info.effect_path;
|
||||
let resource_selector = &target.resource_schema_selector;
|
||||
|
||||
let mut result = InferredResourceTypes::new();
|
||||
|
||||
// Get rules for the effect path
|
||||
if let Some(rules) = interpreter.compiled_policy.rules.get(effect_path.as_ref()) {
|
||||
for rule in rules {
|
||||
analyze_rule_for_resource_types(rule, resource_selector, target, &mut result)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: We don't check default_rules because default rules cannot access input
|
||||
|
||||
// Store the result in the compiled policy for later use
|
||||
let compiled_policy = Rc::make_mut(&mut interpreter.compiled_policy);
|
||||
compiled_policy.inferred_resource_types = Some(result.clone());
|
||||
|
||||
Ok(result)
|
||||
} else {
|
||||
// No target info available
|
||||
Ok(InferredResourceTypes::new())
|
||||
}
|
||||
}
|
||||
|
||||
fn analyze_rule_for_resource_types(
|
||||
rule: &Rule,
|
||||
resource_selector: &str,
|
||||
target: &crate::target::Target,
|
||||
result: &mut InferredResourceTypes,
|
||||
) -> Result<(), TargetCompileError> {
|
||||
if let Rule::Spec { bodies, .. } = rule {
|
||||
for body in bodies {
|
||||
analyze_query_for_resource_types(&body.query, resource_selector, target, result)?;
|
||||
}
|
||||
}
|
||||
// Default rules typically don't contain resource type conditions
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn analyze_query_for_resource_types(
|
||||
query: &Ref<Query>,
|
||||
resource_selector: &str,
|
||||
target: &crate::target::Target,
|
||||
result: &mut InferredResourceTypes,
|
||||
) -> Result<(), TargetCompileError> {
|
||||
let mut found_resource_type: Option<String> = None;
|
||||
|
||||
for stmt in &query.stmts {
|
||||
if let Literal::Expr { expr, .. } = &stmt.literal {
|
||||
if let Some(resource_type) = analyze_expr_for_resource_types(expr, resource_selector) {
|
||||
found_resource_type = Some(resource_type);
|
||||
break; // Found resource type, no need to continue searching
|
||||
}
|
||||
}
|
||||
// Note: We don't analyze NotExpr because it contains the opposite of type equality
|
||||
// (e.g., not input.type == "value" means the type is NOT that value)
|
||||
// Other literal statement (SomeVars, SomeIn, Every) don't typically contain
|
||||
// direct resource type comparisons
|
||||
}
|
||||
|
||||
// Now handle the insertion outside the loop
|
||||
if let Some(resource_type) = found_resource_type {
|
||||
// Look up the schema for this resource type
|
||||
let resource_type_value = Value::String(resource_type.clone());
|
||||
if let Some(schema) = target.resource_schema_lookup.get(&resource_type_value) {
|
||||
result.insert(query.clone(), (resource_type, schema.clone()));
|
||||
return Ok(());
|
||||
}
|
||||
// If not found in lookup, use default schema
|
||||
let default_schema = get_validated_default_schema(target, resource_selector)?;
|
||||
result.insert(query.clone(), (resource_type, default_schema));
|
||||
} else {
|
||||
// If no resource type was found for this query, use default schema
|
||||
let default_schema = get_validated_default_schema(target, resource_selector)?;
|
||||
result.insert(query.clone(), ("<default>".into(), default_schema));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn analyze_expr_for_resource_types(expr: &Expr, resource_selector: &str) -> Option<String> {
|
||||
// Only look for direct equality expressions: input.<resource_selector> == "string"
|
||||
if let Expr::BoolExpr {
|
||||
op: BoolOp::Eq,
|
||||
lhs,
|
||||
rhs,
|
||||
..
|
||||
} = expr
|
||||
{
|
||||
// Check if this is input.<resource_selector> == "string"
|
||||
if let (Some(input_field), Some(string_value)) = (
|
||||
extract_input_field_access(lhs, resource_selector),
|
||||
extract_string_literal(rhs),
|
||||
) {
|
||||
if input_field.as_ref() == resource_selector {
|
||||
return Some(string_value);
|
||||
}
|
||||
}
|
||||
// Also check the reverse: "string" == input.<resource_selector>
|
||||
else if let (Some(string_value), Some(input_field)) = (
|
||||
extract_string_literal(lhs),
|
||||
extract_input_field_access(rhs, resource_selector),
|
||||
) {
|
||||
if input_field.as_ref() == resource_selector {
|
||||
return Some(string_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
// We only look for direct equality expressions, no nested analysis
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract input field access like `input.type` or `input["@odata.type"]`
|
||||
fn extract_input_field_access(expr: &Expr, _expected_field: &str) -> Option<String> {
|
||||
use crate::value::Value;
|
||||
|
||||
match expr {
|
||||
// Handle input.field
|
||||
Expr::RefDot { refr, field, .. } => {
|
||||
if let (
|
||||
Expr::Var {
|
||||
value: Value::String(var_name),
|
||||
..
|
||||
},
|
||||
Value::String(field_name),
|
||||
) = (refr.as_ref(), &field.1)
|
||||
{
|
||||
if var_name.as_ref() == "input" {
|
||||
return Some(field_name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle input["field"] - the field is always a string literal
|
||||
Expr::RefBrack { refr, index, .. } => {
|
||||
if let (
|
||||
Expr::Var {
|
||||
value: Value::String(var_name),
|
||||
..
|
||||
},
|
||||
Some(field_name),
|
||||
) = (refr.as_ref(), extract_string_literal(index))
|
||||
{
|
||||
if var_name.as_ref() == "input" {
|
||||
return Some(field_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract string literal from expression
|
||||
fn extract_string_literal(expr: &Expr) -> Option<String> {
|
||||
use crate::value::Value;
|
||||
|
||||
if let Expr::String {
|
||||
value: Value::String(s),
|
||||
..
|
||||
} = expr
|
||||
{
|
||||
Some(s.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get and validate the default resource schema.
|
||||
/// Returns the default schema if it exists and is compatible with the resource selector.
|
||||
fn get_validated_default_schema(
|
||||
target: &crate::target::Target,
|
||||
resource_selector: &str,
|
||||
) -> Result<Rc<Schema>, TargetCompileError> {
|
||||
if let Some(default_schema) = &target.default_resource_schema {
|
||||
// Validate that default schema can handle the resource selector field
|
||||
validate_default_schema_compatibility(default_schema, resource_selector)?;
|
||||
Ok(default_schema.clone())
|
||||
} else {
|
||||
Err(TargetCompileError::MissingDefaultResourceSchema(
|
||||
format!("Target '{}' has no default resource schema", target.name).into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate that the default schema is compatible with the resource selector field.
|
||||
/// The schema must either allow additional properties or have a property matching the resource selector.
|
||||
fn validate_default_schema_compatibility(
|
||||
schema: &Rc<Schema>,
|
||||
resource_selector: &str,
|
||||
) -> Result<(), TargetCompileError> {
|
||||
use crate::schema::Type;
|
||||
|
||||
match schema.as_type() {
|
||||
Type::Object {
|
||||
properties,
|
||||
additional_properties,
|
||||
..
|
||||
} => {
|
||||
// Check if the schema has a property matching the resource selector
|
||||
if properties.contains_key(resource_selector) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Check if additional properties are allowed
|
||||
if additional_properties.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Neither condition is met
|
||||
Err(TargetCompileError::IncompatibleDefaultSchema(
|
||||
format!(
|
||||
"Default resource schema must either have additional properties enabled or contain a '{}' property",
|
||||
resource_selector
|
||||
).into()
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
// Default schema is not an object type
|
||||
Err(TargetCompileError::InvalidDefaultSchemaType(
|
||||
"Default resource schema must be an object type".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
248
src/interpreter/target/resolve.rs
Normal file
248
src/interpreter/target/resolve.rs
Normal file
@@ -0,0 +1,248 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use super::super::error::TargetCompileError;
|
||||
#[cfg(feature = "azure_policy")]
|
||||
use super::super::TargetInfo;
|
||||
use super::super::*;
|
||||
|
||||
fn format_effect_names(names: &[String]) -> String {
|
||||
match names.len() {
|
||||
0 => String::new(),
|
||||
1 => names[0].clone(),
|
||||
2 => format!("{} or {}", names[0], names[1]),
|
||||
_ => {
|
||||
if let Some((last, rest)) = names.split_last() {
|
||||
format!("{} or {}", rest.join(", "), last)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_target(interpreter: &mut Interpreter) -> Result<(), TargetCompileError> {
|
||||
use crate::registry::targets;
|
||||
|
||||
let mut target_name: Option<String> = None;
|
||||
let mut target_package: Option<String> = None;
|
||||
|
||||
// Check all modules for target specifications
|
||||
for module in interpreter.compiled_policy.modules.iter() {
|
||||
if let Some(ref module_target) = module.target {
|
||||
// Get the package path for this module
|
||||
let module_package = Interpreter::get_path_string(&module.package.refr, None)
|
||||
.map_err(|_| TargetCompileError::TargetNotFound(module_target.clone().into()))?;
|
||||
|
||||
match &target_name {
|
||||
None => {
|
||||
// First target found
|
||||
target_name = Some(module_target.clone());
|
||||
target_package = Some(module_package);
|
||||
}
|
||||
Some(existing_target) => {
|
||||
// Ensure all modules specify the same target
|
||||
if existing_target != module_target {
|
||||
return Err(TargetCompileError::ConflictingTargets {
|
||||
existing: existing_target.as_str().into(),
|
||||
conflicting: module_target.as_str().into(),
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure all modules with targets have the same package
|
||||
if let Some(ref existing_package) = target_package {
|
||||
if existing_package != &module_package {
|
||||
return Err(TargetCompileError::ConflictingPackages {
|
||||
target: module_target.as_str().into(),
|
||||
existing_package: existing_package.as_str().into(),
|
||||
conflicting_package: module_package.as_str().into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If a target is specified, retrieve it from the registry
|
||||
if let Some(target_name) = target_name {
|
||||
match targets::get(&target_name) {
|
||||
Some(target) => {
|
||||
// Target found in registry - store it in the compiled policy
|
||||
// We'll set a default effect schema here, but it will be updated in resolve_effect
|
||||
// once we determine which effect actually has rules defined
|
||||
let default_effect_schema = match target.effects.values().next() {
|
||||
Some(schema) => schema.clone(),
|
||||
None => {
|
||||
return Err(TargetCompileError::TargetNotFound(
|
||||
format!("Target '{}' has no effects defined", target_name)
|
||||
.as_str()
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
let target_info = TargetInfo {
|
||||
target,
|
||||
package: match target_package {
|
||||
Some(pkg) => pkg.as_str().into(),
|
||||
None => {
|
||||
return Err(TargetCompileError::TargetNotFound(
|
||||
format!("No package found for target '{}'", target_name)
|
||||
.as_str()
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
},
|
||||
effect_schema: default_effect_schema,
|
||||
effect_name: "".into(), // Will be updated in resolve_effect
|
||||
effect_path: "".into(), // Will be updated in resolve_effect
|
||||
};
|
||||
interpreter.compiled_policy_mut().target_info = Some(target_info);
|
||||
}
|
||||
None => {
|
||||
return Err(TargetCompileError::TargetNotFound(
|
||||
target_name.as_str().into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No target specified - this is an error when using compile_for_target
|
||||
return Err(TargetCompileError::NoTargetSpecified);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn resolve_effect(interpreter: &mut Interpreter) -> Result<(), TargetCompileError> {
|
||||
// Check if we have target info from resolve_target
|
||||
if let Some(ref target_info) = interpreter.compiled_policy.target_info {
|
||||
let target = &target_info.target;
|
||||
let package = &target_info.package;
|
||||
|
||||
let mut effects_with_rules = Vec::new();
|
||||
|
||||
// For each effect defined in the target, check if rules exist
|
||||
for effect_name in target.effects.keys() {
|
||||
// Rule keys are stored with "data." prefix in CompiledPolicy
|
||||
let expected_path = format!("data.{}.{}", package, effect_name);
|
||||
|
||||
// Disallow sub-paths for effects in rules.
|
||||
for rule_path in interpreter.compiled_policy.rules.keys() {
|
||||
if rule_path.starts_with(&expected_path) && rule_path.len() > expected_path.len() {
|
||||
// Sub-paths are not allowed for effects - they must be exact matches only
|
||||
// This prevents effect rules from being defined at deeper nested paths
|
||||
let all_effect_names: Vec<String> =
|
||||
target.effects.keys().map(|k| k.to_string()).collect();
|
||||
let formatted_names = format_effect_names(&all_effect_names);
|
||||
return Err(TargetCompileError::NoEffectRules {
|
||||
target_name: target.name.to_string().into(),
|
||||
package: package.to_string().into(),
|
||||
effect_names: formatted_names.as_str().into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Disallow sub-paths for effects in default_rules.
|
||||
for rule_path in interpreter.compiled_policy.default_rules.keys() {
|
||||
if rule_path.starts_with(&expected_path) && rule_path.len() > expected_path.len() {
|
||||
// Sub-paths are not allowed for effects - they must be exact matches only
|
||||
let all_effect_names: Vec<String> =
|
||||
target.effects.keys().map(|k| k.to_string()).collect();
|
||||
let formatted_names = format_effect_names(&all_effect_names);
|
||||
return Err(TargetCompileError::NoEffectRules {
|
||||
target_name: target.name.to_string().into(),
|
||||
package: package.to_string().into(),
|
||||
effect_names: formatted_names.as_str().into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check if rules exist at the expected path or any sub-path
|
||||
let mut has_rules = false;
|
||||
|
||||
// Check for exact match in rules
|
||||
if let Some(rules) = interpreter.compiled_policy.rules.get(&expected_path) {
|
||||
if !rules.is_empty() {
|
||||
has_rules = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for exact match in default_rules
|
||||
if !has_rules {
|
||||
if let Some(default_rules) = interpreter
|
||||
.compiled_policy
|
||||
.default_rules
|
||||
.get(&expected_path)
|
||||
{
|
||||
if !default_rules.is_empty() {
|
||||
has_rules = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if has_rules {
|
||||
effects_with_rules.push(effect_name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure exactly one effect has rules defined
|
||||
match effects_with_rules.len() {
|
||||
0 => {
|
||||
let all_effect_names: Vec<String> =
|
||||
target.effects.keys().map(|k| k.to_string()).collect();
|
||||
let formatted_names = format_effect_names(&all_effect_names);
|
||||
return Err(TargetCompileError::NoEffectRules {
|
||||
target_name: target.name.to_string().into(),
|
||||
package: package.to_string().into(),
|
||||
effect_names: formatted_names.as_str().into(),
|
||||
});
|
||||
}
|
||||
1 => {
|
||||
// Exactly one effect has rules - this is correct
|
||||
// Update the target info with the correct effect schema
|
||||
let effect_name = &effects_with_rules[0];
|
||||
let effect_schema = match target.effects.get(effect_name) {
|
||||
Some(schema) => schema.clone(),
|
||||
None => {
|
||||
// This should not happen since we got the effect_name from target.effects.keys()
|
||||
return Err(TargetCompileError::TargetNotFound(
|
||||
format!(
|
||||
"Effect '{}' not found in target '{}'",
|
||||
effect_name, target.name
|
||||
)
|
||||
.as_str()
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Update the target info with the correct effect schema, name, and path
|
||||
let expected_path = format!("data.{}.{}", package, effect_name);
|
||||
if let Some(ref mut target_info) = interpreter.compiled_policy_mut().target_info {
|
||||
target_info.effect_schema = effect_schema;
|
||||
target_info.effect_name = effect_name.as_ref().into();
|
||||
target_info.effect_path = expected_path.as_str().into();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(TargetCompileError::MultipleEffectRules {
|
||||
target_name: target.name.to_string().into(),
|
||||
effect_names: effects_with_rules.join(", ").as_str().into(),
|
||||
path: package.to_string().into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn resolve_and_apply_target(interpreter: &mut Interpreter) -> Result<(), TargetCompileError> {
|
||||
// Resolve the target first
|
||||
resolve_target(interpreter)?;
|
||||
|
||||
// Then resolve the effect
|
||||
resolve_effect(interpreter)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user