|
1 | | -from gettext import gettext as _ |
| 1 | +import logging |
2 | 2 |
|
| 3 | +from gettext import gettext as _ |
| 4 | +from django.db.utils import IntegrityError |
| 5 | +from pydantic import TypeAdapter, ValidationError |
3 | 6 | from rest_framework import serializers |
4 | 7 |
|
5 | 8 | from pulpcore.plugin import models as core_models |
6 | 9 | from pulpcore.plugin import serializers as core_serializers |
| 10 | +from pulpcore.plugin.util import get_domain |
7 | 11 |
|
8 | 12 | from . import models |
9 | 13 |
|
| 14 | +log = logging.getLogger(__name__) |
| 15 | + |
| 16 | + |
| 17 | +class IndexRootSerializer(serializers.Serializer): |
| 18 | + """ |
| 19 | + A Serializer for summary information of an index. |
| 20 | + """ |
| 21 | + |
| 22 | + dl = serializers.CharField(help_text=_("URL of the index root"), read_only=True) |
| 23 | + api = serializers.CharField(help_text=_("URL of the API root"), read_only=True) |
| 24 | + auth_required = serializers.BooleanField( |
| 25 | + help_text=_( |
| 26 | + "Indicates whether this is a private registry that requires all operations to be authenticated" |
| 27 | + ), |
| 28 | + read_only=True, |
| 29 | + ) |
| 30 | + |
10 | 31 |
|
11 | 32 | class RustDependencySerializer(serializers.ModelSerializer): |
12 | 33 | """ |
@@ -178,31 +199,187 @@ class Meta: |
178 | 199 | model = models.RustContent |
179 | 200 |
|
180 | 201 |
|
181 | | -class RustRemoteSerializer(core_serializers.RemoteSerializer): |
| 202 | +class RustDependencySerializer(serializers.ModelSerializer): |
182 | 203 | """ |
183 | | - A Serializer for RustRemote. |
| 204 | + Serializer for RustDependency. |
184 | 205 |
|
185 | | - Add any new fields if defined on RustRemote. |
186 | | - Similar to the example above, in RustContentSerializer. |
187 | | - Additional validators can be added to the parent validators list |
| 206 | + Represents a single dependency entry from the Cargo package index. |
| 207 | + """ |
188 | 208 |
|
189 | | - For example:: |
| 209 | + name = serializers.CharField( |
| 210 | + help_text=_("Dependency name as used in code (may be renamed via 'package' field)") |
| 211 | + ) |
| 212 | + |
| 213 | + req = serializers.CharField( |
| 214 | + help_text=_("Version requirement string (e.g., '^1.0', '>=0.2.3,<0.3')") |
| 215 | + ) |
| 216 | + |
| 217 | + features = serializers.ListField( |
| 218 | + child=serializers.CharField(), |
| 219 | + default=list, |
| 220 | + required=False, |
| 221 | + help_text=_("List of feature flags to enable for this dependency"), |
| 222 | + ) |
| 223 | + |
| 224 | + optional = serializers.BooleanField( |
| 225 | + default=False, required=False, help_text=_("Whether this is an optional dependency") |
| 226 | + ) |
| 227 | + |
| 228 | + default_features = serializers.BooleanField( |
| 229 | + default=True, |
| 230 | + required=False, |
| 231 | + help_text=_("Whether to enable the dependency's default features"), |
| 232 | + ) |
| 233 | + |
| 234 | + target = serializers.CharField( |
| 235 | + allow_null=True, |
| 236 | + required=False, |
| 237 | + help_text=_("Platform-specific target (e.g., 'cfg(unix)', 'cfg(windows)')"), |
| 238 | + ) |
| 239 | + |
| 240 | + kind = serializers.ChoiceField( |
| 241 | + choices=[("normal", "Normal"), ("dev", "Development"), ("build", "Build")], |
| 242 | + default="normal", |
| 243 | + required=False, |
| 244 | + help_text=_( |
| 245 | + "Dependency type: 'normal' (runtime), 'dev' (development), or 'build' (build script)" |
| 246 | + ), |
| 247 | + ) |
| 248 | + |
| 249 | + registry = serializers.CharField( |
| 250 | + allow_null=True, |
| 251 | + required=False, |
| 252 | + help_text=_("Alternative registry URL if dependency is from a different registry"), |
| 253 | + ) |
| 254 | + |
| 255 | + package = serializers.CharField( |
| 256 | + allow_null=True, |
| 257 | + required=False, |
| 258 | + help_text=_("Original crate name if the dependency was renamed"), |
| 259 | + ) |
190 | 260 |
|
191 | 261 | class Meta: |
192 | | - validators = core_serializers.RemoteSerializer.Meta.validators |
193 | | - + [myValidator1, myValidator2] |
| 262 | + model = models.RustDependency |
| 263 | + fields = ( |
| 264 | + "name", |
| 265 | + "req", |
| 266 | + "features", |
| 267 | + "optional", |
| 268 | + "default_features", |
| 269 | + "target", |
| 270 | + "kind", |
| 271 | + "registry", |
| 272 | + "package", |
| 273 | + ) |
| 274 | + |
| 275 | + |
| 276 | +class RustContentSerializer(core_serializers.SingleArtifactContentSerializer): |
| 277 | + """ |
| 278 | + Serializer for RustContent (Cargo package version). |
| 279 | +
|
| 280 | + Represents a single version of a Rust crate as defined in the Cargo registry |
| 281 | + index specification. Includes package metadata, dependencies, and features. |
| 282 | + """ |
| 283 | + |
| 284 | + name = serializers.CharField(help_text=_("Package name (crate name)")) |
| 285 | + |
| 286 | + vers = serializers.CharField(help_text=_("Semantic version string (SemVer 2.0.0)")) |
194 | 287 |
|
195 | | - By default the 'policy' field in core_serializers.RemoteSerializer only validates the choice |
196 | | - 'immediate'. To add on-demand support for more 'policy' options, e.g. 'streamed' or |
197 | | - 'on_demand', re-define the 'policy' option as follows:: |
| 288 | + dependencies = RustDependencySerializer( |
| 289 | + many=True, required=False, help_text=_("List of dependencies for this package version") |
| 290 | + ) |
| 291 | + |
| 292 | + cksum = serializers.CharField(help_text=_("SHA256 checksum of the .crate file (tarball)")) |
| 293 | + |
| 294 | + features = serializers.JSONField( |
| 295 | + default=dict, |
| 296 | + required=False, |
| 297 | + help_text=_( |
| 298 | + "Feature flags mapping - maps feature names to lists of features/dependencies " |
| 299 | + "they enable" |
| 300 | + ), |
| 301 | + ) |
| 302 | + |
| 303 | + features2 = serializers.JSONField( |
| 304 | + default=dict, |
| 305 | + required=False, |
| 306 | + allow_null=True, |
| 307 | + help_text=_("Extended feature syntax support (newer registry format)"), |
| 308 | + ) |
| 309 | + |
| 310 | + yanked = serializers.BooleanField( |
| 311 | + default=False, |
| 312 | + required=False, |
| 313 | + help_text=_("Whether this version has been yanked (removed from normal use)"), |
| 314 | + ) |
| 315 | + |
| 316 | + links = serializers.CharField( |
| 317 | + allow_null=True, |
| 318 | + required=False, |
| 319 | + help_text=_("Name of native library this package links to (from Cargo.toml 'links' field)"), |
| 320 | + ) |
| 321 | + |
| 322 | + v = serializers.IntegerField( |
| 323 | + default=1, required=False, help_text=_("Schema version of the index entry format") |
| 324 | + ) |
| 325 | + rust_version = serializers.CharField( |
| 326 | + allow_null=True, |
| 327 | + required=False, |
| 328 | + help_text=_("Minimum Rust compiler version required (MSRV)"), |
| 329 | + ) |
| 330 | + |
| 331 | + def create(self, validated_data): |
| 332 | + """Create RustContent and related dependencies.""" |
| 333 | + dependencies_data = validated_data.pop("dependencies", []) |
| 334 | + content = super().create(validated_data) |
| 335 | + |
| 336 | + # Create dependency records |
| 337 | + for dep_data in dependencies_data: |
| 338 | + models.RustDependency.objects.create(content=content, **dep_data) |
| 339 | + |
| 340 | + return content |
| 341 | + |
| 342 | + def update(self, instance, validated_data): |
| 343 | + """Update RustContent and related dependencies.""" |
| 344 | + dependencies_data = validated_data.pop("dependencies", None) |
| 345 | + |
| 346 | + instance = super().update(instance, validated_data) |
| 347 | + |
| 348 | + if dependencies_data is not None: |
| 349 | + # Replace all dependencies |
| 350 | + instance.dependencies.all().delete() |
| 351 | + for dep_data in dependencies_data: |
| 352 | + models.RustDependency.objects.create(content=instance, **dep_data) |
| 353 | + |
| 354 | + return instance |
| 355 | + |
| 356 | + class Meta: |
| 357 | + fields = core_serializers.SingleArtifactContentSerializer.Meta.fields + ( |
| 358 | + "name", |
| 359 | + "vers", |
| 360 | + "dependencies", |
| 361 | + "cksum", |
| 362 | + "features", |
| 363 | + "features2", |
| 364 | + "yanked", |
| 365 | + "links", |
| 366 | + "v", |
| 367 | + "rust_version", |
| 368 | + ) |
| 369 | + model = models.RustContent |
| 370 | + |
| 371 | + |
| 372 | +class RustRemoteSerializer(core_serializers.RemoteSerializer): |
| 373 | + """ |
| 374 | + A Serializer for RustRemote. |
| 375 | + """ |
198 | 376 |
|
199 | 377 | policy = serializers.ChoiceField( |
200 | 378 | help_text="The policy to use when downloading content. The possible values include: " |
201 | | - "'immediate', 'on_demand', and 'streamed'. 'immediate' is the default.", |
| 379 | + "'immediate', 'on_demand', and 'streamed'. 'streamed' is the default.", |
202 | 380 | choices=models.Remote.POLICY_CHOICES, |
203 | | - default=models.Remote.IMMEDIATE |
| 381 | + default=models.Remote.STREAMED, |
204 | 382 | ) |
205 | | - """ |
206 | 383 |
|
207 | 384 | class Meta: |
208 | 385 | fields = core_serializers.RemoteSerializer.Meta.fields |
@@ -258,3 +435,31 @@ class Meta: |
258 | 435 | class Meta: |
259 | 436 | fields = core_serializers.DistributionSerializer.Meta.fields + ("allow_uploads", "remote") |
260 | 437 | model = models.RustDistribution |
| 438 | + |
| 439 | + |
| 440 | +class RepositoryAddCachedContentSerializer( |
| 441 | + core_serializers.ValidateFieldsMixin, serializers.Serializer |
| 442 | +): |
| 443 | + remote = core_serializers.DetailRelatedField( |
| 444 | + required=False, |
| 445 | + view_name_pattern=r"remotes(-.*/.*)-detail", |
| 446 | + queryset=models.Remote.objects.all(), |
| 447 | + help_text=_( |
| 448 | + "A remote to use to identify content that was cached. This will override a " |
| 449 | + "remote set on repository." |
| 450 | + ), |
| 451 | + ) |
| 452 | + |
| 453 | + def validate(self, data): |
| 454 | + data = super().validate(data) |
| 455 | + repository = None |
| 456 | + if "repository_pk" in self.context: |
| 457 | + repository = models.Repository.objects.get(pk=self.context["repository_pk"]) |
| 458 | + remote = data.get("remote", None) or getattr(repository, "remote", None) |
| 459 | + |
| 460 | + if not remote: |
| 461 | + raise serializers.ValidationError( |
| 462 | + {"remote": _("This field is required since a remote is not set on the repository.")} |
| 463 | + ) |
| 464 | + self.check_cross_domains({"repository": repository, "remote": remote}) |
| 465 | + return data |
0 commit comments