blob: dff22f8aa3c2b22b4a0f17a24d6b3738680dfce9 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![deny(missing_docs)]
//! Utility methods and traits used throughout assembly.
mod fast_copy;
mod files;
mod insert_unique;
mod named_map;
mod paths;
pub use fast_copy::fast_copy;
pub use files::{
BlobfsCompiledPackageDestination, BootfsCompiledPackageDestination, BootfsDestination,
BootfsPackageDestination, CompiledPackageDestination, Destination, FileEntry,
PackageDestination, PackageSetDestination, TestCompiledPackageDestination,
};
pub use insert_unique::{
BTreeMapDuplicateKeyError, DuplicateKeyError, InsertAllUniqueExt, InsertUniqueExt, MapEntry,
};
pub use named_map::{Key as NamedMapKey, NamedMap};
pub use paths::{PathTypeMarker, TypedPathBuf};
use anyhow::{Context as _, Result};
use serde::Serialize;
use std::fs;
use std::fs::File;
use std::io::Read;
use std::path::Path;
/// Read a config file (or really any JSON/JSON5 file) into a instance of type
/// T, with a useful error context if it fails.
pub fn read_config<T>(path: impl AsRef<Path>) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let mut file = File::open(path.as_ref())
.context(format!("Unable to open file: {}", path.as_ref().display()))?;
from_reader(&mut file).context(format!("Unable to read file: {}", path.as_ref().display()))
}
/// Serializes the given object to a JSON file.
pub fn write_json_file<T: ?Sized>(json_path: impl AsRef<Path>, value: &T) -> Result<()>
where
T: Serialize,
{
let json_path = json_path.as_ref();
if let Some(parent) = json_path.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("cannot create {}", parent.display()))?;
}
let file = File::create(json_path)
.with_context(|| format!("cannot create {}", json_path.display()))?;
serde_json::to_writer_pretty(&file, &value)
.with_context(|| format!("cannot serialize {}", json_path.display()))
}
/// Deserialize an instance of type T from an IO stream of JSON or JSON5
pub fn from_reader<R, T>(reader: &mut R) -> Result<T>
where
R: Read,
T: serde::de::DeserializeOwned,
{
let mut data = String::default();
reader.read_to_string(&mut data).context("Cannot read the config")?;
// First parse the json5 to a `serde_json::Value`, which handles the syntax
// differences between json5 and json.
let value: serde_json::Value =
serde_json5::from_str(&data).context("Cannot parse the json5 config")?;
// Dump the Value into a JSON string.
let json = serde_json::to_string_pretty(&value)?;
// Re-parse using serde_json, which will throw errors when encountering
// maps when deserializing unit-type enum variants (serde_json5 doesn't do
// this).
// TODO: Remove this series of transformations after the following issue
// is fixed: https://github.com/google/serde_json5/issues/10
serde_json::from_str(&json).context("cannot parse the config using serde_json")
}
/// Helper fn to insert into an empty Option, or return an Error.
pub fn set_option_once_or<T, E>(
opt: &mut Option<T>,
value: impl Into<Option<T>>,
e: E,
) -> Result<(), E> {
set_option_once_or_else(opt, value, || e)
}
/// Helper fn to insert into an empty Option, or return an Error created by a
/// closure.
pub fn set_option_once_or_else<T, E, F: FnOnce() -> E>(
opt: &mut Option<T>,
value: impl Into<Option<T>>,
f: F,
) -> Result<(), E> {
let value = value.into();
if value.is_none() {
Ok(())
} else {
if opt.is_some() {
Err(f())
} else {
*opt = value;
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::anyhow;
use serde::Deserialize;
use serde_json::json;
use std::io::Cursor;
use tempfile::TempDir;
#[derive(Debug, Deserialize, PartialEq)]
struct MyStruct {
key1: String,
}
#[test]
fn test_set_option_once() {
let mut opt = None;
// should be able to set None on None.
assert!(
set_option_once_or(&mut opt, None, anyhow!("an error")).is_ok(),
"Setting None on None failed"
);
// should be able to set Value on None.
assert!(
set_option_once_or(&mut opt, Some("some value"), anyhow!("an error")).is_ok(),
"initial set value failed"
);
assert_eq!(opt, Some("some value"));
// setting None on Some should be a no-op.
assert!(
set_option_once_or(&mut opt, None, anyhow!("an error")).is_ok(),
"Setting None on Some failed"
);
assert_eq!(opt, Some("some value"), "Setting None on Some was not a no-op");
// setting Some on Some should fail.
assert!(
set_option_once_or(&mut opt, "other value", anyhow!("an error")).is_err(),
"Setting Some on Some did not fail"
);
assert_eq!(
opt,
Some("some value"),
"Setting Some(other) on Some(value) changed the value with an error"
);
}
#[test]
fn reader_valid_json5() {
let json5: String = r#"{key1: "value1",}"#.to_string();
let mut cursor = Cursor::new(json5);
let value: MyStruct = from_reader(&mut cursor).unwrap();
assert_eq!(value.key1, "value1");
}
#[test]
fn reader_invalid_json5() {
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct MyStruct {}
let json5: String = r#"{key1: "value1",}"#.to_string();
let mut cursor = Cursor::new(json5);
let value: Result<MyStruct> = from_reader(&mut cursor);
assert!(value.is_err());
}
#[test]
fn test_read_config() {
let json = json!({
"key1": "value1",
});
let file = tempfile::NamedTempFile::new().unwrap();
serde_json::ser::to_writer(&file, &json).unwrap();
let value: MyStruct = read_config(file.path()).unwrap();
let expected: MyStruct = serde_json::from_value(json).unwrap();
assert_eq!(expected, value);
}
#[test]
fn test_write_json_file() {
let expected = json!({
"key1": "value1",
});
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().join("config.json");
write_json_file(&path, &expected).unwrap();
let actual: serde_json::Value = read_config(path).unwrap();
assert_eq!(expected, actual);
}
}