use crate::TypespaceTrait;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error<Id>
where
Id: std::fmt::Debug + std::fmt::Display,
{
#[error("a type with the id `{type_id}` has already been inserted")]
DuplicateTypeId {
type_id: Id,
},
#[error("cannot build the {kind}: no name was provided")]
MissingTypeName {
kind: &'static str,
},
#[error("the {kind} name `{name}` {message}")]
InvalidName {
kind: &'static str,
name: String,
message: &'static str,
},
#[error("cannot build the enum `{name}`: no tag type was provided")]
MissingTagType {
name: String,
},
#[error(
"cannot build the tuple struct `{name}`: it has no fixed fields, \
which makes it redundant with {alternative}"
)]
FieldlessTupleStruct {
name: String,
alternative: &'static str,
},
#[error(
"in `{type_name}`, the {axis} name `{name}` is used by more \
than one {kind}"
)]
DuplicateItemName {
kind: &'static str,
type_name: String,
name: String,
axis: NameAxis,
},
#[error(
"`{type_name}` denies unknown fields and flattens the property \
`{property}`; serde does not support that combination"
)]
FlattenWithDenyUnknownFields {
type_name: String,
property: String,
},
#[error("the types with ids `{first}` and `{second}` are both named `{name}`")]
DuplicateTypeName {
name: String,
first: Id,
second: Id,
},
#[error("the derive `{derive}` is not a valid Rust path: {message}")]
InvalidDerive {
derive: String,
message: String,
},
#[error(
"the {position} container `{path}` states obligations for \
{declared} type parameter(s), but the {position} position \
renders {parameters}"
)]
ContainerParameterCount {
position: &'static str,
path: String,
declared: usize,
parameters: usize,
},
#[error(
"the {position} container `{path}` answers `unknown` for the \
trait `{trait_}`, which only a native type may do"
)]
ContainerProvisionUnknown {
position: &'static str,
path: String,
trait_: TypespaceTrait,
},
#[error(
"the native type `{path}` states obligations for {declared} \
type parameter(s), but declares {parameters}"
)]
NativeParameterCount {
path: String,
declared: usize,
parameters: usize,
},
#[error(
"the type with id `{type_id}` references the id `{child_id}` \
for which there is no type"
)]
UnknownTypeId {
type_id: Id,
child_id: Id,
},
#[error(
"the {wrapper} with id `{type_id}` wraps `Type::Never`, which \
adds no meaning over `Type::Never` alone"
)]
NeverInTransparentWrapper {
wrapper: &'static str,
type_id: Id,
},
#[error(
"the {position} `{name}` of the type with id `{type_id}` is \
`Type::Never`, which cannot provide the value the position \
requires"
)]
NeverInValuePosition {
position: &'static str,
name: String,
type_id: Id,
},
#[error(
"the anonymous type with id `{type_id}` refers to `{child_id}`, \
closing a cycle that passes through no named type"
)]
AnonymousCycle {
type_id: Id,
child_id: Id,
},
#[error("{}", format_conflicts(conflicts))]
TraitConflicts {
conflicts: Vec<TraitConflict<Id>>,
},
#[error(
"the newtype struct `{name}` states {kind} constraints with \
nothing in them"
)]
VacuousConstraints {
name: String,
kind: &'static str,
},
#[error("the value `{value}` does not fit the type `{id}`: {reason}")]
InvalidDefault {
value: serde_json::Value,
id: Id,
reason: String,
},
#[error(
"the optional-nullable wrapper `{path}` declares `Default: \
{provision:?}`; it must declare `Default: Always`, since every \
optional property rendering through it is exempted from its own \
`Default` obligation on that assumption"
)]
OptionalNullableWrapperDefault {
path: String,
provision: crate::TraitProvision,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NameAxis {
Rust,
Wire,
}
impl std::fmt::Display for NameAxis {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NameAxis::Rust => f.write_str("Rust"),
NameAxis::Wire => f.write_str("wire"),
}
}
}
fn format_conflicts<Id: std::fmt::Display>(conflicts: &[TraitConflict<Id>]) -> String {
conflicts
.iter()
.map(|conflict| conflict.to_string())
.collect::<Vec<_>>()
.join("\n")
}
#[derive(Debug)]
#[non_exhaustive]
pub struct TraitConflict<Id> {
pub required: TypespaceTrait,
pub origin: RequirementOrigin<Id>,
pub path: Vec<PathStep<Id>>,
pub offender: Id,
pub reason: OffenderReason,
}
impl<Id: std::fmt::Display> std::fmt::Display for TraitConflict<Id> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
required,
origin,
path,
offender,
reason,
} = self;
match reason {
OffenderReason::Primitive { type_name } => write!(
f,
"type `{type_name}` (id `{offender}`) cannot implement \
the required trait `{required}`"
)?,
OffenderReason::NativeMissingImpl { type_name } => write!(
f,
"native type `{type_name}` (id `{offender}`) does not \
declare the required trait `{required}`"
)?,
OffenderReason::ContainerMissingImpl { type_name } => write!(
f,
"the configured container `{type_name}` (id \
`{offender}`) does not provide the required trait \
`{required}`"
)?,
OffenderReason::TypeCannotImplement { kind } => write!(
f,
"the generated {kind} with id `{offender}` cannot \
implement the required trait `{required}`"
)?,
OffenderReason::IrrefutableVariantPayload { variant } => write!(
f,
"the untagged enum with id `{offender}` cannot \
implement the required trait `{required}`: its \
`FromStr` would try the variants in order, and the \
payload of its variant `{variant}` parses every string"
)?,
}
for PathStep { type_id, relation } in path.iter().rev() {
write!(
f,
"\n required because `{type_id}` passes the requirement \
to {relation}"
)?;
}
match origin {
RequirementOrigin::ContainerParameter {
container,
relation,
} => {
write!(
f,
"\n required because the container `{container}` \
requires `{required}` of {relation}"
)
}
RequirementOrigin::PropertyDefault(id) => {
write!(
f,
"\n required because a property of `{id}` \
deserializes with `#[serde(default)]` and so must \
implement `{required}`"
)
}
RequirementOrigin::DefaultValue(id) => {
write!(
f,
"\n required because the default value of `{id}` \
needs to construct it; and generated code does so \
by deserializing it"
)
}
RequirementOrigin::GlobalSettings => {
write!(
f,
"\n required because global settings require \
`{required}` of all types"
)
}
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum RequirementOrigin<Id> {
ContainerParameter {
container: Id,
relation: Relation,
},
PropertyDefault(Id),
DefaultValue(Id),
GlobalSettings,
}
#[derive(Debug, Clone)]
pub struct PathStep<Id> {
pub type_id: Id,
pub relation: Relation,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Relation {
Field(String),
Variant(String),
Element,
Key,
Value,
Parameter(usize),
Boxed,
Inner,
Target,
}
impl std::fmt::Display for Relation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Relation::Field(name) => write!(f, "its field `{name}`"),
Relation::Variant(name) => write!(f, "its variant `{name}`"),
Relation::Element => write!(f, "its element type"),
Relation::Key => write!(f, "its key type"),
Relation::Value => write!(f, "its value type"),
Relation::Parameter(index) => write!(f, "its type parameter {index}"),
Relation::Boxed => write!(f, "its boxed type"),
Relation::Inner => write!(f, "its inner type"),
Relation::Target => write!(f, "its target type"),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum OffenderReason {
Primitive {
type_name: String,
},
NativeMissingImpl {
type_name: String,
},
ContainerMissingImpl {
type_name: String,
},
TypeCannotImplement {
kind: &'static str,
},
IrrefutableVariantPayload {
variant: String,
},
}