Skip to main content

provenant/models/
output.rs

1use super::{FileInfo, Match, Package, TopLevelDependency};
2use serde::{Deserialize, Serialize};
3
4pub const OUTPUT_FORMAT_VERSION: &str = "4.0.0";
5
6#[derive(Serialize, Deserialize, Debug)]
7/// Top-level ScanCode-compatible JSON payload.
8pub struct Output {
9    #[serde(skip_serializing_if = "Option::is_none")]
10    pub summary: Option<Summary>,
11    #[serde(skip_serializing_if = "Option::is_none")]
12    pub tallies: Option<Tallies>,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub tallies_of_key_files: Option<Tallies>,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub tallies_by_facet: Option<Vec<FacetTallies>>,
17    pub headers: Vec<Header>,
18    pub packages: Vec<Package>,
19    pub dependencies: Vec<TopLevelDependency>,
20    #[serde(default, skip_serializing_if = "Vec::is_empty")]
21    pub license_detections: Vec<TopLevelLicenseDetection>,
22    pub files: Vec<FileInfo>,
23    pub license_references: Vec<LicenseReference>,
24    pub license_rule_references: Vec<LicenseRuleReference>,
25}
26
27#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
28pub struct TopLevelLicenseDetection {
29    pub identifier: String,
30    pub license_expression: String,
31    pub license_expression_spdx: String,
32    pub detection_count: usize,
33    #[serde(skip_serializing_if = "Vec::is_empty", default)]
34    pub detection_log: Vec<String>,
35    pub reference_matches: Vec<Match>,
36}
37
38#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
39pub struct Summary {
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub declared_license_expression: Option<String>,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub license_clarity_score: Option<LicenseClarityScore>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub declared_holder: Option<String>,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub primary_language: Option<String>,
48    #[serde(default, skip_serializing_if = "Vec::is_empty")]
49    pub other_license_expressions: Vec<TallyEntry>,
50    #[serde(default, skip_serializing_if = "Vec::is_empty")]
51    pub other_holders: Vec<TallyEntry>,
52    #[serde(default, skip_serializing_if = "Vec::is_empty")]
53    pub other_languages: Vec<TallyEntry>,
54}
55
56#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
57pub struct LicenseClarityScore {
58    pub score: usize,
59    pub declared_license: bool,
60    pub identification_precision: bool,
61    pub has_license_text: bool,
62    pub declared_copyrights: bool,
63    pub conflicting_license_categories: bool,
64    pub ambiguous_compound_licensing: bool,
65}
66
67#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
68pub struct TallyEntry {
69    pub value: Option<String>,
70    pub count: usize,
71}
72
73#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
74pub struct Tallies {
75    #[serde(default, skip_serializing_if = "Vec::is_empty")]
76    pub detected_license_expression: Vec<TallyEntry>,
77    #[serde(default, skip_serializing_if = "Vec::is_empty")]
78    pub copyrights: Vec<TallyEntry>,
79    #[serde(default, skip_serializing_if = "Vec::is_empty")]
80    pub holders: Vec<TallyEntry>,
81    #[serde(default, skip_serializing_if = "Vec::is_empty")]
82    pub authors: Vec<TallyEntry>,
83    #[serde(default, skip_serializing_if = "Vec::is_empty")]
84    pub programming_language: Vec<TallyEntry>,
85}
86
87impl Tallies {
88    pub fn is_empty(&self) -> bool {
89        self.detected_license_expression.is_empty()
90            && self.copyrights.is_empty()
91            && self.holders.is_empty()
92            && self.authors.is_empty()
93            && self.programming_language.is_empty()
94    }
95}
96
97#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
98pub struct FacetTallies {
99    pub facet: String,
100    pub tallies: Tallies,
101}
102
103#[derive(Serialize, Deserialize, Debug)]
104/// Scan execution metadata stored in `output.headers`.
105pub struct Header {
106    pub start_timestamp: String,
107    pub end_timestamp: String,
108    pub duration: f64,
109    pub extra_data: ExtraData,
110    pub errors: Vec<String>,
111    pub output_format_version: String,
112}
113
114#[derive(Serialize, Deserialize, Debug)]
115/// Additional counters and environment details for a scan run.
116pub struct ExtraData {
117    pub files_count: usize,
118    pub directories_count: usize,
119    pub excluded_count: usize,
120    pub system_environment: SystemEnvironment,
121}
122
123#[derive(Serialize, Deserialize, Debug)]
124/// Host environment information captured during scan execution.
125pub struct SystemEnvironment {
126    pub operating_system: Option<String>,
127    pub cpu_architecture: String,
128    pub platform: String,
129    pub rust_version: String,
130}
131
132#[derive(Serialize, Deserialize, Debug)]
133/// Reference entry for a detected license.
134pub struct LicenseReference {
135    #[serde(default, skip_serializing_if = "Option::is_none")]
136    pub key: Option<String>,
137    #[serde(default, skip_serializing_if = "Option::is_none")]
138    pub language: Option<String>,
139    pub name: String,
140    pub short_name: String,
141    #[serde(default, skip_serializing_if = "Option::is_none")]
142    pub owner: Option<String>,
143    #[serde(default, skip_serializing_if = "Option::is_none")]
144    pub homepage_url: Option<String>,
145    pub spdx_license_key: String,
146    #[serde(default, skip_serializing_if = "Vec::is_empty")]
147    pub other_spdx_license_keys: Vec<String>,
148    #[serde(default, skip_serializing_if = "Option::is_none")]
149    pub osi_license_key: Option<String>,
150    #[serde(default, skip_serializing_if = "Vec::is_empty")]
151    pub text_urls: Vec<String>,
152    #[serde(default, skip_serializing_if = "Option::is_none")]
153    pub osi_url: Option<String>,
154    #[serde(default, skip_serializing_if = "Option::is_none")]
155    pub faq_url: Option<String>,
156    #[serde(default, skip_serializing_if = "Vec::is_empty")]
157    pub other_urls: Vec<String>,
158    #[serde(default, skip_serializing_if = "Option::is_none")]
159    pub category: Option<String>,
160    #[serde(default)]
161    pub is_exception: bool,
162    #[serde(default)]
163    pub is_unknown: bool,
164    #[serde(default)]
165    pub is_generic: bool,
166    #[serde(default, skip_serializing_if = "Option::is_none")]
167    pub notes: Option<String>,
168    #[serde(default, skip_serializing_if = "Option::is_none")]
169    pub minimum_coverage: Option<u8>,
170    #[serde(default, skip_serializing_if = "Option::is_none")]
171    pub standard_notice: Option<String>,
172    #[serde(default, skip_serializing_if = "Vec::is_empty")]
173    pub ignorable_copyrights: Vec<String>,
174    #[serde(default, skip_serializing_if = "Vec::is_empty")]
175    pub ignorable_holders: Vec<String>,
176    #[serde(default, skip_serializing_if = "Vec::is_empty")]
177    pub ignorable_authors: Vec<String>,
178    #[serde(default, skip_serializing_if = "Vec::is_empty")]
179    pub ignorable_urls: Vec<String>,
180    #[serde(default, skip_serializing_if = "Vec::is_empty")]
181    pub ignorable_emails: Vec<String>,
182    #[serde(default, skip_serializing_if = "Option::is_none")]
183    pub scancode_url: Option<String>,
184    #[serde(default, skip_serializing_if = "Option::is_none")]
185    pub licensedb_url: Option<String>,
186    #[serde(default, skip_serializing_if = "Option::is_none")]
187    pub spdx_url: Option<String>,
188    pub text: String,
189}
190
191#[derive(Serialize, Deserialize, Debug)]
192/// Reference metadata for a license detection rule.
193pub struct LicenseRuleReference {
194    pub identifier: String,
195    pub license_expression: String,
196    pub is_license_text: bool,
197    pub is_license_notice: bool,
198    pub is_license_reference: bool,
199    pub is_license_tag: bool,
200    pub is_license_clue: bool,
201    pub is_license_intro: bool,
202    #[serde(default, skip_serializing_if = "Option::is_none")]
203    pub language: Option<String>,
204    #[serde(default, skip_serializing_if = "Option::is_none")]
205    pub rule_url: Option<String>,
206    #[serde(default)]
207    pub is_required_phrase: bool,
208    #[serde(default)]
209    pub skip_for_required_phrase_generation: bool,
210    #[serde(default)]
211    pub is_continuous: bool,
212    #[serde(default)]
213    pub is_synthetic: bool,
214    #[serde(default)]
215    pub is_from_license: bool,
216    #[serde(default)]
217    pub length: usize,
218    #[serde(default, skip_serializing_if = "Option::is_none")]
219    pub relevance: Option<u8>,
220    #[serde(default, skip_serializing_if = "Option::is_none")]
221    pub minimum_coverage: Option<u8>,
222    #[serde(default, skip_serializing_if = "Vec::is_empty")]
223    pub referenced_filenames: Vec<String>,
224    #[serde(default, skip_serializing_if = "Option::is_none")]
225    pub notes: Option<String>,
226    #[serde(default, skip_serializing_if = "Vec::is_empty")]
227    pub ignorable_copyrights: Vec<String>,
228    #[serde(default, skip_serializing_if = "Vec::is_empty")]
229    pub ignorable_holders: Vec<String>,
230    #[serde(default, skip_serializing_if = "Vec::is_empty")]
231    pub ignorable_authors: Vec<String>,
232    #[serde(default, skip_serializing_if = "Vec::is_empty")]
233    pub ignorable_urls: Vec<String>,
234    #[serde(default, skip_serializing_if = "Vec::is_empty")]
235    pub ignorable_emails: Vec<String>,
236    #[serde(default, skip_serializing_if = "Option::is_none")]
237    pub text: Option<String>,
238}