apollo_subgraph/database.rs
1//! Valid federation 2 subgraphs.
2//!
3//! Note: technically, federation 1 subgraphs are still accepted as input of
4//! composition. However, there is some pre-composition steps that "massage"
5//! the input schema to transform them in fully valid federation 2 subgraphs,
6//! so the subgraphs seen by composition and query planning are always fully
7//! valid federation 2 ones, and this is what this database handles.
8//! Note2: This does assumes that whichever way an implementation of this
9//! trait is created, some validation that the underlying schema is a valid
10//! federation subgraph (so valid graphql, link to the federation spec, and
11//! pass additional federation validations). If this is not the case, most
12//! of the methods here will panic.
13
14use std::sync::Arc;
15
16use apollo_at_link::database::links_metadata;
17use apollo_at_link::link::Link;
18use apollo_at_link::spec::{Identity, APOLLO_SPEC_DOMAIN};
19use apollo_compiler::executable::{Directive, SelectionSet};
20use apollo_compiler::Schema;
21
22// TODO: we should define this as part as some more generic "FederationSpec" definition, but need
23// to define the ground work for that in `apollo-at-link` first.
24pub fn federation_link_identity() -> Identity {
25 Identity {
26 domain: APOLLO_SPEC_DOMAIN.to_string(),
27 name: "federation".to_string(),
28 }
29}
30
31#[derive(Eq, PartialEq, Debug, Clone)]
32pub struct Key {
33 pub type_name: String,
34 // TODO: this should _not_ be an Option below; but we don't know how to build the SelectionSet,
35 // so until we have a solution, we use None to have code that compiles.
36 selections: Option<Arc<SelectionSet>>,
37}
38
39impl Key {
40 // TODO: same remark as above: not meant to be `Option`
41 pub fn selections(&self) -> Option<Arc<SelectionSet>> {
42 self.selections.clone()
43 }
44
45 pub(crate) fn from_directive_application(
46 type_name: &str,
47 directive: &Directive,
48 ) -> Option<Key> {
49 directive
50 .arguments
51 .iter()
52 .find(|arg| arg.name == "fields")
53 .and_then(|arg| arg.value.as_str())
54 .map(|_value| Key {
55 type_name: type_name.to_string(),
56 // TODO: obviously not what we want.
57 selections: None,
58 })
59 }
60}
61
62pub fn federation_link(schema: &Schema) -> Arc<Link> {
63 links_metadata(schema)
64 // TODO: error handling?
65 .unwrap_or_default()
66 .unwrap_or_default()
67 .for_identity(&federation_link_identity())
68 .expect("The presence of the federation link should have been validated on construction")
69}
70
71/// The name of the @key directive in this subgraph.
72/// This will either return 'federation__key' if the `@key` directive is not imported,
73/// or whatever never it is imported under otherwise. Commonly, this would just be `key`.
74pub fn key_directive_name(schema: &Schema) -> String {
75 federation_link(schema).directive_name_in_schema("key")
76}
77
78pub fn keys(schema: &Schema, type_name: &str) -> Vec<Key> {
79 let key_name = key_directive_name(schema);
80 if let Some(type_def) = schema.types.get(type_name) {
81 type_def
82 .directives()
83 .get_all(&key_name)
84 .filter_map(|directive| Key::from_directive_application(type_name, directive))
85 .collect()
86 } else {
87 vec![]
88 }
89}