1#![forbid(unsafe_code)]
2#![allow(clippy::redundant_field_names)]
3#![doc = include_str!("../README.md")]
4
5mod validation;
6
7use validation::RawVersionInfo;
8
9use serde::{Deserialize, Serialize};
10
11use std::str::FromStr;
12
13#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
35#[serde(try_from = "RawVersionInfo")]
36#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
37pub struct VersionInfo {
38 pub packages: Vec<Package>,
39}
40
41#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
43#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
44pub struct Package {
45 pub name: String,
47 #[cfg_attr(feature = "schema", schemars(with = "String"))]
49 pub version: semver::Version,
50 pub source: Source,
52 #[serde(default)]
55 #[serde(skip_serializing_if = "is_default")]
56 pub kind: DependencyKind,
57 #[serde(default)]
61 #[serde(skip_serializing_if = "is_default")]
62 pub dependencies: Vec<usize>,
63 #[serde(default)]
67 #[serde(skip_serializing_if = "is_default")]
68 pub root: bool,
69}
70
71#[non_exhaustive]
73#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
74#[serde(from = "&str")]
75#[serde(into = "String")]
76#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
77pub enum Source {
78 CratesIo,
79 Git,
80 Local,
81 Registry,
82 Other(String),
83}
84
85impl From<&str> for Source {
86 fn from(s: &str) -> Self {
87 match s {
88 "crates.io" => Self::CratesIo,
89 "git" => Self::Git,
90 "local" => Self::Local,
91 "registry" => Self::Registry,
92 other_str => Self::Other(other_str.to_string()),
93 }
94 }
95}
96
97impl From<Source> for String {
98 fn from(s: Source) -> String {
99 match s {
100 Source::CratesIo => "crates.io".to_owned(),
101 Source::Git => "git".to_owned(),
102 Source::Local => "local".to_owned(),
103 Source::Registry => "registry".to_owned(),
104 Source::Other(string) => string,
105 }
106 }
107}
108
109#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Default)]
110#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
111pub enum DependencyKind {
112 #[serde(rename = "build")]
114 Build,
115 #[default]
116 #[serde(rename = "runtime")]
117 Runtime,
118}
119
120fn is_default<T: Default + PartialEq>(value: &T) -> bool {
121 let default_value = T::default();
122 value == &default_value
123}
124
125impl FromStr for VersionInfo {
126 type Err = serde_json::Error;
127 fn from_str(s: &str) -> Result<Self, Self::Err> {
128 serde_json::from_str(s)
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 #![allow(unused_imports)] use super::*;
136 use std::fs;
137 use std::{
138 convert::TryInto,
139 path::{Path, PathBuf},
140 };
141
142 #[cfg(feature = "schema")]
143 fn generate_schema() -> schemars::schema::RootSchema {
145 let mut schema = schemars::schema_for!(VersionInfo);
146 let mut metadata = *schema.schema.metadata.clone().unwrap();
147
148 let title = "cargo-auditable schema".to_string();
149 metadata.title = Some(title);
150 metadata.id = Some("https://rustsec.org/schemas/cargo-auditable.json".to_string());
151 metadata.examples = [].to_vec();
152 metadata.description = Some(
153 "Describes the `VersionInfo` JSON data structure that cargo-auditable embeds into Rust binaries."
154 .to_string(),
155 );
156 schema.schema.metadata = Some(Box::new(metadata));
157 schema
158 }
159
160 #[test]
161 #[cfg(feature = "schema")]
162 fn verify_schema() {
163 use schemars::schema::RootSchema;
164
165 let expected = generate_schema();
166 println!(
168 "expected schema:\n{}",
169 serde_json::to_string_pretty(&expected).unwrap()
170 );
171
172 let contents = fs::read_to_string(
173 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
175 .parent()
176 .unwrap()
177 .join("cargo-auditable.schema.json"),
178 )
179 .expect("error reading existing schema");
180 let actual: RootSchema =
181 serde_json::from_str(&contents).expect("error deserializing existing schema");
182
183 assert_eq!(expected, actual);
184 }
185}