use super::{
Diagnostic, DiagnosticName, DiagnosticNameRefusal, DiagnosticSeats, Family, IntrinsicRefused,
Line, LineSite, Observed, Phase, Placement, Refused, RelatedIdentity, RelatedSet, Repair,
Route, Site, SiteCoordinate,
};
use crate::bounded::{Bounded, Capping};
use crate::diagnostic::project::{composed, witnessed};
use crate::identity::{
Contract, Identity, RelatedBody, RelatedIssue, Role, ServiceEntry, Transcript, encode_bytes,
};
use crate::request::Door;
use crate::token::{SourceCoordinate, SpanHandle};
impl Family {
#[must_use]
pub const fn declared(name: &'static str) -> Self {
assert!(
interior_separator(name.as_bytes()),
"a family name is namespace/stem, with material on both sides"
);
Self(name)
}
#[must_use]
pub const fn name(self) -> &'static str {
self.0
}
}
impl DiagnosticName {
pub const fn declared(name: &'static str) -> Result<Self, DiagnosticNameRefusal> {
if name.is_empty() {
return Err(DiagnosticNameRefusal::Empty);
}
if !diagnostic_name_is_kebab_case(name.as_bytes()) {
return Err(DiagnosticNameRefusal::NotKebabCase);
}
Ok(Self(name))
}
#[must_use]
pub const fn spelling(self) -> &'static str {
self.0
}
}
const fn diagnostic_name_is_kebab_case(name: &[u8]) -> bool {
let mut rest = name;
let mut separator = true;
while let Some((byte, remaining)) = rest.split_first() {
if *byte == b'-' {
if separator {
return false;
}
separator = true;
} else if byte.is_ascii_lowercase() || byte.is_ascii_digit() {
separator = false;
} else {
return false;
}
rest = remaining;
}
!separator
}
const fn interior_separator(name: &[u8]) -> bool {
let mut rest = name;
let mut ahead = 0_usize;
while let Some((byte, remaining)) = rest.split_first() {
if *byte == b'/' {
return ahead != 0 && !remaining.is_empty();
}
ahead = ahead.saturating_add(1);
rest = remaining;
}
false
}
fn related_content(family: Family, material: &[u8]) -> Vec<u8> {
let mut content = Vec::new();
encode_bytes(family.name().as_bytes(), &mut content);
encode_bytes(material, &mut content);
content
}
fn issue_identity(family: Family, material: &[u8]) -> Identity<RelatedIssue> {
Identity::derived(Transcript::rooted(
Role::DiagnosticRelation,
&related_content(family, material),
0,
))
}
fn body_identity(family: Family, material: &[u8]) -> Identity<RelatedBody> {
Identity::derived(Transcript::rooted(
Role::DiagnosticRelation,
&related_content(family, material),
0,
))
}
impl RelatedSet {
#[must_use]
pub fn derived_over(family: Family, issues: &[Vec<u8>]) -> Self {
if issues.is_empty() {
return Self::nothing_enumerated();
}
let mut body_material = Vec::new();
let mut per_issue = Vec::with_capacity(issues.len());
for issue in issues {
per_issue.push(RelatedIdentity::Issue(issue_identity(family, issue)));
encode_bytes(issue, &mut body_material);
}
let body = RelatedIdentity::Body(body_identity(family, &body_material));
let mut all = Vec::with_capacity(per_issue.len().saturating_add(1));
all.push(body);
all.append(&mut per_issue);
match Bounded::new(all) {
Ok(carried) => Self {
carried,
capping: Capping::Complete,
},
Err(_) => Self {
carried: Bounded::from_array([body]),
capping: Capping::Truncated {
omitted: issues.len(),
},
},
}
}
#[must_use]
pub const fn nothing_enumerated() -> Self {
Self {
carried: Bounded::empty(),
capping: Capping::Complete,
}
}
#[must_use]
pub fn carried(&self) -> &[RelatedIdentity] {
self.carried.as_slice()
}
#[must_use]
pub const fn capping(&self) -> Capping {
self.capping
}
}
impl Site {
pub const fn at_token(token: SpanHandle, coordinate: SiteCoordinate) -> Self {
Self::AtToken { token, coordinate }
}
pub const fn before_capture(coordinate: SourceCoordinate) -> Self {
Self::BeforeCapture { coordinate }
}
pub const fn whole_declaration() -> Self {
Self::WholeDeclaration
}
#[must_use]
pub const fn token(self) -> Option<SpanHandle> {
match self {
Self::AtToken { token, .. } => Some(token),
Self::BeforeCapture { .. } | Self::WholeDeclaration => None,
}
}
#[must_use]
pub const fn coordinate(self) -> Option<SiteCoordinate> {
match self {
Self::AtToken { coordinate, .. } => Some(coordinate),
Self::BeforeCapture { coordinate } => Some(SiteCoordinate::Resolved(coordinate)),
Self::WholeDeclaration => None,
}
}
}
impl Route {
pub(crate) const fn through(entry: Identity<ServiceEntry>) -> Self {
Self { entry }
}
#[must_use]
pub const fn entry(self) -> Identity<ServiceEntry> {
self.entry
}
}
impl Diagnostic {
pub fn refused<E: Refused>(refusal: &E, door: &Door, placement: &Placement<'_>) -> Self {
compose_diagnostic(refusal, door, placement_site(placement))
}
#[must_use]
pub const fn phase(&self) -> Phase {
self.phase
}
pub const fn site(&self) -> Site {
self.site
}
#[must_use]
pub fn summary(&self) -> &str {
&self.carried.summary
}
#[must_use]
pub fn expected(&self) -> Identity<Contract> {
self.carried.expected
}
#[must_use]
pub const fn observed(&self) -> Observed {
self.observed
}
#[must_use]
pub fn related(&self) -> &RelatedSet {
&self.carried.related
}
#[must_use]
pub fn repairs(&self) -> &[Repair] {
self.carried.repairs.as_slice()
}
#[must_use]
pub fn route(&self) -> Route {
self.carried.route
}
}
pub(crate) fn intrinsic_diagnostic<E: IntrinsicRefused>(refusal: &E, door: &Door) -> Diagnostic {
compose_diagnostic(refusal, door, refusal.site())
}
fn compose_diagnostic<E: Refused>(refusal: &E, door: &Door, site: Site) -> Diagnostic {
let related = RelatedSet::derived_over(E::FAMILY, &refusal.related());
let first = refusal.first();
let line = Line {
class: refusal.class(),
first: &first,
body: refusal.body(),
};
let composed_line = composed(door, &line, placement_line_site(site));
Diagnostic {
phase: E::PHASE,
site,
observed: refusal.observed(),
carried: Box::new(DiagnosticSeats {
summary: witnessed(&composed_line, related.capping()),
expected: door.grammar(),
related,
repairs: refusal.repairs(),
route: Route::through(door.entry()),
}),
}
}
fn placement_site(placement: &Placement<'_>) -> Site {
match *placement {
Placement::WholeDeclaration => Site::whole_declaration(),
Placement::AtToken { token, spans } => {
Site::at_token(token, SiteCoordinate::answered(spans.coordinate_of(token)))
}
}
}
fn placement_line_site(site: Site) -> LineSite {
match site {
Site::WholeDeclaration => LineSite::WholeDeclaration,
Site::AtToken { coordinate, .. } => LineSite::At(coordinate),
Site::BeforeCapture { coordinate } => LineSite::At(SiteCoordinate::Resolved(coordinate)),
}
}