Skip to content

Commit b90786d

Browse files
chore(federation): add macros for internal errors (#6080)
1 parent d7329a9 commit b90786d

File tree

4 files changed

+105
-93
lines changed

4 files changed

+105
-93
lines changed

apollo-federation/src/error/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,37 @@ use lazy_static::lazy_static;
1313

1414
use crate::subgraph::spec::FederationSpecError;
1515

16+
/// Break out of the current function, returning an internal error.
17+
#[macro_export]
18+
macro_rules! internal_error {
19+
( $( $arg:tt )+ ) => {
20+
return Err($crate::error::FederationError::internal(format!( $( $arg )+ )).into());
21+
}
22+
}
23+
24+
/// A safe assertion: in debug mode, it panicks on failure, and in production, it returns an
25+
/// internal error.
26+
///
27+
/// Treat this as an assertion. It must only be used for conditions that *should never happen*
28+
/// in normal operation.
29+
#[macro_export]
30+
macro_rules! ensure {
31+
( $expr:expr, $( $arg:tt )+ ) => {
32+
#[cfg(debug_assertions)]
33+
{
34+
if false {
35+
return Err($crate::error::FederationError::internal("ensure!() must be used in a function that returns a Result").into());
36+
}
37+
assert!($expr, $( $arg )+);
38+
}
39+
40+
#[cfg(not(debug_assertions))]
41+
if !$expr {
42+
$crate::internal_error!( $( $arg )+ );
43+
}
44+
}
45+
}
46+
1647
// What we really needed here was the string representations in enum form, this isn't meant to
1748
// replace AST components.
1849
#[derive(Clone, Debug, strum_macros::Display)]

apollo-federation/src/operation/merging.rs

Lines changed: 64 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ use super::NamedFragments;
1515
use super::Selection;
1616
use super::SelectionSet;
1717
use super::SelectionValue;
18+
use crate::ensure;
1819
use crate::error::FederationError;
20+
use crate::internal_error;
1921

2022
impl<'a> FieldSelectionValue<'a> {
2123
/// Merges the given field selections into this one.
@@ -36,31 +38,29 @@ impl<'a> FieldSelectionValue<'a> {
3638
let mut selection_sets = vec![];
3739
for other in others {
3840
let other_field = &other.field;
39-
if other_field.schema != self_field.schema {
40-
return Err(FederationError::internal(
41-
"Cannot merge field selections from different schemas",
42-
));
43-
}
44-
if other_field.field_position != self_field.field_position {
45-
return Err(FederationError::internal(format!(
46-
"Cannot merge field selection for field \"{}\" into a field selection for field \"{}\"",
47-
other_field.field_position,
48-
self_field.field_position,
49-
)));
50-
}
41+
ensure!(
42+
other_field.schema == self_field.schema,
43+
"Cannot merge field selections from different schemas",
44+
);
45+
ensure!(
46+
other_field.field_position == self_field.field_position,
47+
"Cannot merge field selection for field \"{}\" into a field selection for field \"{}\"",
48+
other_field.field_position,
49+
self_field.field_position,
50+
);
5151
if self.get().selection_set.is_some() {
5252
let Some(other_selection_set) = &other.selection_set else {
53-
return Err(FederationError::internal(format!(
53+
internal_error!(
5454
"Field \"{}\" has composite type but not a selection set",
5555
other_field.field_position,
56-
)));
56+
);
5757
};
5858
selection_sets.push(other_selection_set);
5959
} else if other.selection_set.is_some() {
60-
return Err(FederationError::internal(format!(
60+
internal_error!(
6161
"Field \"{}\" has non-composite type but also has a selection set",
6262
other_field.field_position,
63-
)));
63+
);
6464
}
6565
}
6666
if let Some(self_selection_set) = self.get_selection_set_mut() {
@@ -87,22 +87,16 @@ impl<'a> InlineFragmentSelectionValue<'a> {
8787
let mut selection_sets = vec![];
8888
for other in others {
8989
let other_inline_fragment = &other.inline_fragment;
90-
if other_inline_fragment.schema != self_inline_fragment.schema {
91-
return Err(FederationError::internal(
92-
"Cannot merge inline fragment from different schemas",
93-
));
94-
}
95-
if other_inline_fragment.parent_type_position
96-
!= self_inline_fragment.parent_type_position
97-
{
98-
return Err(FederationError::internal(
99-
format!(
100-
"Cannot merge inline fragment of parent type \"{}\" into an inline fragment of parent type \"{}\"",
101-
other_inline_fragment.parent_type_position,
102-
self_inline_fragment.parent_type_position,
103-
),
104-
));
105-
}
90+
ensure!(
91+
other_inline_fragment.schema == self_inline_fragment.schema,
92+
"Cannot merge inline fragment from different schemas",
93+
);
94+
ensure!(
95+
other_inline_fragment.parent_type_position == self_inline_fragment.parent_type_position,
96+
"Cannot merge inline fragment of parent type \"{}\" into an inline fragment of parent type \"{}\"",
97+
other_inline_fragment.parent_type_position,
98+
self_inline_fragment.parent_type_position,
99+
);
106100
selection_sets.push(&other.selection_set);
107101
}
108102
self.get_selection_set_mut()
@@ -127,11 +121,10 @@ impl<'a> FragmentSpreadSelectionValue<'a> {
127121
let self_fragment_spread = &self.get().spread;
128122
for other in others {
129123
let other_fragment_spread = &other.spread;
130-
if other_fragment_spread.schema != self_fragment_spread.schema {
131-
return Err(FederationError::internal(
132-
"Cannot merge fragment spread from different schemas",
133-
));
134-
}
124+
ensure!(
125+
other_fragment_spread.schema == self_fragment_spread.schema,
126+
"Cannot merge fragment spread from different schemas",
127+
);
135128
// Nothing to do since the fragment spread is already part of the selection set.
136129
// Fragment spreads are uniquely identified by fragment name and applied directives.
137130
// Since there is already an entry for the same fragment spread, there is no point
@@ -157,20 +150,16 @@ impl SelectionSet {
157150
) -> Result<(), FederationError> {
158151
let mut selections_to_merge = vec![];
159152
for other in others {
160-
if other.schema != self.schema {
161-
return Err(FederationError::internal(
162-
"Cannot merge selection sets from different schemas",
163-
));
164-
}
165-
if other.type_position != self.type_position {
166-
return Err(FederationError::internal(
167-
format!(
168-
"Cannot merge selection set for type \"{}\" into a selection set for type \"{}\"",
169-
other.type_position,
170-
self.type_position,
171-
),
172-
));
173-
}
153+
ensure!(
154+
other.schema == self.schema,
155+
"Cannot merge selection sets from different schemas",
156+
);
157+
ensure!(
158+
other.type_position == self.type_position,
159+
"Cannot merge selection set for type \"{}\" into a selection set for type \"{}\"",
160+
other.type_position,
161+
self.type_position,
162+
);
174163
selections_to_merge.extend(other.selections.values());
175164
}
176165
self.merge_selections_into(selections_to_merge.into_iter())
@@ -198,12 +187,10 @@ impl SelectionSet {
198187
selection_map::Entry::Occupied(existing) => match existing.get() {
199188
Selection::Field(self_field_selection) => {
200189
let Selection::Field(other_field_selection) = other_selection else {
201-
return Err(FederationError::internal(
202-
format!(
203-
"Field selection key for field \"{}\" references non-field selection",
204-
self_field_selection.field.field_position,
205-
),
206-
));
190+
internal_error!(
191+
"Field selection key for field \"{}\" references non-field selection",
192+
self_field_selection.field.field_position,
193+
);
207194
};
208195
fields
209196
.entry(other_key)
@@ -214,12 +201,10 @@ impl SelectionSet {
214201
let Selection::FragmentSpread(other_fragment_spread_selection) =
215202
other_selection
216203
else {
217-
return Err(FederationError::internal(
218-
format!(
219-
"Fragment spread selection key for fragment \"{}\" references non-field selection",
220-
self_fragment_spread_selection.spread.fragment_name,
221-
),
222-
));
204+
internal_error!(
205+
"Fragment spread selection key for fragment \"{}\" references non-field selection",
206+
self_fragment_spread_selection.spread.fragment_name,
207+
);
223208
};
224209
fragment_spreads
225210
.entry(other_key)
@@ -230,17 +215,15 @@ impl SelectionSet {
230215
let Selection::InlineFragment(other_inline_fragment_selection) =
231216
other_selection
232217
else {
233-
return Err(FederationError::internal(
234-
format!(
235-
"Inline fragment selection key under parent type \"{}\" {}references non-field selection",
236-
self_inline_fragment_selection.inline_fragment.parent_type_position,
237-
self_inline_fragment_selection.inline_fragment.type_condition_position.clone()
238-
.map_or_else(
239-
String::new,
240-
|cond| format!("(type condition: {}) ", cond),
241-
),
242-
),
243-
));
218+
internal_error!(
219+
"Inline fragment selection key under parent type \"{}\" {}references non-field selection",
220+
self_inline_fragment_selection.inline_fragment.parent_type_position,
221+
self_inline_fragment_selection.inline_fragment.type_condition_position.clone()
222+
.map_or_else(
223+
String::new,
224+
|cond| format!("(type condition: {}) ", cond),
225+
),
226+
);
244227
};
245228
inline_fragments
246229
.entry(other_key)
@@ -306,9 +289,8 @@ impl SelectionSet {
306289
&mut self,
307290
selection: &Selection,
308291
) -> Result<(), FederationError> {
309-
debug_assert_eq!(
310-
&self.schema,
311-
selection.schema(),
292+
ensure!(
293+
self.schema == *selection.schema(),
312294
"In order to add selection it needs to point to the same schema"
313295
);
314296
self.merge_selections_into(std::iter::once(selection))
@@ -328,12 +310,12 @@ impl SelectionSet {
328310
&mut self,
329311
selection_set: &SelectionSet,
330312
) -> Result<(), FederationError> {
331-
debug_assert_eq!(
332-
self.schema, selection_set.schema,
313+
ensure!(
314+
self.schema == selection_set.schema,
333315
"In order to add selection set it needs to point to the same schema."
334316
);
335-
debug_assert_eq!(
336-
self.type_position, selection_set.type_position,
317+
ensure!(
318+
self.type_position == selection_set.type_position,
337319
"In order to add selection set it needs to point to the same type position"
338320
);
339321
self.merge_into(std::iter::once(selection_set))
@@ -386,9 +368,7 @@ pub(crate) fn merge_selection_sets(
386368
mut selection_sets: Vec<SelectionSet>,
387369
) -> Result<SelectionSet, FederationError> {
388370
let Some((first, remainder)) = selection_sets.split_first_mut() else {
389-
return Err(FederationError::internal(
390-
"merge_selection_sets(): must have at least one selection set",
391-
));
371+
internal_error!("merge_selection_sets(): must have at least one selection set");
392372
};
393373
first.merge_into(remainder.iter())?;
394374

apollo-federation/src/operation/rebase.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use super::Selection;
2222
use super::SelectionId;
2323
use super::SelectionSet;
2424
use super::TYPENAME_FIELD;
25+
use crate::ensure;
2526
use crate::error::FederationError;
2627
use crate::schema::position::CompositeTypeDefinitionPosition;
2728
use crate::schema::position::OutputTypeDefinitionPosition;
@@ -376,12 +377,12 @@ impl FragmentSpread {
376377
}
377378
.into());
378379
};
379-
debug_assert_eq!(
380-
*schema, self.schema,
380+
ensure!(
381+
*schema == self.schema,
381382
"Fragment spread should only be rebased within the same subgraph"
382383
);
383-
debug_assert_eq!(
384-
*schema, named_fragment.schema,
384+
ensure!(
385+
*schema == named_fragment.schema,
385386
"Referenced named fragment should've been rebased for the subgraph"
386387
);
387388
if runtime_types_intersect(

apollo-federation/src/operation/simplify.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use super::NamedFragments;
1414
use super::Selection;
1515
use super::SelectionMap;
1616
use super::SelectionSet;
17+
use crate::ensure;
1718
use crate::error::FederationError;
1819
use crate::schema::position::CompositeTypeDefinitionPosition;
1920
use crate::schema::ValidFederationSchema;
@@ -136,11 +137,10 @@ impl FragmentSpreadSelection {
136137

137138
// We must update the spread parent type if necessary since we're not going deeper,
138139
// or we'll be fundamentally losing context.
139-
if self.spread.schema != *schema {
140-
return Err(FederationError::internal(
141-
"Should not try to flatten_unnecessary_fragments using a type from another schema",
142-
));
143-
}
140+
ensure!(
141+
self.spread.schema == *schema,
142+
"Should not try to flatten_unnecessary_fragments using a type from another schema",
143+
);
144144

145145
let rebased_fragment_spread = self.rebase_on(parent_type, named_fragments, schema)?;
146146
Ok(Some(SelectionOrSet::Selection(rebased_fragment_spread)))

0 commit comments

Comments
 (0)