Skip to content

Conversation

vicheey
Copy link
Contributor

@vicheey vicheey commented Aug 22, 2025

Issue #, if available

None

Description of changes

This PR introduces a comprehensive validation framework for SAM resource properties, and enhancing the translator's ability to catch configuration errors early in the deployment process.

  • Enhanced validation logic in samtranslator/model/init.py
  • Updated SAM resources in samtranslator/model/sam_resources.py to integrate
    validation
  • Comprehensive test coverage in tests/test_model.py

Description of how you validated changes

  • Added extensive test suite covering validation scenarios
  • All existing tests continue to pass

Checklist

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@vicheey vicheey requested a review from a team as a code owner August 22, 2025 21:16
@vicheey
Copy link
Contributor Author

vicheey commented Aug 22, 2025

The Tests failed because of lower coverage percentage. (94.82% instead of 95%). Added validation logic push the overall total line increase and lower the coverage percentage.

Low coverage files:
• samtranslator/validator/validator.py - 27% coverage
• samtranslator/model/init.py - 69% coverage
• samtranslator/model/naming.py - 71% coverage
• samtranslator/region_configuration.py - 79% coverage

Removing deprecated and unused samtranslator/validator/validator.py

@vicheey
Copy link
Contributor Author

vicheey commented Aug 22, 2025

Current usage analysis:
• No active imports of SamTemplateValidator in any source
files
• No test files directly testing SamTemplateValidator

What IS being used:
samtranslator.validator.value_validator.sam_expect - Used extensively across 12+ files
• Direct jsonschema.validate() calls in tests (instead of the deprecated SAM wrapper)

However, we cannot delete this file yet because this public facing interface might be used by package consuming this package.
I added the samtranslator.validator.validator.py to test coverage ignore list.

--------------------------------
TOTAL   9640    314   3402    220    95%

Required test coverage of 95% reached. Total coverage: 95.28%

=============== 4118 passed in 86.15s (0:01:26) ================

@vicheey vicheey force-pushed the validation-framework-extraction branch from 188db2a to b52c322 Compare August 22, 2025 23:50
elif (
rule_type == ValidationRule.CONDITIONAL_REQUIREMENT
and self._get_property_value(properties[0], validated_model) is not None
and self._get_property_value(properties[1], validated_model) is None
Copy link
Member

Choose a reason for hiding this comment

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

you can only set 2 prop in one rule?


error_messages = []

for rule_type, properties in rules:
Copy link
Member

Choose a reason for hiding this comment

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

Can we do something like [ rule.run() for rules in self.__validation_rules__] so we can save all the if /elif below?

error_messages = []

for rule_type, properties in rules:
present = [prop for prop in properties if self._get_property_value(prop, validated_model) is not None]
Copy link
Member

Choose a reason for hiding this comment

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

is there a better name for present? It's hard to understnad

prop_names = [f"'{p}'" for p in present]
error_messages.append(f"Cannot specify {self._combine_string(prop_names)} together.")

elif rule_type == ValidationRule.MUTUALLY_INCLUSIVE:
Copy link
Member

Choose a reason for hiding this comment

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

Is this like an antipattern? User need to constantly set 2 or more params to achieve a single action. Is there an example for this MUTUALLY_INCLUSIVE? Can we optimize it to be CONDITIONAL_REQUIREMENT?

@@ -582,6 +687,71 @@ def _check_tag(self, reserved_tag_name, tags): # type: ignore[no-untyped-def]
"input.",
)

def validate_before_transform(self, schema_class: Optional[Type[RT]], collect_all_errors: bool = False) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

should collect_all_errors and schema_class be a class attr? So it's like this.collect_all_errors and this. schema_class

raise InvalidResourceException(self.logical_id, "\n".join(error_messages))

def _combine_string(self, words: List[str]) -> str:
return ", ".join(words[:-1]) + (" and " + words[-1] if len(words) > 1 else words[0] if words else "")
Copy link
Member

Choose a reason for hiding this comment

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

why? why not ", ".join(words)

@@ -553,6 +624,40 @@ def _construct_tag_list(
# customer's knowledge.
return get_tag_list(sam_tag) + get_tag_list(additional_tags) + get_tag_list(tags)

@staticmethod
def propagate_tags_combine(
Copy link
Member

Choose a reason for hiding this comment

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

This is not mentioned in PR title or description

def _combine_string(self, words: List[str]) -> str:
return ", ".join(words[:-1]) + (" and " + words[-1] if len(words) > 1 else words[0] if words else "")

def _get_property_value(self, prop: str, validated_model: Any = None) -> Any:
Copy link
Member

@roger-zhangg roger-zhangg Aug 26, 2025

Choose a reason for hiding this comment

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

I would suggest to update validate_properties_and_return_model return a custom dict that allow you to directly use validated_model["a.b.c"]. Like

class DotDict(dict):
    def __getitem__(self, path):
        value = self
        for key in path.split('.'):
            value = dict.__getitem__(value, key)
        return value

validated_model["a.b.c"] would have a much better readability than _get_property_value("a.b.c", validated_model)

class Resource(self.TestResource):
__validation_rules__ = [
(self.ValidationRule.CONDITIONAL_REQUIREMENT, ["ConditionalVar1", "ConditionalVar2"]),
(self.ValidationRule.CONDITIONAL_REQUIREMENT, ["ConditionalVar2", "ConditionalVar3"]),
Copy link
Member

@roger-zhangg roger-zhangg Aug 26, 2025

Choose a reason for hiding this comment

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

from actually seeing this, does Rule('ConditionalVar2').require('ConditionalVar1') sounds better?
This can even extend to like Rule('ConditionalVar2').require('ConditionalVar1==123')

self.ValidationRule.CONDITIONAL_REQUIREMENT,
["NestedSetting1.NestedVar1", "NestedSetting2.NestedVar2"],
),
(self.ValidationRule.MUTUALLY_EXCLUSIVE, ["ExclusiveVar1", "ExclusiveVar2"]),
Copy link
Member

@roger-zhangg roger-zhangg Aug 26, 2025

Choose a reason for hiding this comment

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

Rule('ConditionalVar2').reject('ConditionalVar1')

["NestedSetting1.NestedVar1", "NestedSetting2.NestedVar1"],
),
(self.ValidationRule.MUTUALLY_EXCLUSIVE, ["NestedSetting1.NestedVar2", "ExclusiveVar3"]),
(self.ValidationRule.MUTUALLY_INCLUSIVE, ["InclusiveVar1", "ExclusiveVar1"]),
Copy link
Member

@roger-zhangg roger-zhangg Aug 26, 2025

Choose a reason for hiding this comment

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

Rule('ConditionalVar1', 'ConditionalVar2')
Just some thoughts

if not hasattr(self, "__validation_rules__"):
return

rules = self.__validation_rules__
Copy link
Member

@roger-zhangg roger-zhangg Aug 26, 2025

Choose a reason for hiding this comment

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

One concern, currently there's no type hint on self. __validation_rules__, so we need to look at document/ previous implementation everytime when we need to use this function

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.

2 participants