diff --git a/Cargo.lock b/Cargo.lock index 974a818..9335ef3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -273,9 +273,9 @@ checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "schemars" -version = "0.8.12" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" dependencies = [ "dyn-clone", "schemars_derive", @@ -285,9 +285,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.12" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" dependencies = [ "proc-macro2", "quote", diff --git a/auditable-serde/src/compact_enum_variant.rs b/auditable-serde/src/compact_enum_variant.rs deleted file mode 100644 index 4d00250..0000000 --- a/auditable-serde/src/compact_enum_variant.rs +++ /dev/null @@ -1,181 +0,0 @@ -//! A new enum representation for serde built on generics. Using it requires some extra attributes and impls. -//! -//! This new representation of enum variants makes it much simpler to -//! deserialize variant from a string and fill in the missing fields using e.g. -//! [`Default::default`] or to deserialize from a struct which is tagged and -//! allows overrifing the default values. -//! -//! It is built on the `VariantRepr` type. It is not recommended to use this -//! type directly. Instead for a selected type that appears within a "newtype" -//! variant of an enum (a variant which wraps a single type) certain traits -//! should be implemented. -//! -//! The traits one should implement before using this module are -//! - `IsEnumVariant<&str, ENUM>` for `VARIANT`, -//! - `Into>` for `VARIANT`, -//! - `TryFrom>` for `VARIANT`, -//! where __`ENUM`__ is the __enum type__ containing the variant which -//! serialization we would like to change and __`VARIANT`__ is the type -//! __wrapped by the variant__. -//! -//! Once those are implemented and the module in which this struct resides is -//! used in serde's attribute as follows: -//! ```rust,ignore -//! #[derive(Serialize, Deserialize, JsonSchema)] -//! #[serde(untagged)] -//! pub enum Source { -//! /// `Source` is the __ENUM__ and `GitSource` is the __VARIANT__ type -//! #[serde(with = "compact_enum_variant")] -//! #[schemars(schema_with = "compact_enum_variant::schema::")] -//! Git(GitSource), -//! } -//! ``` -//! -//! Changing a unit variant of an enum to wrap a type and use this module for -//! the serialization can be made to be a backwards compatible change. - -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::{convert::TryFrom, fmt::Display, marker::PhantomData}; - -/// Marks a string or other type that can be converted to a string as a label -/// for an variant of type `ENUM`. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] -#[serde(transparent)] -#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -#[cfg_attr(feature = "schema", schemars(bound = "S: schemars::JsonSchema"))] -pub struct EnumVariant, ENUM>(S, PhantomData ENUM>); - -/// Establishes a relation with the implementing type and an enum `ENUM`. -pub trait IsEnumVariant, ENUM> { - /// Returns a label identifying the type as belonging to one of possible - /// types stored in the enum `ENUM`. - fn variant() -> EnumVariant; -} - -impl, E> EnumVariant { - pub fn new(variant_tag: S) -> Self { - Self(variant_tag, PhantomData) - } -} - -impl, E> From> for String { - fn from(value: EnumVariant) -> Self { - value.0.into() - } -} - -impl From<&'static str> for EnumVariant<&'static str, E> { - fn from(value: &'static str) -> Self { - EnumVariant::new(value) - } -} -impl From<&str> for EnumVariant { - fn from(value: &str) -> Self { - EnumVariant::new(value.to_owned()) - } -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone)] -#[serde( - untagged, - bound( - serialize = "INNER: Serialize, S: 'static + Serialize", - deserialize = "INNER: Deserialize<'de>, S: Deserialize<'de>" - ) -)] -#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -pub enum VariantRepr, ENUM, INNER> { - // Short stringly-typed representation of the enum variant - a label - - // which assumes all fields are set to their defualts. - Kind(EnumVariant), - // Longer representation that describes changes to default contents. - Struct { - kind: EnumVariant, - #[serde(flatten)] - strct: INNER, - }, -} - -pub fn serialize(inner: &VARIANT, serializer: S) -> Result -where - S: Serializer, - VARIANT: ToOwned + IsEnumVariant<&'static str, ENUM> + Serialize, - ::Owned: Into>, -{ - let compact: VariantRepr<&'static str, ENUM, VARIANT> = inner.to_owned().into(); - - Serialize::serialize(&compact, serializer) -} - -pub fn deserialize<'de, 's, D, ENUM, VARIANT>(deserializer: D) -> Result -where - D: Deserializer<'de>, - VARIANT: IsEnumVariant<&'s str, ENUM> - + TryFrom> - + Deserialize<'de>, - >>::Error: Display, - 'de: 's, -{ - let compact: VariantRepr<&'s str, ENUM, VARIANT> = Deserialize::deserialize(deserializer)?; - let variant = VARIANT::try_from(compact).map_err(serde::de::Error::custom)?; - - Ok(variant) -} - -/// Enriches the schema generated for `VariantRepr` with const values adequate -/// to the selected variant of an enum. -#[cfg(feature = "schema")] -pub fn schema< - 'a, - ENUM: schemars::JsonSchema, - VARIANT: Into> - + IsEnumVariant<&'a str, ENUM> - + schemars::JsonSchema, ->( - gen: &mut schemars::gen::SchemaGenerator, -) -> schemars::schema::Schema { - use schemars::JsonSchema; - - let mut schema = - as JsonSchema>::json_schema(gen).into_object(); - - schema - .subschemas - .as_mut() - .and_then(|subschemas| subschemas.any_of.as_mut()) - .map(|subschemas| { - let new_subschemas = subschemas.iter_mut().map(|schema| { - let mut schema = schema.clone().into_object(); - let typ = &schema - .instance_type - .as_ref() - .and_then(|instance_type| match instance_type { - schemars::schema::SingleOrVec::Single(typ) => Some(**typ), - schemars::schema::SingleOrVec::Vec(_) => None, - }) - .unwrap(); - match typ { - schemars::schema::InstanceType::Object => { - let object_schema = schema.object(); - let kind_property = object_schema.properties.get_mut("kind").unwrap(); - let mut kind_property_object = kind_property.clone().into_object(); - kind_property_object.const_value = Some(serde_json::Value::String(VARIANT::variant().into())); - *kind_property = schemars::schema::Schema::Object(kind_property_object); - - schemars::schema::Schema::Object(schema) - }, - schemars::schema::InstanceType::String => { - schema.const_value = Some(serde_json::Value::String(VARIANT::variant().into())); - schema.string = None; - - schemars::schema::Schema::Object(schema) - }, - _ => panic!("the schema using compact enum variant representation should allow only string or object instances"), - } - }).collect(); - *subschemas = new_subschemas; - subschemas - }); - - schemars::schema::Schema::Object(schema) -} diff --git a/auditable-serde/src/lib.rs b/auditable-serde/src/lib.rs index a99b05b..dfc30a2 100644 --- a/auditable-serde/src/lib.rs +++ b/auditable-serde/src/lib.rs @@ -45,23 +45,22 @@ //! } //! ``` -mod compact_enum_variant; mod validation; -use compact_enum_variant::{EnumVariant, IsEnumVariant, VariantRepr}; use validation::RawVersionInfo; use serde::{Deserialize, Serialize}; +#[cfg(feature = "toml")] +use cargo_lock; #[cfg(any(feature = "from_metadata", feature = "toml"))] use std::convert::TryFrom; #[cfg(feature = "toml")] use std::convert::TryInto; +use std::str::FromStr; #[cfg(feature = "from_metadata")] #[cfg(feature = "from_metadata")] -use std::{ - cmp::min, cmp::Ordering::*, collections::HashMap, error::Error, fmt::Display, str::FromStr, -}; +use std::{cmp::min, cmp::Ordering::*, collections::HashMap, error::Error, fmt::Display}; /// Dependency tree embedded in the binary. /// @@ -113,7 +112,7 @@ pub struct Package { /// The package's version in the [semantic version](https://semver.org) format. #[cfg_attr(feature = "schema", schemars(with = "String"))] pub version: semver::Version, - /// The description of package's source. + /// Currently "git", "local", "crates.io" or "registry". Designed to be extensible with other revision control systems, etc. pub source: Source, /// "build" or "runtime". May be omitted if set to "runtime". /// If it's both a build and a runtime dependency, "runtime" is recorded. @@ -134,36 +133,17 @@ pub struct Package { pub root: bool, } -/// Serializes to "git", "local", "crates.io", "registry" or a more complex -/// struct with any of those values in the `kind` field. Designed to be -/// extensible with other revision control systems, etc. -// -// The abundance of schemars attributes was introduced to fix an unexpected -// way of representing untagged enums that is inconsistent with serde. Without -// extra `with` attributes the generated schema assigns null types to instances -// of the enum's variants which are unit types instead of using string type. +/// Serializes to "git", "local", "crates.io" or "registry". Designed to be extensible with other revision control systems, etc. #[non_exhaustive] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -#[serde(rename_all = "snake_case", untagged)] +#[serde(from = "&str")] +#[serde(into = "String")] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub enum Source { - /// "crates.io" - #[serde(rename = "crates.io")] - #[cfg_attr(feature = "schema", schemars(with = "String"))] CratesIo, - /// "local" - #[cfg_attr(feature = "schema", schemars(with = "String"))] + Git, Local, - /// "registry" - #[cfg_attr(feature = "schema", schemars(with = "String"))] Registry, - #[serde(with = "compact_enum_variant")] - #[cfg_attr( - feature = "schema", - schemars(schema_with = "compact_enum_variant::schema::",) - )] - Git(GitSource), - /// Any other source Other(String), } @@ -171,7 +151,7 @@ impl From<&str> for Source { fn from(s: &str) -> Self { match s { "crates.io" => Self::CratesIo, - "git" => Self::Git(GitSource::default()), + "git" => Self::Git, "local" => Self::Local, "registry" => Self::Registry, other_str => Self::Other(other_str.to_string()), @@ -180,11 +160,10 @@ impl From<&str> for Source { } impl From for String { - /// Provides a lossy conversion for variants with values different than the default fn from(s: Source) -> String { match s { Source::CratesIo => "crates.io".to_owned(), - Source::Git(_) => "git".to_owned(), + Source::Git => "git".to_owned(), Source::Local => "local".to_owned(), Source::Registry => "registry".to_owned(), Source::Other(string) => string, @@ -192,99 +171,17 @@ impl From for String { } } -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Default)] -#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -pub struct GitSource { - /// Commit hash pointing to specific revision - #[serde(skip_serializing_if = "is_default", default)] - pub rev: Option, -} - -impl IsEnumVariant<&str, Source> for GitSource { - fn variant() -> EnumVariant<&'static str, Source> { - EnumVariant::new("git") - } -} - -impl From for VariantRepr<&'static str, Source, GitSource> { - fn from(value: GitSource) -> Self { - if is_default(&value) { - VariantRepr::Kind(GitSource::variant()) - } else { - VariantRepr::Struct { - kind: GitSource::variant(), - strct: value, - } - } - } -} - -impl TryFrom> for GitSource { - type Error = &'static str; - - fn try_from(value: VariantRepr<&str, Source, GitSource>) -> Result { - use compact_enum_variant::*; - - match value { - VariantRepr::Kind(kind) => { - if kind != Self::variant() { - Err("cannot construct expected variant from provided value") - } else { - Ok(Self::default()) - } - } - VariantRepr::Struct { kind, strct } => { - if kind != Self::variant() { - Err("cannot construct expected variant from provided value") - } else { - Ok(strct) - } - } - } - } -} - #[cfg(feature = "from_metadata")] impl From<&cargo_metadata::Source> for Source { fn from(meta_source: &cargo_metadata::Source) -> Self { match meta_source.repr.as_str() { "registry+https://github.com/rust-lang/crates.io-index" => Source::CratesIo, - source => { - let mut source_components = source.split('+'); - let starts_with = source_components + source => Source::from( + source + .split('+') .next() - .expect("Encoding of source strings in `cargo metadata` has changed!"); - - match starts_with { - "git" => { - let url = source_components.next().expect( - "Encoding of git source strings in `cargo metadata` has changed!", - ); - - if let Some(url_params) = url.split('?').nth(1) { - let mut git = GitSource::default(); - - url_params.split('&').for_each(|kv| { - if let Some((key, value)) = kv.split_once('=') { - if key == "rev" { - let mut value = value.to_owned(); - if let Some(idx) = value.find('#') { - value.truncate(idx); - } - - git.rev = Some(value.to_owned()); - } - } - }); - - Source::Git(git) - } else { - Source::Git(GitSource::default()) - } - } - _ => Source::from(starts_with), - } - } + .expect("Encoding of source strings in `cargo metadata` has changed!"), + ), } } } @@ -595,46 +492,6 @@ mod tests { use std::fs; use std::{convert::TryInto, path::PathBuf}; - #[test] - fn deserialize_source_with_detailed_git_source() { - let package_source_str = r#"{ "kind": "git", "rev": "abc" }"#; - let package_source: Source = - serde_json::from_str(package_source_str).expect("deserialization failure"); - match package_source { - Source::Git(git) => { - assert!(git.rev.unwrap() == "abc") - } - _ => panic!("expected git variant"), - } - } - - #[test] - #[should_panic] - fn fail_deserializing_invalid_git_source_variant() { - let package_source_str = r#"{ "kind": "abc", "rev": "abc" }"#; - serde_json::from_str(package_source_str).expect("deserialization failure") - } - - #[test] - fn deserialize_source_with_simple_git_source() { - let package_source_str = r#""git""#; - let package_source: Source = serde_json::from_str(package_source_str).unwrap(); - assert!(package_source == Source::Git(GitSource::default())); - } - - #[test] - fn allow_any_other_unkown_sources_as_source_variant() { - let package_source_str = r#""unknown""#; - let package_source: Source = - serde_json::from_str(package_source_str).expect("deserialization failure"); - match package_source { - Source::Other(unknown) => { - assert!(unknown == "unknown") - } - _ => panic!("expected Other(unknown) variant"), - } - } - #[cfg(feature = "toml")] #[cfg(feature = "from_metadata")] fn load_own_metadata() -> cargo_metadata::Metadata { diff --git a/cargo-auditable.schema.json b/cargo-auditable.schema.json index 27f84ed..9fbddfd 100644 --- a/cargo-auditable.schema.json +++ b/cargo-auditable.schema.json @@ -1,123 +1,101 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://rustsec.org/schemas/cargo-auditable.json", - "title": "cargo-auditable schema", - "description": "Describes the `VersionInfo` JSON data structure that cargo-auditable embeds into Rust binaries.", - "type": "object", - "required": [ - "packages" - ], - "properties": { - "packages": { - "type": "array", - "items": { - "$ref": "#/definitions/Package" - } - } - }, - "definitions": { - "DependencyKind": { - "type": "string", - "enum": [ - "build", - "runtime" - ] - }, - "Package": { - "description": "A single package in the dependency tree", - "type": "object", - "required": [ - "name", - "source", - "version" - ], - "properties": { - "dependencies": { - "description": "Packages are stored in an ordered array both in the `VersionInfo` struct and in JSON. Here we refer to each package by its index in the array. May be omitted if the list is empty.", - "type": "array", - "items": { - "type": "integer", - "format": "uint", - "minimum": 0.0 - } - }, - "kind": { - "description": "\"build\" or \"runtime\". May be omitted if set to \"runtime\". If it's both a build and a runtime dependency, \"runtime\" is recorded.", - "allOf": [ - { - "$ref": "#/definitions/DependencyKind" - } - ] - }, - "name": { - "description": "Crate name specified in the `name` field in Cargo.toml file. Examples: \"libc\", \"rand\"", - "type": "string" - }, - "root": { - "description": "Whether this is the root package in the dependency tree. There should only be one root package. May be omitted if set to `false`.", - "type": "boolean" - }, - "source": { - "description": "The description of package's source.", - "allOf": [ - { - "$ref": "#/definitions/Source" + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://rustsec.org/schemas/cargo-auditable.json", + "title": "cargo-auditable schema", + "description": "Describes the `VersionInfo` JSON data structure that cargo-auditable embeds into Rust binaries.", + "type": "object", + "required": [ + "packages" + ], + "properties": { + "packages": { + "type": "array", + "items": { + "$ref": "#/definitions/Package" } - ] - }, - "version": { - "description": "The package's version in the [semantic version](https://semver.org) format.", - "type": "string" } - } }, - "Source": { - "description": "Serializes to \"git\", \"local\", \"crates.io\", \"registry\" or a more complex struct with any of those values in the `kind` field. Designed to be extensible with other revision control systems, etc.", - "anyOf": [ - { - "description": "\"crates.io\"", - "type": "string" + "definitions": { + "DependencyKind": { + "type": "string", + "enum": [ + "build", + "runtime" + ] }, - { - "description": "\"local\"", - "type": "string" - }, - { - "description": "\"registry\"", - "type": "string" - }, - { - "anyOf": [ - { - "type": "string", - "const": "git" - }, - { - "type": "object", - "required": [ - "kind" - ], - "properties": { + "Package": { + "description": "A single package in the dependency tree", + "type": "object", + "required": [ + "name", + "source", + "version" + ], + "properties": { + "dependencies": { + "description": "Packages are stored in an ordered array both in the `VersionInfo` struct and in JSON. Here we refer to each package by its index in the array. May be omitted if the list is empty.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + }, "kind": { - "type": "string", - "const": "git" + "description": "\"build\" or \"runtime\". May be omitted if set to \"runtime\". If it's both a build and a runtime dependency, \"runtime\" is recorded.", + "allOf": [ + { + "$ref": "#/definitions/DependencyKind" + } + ] + }, + "name": { + "description": "Crate name specified in the `name` field in Cargo.toml file. Examples: \"libc\", \"rand\"", + "type": "string" }, - "rev": { - "description": "Commit hash pointing to specific revision", - "type": [ - "string", - "null" - ] + "root": { + "description": "Whether this is the root package in the dependency tree. There should only be one root package. May be omitted if set to `false`.", + "type": "boolean" + }, + "source": { + "description": "Currently \"git\", \"local\", \"crates.io\" or \"registry\". Designed to be extensible with other revision control systems, etc.", + "allOf": [ + { + "$ref": "#/definitions/Source" + } + ] + }, + "version": { + "description": "The package's version in the [semantic version](https://semver.org) format.", + "type": "string" } - } } - ] }, - { - "description": "Any other source", - "type": "string" + "Source": { + "description": "Serializes to \"git\", \"local\", \"crates.io\" or \"registry\". Designed to be extensible with other revision control systems, etc.", + "oneOf": [ + { + "type": "string", + "enum": [ + "CratesIo", + "Git", + "Local", + "Registry" + ] + }, + { + "type": "object", + "required": [ + "Other" + ], + "properties": { + "Other": { + "type": "string" + } + }, + "additionalProperties": false + } + ] } - ] } - } -} \ No newline at end of file +} diff --git a/cargo-auditable/src/collect_audit_data.rs b/cargo-auditable/src/collect_audit_data.rs index f0316c5..9c55b1b 100644 --- a/cargo-auditable/src/collect_audit_data.rs +++ b/cargo-auditable/src/collect_audit_data.rs @@ -5,7 +5,7 @@ use std::{convert::TryFrom, str::from_utf8}; use crate::{cargo_arguments::CargoArgs, rustc_arguments::RustcArgs}; -/// Calls `cargo metadata` to obtain the dependency tree, serializes it to JSON and compresses it. +/// Calls `cargo metadata` to obtain the dependency tree, serializes it to JSON and compresses it pub fn compressed_dependency_list(rustc_args: &RustcArgs, target_triple: &str) -> Vec { let metadata = get_metadata(rustc_args, target_triple); let version_info = VersionInfo::try_from(&metadata).unwrap(); diff --git a/cargo-auditable/tests/fixtures/git_source_of_dep/Cargo.toml b/cargo-auditable/tests/fixtures/git_source_of_dep/Cargo.toml deleted file mode 100644 index 8c6b9fa..0000000 --- a/cargo-auditable/tests/fixtures/git_source_of_dep/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "git_source_of_dep" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[workspace] - -[dependencies] -serde = { package = "serde", git = "https://github.com/serde-rs/serde", rev = "2ba406726f9f84bc3b65ce4e824ae636dfa7dc85" } diff --git a/cargo-auditable/tests/fixtures/git_source_of_dep/src/main.rs b/cargo-auditable/tests/fixtures/git_source_of_dep/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/cargo-auditable/tests/fixtures/git_source_of_dep/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/cargo-auditable/tests/it.rs b/cargo-auditable/tests/it.rs index f3ec822..24e58e2 100644 --- a/cargo-auditable/tests/it.rs +++ b/cargo-auditable/tests/it.rs @@ -7,7 +7,7 @@ use std::{ process::{Command, Output, Stdio}, }; -use auditable_serde::{DependencyKind, Source, VersionInfo}; +use auditable_serde::{DependencyKind, VersionInfo}; use cargo_metadata::{ camino::{Utf8Path, Utf8PathBuf}, Artifact, @@ -403,33 +403,3 @@ fn test_workspace_member_version_info() { let status = command.status().unwrap(); assert!(status.success()); } - -#[test] -fn test_git_source_of_dep() { - // Path to workspace fixture Cargo.toml. See that file for overview of workspace members and their dependencies. - let workspace_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/git_source_of_dep/Cargo.toml"); - // Run in workspace root with default features - let bins = run_cargo_auditable(workspace_cargo_toml, &["--release"], &[]); - eprintln!("Test fixture binary map: {bins:?}"); - - // lto_binary_crate should only depend on itself - let git_source_of_dep = &bins.get("git_source_of_dep").unwrap()[0]; - let dep_info = get_dependency_info(git_source_of_dep); - eprintln!("{git_source_of_dep} dependency info: {dep_info:?}"); - assert!(dep_info - .packages - .iter() - .any(|p| p.name == "git_source_of_dep")); - assert!(dep_info.packages.iter().any(|p| p.name == "serde" - && match &p.source { - Source::Git(git) => { - if git.rev == Some(String::from("2ba406726f9f84bc3b65ce4e824ae636dfa7dc85")) { - true - } else { - false - } - } - _ => false, - })); -}