pact_plugin_driver/
utils.rs1use std::collections::{BTreeMap, HashMap};
4use anyhow::bail;
5use itertools::Itertools;
6use os_info::Type;
7
8use prost_types::{ListValue, Struct};
9use prost_types::value::Kind;
10use semver::{Version, VersionReq};
11use serde_json::{json, Value};
12use tracing::debug;
13
14pub fn to_proto_struct(values: &HashMap<String, Value>) -> Struct {
16 Struct {
17 fields: values.iter().map(|(k, v)| (k.clone(), to_proto_value(v))).collect()
18 }
19}
20
21pub fn to_proto_value(val: &Value) -> prost_types::Value {
23 match val {
24 Value::Null => prost_types::Value { kind: Some(prost_types::value::Kind::NullValue(0)) },
25 Value::Bool(b) => prost_types::Value { kind: Some(prost_types::value::Kind::BoolValue(*b)) },
26 Value::Number(n) => if let Some(n) = n.as_u64() {
27 prost_types::Value { kind: Some(prost_types::value::Kind::NumberValue(n as f64)) }
28 } else if let Some(n) = n.as_i64() {
29 prost_types::Value { kind: Some(prost_types::value::Kind::NumberValue(n as f64)) }
30 } else {
31 prost_types::Value { kind: Some(prost_types::value::Kind::NumberValue(n.as_f64().unwrap_or_default())) }
32 }
33 Value::String(s) => prost_types::Value { kind: Some(prost_types::value::Kind::StringValue(s.clone())) },
34 Value::Array(a) => prost_types::Value { kind: Some(prost_types::value::Kind::ListValue(ListValue {
35 values: a.iter().map(|v| to_proto_value(v)).collect()
36 }))},
37 Value::Object(o) => prost_types::Value { kind: Some(prost_types::value::Kind::StructValue(Struct {
38 fields: o.iter().map(|(k, v)| (k.clone(), to_proto_value(v))).collect()
39 }))}
40 }
41}
42
43pub fn proto_struct_to_json(val: &prost_types::Struct) -> Value {
45 Value::Object(val.fields.iter()
46 .map(|(k, v)| (k.clone(), proto_value_to_json(v))).collect())
47}
48
49pub fn proto_value_to_json(val: &prost_types::Value) -> Value {
51 match &val.kind {
52 Some(kind) => match kind {
53 Kind::NullValue(_) => Value::Null,
54 Kind::NumberValue(n) => {
55 if n.fract() == 0.0 && *n >= i64::MIN as f64 && *n <= i64::MAX as f64 {
56 json!(*n as i64)
57 } else {
58 json!(n)
59 }
60 }
61 Kind::StringValue(s) => Value::String(s.clone()),
62 Kind::BoolValue(b) => Value::Bool(*b),
63 Kind::StructValue(s) => proto_struct_to_json(s),
64 Kind::ListValue(l) => Value::Array(l.values.iter()
65 .map(|v| proto_value_to_json(v)).collect())
66 }
67 None => Value::Null
68 }
69}
70
71pub fn proto_struct_to_map(val: &prost_types::Struct) -> BTreeMap<String, Value> {
73 val.fields.iter()
74 .sorted_by(|(k1, _), (k2, _)| Ord::cmp(k1, k2))
75 .map(|(k, v)| (k.clone(), proto_value_to_json(v)))
76 .collect()
77}
78
79pub fn proto_struct_to_hashmap(val: &prost_types::Struct) -> HashMap<String, Value> {
81 val.fields.iter()
82 .map(|(k, v)| (k.clone(), proto_value_to_json(v)))
83 .collect()
84}
85
86pub fn proto_value_to_string(val: &prost_types::Value) -> Option<String> {
88 match &val.kind {
89 Some(kind) => match kind {
90 Kind::NullValue(_) => None,
91 Kind::NumberValue(n) => Some(n.to_string()),
92 Kind::StringValue(s) => Some(s.clone()),
93 Kind::BoolValue(b) => Some(b.to_string()),
94 Kind::StructValue(s) => Some(proto_struct_to_json(s).to_string()),
95 Kind::ListValue(l) => Some(Value::Array(l.values.iter()
96 .map(|v| proto_value_to_json(v)).collect()).to_string())
97 }
98 None => None
99 }
100}
101
102pub fn versions_compatible(version: &str, required: &Option<String>) -> bool {
104 match required {
105 None => true,
106 Some(required) => {
107 if required == version {
108 true
109 } else if let Ok(version) = lenient_semver::parse(version) {
110 if let Ok(required) = lenient_semver::parse(required) {
111 version >= drop_pre_release(required)
112 } else {
113 if let Ok(req) = VersionReq::parse(format!(">={}", required).as_str()) {
114 req.matches(&version)
115 } else {
116 false
117 }
118 }
119 } else {
120 false
121 }
122 }
123 }
124}
125
126fn drop_pre_release(version: Version) -> Version {
127 Version::new(version.major, version.minor, version.patch)
128}
129
130pub fn optional_string<S: Into<String>>(string: S) -> Option<String> {
131 let string = string.into();
132 if string.is_empty() {
133 None
134 } else {
135 Some(string)
136 }
137}
138
139pub fn os_and_arch() -> anyhow::Result<(&'static str, &'static str)> {
141 let os_info = os_info::get();
142 debug!("Detected OS: {}", os_info);
143
144 let os = match os_info.os_type() {
145 Type::Alpine | Type::Amazon| Type::Android| Type::Arch| Type::CentOS| Type::Debian |
146 Type::EndeavourOS | Type::Fedora | Type::Gentoo | Type::Linux | Type::Manjaro | Type::Mariner |
147 Type::Mint | Type::NixOS | Type::openSUSE | Type::OracleLinux | Type::Redhat |
148 Type::RedHatEnterprise | Type::Pop | Type::Raspbian | Type::Solus | Type::SUSE |
149 Type::Ubuntu => "linux",
150 Type::Macos => "osx",
151 Type::Windows => "windows",
152 _ => bail!("{} is not a supported operating system", os_info)
153 };
154
155 Ok((os, std::env::consts::ARCH))
156}
157
158#[cfg(test)]
159mod tests {
160 use expectest::prelude::*;
161 use serde_json::json;
162
163 use super::{proto_value_to_json, versions_compatible};
164
165 #[test]
166 fn proto_value_to_json_preserves_integers() {
167 let make_num = |n: f64| prost_types::Value {
168 kind: Some(prost_types::value::Kind::NumberValue(n)),
169 };
170
171 assert_eq!(proto_value_to_json(&make_num(2.0)), json!(2));
173 assert_eq!(proto_value_to_json(&make_num(0.0)), json!(0));
174 assert_eq!(proto_value_to_json(&make_num(-42.0)), json!(-42));
175
176 assert_eq!(proto_value_to_json(&make_num(2.5)), json!(2.5));
178 assert_eq!(proto_value_to_json(&make_num(-0.1)), json!(-0.1));
179 }
180
181 #[test]
182 fn versions_compatible_test() {
183 expect!(versions_compatible("1.0.0", &None)).to(be_true());
184 expect!(versions_compatible("1.0.0", &Some("1.0.0".to_string()))).to(be_true());
185 expect!(versions_compatible("1.0.0", &Some("1.0.1".to_string()))).to(be_false());
186 expect!(versions_compatible("1.0.4", &Some("1.0.3".to_string()))).to(be_true());
187 expect!(versions_compatible("1.1.0", &Some("1.0.3".to_string()))).to(be_true());
188 expect!(versions_compatible("2.0.1", &Some("1.1.0".to_string()))).to(be_true());
189 expect!(versions_compatible("1.0.1", &Some("2.1.0".to_string()))).to(be_false());
190 expect!(versions_compatible("0.1.0", &Some("0.0.3".to_string()))).to(be_true());
191 }
192
193 #[test]
194 fn versions_compatible_accepts_future_dev_versions() {
195 expect!(versions_compatible("0.1", &Some("0.1.0-beta.1".to_string()))).to(be_true());
196 }
197}