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

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. 

14 

15"""Pydantic models and types for Maven services. 

16 

17This module defines the data structures used by Maven services including 

18request/response models, error codes, and validation schemas. 

19""" 

20 

21from enum import Enum 

22from typing import Any 

23 

24from pydantic import BaseModel, Field, field_validator 

25 

26 

27class MavenErrorCode(str, Enum): 

28 """Error codes for Maven operations.""" 

29 

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" 

38 

39 

40class MavenCoordinate(BaseModel): 

41 """Maven dependency coordinate (groupId:artifactId).""" 

42 

43 group_id: str = Field(..., description="Maven group ID") 

44 artifact_id: str = Field(..., description="Maven artifact ID") 

45 

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() 

53 

54 @classmethod 

55 def parse(cls, coordinate: str) -> "MavenCoordinate": 

56 """Parse a Maven coordinate string (groupId:artifactId). 

57 

58 Args: 

59 coordinate: Maven coordinates in groupId:artifactId format 

60 

61 Returns: 

62 MavenCoordinate instance 

63 

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 ) 

72 

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 ) 

79 

80 return cls(group_id=parts[0], artifact_id=parts[1]) 

81 

82 def __str__(self) -> str: 

83 """Return coordinate as string.""" 

84 return f"{self.group_id}:{self.artifact_id}" 

85 

86 

87class VersionCheck(BaseModel): 

88 """Request model for version check operations.""" 

89 

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") 

94 

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 

101 

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() 

109 

110 

111class LatestVersions(BaseModel): 

112 """Latest versions by update type.""" 

113 

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") 

118 

119 

120class VersionCheckResult(BaseModel): 

121 """Result of a version check operation.""" 

122 

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") 

133 

134 

135class BatchVersionCheckResult(BaseModel): 

136 """Result of a batch version check operation.""" 

137 

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") 

143 

144 

145class MavenMetadata(BaseModel): 

146 """Maven artifact metadata from maven-metadata.xml.""" 

147 

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") 

153 

154 

155class VulnerabilitySeverity(str, Enum): 

156 """Vulnerability severity levels.""" 

157 

158 CRITICAL = "critical" 

159 HIGH = "high" 

160 MEDIUM = "medium" 

161 LOW = "low" 

162 UNKNOWN = "unknown" 

163 

164 

165class Vulnerability(BaseModel): 

166 """Security vulnerability information.""" 

167 

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") 

174 

175 

176class SecurityScanResult(BaseModel): 

177 """Result of a security scan operation.""" 

178 

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") 

187 

188 

189class PomDependency(BaseModel): 

190 """Dependency extracted from a POM file.""" 

191 

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") 

197 

198 

199class PomAnalysisResult(BaseModel): 

200 """Result of POM file analysis.""" 

201 

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")