apollo_federation/connectors/spec/
mod.rs1pub(crate) mod connect;
3pub(crate) mod errors;
4pub(crate) mod http;
5pub(crate) mod source;
6mod type_and_directive_specifications;
7
8use std::fmt::Display;
9use std::sync::LazyLock;
10
11use apollo_compiler::Name;
12use apollo_compiler::Schema;
13use apollo_compiler::ast::Directive;
14use apollo_compiler::name;
15use apollo_compiler::schema::Component;
16pub use connect::ConnectHTTPArguments;
17pub(crate) use connect::extract_connect_directive_arguments;
18use itertools::Itertools;
19pub use source::SourceHTTPArguments;
20pub(crate) use source::extract_source_directive_arguments;
21use strum::IntoEnumIterator;
22use strum_macros::EnumIter;
23
24use self::connect::CONNECT_DIRECTIVE_NAME_IN_SPEC;
25use self::source::SOURCE_DIRECTIVE_NAME_IN_SPEC;
26use crate::connectors::spec::type_and_directive_specifications::directive_specifications;
27use crate::connectors::spec::type_and_directive_specifications::type_specifications;
28use crate::connectors::validation::Code;
29use crate::connectors::validation::Message;
30use crate::error::FederationError;
31use crate::link::Link;
32use crate::link::Purpose;
33use crate::link::spec::APOLLO_SPEC_DOMAIN;
34use crate::link::spec::Identity;
35use crate::link::spec::Url;
36use crate::link::spec::Version;
37use crate::link::spec_definition::SpecDefinition;
38use crate::link::spec_definition::SpecDefinitions;
39use crate::schema::type_and_directive_specification::TypeAndDirectiveSpecification;
40
41const CONNECT_IDENTITY_NAME: Name = name!("connect");
42
43#[derive(Clone, Debug)]
45pub(crate) struct ConnectLink {
46 pub(crate) spec: ConnectSpec,
47 pub(crate) source_directive_name: Name,
48 pub(crate) connect_directive_name: Name,
49 pub(crate) directive: Component<Directive>,
50 pub(crate) link: Link,
51}
52
53impl<'schema> ConnectLink {
54 pub(super) fn new(schema: &'schema Schema) -> Option<Result<Self, Message>> {
60 let (link, directive) = Link::for_identity(schema, &ConnectSpec::identity())?;
61
62 let spec = match ConnectSpec::try_from(&link.url.version) {
63 Err(err) => {
64 let message = format!(
65 "{err}; should be one of {available_versions}.",
66 available_versions = ConnectSpec::iter().map(ConnectSpec::as_str).join(", "),
67 );
68 return Some(Err(Message {
69 code: Code::UnknownConnectorsVersion,
70 message,
71 locations: directive
72 .line_column_range(&schema.sources)
73 .into_iter()
74 .collect(),
75 }));
76 }
77 Ok(spec) => spec,
78 };
79 let source_directive_name = link.directive_name_in_schema(&SOURCE_DIRECTIVE_NAME_IN_SPEC);
80 let connect_directive_name = link.directive_name_in_schema(&CONNECT_DIRECTIVE_NAME_IN_SPEC);
81 Some(Ok(Self {
82 spec,
83 source_directive_name,
84 connect_directive_name,
85 directive: directive.clone(),
86 link,
87 }))
88 }
89}
90
91pub(crate) fn connect_spec_from_schema(schema: &Schema) -> Option<ConnectSpec> {
92 let connect_identity = ConnectSpec::identity();
93 Link::for_identity(schema, &connect_identity)
94 .and_then(|(link, _directive)| ConnectSpec::try_from(&link.url.version).ok())
95}
96
97impl Display for ConnectLink {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 write!(f, "{}", self.link)
100 }
101}
102
103#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, EnumIter)]
105pub enum ConnectSpec {
106 V0_1,
107 V0_2,
108 V0_3,
109 V0_4,
110}
111
112impl PartialOrd for ConnectSpec {
113 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
114 let self_version: Version = (*self).into();
115 let other_version: Version = (*other).into();
116 self_version.partial_cmp(&other_version)
117 }
118}
119
120impl ConnectSpec {
121 #[cfg(test)]
124 pub(crate) fn latest() -> Self {
125 Self::V0_2
126 }
127
128 #[cfg(test)]
131 pub(crate) fn next() -> Self {
132 Self::V0_3
133 }
134
135 pub const fn as_str(self) -> &'static str {
136 match self {
137 Self::V0_1 => "0.1",
138 Self::V0_2 => "0.2",
139 Self::V0_3 => "0.3",
140 Self::V0_4 => "0.4",
141 }
142 }
143
144 pub(crate) fn identity() -> Identity {
145 Identity {
146 domain: APOLLO_SPEC_DOMAIN.to_string(),
147 name: CONNECT_IDENTITY_NAME,
148 }
149 }
150
151 pub(crate) fn url(&self) -> Url {
152 Url {
153 identity: Self::identity(),
154 version: (*self).into(),
155 }
156 }
157}
158
159impl TryFrom<&Version> for ConnectSpec {
160 type Error = String;
161 fn try_from(version: &Version) -> Result<Self, Self::Error> {
162 match (version.major, version.minor) {
163 (0, 1) => Ok(Self::V0_1),
164 (0, 2) => Ok(Self::V0_2),
165 (0, 3) => Ok(Self::V0_3),
166 (0, 4) => Ok(Self::V0_4),
167 _ => Err(format!("Unknown connect version: {version}")),
168 }
169 }
170}
171
172impl Display for ConnectSpec {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 f.write_str(self.as_str())
175 }
176}
177
178impl From<ConnectSpec> for Version {
179 fn from(spec: ConnectSpec) -> Self {
180 match spec {
181 ConnectSpec::V0_1 => Version { major: 0, minor: 1 },
182 ConnectSpec::V0_2 => Version { major: 0, minor: 2 },
183 ConnectSpec::V0_3 => Version { major: 0, minor: 3 },
184 ConnectSpec::V0_4 => Version { major: 0, minor: 4 },
185 }
186 }
187}
188
189pub(crate) struct ConnectSpecDefinition {
190 minimum_federation_version: Version,
191 url: Url,
192}
193
194impl ConnectSpecDefinition {
195 pub(crate) fn new(version: Version, minimum_federation_version: Version) -> Self {
196 Self {
197 url: Url {
198 identity: ConnectSpec::identity(),
199 version,
200 },
201 minimum_federation_version,
202 }
203 }
204
205 pub(crate) fn from_directive(
206 directive: &Directive,
207 ) -> Result<Option<&'static Self>, FederationError> {
208 let Some(url) = directive
209 .specified_argument_by_name("url")
210 .and_then(|a| a.as_str())
211 else {
212 return Ok(None);
213 };
214
215 let url: Url = url.parse()?;
216 if url.identity.domain != APOLLO_SPEC_DOMAIN || url.identity.name != CONNECT_IDENTITY_NAME {
217 return Ok(None);
218 }
219
220 Ok(CONNECT_VERSIONS.find(&url.version))
221 }
222}
223
224impl SpecDefinition for ConnectSpecDefinition {
225 fn url(&self) -> &Url {
226 &self.url
227 }
228
229 fn directive_specs(&self) -> Vec<Box<dyn TypeAndDirectiveSpecification>> {
230 directive_specifications()
231 }
232
233 fn type_specs(&self) -> Vec<Box<dyn TypeAndDirectiveSpecification>> {
234 type_specifications()
235 }
236
237 fn minimum_federation_version(&self) -> &Version {
238 &self.minimum_federation_version
239 }
240
241 fn purpose(&self) -> Option<Purpose> {
242 Some(Purpose::EXECUTION)
243 }
244}
245
246pub(crate) static CONNECT_VERSIONS: LazyLock<SpecDefinitions<ConnectSpecDefinition>> =
247 LazyLock::new(|| {
248 let mut definitions = SpecDefinitions::new(Identity::connect_identity());
249 definitions.add(ConnectSpecDefinition::new(
250 Version { major: 0, minor: 1 },
251 Version {
252 major: 2,
253 minor: 10,
254 },
255 ));
256 definitions.add(ConnectSpecDefinition::new(
257 Version { major: 0, minor: 2 },
258 Version {
259 major: 2,
260 minor: 11,
261 },
262 ));
263 definitions.add(ConnectSpecDefinition::new(
264 Version { major: 0, minor: 3 },
265 Version {
266 major: 2,
267 minor: 12,
268 },
269 ));
270 definitions.add(ConnectSpecDefinition::new(
271 Version { major: 0, minor: 4 },
272 Version {
273 major: 2,
274 minor: 13,
275 },
276 ));
277 definitions
278 });