apollo_federation/link/
mod.rs1use std::fmt;
2use std::ops::Range;
3use std::sync::Arc;
4
5use apollo_compiler::Name;
6use apollo_compiler::Node;
7use apollo_compiler::Schema;
8use apollo_compiler::ast::Directive;
9use apollo_compiler::ast::Value;
10use apollo_compiler::parser::LineColumn;
11use apollo_compiler::schema::Component;
12
13use crate::error::FederationError;
14use crate::link::link_spec_definition::CORE_VERSIONS;
15use crate::link::link_spec_definition::LINK_VERSIONS;
16use crate::link::spec::Identity;
17use crate::link::spec::Url;
18
19pub(crate) mod argument;
20pub(crate) mod authenticated_spec_definition;
21pub(crate) mod cache_tag_spec_definition;
22pub(crate) mod context_spec_definition;
23pub mod cost_spec_definition;
24pub(crate) mod federation_spec_definition;
25pub(crate) mod graphql_definition;
26pub(crate) mod inaccessible_spec_definition;
27pub(crate) mod join_spec_definition;
28pub(crate) mod link_spec_definition;
29pub mod metadata;
30pub(crate) mod policy_spec_definition;
31pub(crate) mod requires_scopes_spec_definition;
32pub mod spec;
33pub(crate) mod spec_definition;
34pub(crate) mod spec_registry;
35pub(crate) mod tag_spec_definition;
36
37#[derive(Clone, Copy, Eq, PartialEq, Debug)]
38pub enum Purpose {
39 SECURITY,
40 EXECUTION,
41}
42
43impl fmt::Display for Purpose {
44 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45 Value::from(self).fmt(f)
46 }
47}
48
49#[derive(Debug, Clone, Eq, PartialEq)]
50pub struct Import {
51 pub element: Name,
56
57 pub is_directive: bool,
59
60 pub alias: Option<Name>,
62}
63
64impl Import {
65 pub fn name_in_schema(&self) -> &Name {
66 self.alias.as_ref().unwrap_or(&self.element)
67 }
68
69 pub fn element_name_in_spec(&self) -> ElementName {
70 ElementName {
71 name: self.element.clone(),
72 is_directive: self.is_directive,
73 }
74 }
75
76 pub fn element_name_in_schema(&self) -> ElementName {
77 ElementName {
78 name: self.name_in_schema().clone(),
79 is_directive: self.is_directive,
80 }
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Hash)]
87pub struct ElementName {
88 pub name: Name,
89 pub is_directive: bool,
90}
91
92impl fmt::Display for ElementName {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 if self.is_directive {
95 f.write_str("@")?;
96 }
97 f.write_str(&self.name)
98 }
99}
100
101impl fmt::Display for Import {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 Value::from(self).fmt(f)
104 }
105}
106
107#[derive(Clone, Debug, Eq, PartialEq)]
110pub struct Link {
111 pub url: Url,
112 pub spec_alias: Option<Name>,
113 pub imports: Vec<Arc<Import>>,
114 pub purpose: Option<Purpose>,
115 pub line_column_range: Option<Range<LineColumn>>,
116}
117
118impl Link {
119 pub(crate) fn from_directive_application_when_link_spec_unknown(
122 directive: &Node<Directive>,
123 schema: &Schema,
124 ) -> Result<Link, FederationError> {
125 LINK_VERSIONS
126 .latest()
127 .link_from_directive(directive, schema)
128 .or_else(|error| {
129 CORE_VERSIONS
132 .latest()
133 .link_from_directive(directive, schema)
134 .or(Err(error))
135 })
136 }
137
138 pub fn spec_name_in_schema(&self) -> Name {
139 if let Some(spec_alias) = &self.spec_alias {
140 return spec_alias.clone();
141 }
142 let name = &self.url.identity.name;
143 name.clone().try_into().unwrap_or_else(|_| {
144 Name::new_unchecked(name)
154 })
155 }
156
157 pub fn directive_name_in_schema(&self, name: &Name) -> Name {
158 if let Some(import) = self.imports.iter().find(|i| i.element == *name) {
163 import.alias.clone().unwrap_or_else(|| name.clone())
164 } else if name.as_str() == self.url.identity.name.as_ref() {
165 self.spec_name_in_schema().clone()
166 } else {
167 Name::new_unchecked(&format!("{}__{}", self.spec_name_in_schema(), name))
169 }
170 }
171
172 pub(crate) fn directive_name_in_schema_for_core_arguments(
173 spec_url: &Url,
174 spec_name_in_schema: &Name,
175 imports: &[Import],
176 directive_name_in_spec: &Name,
177 ) -> Name {
178 if let Some(element_import) = imports
179 .iter()
180 .find(|i| i.element == *directive_name_in_spec)
181 {
182 element_import.name_in_schema().clone()
183 } else if spec_url.identity.name.as_ref() == directive_name_in_spec.as_str() {
184 spec_name_in_schema.clone()
185 } else {
186 Name::new_unchecked(format!("{spec_name_in_schema}__{directive_name_in_spec}").as_str())
187 }
188 }
189
190 pub fn type_name_in_schema(&self, name: &Name) -> Name {
191 if let Some(import) = self.imports.iter().find(|i| i.element == *name) {
194 import.alias.clone().unwrap_or_else(|| name.clone())
195 } else {
196 Name::new_unchecked(&format!("{}__{}", self.spec_name_in_schema(), name))
198 }
199 }
200
201 pub(crate) fn for_identity<'schema>(
202 schema: &'schema Schema,
203 identity: &Identity,
204 ) -> Option<(Self, &'schema Component<Directive>)> {
205 schema
206 .schema_definition
207 .directives
208 .iter()
209 .find_map(|directive| {
210 let link =
211 Link::from_directive_application_when_link_spec_unknown(directive, schema)
212 .ok()?;
213 if link.url.identity == *identity {
214 Some((link, directive))
215 } else {
216 None
217 }
218 })
219 }
220}
221
222impl fmt::Display for Link {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 LINK_VERSIONS.latest().directive_from_link(self).fmt(f)
225 }
226}