shape/
graphql.rs

1//! Helper functions and impls for creating [`Shape`]s from [`apollo_compiler`] types.
2
3use crate::location::{Location, SourceId};
4use crate::Shape;
5use apollo_compiler::ast::{FieldDefinition, Type};
6use apollo_compiler::collections::IndexMap;
7use apollo_compiler::parser::SourceSpan;
8use apollo_compiler::schema::{
9    Component, EnumType, ExtendedType, InputObjectType, InterfaceType, ObjectType, UnionType,
10};
11use apollo_compiler::{Name, Node, Schema};
12
13/// Get all the shapes for a GraphQL [`Schema`], keyed by their GraphQL [`Name`].
14///
15/// Locations will be automatically set with
16#[must_use]
17pub fn shapes_for_schema(schema: &Schema) -> IndexMap<&str, Shape> {
18    schema
19        .types
20        .iter()
21        .filter(|&(_name, ty)| !ty.is_built_in())
22        .map(|(name, ty)| (name.as_str(), Shape::from(ty)))
23        .collect()
24}
25
26#[must_use]
27pub fn shape_for_arguments(field_definition: &Component<FieldDefinition>) -> Shape {
28    // Accumulate the locations of all the arguments so they can be highlighted together
29    let mut source_span = None;
30    for next_source_span in field_definition.arguments.iter().map(Node::location) {
31        source_span = SourceSpan::recompose(source_span, next_source_span);
32    }
33    Shape::record(
34        field_definition
35            .arguments
36            .iter()
37            .map(|arg| {
38                (
39                    arg.name.to_string(),
40                    from_type(arg.ty.as_ref(), span(arg.location())),
41                )
42            })
43            .collect(),
44        span(source_span),
45    )
46}
47
48impl From<&ExtendedType> for Shape {
49    fn from(ext: &ExtendedType) -> Self {
50        match ext {
51            ExtendedType::Scalar(scalar) => Shape::unknown(span(scalar.location())),
52            ExtendedType::Object(obj) => Self::from(obj),
53            ExtendedType::Interface(intf) => Self::from(intf),
54            ExtendedType::Union(union) => Self::from(union),
55            ExtendedType::Enum(enm) => Self::from(enm),
56            ExtendedType::InputObject(input) => Self::from(input),
57        }
58    }
59}
60
61impl From<&Name> for Shape {
62    fn from(name: &Name) -> Self {
63        let locations = span(name.location());
64        match name.as_str() {
65            "String" | "ID" => Self::string(locations),
66            "Int" => Self::int(locations),
67            "Float" => Self::float(locations),
68            "Boolean" => Self::bool(locations),
69            other => Self::name(other, locations),
70        }
71    }
72}
73
74impl From<&Node<ObjectType>> for Shape {
75    fn from(object_type: &Node<ObjectType>) -> Self {
76        record(&object_type.fields, span(object_type.location()))
77    }
78}
79
80fn record(fields: &IndexMap<Name, Component<FieldDefinition>>, locations: Vec<Location>) -> Shape {
81    let fields = fields
82        .iter()
83        .map(|(key, value)| (key.to_string(), Shape::from(value)))
84        .collect();
85    Shape::record(fields, locations)
86}
87
88fn nullable(shape: Shape, locations: Vec<Location>) -> Shape {
89    Shape::one([shape, Shape::null(locations.clone())], locations)
90}
91
92impl From<&Component<FieldDefinition>> for Shape {
93    fn from(field: &Component<FieldDefinition>) -> Self {
94        let locations = span(field.location());
95        from_type(&field.ty, locations)
96    }
97}
98
99fn from_type(ty: &Type, locations: Vec<Location>) -> Shape {
100    match ty {
101        Type::Named(name) => nullable(Shape::from(name), locations),
102        Type::NonNullNamed(name) => Shape::from(name),
103        Type::List(ty) => nullable(
104            Shape::list(from_type(ty.as_ref(), locations.clone()), locations.clone()),
105            locations,
106        ),
107        Type::NonNullList(ty) => Shape::list(from_type(ty.as_ref(), locations.clone()), locations),
108    }
109}
110
111impl From<&Node<InterfaceType>> for Shape {
112    fn from(interface_type: &Node<InterfaceType>) -> Self {
113        record(&interface_type.fields, span(interface_type.location()))
114    }
115}
116
117impl From<&Node<UnionType>> for Shape {
118    fn from(union_type: &Node<UnionType>) -> Self {
119        Self::one(
120            union_type
121                .members
122                .iter()
123                .map(|member| Self::from(&member.name)),
124            span(union_type.location()),
125        )
126    }
127}
128
129impl From<&Node<EnumType>> for Shape {
130    fn from(enum_type: &Node<EnumType>) -> Self {
131        Shape::one(
132            enum_type
133                .values
134                .iter()
135                .map(|(name, _)| Shape::string_value(name.as_str(), span(name.location()))),
136            span(enum_type.location()),
137        )
138    }
139}
140
141impl From<&Node<InputObjectType>> for Shape {
142    fn from(input_object_type: &Node<InputObjectType>) -> Self {
143        Self::record(
144            input_object_type
145                .fields
146                .iter()
147                .map(|(name, input)| {
148                    (
149                        name.to_string(),
150                        from_type(input.ty.as_ref(), span(input.location())),
151                    )
152                })
153                .collect(),
154            span(input_object_type.location()),
155        )
156    }
157}
158
159fn span(source_span: Option<SourceSpan>) -> Vec<Location> {
160    source_span.into_iter().map(Location::from).collect()
161}
162
163impl From<SourceSpan> for Location {
164    fn from(span: SourceSpan) -> Self {
165        Location {
166            source_id: SourceId::GraphQL(span.file_id()),
167            span: span.offset()..span.end_offset(),
168        }
169    }
170}