#!/usr/bin/env python3 # Copyright 2023 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import textwrap from typing import Dict, List, Optional, Tuple _CHROMIUM_METADATA_PRESCRIPT = "Third party metadata issue:" _CHROMIUM_METADATA_POSTSCRIPT = ("Check //third_party/README.chromium.template " "for details.") class ValidationResult: """Base class for validation issues.""" def __init__(self, reason: str, fatal: bool, additional: List[str] = []): """Constructor for a validation issue. Args: reason: the root cause of the issue. fatal: whether the issue is fatal. additional: details that should be included in the validation message, e.g. advice on how to address the issue, or specific problematic values. """ self._reason = reason self._fatal = fatal self._additional = additional self._tags = {} self._lines = [] def __str__(self) -> str: prefix = self.get_severity_prefix() additional_text = ' '.join(self._additional) return f"{prefix} - {self._reason} {additional_text}" def __repr__(self) -> str: return str(self) def _comparision_key(self) -> Tuple: return (not self._fatal, self._reason, self._additional) # PEP 8 recommends implementing all 6 rich comparisons. # Here we make use of tuple comparison, and order based on the severity # (e.g. fatal comes before non-fatal), then the message. def __lt__(self, other) -> bool: return self._comparision_key() < other._comparision_key() def __le__(self, other) -> bool: return self._comparision_key() <= other._comparision_key() def __gt__(self, other) -> bool: return self._comparision_key() > other._comparision_key() def __ge__(self, other) -> bool: return self._comparision_key() >= other._comparision_key() def __eq__(self, other) -> bool: return self._comparision_key() == other._comparision_key() def __ne__(self, other) -> bool: return self._comparision_key() != other._comparision_key() def is_fatal(self) -> bool: return self._fatal def get_severity_prefix(self): if self._fatal: return "ERROR" return "[non-fatal]" def get_reason(self) -> str: return self._reason def set_tag(self, tag: str, value: str) -> bool: self._tags[tag] = value def get_tag(self, tag: str) -> Optional[str]: return self._tags.get(tag) def get_all_tags(self) -> Dict[str, str]: return dict(self._tags) def get_additional(self) -> List[str]: return self._additional def get_message(self, prescript: str = _CHROMIUM_METADATA_PRESCRIPT, postscript: str = _CHROMIUM_METADATA_POSTSCRIPT, width: int = 0) -> str: additional_text = ' '.join(self._additional) components = [prescript, self._reason, additional_text, postscript] message = " ".join( [component for component in components if len(component) > 0]) if width > 0: return textwrap.fill(text=message, width=width) return message def set_lines(self, lines: List[int]): self._lines = lines def get_lines(self) -> List[int]: return self._lines class ValidationError(ValidationResult): """Fatal validation issue. Presubmit should fail.""" def __init__(self, reason: str, additional: List[str] = []): super().__init__(reason=reason, fatal=True, additional=additional) class ValidationWarning(ValidationResult): """Non-fatal validation issue. Presubmit should pass.""" def __init__(self, reason: str, additional: List[str] = []): super().__init__(reason=reason, fatal=False, additional=additional)