Skip to content

Commit a1bc775

Browse files
authored
Sort order as "type-then-name" in introspection output (#1239, #1134)
1 parent c54137f commit a1bc775

File tree

5 files changed

+1312
-1274
lines changed

5 files changed

+1312
-1274
lines changed

juniper/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
103103
- Incorrect error when explicit `null` provided for `null`able list input parameter. ([#1086], [#1085])
104104
- Stack overflow on nested GraphQL fragments. ([CVE-2022-31173])
105105
- Unstable definitions order in schema generated by `RootNode::as_sdl()`. ([#1237], [#1134])
106+
- Unstable definitions order in schema generated by `introspect()` or other introspection queries. ([#1239], [#1134])
106107

107108
[#103]: /../../issues/103
108109
[#113]: /../../issues/113
@@ -165,6 +166,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
165166
[#1228]: /../../pull/1228
166167
[#1235]: /../../pull/1235
167168
[#1237]: /../../pull/1237
169+
[#1239]: /../../pull/1239
168170
[ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083
169171
[CVE-2022-31173]: /../../security/advisories/GHSA-4rx6-g5vg-5f3j
170172

juniper/src/schema/model.rs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,13 @@ impl<'a, S> SchemaType<'a, S> {
417417

418418
/// Get a list of types.
419419
pub fn type_list(&self) -> Vec<TypeType<S>> {
420-
self.types.values().map(|t| TypeType::Concrete(t)).collect()
420+
let mut types = self
421+
.types
422+
.values()
423+
.map(|t| TypeType::Concrete(t))
424+
.collect::<Vec<_>>();
425+
sort_concrete_types(&mut types);
426+
types
421427
}
422428

423429
/// Get a list of concrete types.
@@ -443,7 +449,9 @@ impl<'a, S> SchemaType<'a, S> {
443449

444450
/// Get a list of directives.
445451
pub fn directive_list(&self) -> Vec<&DirectiveType<S>> {
446-
self.directives.values().collect()
452+
let mut directives = self.directives.values().collect::<Vec<_>>();
453+
sort_directives(&mut directives);
454+
directives
447455
}
448456

449457
/// Get directive by name.
@@ -685,6 +693,66 @@ impl<'a, S> fmt::Display for TypeType<'a, S> {
685693
}
686694
}
687695

696+
/// Sorts the provided [`TypeType`]s in the "type-then-name" manner.
697+
fn sort_concrete_types<S>(types: &mut [TypeType<S>]) {
698+
types.sort_by(|a, b| {
699+
concrete_type_sort::by_type(a)
700+
.cmp(&concrete_type_sort::by_type(b))
701+
.then_with(|| concrete_type_sort::by_name(a).cmp(&concrete_type_sort::by_name(b)))
702+
});
703+
}
704+
705+
/// Sorts the provided [`DirectiveType`]s by name.
706+
fn sort_directives<S>(directives: &mut [&DirectiveType<S>]) {
707+
directives.sort_by(|a, b| a.name.cmp(&b.name));
708+
}
709+
710+
/// Evaluation of a [`TypeType`] weights for sorting (for concrete types only).
711+
///
712+
/// Used for deterministic introspection output.
713+
mod concrete_type_sort {
714+
use crate::meta::MetaType;
715+
716+
use super::TypeType;
717+
718+
/// Returns a [`TypeType`] sorting weight by its type.
719+
pub fn by_type<S>(t: &TypeType<S>) -> u8 {
720+
match t {
721+
TypeType::Concrete(MetaType::Enum(_)) => 0,
722+
TypeType::Concrete(MetaType::InputObject(_)) => 1,
723+
TypeType::Concrete(MetaType::Interface(_)) => 2,
724+
TypeType::Concrete(MetaType::Scalar(_)) => 3,
725+
TypeType::Concrete(MetaType::Object(_)) => 4,
726+
TypeType::Concrete(MetaType::Union(_)) => 5,
727+
// NOTE: The following types are not part of the introspected types.
728+
TypeType::Concrete(
729+
MetaType::List(_) | MetaType::Nullable(_) | MetaType::Placeholder(_),
730+
) => 6,
731+
// NOTE: Other variants will not appear since we're only sorting concrete types.
732+
TypeType::List(..) | TypeType::NonNull(_) => 7,
733+
}
734+
}
735+
736+
/// Returns a [`TypeType`] sorting weight by its name.
737+
pub fn by_name<'a, S>(t: &'a TypeType<'a, S>) -> Option<&'a str> {
738+
match t {
739+
TypeType::Concrete(MetaType::Enum(meta)) => Some(&meta.name),
740+
TypeType::Concrete(MetaType::InputObject(meta)) => Some(&meta.name),
741+
TypeType::Concrete(MetaType::Interface(meta)) => Some(&meta.name),
742+
TypeType::Concrete(MetaType::Scalar(meta)) => Some(&meta.name),
743+
TypeType::Concrete(MetaType::Object(meta)) => Some(&meta.name),
744+
TypeType::Concrete(MetaType::Union(meta)) => Some(&meta.name),
745+
TypeType::Concrete(
746+
// NOTE: The following types are not part of the introspected types.
747+
MetaType::List(_) | MetaType::Nullable(_) | MetaType::Placeholder(_),
748+
)
749+
// NOTE: Other variants will not appear since we're only sorting concrete types.
750+
| TypeType::List(..)
751+
| TypeType::NonNull(_) => None,
752+
}
753+
}
754+
}
755+
688756
#[cfg(test)]
689757
mod root_node_test {
690758
#[cfg(feature = "schema-language")]

juniper/src/schema/schema.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -282,11 +282,11 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
282282
TypeType::Concrete(&MetaType::Interface(InterfaceMeta {
283283
name: ref iface_name,
284284
..
285-
})) => Some(
286-
context
287-
.concrete_type_list()
288-
.iter()
289-
.filter_map(|&ct| {
285+
})) => {
286+
let mut type_names = context
287+
.types
288+
.values()
289+
.filter_map(|ct| {
290290
if let MetaType::Object(ObjectMeta {
291291
name,
292292
interface_names,
@@ -295,15 +295,21 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
295295
{
296296
interface_names
297297
.iter()
298-
.any(|name| name == iface_name)
299-
.then(|| context.type_by_name(name))
300-
.flatten()
298+
.any(|iname| iname == iface_name)
299+
.then(|| name.as_ref())
301300
} else {
302301
None
303302
}
304303
})
305-
.collect(),
306-
),
304+
.collect::<Vec<_>>();
305+
type_names.sort();
306+
Some(
307+
type_names
308+
.into_iter()
309+
.filter_map(|n| context.type_by_name(n))
310+
.collect(),
311+
)
312+
}
307313
_ => None,
308314
}
309315
}

juniper/src/tests/introspection_tests.rs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::collections::HashSet;
22

3+
use pretty_assertions::assert_eq;
4+
35
use crate::{
46
graphql_vars,
57
introspection::IntrospectionFormat,
@@ -184,14 +186,20 @@ async fn test_introspection_directives() {
184186
EmptySubscription::<Database>::new(),
185187
);
186188

187-
let mut result = crate::execute(q, None, &schema, &graphql_vars! {}, &database)
189+
let result = crate::execute(q, None, &schema, &graphql_vars! {}, &database)
188190
.await
189191
.unwrap();
190-
sort_schema_value(&mut result.0);
191192

192-
let mut expected = graphql_value!({
193+
let expected = graphql_value!({
193194
"__schema": {
194195
"directives": [
196+
{
197+
"name": "deprecated",
198+
"locations": [
199+
"FIELD_DEFINITION",
200+
"ENUM_VALUE",
201+
],
202+
},
195203
{
196204
"name": "include",
197205
"locations": [
@@ -208,13 +216,6 @@ async fn test_introspection_directives() {
208216
"INLINE_FRAGMENT",
209217
],
210218
},
211-
{
212-
"name": "deprecated",
213-
"locations": [
214-
"FIELD_DEFINITION",
215-
"ENUM_VALUE",
216-
],
217-
},
218219
{
219220
"name": "specifiedBy",
220221
"locations": [
@@ -224,7 +225,6 @@ async fn test_introspection_directives() {
224225
],
225226
},
226227
});
227-
sort_schema_value(&mut expected);
228228

229229
assert_eq!(result, (expected, vec![]));
230230
}
@@ -286,9 +286,9 @@ async fn test_builtin_introspection_query() {
286286
EmptyMutation::<Database>::new(),
287287
EmptySubscription::<Database>::new(),
288288
);
289-
let mut result = crate::introspect(&schema, &database, IntrospectionFormat::default()).unwrap();
290-
sort_schema_value(&mut result.0);
289+
let result = crate::introspect(&schema, &database, IntrospectionFormat::default()).unwrap();
291290
let expected = schema_introspection_result();
291+
292292
assert_eq!(result, (expected, vec![]));
293293
}
294294

@@ -301,9 +301,8 @@ async fn test_builtin_introspection_query_without_descriptions() {
301301
EmptySubscription::<Database>::new(),
302302
);
303303

304-
let mut result =
304+
let result =
305305
crate::introspect(&schema, &database, IntrospectionFormat::WithoutDescriptions).unwrap();
306-
sort_schema_value(&mut result.0);
307306
let expected = schema_introspection_result_without_descriptions();
308307

309308
assert_eq!(result, (expected, vec![]));

0 commit comments

Comments
 (0)