Skip to main content

apollo_federation/link/
spec_registry.rs

1use std::collections::BTreeSet;
2use std::collections::HashMap;
3use std::str;
4use std::sync::LazyLock;
5
6use apollo_compiler::Name;
7use apollo_compiler::name;
8
9use crate::connectors::spec::CONNECT_VERSIONS;
10use crate::link::Link;
11use crate::link::authenticated_spec_definition::AUTHENTICATED_VERSIONS;
12use crate::link::cache_tag_spec_definition::CACHE_TAG_VERSIONS;
13use crate::link::context_spec_definition::CONTEXT_VERSIONS;
14use crate::link::cost_spec_definition::COST_VERSIONS;
15use crate::link::federation_spec_definition::FEDERATION_VERSIONS;
16use crate::link::inaccessible_spec_definition::INACCESSIBLE_VERSIONS;
17use crate::link::join_spec_definition::JOIN_VERSIONS;
18use crate::link::policy_spec_definition::POLICY_VERSIONS;
19use crate::link::requires_scopes_spec_definition::REQUIRES_SCOPES_VERSIONS;
20use crate::link::spec::Identity;
21use crate::link::spec::Url;
22use crate::link::spec::Version;
23use crate::link::spec_definition::SpecDefinition;
24use crate::link::spec_definition::SpecDefinitions;
25use crate::link::tag_spec_definition::TAG_VERSIONS;
26use crate::schema::type_and_directive_specification::DirectiveCompositionSpecification;
27use crate::schema::type_and_directive_specification::DirectiveSpecification;
28
29pub(crate) const APOLLO_SPEC_DOMAIN: &str = "https://specs.apollo.dev";
30
31impl Identity {
32    pub const AUTHENTICATED_NAME: Name = name!("authenticated");
33    pub fn authenticated_identity() -> Identity {
34        Identity {
35            domain: APOLLO_SPEC_DOMAIN.to_string(),
36            name: Self::AUTHENTICATED_NAME.into(),
37        }
38    }
39
40    pub const CACHE_TAG_NAME: Name = name!("cacheTag");
41    pub fn cache_tag_identity() -> Identity {
42        Identity {
43            domain: APOLLO_SPEC_DOMAIN.to_string(),
44            name: Self::CACHE_TAG_NAME.into(),
45        }
46    }
47
48    pub const CONNECT_NAME: Name = name!("connect");
49    pub fn connect_identity() -> Identity {
50        Identity {
51            domain: APOLLO_SPEC_DOMAIN.to_string(),
52            name: Self::CONNECT_NAME.into(),
53        }
54    }
55
56    pub const CONTEXT_NAME: Name = name!("context");
57    pub fn context_identity() -> Identity {
58        Identity {
59            domain: APOLLO_SPEC_DOMAIN.to_string(),
60            name: Self::CONTEXT_NAME.into(),
61        }
62    }
63
64    pub const CORE_NAME: Name = name!("core");
65    pub fn core_identity() -> Identity {
66        Identity {
67            domain: APOLLO_SPEC_DOMAIN.to_string(),
68            name: Self::CORE_NAME.into(),
69        }
70    }
71
72    pub const COST_NAME: Name = name!("cost");
73    pub fn cost_identity() -> Identity {
74        Identity {
75            domain: APOLLO_SPEC_DOMAIN.to_string(),
76            name: Self::COST_NAME.into(),
77        }
78    }
79
80    pub const FEDERATION_NAME: Name = name!("federation");
81    pub fn federation_identity() -> Identity {
82        Identity {
83            domain: APOLLO_SPEC_DOMAIN.to_string(),
84            name: Self::FEDERATION_NAME.into(),
85        }
86    }
87
88    pub const INACCESSIBLE_NAME: Name = name!("inaccessible");
89    pub fn inaccessible_identity() -> Identity {
90        Identity {
91            domain: APOLLO_SPEC_DOMAIN.to_string(),
92            name: Self::INACCESSIBLE_NAME.into(),
93        }
94    }
95
96    pub const JOIN_NAME: Name = name!("join");
97    pub fn join_identity() -> Identity {
98        Identity {
99            domain: APOLLO_SPEC_DOMAIN.to_string(),
100            name: Self::JOIN_NAME.into(),
101        }
102    }
103
104    pub const LINK_NAME: Name = name!("link");
105    pub fn link_identity() -> Identity {
106        Identity {
107            domain: APOLLO_SPEC_DOMAIN.to_string(),
108            name: Self::LINK_NAME.into(),
109        }
110    }
111
112    pub const POLICY_NAME: Name = name!("policy");
113    pub fn policy_identity() -> Identity {
114        Identity {
115            domain: APOLLO_SPEC_DOMAIN.to_string(),
116            name: Self::POLICY_NAME.into(),
117        }
118    }
119
120    pub const REQUIRES_SCOPES_NAME: Name = name!("requiresScopes");
121    pub fn requires_scopes_identity() -> Identity {
122        Identity {
123            domain: APOLLO_SPEC_DOMAIN.to_string(),
124            name: Self::REQUIRES_SCOPES_NAME.into(),
125        }
126    }
127
128    pub const SOURCE_NAME: Name = name!("source");
129    pub fn source_identity() -> Identity {
130        Identity {
131            domain: APOLLO_SPEC_DOMAIN.to_string(),
132            name: Self::SOURCE_NAME.into(),
133        }
134    }
135
136    pub const TAG_NAME: Name = name!("tag");
137    pub fn tag_identity() -> Identity {
138        Identity {
139            domain: APOLLO_SPEC_DOMAIN.to_string(),
140            name: Self::TAG_NAME.into(),
141        }
142    }
143}
144
145pub(crate) static SPEC_REGISTRY: LazyLock<SpecRegistry> = LazyLock::new(|| {
146    let mut registry = SpecRegistry::new();
147    registry.extend(&AUTHENTICATED_VERSIONS);
148    registry.extend(&CACHE_TAG_VERSIONS);
149    registry.extend(&CONNECT_VERSIONS);
150    registry.extend(&CONTEXT_VERSIONS);
151    registry.extend(&COST_VERSIONS);
152    registry.extend(&FEDERATION_VERSIONS);
153    registry.extend(&INACCESSIBLE_VERSIONS);
154    registry.extend(&POLICY_VERSIONS);
155    registry.extend(&REQUIRES_SCOPES_VERSIONS);
156    registry.extend(&TAG_VERSIONS);
157    registry.extend(&JOIN_VERSIONS);
158    registry
159});
160
161pub(crate) struct SpecRegistry {
162    definitions_by_url: HashMap<Url, &'static (dyn SpecDefinition + Sync)>,
163    available_versions_by_identity: HashMap<Identity, BTreeSet<Version>>,
164}
165
166impl SpecRegistry {
167    fn new() -> Self {
168        Self {
169            definitions_by_url: HashMap::new(),
170            available_versions_by_identity: HashMap::new(),
171        }
172    }
173
174    fn extend<T: SpecDefinition + Sync>(&mut self, definitions: &'static SpecDefinitions<T>) {
175        for (v, spec) in definitions.iter() {
176            self.definitions_by_url.insert(spec.url().clone(), spec);
177            self.available_versions_by_identity
178                .entry(spec.url().identity.clone())
179                .or_default()
180                .insert(v.clone());
181        }
182    }
183
184    pub(crate) fn get_definition(&self, url: &Url) -> Option<&&(dyn SpecDefinition + Sync)> {
185        self.definitions_by_url.get(url)
186    }
187
188    pub(crate) fn get_versions(&self, identity: &Identity) -> Option<&BTreeSet<Version>> {
189        self.available_versions_by_identity.get(identity)
190    }
191
192    /// Generates the composition spec for an imported directive. Currently, this generates the
193    /// entire spec, then loops over available directive specifications and clones the applicable
194    /// directive. An alternative would be to mark everything as `Sync` and store them on the
195    /// individual feature specs, but we have omitted this for now due to a non-trivial (~10%)
196    /// increase in heap usage that affects query planning.
197    pub(crate) fn get_composition_spec(
198        &self,
199        source: &Link,
200        directive_name_in_spec: &Name,
201    ) -> Option<DirectiveCompositionSpecification> {
202        let specs = self.get_definition(&source.url)?.directive_specs();
203        let spec = specs.iter().find(|s| s.name() == directive_name_in_spec)?;
204        let directive_spec: DirectiveSpecification = spec.as_any().downcast_ref().cloned()?;
205        directive_spec.composition
206    }
207}