pact_plugin_driver/
utils.rs

1//! Utils for dealing with protobufs structs
2
3use 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
14/// Converts a map of key -> JSON to a prost Struct
15pub 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
21/// Converts a JSON value to a prost struct
22pub 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
43/// Converts a prost struct to JSON value
44pub 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
49/// Converts a prost value to JSON value
50pub 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) => json!(n),
55      Kind::StringValue(s) => Value::String(s.clone()),
56      Kind::BoolValue(b) => Value::Bool(*b),
57      Kind::StructValue(s) => proto_struct_to_json(s),
58      Kind::ListValue(l) => Value::Array(l.values.iter()
59        .map(|v| proto_value_to_json(v)).collect())
60    }
61    None => Value::Null
62  }
63}
64
65/// Converts a prost struct to a map of key -> JSON
66pub fn proto_struct_to_map(val: &prost_types::Struct) -> BTreeMap<String, Value> {
67  val.fields.iter()
68    .sorted_by(|(k1, _), (k2, _)| Ord::cmp(k1, k2))
69    .map(|(k, v)| (k.clone(), proto_value_to_json(v)))
70    .collect()
71}
72
73/// Converts a prost struct to a hash map of key -> JSON
74pub fn proto_struct_to_hashmap(val: &prost_types::Struct) -> HashMap<String, Value> {
75  val.fields.iter()
76    .map(|(k, v)| (k.clone(), proto_value_to_json(v)))
77    .collect()
78}
79
80/// Convert a proto struct into a String
81pub fn proto_value_to_string(val: &prost_types::Value) -> Option<String> {
82  match &val.kind {
83    Some(kind) => match kind {
84      Kind::NullValue(_) => None,
85      Kind::NumberValue(n) => Some(n.to_string()),
86      Kind::StringValue(s) => Some(s.clone()),
87      Kind::BoolValue(b) => Some(b.to_string()),
88      Kind::StructValue(s) => Some(proto_struct_to_json(s).to_string()),
89      Kind::ListValue(l) => Some(Value::Array(l.values.iter()
90        .map(|v| proto_value_to_json(v)).collect()).to_string())
91    }
92    None => None
93  }
94}
95
96/// Check if the versions are compatible (differ in patch version only)
97pub fn versions_compatible(version: &str, required: &Option<String>) -> bool {
98  match required {
99    None => true,
100    Some(required) => {
101      if required == version {
102        true
103      } else if let Ok(version) = Version::parse(version) {
104        if let Ok(req) = VersionReq::parse(format!(">={}", required).as_str()) {
105          req.matches(&version)
106        } else {
107          false
108        }
109      } else {
110        false
111      }
112    }
113  }
114}
115
116pub fn optional_string<S: Into<String>>(string: S) -> Option<String> {
117  let string = string.into();
118  if string.is_empty() {
119    None
120  } else {
121    Some(string)
122  }
123}
124
125/// Returns the current running OS and architecture
126pub fn os_and_arch() -> anyhow::Result<(&'static str, &'static str)> {
127  let os_info = os_info::get();
128  debug!("Detected OS: {}", os_info);
129
130  let os = match os_info.os_type() {
131    Type::Alpine | Type::Amazon| Type::Android| Type::Arch| Type::CentOS| Type::Debian |
132    Type::EndeavourOS | Type::Fedora | Type::Gentoo | Type::Linux | Type::Manjaro | Type::Mariner |
133    Type::Mint | Type::NixOS | Type::openSUSE | Type::OracleLinux | Type::Redhat |
134    Type::RedHatEnterprise | Type::Pop | Type::Raspbian | Type::Solus | Type::SUSE |
135    Type::Ubuntu => "linux",
136    Type::Macos => "osx",
137    Type::Windows => "windows",
138    _ => bail!("{} is not a supported operating system", os_info)
139  };
140
141  Ok((os, std::env::consts::ARCH))
142}
143
144#[cfg(test)]
145mod tests {
146  use expectest::prelude::*;
147
148  use super::versions_compatible;
149
150  #[test]
151  fn versions_compatible_test() {
152    expect!(versions_compatible("1.0.0", &None)).to(be_true());
153    expect!(versions_compatible("1.0.0", &Some("1.0.0".to_string()))).to(be_true());
154    expect!(versions_compatible("1.0.0", &Some("1.0.1".to_string()))).to(be_false());
155    expect!(versions_compatible("1.0.4", &Some("1.0.3".to_string()))).to(be_true());
156    expect!(versions_compatible("1.1.0", &Some("1.0.3".to_string()))).to(be_true());
157    expect!(versions_compatible("2.0.1", &Some("1.1.0".to_string()))).to(be_true());
158    expect!(versions_compatible("1.0.1", &Some("2.1.0".to_string()))).to(be_false());
159    expect!(versions_compatible("0.1.0", &Some("0.0.3".to_string()))).to(be_true());
160  }
161}