dropshot_api_manager_types/
validation.rs1use crate::{ManagedApiMetadata, Versions};
4use camino::Utf8PathBuf;
5use std::{fmt, ops::Deref};
6
7pub struct ValidationContext<'a> {
9 backend: &'a mut dyn ValidationBackend,
10}
11
12impl<'a> ValidationContext<'a> {
13 #[doc(hidden)]
15 pub fn new(backend: &'a mut dyn ValidationBackend) -> Self {
16 Self { backend }
17 }
18
19 pub fn ident(&self) -> &ApiIdent {
24 self.backend.ident()
25 }
26
27 pub fn file_name(&self) -> &ApiDocFileName {
32 self.backend.file_name()
33 }
34
35 pub fn is_latest(&self) -> bool {
42 self.backend.is_latest()
43 }
44
45 pub fn is_blessed(&self) -> Option<bool> {
48 self.backend.is_blessed()
49 }
50
51 pub fn versions(&self) -> &Versions {
53 self.backend.versions()
54 }
55
56 pub fn title(&self) -> &str {
58 self.backend.title()
59 }
60
61 pub fn metadata(&self) -> &ManagedApiMetadata {
63 self.backend.metadata()
64 }
65
66 pub fn report_error(&mut self, error: anyhow::Error) {
68 self.backend.report_error(error);
69 }
70
71 pub fn record_file_contents(
79 &mut self,
80 path: impl Into<Utf8PathBuf>,
81 contents: Vec<u8>,
82 ) {
83 self.backend.record_file_contents(path.into(), contents);
84 }
85}
86
87#[doc(hidden)]
91pub trait ValidationBackend {
92 fn ident(&self) -> &ApiIdent;
93 fn file_name(&self) -> &ApiDocFileName;
94 fn versions(&self) -> &Versions;
95 fn is_latest(&self) -> bool;
96 fn is_blessed(&self) -> Option<bool>;
97 fn title(&self) -> &str;
98 fn metadata(&self) -> &ManagedApiMetadata;
99 fn report_error(&mut self, error: anyhow::Error);
100 fn record_file_contents(&mut self, path: Utf8PathBuf, contents: Vec<u8>);
101}
102
103#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
108pub struct LockstepApiDocFileName {
109 ident: ApiIdent,
110}
111
112impl LockstepApiDocFileName {
113 pub fn new(ident: ApiIdent) -> Self {
115 Self { ident }
116 }
117
118 pub fn ident(&self) -> &ApiIdent {
120 &self.ident
121 }
122
123 pub fn path(&self) -> Utf8PathBuf {
126 Utf8PathBuf::from(self.basename())
127 }
128
129 pub fn basename(&self) -> String {
131 format!("{}.json", self.ident)
132 }
133}
134
135impl fmt::Display for LockstepApiDocFileName {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 f.write_str(&self.basename())
139 }
140}
141
142#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
148pub struct VersionedApiDocFileName {
149 ident: ApiIdent,
150 version: semver::Version,
151 hash: String,
152 kind: VersionedApiDocKind,
153}
154
155impl VersionedApiDocFileName {
156 pub fn new(
158 ident: ApiIdent,
159 version: semver::Version,
160 hash: String,
161 ) -> Self {
162 Self { ident, version, hash, kind: VersionedApiDocKind::Json }
163 }
164
165 pub fn new_git_stub(
167 ident: ApiIdent,
168 version: semver::Version,
169 hash: String,
170 ) -> Self {
171 Self { ident, version, hash, kind: VersionedApiDocKind::GitStub }
172 }
173
174 pub fn ident(&self) -> &ApiIdent {
176 &self.ident
177 }
178
179 pub fn version(&self) -> &semver::Version {
181 &self.version
182 }
183
184 pub fn hash(&self) -> &str {
186 &self.hash
187 }
188
189 pub fn kind(&self) -> VersionedApiDocKind {
191 self.kind
192 }
193
194 pub fn is_git_stub(&self) -> bool {
196 self.kind == VersionedApiDocKind::GitStub
197 }
198
199 pub fn path(&self) -> Utf8PathBuf {
204 Utf8PathBuf::from(format!("{}/{}", self.ident, self.basename()))
205 }
206
207 pub fn basename(&self) -> String {
209 self.basename_for_kind(self.kind)
210 }
211
212 fn basename_for_kind(&self, kind: VersionedApiDocKind) -> String {
214 match kind {
215 VersionedApiDocKind::Json => {
216 format!("{}-{}-{}.json", self.ident, self.version, self.hash)
217 }
218 VersionedApiDocKind::GitStub => {
219 format!(
220 "{}-{}-{}.json.gitstub",
221 self.ident, self.version, self.hash
222 )
223 }
224 }
225 }
226
227 fn with_kind(&self, kind: VersionedApiDocKind) -> Self {
229 Self {
230 ident: self.ident.clone(),
231 version: self.version.clone(),
232 hash: self.hash.clone(),
233 kind,
234 }
235 }
236
237 pub fn to_json(&self) -> Self {
241 self.with_kind(VersionedApiDocKind::Json)
242 }
243
244 pub fn to_git_stub(&self) -> Self {
248 self.with_kind(VersionedApiDocKind::GitStub)
249 }
250
251 pub fn git_stub_basename(&self) -> String {
256 self.basename_for_kind(VersionedApiDocKind::GitStub)
257 }
258
259 pub fn json_basename(&self) -> String {
264 self.basename_for_kind(VersionedApiDocKind::Json)
265 }
266}
267
268impl fmt::Display for VersionedApiDocFileName {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 write!(f, "{}/{}", self.ident, self.basename())
272 }
273}
274
275#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)]
277pub enum VersionedApiDocKind {
278 Json,
281 GitStub,
287}
288
289#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
292pub enum ApiDocFileName {
293 Lockstep(LockstepApiDocFileName),
295 Versioned(VersionedApiDocFileName),
297}
298
299impl fmt::Display for ApiDocFileName {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 match self {
302 ApiDocFileName::Lockstep(l) => fmt::Display::fmt(l, f),
303 ApiDocFileName::Versioned(v) => fmt::Display::fmt(v, f),
304 }
305 }
306}
307
308impl ApiDocFileName {
309 pub fn ident(&self) -> &ApiIdent {
311 match self {
312 ApiDocFileName::Lockstep(l) => l.ident(),
313 ApiDocFileName::Versioned(v) => v.ident(),
314 }
315 }
316
317 pub fn path(&self) -> Utf8PathBuf {
320 match self {
321 ApiDocFileName::Lockstep(l) => l.path(),
322 ApiDocFileName::Versioned(v) => v.path(),
323 }
324 }
325
326 pub fn basename(&self) -> String {
328 match self {
329 ApiDocFileName::Lockstep(l) => l.basename(),
330 ApiDocFileName::Versioned(v) => v.basename(),
331 }
332 }
333
334 pub fn version(&self) -> Option<&semver::Version> {
336 match self {
337 ApiDocFileName::Lockstep(_) => None,
338 ApiDocFileName::Versioned(v) => Some(v.version()),
339 }
340 }
341
342 pub fn hash(&self) -> Option<&str> {
344 match self {
345 ApiDocFileName::Lockstep(_) => None,
346 ApiDocFileName::Versioned(v) => Some(v.hash()),
347 }
348 }
349
350 pub fn is_git_stub(&self) -> bool {
352 match self {
353 ApiDocFileName::Lockstep(_) => false,
354 ApiDocFileName::Versioned(v) => v.is_git_stub(),
355 }
356 }
357
358 pub fn versioned_kind(&self) -> Option<VersionedApiDocKind> {
360 match self {
361 ApiDocFileName::Lockstep(_) => None,
362 ApiDocFileName::Versioned(v) => Some(v.kind()),
363 }
364 }
365
366 pub fn to_json_filename(&self) -> ApiDocFileName {
370 match self {
371 ApiDocFileName::Lockstep(_) => self.clone(),
372 ApiDocFileName::Versioned(v) => {
373 ApiDocFileName::Versioned(v.to_json())
374 }
375 }
376 }
377
378 pub fn to_git_stub_filename(&self) -> ApiDocFileName {
384 match self {
385 ApiDocFileName::Lockstep(_) => self.clone(),
386 ApiDocFileName::Versioned(v) => {
387 ApiDocFileName::Versioned(v.to_git_stub())
388 }
389 }
390 }
391
392 pub fn git_stub_basename(&self) -> String {
399 match self {
400 ApiDocFileName::Lockstep(l) => l.basename(),
401 ApiDocFileName::Versioned(v) => v.git_stub_basename(),
402 }
403 }
404
405 pub fn json_basename(&self) -> String {
411 match self {
412 ApiDocFileName::Lockstep(l) => l.basename(),
413 ApiDocFileName::Versioned(v) => v.json_basename(),
414 }
415 }
416
417 pub fn as_versioned(&self) -> Option<&VersionedApiDocFileName> {
420 match self {
421 ApiDocFileName::Lockstep(_) => None,
422 ApiDocFileName::Versioned(v) => Some(v),
423 }
424 }
425
426 pub fn into_versioned(self) -> Option<VersionedApiDocFileName> {
429 match self {
430 ApiDocFileName::Lockstep(_) => None,
431 ApiDocFileName::Versioned(v) => Some(v),
432 }
433 }
434
435 pub fn as_lockstep(&self) -> Option<&LockstepApiDocFileName> {
438 match self {
439 ApiDocFileName::Lockstep(l) => Some(l),
440 ApiDocFileName::Versioned(_) => None,
441 }
442 }
443
444 pub fn into_lockstep(self) -> Option<LockstepApiDocFileName> {
447 match self {
448 ApiDocFileName::Lockstep(l) => Some(l),
449 ApiDocFileName::Versioned(_) => None,
450 }
451 }
452}
453
454impl From<LockstepApiDocFileName> for ApiDocFileName {
455 fn from(l: LockstepApiDocFileName) -> Self {
456 ApiDocFileName::Lockstep(l)
457 }
458}
459
460impl From<VersionedApiDocFileName> for ApiDocFileName {
461 fn from(v: VersionedApiDocFileName) -> Self {
462 ApiDocFileName::Versioned(v)
463 }
464}
465
466#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
468pub struct ApiIdent(String);
469
470impl fmt::Debug for ApiIdent {
471 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472 self.0.fmt(f)
473 }
474}
475
476impl Deref for ApiIdent {
477 type Target = String;
478
479 fn deref(&self) -> &Self::Target {
480 &self.0
481 }
482}
483
484impl fmt::Display for ApiIdent {
485 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
486 self.0.fmt(f)
487 }
488}
489
490impl<S: Into<String>> From<S> for ApiIdent {
491 fn from(value: S) -> Self {
492 Self(value.into())
493 }
494}
495
496impl ApiIdent {
497 pub fn versioned_api_latest_symlink(&self) -> String {
499 format!("{self}-latest.json")
500 }
501
502 pub fn versioned_api_is_latest_symlink(&self, base_name: &str) -> bool {
505 base_name
506 .strip_prefix(self.0.as_str())
507 .is_some_and(|rest| rest == "-latest.json")
508 }
509}