blob: 65a5f4e00135c11a3f0d6f262289ca90eaad9a3a [file] [log] [blame]
// Copyright 2019 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.
use crate::errors::MetaPackageError;
use crate::path::{check_package_name, check_package_variant};
use serde_derive::{Deserialize, Serialize};
use std::io;
/// A `MetaPackage` represents the "meta/package" file of a meta.far (which is
/// a Fuchsia archive file of a Fuchsia package).
/// It validates that the name and variant (called "version" in json) are valid.
#[derive(Debug, Eq, PartialEq)]
pub struct MetaPackage {
name: String,
variant: String,
}
impl MetaPackage {
/// Create a `MetaPackage` with `name` and `variant`.
pub fn from_name_and_variant(
name: impl Into<String>,
variant: impl Into<String>,
) -> Result<Self, MetaPackageError> {
let name = name.into();
let variant = variant.into();
check_package_name(&name)?;
check_package_variant(&variant)?;
Ok(MetaPackage { name, variant })
}
/// Deserializes a `MetaPackage` from json.
///
/// # Examples
/// ```
/// # use fuchsia_pkg::MetaPackage;
/// let json = r#"
/// {"name": "package-name",
/// "version": "package-variant"}"#;
/// assert_eq!(
/// MetaPackage::deserialize(json.as_bytes()).unwrap(),
/// MetaPackage::from_name_and_variant(
/// "package-name",
/// "package-variant").unwrap());
/// ```
pub fn deserialize(reader: impl io::Read) -> Result<Self, MetaPackageError> {
MetaPackage::from_v0(serde_json::from_reader(reader)?)
}
/// Serializes a `MetaPackage` to json.
///
/// # Examples
/// ```
/// # use fuchsia_pkg::MetaPackage;
/// let meta_package = MetaPackage::from_name_and_variant(
/// "package-name",
/// "package-variant").unwrap();
/// let mut v: Vec<u8> = vec![];
/// meta_package.serialize(&mut v).unwrap();
/// assert_eq!(std::str::from_utf8(v.as_slice()).unwrap(),
/// r#"{"name":"package-name","version":"package-variant"}"#);
/// ```
pub fn serialize(&self, writer: impl io::Write) -> Result<(), MetaPackageError> {
serde_json::to_writer(
writer,
&MetaPackageV0Serialize { name: &self.name, variant: &self.variant },
)?;
Ok(())
}
fn from_v0(v0: MetaPackageV0Deserialize) -> Result<MetaPackage, MetaPackageError> {
MetaPackage::from_name_and_variant(v0.name, v0.variant)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
struct MetaPackageV0Deserialize {
name: String,
#[serde(rename = "version")]
variant: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
struct MetaPackageV0Serialize<'a> {
name: &'a str,
#[serde(rename = "version")]
variant: &'a str,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::{PackageNameError, PackageVariantError};
use crate::test::{random_meta_package, random_package_name, random_package_variant};
use lazy_static::lazy_static;
use proptest::prelude::*;
use regex::Regex;
#[test]
fn test_reject_invalid_name() {
let invalid_name = "name-with-question-mark?".to_string();
assert_matches!(
MetaPackage::from_name_and_variant(invalid_name.clone(), "valid-variant"),
Err(MetaPackageError::PackageName(PackageNameError::InvalidCharacter{invalid_name: name})) => assert_eq!(invalid_name, name)
)
}
#[test]
fn test_reject_invalid_variant() {
let invalid_variant = "variant-with-question-mark?".to_string();
assert_matches!(
MetaPackage::from_name_and_variant(
"valid-name",
invalid_variant.clone()),
Err(MetaPackageError::PackageVariant(
PackageVariantError::InvalidCharacter { invalid_variant: variant } ))
=> assert_eq!(invalid_variant, variant)
)
}
#[test]
fn test_from_name_and_variant() {
let name = "package-name";
let variant = "package-variant";
assert_eq!(
MetaPackage::from_name_and_variant(name, variant).unwrap(),
MetaPackage { name: name.to_string(), variant: variant.to_string() }
);
}
#[test]
fn test_serialize() {
let meta_package =
MetaPackage::from_name_and_variant("package-name", "package-variant").unwrap();
let mut v: Vec<u8> = Vec::new();
meta_package.serialize(&mut v).unwrap();
let expected = r#"{"name":"package-name","version":"package-variant"}"#;
assert_eq!(v.as_slice(), expected.as_bytes());
}
#[test]
fn test_deserialize() {
let json_bytes = r#"{"name":"package-name","version":"package-variant"}"#.as_bytes();
assert_eq!(
MetaPackage::deserialize(json_bytes).unwrap(),
MetaPackage {
name: "package-name".to_string(),
variant: "package-variant".to_string()
}
);
}
proptest! {
#[test]
fn test_serialize_deserialize_is_identity(
ref meta_package in random_meta_package()
) {
let mut v: Vec<u8> = Vec::new();
meta_package.serialize(&mut v).unwrap();
let meta_package_round_trip = MetaPackage::deserialize(v.as_slice()).unwrap();
assert_eq!(meta_package, &meta_package_round_trip);
}
#[test]
fn test_serialized_contains_no_whitespace(
ref meta_package in random_meta_package()
) {
lazy_static! {
static ref RE: Regex = Regex::new(r"(\p{White_Space})").unwrap();
}
let mut v: Vec<u8> = Vec::new();
meta_package.serialize(&mut v).unwrap();
assert!(!RE.is_match(std::str::from_utf8(v.as_slice()).unwrap()));
}
#[test]
fn test_from_name_and_variant_no_error_on_valid_inputs(
name in random_package_name(),
variant in random_package_variant()
) {
assert!(MetaPackage::from_name_and_variant(name, variant).is_ok());
}
}
}