106 lines
3.7 KiB
Python
106 lines
3.7 KiB
Python
import importlib
|
|
from dataclasses import field
|
|
from pathlib import Path
|
|
from typing import List, Union
|
|
|
|
from pydantic import ValidationError
|
|
from pydantic.dataclasses import dataclass
|
|
from typing_extensions import Self
|
|
|
|
from ..report.schemas.v3_0 import main as v3_0
|
|
from .base import ReportSchemaVersion, SafetyBaseModel, ScanType
|
|
from .file import FileModel
|
|
from .metadata import MetadataModel
|
|
from .project import ProjectModel
|
|
from .telemetry import TelemetryModel
|
|
|
|
from pydantic.version import VERSION as PYDANTIC_VERSION
|
|
|
|
def version_validator(func):
|
|
if PYDANTIC_VERSION.startswith("1."):
|
|
from pydantic import validator
|
|
return validator('verison', pre=True, always=True)
|
|
else:
|
|
from pydantic import field_validator
|
|
return field_validator('verison', mode='before')
|
|
|
|
@dataclass
|
|
class ReportModel(SafetyBaseModel):
|
|
"""
|
|
Used as an entrypoint to keep backwards compatibility with old formats.
|
|
Use this model if you want to generate a standard JSON report.
|
|
"""
|
|
|
|
DEFAULT_SCHEMA_VERSION = ReportSchemaVersion.v3_0
|
|
|
|
telemetry: TelemetryModel
|
|
metadata: MetadataModel
|
|
files: List[FileModel]
|
|
projects: List[ProjectModel] = field(default_factory=lambda: [])
|
|
version: ReportSchemaVersion = DEFAULT_SCHEMA_VERSION
|
|
|
|
@version_validator
|
|
def validate_version(cls, version: ReportSchemaVersion) -> ReportSchemaVersion:
|
|
versions = list(ReportSchemaVersion)
|
|
if version not in (versions):
|
|
raise ValueError(f"Invalid version, allowed versions are {versions}")
|
|
return version
|
|
|
|
def as_v30(self) -> v3_0.Report:
|
|
full = self.metadata.scan_type is ScanType.scan
|
|
|
|
projects_data = [p.as_v30(full=full) for p in self.projects]
|
|
|
|
if not all(isinstance(p, type(projects_data[0])) for p in projects_data):
|
|
raise ValueError("All objects in projects_data must be of the same type")
|
|
|
|
results = v3_0.ScanResults(
|
|
files=[f.as_v30() for f in self.files],
|
|
projects=projects_data, # type: ignore
|
|
)
|
|
|
|
meta_data = self.metadata.as_v30()
|
|
|
|
if not isinstance(meta_data, v3_0.Meta) or not isinstance(results, v3_0.ScanResults):
|
|
raise TypeError("Expected instance of v3_0.Meta and v3_0.ScanResults")
|
|
|
|
report = v3_0.Report(meta=meta_data, scan_results=results)
|
|
|
|
return report
|
|
|
|
@classmethod
|
|
def from_v30(cls, obj: v3_0.SchemaModelV30) -> 'ReportModel':
|
|
|
|
if not isinstance(obj, v3_0.Report):
|
|
raise TypeError('Expected instance of v3_0.Report')
|
|
|
|
return ReportModel(
|
|
version=ReportSchemaVersion(obj.meta.schema_version),
|
|
telemetry=TelemetryModel.from_v30(obj.meta.telemetry),
|
|
metadata=MetadataModel.from_v30(obj.meta),
|
|
projects=[ProjectModel.from_v30(p) for p in obj.scan_results.projects],
|
|
files=[FileModel.from_v30(f) for f in obj.scan_results.files],
|
|
)
|
|
|
|
@classmethod
|
|
def parse_report(
|
|
cls, raw_report: Union[str, Path], schema: ReportSchemaVersion
|
|
) -> Union['ReportModel', ValidationError]:
|
|
parse = "parse_raw"
|
|
|
|
if isinstance(raw_report, Path):
|
|
raw_report = raw_report.expanduser().resolve()
|
|
parse = "parse_file"
|
|
|
|
target_schema = schema.value.replace(".", "_")
|
|
module_name = "safety_schemas.report.schemas." f"v{target_schema}.main"
|
|
|
|
module = importlib.import_module(module_name)
|
|
report_model = module.Report
|
|
|
|
# This will raise a validation error if the content is wrong
|
|
validated_report = getattr(report_model, parse)(raw_report)
|
|
|
|
# TODO: Select the from from the version passed
|
|
return ReportModel.from_v30(obj=validated_report)
|