use std::fmt;
use crate::desired_state::{ProjectId, ResourceScope, TenantId};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum InvalidToken {
#[error("a {kind} reference must not be empty")]
Empty { kind: &'static str },
#[error("{kind} reference is {length} bytes, over the {max}-byte limit")]
TooLong {
kind: &'static str,
length: usize,
max: usize,
},
#[error("{kind} reference contains {codepoint:#06x}, which is not printable ASCII")]
Unprintable { kind: &'static str, codepoint: u32 },
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Token(String);
impl Token {
pub const MAX_LEN: usize = 128;
fn parse(kind: &'static str, input: &str) -> Result<Self, InvalidToken> {
if input.is_empty() {
return Err(InvalidToken::Empty { kind });
}
if input.len() > Self::MAX_LEN {
return Err(InvalidToken::TooLong {
kind,
length: input.len(),
max: Self::MAX_LEN,
});
}
if let Some(character) = input
.chars()
.find(|c| !c.is_ascii_graphic() || *c == '\u{7f}')
{
return Err(InvalidToken::Unprintable {
kind,
codepoint: u32::from(character),
});
}
Ok(Self(input.to_owned()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
macro_rules! token_ref {
($name:ident, $kind:literal, $doc:literal) => {
#[doc = $doc]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name(Token);
impl $name {
pub const KIND: &'static str = $kind;
pub fn parse(input: &str) -> Result<Self, InvalidToken> {
Token::parse($kind, input).map($name)
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
};
}
token_ref!(
ProviderRef,
"provider",
"Which provider a target belongs to: the `[[provider]]` id today, a catalogue \
provider identity after #192."
);
token_ref!(
ModelRef,
"model",
"The upstream model or deployment a target names. Not a caller-facing alias: \
availability is evaluated over what the gateway would actually call."
);
token_ref!(
CredentialRef,
"credential",
"Which credential an entitlement was decided against. A reference, never \
material, and never carried into a verdict."
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ScopeRef {
pub tenant: TenantId,
pub project: Option<ProjectId>,
}
impl ScopeRef {
pub const fn tenant(tenant: TenantId) -> Self {
Self {
tenant,
project: None,
}
}
pub const fn project(tenant: TenantId, project: ProjectId) -> Self {
Self {
tenant,
project: Some(project),
}
}
pub const fn is_tenant_wide(&self) -> bool {
self.project.is_none()
}
pub const fn of(scope: &ResourceScope) -> Option<Self> {
match scope {
ResourceScope::Deployment => None,
ResourceScope::Tenant(tenant) => Some(Self::tenant(*tenant)),
ResourceScope::Project { tenant, project } => Some(Self::project(*tenant, *project)),
}
}
}
impl fmt::Display for ScopeRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.project {
Some(project) => write!(f, "{}/{project}", self.tenant),
None => write!(f, "{}", self.tenant),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TargetRef {
pub provider: ProviderRef,
pub model: ModelRef,
}
impl TargetRef {
pub const fn new(provider: ProviderRef, model: ModelRef) -> Self {
Self { provider, model }
}
pub fn parse(provider: &str, model: &str) -> Result<Self, InvalidToken> {
Ok(Self::new(
ProviderRef::parse(provider)?,
ModelRef::parse(model)?,
))
}
}
impl fmt::Display for TargetRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.provider, self.model)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AvailabilityKey {
pub scope: ScopeRef,
pub target: TargetRef,
}
impl AvailabilityKey {
pub const fn new(scope: ScopeRef, target: TargetRef) -> Self {
Self { scope, target }
}
}
impl fmt::Display for AvailabilityKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.scope, self.target)
}
}