pact_models/
provider_states.rs1use std::cmp::Eq;
5use std::collections::HashMap;
6use std::hash::{Hash, Hasher};
7
8use maplit::*;
9use serde::{Deserialize, Serialize};
10use serde_json::*;
11use tracing::warn;
12
13use crate::PactSpecification;
14use crate::verify_json::{json_type_of, PactFileVerificationResult, PactJsonVerifier, ResultLevel};
15
16#[derive(Serialize, Deserialize, Debug, Clone, Eq)]
18pub struct ProviderState {
19 pub name: String,
21 pub params: HashMap<String, Value>
23}
24
25impl ProviderState {
26
27 pub fn default<T: Into<String>>(name: T) -> ProviderState {
29 ProviderState {
30 name: name.into(),
31 params: hashmap!{}
32 }
33 }
34
35 pub fn from_json_v3(pact_json: &Value) -> ProviderState {
37 let state = match pact_json.get("name") {
38 Some(v) => match *v {
39 Value::String(ref s) => s.clone(),
40 _ => v.to_string()
41 },
42 None => {
43 warn!("Provider state does not have a 'name' field");
44 "unknown provider states".to_string()
45 }
46 };
47 let params = match pact_json.get("params") {
48 Some(v) => match *v {
49 Value::Object(ref map) => map.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
50 _ => {
51 warn!("Provider state parameters must be a map");
52 hashmap!{}
53 }
54 },
55 None => hashmap!{}
56 };
57 ProviderState{
58 name: state,
59 params
60 }
61 }
62
63 pub fn from_json(pact_json: &Value) -> Vec<ProviderState> {
65 match pact_json.get("providerStates") {
66 Some(v) => match *v {
67 Value::Array(ref a) => a.iter().map(|i| ProviderState::from_json_v3(i)).collect(),
68 _ => vec![]
69 },
70 None => match pact_json.get("providerState").or_else(|| pact_json.get("provider_state")) {
71 Some(v) => match *v {
72 Value::String(ref s) => if s.is_empty() {
73 vec![]
74 } else {
75 vec![ProviderState{ name: s.clone(), params: hashmap!{} }]
76 },
77 Value::Null => vec![],
78 _ => vec![ProviderState{ name: v.to_string(), params: hashmap!{} }]
79 },
80 None => vec![]
81 }
82 }
83 }
84
85 pub fn to_json(&self) -> Value {
87 let mut value = json!({
88 "name": Value::String(self.name.clone())
89 });
90 if !self.params.is_empty() {
91 let map = value.as_object_mut().unwrap();
92 map.insert("params".into(), Value::Object(
93 self.params.iter().map(|(k, v)| (k.clone(), v.clone())).collect()));
94 }
95 value
96 }
97
98}
99
100impl Hash for ProviderState {
101 fn hash<H: Hasher>(&self, state: &mut H) {
102 self.name.hash(state);
103 for (k, v) in self.params.clone() {
104 k.hash(state);
105 match v {
106 Value::Number(n) => if n.is_u64() {
107 n.as_u64().unwrap().hash(state)
108 } else if n.is_i64() {
109 n.as_i64().unwrap().hash(state)
110 } else if n.is_f64() {
111 n.as_f64().unwrap().to_string().hash(state)
112 },
113 Value::String(s) => s.hash(state),
114 Value::Bool(b) => b.hash(state),
115 _ => ()
116 }
117 }
118 }
119}
120
121impl PartialEq for ProviderState {
122 fn eq(&self, other: &Self) -> bool {
123 self.name == other.name && self.params == other.params
124 }
125}
126
127impl PactJsonVerifier for ProviderState {
128 fn verify_json(path: &str, pact_json: &Value, strict: bool, _spec_version: PactSpecification) -> Vec<PactFileVerificationResult> {
129 let mut results = vec![];
130
131 match pact_json {
132 Value::String(_) => {}
133 Value::Object(values) => {
134 match values.get("name") {
135 None => results.push(PactFileVerificationResult::new(path, ResultLevel::ERROR,
136 "Provider state 'name' is required")),
137 Some(name) => if !name.is_string() {
138 results.push(PactFileVerificationResult::new(path, ResultLevel::ERROR,
139 format!("Provider state 'name' must be a String, got {}", json_type_of(pact_json))))
140 }
141 }
142
143 if let Some(params) = values.get("params") {
144 if !params.is_object() {
145 results.push(PactFileVerificationResult::new(path, ResultLevel::ERROR,
146 format!("Provider state 'params' must be an Object, got {}", json_type_of(pact_json))))
147 }
148 }
149
150 let valid_attr = hashset! { "name", "params" };
151 for key in values.keys() {
152 if !valid_attr.contains(key.as_str()) {
153 results.push(PactFileVerificationResult::new(path.to_owned(),
154 if strict { ResultLevel::ERROR } else { ResultLevel::WARNING }, format!("Unknown attribute '{}'", key)))
155 }
156 }
157 }
158 _ => results.push(PactFileVerificationResult::new(path, ResultLevel::ERROR,
159 format!("Must be a String or Object, got {}", json_type_of(pact_json))))
160 }
161
162 results
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use expectest::expect;
169 use expectest::prelude::*;
170 use serde_json;
171 use serde_json::Value;
172
173 use super::*;
174
175 #[test]
176 fn defaults_to_v3_pact_provider_states() {
177 let json = r#"{
178 "providerStates": [
179 {
180 "name": "test state",
181 "params": { "name": "Testy" }
182 },
183 {
184 "name": "test state 2",
185 "params": { "name": "Testy2" }
186 }
187 ],
188 "description" : "test interaction"
189 }"#;
190 let provider_states = ProviderState::from_json(&serde_json::from_str(json).unwrap());
191 expect!(provider_states.iter()).to(have_count(2));
192 expect!(&provider_states[0]).to(be_equal_to(&ProviderState {
193 name: "test state".into(),
194 params: hashmap!{ "name".to_string() => Value::String("Testy".into()) }
195 }));
196 expect!(&provider_states[1]).to(be_equal_to(&ProviderState {
197 name: "test state 2".into(),
198 params: hashmap!{ "name".to_string() => Value::String("Testy2".into()) }
199 }));
200 }
201
202 #[test]
203 fn falls_back_to_v2_pact_provider_state() {
204 let json = r#"{
205 "providerState": "test state",
206 "description" : "test interaction"
207 }"#;
208 let provider_states = ProviderState::from_json(&serde_json::from_str(json).unwrap());
209 expect!(provider_states.iter()).to(have_count(1));
210 expect!(&provider_states[0]).to(be_equal_to(&ProviderState {
211 name: "test state".to_string(),
212 params: hashmap!{}
213 }));
214 }
215
216 #[test]
217 fn pact_with_no_provider_states() {
218 let json = r#"{
219 "description" : "test interaction"
220 }"#;
221 let provider_states = ProviderState::from_json(&serde_json::from_str(json).unwrap());
222 expect!(provider_states.iter()).to(be_empty());
223 }
224}