Skip to content

Commit e64287c

Browse files
Sort order as "type-then-name" in generated schema's SDL (#1237, #1134)
- rename `RootNode::as_schema_language()` method as `RootNode::as_sdl()` - rename `RootNode::as_parser_document()` method as `RootNode::as_document()` - merge `graphql-parser` and `schema-language` Cargo features Co-authored-by: Michael Groble <[email protected]>
1 parent b1b31ff commit e64287c

File tree

9 files changed

+278
-128
lines changed

9 files changed

+278
-128
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ jobs:
107107
- { feature: chrono-clock, crate: juniper }
108108
- { feature: chrono-tz, crate: juniper }
109109
- { feature: expose-test-schema, crate: juniper }
110-
- { feature: graphql-parser, crate: juniper }
111110
- { feature: rust_decimal, crate: juniper }
112111
- { feature: schema-language, crate: juniper }
113112
- { feature: time, crate: juniper }

book/src/schema/schemas_and_mutations.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,17 @@ fn main() {
8989
EmptySubscription::<()>::new(),
9090
);
9191

92-
// Convert the Rust schema into the GraphQL Schema Language.
93-
let result = schema.as_schema_language();
92+
// Convert the Rust schema into the GraphQL Schema Definition Language.
93+
let result = schema.as_sdl();
9494

9595
let expected = "\
96-
type Query {
97-
hello: String!
98-
}
99-
10096
schema {
10197
query: Query
10298
}
99+
100+
type Query {
101+
hello: String!
102+
}
103103
";
104104
# #[cfg(not(target_os = "windows"))]
105105
assert_eq!(result, expected);

juniper/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
5555
- Made `LookAheadMethods::children()` method to return slice instead of `Vec`. ([#1200])
5656
- Abstracted `Spanning::start` and `Spanning::end` fields into separate struct `Span`. ([#1207], [#1208])
5757
- Added `Span` to `Arguments` and `LookAheadArguments`. ([#1206], [#1209])
58+
- Removed `graphql-parser-integration` and `graphql-parser` [Cargo feature]s by merging them into `schema-language` [Cargo feature]. ([#1237])
59+
- Renamed `RootNode::as_schema_language()` method as `RootNode::as_sdl()`. ([#1237])
60+
- Renamed `RootNode::as_parser_document()` method as `RootNode::as_document()`. ([#1237])
5861

5962
### Added
6063

@@ -89,6 +92,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
8992
- Incorrect input value coercion with defaults. ([#1080], [#1073])
9093
- Incorrect error when explicit `null` provided for `null`able list input parameter. ([#1086], [#1085])
9194
- Stack overflow on nested GraphQL fragments. ([CVE-2022-31173])
95+
- Unstable definitions order in schema generated by `RootNode::as_sdl()`. ([#1237], [#1134])
9296

9397
[#103]: /../../issues/103
9498
[#113]: /../../issues/113
@@ -132,6 +136,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
132136
[#1086]: /../../pull/1086
133137
[#1118]: /../../issues/1118
134138
[#1119]: /../../pull/1119
139+
[#1134]: /../../issues/1134
135140
[#1138]: /../../issues/1138
136141
[#1145]: /../../pull/1145
137142
[#1147]: /../../pull/1147
@@ -149,6 +154,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
149154
[#1227]: /../../pull/1227
150155
[#1228]: /../../pull/1228
151156
[#1235]: /../../pull/1235
157+
[#1237]: /../../pull/1237
152158
[ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083
153159
[CVE-2022-31173]: /../../security/advisories/GHSA-4rx6-g5vg-5f3j
154160

juniper/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,9 @@ chrono = ["dep:chrono"]
3939
chrono-clock = ["chrono", "chrono/clock"]
4040
chrono-tz = ["dep:chrono-tz", "dep:regex"]
4141
expose-test-schema = ["dep:anyhow", "dep:serde_json"]
42-
graphql-parser = ["dep:graphql-parser", "dep:void"]
4342
js = ["chrono?/wasmbind", "time?/wasm-bindgen", "uuid?/js"]
4443
rust_decimal = ["dep:rust_decimal"]
45-
schema-language = ["graphql-parser"]
44+
schema-language = ["dep:graphql-parser", "dep:void"]
4645
time = ["dep:time"]
4746
url = ["dep:url"]
4847
uuid = ["dep:uuid"]

juniper/src/schema/model.rs

Lines changed: 129 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{borrow::Cow, fmt};
22

33
use fnv::FnvHashMap;
4-
#[cfg(feature = "graphql-parser")]
4+
#[cfg(feature = "schema-language")]
55
use graphql_parser::schema::Document;
66

77
use crate::{
@@ -13,9 +13,6 @@ use crate::{
1313
GraphQLEnum,
1414
};
1515

16-
#[cfg(feature = "graphql-parser")]
17-
use crate::schema::translate::{graphql_parser::GraphQLParserTranslator, SchemaTranslator};
18-
1916
/// Root query node of a schema
2017
///
2118
/// This brings the mutation, subscription and query types together,
@@ -221,17 +218,40 @@ where
221218
}
222219

223220
#[cfg(feature = "schema-language")]
224-
/// The schema definition as a `String` in the
225-
/// [GraphQL Schema Language](https://graphql.org/learn/schema/#type-language)
226-
/// format.
227-
pub fn as_schema_language(&self) -> String {
228-
self.as_parser_document().to_string()
221+
/// Returns this [`RootNode`] as a [`String`] containing the schema in [SDL (schema definition language)].
222+
///
223+
/// # Sorted
224+
///
225+
/// The order of the generated definitions is stable and is sorted in the "type-then-name" manner.
226+
///
227+
/// If another sorting order is required, then the [`as_document()`] method should be used, which allows to sort the
228+
/// returned [`Document`] in the desired manner and then to convert it [`to_string()`].
229+
///
230+
/// [`as_document()`]: RootNode::as_document
231+
/// [`to_string()`]: ToString::to_string
232+
/// [0]: https://graphql.org/learn/schema#type-language
233+
#[must_use]
234+
pub fn as_sdl(&self) -> String {
235+
use crate::schema::translate::graphql_parser::sort_schema_document;
236+
237+
let mut doc = self.as_document();
238+
sort_schema_document(&mut doc);
239+
doc.to_string()
229240
}
230241

231-
#[cfg(feature = "graphql-parser")]
232-
/// The schema definition as a [`graphql_parser`](https://crates.io/crates/graphql-parser)
233-
/// [`Document`](https://docs.rs/graphql-parser/latest/graphql_parser/schema/struct.Document.html).
234-
pub fn as_parser_document(&'a self) -> Document<'a, &'a str> {
242+
#[cfg(feature = "schema-language")]
243+
/// Returns this [`RootNode`] as a [`graphql_parser`]'s [`Document`].
244+
///
245+
/// # Unsorted
246+
///
247+
/// The order of the generated definitions in the returned [`Document`] is NOT stable and may change without any
248+
/// real schema changes.
249+
#[must_use]
250+
pub fn as_document(&'a self) -> Document<'a, &'a str> {
251+
use crate::schema::translate::{
252+
graphql_parser::GraphQLParserTranslator, SchemaTranslator as _,
253+
};
254+
235255
GraphQLParserTranslator::translate_schema(&self.schema)
236256
}
237257
}
@@ -666,119 +686,141 @@ impl<'a, S> fmt::Display for TypeType<'a, S> {
666686
}
667687

668688
#[cfg(test)]
669-
mod test {
670-
671-
#[cfg(feature = "graphql-parser")]
672-
mod graphql_parser_integration {
689+
mod root_node_test {
690+
#[cfg(feature = "schema-language")]
691+
mod as_document {
673692
use crate::{graphql_object, EmptyMutation, EmptySubscription, RootNode};
674693

675-
#[test]
676-
fn graphql_parser_doc() {
677-
struct Query;
678-
#[graphql_object]
679-
impl Query {
680-
fn blah() -> bool {
681-
true
682-
}
694+
struct Query;
695+
696+
#[graphql_object]
697+
impl Query {
698+
fn blah() -> bool {
699+
true
683700
}
701+
}
702+
703+
#[test]
704+
fn generates_correct_document() {
684705
let schema = RootNode::new(
685706
Query,
686707
EmptyMutation::<()>::new(),
687708
EmptySubscription::<()>::new(),
688709
);
689710
let ast = graphql_parser::parse_schema::<&str>(
711+
//language=GraphQL
690712
r#"
691713
type Query {
692-
blah: Boolean!
714+
blah: Boolean!
693715
}
694716
695717
schema {
696718
query: Query
697719
}
698-
"#,
720+
"#,
699721
)
700722
.unwrap();
701-
assert_eq!(ast.to_string(), schema.as_parser_document().to_string());
723+
724+
assert_eq!(ast.to_string(), schema.as_document().to_string());
702725
}
703726
}
704727

705728
#[cfg(feature = "schema-language")]
706-
mod schema_language {
729+
mod as_sdl {
707730
use crate::{
708731
graphql_object, EmptyMutation, EmptySubscription, GraphQLEnum, GraphQLInputObject,
709732
GraphQLObject, GraphQLUnion, RootNode,
710733
};
711734

712-
#[test]
713-
fn schema_language() {
714-
#[derive(GraphQLObject, Default)]
715-
struct Cake {
716-
fresh: bool,
717-
}
718-
#[derive(GraphQLObject, Default)]
719-
struct IceCream {
720-
cold: bool,
735+
#[derive(GraphQLObject, Default)]
736+
struct Cake {
737+
fresh: bool,
738+
}
739+
740+
#[derive(GraphQLObject, Default)]
741+
struct IceCream {
742+
cold: bool,
743+
}
744+
745+
#[derive(GraphQLUnion)]
746+
enum GlutenFree {
747+
Cake(Cake),
748+
IceCream(IceCream),
749+
}
750+
751+
#[derive(GraphQLEnum)]
752+
enum Fruit {
753+
Apple,
754+
Orange,
755+
}
756+
757+
#[derive(GraphQLInputObject)]
758+
struct Coordinate {
759+
latitude: f64,
760+
longitude: f64,
761+
}
762+
763+
struct Query;
764+
765+
#[graphql_object]
766+
impl Query {
767+
fn blah() -> bool {
768+
true
721769
}
722-
#[derive(GraphQLUnion)]
723-
enum GlutenFree {
724-
Cake(Cake),
725-
IceCream(IceCream),
770+
771+
/// This is whatever's description.
772+
fn whatever() -> String {
773+
"foo".into()
726774
}
727-
#[derive(GraphQLEnum)]
728-
enum Fruit {
729-
Apple,
730-
Orange,
775+
776+
fn arr(stuff: Vec<Coordinate>) -> Option<&'static str> {
777+
(!stuff.is_empty()).then_some("stuff")
731778
}
732-
#[derive(GraphQLInputObject)]
733-
struct Coordinate {
734-
latitude: f64,
735-
longitude: f64,
779+
780+
fn fruit() -> Fruit {
781+
Fruit::Apple
736782
}
737-
struct Query;
738-
#[graphql_object]
739-
impl Query {
740-
fn blah() -> bool {
741-
true
742-
}
743-
/// This is whatever's description.
744-
fn whatever() -> String {
745-
"foo".into()
746-
}
747-
fn arr(stuff: Vec<Coordinate>) -> Option<&'static str> {
748-
(!stuff.is_empty()).then_some("stuff")
749-
}
750-
fn fruit() -> Fruit {
751-
Fruit::Apple
752-
}
753-
fn gluten_free(flavor: String) -> GlutenFree {
754-
if flavor == "savory" {
755-
GlutenFree::Cake(Cake::default())
756-
} else {
757-
GlutenFree::IceCream(IceCream::default())
758-
}
759-
}
760-
#[deprecated]
761-
fn old() -> i32 {
762-
42
763-
}
764-
#[deprecated(note = "This field is deprecated, use another.")]
765-
fn really_old() -> f64 {
766-
42.0
783+
784+
fn gluten_free(flavor: String) -> GlutenFree {
785+
if flavor == "savory" {
786+
GlutenFree::Cake(Cake::default())
787+
} else {
788+
GlutenFree::IceCream(IceCream::default())
767789
}
768790
}
769791

770-
let schema = RootNode::new(
792+
#[deprecated]
793+
fn old() -> i32 {
794+
42
795+
}
796+
797+
#[deprecated(note = "This field is deprecated, use another.")]
798+
fn really_old() -> f64 {
799+
42.0
800+
}
801+
}
802+
803+
#[test]
804+
fn generates_correct_sdl() {
805+
let actual = RootNode::new(
771806
Query,
772807
EmptyMutation::<()>::new(),
773808
EmptySubscription::<()>::new(),
774809
);
775-
let ast = graphql_parser::parse_schema::<&str>(
810+
let expected = graphql_parser::parse_schema::<&str>(
811+
//language=GraphQL
776812
r#"
777-
union GlutenFree = Cake | IceCream
813+
schema {
814+
query: Query
815+
}
778816
enum Fruit {
779817
APPLE
780818
ORANGE
781819
}
820+
input Coordinate {
821+
latitude: Float!
822+
longitude: Float!
823+
}
782824
type Cake {
783825
fresh: Boolean!
784826
}
@@ -795,17 +837,12 @@ mod test {
795837
old: Int! @deprecated
796838
reallyOld: Float! @deprecated(reason: "This field is deprecated, use another.")
797839
}
798-
input Coordinate {
799-
latitude: Float!
800-
longitude: Float!
801-
}
802-
schema {
803-
query: Query
804-
}
805-
"#,
840+
union GlutenFree = Cake | IceCream
841+
"#,
806842
)
807843
.unwrap();
808-
assert_eq!(ast.to_string(), schema.as_schema_language());
844+
845+
assert_eq!(actual.as_sdl(), expected.to_string());
809846
}
810847
}
811848
}

0 commit comments

Comments
 (0)