blob: 22fcb884b1e7e7cc48016a126060abcbf21deea3 [file] [log] [blame]
Mårten Kongstadbb520722023-04-26 13:16:41 +02001/*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use anyhow::{anyhow, Context, Error, Result};
18
Mårten Kongstad09c28d12023-05-04 13:29:26 +020019use crate::protos::{
20 ProtoAndroidConfig, ProtoFlag, ProtoOverride, ProtoOverrideConfig, ProtoValue,
21};
22
23#[derive(Debug, PartialEq, Eq)]
24pub struct Value {
25 value: bool,
26 since: Option<u32>,
27}
28
29#[allow(dead_code)] // only used in unit tests
30impl Value {
31 pub fn new(value: bool, since: u32) -> Value {
32 Value { value, since: Some(since) }
33 }
34
35 pub fn default(value: bool) -> Value {
36 Value { value, since: None }
37 }
38}
39
40impl TryFrom<ProtoValue> for Value {
41 type Error = Error;
42
43 fn try_from(proto: ProtoValue) -> Result<Self, Self::Error> {
44 let Some(value) = proto.value else {
45 return Err(anyhow!("missing 'value' field"));
46 };
47 Ok(Value { value, since: proto.since })
48 }
49}
Mårten Kongstadbb520722023-04-26 13:16:41 +020050
51#[derive(Debug, PartialEq, Eq)]
52pub struct Flag {
53 pub id: String,
54 pub description: String,
Mårten Kongstad09c28d12023-05-04 13:29:26 +020055
56 // ordered by Value.since; guaranteed to contain at least one item (the default value, with
57 // since == None)
58 pub values: Vec<Value>,
Mårten Kongstadbb520722023-04-26 13:16:41 +020059}
60
61impl Flag {
Mårten Kongstad4d2b4b02023-04-27 16:05:58 +020062 #[allow(dead_code)] // only used in unit tests
Mårten Kongstadbb520722023-04-26 13:16:41 +020063 pub fn try_from_text_proto(text_proto: &str) -> Result<Flag> {
64 let proto: ProtoFlag = crate::protos::try_from_text_proto(text_proto)
65 .with_context(|| text_proto.to_owned())?;
66 proto.try_into()
67 }
68
69 pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<Flag>> {
70 let proto: ProtoAndroidConfig = crate::protos::try_from_text_proto(text_proto)
71 .with_context(|| text_proto.to_owned())?;
72 proto.flag.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
73 }
Mårten Kongstad09c28d12023-05-04 13:29:26 +020074
75 pub fn resolve_value(&self, build_id: u32) -> bool {
76 let mut value = self.values[0].value;
77 for candidate in self.values.iter().skip(1) {
78 let since = candidate.since.expect("invariant: non-defaults values have Some(since)");
79 if since <= build_id {
80 value = candidate.value;
81 }
82 }
83 value
84 }
Mårten Kongstadbb520722023-04-26 13:16:41 +020085}
86
87impl TryFrom<ProtoFlag> for Flag {
88 type Error = Error;
89
90 fn try_from(proto: ProtoFlag) -> Result<Self, Self::Error> {
91 let Some(id) = proto.id else {
92 return Err(anyhow!("missing 'id' field"));
93 };
94 let Some(description) = proto.description else {
95 return Err(anyhow!("missing 'description' field"));
96 };
Mårten Kongstad09c28d12023-05-04 13:29:26 +020097 if proto.value.is_empty() {
Mårten Kongstadbb520722023-04-26 13:16:41 +020098 return Err(anyhow!("missing 'value' field"));
Mårten Kongstad09c28d12023-05-04 13:29:26 +020099 }
100
101 let mut values: Vec<Value> = vec![];
102 for proto_value in proto.value.into_iter() {
103 let v: Value = proto_value.try_into()?;
104 if values.iter().any(|w| v.since == w.since) {
105 let msg = match v.since {
106 None => format!("flag {}: multiple default values", id),
107 Some(x) => format!("flag {}: multiple values for since={}", id, x),
108 };
109 return Err(anyhow!(msg));
110 }
111 values.push(v);
112 }
113 values.sort_by_key(|v| v.since);
114
115 Ok(Flag { id, description, values })
Mårten Kongstadbb520722023-04-26 13:16:41 +0200116 }
117}
118
119#[derive(Debug, PartialEq, Eq)]
120pub struct Override {
121 pub id: String,
122 pub value: bool,
123}
124
125impl Override {
Mårten Kongstad4d2b4b02023-04-27 16:05:58 +0200126 #[allow(dead_code)] // only used in unit tests
Mårten Kongstadbb520722023-04-26 13:16:41 +0200127 pub fn try_from_text_proto(text_proto: &str) -> Result<Override> {
128 let proto: ProtoOverride = crate::protos::try_from_text_proto(text_proto)?;
129 proto.try_into()
130 }
131
132 pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<Override>> {
133 let proto: ProtoOverrideConfig = crate::protos::try_from_text_proto(text_proto)?;
134 proto.override_.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
135 }
136}
137
138impl TryFrom<ProtoOverride> for Override {
139 type Error = Error;
140
141 fn try_from(proto: ProtoOverride) -> Result<Self, Self::Error> {
142 let Some(id) = proto.id else {
143 return Err(anyhow!("missing 'id' field"));
144 };
145 let Some(value) = proto.value else {
146 return Err(anyhow!("missing 'value' field"));
147 };
148 Ok(Override { id, value })
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_flag_try_from_text_proto() {
158 let expected = Flag {
159 id: "1234".to_owned(),
160 description: "Description of the flag".to_owned(),
Mårten Kongstad09c28d12023-05-04 13:29:26 +0200161 values: vec![Value::default(false), Value::new(true, 8)],
Mårten Kongstadbb520722023-04-26 13:16:41 +0200162 };
163
164 let s = r#"
165 id: "1234"
166 description: "Description of the flag"
Mårten Kongstad09c28d12023-05-04 13:29:26 +0200167 value {
168 value: false
169 }
170 value {
171 value: true
172 since: 8
173 }
Mårten Kongstadbb520722023-04-26 13:16:41 +0200174 "#;
175 let actual = Flag::try_from_text_proto(s).unwrap();
176
177 assert_eq!(expected, actual);
178 }
179
180 #[test]
Mårten Kongstad09c28d12023-05-04 13:29:26 +0200181 fn test_flag_try_from_text_proto_bad_input() {
182 let s = r#"
183 id: "a"
184 description: "Description of the flag"
185 "#;
186 let error = Flag::try_from_text_proto(s).unwrap_err();
187 assert_eq!(format!("{:?}", error), "missing 'value' field");
188
Mårten Kongstadbb520722023-04-26 13:16:41 +0200189 let s = r#"
190 description: "Description of the flag"
Mårten Kongstad09c28d12023-05-04 13:29:26 +0200191 value {
192 value: true
193 }
Mårten Kongstadbb520722023-04-26 13:16:41 +0200194 "#;
195 let error = Flag::try_from_text_proto(s).unwrap_err();
196 assert!(format!("{:?}", error).contains("Message not initialized"));
Mårten Kongstad09c28d12023-05-04 13:29:26 +0200197
198 let s = r#"
199 id: "a"
200 description: "Description of the flag"
201 value {
202 value: true
203 }
204 value {
205 value: true
206 }
207 "#;
208 let error = Flag::try_from_text_proto(s).unwrap_err();
209 assert_eq!(format!("{:?}", error), "flag a: multiple default values");
Mårten Kongstadbb520722023-04-26 13:16:41 +0200210 }
211
212 #[test]
213 fn test_flag_try_from_text_proto_list() {
214 let expected = vec![
Mårten Kongstad09c28d12023-05-04 13:29:26 +0200215 Flag {
216 id: "a".to_owned(),
217 description: "A".to_owned(),
218 values: vec![Value::default(true)],
219 },
220 Flag {
221 id: "b".to_owned(),
222 description: "B".to_owned(),
223 values: vec![Value::default(false)],
224 },
Mårten Kongstadbb520722023-04-26 13:16:41 +0200225 ];
226
227 let s = r#"
228 flag {
229 id: "a"
230 description: "A"
Mårten Kongstad09c28d12023-05-04 13:29:26 +0200231 value {
232 value: true
233 }
Mårten Kongstadbb520722023-04-26 13:16:41 +0200234 }
235 flag {
236 id: "b"
237 description: "B"
Mårten Kongstad09c28d12023-05-04 13:29:26 +0200238 value {
239 value: false
240 }
Mårten Kongstadbb520722023-04-26 13:16:41 +0200241 }
242 "#;
243 let actual = Flag::try_from_text_proto_list(s).unwrap();
244
245 assert_eq!(expected, actual);
246 }
247
248 #[test]
249 fn test_override_try_from_text_proto_list() {
250 let expected = Override { id: "1234".to_owned(), value: true };
251
252 let s = r#"
253 id: "1234"
254 value: true
255 "#;
256 let actual = Override::try_from_text_proto(s).unwrap();
257
258 assert_eq!(expected, actual);
259 }
Mårten Kongstad09c28d12023-05-04 13:29:26 +0200260
261 #[test]
262 fn test_resolve_value() {
263 let flag = Flag {
264 id: "a".to_owned(),
265 description: "A".to_owned(),
266 values: vec![
267 Value::default(true),
268 Value::new(false, 10),
269 Value::new(true, 20),
270 Value::new(false, 30),
271 ],
272 };
273 assert!(flag.resolve_value(0));
274 assert!(flag.resolve_value(9));
275 assert!(!flag.resolve_value(10));
276 assert!(!flag.resolve_value(11));
277 assert!(!flag.resolve_value(19));
278 assert!(flag.resolve_value(20));
279 assert!(flag.resolve_value(21));
280 assert!(flag.resolve_value(29));
281 assert!(!flag.resolve_value(30));
282 assert!(!flag.resolve_value(10_000));
283 }
Mårten Kongstadbb520722023-04-26 13:16:41 +0200284}