Skip to content

Commit d0b46d5

Browse files
authored
CM-25543 - Implement proper handling of errors in printers (#151)
1 parent d03b42f commit d0b46d5

File tree

7 files changed

+78
-40
lines changed

7 files changed

+78
-40
lines changed

cycode/cli/code_scanner.py

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def scan_repository(context: click.Context, path: str, branch: str) -> None:
9090
perform_pre_scan_documents_actions(context, scan_type, documents_to_scan, is_git_diff=False)
9191

9292
logger.debug('Found all relevant files for scanning %s', {'path': path, 'branch': branch})
93-
return scan_documents(
93+
scan_documents(
9494
context, documents_to_scan, is_git_diff=False, scan_parameters=get_scan_parameters(context, path)
9595
)
9696
except Exception as e:
@@ -420,6 +420,16 @@ def scan_documents(
420420
) -> None:
421421
progress_bar = context.obj['progress_bar']
422422

423+
if not documents_to_scan:
424+
progress_bar.stop()
425+
ConsolePrinter(context).print_error(
426+
CliError(
427+
code='no_relevant_files',
428+
message='Error: The scan could not be completed - relevant files to scan are not found.',
429+
)
430+
)
431+
return
432+
423433
scan_batch_thread_func = _get_scan_documents_thread_func(context, is_git_diff, is_commit_range, scan_parameters)
424434
errors, local_scan_results = run_parallel_batched_scan(
425435
scan_batch_thread_func, documents_to_scan, progress_bar=progress_bar
@@ -430,25 +440,7 @@ def scan_documents(
430440
progress_bar.stop()
431441

432442
set_issue_detected_by_scan_results(context, local_scan_results)
433-
print_results(context, local_scan_results)
434-
435-
if not errors:
436-
return
437-
438-
if context.obj['output'] == 'json':
439-
# TODO(MarshalX): we can't just print JSON formatted errors here
440-
# because we should return only one root json structure per scan
441-
# could be added later to "print_results" function if we wish to display detailed errors in UI
442-
return
443-
444-
click.secho(
445-
'Unfortunately, Cycode was unable to complete the full scan. '
446-
'Please note that not all results may be available:',
447-
fg='red',
448-
)
449-
for scan_id, error in errors.items():
450-
click.echo(f'- {scan_id}: ', nl=False)
451-
ConsolePrinter(context).print_error(error)
443+
print_results(context, local_scan_results, errors)
452444

453445

454446
def scan_commit_range_documents(
@@ -506,6 +498,7 @@ def scan_commit_range_documents(
506498
progress_bar.update(ProgressBarSection.GENERATE_REPORT)
507499
progress_bar.stop()
508500

501+
# errors will be handled with try-except block; printing will not occur on errors
509502
print_results(context, [local_scan_result])
510503

511504
scan_completed = True
@@ -693,9 +686,11 @@ def print_debug_scan_details(scan_details_response: 'ScanDetailsResponse') -> No
693686
logger.debug(f'Scan message: {scan_details_response.message}')
694687

695688

696-
def print_results(context: click.Context, local_scan_results: List[LocalScanResult]) -> None:
689+
def print_results(
690+
context: click.Context, local_scan_results: List[LocalScanResult], errors: Optional[Dict[str, 'CliError']] = None
691+
) -> None:
697692
printer = ConsolePrinter(context)
698-
printer.print_scan_results(local_scan_results)
693+
printer.print_scan_results(local_scan_results, errors)
699694

700695

701696
def get_document_detections(

cycode/cli/printers/console_printer.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import TYPE_CHECKING, ClassVar, Dict, List
1+
from typing import TYPE_CHECKING, ClassVar, Dict, List, Optional
22

33
import click
44

@@ -33,9 +33,11 @@ def __init__(self, context: click.Context) -> None:
3333
if self._printer_class is None:
3434
raise CycodeError(f'"{self.output_type}" output type is not supported.')
3535

36-
def print_scan_results(self, local_scan_results: List['LocalScanResult']) -> None:
36+
def print_scan_results(
37+
self, local_scan_results: List['LocalScanResult'], errors: Optional[Dict[str, 'CliError']] = None
38+
) -> None:
3739
printer = self._get_scan_printer()
38-
printer.print_scan_results(local_scan_results)
40+
printer.print_scan_results(local_scan_results, errors)
3941

4042
def _get_scan_printer(self) -> 'PrinterBase':
4143
printer_class = self._printer_class

cycode/cli/printers/json_printer.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from typing import TYPE_CHECKING, List
2+
from typing import TYPE_CHECKING, Dict, List, Optional
33

44
import click
55

@@ -15,27 +15,35 @@ class JsonPrinter(PrinterBase):
1515
def print_result(self, result: CliResult) -> None:
1616
result = {'result': result.success, 'message': result.message}
1717

18-
click.secho(self.get_data_json(result))
18+
click.echo(self.get_data_json(result))
1919

2020
def print_error(self, error: CliError) -> None:
2121
result = {'error': error.code, 'message': error.message}
2222

23-
click.secho(self.get_data_json(result))
23+
click.echo(self.get_data_json(result))
2424

25-
def print_scan_results(self, local_scan_results: List['LocalScanResult']) -> None:
25+
def print_scan_results(
26+
self, local_scan_results: List['LocalScanResult'], errors: Optional[Dict[str, 'CliError']] = None
27+
) -> None:
2628
detections = []
2729
for local_scan_result in local_scan_results:
2830
for document_detections in local_scan_result.document_detections:
2931
detections.extend(document_detections.detections)
3032

3133
detections_dict = DetectionSchema(many=True).dump(detections)
3234

33-
click.secho(self._get_json_scan_result(detections_dict))
35+
inlined_errors = []
36+
if errors:
37+
# FIXME(MarshalX): we don't care about scan IDs in JSON output due to clumsy JSON root structure
38+
inlined_errors = [err._asdict() for err in errors.values()]
3439

35-
def _get_json_scan_result(self, detections: dict) -> str:
40+
click.echo(self._get_json_scan_result(detections_dict, inlined_errors))
41+
42+
def _get_json_scan_result(self, detections: dict, errors: List[dict]) -> str:
3643
result = {
3744
'scan_id': 'DEPRECATED', # FIXME(MarshalX): we need change JSON struct to support multiple scan results
3845
'detections': detections,
46+
'errors': errors,
3947
}
4048

4149
return self.get_data_json(result)

cycode/cli/printers/printer_base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from abc import ABC, abstractmethod
2-
from typing import TYPE_CHECKING, List
2+
from typing import TYPE_CHECKING, Dict, List, Optional
33

44
import click
55

@@ -18,7 +18,9 @@ def __init__(self, context: click.Context) -> None:
1818
self.context = context
1919

2020
@abstractmethod
21-
def print_scan_results(self, local_scan_results: List['LocalScanResult']) -> None:
21+
def print_scan_results(
22+
self, local_scan_results: List['LocalScanResult'], errors: Optional[Dict[str, 'CliError']] = None
23+
) -> None:
2224
pass
2325

2426
@abstractmethod

cycode/cli/printers/tables/table_printer_base.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import abc
2-
from typing import TYPE_CHECKING, List
2+
from typing import TYPE_CHECKING, Dict, List, Optional
33

44
import click
55

@@ -24,13 +24,27 @@ def print_result(self, result: CliResult) -> None:
2424
def print_error(self, error: CliError) -> None:
2525
TextPrinter(self.context).print_error(error)
2626

27-
def print_scan_results(self, local_scan_results: List['LocalScanResult']) -> None:
28-
if all(result.issue_detected == 0 for result in local_scan_results):
27+
def print_scan_results(
28+
self, local_scan_results: List['LocalScanResult'], errors: Optional[Dict[str, 'CliError']] = None
29+
) -> None:
30+
if not errors and all(result.issue_detected == 0 for result in local_scan_results):
2931
click.secho('Good job! No issues were found!!! 👏👏👏', fg=self.GREEN_COLOR_NAME)
3032
return
3133

3234
self._print_results(local_scan_results)
3335

36+
if not errors:
37+
return
38+
39+
click.secho(
40+
'Unfortunately, Cycode was unable to complete the full scan. '
41+
'Please note that not all results may be available:',
42+
fg='red',
43+
)
44+
for scan_id, error in errors.items():
45+
click.echo(f'- {scan_id}: ', nl=False)
46+
self.print_error(error)
47+
3448
def _is_git_repository(self) -> bool:
3549
return self.context.obj.get('remote_url') is not None
3650

@@ -40,4 +54,5 @@ def _print_results(self, local_scan_results: List['LocalScanResult']) -> None:
4054

4155
@staticmethod
4256
def _print_table(table: 'Table') -> None:
43-
click.echo(table.get_table().draw())
57+
if table.get_rows():
58+
click.echo(table.get_table().draw())

cycode/cli/printers/text_printer.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import math
2-
from typing import TYPE_CHECKING, List, Optional
2+
from typing import TYPE_CHECKING, Dict, List, Optional
33

44
import click
55

@@ -30,8 +30,10 @@ def print_result(self, result: CliResult) -> None:
3030
def print_error(self, error: CliError) -> None:
3131
click.secho(error.message, fg=self.RED_COLOR_NAME)
3232

33-
def print_scan_results(self, local_scan_results: List['LocalScanResult']) -> None:
34-
if all(result.issue_detected == 0 for result in local_scan_results):
33+
def print_scan_results(
34+
self, local_scan_results: List['LocalScanResult'], errors: Optional[Dict[str, 'CliError']] = None
35+
) -> None:
36+
if not errors and all(result.issue_detected == 0 for result in local_scan_results):
3537
click.secho('Good job! No issues were found!!! 👏👏👏', fg=self.GREEN_COLOR_NAME)
3638
return
3739

@@ -41,6 +43,18 @@ def print_scan_results(self, local_scan_results: List['LocalScanResult']) -> Non
4143
document_detections, local_scan_result.scan_id, local_scan_result.report_url
4244
)
4345

46+
if not errors:
47+
return
48+
49+
click.secho(
50+
'Unfortunately, Cycode was unable to complete the full scan. '
51+
'Please note that not all results may be available:',
52+
fg='red',
53+
)
54+
for scan_id, error in errors.items():
55+
click.echo(f'- {scan_id}: ', nl=False)
56+
self.print_error(error)
57+
4458
def _print_document_detections(
4559
self, document_detections: DocumentDetections, scan_id: str, report_url: Optional[str]
4660
) -> None:

cycode/cli/utils/progress_bar.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ def set_section_length(self, section: 'ProgressBarSection', length: int) -> None
146146

147147
if length == 0:
148148
self._skip_section(section)
149+
else:
150+
self._maybe_update_current_section()
149151

150152
def _skip_section(self, section: 'ProgressBarSection') -> None:
151153
self._progress_bar.update(_get_section_length(section))

0 commit comments

Comments
 (0)