Skip to content

Commit 55752dc

Browse files
authored
Restructure the decode module (#2607)
1 parent bd28275 commit 55752dc

File tree

3 files changed

+288
-162
lines changed

3 files changed

+288
-162
lines changed

src/cfnlint/decode/__init__.py

+7-162
Original file line numberDiff line numberDiff line change
@@ -2,166 +2,11 @@
22
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
SPDX-License-Identifier: MIT-0
44
"""
5-
import logging
6-
from ast import Call
7-
from json.decoder import JSONDecodeError
8-
from typing import Callable, List, Optional, Tuple, Union
95

10-
from yaml import YAMLError
11-
from yaml.parser import ParserError
12-
from yaml.scanner import ScannerError
13-
14-
from cfnlint.decode import cfn_json, cfn_yaml
15-
from cfnlint.rules import Match, ParseError
16-
17-
LOGGER = logging.getLogger(__name__)
18-
19-
Matches = List[Match]
20-
Decode = Tuple[Union[str, None], Matches]
21-
22-
23-
def decode_str(s: str) -> Decode:
24-
"""Decode the string s into an object."""
25-
return _decode(cfn_yaml.loads, cfn_json.loads, s, None)
26-
27-
28-
def decode(filename: str) -> Decode:
29-
"""Decode filename into an object."""
30-
return _decode(cfn_yaml.load, cfn_json.load, filename, filename)
31-
32-
33-
def _decode(
34-
yaml_f: Callable, json_f: Callable, payload: str, filename: Optional[str]
35-
) -> Decode:
36-
"""Decode payload using yaml_f and json_f, using filename for log output."""
37-
template = None
38-
matches = []
39-
try:
40-
template = yaml_f(payload)
41-
except IOError as e:
42-
if e.errno == 2:
43-
LOGGER.error("Template file not found: %s", filename)
44-
matches.append(
45-
create_match_file_error(
46-
filename, f"Template file not found: {filename}"
47-
)
48-
)
49-
elif e.errno == 21:
50-
LOGGER.error("Template references a directory, not a file: %s", filename)
51-
matches.append(
52-
create_match_file_error(
53-
filename, "Template references a directory, not a file: {filename}"
54-
)
55-
)
56-
elif e.errno == 13:
57-
LOGGER.error("Permission denied when accessing template file: %s", filename)
58-
matches.append(
59-
create_match_file_error(
60-
filename,
61-
"Permission denied when accessing template file: {filename}",
62-
)
63-
)
64-
65-
if matches:
66-
return (None, matches)
67-
except UnicodeDecodeError as _:
68-
LOGGER.error("Cannot read file contents: %s", filename)
69-
matches.append(
70-
create_match_file_error(filename, "Cannot read file contents: {filename}")
71-
)
72-
except cfn_yaml.CfnParseError as err:
73-
matches = err.matches
74-
except ParserError as err:
75-
matches = [create_match_yaml_parser_error(err, filename)]
76-
except ScannerError as err:
77-
if err.problem and (
78-
err.problem
79-
in [
80-
"found character '\\t' that cannot start any token",
81-
"found unknown escape character",
82-
]
83-
or err.problem.startswith("found unknown escape character")
84-
):
85-
try:
86-
template = json_f(payload)
87-
except cfn_json.JSONDecodeError as json_errs:
88-
for json_err in json_errs.matches:
89-
json_err.filename = filename
90-
matches = json_errs.matches
91-
except JSONDecodeError as json_err:
92-
if hasattr(json_err, "msg"):
93-
if (
94-
json_err.msg == "No JSON object could be decoded"
95-
): # pylint: disable=no-member
96-
matches = [create_match_yaml_parser_error(err, filename)]
97-
else:
98-
matches = [create_match_json_parser_error(json_err, filename)]
99-
if hasattr(json_err, "msg"):
100-
if json_err.msg == "Expecting value": # pylint: disable=no-member
101-
matches = [create_match_yaml_parser_error(err, filename)]
102-
else:
103-
matches = [create_match_json_parser_error(json_err, filename)]
104-
except Exception as json_err: # pylint: disable=W0703
105-
LOGGER.error("Template %s is malformed: %s", filename, err.problem)
106-
LOGGER.error(
107-
"Tried to parse %s as JSON but got error: %s",
108-
filename,
109-
str(json_err),
110-
)
111-
return (
112-
None,
113-
[
114-
create_match_file_error(
115-
filename,
116-
f"Tried to parse {filename} as JSON but got error: {str(json_err)}",
117-
)
118-
],
119-
)
120-
else:
121-
matches = [create_match_yaml_parser_error(err, filename)]
122-
except YAMLError as err:
123-
matches = [create_match_file_error(filename, err)]
124-
125-
if not isinstance(template, dict) and not matches:
126-
# Template isn't a dict which means nearly nothing will work
127-
matches = [
128-
Match(
129-
1,
130-
1,
131-
1,
132-
1,
133-
filename,
134-
ParseError(),
135-
message="Template needs to be an object.",
136-
)
137-
]
138-
return (template, matches)
139-
140-
141-
def create_match_yaml_parser_error(parser_error, filename):
142-
"""Create a Match for a parser error"""
143-
lineno = parser_error.problem_mark.line + 1
144-
colno = parser_error.problem_mark.column + 1
145-
msg = parser_error.problem
146-
return Match(lineno, colno, lineno, colno + 1, filename, ParseError(), message=msg)
147-
148-
149-
def create_match_file_error(filename, msg):
150-
"""Create a Match for a parser error"""
151-
return Match(
152-
linenumber=1,
153-
columnnumber=1,
154-
linenumberend=1,
155-
columnnumberend=2,
156-
filename=filename,
157-
rule=ParseError(),
158-
message=msg,
159-
)
160-
161-
162-
def create_match_json_parser_error(parser_error, filename):
163-
"""Create a Match for a parser error"""
164-
lineno = parser_error.lineno
165-
colno = parser_error.colno
166-
msg = parser_error.msg
167-
return Match(lineno, colno, lineno, colno + 1, filename, ParseError(), message=msg)
6+
from cfnlint.decode.decode import (
7+
create_match_file_error,
8+
create_match_json_parser_error,
9+
create_match_yaml_parser_error,
10+
decode,
11+
decode_str,
12+
)

src/cfnlint/decode/decode.py

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: MIT-0
4+
"""
5+
import logging
6+
from json.decoder import JSONDecodeError
7+
from typing import Callable, List, Optional, Tuple, Union
8+
9+
from yaml import YAMLError
10+
from yaml.parser import ParserError
11+
from yaml.scanner import ScannerError
12+
13+
from cfnlint.decode import cfn_json, cfn_yaml
14+
from cfnlint.rules import Match, ParseError
15+
16+
LOGGER = logging.getLogger(__name__)
17+
18+
Matches = List[Match]
19+
Decode = Tuple[Union[str, None], Matches]
20+
21+
22+
def decode_str(s: str) -> Decode:
23+
"""Decode the string s into an object."""
24+
return _decode(cfn_yaml.loads, cfn_json.loads, s, None)
25+
26+
27+
def decode(filename: str) -> Decode:
28+
"""Decode filename into an object."""
29+
return _decode(cfn_yaml.load, cfn_json.load, filename, filename)
30+
31+
32+
def _decode(
33+
yaml_f: Callable, json_f: Callable, payload: str, filename: Optional[str]
34+
) -> Decode:
35+
"""Decode payload using yaml_f and json_f, using filename for log output."""
36+
template = None
37+
matches = []
38+
try:
39+
template = yaml_f(payload)
40+
except IOError as e:
41+
if e.errno == 2:
42+
LOGGER.error("Template file not found: %s", filename)
43+
matches.append(
44+
create_match_file_error(
45+
filename, f"Template file not found: {filename}"
46+
)
47+
)
48+
elif e.errno == 21:
49+
LOGGER.error("Template references a directory, not a file: %s", filename)
50+
matches.append(
51+
create_match_file_error(
52+
filename, f"Template references a directory, not a file: {filename}"
53+
)
54+
)
55+
elif e.errno == 13:
56+
LOGGER.error("Permission denied when accessing template file: %s", filename)
57+
matches.append(
58+
create_match_file_error(
59+
filename,
60+
f"Permission denied when accessing template file: {filename}",
61+
)
62+
)
63+
64+
if matches:
65+
return (None, matches)
66+
except UnicodeDecodeError as _:
67+
LOGGER.error("Cannot read file contents: %s", filename)
68+
matches.append(
69+
create_match_file_error(filename, "Cannot read file contents: {filename}")
70+
)
71+
except cfn_yaml.CfnParseError as err:
72+
matches = err.matches
73+
except ParserError as err:
74+
matches = [create_match_yaml_parser_error(err, filename)]
75+
except ScannerError as err:
76+
if err.problem and (
77+
err.problem
78+
in [
79+
"found character '\\t' that cannot start any token",
80+
"found unknown escape character",
81+
]
82+
or err.problem.startswith("found unknown escape character")
83+
):
84+
try:
85+
template = json_f(payload)
86+
except cfn_json.JSONDecodeError as json_errs:
87+
for json_err in json_errs.matches:
88+
json_err.filename = filename
89+
matches = json_errs.matches
90+
except JSONDecodeError as json_err:
91+
if hasattr(json_err, "msg"):
92+
if json_err.msg in [
93+
"No JSON object could be decoded",
94+
"Expecting value",
95+
]: # pylint: disable=no-member
96+
matches = [create_match_yaml_parser_error(err, filename)]
97+
else:
98+
matches = [create_match_json_parser_error(json_err, filename)]
99+
except Exception as json_err: # pylint: disable=W0703
100+
LOGGER.error("Template %s is malformed: %s", filename, err.problem)
101+
LOGGER.error(
102+
"Tried to parse %s as JSON but got error: %s",
103+
filename,
104+
str(json_err),
105+
)
106+
return (
107+
None,
108+
[
109+
create_match_file_error(
110+
filename,
111+
f"Tried to parse {filename} as JSON but got error: {str(json_err)}",
112+
)
113+
],
114+
)
115+
else:
116+
matches = [create_match_yaml_parser_error(err, filename)]
117+
except YAMLError as err:
118+
matches = [create_match_file_error(filename, str(err))]
119+
120+
if not isinstance(template, dict) and not matches:
121+
# Template isn't a dict which means nearly nothing will work
122+
matches = [
123+
Match(
124+
1,
125+
1,
126+
1,
127+
1,
128+
filename,
129+
ParseError(),
130+
message="Template needs to be an object.",
131+
)
132+
]
133+
return (template, matches)
134+
135+
136+
def create_match_yaml_parser_error(parser_error, filename):
137+
"""Create a Match for a parser error"""
138+
lineno = parser_error.problem_mark.line + 1
139+
colno = parser_error.problem_mark.column + 1
140+
msg = parser_error.problem
141+
return Match(lineno, colno, lineno, colno + 1, filename, ParseError(), message=msg)
142+
143+
144+
def create_match_file_error(filename, msg):
145+
"""Create a Match for a parser error"""
146+
return Match(
147+
linenumber=1,
148+
columnnumber=1,
149+
linenumberend=1,
150+
columnnumberend=2,
151+
filename=filename,
152+
rule=ParseError(),
153+
message=msg,
154+
)
155+
156+
157+
def create_match_json_parser_error(parser_error, filename):
158+
"""Create a Match for a parser error"""
159+
lineno = parser_error.lineno
160+
colno = parser_error.colno
161+
msg = parser_error.msg
162+
return Match(lineno, colno, lineno, colno + 1, filename, ParseError(), message=msg)

0 commit comments

Comments
 (0)