apollo_encoder/
interface_def.rs

1use std::fmt;
2
3use crate::{Directive, FieldDefinition, StringValue};
4
5/// InterfaceDefinition is an abstract type where there are common fields declared.
6///
7/// Any type that implements an interface must define all the fields with names
8/// and types exactly matching. The implementations of this interface are
9/// explicitly listed out in possibleTypes.
10///
11/// *InterfaceDefTypeDefinition*:
12///     Description? **interface** Name ImplementsInterfaceDefs? Directives?
13///
14/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-InterfaceDef).
15///
16/// ### Example
17/// ```rust
18/// use apollo_encoder::{Type_, FieldDefinition, InterfaceDefinition};
19/// use indoc::indoc;
20///
21/// let ty_1 = Type_::NamedType {
22///     name: "String".to_string(),
23/// };
24///
25/// let ty_2 = Type_::NamedType {
26///     name: "String".to_string(),
27/// };
28///
29/// let ty_3 = Type_::NonNull { ty: Box::new(ty_2) };
30/// let ty_4 = Type_::List { ty: Box::new(ty_3) };
31/// let ty_5 = Type_::NonNull { ty: Box::new(ty_4) };
32///
33/// let ty_6 = Type_::NamedType {
34///     name: "Boolean".to_string(),
35/// };
36///
37/// let mut field_1 = FieldDefinition::new("main".to_string(), ty_1);
38/// field_1.description("Cat's main dish of a meal.".to_string());
39///
40/// let mut field_2 = FieldDefinition::new("snack".to_string(), ty_5);
41/// field_2.description("Cat's post meal snack.".to_string());
42///
43/// let mut field_3 = FieldDefinition::new("pats".to_string(), ty_6);
44/// field_3.description("Does cat get a pat after meal?".to_string());
45///
46/// // a schema definition
47/// let mut interface = InterfaceDefinition::new("Meal".to_string());
48/// interface.description(
49///     "Meal interface for various\nmeals during the day.".to_string(),
50/// );
51/// interface.field(field_1);
52/// interface.field(field_2);
53/// interface.field(field_3);
54///
55/// assert_eq!(
56///     interface.to_string(),
57///     indoc! { r#"
58///     """
59///     Meal interface for various
60///     meals during the day.
61///     """
62///     interface Meal {
63///       "Cat's main dish of a meal."
64///       main: String
65///       "Cat's post meal snack."
66///       snack: [String!]!
67///       "Does cat get a pat after meal?"
68///       pats: Boolean
69///     }
70///     "# }
71/// );
72/// ```
73#[derive(Debug, Clone)]
74pub struct InterfaceDefinition {
75    // Name must return a String.
76    name: String,
77    // Description may return a String or null.
78    description: Option<StringValue>,
79    // The vector of interfaces that this interface implements.
80    interfaces: Vec<String>,
81    // The vector of fields required by this interface.
82    fields: Vec<FieldDefinition>,
83    /// Contains all directives.
84    directives: Vec<Directive>,
85    extend: bool,
86}
87
88impl InterfaceDefinition {
89    /// Create a new instance of InterfaceDef.
90    pub fn new(name: String) -> Self {
91        Self {
92            name,
93            description: None,
94            fields: Vec::new(),
95            interfaces: Vec::new(),
96            directives: Vec::new(),
97            extend: false,
98        }
99    }
100
101    /// Set the schema def's description.
102    pub fn description(&mut self, description: String) {
103        self.description = Some(StringValue::Top {
104            source: description,
105        });
106    }
107
108    /// Set the interfaces ObjectDef implements.
109    pub fn interface(&mut self, interface: String) {
110        self.interfaces.push(interface)
111    }
112
113    /// Set the interface as an extension
114    pub fn extend(&mut self) {
115        self.extend = true;
116    }
117
118    /// Push a Field to schema def's fields vector.
119    pub fn field(&mut self, field: FieldDefinition) {
120        self.fields.push(field)
121    }
122
123    /// Add a directive.
124    pub fn directive(&mut self, directive: Directive) {
125        self.directives.push(directive)
126    }
127}
128
129impl fmt::Display for InterfaceDefinition {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        if self.extend {
132            write!(f, "extend ")?;
133        } else {
134            // No description when it's a extension
135            if let Some(description) = &self.description {
136                writeln!(f, "{description}")?;
137            }
138        }
139
140        write!(f, "interface {}", &self.name)?;
141        for (i, interface) in self.interfaces.iter().enumerate() {
142            match i {
143                0 => write!(f, " implements {interface}")?,
144                _ => write!(f, "& {interface}")?,
145            }
146        }
147        for directive in &self.directives {
148            write!(f, " {directive}")?;
149        }
150
151        write!(f, " {{")?;
152
153        for field in &self.fields {
154            write!(f, "\n{field}")?;
155        }
156        writeln!(f, "\n}}")
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::{Argument, Type_, Value};
164    use indoc::indoc;
165    use pretty_assertions::assert_eq;
166
167    #[test]
168    fn it_encodes_interfaces() {
169        let ty_1 = Type_::NamedType {
170            name: "String".to_string(),
171        };
172
173        let ty_2 = Type_::NamedType {
174            name: "String".to_string(),
175        };
176
177        let ty_3 = Type_::NonNull { ty: Box::new(ty_2) };
178        let ty_4 = Type_::List { ty: Box::new(ty_3) };
179        let ty_5 = Type_::NonNull { ty: Box::new(ty_4) };
180
181        let ty_6 = Type_::NamedType {
182            name: "Boolean".to_string(),
183        };
184
185        let mut field_1 = FieldDefinition::new("main".to_string(), ty_1);
186        field_1.description("Cat's main dish of a meal.".to_string());
187
188        let mut field_2 = FieldDefinition::new("snack".to_string(), ty_5);
189        field_2.description("Cat's post meal snack.".to_string());
190
191        let mut field_3 = FieldDefinition::new("pats".to_string(), ty_6);
192        field_3.description("Does cat get a pat\nafter meal?".to_string());
193
194        let mut directive = Directive::new(String::from("testDirective"));
195        directive.arg(Argument::new(
196            String::from("first"),
197            Value::String("one".to_string()),
198        ));
199
200        // a schema definition
201        let mut interface = InterfaceDefinition::new("Meal".to_string());
202        interface.description("Meal interface for various\nmeals during the day.".to_string());
203        interface.field(field_1);
204        interface.field(field_2);
205        interface.field(field_3);
206        interface.directive(directive);
207
208        assert_eq!(
209            interface.to_string(),
210            indoc! { r#"
211            """
212            Meal interface for various
213            meals during the day.
214            """
215            interface Meal @testDirective(first: "one") {
216              "Cat's main dish of a meal."
217              main: String
218              "Cat's post meal snack."
219              snack: [String!]!
220              """
221              Does cat get a pat
222              after meal?
223              """
224              pats: Boolean
225            }
226            "# }
227        );
228    }
229
230    #[test]
231    fn it_encodes_interface_extension() {
232        let ty_1 = Type_::NamedType {
233            name: "String".to_string(),
234        };
235
236        let ty_2 = Type_::NamedType {
237            name: "String".to_string(),
238        };
239
240        let ty_3 = Type_::NonNull { ty: Box::new(ty_2) };
241        let ty_4 = Type_::List { ty: Box::new(ty_3) };
242        let ty_5 = Type_::NonNull { ty: Box::new(ty_4) };
243
244        let ty_6 = Type_::NamedType {
245            name: "Boolean".to_string(),
246        };
247
248        let mut field_1 = FieldDefinition::new("main".to_string(), ty_1);
249        field_1.description("Cat's main dish of a meal.".to_string());
250
251        let mut field_2 = FieldDefinition::new("snack".to_string(), ty_5);
252        field_2.description("Cat's post meal snack.".to_string());
253
254        let mut field_3 = FieldDefinition::new("pats".to_string(), ty_6);
255        field_3.description("Does cat get a pat\nafter meal?".to_string());
256
257        let mut directive = Directive::new(String::from("testDirective"));
258        directive.arg(Argument::new(
259            String::from("first"),
260            Value::String("one".to_string()),
261        ));
262
263        // a schema definition
264        let mut interface = InterfaceDefinition::new("Meal".to_string());
265        interface.description("Meal interface for various\nmeals during the day.".to_string());
266        interface.field(field_1);
267        interface.field(field_2);
268        interface.field(field_3);
269        interface.directive(directive);
270        interface.extend();
271
272        assert_eq!(
273            interface.to_string(),
274            indoc! { r#"
275            extend interface Meal @testDirective(first: "one") {
276              "Cat's main dish of a meal."
277              main: String
278              "Cat's post meal snack."
279              snack: [String!]!
280              """
281              Does cat get a pat
282              after meal?
283              """
284              pats: Boolean
285            }
286            "# }
287        );
288    }
289}