Repository: apollographql/apollo-rs (rust)

Issue:
add enums to compiler's context
- add enum definition to source database
   - add enum values definition
- add enum definition validation: 
  - error: all enum values must be unique
  - hint: capitalise enum values 

Pull request:
feat(apollo-compiler): add enum definitions to source database
- adds enum definitions, enum values definition, and enum value to source database
- validate enum definitions:
     - enum values must be unique (error)
     - enum values should be capitalised (warning)

closes #235 

Patch:
diff --git a/crates/apollo-compiler/src/diagnostics.rs b/crates/apollo-compiler/src/diagnostics.rs
index b7a8a06..7a51942 100644
--- a/crates/apollo-compiler/src/diagnostics.rs
+++ b/crates/apollo-compiler/src/diagnostics.rs
@@ -30,6 +30,10 @@ pub enum ErrorDiagnostic {
         named_type: String,
         operation_type: String,
     },
+    UniqueValue {
+        message: String,
+        value: String,
+    },
     UnsupportedOperation {
         message: String,
         operation: Option<String>,
@@ -51,6 +55,7 @@ pub enum ErrorDiagnostic {
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub enum WarningDiagnostic {
     UnusedVariable { message: String, variable: String },
+    CapitalizedValue { message: String, value: String },
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
diff --git a/crates/apollo-compiler/src/lib.rs b/crates/apollo-compiler/src/lib.rs
index 9794ca4..6808176 100644
--- a/crates/apollo-compiler/src/lib.rs
+++ b/crates/apollo-compiler/src/lib.rs
@@ -64,6 +64,10 @@ impl ApolloCompiler {
     pub fn scalars(&self) -> Arc<Vec<values::ScalarDefinition>> {
         self.db.scalars()
     }
+
+    pub fn enums(&self) -> Arc<Vec<values::EnumDefinition>> {
+        self.db.enums()
+    }
 }
 
 #[cfg(test)]
@@ -208,4 +212,36 @@ scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
             .collect();
         assert_eq!(directives, ["specifiedBy"]);
     }
+
+    #[test]
+    fn it_accesses_enum_definitions() {
+        let input = r#"
+type Query {
+  pet: Pet,
+}
+
+enum Pet {
+    CAT
+    DOG
+    FOX
+}
+"#;
+
+        let ctx = ApolloCompiler::new(input);
+        let errors = ctx.validate();
+
+        assert!(errors.is_empty());
+
+        let enums = ctx.enums();
+
+        let enum_values: Vec<&str> = enums
+            .iter()
+            .find(|enum_def| enum_def.name() == "Pet")
+            .unwrap()
+            .enum_values_definition()
+            .iter()
+            .map(|enum_val| enum_val.enum_value())
+            .collect();
+        assert_eq!(enum_values, ["CAT", "DOG", "FOX"]);
+    }
 }
diff --git a/crates/apollo-compiler/src/queries/database.rs b/crates/apollo-compiler/src/queries/database.rs
index 75a3e97..4d1215c 100644
--- a/crates/apollo-compiler/src/queries/database.rs
+++ b/crates/apollo-compiler/src/queries/database.rs
@@ -46,6 +46,8 @@ pub trait SourceDatabase {
 
     fn scalars(&self) -> Arc<Vec<ScalarDefinition>>;
 
+    fn enums(&self) -> Arc<Vec<EnumDefinition>>;
+
     fn query_operations(&self) -> Operations;
 
     fn mutation_operations(&self) -> Operations;
@@ -354,6 +356,20 @@ fn scalars(db: &dyn SourceDatabase) -> Arc<Vec<ScalarDefinition>> {
     Arc::new(scalars)
 }
 
+fn enums(db: &dyn SourceDatabase) -> Arc<Vec<EnumDefinition>> {
+    let enums = db
+        .definitions()
+        .iter()
+        .filter_map(|definition| match definition {
+            ast::Definition::EnumTypeDefinition(enum_def) => {
+                Some(enum_definition(enum_def.clone()))
+            }
+            _ => None,
+        })
+        .collect();
+    Arc::new(enums)
+}
+
 fn find_object_type(db: &dyn SourceDatabase, id: Uuid) -> Option<Arc<ObjectTypeDefinition>> {
     db.object_types().iter().find_map(|object_type| {
         if &id == object_type.id() {
@@ -475,6 +491,48 @@ fn scalar_definition(scalar_def: ast::ScalarTypeDefinition) -> ScalarDefinition
     }
 }
 
+fn enum_definition(enum_def: ast::EnumTypeDefinition) -> EnumDefinition {
+    let description = description(enum_def.description());
+    let name = name(enum_def.name());
+    let directives = directives(enum_def.directives());
+    let enum_values_definition = enum_values_definition(enum_def.enum_values_definition());
+
+    EnumDefinition {
+        description,
+        name,
+        directives,
+        enum_values_definition,
+    }
+}
+
+fn enum_values_definition(
+    enum_values_def: Option<ast::EnumValuesDefinition>,
+) -> Arc<Vec<EnumValueDefinition>> {
+    match enum_values_def {
+        Some(enum_values) => {
+            let enum_values = enum_values
+                .enum_value_definitions()
+                .into_iter()
+                .map(enum_value_definition)
+                .collect();
+            Arc::new(enum_values)
+        }
+        None => Arc::new(Vec::new()),
+    }
+}
+
+fn enum_value_definition(enum_value_def: ast::EnumValueDefinition) -> EnumValueDefinition {
+    let description = description(enum_value_def.description());
+    let enum_value = enum_value(enum_value_def.enum_value());
+    let directives = directives(enum_value_def.directives());
+
+    EnumValueDefinition {
+        description,
+        enum_value,
+        directives,
+    }
+}
+
 fn add_object_type_id_to_schema(db: &dyn SourceDatabase) -> Arc<Vec<RootOperationTypeDefinition>> {
     // Schema Definition does not have to be present in the SDL if ObjectType name is
     // - Query
@@ -862,6 +920,15 @@ fn name(name: Option<ast::Name>) -> String {
     name.expect("Field must have a name").text().to_string()
 }
 
+fn enum_value(enum_value: Option<ast::EnumValue>) -> String {
+    enum_value
+        .expect("Enum value must have a name")
+        .name()
+        .expect("Name must have text")
+        .text()
+        .to_string()
+}
+
 fn description(description: Option<ast::Description>) -> Option<String> {
     description.map(|desc| {
         desc.string_value()
diff --git a/crates/apollo-compiler/src/queries/values.rs b/crates/apollo-compiler/src/queries/values.rs
index e6f8ae3..0eadf4c 100644
--- a/crates/apollo-compiler/src/queries/values.rs
+++ b/crates/apollo-compiler/src/queries/values.rs
@@ -712,7 +712,7 @@ pub struct ScalarDefinition {
 }
 
 impl ScalarDefinition {
-    /// get a reference to the scalar definition's name.
+    /// Get a reference to the scalar definition's name.
     pub fn name(&self) -> &str {
         self.name.as_ref()
     }
@@ -722,3 +722,41 @@ impl ScalarDefinition {
         self.directives.as_ref()
     }
 }
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub struct EnumDefinition {
+    pub(crate) description: Option<String>,
+    pub(crate) name: String,
+    pub(crate) directives: Arc<Vec<Directive>>,
+    pub(crate) enum_values_definition: Arc<Vec<EnumValueDefinition>>,
+}
+
+impl EnumDefinition {
+    /// Get a reference to the enum definition's name.
+    pub fn name(&self) -> &str {
+        self.name.as_ref()
+    }
+
+    /// Get a reference to enum definition's directives.
+    pub fn directives(&self) -> &[Directive] {
+        self.directives.as_ref()
+    }
+
+    /// Get a reference to enum definition's enum values definition vector.
+    pub fn enum_values_definition(&self) -> &[EnumValueDefinition] {
+        self.enum_values_definition.as_ref()
+    }
+}
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub struct EnumValueDefinition {
+    pub(crate) description: Option<String>,
+    pub(crate) enum_value: String,
+    pub(crate) directives: Arc<Vec<Directive>>,
+}
+
+impl EnumValueDefinition {
+    /// Get a reference to enum value definition's enum value
+    pub fn enum_value(&self) -> &str {
+        self.enum_value.as_ref()
+    }
+}
diff --git a/crates/apollo-compiler/src/validation/enums.rs b/crates/apollo-compiler/src/validation/enums.rs
new file mode 100644
index 0000000..8cd2e6e
--- /dev/null
+++ b/crates/apollo-compiler/src/validation/enums.rs
@@ -0,0 +1,47 @@
+use std::collections::HashSet;
+
+use crate::{
+    diagnostics::{ErrorDiagnostic, WarningDiagnostic},
+    ApolloDiagnostic, SourceDatabase,
+};
+
+pub fn check(db: &dyn SourceDatabase) -> Vec<ApolloDiagnostic> {
+    let mut errors = Vec::new();
+
+    // An Enum type must define one or more unique enum values.
+    //
+    // Return a Unique Operation Definition error in case of a duplicate name.
+    for enum_def in db.enums().iter() {
+        let mut seen = HashSet::new();
+        for enum_value in enum_def.enum_values_definition().iter() {
+            let value = enum_value.enum_value();
+            if seen.contains(&value) {
+                errors.push(ApolloDiagnostic::Error(ErrorDiagnostic::UniqueValue {
+                    message: "Enum Definition must have unique values".into(),
+                    value: value.into(),
+                }));
+            } else {
+                seen.insert(value);
+            }
+        }
+    }
+
+    // (convention) Values in an Enum Definition should be capitalized.
+    //
+    // Return a Capitalized Value warning if enum value is not capitalized.
+    for enum_def in db.enums().iter() {
+        for enum_value in enum_def.enum_values_definition().iter() {
+            let value = enum_value.enum_value();
+            if value.to_uppercase() != value {
+                errors.push(ApolloDiagnostic::Warning(
+                    WarningDiagnostic::CapitalizedValue {
+                        message: "Enum values are recommended to be capitalized".into(),
+                        value: value.into(),
+                    },
+                ));
+            }
+        }
+    }
+
+    errors
+}
diff --git a/crates/apollo-compiler/src/validation/mod.rs b/crates/apollo-compiler/src/validation/mod.rs
index 58bef8a..8d9b76d 100644
--- a/crates/apollo-compiler/src/validation/mod.rs
+++ b/crates/apollo-compiler/src/validation/mod.rs
@@ -1,8 +1,15 @@
 use crate::{ApolloDiagnostic, SourceDatabase};
 
-pub mod operations;
-pub mod scalars;
+// schema
 pub mod schema;
+
+// leaf nodes
+pub mod enums;
+pub mod scalars;
+
+// executable definitions
+pub mod operations;
+
 pub mod unused_implements_interfaces;
 pub mod unused_variables;
 
@@ -23,6 +30,7 @@ impl<'a> Validator<'a> {
         self.errors.extend(self.db.syntax_errors());
         self.errors.extend(schema::check(self.db));
         self.errors.extend(scalars::check(self.db));
+        self.errors.extend(enums::check(self.db));
         self.errors.extend(operations::check(self.db));
         self.errors.extend(unused_variables::check(self.db));
         self.errors.as_mut()


Test patch:
diff --git a/crates/apollo-compiler/test_data/err/0019_enum_definition_with_duplicate_values.graphql b/crates/apollo-compiler/test_data/err/0019_enum_definition_with_duplicate_values.graphql
new file mode 100644
index 0000000..da0359e
--- /dev/null
+++ b/crates/apollo-compiler/test_data/err/0019_enum_definition_with_duplicate_values.graphql
@@ -0,0 +1,18 @@
+type Query {
+  pet: Pet,
+  snacks: Snack,
+}
+
+enum Pet {
+    CAT
+    DOG
+    FOX
+    CAT
+}
+
+enum Snack {
+    THRIVE_PET_FOODS
+    LILYS_KITCHEN
+    ACANA
+    THRIVE_PET_FOODS
+}
\ No newline at end of file
diff --git a/crates/apollo-compiler/test_data/err/0019_enum_definition_with_duplicate_values.txt b/crates/apollo-compiler/test_data/err/0019_enum_definition_with_duplicate_values.txt
new file mode 100644
index 0000000..0beed27
--- /dev/null
+++ b/crates/apollo-compiler/test_data/err/0019_enum_definition_with_duplicate_values.txt
@@ -0,0 +1,1 @@
+[Error(UniqueValue { message: "Enum Definition must have unique values", value: "CAT" }), Error(UniqueValue { message: "Enum Definition must have unique values", value: "THRIVE_PET_FOODS" })]
diff --git a/crates/apollo-compiler/test_data/err/0020_enum_values_with_uncapitalised_values.graphql b/crates/apollo-compiler/test_data/err/0020_enum_values_with_uncapitalised_values.graphql
new file mode 100644
index 0000000..b325012
--- /dev/null
+++ b/crates/apollo-compiler/test_data/err/0020_enum_values_with_uncapitalised_values.graphql
@@ -0,0 +1,12 @@
+type Query {
+  pet: Pet,
+}
+
+enum Pet {
+    CAt
+    DOG_gO
+    FOX
+    _fox
+    squirrel
+    PARRot1
+}
\ No newline at end of file
diff --git a/crates/apollo-compiler/test_data/err/0020_enum_values_with_uncapitalised_values.txt b/crates/apollo-compiler/test_data/err/0020_enum_values_with_uncapitalised_values.txt
new file mode 100644
index 0000000..ef17eb9
--- /dev/null
+++ b/crates/apollo-compiler/test_data/err/0020_enum_values_with_uncapitalised_values.txt
@@ -0,0 +1,1 @@
+[Warning(CapitalizedValue { message: "Enum values are recommended to be capitalized", value: "CAt" }), Warning(CapitalizedValue { message: "Enum values are recommended to be capitalized", value: "DOG_gO" }), Warning(CapitalizedValue { message: "Enum values are recommended to be capitalized", value: "_fox" }), Warning(CapitalizedValue { message: "Enum values are recommended to be capitalized", value: "squirrel" }), Warning(CapitalizedValue { message: "Enum values are recommended to be capitalized", value: "PARRot1" })]
diff --git a/crates/apollo-compiler/test_data/ok/0005_schema_with_valid_enum_definitions.graphql b/crates/apollo-compiler/test_data/ok/0005_schema_with_valid_enum_definitions.graphql
new file mode 100644
index 0000000..5599c9a
--- /dev/null
+++ b/crates/apollo-compiler/test_data/ok/0005_schema_with_valid_enum_definitions.graphql
@@ -0,0 +1,16 @@
+type Query {
+  pet: Pet,
+  snacks: Snack,
+}
+
+enum Pet {
+    CAT
+    DOG
+    FOX
+}
+
+enum Snack {
+    THRIVE_PET_FOODS
+    LILYS_KITCHEN
+    ACANA
+}
\ No newline at end of file
diff --git a/crates/apollo-compiler/test_data/ok/0005_schema_with_valid_enum_definitions.txt b/crates/apollo-compiler/test_data/ok/0005_schema_with_valid_enum_definitions.txt
new file mode 100644
index 0000000..9707fee
--- /dev/null
+++ b/crates/apollo-compiler/test_data/ok/0005_schema_with_valid_enum_definitions.txt
@@ -0,0 +1,84 @@
+- DOCUMENT@0..146
+    - OBJECT_TYPE_DEFINITION@0..45
+        - type_KW@0..4 "type"
+        - WHITESPACE@4..5 " "
+        - NAME@5..11
+            - IDENT@5..10 "Query"
+            - WHITESPACE@10..11 " "
+        - FIELDS_DEFINITION@11..45
+            - L_CURLY@11..12 "{"
+            - WHITESPACE@12..15 "\n  "
+            - FIELD_DEFINITION@15..27
+                - NAME@15..18
+                    - IDENT@15..18 "pet"
+                - COLON@18..19 ":"
+                - WHITESPACE@19..20 " "
+                - NAMED_TYPE@20..23
+                    - NAME@20..23
+                        - IDENT@20..23 "Pet"
+                - COMMA@23..24 ","
+                - WHITESPACE@24..27 "\n  "
+            - FIELD_DEFINITION@27..42
+                - NAME@27..33
+                    - IDENT@27..33 "snacks"
+                - COLON@33..34 ":"
+                - WHITESPACE@34..35 " "
+                - NAMED_TYPE@35..40
+                    - NAME@35..40
+                        - IDENT@35..40 "Snack"
+                - COMMA@40..41 ","
+                - WHITESPACE@41..42 "\n"
+            - R_CURLY@42..43 "}"
+            - WHITESPACE@43..45 "\n\n"
+    - ENUM_TYPE_DEFINITION@45..83
+        - enum_KW@45..49 "enum"
+        - WHITESPACE@49..50 " "
+        - NAME@50..54
+            - IDENT@50..53 "Pet"
+            - WHITESPACE@53..54 " "
+        - ENUM_VALUES_DEFINITION@54..83
+            - L_CURLY@54..55 "{"
+            - WHITESPACE@55..60 "\n    "
+            - ENUM_VALUE_DEFINITION@60..68
+                - ENUM_VALUE@60..68
+                    - NAME@60..68
+                        - IDENT@60..63 "CAT"
+                        - WHITESPACE@63..68 "\n    "
+            - ENUM_VALUE_DEFINITION@68..76
+                - ENUM_VALUE@68..76
+                    - NAME@68..76
+                        - IDENT@68..71 "DOG"
+                        - WHITESPACE@71..76 "\n    "
+            - ENUM_VALUE_DEFINITION@76..80
+                - ENUM_VALUE@76..80
+                    - NAME@76..80
+                        - IDENT@76..79 "FOX"
+                        - WHITESPACE@79..80 "\n"
+            - R_CURLY@80..81 "}"
+            - WHITESPACE@81..83 "\n\n"
+    - ENUM_TYPE_DEFINITION@83..146
+        - enum_KW@83..87 "enum"
+        - WHITESPACE@87..88 " "
+        - NAME@88..94
+            - IDENT@88..93 "Snack"
+            - WHITESPACE@93..94 " "
+        - ENUM_VALUES_DEFINITION@94..146
+            - L_CURLY@94..95 "{"
+            - WHITESPACE@95..100 "\n    "
+            - ENUM_VALUE_DEFINITION@100..121
+                - ENUM_VALUE@100..121
+                    - NAME@100..121
+                        - IDENT@100..116 "THRIVE_PET_FOODS"
+                        - WHITESPACE@116..121 "\n    "
+            - ENUM_VALUE_DEFINITION@121..139
+                - ENUM_VALUE@121..139
+                    - NAME@121..139
+                        - IDENT@121..134 "LILYS_KITCHEN"
+                        - WHITESPACE@134..139 "\n    "
+            - ENUM_VALUE_DEFINITION@139..145
+                - ENUM_VALUE@139..145
+                    - NAME@139..145
+                        - IDENT@139..144 "ACANA"
+                        - WHITESPACE@144..145 "\n"
+            - R_CURLY@145..146 "}"
+