import { z } from 'zod/v4'; import type { SkipReason } from '../../../types/index.ts'; import { Toml, withDepType } from '../../../util/schema-utils/index.ts'; import { CrateDatasource } from '../../datasource/crate/index.ts'; import type { PackageDependency } from '../types.ts'; import { applyGitSource } from '../util.ts'; import type { CargoManagerData } from './types.ts'; const CargoDep = z.union([ z .object({ /** Path on disk to the crate sources */ path: z.string().optional(), /** Git URL for the dependency */ git: z.string().optional(), /** Git tag for the dependency */ tag: z.string().optional(), /** Git revision for the dependency */ rev: z.string().optional(), /** Git branch for the dependency */ branch: z.string().optional(), /** Semver version */ version: z.string().optional(), /** Name of a registry whose URL is configured in `.cargo/config.toml` or `.cargo/config` */ registry: z.string().optional(), /** Name of a package to look up */ package: z.string().optional(), /** Whether the dependency is inherited from the workspace */ workspace: z.boolean().optional(), }) .transform( ({ path, git, tag, rev, branch, version, registry, package: pkg, workspace, }): PackageDependency => { let skipReason: SkipReason | undefined; let currentValue: string; let nestedVersion = false; if (version) { currentValue = version; nestedVersion = true; } else { currentValue = ''; } const dep: PackageDependency = { currentValue, managerData: { nestedVersion }, datasource: CrateDatasource.id, }; if (path) { skipReason = 'path-dependency'; } else if (workspace) { skipReason = 'inherited-dependency'; } else if (git) { applyGitSource(dep, git, rev, tag, branch); } else if (!version) { skipReason = 'invalid-dependency-specification'; } if (skipReason) { dep.skipReason = skipReason; } if (pkg && !git) { dep.packageName = pkg; } if (registry) { dep.managerData!.registryName = registry; } return dep; }, ), z.string().transform((version): PackageDependency => ({ currentValue: version, managerData: { nestedVersion: false }, datasource: CrateDatasource.id, })), ]); const CargoDeps = z.record(z.string(), CargoDep).transform((record) => { const deps: PackageDependency[] = []; for (const [depName, dep] of Object.entries(record)) { dep.depName = depName; deps.push(dep); } return deps; }); export type CargoDeps = z.infer; const CargoSection = z.object({ dependencies: withDepType(CargoDeps, 'dependencies').optional(), 'dev-dependencies': withDepType(CargoDeps, 'dev-dependencies').optional(), 'build-dependencies': withDepType(CargoDeps, 'build-dependencies').optional(), }); const CargoWorkspace = z.object({ dependencies: withDepType(CargoDeps, 'workspace.dependencies').optional(), package: z .object({ version: z.string().optional(), }) .optional(), }); const CargoTarget = z.record(z.string(), CargoSection); export const CargoManifest = Toml.pipe( CargoSection.extend({ package: z .object({ version: z .union([z.string(), z.object({ workspace: z.literal(true) })]) .optional(), }) .optional(), workspace: CargoWorkspace.optional(), target: CargoTarget.optional(), }), ); const CargoConfigRegistry = z.object({ index: z.string().optional(), }); const CargoConfigSource = z.object({ 'replace-with': z.string().optional(), registry: z.string().optional(), }); export const CargoConfig = Toml.pipe( z.object({ registries: z.record(z.string(), CargoConfigRegistry).optional(), source: z.record(z.string(), CargoConfigSource).optional(), }), ); export type CargoConfig = z.infer; const CargoLockPackage = z.object({ name: z.string(), version: z.string(), source: z.string().optional(), }); export const CargoLock = Toml.pipe( z.object({ package: z.array(CargoLockPackage).optional(), }), ); export type CargoLock = z.infer;