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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use std::{
    borrow::Cow,
    fmt::{self, Display},
};

/// A type reference
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum TypeRef {
    /// Named type
    Named(Cow<'static, str>),
    /// Non-null type
    NonNull(Box<TypeRef>),
    /// List type
    List(Box<TypeRef>),
}

impl Display for TypeRef {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            TypeRef::Named(name) => write!(f, "{}", name),
            TypeRef::NonNull(ty) => write!(f, "{}!", ty),
            TypeRef::List(ty) => write!(f, "[{}]", ty),
        }
    }
}

impl TypeRef {
    /// A int scalar type
    pub const INT: &'static str = "Int";

    /// A float scalar type
    pub const FLOAT: &'static str = "Float";

    /// A string scalar type
    pub const STRING: &'static str = "String";

    /// A boolean scalar type
    pub const BOOLEAN: &'static str = "Boolean";

    /// A ID scalar type
    pub const ID: &'static str = "ID";

    /// A Upload type
    pub const UPLOAD: &'static str = "Upload";

    /// Returns the nullable type reference
    ///
    /// GraphQL Type: `T`
    #[inline]
    pub fn named(type_name: impl Into<String>) -> TypeRef {
        TypeRef::Named(type_name.into().into())
    }

    /// Returns the non-null type reference
    ///
    /// GraphQL Type: `T!`
    #[inline]
    pub fn named_nn(type_name: impl Into<String>) -> TypeRef {
        TypeRef::NonNull(Box::new(TypeRef::Named(type_name.into().into())))
    }

    /// Returns a nullable list of nullable members type reference
    ///
    /// GraphQL Type: `[T]`
    #[inline]
    pub fn named_list(type_name: impl Into<String>) -> TypeRef {
        TypeRef::List(Box::new(TypeRef::Named(type_name.into().into())))
    }

    /// Returns a nullable list of non-null members type reference
    ///
    /// GraphQL Type: `[T!]`
    #[inline]
    pub fn named_nn_list(type_name: impl Into<String>) -> TypeRef {
        TypeRef::List(Box::new(TypeRef::NonNull(Box::new(TypeRef::Named(
            type_name.into().into(),
        )))))
    }

    /// Returns a non-null list of nullable members type reference
    ///
    /// GraphQL Type: `[T]!`
    #[inline]
    pub fn named_list_nn(type_name: impl Into<String>) -> TypeRef {
        TypeRef::NonNull(Box::new(TypeRef::List(Box::new(TypeRef::Named(
            type_name.into().into(),
        )))))
    }

    /// Returns a non-null list of non-null members type reference
    ///
    /// GraphQL Type: `[T!]!`
    #[inline]
    pub fn named_nn_list_nn(type_name: impl Into<String>) -> TypeRef {
        TypeRef::NonNull(Box::new(TypeRef::List(Box::new(TypeRef::NonNull(
            Box::new(TypeRef::Named(type_name.into().into())),
        )))))
    }

    /// Returns the type name
    ///
    /// `[Foo!]` -> `Foo`
    #[inline(always)]
    pub fn type_name(&self) -> &str {
        match self {
            TypeRef::Named(name) => name,
            TypeRef::NonNull(inner) => inner.type_name(),
            TypeRef::List(inner) => inner.type_name(),
        }
    }

    #[inline]
    pub(crate) fn is_nullable(&self) -> bool {
        match self {
            TypeRef::Named(_) => true,
            TypeRef::NonNull(_) => false,
            TypeRef::List(_) => true,
        }
    }

    pub(crate) fn is_subtype(&self, sub: &TypeRef) -> bool {
        fn is_subtype(cur: &TypeRef, sub: &TypeRef) -> bool {
            match (cur, sub) {
                (TypeRef::NonNull(super_type), TypeRef::NonNull(sub_type)) => {
                    is_subtype(&super_type, &sub_type)
                }
                (_, TypeRef::NonNull(sub_type)) => is_subtype(cur, &sub_type),
                (TypeRef::Named(super_type), TypeRef::Named(sub_type)) => super_type == sub_type,
                (TypeRef::List(super_type), TypeRef::List(sub_type)) => {
                    is_subtype(super_type, sub_type)
                }
                _ => false,
            }
        }

        is_subtype(self, sub)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn create() {
        assert_eq!(TypeRef::named("MyObj").to_string(), "MyObj");
        assert_eq!(TypeRef::named_nn("MyObj").to_string(), "MyObj!");
        assert_eq!(TypeRef::named_list("MyObj").to_string(), "[MyObj]");
        assert_eq!(TypeRef::named_list_nn("MyObj").to_string(), "[MyObj]!");
        assert_eq!(TypeRef::named_nn_list("MyObj").to_string(), "[MyObj!]");
        assert_eq!(TypeRef::named_nn_list_nn("MyObj").to_string(), "[MyObj!]!");
    }
}