graphql: Add __typename fields as part of generating the API schema
@@ -11,6 +11,8 @@ pub enum APISchemaError {
TypeExists(String),
#[fail(display = "Type {} not found", _0)]
TypeNotFound(String),
+ #[fail(display = "Type {} must not have a __typename field", _0)]
+ TypenameFieldExists(String),
}
/// Derives a full-fledged GraphQL API schema from an input schema.
@@ -30,6 +32,7 @@ pub fn api_schema(input_schema: &Document) -> Result<Document, APISchemaError> {
add_types_for_interface_types(&mut schema, &interface_types)?;
add_query_type(&mut schema, &object_types, &interface_types)?;
add_subscription_type(&mut schema, &object_types, &interface_types)?;
+ add_typename_fields(&mut schema)?;
Ok(schema)
}
@@ -395,6 +398,62 @@ fn query_fields_for_type(_schema: &Document, type_name: &Name) -> Vec<Field> {
]
}
+fn add_typename_fields(schema: &mut Document) -> Result<(), APISchemaError> {
+ for object_type in ast::get_object_type_definitions_mut(schema) {
+ add_typename_field_to_object_type(object_type)?;
+ }
+
+ for interface_type in ast::get_interface_type_definitions_mut(schema) {
+ add_typename_field_to_interface_type(interface_type)?;
+ }
+
+ Ok(())
+}
+
+fn add_typename_field_to_object_type(object_type: &mut ObjectType) -> Result<(), APISchemaError> {
+ // Fail if this type already has a __typename field
+ if ast::get_field_type(object_type, &String::from("__typename")).is_some() {
+ return Err(APISchemaError::TypenameFieldExists(
+ object_type.name.to_string(),
+ ));
+ }
+
+ // Add the __typename: String! field
+ object_type.fields.push(Field {
+ position: Pos::default(),
+ description: None,
+ name: String::from("__typename"),
+ arguments: vec![],
+ field_type: Type::NonNullType(Box::new(Type::NamedType(String::from("String")))),
+ directives: vec![],
+ });
+
+ Ok(())
+}
+
+fn add_typename_field_to_interface_type(
+ interface_type: &mut InterfaceType,
+) -> Result<(), APISchemaError> {
+ // Fail if this type already has a __typename field
+ if ast::get_interface_field_type(interface_type, &String::from("__typename")).is_some() {
+ return Err(APISchemaError::TypenameFieldExists(
+ interface_type.name.to_string(),
+ ));
+ }
+
+ // Add the __typename: String! field
+ interface_type.fields.push(Field {
+ position: Pos::default(),
+ description: None,
+ name: String::from("__typename"),
+ arguments: vec![],
+ field_type: Type::NonNullType(Box::new(Type::NamedType(String::from("String")))),
+ directives: vec![],
+ });
+
+ Ok(())
+}
+
#[cfg(test)]
mod tests {
use graphql_parser::schema::*;
