Skip to content

Commit 9bd2a20

Browse files
authored
Default fn validator context will be not strict type checking (#3386)
* Default fn validator context will be not strict type checking * Update getatt testing as well
1 parent 3dfb0e4 commit 9bd2a20

File tree

7 files changed

+78
-11
lines changed

7 files changed

+78
-11
lines changed

src/cfnlint/helpers.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,9 @@ def _translate_types(types: Sequence[str]) -> list[str]:
564564

565565

566566
def is_types_compatible(
567-
source_types: str | Sequence[str], destination_types: str | Sequence[str]
567+
source_types: str | Sequence[str],
568+
destination_types: str | Sequence[str],
569+
strict_types: bool = False,
568570
) -> bool:
569571
"""
570572
Validate if desination types are compatible with source types.
@@ -576,7 +578,8 @@ def is_types_compatible(
576578
Returns:
577579
bool: If any type of source is compatible with any type in the destination
578580
"""
579-
source_types = _translate_types(ensure_list(source_types))
581+
if not strict_types:
582+
source_types = _translate_types(ensure_list(source_types))
580583
destination_types = ensure_list(destination_types)
581584

582585
if any(schema_type in source_types for schema_type in destination_types):

src/cfnlint/jsonschema/_keywords_cfn.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ def validate(
5959
for (index, item), subschema in zip(enumerate(instance), s):
6060
yield from validator.evolve(
6161
context=validator.context.evolve(
62-
functions=subschema.get("functions", [])
62+
functions=subschema.get("functions", []),
63+
strict_types=False,
6364
),
6465
).descend(
6566
instance=item,
@@ -69,7 +70,10 @@ def validate(
6970
else:
7071
for index, item in enumerate(instance):
7172
yield from validator.evolve(
72-
context=validator.context.evolve(functions=s.get("functions", [])),
73+
context=validator.context.evolve(
74+
functions=s.get("functions", []),
75+
strict_types=False,
76+
),
7377
).descend(
7478
instance=item,
7579
schema=s.get("schema", {}),

src/cfnlint/rules/functions/GetAtt.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ def _resolve_getatt(
132132

133133
types = ensure_list(s.get("type"))
134134

135-
if is_types_compatible(types, schema_types):
135+
if is_types_compatible(
136+
types, schema_types, validator.context.strict_types
137+
):
136138
continue
137139

138140
reprs = ", ".join(repr(type) for type in types)

src/cfnlint/rules/functions/Sub.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def _validate_string(
9090
path=validator.context.path.descend(
9191
path=key,
9292
),
93+
strict_types=True,
9394
),
9495
function_filter=validator.function_filter.evolve(
9596
add_cfn_lint_keyword=False,

test/unit/module/jsonschema/test_keywords_cfn.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from typing import List
99

1010
from cfnlint.context import Context
11-
from cfnlint.jsonschema._keywords_cfn import cfn_type
11+
from cfnlint.jsonschema._keywords_cfn import FnItems, cfn_type
1212
from cfnlint.jsonschema.exceptions import UnknownType
1313
from cfnlint.jsonschema.validators import CfnTemplateValidator
1414

@@ -136,3 +136,18 @@ def test_cfn_type_failure(self):
136136
self.assertIn(
137137
"Unknown type 'bar' for validator with schema", str(err.exception)
138138
)
139+
140+
141+
class TestFnItems(Base):
142+
143+
def test_change_type(self):
144+
schema = [
145+
{
146+
"schema": {"type": "string"},
147+
},
148+
]
149+
validator = CfnTemplateValidator({})
150+
151+
items = list(FnItems().validate(validator, schema, [1], {}))
152+
153+
self.assertListEqual(items, [])

test/unit/rules/functions/test_getatt.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,32 @@ def validate(self, validator, s, instance, schema):
158158
"Valid GetAtt with integer to string",
159159
{"Fn::GetAtt": "MyCodePipeline.Version"},
160160
{"type": ["integer"]},
161-
{},
161+
{
162+
"strict_types": False,
163+
},
162164
{},
163165
[],
164166
),
167+
(
168+
"Invalid GetAtt with integer to string",
169+
{"Fn::GetAtt": "MyCodePipeline.Version"},
170+
{"type": ["integer"]},
171+
{
172+
"strict_types": True,
173+
},
174+
{},
175+
[
176+
ValidationError(
177+
(
178+
"{'Fn::GetAtt': 'MyCodePipeline.Version'} "
179+
"is not of type 'integer'"
180+
),
181+
path=deque(["Fn::GetAtt"]),
182+
schema_path=deque(["type"]),
183+
validator="fn_getatt",
184+
)
185+
],
186+
),
165187
(
166188
"Valid GetAtt with one good response type",
167189
{"Fn::GetAtt": "MyBucket.Arn"},
@@ -244,4 +266,9 @@ def test_validate(
244266
rule.child_rules = child_rules
245267
validator = CfnTemplateValidator({}, context=context, cfn=cfn)
246268
errs = list(rule.fn_getatt(validator, schema, instance, {}))
269+
270+
for err in errs:
271+
print(err.validator)
272+
print(err.path)
273+
print(err.schema_path)
247274
assert errs == expected, f"Test {name!r} got {errs!r}"

test/unit/rules/functions/test_sub.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def cfn():
2929
"Resources": {
3030
"MyResource": {"Type": "AWS::S3::Bucket"},
3131
"MySimpleAd": {"Type": "AWS::DirectoryService::SimpleAD"},
32+
"MyPolicy": {"Type": "AWS::IAM::ManagedPolicy"},
3233
},
3334
},
3435
regions=["us-east-1"],
@@ -95,7 +96,7 @@ def context(cfn):
9596
ValidationError(
9697
(
9798
"'bar' is not one of ["
98-
"'MyResource', 'MySimpleAd', 'AWS::AccountId', "
99+
"'MyResource', 'MySimpleAd', 'MyPolicy', 'AWS::AccountId', "
99100
"'AWS::NoValue', 'AWS::NotificationARNs', "
100101
"'AWS::Partition', 'AWS::Region', "
101102
"'AWS::StackId', 'AWS::StackName', 'AWS::URLSuffix']"
@@ -114,7 +115,7 @@ def context(cfn):
114115
ValidationError(
115116
(
116117
"'foo' is not one of ["
117-
"'MyResource', 'MySimpleAd', 'AWS::AccountId', "
118+
"'MyResource', 'MySimpleAd', 'MyPolicy', 'AWS::AccountId', "
118119
"'AWS::NoValue', 'AWS::NotificationARNs', "
119120
"'AWS::Partition', 'AWS::Region', "
120121
"'AWS::StackId', 'AWS::StackName', 'AWS::URLSuffix']"
@@ -228,7 +229,7 @@ def context(cfn):
228229
{"type": "string"},
229230
[
230231
ValidationError(
231-
"'Foo' is not one of ['MyResource', 'MySimpleAd']",
232+
"'Foo' is not one of ['MyResource', 'MySimpleAd', 'MyPolicy']",
232233
path=deque(["Fn::Sub"]),
233234
schema_path=deque([]),
234235
validator="fn_sub",
@@ -241,7 +242,21 @@ def context(cfn):
241242
{"type": "string"},
242243
[
243244
ValidationError(
244-
("'MySimpleAd.DnsIpAddresses' is not " "of type 'string'"),
245+
("'MySimpleAd.DnsIpAddresses' is not of type 'string'"),
246+
instance="MySimpleAd.DnsIpAddresses",
247+
path=deque(["Fn::Sub"]),
248+
schema_path=deque([]),
249+
validator="fn_sub",
250+
),
251+
],
252+
),
253+
(
254+
"Invalid Fn::Sub with a GetAtt to an integer",
255+
{"Fn::Sub": "${MyPolicy.AttachmentCount}"},
256+
{"type": "string"},
257+
[
258+
ValidationError(
259+
("'MyPolicy.AttachmentCount' is not of type 'string'"),
245260
instance="MySimpleAd.DnsIpAddresses",
246261
path=deque(["Fn::Sub"]),
247262
schema_path=deque([]),

0 commit comments

Comments
 (0)