Skip to content

Commit 1e14cec

Browse files
authored
Merge pull request #80 from sam-berning/datastore_expect_json
Update datastore serializer to expect JSON and correctly handle null values
2 parents af909b4 + 8f882a4 commit 1e14cec

File tree

8 files changed

+182
-42
lines changed

8 files changed

+182
-42
lines changed

sources/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sources/api/apiserver/src/server/controller.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ where
136136
let os = BottlerocketRelease::new().context(error::ReleaseDataSnafu)?;
137137

138138
// Turn into a serde Value we can manipulate.
139-
let val = serde_json::to_value(os).expect("struct to value can't fail");
139+
let val = serde_json::to_value(os).context(error::SettingsToJsonSnafu)?;
140140

141141
// Structs are Objects in serde_json, which have a map of field -> value inside. We
142142
// destructure to get it by value, instead of as_object() which gives references.
@@ -366,7 +366,8 @@ pub(crate) fn set_settings<D: DataStore>(
366366
transaction: &str,
367367
) -> Result<()> {
368368
trace!("Serializing Settings to write to data store");
369-
let pairs = to_pairs_with_prefix("settings", settings)
369+
let settings_json = serde_json::to_value(settings).expect("struct to value can't fail");
370+
let pairs = to_pairs_with_prefix("settings", &settings_json)
370371
.context(error::DataStoreSerializationSnafu { given: "Settings" })?;
371372
let pending = Committed::Pending {
372373
tx: transaction.into(),

sources/api/apiserver/src/server/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ pub enum Error {
9494
#[snafu(display("Unable to serialize data: {}", source))]
9595
Serialize { source: serde_json::Error },
9696

97+
#[snafu(display("Error serializing settings to JSON: {}", source))]
98+
SettingsToJson { source: serde_json::Error },
99+
97100
#[snafu(display("Error serializing {}: {} ", given, source))]
98101
DataStoreSerialization {
99102
given: String,

sources/api/apiserver/src/server/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,7 @@ impl ResponseError for error::Error {
792792
SystemdNotifyStatus {} => StatusCode::INTERNAL_SERVER_ERROR,
793793
SetPermissions { .. } => StatusCode::INTERNAL_SERVER_ERROR,
794794
SetGroup { .. } => StatusCode::INTERNAL_SERVER_ERROR,
795+
SettingsToJson { .. } => StatusCode::INTERNAL_SERVER_ERROR,
795796
ReleaseData { .. } => StatusCode::INTERNAL_SERVER_ERROR,
796797
Shutdown { .. } => StatusCode::INTERNAL_SERVER_ERROR,
797798
Reboot { .. } => StatusCode::INTERNAL_SERVER_ERROR,

sources/api/datastore/src/serialization/pairs.rs

Lines changed: 156 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::{serialize_scalar, Key, KeyType, ScalarError};
2121
/// Settings -> DockerSettings -> bridge_ip = u64
2222
/// would turn into a key of "settings.docker-settings.bridge-ip" and a serialized String
2323
/// representing the u64 data.
24-
pub fn to_pairs<T: Serialize>(value: &T) -> Result<HashMap<Key, String>> {
24+
pub fn to_pairs(value: &serde_json::Value) -> Result<HashMap<Key, String>> {
2525
let mut output = HashMap::new();
2626
let serializer = Serializer::new(&mut output, None);
2727
value.serialize(serializer)?;
@@ -30,10 +30,9 @@ pub fn to_pairs<T: Serialize>(value: &T) -> Result<HashMap<Key, String>> {
3030

3131
/// Like to_pairs, but lets you add an arbitrary prefix to the resulting keys. A separator will
3232
/// automatically be added after the prefix.
33-
pub fn to_pairs_with_prefix<S, T>(prefix: S, value: &T) -> Result<HashMap<Key, String>>
33+
pub fn to_pairs_with_prefix<S>(prefix: S, value: &serde_json::Value) -> Result<HashMap<Key, String>>
3434
where
3535
S: AsRef<str>,
36-
T: Serialize,
3736
{
3837
let prefix = prefix.as_ref();
3938
let prefix_key = Key::new(KeyType::Data, prefix).map_err(|e| {
@@ -230,9 +229,12 @@ impl<'a> ser::Serializer for Serializer<'a> {
230229
bad_type("bytes")
231230
}
232231

233-
// We just don't expect to need these, and we doesn't have a great way to represent them.
232+
// serde_json::Value::Null is the only case where we should see this, so we can essentially
233+
// consider this to be serialize_null(). Since we should only see null values in a settings
234+
// input when the settings structure has an Option<T>, it should be safe to omit the key/value
235+
// pair from the serialization output if the value is null.
234236
fn serialize_unit(self) -> Result<()> {
235-
bad_type("unit")
237+
Ok(())
236238
}
237239

238240
fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
@@ -490,6 +492,7 @@ mod test {
490492
use crate::{Key, KeyType};
491493
use maplit::hashmap;
492494
use serde::Serialize;
495+
use serde_json::json;
493496

494497
// Helper macro for making a data Key for testing whose name we know is valid.
495498
macro_rules! key {
@@ -516,20 +519,22 @@ mod test {
516519
list: vec![3, 4, 5],
517520
boolean: true,
518521
};
519-
let keys = to_pairs(&b).unwrap();
522+
let j = serde_json::to_value(b).unwrap();
523+
let keys = to_pairs(&j).unwrap();
520524
assert_eq!(
521525
keys,
522526
hashmap!(
523-
key!("B.list") => "[3,4,5]".to_string(),
524-
key!("B.boolean") => "true".to_string(),
527+
key!("list") => "[3,4,5]".to_string(),
528+
key!("boolean") => "true".to_string(),
525529
)
526530
);
527531
}
528532

529533
#[test]
530534
fn empty_value() {
531535
let val: toml::Value = toml::from_str("").unwrap();
532-
let keys = to_pairs(&val).unwrap();
536+
let json = serde_json::to_value(val).unwrap();
537+
let keys = to_pairs(&json).unwrap();
533538
assert_eq!(keys, hashmap!())
534539
}
535540

@@ -540,13 +545,14 @@ mod test {
540545
boolean: true,
541546
};
542547
let a = A { id: 42, b: Some(b) };
543-
let keys = to_pairs(&a).unwrap();
548+
let j = serde_json::to_value(a).unwrap();
549+
let keys = to_pairs(&j).unwrap();
544550
assert_eq!(
545551
keys,
546552
hashmap!(
547-
key!("A.b.list") => "[5,6,7]".to_string(),
548-
key!("A.b.boolean") => "true".to_string(),
549-
key!("A.id") => "42".to_string(),
553+
key!("b.list") => "[5,6,7]".to_string(),
554+
key!("b.boolean") => "true".to_string(),
555+
key!("id") => "42".to_string(),
550556
)
551557
);
552558
}
@@ -559,7 +565,8 @@ mod test {
559565
key!("ie") => 43,
560566
),
561567
);
562-
let keys = to_pairs_with_prefix("map", &m).unwrap();
568+
let j = serde_json::to_value(m).unwrap();
569+
let keys = to_pairs_with_prefix("map", &j).unwrap();
563570
assert_eq!(
564571
keys,
565572
hashmap!(
@@ -577,7 +584,8 @@ mod test {
577584
key!("ie") => 43,
578585
),
579586
);
580-
let keys = to_pairs(&m).unwrap();
587+
let j = serde_json::to_value(m).unwrap();
588+
let keys = to_pairs(&j).unwrap();
581589
assert_eq!(
582590
keys,
583591
hashmap!(
@@ -590,7 +598,8 @@ mod test {
590598
#[test]
591599
fn concrete_fails() {
592600
let i = 42;
593-
to_pairs(&i).unwrap_err();
601+
let j = serde_json::to_value(i).unwrap();
602+
to_pairs(&j).unwrap_err();
594603
}
595604

596605
#[test]
@@ -601,7 +610,8 @@ mod test {
601610
key!("ie") => "oranges",
602611
),
603612
);
604-
let keys = to_pairs(&m).unwrap();
613+
let j = serde_json::to_value(m).unwrap();
614+
let keys = to_pairs(&j).unwrap();
605615
assert_eq!(
606616
keys,
607617
hashmap!(
@@ -626,7 +636,8 @@ mod test {
626636
key!("ie") => TestEnum::Beta,
627637
),
628638
);
629-
let keys = to_pairs(&m).unwrap();
639+
let j = serde_json::to_value(m).unwrap();
640+
let keys = to_pairs(&j).unwrap();
630641
assert_eq!(
631642
keys,
632643
hashmap!(
@@ -635,4 +646,131 @@ mod test {
635646
)
636647
);
637648
}
649+
650+
#[test]
651+
fn json_null() {
652+
let j = json!(null);
653+
let keys = to_pairs_with_prefix("null", &j).unwrap();
654+
// the null value and its key should be skipped in serialization, resulting in an empty
655+
// hashmap
656+
assert_eq!(keys, hashmap!());
657+
}
658+
659+
#[test]
660+
fn json_bool() {
661+
let j = json!(true);
662+
let keys = to_pairs_with_prefix("bool", &j).unwrap();
663+
assert_eq!(
664+
keys,
665+
hashmap!(
666+
key!("bool") => "true".to_string(),
667+
)
668+
);
669+
}
670+
671+
#[test]
672+
fn json_number() {
673+
let j = json!(42);
674+
let keys = to_pairs_with_prefix("number", &j).unwrap();
675+
assert_eq!(
676+
keys,
677+
hashmap!(
678+
key!("number") => "42".to_string(),
679+
)
680+
);
681+
}
682+
683+
#[test]
684+
fn json_number_float() {
685+
let j = json!(4.2);
686+
let keys = to_pairs_with_prefix("number", &j).unwrap();
687+
assert_eq!(
688+
keys,
689+
hashmap!(
690+
key!("number") => "4.2".to_string(),
691+
)
692+
);
693+
}
694+
695+
#[test]
696+
fn json_string() {
697+
let j = json!("hello");
698+
let keys = to_pairs_with_prefix("string", &j).unwrap();
699+
assert_eq!(
700+
keys,
701+
hashmap!(
702+
key!("string") => "\"hello\"".to_string(),
703+
)
704+
);
705+
}
706+
707+
#[test]
708+
fn json_array() {
709+
let j = json!(["foo", true, 42]);
710+
let keys = to_pairs_with_prefix("array", &j).unwrap();
711+
assert_eq!(
712+
keys,
713+
hashmap!(
714+
key!("array") => "[\"foo\",true,42]".to_string(),
715+
)
716+
);
717+
}
718+
719+
#[test]
720+
fn json_array_with_null() {
721+
let j = json!(["foo", null, true, 42]);
722+
let keys = to_pairs_with_prefix("array", &j).unwrap();
723+
assert_eq!(
724+
keys,
725+
hashmap!(
726+
key!("array") => "[\"foo\",null,true,42]".to_string(),
727+
)
728+
);
729+
}
730+
731+
#[test]
732+
fn json_object() {
733+
let j = json!({
734+
"bool": true,
735+
"number": 42,
736+
"string": "foo",
737+
"array": ["foo", true, null, 42],
738+
"object": {"number": 4.2}
739+
});
740+
let keys = to_pairs_with_prefix("object", &j).unwrap();
741+
assert_eq!(
742+
keys,
743+
hashmap!(
744+
key!("object.bool") => "true".to_string(),
745+
key!("object.number") => "42".to_string(),
746+
key!("object.string") => "\"foo\"".to_string(),
747+
key!("object.array") => "[\"foo\",true,null,42]".to_string(),
748+
key!("object.object.number") => "4.2".to_string(),
749+
)
750+
);
751+
}
752+
753+
#[test]
754+
fn json_object_with_null() {
755+
let j = json!({
756+
"null": null,
757+
"bool": true,
758+
"number": 42,
759+
"string": "foo",
760+
"array": ["foo", true, null, 42],
761+
"object": {"number": 4.2}
762+
});
763+
let keys = to_pairs_with_prefix("object", &j).unwrap();
764+
// the null value and its key should be skipped in serialization
765+
assert_eq!(
766+
keys,
767+
hashmap!(
768+
key!("object.bool") => "true".to_string(),
769+
key!("object.number") => "42".to_string(),
770+
key!("object.string") => "\"foo\"".to_string(),
771+
key!("object.array") => "[\"foo\",true,null,42]".to_string(),
772+
key!("object.object.number") => "4.2".to_string(),
773+
)
774+
);
775+
}
638776
}

sources/api/storewolf/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ log.workspace = true
1717
models.workspace = true
1818
rand = { workspace = true, features = ["std", "std_rng"] }
1919
semver.workspace = true
20+
serde_json.workspace = true
2021
simplelog.workspace = true
2122
snafu.workspace = true
2223
toml.workspace = true

sources/api/storewolf/src/main.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ mod error {
129129

130130
#[snafu(display("Could not read defaults from '{}': {}", path.display(), source))]
131131
ReadDefaults { path: PathBuf, source: io::Error },
132+
133+
#[snafu(display("Could not convert toml value to JSON: {}", source))]
134+
JsonConversion { source: serde_json::Error },
132135
}
133136
}
134137

@@ -288,16 +291,21 @@ fn populate_default_datastore<P: AsRef<Path>>(
288291
// services run, which will create config files for default keys that require them.
289292
if let Some(def_settings_val) = maybe_settings_val {
290293
debug!("Serializing default settings and writing new ones to datastore");
291-
let def_settings_table = def_settings_val
292-
.as_table()
293-
.context(error::DefaultSettingsNotTableSnafu)?;
294+
295+
ensure!(
296+
def_settings_val.is_table(),
297+
error::DefaultSettingsNotTableSnafu
298+
);
299+
300+
let settings_json =
301+
serde_json::to_value(def_settings_val).context(error::JsonConversionSnafu)?;
294302

295303
// The default settings were removed from the "settings" key of the
296304
// defaults table above. We still need them under a "settings" key
297305
// before serializing so we have full dotted keys like
298306
// "settings.foo.bar" and not just "foo.bar". We use a HashMap
299307
// to rebuild the nested structure.
300-
let def_settings = to_pairs_with_prefix("settings", &def_settings_table).context(
308+
let def_settings = to_pairs_with_prefix("settings", &settings_json).context(
301309
error::SerializationSnafu {
302310
given: "default settings",
303311
},
@@ -385,7 +393,8 @@ fn populate_default_datastore<P: AsRef<Path>>(
385393
// If any other defaults remain (configuration files, services, etc),
386394
// write them to the datastore in Live state
387395
debug!("Serializing other defaults and writing new ones to datastore");
388-
let defaults = to_pairs(&defaults_val).context(error::SerializationSnafu {
396+
let defaults_json = serde_json::to_value(defaults_val).context(error::JsonConversionSnafu)?;
397+
let defaults = to_pairs(&defaults_json).context(error::SerializationSnafu {
389398
given: "other defaults",
390399
})?;
391400

0 commit comments

Comments
 (0)