blob: fb5dab4fe23772d907cc0ef632a0d9678a0f1bf8 [file] [log] [blame]
Mårten Kongstadfe753f52023-04-26 09:13:03 +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
17// When building with the Android tool-chain
18//
19// - an external crate `aconfig_protos` will be generated
20// - the feature "cargo" will be disabled
21//
22// When building with cargo
23//
24// - a local sub-module will be generated in OUT_DIR and included in this file
25// - the feature "cargo" will be enabled
26//
27// This module hides these differences from the rest of aconfig.
28
Mårten Kongstadbb520722023-04-26 13:16:41 +020029// ---- When building with the Android tool-chain ----
Mårten Kongstadfe753f52023-04-26 09:13:03 +020030#[cfg(not(feature = "cargo"))]
Mårten Kongstadf9422522023-06-14 08:38:46 +020031mod auto_generated {
32 pub use aconfig_protos::aconfig::Flag_declaration as ProtoFlagDeclaration;
33 pub use aconfig_protos::aconfig::Flag_declarations as ProtoFlagDeclarations;
34 pub use aconfig_protos::aconfig::Flag_permission as ProtoFlagPermission;
35 pub use aconfig_protos::aconfig::Flag_state as ProtoFlagState;
36 pub use aconfig_protos::aconfig::Flag_value as ProtoFlagValue;
37 pub use aconfig_protos::aconfig::Flag_values as ProtoFlagValues;
38 pub use aconfig_protos::aconfig::Parsed_flag as ProtoParsedFlag;
39 pub use aconfig_protos::aconfig::Parsed_flags as ProtoParsedFlags;
40 pub use aconfig_protos::aconfig::Tracepoint as ProtoTracepoint;
41}
Mårten Kongstada1029092023-05-08 11:51:59 +020042
Mårten Kongstadbb520722023-04-26 13:16:41 +020043// ---- When building with cargo ----
Mårten Kongstadfe753f52023-04-26 09:13:03 +020044#[cfg(feature = "cargo")]
Mårten Kongstadf9422522023-06-14 08:38:46 +020045mod auto_generated {
46 // include! statements should be avoided (because they import file contents verbatim), but
47 // because this is only used during local development, and only if using cargo instead of the
48 // Android tool-chain, we allow it
49 include!(concat!(env!("OUT_DIR"), "/aconfig_proto/mod.rs"));
50 pub use aconfig::Flag_declaration as ProtoFlagDeclaration;
51 pub use aconfig::Flag_declarations as ProtoFlagDeclarations;
52 pub use aconfig::Flag_permission as ProtoFlagPermission;
53 pub use aconfig::Flag_state as ProtoFlagState;
54 pub use aconfig::Flag_value as ProtoFlagValue;
55 pub use aconfig::Flag_values as ProtoFlagValues;
56 pub use aconfig::Parsed_flag as ProtoParsedFlag;
57 pub use aconfig::Parsed_flags as ProtoParsedFlags;
58 pub use aconfig::Tracepoint as ProtoTracepoint;
59}
Mårten Kongstada1029092023-05-08 11:51:59 +020060
Mårten Kongstadbb520722023-04-26 13:16:41 +020061// ---- Common for both the Android tool-chain and cargo ----
Mårten Kongstadf9422522023-06-14 08:38:46 +020062pub use auto_generated::*;
63
Mårten Kongstadbb520722023-04-26 13:16:41 +020064use anyhow::Result;
65
Mårten Kongstad403658f2023-06-14 09:51:56 +020066fn try_from_text_proto<T>(s: &str) -> Result<T>
Mårten Kongstadbb520722023-04-26 13:16:41 +020067where
68 T: protobuf::MessageFull,
69{
70 // warning: parse_from_str does not check if required fields are set
71 protobuf::text_format::parse_from_str(s).map_err(|e| e.into())
72}
Mårten Kongstad403658f2023-06-14 09:51:56 +020073
74pub mod flag_declaration {
75 use super::*;
76 use crate::codegen;
77 use anyhow::ensure;
78
79 pub fn verify_fields(pdf: &ProtoFlagDeclaration) -> Result<()> {
80 ensure!(codegen::is_valid_name_ident(pdf.name()), "bad flag declaration: bad name");
81 ensure!(codegen::is_valid_name_ident(pdf.namespace()), "bad flag declaration: bad name");
82 ensure!(!pdf.description().is_empty(), "bad flag declaration: empty description");
83 Ok(())
84 }
85}
86
87pub mod flag_declarations {
88 use super::*;
89 use crate::codegen;
90 use anyhow::ensure;
91
92 pub fn try_from_text_proto(s: &str) -> Result<ProtoFlagDeclarations> {
93 let pdf: ProtoFlagDeclarations = super::try_from_text_proto(s)?;
94 verify_fields(&pdf)?;
95 Ok(pdf)
96 }
97
98 pub fn verify_fields(pdf: &ProtoFlagDeclarations) -> Result<()> {
99 ensure!(
100 codegen::is_valid_package_ident(pdf.package()),
101 "bad flag declarations: bad package"
102 );
103 for flag_declaration in pdf.flag.iter() {
104 super::flag_declaration::verify_fields(flag_declaration)?;
105 }
106 Ok(())
107 }
108}
109
110pub mod flag_value {
111 use super::*;
112 use crate::codegen;
113 use anyhow::ensure;
114
115 pub fn verify_fields(fv: &ProtoFlagValue) -> Result<()> {
116 ensure!(codegen::is_valid_package_ident(fv.package()), "bad flag value: bad package");
117 ensure!(codegen::is_valid_name_ident(fv.name()), "bad flag value: bad name");
118 Ok(())
119 }
120}
121
122pub mod flag_values {
123 use super::*;
124
125 pub fn try_from_text_proto(s: &str) -> Result<ProtoFlagValues> {
126 let pfv: ProtoFlagValues = super::try_from_text_proto(s)?;
127 verify_fields(&pfv)?;
128 Ok(pfv)
129 }
130
131 pub fn verify_fields(pfv: &ProtoFlagValues) -> Result<()> {
132 for flag_value in pfv.flag_value.iter() {
133 super::flag_value::verify_fields(flag_value)?;
134 }
135 Ok(())
136 }
137}
138
139pub mod tracepoint {
140 use super::*;
141 use anyhow::ensure;
142
143 pub fn verify_fields(tp: &ProtoTracepoint) -> Result<()> {
144 ensure!(!tp.source().is_empty(), "bad tracepoint: empty source");
145 Ok(())
146 }
147}
148
149pub mod parsed_flag {
150 use super::*;
151 use crate::codegen;
152 use anyhow::ensure;
153
154 pub fn verify_fields(pf: &ProtoParsedFlag) -> Result<()> {
155 ensure!(codegen::is_valid_package_ident(pf.package()), "bad parsed flag: bad package");
156 ensure!(codegen::is_valid_name_ident(pf.name()), "bad parsed flag: bad name");
157 ensure!(codegen::is_valid_name_ident(pf.namespace()), "bad parsed flag: bad namespace");
158 ensure!(!pf.description().is_empty(), "bad parsed flag: empty description");
159 ensure!(!pf.trace.is_empty(), "bad parsed flag: empty trace");
160 for tp in pf.trace.iter() {
161 super::tracepoint::verify_fields(tp)?;
162 }
163 Ok(())
164 }
165}
166
167pub mod parsed_flags {
168 use super::*;
169 use anyhow::bail;
170 use std::cmp::Ordering;
171
172 pub fn try_from_binary_proto(bytes: &[u8]) -> Result<ProtoParsedFlags> {
173 let message: ProtoParsedFlags = protobuf::Message::parse_from_bytes(bytes)?;
174 verify_fields(&message)?;
175 Ok(message)
176 }
177
178 pub fn verify_fields(pf: &ProtoParsedFlags) -> Result<()> {
179 let mut previous: Option<&ProtoParsedFlag> = None;
180 for parsed_flag in pf.parsed_flag.iter() {
181 if let Some(prev) = previous {
182 let a = create_sorting_key(prev);
183 let b = create_sorting_key(parsed_flag);
184 match a.cmp(&b) {
185 Ordering::Less => {}
186 Ordering::Equal => bail!("bad parsed flags: duplicate flag {}", a),
187 Ordering::Greater => {
188 bail!("bad parsed flags: not sorted: {} comes before {}", a, b)
189 }
190 }
191 }
192 super::parsed_flag::verify_fields(parsed_flag)?;
193 previous = Some(parsed_flag);
194 }
195 Ok(())
196 }
197
198 pub fn merge(parsed_flags: Vec<ProtoParsedFlags>) -> Result<ProtoParsedFlags> {
199 let mut merged = ProtoParsedFlags::new();
200 for mut pfs in parsed_flags.into_iter() {
201 merged.parsed_flag.append(&mut pfs.parsed_flag);
202 }
203 merged.parsed_flag.sort_by_cached_key(create_sorting_key);
204 verify_fields(&merged)?;
205 Ok(merged)
206 }
207
208 fn create_sorting_key(pf: &ProtoParsedFlag) -> String {
209 format!("{}.{}", pf.package(), pf.name())
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_flag_declarations_try_from_text_proto() {
219 // valid input
220 let flag_declarations = flag_declarations::try_from_text_proto(
221 r#"
222package: "com.foo.bar"
223flag {
224 name: "first"
225 namespace: "first_ns"
226 description: "This is the description of the first flag."
227}
228flag {
229 name: "second"
230 namespace: "second_ns"
231 description: "This is the description of the second flag."
232}
233"#,
234 )
235 .unwrap();
236 assert_eq!(flag_declarations.package(), "com.foo.bar");
237 let first = flag_declarations.flag.iter().find(|pf| pf.name() == "first").unwrap();
238 assert_eq!(first.name(), "first");
239 assert_eq!(first.namespace(), "first_ns");
240 assert_eq!(first.description(), "This is the description of the first flag.");
241 let second = flag_declarations.flag.iter().find(|pf| pf.name() == "second").unwrap();
242 assert_eq!(second.name(), "second");
243 assert_eq!(second.namespace(), "second_ns");
244 assert_eq!(second.description(), "This is the description of the second flag.");
245
246 // bad input: missing package in flag declarations
247 let error = flag_declarations::try_from_text_proto(
248 r#"
249flag {
250 name: "first"
251 namespace: "first_ns"
252 description: "This is the description of the first flag."
253}
254flag {
255 name: "second"
256 namespace: "second_ns"
257 description: "This is the description of the second flag."
258}
259"#,
260 )
261 .unwrap_err();
262 assert!(format!("{:?}", error).contains("Message not initialized"));
263
264 // bad input: missing namespace in flag declaration
265 let error = flag_declarations::try_from_text_proto(
266 r#"
267package: "com.foo.bar"
268flag {
269 name: "first"
270 description: "This is the description of the first flag."
271}
272flag {
273 name: "second"
274 namespace: "second_ns"
275 description: "This is the description of the second flag."
276}
277"#,
278 )
279 .unwrap_err();
280 assert!(format!("{:?}", error).contains("Message not initialized"));
281
282 // bad input: bad package name in flag declarations
283 let error = flag_declarations::try_from_text_proto(
284 r#"
285package: "_com.FOO__BAR"
286flag {
287 name: "first"
288 namespace: "first_ns"
289 description: "This is the description of the first flag."
290}
291flag {
292 name: "second"
293 namespace: "second_ns"
294 description: "This is the description of the second flag."
295}
296"#,
297 )
298 .unwrap_err();
299 assert!(format!("{:?}", error).contains("bad flag declarations: bad package"));
300
301 // bad input: bad name in flag declaration
302 let error = flag_declarations::try_from_text_proto(
303 r#"
304package: "com.foo.bar"
305flag {
306 name: "FIRST"
307 namespace: "first_ns"
308 description: "This is the description of the first flag."
309}
310flag {
311 name: "second"
312 namespace: "second_ns"
313 description: "This is the description of the second flag."
314}
315"#,
316 )
317 .unwrap_err();
318 assert!(format!("{:?}", error).contains("bad flag declaration: bad name"));
319 }
320
321 #[test]
322 fn test_flag_values_try_from_text_proto() {
323 // valid input
324 let flag_values = flag_values::try_from_text_proto(
325 r#"
326flag_value {
327 package: "com.first"
328 name: "first"
329 state: DISABLED
330 permission: READ_ONLY
331}
332flag_value {
333 package: "com.second"
334 name: "second"
335 state: ENABLED
336 permission: READ_WRITE
337}
338"#,
339 )
340 .unwrap();
341 let first = flag_values.flag_value.iter().find(|fv| fv.name() == "first").unwrap();
342 assert_eq!(first.package(), "com.first");
343 assert_eq!(first.name(), "first");
344 assert_eq!(first.state(), ProtoFlagState::DISABLED);
345 assert_eq!(first.permission(), ProtoFlagPermission::READ_ONLY);
346 let second = flag_values.flag_value.iter().find(|fv| fv.name() == "second").unwrap();
347 assert_eq!(second.package(), "com.second");
348 assert_eq!(second.name(), "second");
349 assert_eq!(second.state(), ProtoFlagState::ENABLED);
350 assert_eq!(second.permission(), ProtoFlagPermission::READ_WRITE);
351
352 // bad input: bad package in flag value
353 let error = flag_values::try_from_text_proto(
354 r#"
355flag_value {
356 package: "COM.FIRST"
357 name: "first"
358 state: DISABLED
359 permission: READ_ONLY
360}
361"#,
362 )
363 .unwrap_err();
364 assert!(format!("{:?}", error).contains("bad flag value: bad package"));
365
366 // bad input: bad name in flag value
367 let error = flag_values::try_from_text_proto(
368 r#"
369flag_value {
370 package: "com.first"
371 name: "FIRST"
372 state: DISABLED
373 permission: READ_ONLY
374}
375"#,
376 )
377 .unwrap_err();
378 assert!(format!("{:?}", error).contains("bad flag value: bad name"));
379
380 // bad input: missing state in flag value
381 let error = flag_values::try_from_text_proto(
382 r#"
383flag_value {
384 package: "com.first"
385 name: "first"
386 permission: READ_ONLY
387}
388"#,
389 )
390 .unwrap_err();
391 assert!(format!("{:?}", error).contains("Message not initialized"));
392
393 // bad input: missing permission in flag value
394 let error = flag_values::try_from_text_proto(
395 r#"
396flag_value {
397 package: "com.first"
398 name: "first"
399 state: DISABLED
400}
401"#,
402 )
403 .unwrap_err();
404 assert!(format!("{:?}", error).contains("Message not initialized"));
405 }
406
407 fn try_from_binary_proto_from_text_proto(text_proto: &str) -> Result<ProtoParsedFlags> {
408 use protobuf::Message;
409
410 let parsed_flags: ProtoParsedFlags = try_from_text_proto(text_proto)?;
411 let mut binary_proto = Vec::new();
412 parsed_flags.write_to_vec(&mut binary_proto)?;
413 parsed_flags::try_from_binary_proto(&binary_proto)
414 }
415
416 #[test]
417 fn test_parsed_flags_try_from_text_proto() {
418 // valid input
419 let text_proto = r#"
420parsed_flag {
421 package: "com.first"
422 name: "first"
423 namespace: "first_ns"
424 description: "This is the description of the first flag."
425 state: DISABLED
426 permission: READ_ONLY
427 trace {
428 source: "flags.declarations"
429 state: DISABLED
430 permission: READ_ONLY
431 }
432}
433parsed_flag {
434 package: "com.second"
435 name: "second"
436 namespace: "second_ns"
437 description: "This is the description of the second flag."
438 state: ENABLED
439 permission: READ_WRITE
440 trace {
441 source: "flags.declarations"
442 state: DISABLED
443 permission: READ_ONLY
444 }
445 trace {
446 source: "flags.values"
447 state: ENABLED
448 permission: READ_WRITE
449 }
450}
451"#;
452 let parsed_flags = try_from_binary_proto_from_text_proto(text_proto).unwrap();
453 assert_eq!(parsed_flags.parsed_flag.len(), 2);
454 let second = parsed_flags.parsed_flag.iter().find(|fv| fv.name() == "second").unwrap();
455 assert_eq!(second.package(), "com.second");
456 assert_eq!(second.name(), "second");
457 assert_eq!(second.namespace(), "second_ns");
458 assert_eq!(second.description(), "This is the description of the second flag.");
459 assert_eq!(second.state(), ProtoFlagState::ENABLED);
460 assert_eq!(second.permission(), ProtoFlagPermission::READ_WRITE);
461 assert_eq!(2, second.trace.len());
462 assert_eq!(second.trace[0].source(), "flags.declarations");
463 assert_eq!(second.trace[0].state(), ProtoFlagState::DISABLED);
464 assert_eq!(second.trace[0].permission(), ProtoFlagPermission::READ_ONLY);
465 assert_eq!(second.trace[1].source(), "flags.values");
466 assert_eq!(second.trace[1].state(), ProtoFlagState::ENABLED);
467 assert_eq!(second.trace[1].permission(), ProtoFlagPermission::READ_WRITE);
468
469 // valid input: empty
470 let parsed_flags = try_from_binary_proto_from_text_proto("").unwrap();
471 assert!(parsed_flags.parsed_flag.is_empty());
472
473 // bad input: empty trace
474 let text_proto = r#"
475parsed_flag {
476 package: "com.first"
477 name: "first"
478 namespace: "first_ns"
479 description: "This is the description of the first flag."
480 state: DISABLED
481 permission: READ_ONLY
482}
483"#;
484 let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
485 assert_eq!(format!("{:?}", error), "bad parsed flag: empty trace");
486
487 // bad input: missing fields in parsed_flag
488 let text_proto = r#"
489parsed_flag {
490 package: "com.first"
491 name: "first"
492 description: "This is the description of the first flag."
493 state: DISABLED
494 permission: READ_ONLY
495 trace {
496 source: "flags.declarations"
497 state: DISABLED
498 permission: READ_ONLY
499 }
500}
501"#;
502 let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
503 assert!(format!("{:?}", error).contains("Message not initialized"));
504
505 // bad input: parsed_flag not sorted by package
506 let text_proto = r#"
507parsed_flag {
508 package: "bbb"
509 name: "first"
510 namespace: "first_ns"
511 description: "This is the description of the first flag."
512 state: DISABLED
513 permission: READ_ONLY
514 trace {
515 source: "flags.declarations"
516 state: DISABLED
517 permission: READ_ONLY
518 }
519}
520parsed_flag {
521 package: "aaa"
522 name: "second"
523 namespace: "second_ns"
524 description: "This is the description of the second flag."
525 state: ENABLED
526 permission: READ_WRITE
527 trace {
528 source: "flags.declarations"
529 state: DISABLED
530 permission: READ_ONLY
531 }
532}
533"#;
534 let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
535 assert_eq!(
536 format!("{:?}", error),
537 "bad parsed flags: not sorted: bbb.first comes before aaa.second"
538 );
539
540 // bad input: parsed_flag not sorted by name
541 let text_proto = r#"
542parsed_flag {
543 package: "com.foo"
544 name: "bbb"
545 namespace: "first_ns"
546 description: "This is the description of the first flag."
547 state: DISABLED
548 permission: READ_ONLY
549 trace {
550 source: "flags.declarations"
551 state: DISABLED
552 permission: READ_ONLY
553 }
554}
555parsed_flag {
556 package: "com.foo"
557 name: "aaa"
558 namespace: "second_ns"
559 description: "This is the description of the second flag."
560 state: ENABLED
561 permission: READ_WRITE
562 trace {
563 source: "flags.declarations"
564 state: DISABLED
565 permission: READ_ONLY
566 }
567}
568"#;
569 let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
570 assert_eq!(
571 format!("{:?}", error),
572 "bad parsed flags: not sorted: com.foo.bbb comes before com.foo.aaa"
573 );
574
575 // bad input: duplicate flags
576 let text_proto = r#"
577parsed_flag {
578 package: "com.foo"
579 name: "bar"
580 namespace: "first_ns"
581 description: "This is the description of the first flag."
582 state: DISABLED
583 permission: READ_ONLY
584 trace {
585 source: "flags.declarations"
586 state: DISABLED
587 permission: READ_ONLY
588 }
589}
590parsed_flag {
591 package: "com.foo"
592 name: "bar"
593 namespace: "second_ns"
594 description: "This is the description of the second flag."
595 state: ENABLED
596 permission: READ_WRITE
597 trace {
598 source: "flags.declarations"
599 state: DISABLED
600 permission: READ_ONLY
601 }
602}
603"#;
604 let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
605 assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.foo.bar");
606 }
607
608 #[test]
609 fn test_parsed_flags_merge() {
610 let text_proto = r#"
611parsed_flag {
612 package: "com.first"
613 name: "first"
614 namespace: "first_ns"
615 description: "This is the description of the first flag."
616 state: DISABLED
617 permission: READ_ONLY
618 trace {
619 source: "flags.declarations"
620 state: DISABLED
621 permission: READ_ONLY
622 }
623}
624parsed_flag {
625 package: "com.second"
626 name: "second"
627 namespace: "second_ns"
628 description: "This is the description of the second flag."
629 state: ENABLED
630 permission: READ_WRITE
631 trace {
632 source: "flags.declarations"
633 state: DISABLED
634 permission: READ_ONLY
635 }
636}
637"#;
638 let expected = try_from_binary_proto_from_text_proto(text_proto).unwrap();
639
640 let text_proto = r#"
641parsed_flag {
642 package: "com.first"
643 name: "first"
644 namespace: "first_ns"
645 description: "This is the description of the first flag."
646 state: DISABLED
647 permission: READ_ONLY
648 trace {
649 source: "flags.declarations"
650 state: DISABLED
651 permission: READ_ONLY
652 }
653}
654"#;
655 let first = try_from_binary_proto_from_text_proto(text_proto).unwrap();
656
657 let text_proto = r#"
658parsed_flag {
659 package: "com.second"
660 name: "second"
661 namespace: "second_ns"
662 description: "This is the description of the second flag."
663 state: ENABLED
664 permission: READ_WRITE
665 trace {
666 source: "flags.declarations"
667 state: DISABLED
668 permission: READ_ONLY
669 }
670}
671"#;
672 let second = try_from_binary_proto_from_text_proto(text_proto).unwrap();
673
674 // bad cases
675 let error = parsed_flags::merge(vec![first.clone(), first.clone()]).unwrap_err();
676 assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.first.first");
677
678 // valid cases
679 assert!(parsed_flags::merge(vec![]).unwrap().parsed_flag.is_empty());
680 assert_eq!(first, parsed_flags::merge(vec![first.clone()]).unwrap());
681 assert_eq!(expected, parsed_flags::merge(vec![first.clone(), second.clone()]).unwrap());
682 assert_eq!(expected, parsed_flags::merge(vec![second, first]).unwrap());
683 }
684}