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 pub fn latest() -> Self {
123 Self::V0_3
124 }
125
126 #[cfg(test)]
129 pub(crate) fn next() -> Self {
130 Self::V0_4
131 }
132
133 pub const fn as_str(self) -> &'static str {
134 match self {
135 Self::V0_1 => "0.1",
136 Self::V0_2 => "0.2",
137 Self::V0_3 => "0.3",
138 Self::V0_4 => "0.4",
139 }
140 }
141
142 pub(crate) fn identity() -> Identity {
143 Identity {
144 domain: APOLLO_SPEC_DOMAIN.to_string(),
145 name: CONNECT_IDENTITY_NAME,
146 }
147 }
148
149 pub(crate) fn url(&self) -> Url {
150 Url {
151 identity: Self::identity(),
152 version: (*self).into(),
153 }
154 }
155}
156
157impl TryFrom<&Version> for ConnectSpec {
158 type Error = String;
159 fn try_from(version: &Version) -> Result<Self, Self::Error> {
160 match (version.major, version.minor) {
161 (0, 1) => Ok(Self::V0_1),
162 (0, 2) => Ok(Self::V0_2),
163 (0, 3) => Ok(Self::V0_3),
164 (0, 4) => Ok(Self::V0_4),
165 _ => Err(format!("Unknown connect version: {version}")),
166 }
167 }
168}
169
170impl Display for ConnectSpec {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 f.write_str(self.as_str())
173 }
174}
175
176impl From<ConnectSpec> for Version {
177 fn from(spec: ConnectSpec) -> Self {
178 match spec {
179 ConnectSpec::V0_1 => Version { major: 0, minor: 1 },
180 ConnectSpec::V0_2 => Version { major: 0, minor: 2 },
181 ConnectSpec::V0_3 => Version { major: 0, minor: 3 },
182 ConnectSpec::V0_4 => Version { major: 0, minor: 4 },
183 }
184 }
185}
186
187pub(crate) struct ConnectSpecDefinition {
188 minimum_federation_version: Version,
189 url: Url,
190}
191
192impl ConnectSpecDefinition {
193 pub(crate) fn new(version: Version, minimum_federation_version: Version) -> Self {
194 Self {
195 url: Url {
196 identity: ConnectSpec::identity(),
197 version,
198 },
199 minimum_federation_version,
200 }
201 }
202
203 pub(crate) fn from_directive(
204 directive: &Directive,
205 ) -> Result<Option<&'static Self>, FederationError> {
206 let Some(url) = directive
207 .specified_argument_by_name("url")
208 .and_then(|a| a.as_str())
209 else {
210 return Ok(None);
211 };
212
213 let url: Url = url.parse()?;
214 if url.identity.domain != APOLLO_SPEC_DOMAIN || url.identity.name != CONNECT_IDENTITY_NAME {
215 return Ok(None);
216 }
217
218 Ok(CONNECT_VERSIONS.find(&url.version))
219 }
220}
221
222impl SpecDefinition for ConnectSpecDefinition {
223 fn url(&self) -> &Url {
224 &self.url
225 }
226
227 fn directive_specs(&self) -> Vec<Box<dyn TypeAndDirectiveSpecification>> {
228 directive_specifications()
229 }
230
231 fn type_specs(&self) -> Vec<Box<dyn TypeAndDirectiveSpecification>> {
232 type_specifications()
233 }
234
235 fn minimum_federation_version(&self) -> &Version {
236 &self.minimum_federation_version
237 }
238
239 fn purpose(&self) -> Option<Purpose> {
240 Some(Purpose::EXECUTION)
241 }
242}
243
244pub(crate) static CONNECT_VERSIONS: LazyLock<SpecDefinitions<ConnectSpecDefinition>> =
245 LazyLock::new(|| {
246 let mut definitions = SpecDefinitions::new(Identity::connect_identity());
247 definitions.add(ConnectSpecDefinition::new(
248 Version { major: 0, minor: 1 },
249 Version {
250 major: 2,
251 minor: 10,
252 },
253 ));
254 definitions.add(ConnectSpecDefinition::new(
255 Version { major: 0, minor: 2 },
256 Version {
257 major: 2,
258 minor: 11,
259 },
260 ));
261 definitions.add(ConnectSpecDefinition::new(
262 Version { major: 0, minor: 3 },
263 Version {
264 major: 2,
265 minor: 12,
266 },
267 ));
268 definitions.add_preview(ConnectSpecDefinition::new(
269 Version { major: 0, minor: 4 },
270 Version {
271 major: 2,
272 minor: 13,
273 },
274 ));
275 definitions
276 });