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) -> &ApiSpecFileName {
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) -> &ApiSpecFileName;
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 LockstepApiSpecFileName {
109 ident: ApiIdent,
110}
111
112impl LockstepApiSpecFileName {
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 LockstepApiSpecFileName {
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 VersionedApiSpecFileName {
149 ident: ApiIdent,
150 version: semver::Version,
151 hash: String,
152 kind: VersionedApiSpecKind,
153}
154
155impl VersionedApiSpecFileName {
156 pub fn new(
158 ident: ApiIdent,
159 version: semver::Version,
160 hash: String,
161 ) -> Self {
162 Self { ident, version, hash, kind: VersionedApiSpecKind::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: VersionedApiSpecKind::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) -> VersionedApiSpecKind {
191 self.kind
192 }
193
194 pub fn is_git_stub(&self) -> bool {
196 self.kind == VersionedApiSpecKind::GitStub
197 }
198
199 pub fn path(&self) -> Utf8PathBuf {
202 Utf8PathBuf::from_iter([self.ident.as_str(), &self.basename()])
203 }
204
205 pub fn basename(&self) -> String {
207 self.basename_for_kind(self.kind)
208 }
209
210 fn basename_for_kind(&self, kind: VersionedApiSpecKind) -> String {
212 match kind {
213 VersionedApiSpecKind::Json => {
214 format!("{}-{}-{}.json", self.ident, self.version, self.hash)
215 }
216 VersionedApiSpecKind::GitStub => {
217 format!(
218 "{}-{}-{}.json.gitstub",
219 self.ident, self.version, self.hash
220 )
221 }
222 }
223 }
224
225 fn with_kind(&self, kind: VersionedApiSpecKind) -> Self {
227 Self {
228 ident: self.ident.clone(),
229 version: self.version.clone(),
230 hash: self.hash.clone(),
231 kind,
232 }
233 }
234
235 pub fn to_json(&self) -> Self {
239 self.with_kind(VersionedApiSpecKind::Json)
240 }
241
242 pub fn to_git_stub(&self) -> Self {
246 self.with_kind(VersionedApiSpecKind::GitStub)
247 }
248
249 pub fn git_stub_basename(&self) -> String {
254 self.basename_for_kind(VersionedApiSpecKind::GitStub)
255 }
256
257 pub fn json_basename(&self) -> String {
262 self.basename_for_kind(VersionedApiSpecKind::Json)
263 }
264}
265
266impl fmt::Display for VersionedApiSpecFileName {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 write!(f, "{}/{}", self.ident, self.basename())
270 }
271}
272
273#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)]
275pub enum VersionedApiSpecKind {
276 Json,
278 GitStub,
284}
285
286#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
289pub enum ApiSpecFileName {
290 Lockstep(LockstepApiSpecFileName),
292 Versioned(VersionedApiSpecFileName),
294}
295
296impl fmt::Display for ApiSpecFileName {
297 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298 match self {
299 ApiSpecFileName::Lockstep(l) => fmt::Display::fmt(l, f),
300 ApiSpecFileName::Versioned(v) => fmt::Display::fmt(v, f),
301 }
302 }
303}
304
305impl ApiSpecFileName {
306 pub fn ident(&self) -> &ApiIdent {
308 match self {
309 ApiSpecFileName::Lockstep(l) => l.ident(),
310 ApiSpecFileName::Versioned(v) => v.ident(),
311 }
312 }
313
314 pub fn path(&self) -> Utf8PathBuf {
317 match self {
318 ApiSpecFileName::Lockstep(l) => l.path(),
319 ApiSpecFileName::Versioned(v) => v.path(),
320 }
321 }
322
323 pub fn basename(&self) -> String {
325 match self {
326 ApiSpecFileName::Lockstep(l) => l.basename(),
327 ApiSpecFileName::Versioned(v) => v.basename(),
328 }
329 }
330
331 pub fn version(&self) -> Option<&semver::Version> {
333 match self {
334 ApiSpecFileName::Lockstep(_) => None,
335 ApiSpecFileName::Versioned(v) => Some(v.version()),
336 }
337 }
338
339 pub fn hash(&self) -> Option<&str> {
341 match self {
342 ApiSpecFileName::Lockstep(_) => None,
343 ApiSpecFileName::Versioned(v) => Some(v.hash()),
344 }
345 }
346
347 pub fn is_git_stub(&self) -> bool {
349 match self {
350 ApiSpecFileName::Lockstep(_) => false,
351 ApiSpecFileName::Versioned(v) => v.is_git_stub(),
352 }
353 }
354
355 pub fn versioned_kind(&self) -> Option<VersionedApiSpecKind> {
357 match self {
358 ApiSpecFileName::Lockstep(_) => None,
359 ApiSpecFileName::Versioned(v) => Some(v.kind()),
360 }
361 }
362
363 pub fn to_json_filename(&self) -> ApiSpecFileName {
367 match self {
368 ApiSpecFileName::Lockstep(_) => self.clone(),
369 ApiSpecFileName::Versioned(v) => {
370 ApiSpecFileName::Versioned(v.to_json())
371 }
372 }
373 }
374
375 pub fn to_git_stub_filename(&self) -> ApiSpecFileName {
381 match self {
382 ApiSpecFileName::Lockstep(_) => self.clone(),
383 ApiSpecFileName::Versioned(v) => {
384 ApiSpecFileName::Versioned(v.to_git_stub())
385 }
386 }
387 }
388
389 pub fn git_stub_basename(&self) -> String {
396 match self {
397 ApiSpecFileName::Lockstep(l) => l.basename(),
398 ApiSpecFileName::Versioned(v) => v.git_stub_basename(),
399 }
400 }
401
402 pub fn json_basename(&self) -> String {
408 match self {
409 ApiSpecFileName::Lockstep(l) => l.basename(),
410 ApiSpecFileName::Versioned(v) => v.json_basename(),
411 }
412 }
413
414 pub fn as_versioned(&self) -> Option<&VersionedApiSpecFileName> {
417 match self {
418 ApiSpecFileName::Lockstep(_) => None,
419 ApiSpecFileName::Versioned(v) => Some(v),
420 }
421 }
422
423 pub fn into_versioned(self) -> Option<VersionedApiSpecFileName> {
426 match self {
427 ApiSpecFileName::Lockstep(_) => None,
428 ApiSpecFileName::Versioned(v) => Some(v),
429 }
430 }
431
432 pub fn as_lockstep(&self) -> Option<&LockstepApiSpecFileName> {
435 match self {
436 ApiSpecFileName::Lockstep(l) => Some(l),
437 ApiSpecFileName::Versioned(_) => None,
438 }
439 }
440
441 pub fn into_lockstep(self) -> Option<LockstepApiSpecFileName> {
444 match self {
445 ApiSpecFileName::Lockstep(l) => Some(l),
446 ApiSpecFileName::Versioned(_) => None,
447 }
448 }
449}
450
451impl From<LockstepApiSpecFileName> for ApiSpecFileName {
452 fn from(l: LockstepApiSpecFileName) -> Self {
453 ApiSpecFileName::Lockstep(l)
454 }
455}
456
457impl From<VersionedApiSpecFileName> for ApiSpecFileName {
458 fn from(v: VersionedApiSpecFileName) -> Self {
459 ApiSpecFileName::Versioned(v)
460 }
461}
462
463#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
465pub struct ApiIdent(String);
466
467impl fmt::Debug for ApiIdent {
468 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469 self.0.fmt(f)
470 }
471}
472
473impl Deref for ApiIdent {
474 type Target = String;
475
476 fn deref(&self) -> &Self::Target {
477 &self.0
478 }
479}
480
481impl fmt::Display for ApiIdent {
482 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
483 self.0.fmt(f)
484 }
485}
486
487impl<S: Into<String>> From<S> for ApiIdent {
488 fn from(value: S) -> Self {
489 Self(value.into())
490 }
491}
492
493impl ApiIdent {
494 pub fn versioned_api_latest_symlink(&self) -> String {
496 format!("{self}-latest.json")
497 }
498
499 pub fn versioned_api_is_latest_symlink(&self, base_name: &str) -> bool {
502 base_name
503 .strip_prefix(self.0.as_str())
504 .is_some_and(|rest| rest == "-latest.json")
505 }
506}