Skip to content

Commit d31fe03

Browse files
committed
all: Change GraphQL output to follow the order of fields in the query
That is demanded by the GraphQL spec. We need to have a predictable order to ensure attestations remain stable, though this change breaks attestations since it changes the order in which fields appear in the output from alphabetical (however a `BTreeMap` orders string keys) to the order in which fields appear in a query. It also allows us to replace `BTreeMaps`, which are fairly memory intensive, with cheaper `Vec`. The test changes all reflect the changed output behavior; they only reorder fields in the expected output but do not otherwise alter the tests. Fixes #2943
1 parent b8b6a68 commit d31fe03

File tree

6 files changed

+132
-51
lines changed

6 files changed

+132
-51
lines changed

core/tests/interfaces.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ async fn interface_inline_fragment_with_subquery() {
553553
.unwrap();
554554
let data = extract_data!(res).unwrap();
555555
let exp = object! {
556-
leggeds: vec![ object!{ airspeed: 5, legs: 2, parent: object! { id: "mama_bird" } },
556+
leggeds: vec![ object!{ legs: 2, airspeed: 5, parent: object! { id: "mama_bird" } },
557557
object!{ legs: 4 }]
558558
};
559559
assert_eq!(data, exp);

graph/src/data/graphql/object_macro.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,17 @@ macro_rules! impl_into_values {
8383

8484
impl_into_values![(String, String), (f64, Float), (bool, Boolean)];
8585

86-
/// Creates a `graphql_parser::query::Value::Object` from key/value pairs.
86+
/// Creates a `data::value::Value::Object` from key/value pairs.
8787
#[macro_export]
8888
macro_rules! object {
8989
($($name:ident: $value:expr,)*) => {
9090
{
91-
let mut result = ::std::collections::BTreeMap::new();
91+
let mut result = $crate::data::value::Object::new();
9292
$(
9393
let value = $crate::data::graphql::object_macro::IntoValue::into_value($value);
9494
result.insert(stringify!($name).to_string(), value);
9595
)*
96-
$crate::prelude::r::Value::object(result)
96+
$crate::prelude::r::Value::Object(result)
9797
}
9898
};
9999
($($name:ident: $value:expr),*) => {

graph/src/data/value.rs

Lines changed: 95 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,41 @@ use std::collections::BTreeMap;
55
use std::convert::TryFrom;
66
use std::iter::FromIterator;
77

8+
const TOMBSTONE_KEY: &str = "*dead*";
9+
10+
#[derive(Clone, Debug, PartialEq)]
11+
struct Entry {
12+
key: String,
13+
value: Value,
14+
}
15+
816
#[derive(Clone, PartialEq)]
9-
pub struct Object(BTreeMap<String, Value>);
17+
pub struct Object(Vec<Entry>);
1018

1119
impl Object {
1220
pub fn new() -> Self {
13-
Self(BTreeMap::default())
21+
Self(Vec::new())
1422
}
1523

1624
pub fn get(&self, key: &str) -> Option<&Value> {
17-
self.0.get(key)
25+
self.0
26+
.iter()
27+
.find(|entry| entry.key == key)
28+
.map(|entry| &entry.value)
1829
}
1930

2031
pub fn remove(&mut self, key: &str) -> Option<Value> {
21-
self.0.remove(key)
32+
self.0
33+
.iter_mut()
34+
.find(|entry| entry.key == key)
35+
.map(|entry| {
36+
entry.key = TOMBSTONE_KEY.to_string();
37+
std::mem::replace(&mut entry.value, Value::Null)
38+
})
2239
}
2340

24-
pub fn iter(&self) -> std::collections::btree_map::Iter<String, Value> {
25-
self.into_iter()
41+
pub fn iter(&self) -> impl Iterator<Item = (&String, &Value)> {
42+
ObjectIter::new(self)
2643
}
2744

2845
fn len(&self) -> usize {
@@ -34,33 +51,92 @@ impl Object {
3451
}
3552

3653
pub fn insert(&mut self, key: String, value: Value) -> Option<Value> {
37-
self.0.insert(key, value)
54+
match self.0.iter_mut().find(|entry| &entry.key == &key) {
55+
Some(entry) => Some(std::mem::replace(&mut entry.value, value)),
56+
None => {
57+
self.0.push(Entry { key, value });
58+
None
59+
}
60+
}
3861
}
3962
}
4063

4164
impl FromIterator<(String, Value)> for Object {
4265
fn from_iter<T: IntoIterator<Item = (String, Value)>>(iter: T) -> Self {
43-
Object(BTreeMap::from_iter(iter))
66+
let mut items: Vec<_> = Vec::new();
67+
for (key, value) in iter {
68+
items.push(Entry { key, value })
69+
}
70+
Object(items)
71+
}
72+
}
73+
74+
pub struct ObjectOwningIter {
75+
iter: std::vec::IntoIter<Entry>,
76+
}
77+
78+
impl Iterator for ObjectOwningIter {
79+
type Item = (String, Value);
80+
81+
fn next(&mut self) -> Option<Self::Item> {
82+
while let Some(entry) = self.iter.next() {
83+
if &entry.key != TOMBSTONE_KEY {
84+
return Some((entry.key, entry.value));
85+
}
86+
}
87+
None
4488
}
4589
}
4690

4791
impl IntoIterator for Object {
4892
type Item = (String, Value);
4993

50-
type IntoIter = std::collections::btree_map::IntoIter<String, Value>;
94+
type IntoIter = ObjectOwningIter;
5195

5296
fn into_iter(self) -> Self::IntoIter {
53-
self.0.into_iter()
97+
ObjectOwningIter {
98+
iter: self.0.into_iter(),
99+
}
54100
}
55101
}
56102

57-
impl<'a> IntoIterator for &'a Object {
103+
pub struct ObjectIter<'a> {
104+
iter: std::slice::Iter<'a, Entry>,
105+
}
106+
107+
impl<'a> ObjectIter<'a> {
108+
fn new(object: &'a Object) -> Self {
109+
Self {
110+
iter: object.0.as_slice().iter(),
111+
}
112+
}
113+
}
114+
impl<'a> Iterator for ObjectIter<'a> {
58115
type Item = (&'a String, &'a Value);
59116

60-
type IntoIter = std::collections::btree_map::Iter<'a, String, Value>;
117+
fn next(&mut self) -> Option<Self::Item> {
118+
while let Some(entry) = self.iter.next() {
119+
if &entry.key != TOMBSTONE_KEY {
120+
return Some((&entry.key, &entry.value));
121+
}
122+
}
123+
None
124+
}
125+
}
126+
127+
impl<'a> IntoIterator for &'a Object {
128+
type Item = <ObjectIter<'a> as Iterator>::Item;
129+
130+
type IntoIter = ObjectIter<'a>;
61131

62132
fn into_iter(self) -> Self::IntoIter {
63-
self.0.iter()
133+
ObjectIter::new(self)
134+
}
135+
}
136+
137+
impl CacheWeight for Entry {
138+
fn indirect_weight(&self) -> usize {
139+
self.key.indirect_weight() + self.value.indirect_weight()
64140
}
65141
}
66142

@@ -72,7 +148,7 @@ impl CacheWeight for Object {
72148

73149
impl Default for Object {
74150
fn default() -> Self {
75-
Self(BTreeMap::default())
151+
Self(Vec::default())
76152
}
77153
}
78154

@@ -96,7 +172,11 @@ pub enum Value {
96172

97173
impl Value {
98174
pub fn object(map: BTreeMap<String, Value>) -> Self {
99-
Value::Object(Object(map))
175+
let items = map
176+
.into_iter()
177+
.map(|(key, value)| Entry { key, value })
178+
.collect();
179+
Value::Object(Object(items))
100180
}
101181

102182
pub fn is_null(&self) -> bool {

0 commit comments

Comments
 (0)