1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
use core::fmt;
use std::borrow::Cow;

use specta_serde::SerdeError;
use thiserror::Error;

use specta::ImplLocation;

use super::ExportPath;

/// Describes where an error occurred.
#[derive(Error, Debug, PartialEq)]
pub enum NamedLocation {
    Type,
    Field,
    Variant,
}

impl fmt::Display for NamedLocation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Type => write!(f, "type"),
            Self::Field => write!(f, "field"),
            Self::Variant => write!(f, "variant"),
        }
    }
}

/// The error type for the TypeScript exporter.
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum ExportError {
    #[error("Attempted to export '{0}' but Specta configuration forbids exporting BigInt types (i64, u64, i128, u128) because we don't know if your se/deserializer supports it. You can change this behavior by editing your `ExportConfiguration`!")]
    BigIntForbidden(ExportPath),
    #[error("Serde error: {0}")]
    Serde(#[from] SerdeError),
    // #[error("Attempted to export '{0}' but was unable to export a tagged type which is unnamed")]
    // UnableToTagUnnamedType(ExportPath),
    #[error("Attempted to export '{1}' but was unable to due to {0} name '{2}' conflicting with a reserved keyword in Typescript. Try renaming it or using `#[specta(rename = \"new name\")]`")]
    ForbiddenName(NamedLocation, ExportPath, &'static str),
    #[error("Attempted to export '{1}' but was unable to due to {0} name '{2}' containing an invalid character")]
    InvalidName(NamedLocation, ExportPath, String),
    #[error("Attempted to export '{0}' with tagging but the type is not tagged.")]
    InvalidTagging(ExportPath),
    #[error("Attempted to export '{0}' with internal tagging but the variant is a tuple struct.")]
    InvalidTaggedVariantContainingTupleStruct(ExportPath),
    #[error("Unable to export type named '{0}' from locations '{:?}' '{:?}'", .1.as_str(), .2.as_str())]
    DuplicateTypeName(Cow<'static, str>, ImplLocation, ImplLocation),
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("fmt error: {0}")]
    Fmt(#[from] std::fmt::Error),
    #[error("Failed to export '{0}' due to error: {1}")]
    Other(ExportPath, String),
}

// TODO: This `impl` is cringe
impl PartialEq for ExportError {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::BigIntForbidden(l0), Self::BigIntForbidden(r0)) => l0 == r0,
            (Self::Serde(l0), Self::Serde(r0)) => l0 == r0,
            // (Self::UnableToTagUnnamedType(l0), Self::UnableToTagUnnamedType(r0)) => l0 == r0,
            (Self::ForbiddenName(l0, l1, l2), Self::ForbiddenName(r0, r1, r2)) => {
                l0 == r0 && l1 == r1 && l2 == r2
            }
            (Self::InvalidName(l0, l1, l2), Self::InvalidName(r0, r1, r2)) => {
                l0 == r0 && l1 == r1 && l2 == r2
            }
            (Self::InvalidTagging(l0), Self::InvalidTagging(r0)) => l0 == r0,
            (
                Self::InvalidTaggedVariantContainingTupleStruct(l0),
                Self::InvalidTaggedVariantContainingTupleStruct(r0),
            ) => l0 == r0,
            (Self::DuplicateTypeName(l0, l1, l2), Self::DuplicateTypeName(r0, r1, r2)) => {
                l0 == r0 && l1 == r1 && l2 == r2
            }
            (Self::Io(l0), Self::Io(r0)) => l0.to_string() == r0.to_string(), // This is a bit hacky but it will be fine for usage in unit tests!
            (Self::Other(l0, l1), Self::Other(r0, r1)) => l0 == r0 && l1 == r1,
            _ => false,
        }
    }
}