use crate::{ManagedApiMetadata, Versions};
use camino::Utf8PathBuf;
use std::{fmt, ops::Deref};
pub struct ValidationContext<'a> {
backend: &'a mut dyn ValidationBackend,
}
impl<'a> ValidationContext<'a> {
#[doc(hidden)]
pub fn new(backend: &'a mut dyn ValidationBackend) -> Self {
Self { backend }
}
pub fn ident(&self) -> &ApiIdent {
self.backend.ident()
}
pub fn file_name(&self) -> &ApiSpecFileName {
self.backend.file_name()
}
pub fn is_latest(&self) -> bool {
self.backend.is_latest()
}
pub fn is_blessed(&self) -> Option<bool> {
self.backend.is_blessed()
}
pub fn versions(&self) -> &Versions {
self.backend.versions()
}
pub fn title(&self) -> &str {
self.backend.title()
}
pub fn metadata(&self) -> &ManagedApiMetadata {
self.backend.metadata()
}
pub fn report_error(&mut self, error: anyhow::Error) {
self.backend.report_error(error);
}
pub fn record_file_contents(
&mut self,
path: impl Into<Utf8PathBuf>,
contents: Vec<u8>,
) {
self.backend.record_file_contents(path.into(), contents);
}
}
#[doc(hidden)]
pub trait ValidationBackend {
fn ident(&self) -> &ApiIdent;
fn file_name(&self) -> &ApiSpecFileName;
fn versions(&self) -> &Versions;
fn is_latest(&self) -> bool;
fn is_blessed(&self) -> Option<bool>;
fn title(&self) -> &str;
fn metadata(&self) -> &ManagedApiMetadata;
fn report_error(&mut self, error: anyhow::Error);
fn record_file_contents(&mut self, path: Utf8PathBuf, contents: Vec<u8>);
}
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub struct LockstepApiSpecFileName {
ident: ApiIdent,
}
impl LockstepApiSpecFileName {
pub fn new(ident: ApiIdent) -> Self {
Self { ident }
}
pub fn ident(&self) -> &ApiIdent {
&self.ident
}
pub fn path(&self) -> Utf8PathBuf {
Utf8PathBuf::from(self.basename())
}
pub fn basename(&self) -> String {
format!("{}.json", self.ident)
}
}
impl fmt::Display for LockstepApiSpecFileName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.basename())
}
}
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub struct VersionedApiSpecFileName {
ident: ApiIdent,
version: semver::Version,
hash: String,
kind: VersionedApiSpecKind,
}
impl VersionedApiSpecFileName {
pub fn new(
ident: ApiIdent,
version: semver::Version,
hash: String,
) -> Self {
Self { ident, version, hash, kind: VersionedApiSpecKind::Json }
}
pub fn new_git_stub(
ident: ApiIdent,
version: semver::Version,
hash: String,
) -> Self {
Self { ident, version, hash, kind: VersionedApiSpecKind::GitStub }
}
pub fn ident(&self) -> &ApiIdent {
&self.ident
}
pub fn version(&self) -> &semver::Version {
&self.version
}
pub fn hash(&self) -> &str {
&self.hash
}
pub fn kind(&self) -> VersionedApiSpecKind {
self.kind
}
pub fn is_git_stub(&self) -> bool {
self.kind == VersionedApiSpecKind::GitStub
}
pub fn path(&self) -> Utf8PathBuf {
Utf8PathBuf::from_iter([self.ident.as_str(), &self.basename()])
}
pub fn basename(&self) -> String {
self.basename_for_kind(self.kind)
}
fn basename_for_kind(&self, kind: VersionedApiSpecKind) -> String {
match kind {
VersionedApiSpecKind::Json => {
format!("{}-{}-{}.json", self.ident, self.version, self.hash)
}
VersionedApiSpecKind::GitStub => {
format!(
"{}-{}-{}.json.gitstub",
self.ident, self.version, self.hash
)
}
}
}
fn with_kind(&self, kind: VersionedApiSpecKind) -> Self {
Self {
ident: self.ident.clone(),
version: self.version.clone(),
hash: self.hash.clone(),
kind,
}
}
pub fn to_json(&self) -> Self {
self.with_kind(VersionedApiSpecKind::Json)
}
pub fn to_git_stub(&self) -> Self {
self.with_kind(VersionedApiSpecKind::GitStub)
}
pub fn git_stub_basename(&self) -> String {
self.basename_for_kind(VersionedApiSpecKind::GitStub)
}
pub fn json_basename(&self) -> String {
self.basename_for_kind(VersionedApiSpecKind::Json)
}
}
impl fmt::Display for VersionedApiSpecFileName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.ident, self.basename())
}
}
#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub enum VersionedApiSpecKind {
Json,
GitStub,
}
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub enum ApiSpecFileName {
Lockstep(LockstepApiSpecFileName),
Versioned(VersionedApiSpecFileName),
}
impl fmt::Display for ApiSpecFileName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ApiSpecFileName::Lockstep(l) => fmt::Display::fmt(l, f),
ApiSpecFileName::Versioned(v) => fmt::Display::fmt(v, f),
}
}
}
impl ApiSpecFileName {
pub fn ident(&self) -> &ApiIdent {
match self {
ApiSpecFileName::Lockstep(l) => l.ident(),
ApiSpecFileName::Versioned(v) => v.ident(),
}
}
pub fn path(&self) -> Utf8PathBuf {
match self {
ApiSpecFileName::Lockstep(l) => l.path(),
ApiSpecFileName::Versioned(v) => v.path(),
}
}
pub fn basename(&self) -> String {
match self {
ApiSpecFileName::Lockstep(l) => l.basename(),
ApiSpecFileName::Versioned(v) => v.basename(),
}
}
pub fn version(&self) -> Option<&semver::Version> {
match self {
ApiSpecFileName::Lockstep(_) => None,
ApiSpecFileName::Versioned(v) => Some(v.version()),
}
}
pub fn hash(&self) -> Option<&str> {
match self {
ApiSpecFileName::Lockstep(_) => None,
ApiSpecFileName::Versioned(v) => Some(v.hash()),
}
}
pub fn is_git_stub(&self) -> bool {
match self {
ApiSpecFileName::Lockstep(_) => false,
ApiSpecFileName::Versioned(v) => v.is_git_stub(),
}
}
pub fn versioned_kind(&self) -> Option<VersionedApiSpecKind> {
match self {
ApiSpecFileName::Lockstep(_) => None,
ApiSpecFileName::Versioned(v) => Some(v.kind()),
}
}
pub fn to_json_filename(&self) -> ApiSpecFileName {
match self {
ApiSpecFileName::Lockstep(_) => self.clone(),
ApiSpecFileName::Versioned(v) => {
ApiSpecFileName::Versioned(v.to_json())
}
}
}
pub fn to_git_stub_filename(&self) -> ApiSpecFileName {
match self {
ApiSpecFileName::Lockstep(_) => self.clone(),
ApiSpecFileName::Versioned(v) => {
ApiSpecFileName::Versioned(v.to_git_stub())
}
}
}
pub fn git_stub_basename(&self) -> String {
match self {
ApiSpecFileName::Lockstep(l) => l.basename(),
ApiSpecFileName::Versioned(v) => v.git_stub_basename(),
}
}
pub fn json_basename(&self) -> String {
match self {
ApiSpecFileName::Lockstep(l) => l.basename(),
ApiSpecFileName::Versioned(v) => v.json_basename(),
}
}
pub fn as_versioned(&self) -> Option<&VersionedApiSpecFileName> {
match self {
ApiSpecFileName::Lockstep(_) => None,
ApiSpecFileName::Versioned(v) => Some(v),
}
}
pub fn into_versioned(self) -> Option<VersionedApiSpecFileName> {
match self {
ApiSpecFileName::Lockstep(_) => None,
ApiSpecFileName::Versioned(v) => Some(v),
}
}
pub fn as_lockstep(&self) -> Option<&LockstepApiSpecFileName> {
match self {
ApiSpecFileName::Lockstep(l) => Some(l),
ApiSpecFileName::Versioned(_) => None,
}
}
pub fn into_lockstep(self) -> Option<LockstepApiSpecFileName> {
match self {
ApiSpecFileName::Lockstep(l) => Some(l),
ApiSpecFileName::Versioned(_) => None,
}
}
}
impl From<LockstepApiSpecFileName> for ApiSpecFileName {
fn from(l: LockstepApiSpecFileName) -> Self {
ApiSpecFileName::Lockstep(l)
}
}
impl From<VersionedApiSpecFileName> for ApiSpecFileName {
fn from(v: VersionedApiSpecFileName) -> Self {
ApiSpecFileName::Versioned(v)
}
}
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct ApiIdent(String);
impl fmt::Debug for ApiIdent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Deref for ApiIdent {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for ApiIdent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<S: Into<String>> From<S> for ApiIdent {
fn from(value: S) -> Self {
Self(value.into())
}
}
impl ApiIdent {
pub fn versioned_api_latest_symlink(&self) -> String {
format!("{self}-latest.json")
}
pub fn versioned_api_is_latest_symlink(&self, base_name: &str) -> bool {
base_name
.strip_prefix(self.0.as_str())
.is_some_and(|rest| rest == "-latest.json")
}
}