Coverage for src / agent / services / maven / types.py: 95%
110 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 14:30 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 14:30 +0000
1# Copyright 2025-2026 Microsoft Corporation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""Pydantic models and types for Maven services.
17This module defines the data structures used by Maven services including
18request/response models, error codes, and validation schemas.
19"""
21from enum import Enum
22from typing import Any
24from pydantic import BaseModel, Field, field_validator
27class MavenErrorCode(str, Enum):
28 """Error codes for Maven operations."""
30 INVALID_COORDINATE = "invalid_coordinate"
31 DEPENDENCY_NOT_FOUND = "dependency_not_found"
32 VERSION_NOT_FOUND = "version_not_found"
33 MAVEN_API_ERROR = "maven_api_error"
34 TRIVY_NOT_AVAILABLE = "trivy_not_available"
35 TRIVY_SCAN_FAILED = "trivy_scan_failed"
36 POM_PARSE_ERROR = "pom_parse_error"
37 INVALID_PATH = "invalid_path"
40class MavenCoordinate(BaseModel):
41 """Maven dependency coordinate (groupId:artifactId)."""
43 group_id: str = Field(..., description="Maven group ID")
44 artifact_id: str = Field(..., description="Maven artifact ID")
46 @field_validator("group_id", "artifact_id")
47 @classmethod
48 def validate_not_empty(cls, v: str) -> str:
49 """Ensure coordinate parts are not empty."""
50 if not v or not v.strip():
51 raise ValueError("Coordinate part cannot be empty")
52 return v.strip()
54 @classmethod
55 def parse(cls, coordinate: str) -> "MavenCoordinate":
56 """Parse a Maven coordinate string (groupId:artifactId).
58 Args:
59 coordinate: Maven coordinates in groupId:artifactId format
61 Returns:
62 MavenCoordinate instance
64 Raises:
65 ValueError: If coordinate format is invalid
66 """
67 if not coordinate or ":" not in coordinate:
68 raise ValueError(
69 f"Invalid Maven coordinate format: {coordinate}. "
70 "Use 'groupId:artifactId' format."
71 )
73 parts = coordinate.split(":")
74 if len(parts) != 2 or not all(parts):
75 raise ValueError(
76 f"Invalid Maven coordinate format: {coordinate}. "
77 "Use 'groupId:artifactId' format."
78 )
80 return cls(group_id=parts[0], artifact_id=parts[1])
82 def __str__(self) -> str:
83 """Return coordinate as string."""
84 return f"{self.group_id}:{self.artifact_id}"
87class VersionCheck(BaseModel):
88 """Request model for version check operations."""
90 dependency: str = Field(..., description="Maven coordinates (groupId:artifactId)")
91 version: str = Field(..., description="Version to check")
92 packaging: str = Field(default="jar", description="Package type (jar, war, pom)")
93 classifier: str | None = Field(default=None, description="Optional classifier")
95 @field_validator("dependency")
96 @classmethod
97 def validate_dependency(cls, v: str) -> str:
98 """Validate Maven coordinate format."""
99 MavenCoordinate.parse(v)
100 return v
102 @field_validator("version")
103 @classmethod
104 def validate_version(cls, v: str) -> str:
105 """Ensure version is not empty."""
106 if not v or not v.strip():
107 raise ValueError("Version cannot be empty")
108 return v.strip()
111class LatestVersions(BaseModel):
112 """Latest versions by update type."""
114 latest_major: str | None = Field(default=None, description="Latest major version")
115 latest_minor: str | None = Field(default=None, description="Latest minor version")
116 latest_patch: str | None = Field(default=None, description="Latest patch version")
117 latest_overall: str | None = Field(default=None, description="Latest overall version")
120class VersionCheckResult(BaseModel):
121 """Result of a version check operation."""
123 dependency: str = Field(..., description="Maven coordinates checked")
124 version: str = Field(..., description="Version checked")
125 exists: bool = Field(..., description="Whether the version exists")
126 latest_versions: LatestVersions = Field(
127 default_factory=LatestVersions, description="Latest available versions"
128 )
129 has_major_update: bool = Field(default=False, description="Major update available")
130 has_minor_update: bool = Field(default=False, description="Minor update available")
131 has_patch_update: bool = Field(default=False, description="Patch update available")
132 all_versions: list[str] = Field(default_factory=list, description="All available versions")
135class BatchVersionCheckResult(BaseModel):
136 """Result of a batch version check operation."""
138 total: int = Field(..., description="Total dependencies checked")
139 success: int = Field(..., description="Successful checks")
140 failed: int = Field(..., description="Failed checks")
141 with_updates: int = Field(..., description="Dependencies with updates available")
142 results: list[dict[str, Any]] = Field(default_factory=list, description="Individual results")
145class MavenMetadata(BaseModel):
146 """Maven artifact metadata from maven-metadata.xml."""
148 group_id: str = Field(..., description="Maven group ID")
149 artifact_id: str = Field(..., description="Maven artifact ID")
150 latest_version: str | None = Field(default=None, description="Latest version")
151 release_version: str | None = Field(default=None, description="Release version")
152 versions: list[str] = Field(default_factory=list, description="Available versions")
155class VulnerabilitySeverity(str, Enum):
156 """Vulnerability severity levels."""
158 CRITICAL = "critical"
159 HIGH = "high"
160 MEDIUM = "medium"
161 LOW = "low"
162 UNKNOWN = "unknown"
165class Vulnerability(BaseModel):
166 """Security vulnerability information."""
168 cve_id: str = Field(..., description="CVE identifier")
169 severity: VulnerabilitySeverity = Field(..., description="Severity level")
170 package_name: str = Field(..., description="Affected package")
171 installed_version: str = Field(..., description="Installed version")
172 fixed_version: str | None = Field(default=None, description="Version with fix")
173 description: str = Field(default="", description="Vulnerability description")
176class SecurityScanResult(BaseModel):
177 """Result of a security scan operation."""
179 vulnerabilities_found: bool = Field(..., description="Whether vulnerabilities were found")
180 total_vulnerabilities: int = Field(default=0, description="Total count")
181 severity_counts: dict[str, int] = Field(default_factory=dict, description="Count by severity")
182 vulnerabilities: list[Vulnerability] = Field(
183 default_factory=list, description="Vulnerability details"
184 )
185 scan_target: str = Field(..., description="Scan target path")
186 trivy_available: bool = Field(default=True, description="Trivy scanner available")
189class PomDependency(BaseModel):
190 """Dependency extracted from a POM file."""
192 group_id: str = Field(..., description="Maven group ID")
193 artifact_id: str = Field(..., description="Maven artifact ID")
194 version: str | None = Field(default=None, description="Version (may be property)")
195 scope: str = Field(default="compile", description="Dependency scope")
196 optional: bool = Field(default=False, description="Optional dependency")
199class PomAnalysisResult(BaseModel):
200 """Result of POM file analysis."""
202 pom_path: str = Field(..., description="Path to analyzed POM")
203 group_id: str | None = Field(default=None, description="Project group ID")
204 artifact_id: str | None = Field(default=None, description="Project artifact ID")
205 version: str | None = Field(default=None, description="Project version")
206 packaging: str = Field(default="jar", description="Packaging type")
207 parent: dict[str, str] | None = Field(default=None, description="Parent POM info")
208 dependencies: list[PomDependency] = Field(default_factory=list, description="Dependencies")
209 dependency_management: list[PomDependency] = Field(
210 default_factory=list, description="Managed dependencies"
211 )
212 properties: dict[str, str] = Field(default_factory=dict, description="POM properties")
213 modules: list[str] = Field(default_factory=list, description="Child modules")