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>;
pub fn infer_resource_type(
interpreter: &mut Interpreter,
) -> Result<InferredResourceTypes, TargetCompileError> {
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();
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)?;
}
}
let compiled_policy = Rc::make_mut(&mut interpreter.compiled_policy);
compiled_policy.inferred_resource_types = Some(result.clone());
Ok(result)
} else {
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)?;
}
}
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; }
}
}
if let Some(resource_type) = found_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(());
}
let default_schema = get_validated_default_schema(target, resource_selector)?;
result.insert(query.clone(), (resource_type, default_schema));
} else {
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> {
if let Expr::BoolExpr {
op: BoolOp::Eq,
lhs,
rhs,
..
} = expr
{
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);
}
}
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);
}
}
}
None
}
fn extract_input_field_access(expr: &Expr, _expected_field: &str) -> Option<String> {
use crate::value::Value;
match expr {
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());
}
}
}
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
}
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
}
}
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_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(),
))
}
}
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,
..
} => {
if properties.contains_key(resource_selector) {
return Ok(());
}
if additional_properties.is_some() {
return Ok(());
}
Err(TargetCompileError::IncompatibleDefaultSchema(
format!(
"Default resource schema must either have additional properties enabled or contain a '{}' property",
resource_selector
).into()
))
}
_ => {
Err(TargetCompileError::InvalidDefaultSchemaType(
"Default resource schema must be an object type".into(),
))
}
}
}