Skip to main content

amiss_wire/
resolution.rs

1use crate::digest::Digest;
2use strum::{AsRefStr, EnumDiscriminants, EnumIter, EnumString};
3
4/// The two ordinary Git blob modes. Trees, symlinks, and gitlinks are
5/// represented by other target types and cannot be smuggled into a blob.
6#[derive(Clone, Copy, Debug, PartialEq, Eq, AsRefStr, EnumString, EnumIter)]
7pub enum BlobMode {
8    #[strum(serialize = "100644")]
9    Regular,
10    #[strum(serialize = "100755")]
11    Executable,
12}
13
14/// The content evidence retained for a located blob. An available blob has
15/// both digests; an LFS pointer has only the digest of the pointer bytes.
16#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumDiscriminants)]
17#[strum_discriminants(name(BlobContentTag))]
18#[strum_discriminants(derive(AsRefStr, EnumString, EnumIter))]
19#[strum_discriminants(strum(serialize_all = "kebab-case"))]
20pub enum BlobContent {
21    Available {
22        raw_digest: Digest,
23        projection_digest: Digest,
24    },
25    LfsPointer {
26        raw_digest: Digest,
27    },
28}
29
30impl BlobContent {
31    #[must_use]
32    pub const fn projection_digest(self) -> Option<Digest> {
33        match self {
34            Self::Available {
35                projection_digest, ..
36            } => Some(projection_digest),
37            Self::LfsPointer { .. } => None,
38        }
39    }
40
41    #[must_use]
42    pub const fn is_lfs_pointer(self) -> bool {
43        matches!(self, Self::LfsPointer { .. })
44    }
45}
46
47/// A located ordinary blob and the evidence read from it.
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct BlobTarget<P> {
50    pub path: P,
51    pub mode: BlobMode,
52    pub content: BlobContent,
53}
54
55/// A located target. A tree has no blob content; a blob always carries a
56/// valid blob mode and one exact content-evidence shape.
57#[derive(Clone, Debug, PartialEq, Eq, EnumDiscriminants)]
58#[strum_discriminants(name(TargetTag))]
59#[strum_discriminants(derive(AsRefStr, EnumString, EnumIter))]
60#[strum_discriminants(strum(serialize_all = "kebab-case"))]
61pub enum Target<P> {
62    Tree { path: P },
63    Blob(BlobTarget<P>),
64}
65
66impl<P> Target<P> {
67    #[must_use]
68    pub const fn projection_digest(&self) -> Option<Digest> {
69        match self {
70            Self::Tree { .. } => None,
71            Self::Blob(blob) => blob.content.projection_digest(),
72        }
73    }
74
75    #[must_use]
76    pub const fn is_lfs_pointer(&self) -> bool {
77        match self {
78            Self::Tree { .. } => false,
79            Self::Blob(blob) => blob.content.is_lfs_pointer(),
80        }
81    }
82}
83
84/// A target that was absent at a known repository path. Each diagnostic owns
85/// the path required by its wire row.
86#[derive(Clone, Debug, PartialEq, Eq, EnumDiscriminants)]
87#[strum_discriminants(name(MissingTag))]
88#[strum_discriminants(derive(AsRefStr, EnumString, EnumIter))]
89#[strum_discriminants(strum(serialize_all = "kebab-case"))]
90pub enum Missing<P> {
91    PathNotFound { path: P },
92    LineFragmentOutOfRange { path: P },
93}
94
95/// A special Git entry that is present but cannot be followed as an ordinary
96/// repository target. Each diagnostic owns the affected path.
97#[derive(Clone, Debug, PartialEq, Eq, EnumDiscriminants)]
98#[strum_discriminants(name(UnsupportedTargetTag))]
99#[strum_discriminants(derive(AsRefStr, EnumString, EnumIter))]
100#[strum_discriminants(strum(serialize_all = "kebab-case"))]
101pub enum UnsupportedTarget<P> {
102    Symlink { path: P },
103    Gitlink { path: P },
104}
105
106/// Reference syntax whose target may be located but whose meaning is outside
107/// the scanner's current evaluator.
108#[derive(Clone, Debug, PartialEq, Eq, EnumDiscriminants)]
109#[strum_discriminants(name(UnsupportedSemanticsTag))]
110#[strum_discriminants(derive(AsRefStr, EnumString, EnumIter))]
111#[strum_discriminants(strum(serialize_all = "kebab-case"))]
112pub enum UnsupportedSemantics<P> {
113    Query(Target<P>),
114    Fragment(BlobTarget<P>),
115    CodeFragment(Target<P>),
116    SiteRoute,
117    NetworkPath,
118}
119
120impl<P> UnsupportedSemantics<P> {
121    #[must_use]
122    pub const fn is_lfs_pointer(&self) -> bool {
123        match self {
124            Self::Query(target) | Self::CodeFragment(target) => target.is_lfs_pointer(),
125            Self::Fragment(blob) => blob.content.is_lfs_pointer(),
126            Self::SiteRoute | Self::NetworkPath => false,
127        }
128    }
129}
130
131/// Version-scoped forge references either identify a contained path outside
132/// the candidate scope or fail before a path can be identified.
133#[derive(Clone, Debug, PartialEq, Eq, EnumDiscriminants)]
134#[strum_discriminants(name(VersionScopeTag))]
135#[strum_discriminants(derive(AsRefStr, EnumString, EnumIter))]
136#[strum_discriminants(strum(serialize_all = "kebab-case"))]
137pub enum VersionScope<P> {
138    KnownPath { path: P },
139    UnknownPath,
140}
141
142/// A syntax defect that prevents a reference from identifying a repository or
143/// external target.
144#[derive(Clone, Copy, Debug, PartialEq, Eq, AsRefStr, EnumString, EnumIter)]
145#[strum(serialize_all = "kebab-case")]
146pub enum InvalidReference {
147    Uri,
148    PercentEncoding,
149    DecodedPathControl,
150    PathTraversal,
151    BackslashSeparator,
152    EncodedSlash,
153    FragmentEncoding,
154    Syntax,
155}
156
157/// References that are valid but intentionally outside the evaluated
158/// repository.
159#[derive(Clone, Copy, Debug, PartialEq, Eq, AsRefStr, EnumString, EnumIter)]
160#[strum(serialize_all = "kebab-case")]
161pub enum ExternalReference {
162    Url,
163    ForeignRepository,
164}
165
166/// The total outcome of resolving one authored reference. The outer variants
167/// are the semantic partitions used by evaluation, correlation, and summary
168/// reporting; leaf enums retain the exact diagnostic and its required data.
169#[derive(Clone, Debug, PartialEq, Eq, EnumDiscriminants)]
170#[strum_discriminants(name(ResolutionTag))]
171#[strum_discriminants(derive(AsRefStr, EnumString, EnumIter))]
172#[strum_discriminants(strum(serialize_all = "kebab-case"))]
173pub enum Resolution<P> {
174    Resolved(Target<P>),
175    Missing(Missing<P>),
176    TypeMismatch(Target<P>),
177    UnsupportedTarget(UnsupportedTarget<P>),
178    UnsupportedSemantics(UnsupportedSemantics<P>),
179    UnsupportedVersion(VersionScope<P>),
180    Invalid(InvalidReference),
181    External(ExternalReference),
182}
183
184impl<P> Resolution<P> {
185    #[must_use]
186    pub const fn is_lfs_pointer(&self) -> bool {
187        match self {
188            Self::Resolved(target) | Self::TypeMismatch(target) => target.is_lfs_pointer(),
189            Self::UnsupportedSemantics(semantics) => semantics.is_lfs_pointer(),
190            Self::Missing(_)
191            | Self::UnsupportedTarget(_)
192            | Self::UnsupportedVersion(_)
193            | Self::Invalid(_)
194            | Self::External(_) => false,
195        }
196    }
197}