Restructure aconfig repo to be a cargo workspace with many crates

Previously, aconfig repo is the root directory of aconfig binary crate,
but it also hosts printflags crate inside, and there is no cargo support
for printflags binary crate. In addition, with more aconfig development,
more crates are being added to this repo. Thus this repo should be
configured as a Cargo workspace with multiple crates rather than a
single crate.

Note the top level Cargo.toml file specifies the crates this workspace
carries:
(1) aconfig_protos: the proto library crate that will be used by many other
crates such as aconfig binary crate and printflags binary crate
(2) aconfig: the aconfig binary crate
(3) printflags: the printflags binary crate

(1) aconfig_protos crate setup:

Inside aconfig_protos dir we set up the aconfig_protos crate, the
previously src/proto.rs is now aconfig_protos/src/lib.rs, the build.rs
is carried over to this crate.

(2) aconfig binary crate setup:

Notice its Cargo.toml file claims package dependency on aconfig_protos
crate. It no longer carries proto related module and build.rs file.

(3) printflags binary crate setup:

Similary, notice that in its Cargo.toml file, it claims package
dependency on aconfig_protos crate.

With this setup, we can Cargo build/test each crate individually when
inside a specific crate dir. But we can also run Cargo build/test at
repo root level, which will build/test all the crates in this workplace.

This is the structuring cl. The next cl is to move storage modules into
its own library crate. This storage file library crate will be used by
both aconfig binary crate as well as flag read library crate (to be
created as another new crate here).

Bug: b/321984352
Test: top and individual crate dir level Cargo build/test, m each
individual targets

Change-Id: I75833f4997f7ee554ff6c1557df9ac87f62b2732
Merged-In: I75833f4997f7ee554ff6c1557df9ac87f62b2732
diff --git a/tools/aconfig/aconfig_protos/Android.bp b/tools/aconfig/aconfig_protos/Android.bp
new file mode 100644
index 0000000..1cc4e41
--- /dev/null
+++ b/tools/aconfig/aconfig_protos/Android.bp
@@ -0,0 +1,62 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// proto libraries for consumers of `aconfig dump --format=protobuf` output
+
+java_library {
+    name: "libaconfig_java_proto_lite",
+    host_supported: true,
+    srcs: ["protos/aconfig.proto"],
+    static_libs: ["libprotobuf-java-lite"],
+    proto: {
+        type: "lite",
+    },
+    sdk_version: "current",
+    min_sdk_version: "UpsideDownCake",
+    apex_available: [
+        "com.android.configinfrastructure",
+        "//apex_available:platform",
+    ]
+}
+
+java_library_host {
+    name: "libaconfig_java_proto_full",
+    srcs: ["protos/aconfig.proto"],
+    static_libs: ["libprotobuf-java-full"],
+    proto: {
+        type: "full",
+    },
+}
+
+python_library_host {
+    name: "libaconfig_python_proto",
+    srcs: ["protos/aconfig.proto"],
+    proto: {
+        canonical_path_from_root: false,
+    },
+}
+
+rust_protobuf {
+    name: "libaconfig_rust_proto",
+    protos: ["protos/aconfig.proto"],
+    crate_name: "aconfig_rust_proto",
+    source_stem: "aconfig_rust_proto",
+    host_supported: true,
+}
+
+rust_library {
+    name: "libaconfig_protos",
+    srcs: ["src/lib.rs"],
+    crate_name: "aconfig_protos",
+    host_supported: true,
+    lints: "none",
+    rustlibs: [
+        "libaconfig_rust_proto",
+        "libanyhow",
+        "libprotobuf",
+    ],
+    proc_macros: [
+        "libpaste",
+    ]
+}
diff --git a/tools/aconfig/aconfig_protos/Cargo.toml b/tools/aconfig/aconfig_protos/Cargo.toml
new file mode 100644
index 0000000..114cf80
--- /dev/null
+++ b/tools/aconfig/aconfig_protos/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "aconfig_protos"
+version = "0.1.0"
+edition = "2021"
+build = "build.rs"
+
+[features]
+default = ["cargo"]
+cargo = []
+
+[dependencies]
+anyhow = "1.0.69"
+paste = "1.0.11"
+protobuf = "3.2.0"
+
+[build-dependencies]
+protobuf-codegen = "3.2.0"
diff --git a/tools/aconfig/aconfig_protos/build.rs b/tools/aconfig/aconfig_protos/build.rs
new file mode 100644
index 0000000..5ef5b60
--- /dev/null
+++ b/tools/aconfig/aconfig_protos/build.rs
@@ -0,0 +1,17 @@
+use protobuf_codegen::Codegen;
+
+fn main() {
+    let proto_files = vec!["protos/aconfig.proto"];
+
+    // tell cargo to only re-run the build script if any of the proto files has changed
+    for path in &proto_files {
+        println!("cargo:rerun-if-changed={}", path);
+    }
+
+    Codegen::new()
+        .pure()
+        .include("protos")
+        .inputs(proto_files)
+        .cargo_out_dir("aconfig_proto")
+        .run_from_script();
+}
diff --git a/tools/aconfig/aconfig_protos/protos/aconfig.proto b/tools/aconfig/aconfig_protos/protos/aconfig.proto
new file mode 100644
index 0000000..ed4b24c
--- /dev/null
+++ b/tools/aconfig/aconfig_protos/protos/aconfig.proto
@@ -0,0 +1,104 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License
+
+// This is the schema definition for aconfig files. Modifications need to be
+// either backwards compatible, or include updates to all aconfig files in the
+// Android tree.
+
+syntax = "proto2";
+
+package android.aconfig;
+
+// messages used in both aconfig input and output
+
+enum flag_state {
+  ENABLED = 1;
+  DISABLED = 2;
+}
+
+enum flag_permission {
+  READ_ONLY = 1;
+  READ_WRITE = 2;
+}
+
+// aconfig input messages: flag declarations and values
+
+message flag_declaration {
+  optional string name = 1;
+  optional string namespace = 2;
+  optional string description = 3;
+  repeated string bug = 4;
+  optional bool is_fixed_read_only = 5;
+  optional bool is_exported = 6;
+  optional flag_metadata metadata = 7;
+};
+
+// Optional metadata about the flag, such as its purpose and its intended form factors.
+// Can influence the applied policies and testing strategy.
+message flag_metadata {
+  enum flag_purpose {
+    PURPOSE_UNSPECIFIED = 0;
+    PURPOSE_FEATURE = 1;
+    PURPOSE_BUGFIX = 2;
+  }
+
+  optional flag_purpose purpose = 1;
+
+  // TODO(b/315025930): Add field to designate intended target device form factor(s), such as phone, watch or other.
+}
+
+message flag_declarations {
+  optional string package = 1;
+  repeated flag_declaration flag = 2;
+  optional string container = 3;
+};
+
+message flag_value {
+  optional string package = 1;
+  optional string name = 2;
+  optional flag_state state = 3;
+  optional flag_permission permission = 4;
+};
+
+message flag_values {
+  repeated flag_value flag_value = 1;
+};
+
+// aconfig output messages: parsed and verified flag declarations and values
+
+message tracepoint {
+  // path to declaration or value file relative to $TOP
+  optional string source = 1;
+  optional flag_state state = 2;
+  optional flag_permission permission = 3;
+}
+
+message parsed_flag {
+  optional string package = 1;
+  optional string name = 2;
+  optional string namespace = 3;
+  optional string description = 4;
+  repeated string bug = 5;
+  optional flag_state state = 6;
+  optional flag_permission permission = 7;
+  repeated tracepoint trace = 8;
+  optional bool is_fixed_read_only = 9;
+  optional bool is_exported = 10;
+  optional string container = 11;
+  optional flag_metadata metadata = 12;
+}
+
+message parsed_flags {
+  repeated parsed_flag parsed_flag = 1;
+}
diff --git a/tools/aconfig/aconfig_protos/src/lib.rs b/tools/aconfig/aconfig_protos/src/lib.rs
new file mode 100644
index 0000000..f0d27d6
--- /dev/null
+++ b/tools/aconfig/aconfig_protos/src/lib.rs
@@ -0,0 +1,1011 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// When building with the Android tool-chain
+//
+//   - an external crate `aconfig_protos` will be generated
+//   - the feature "cargo" will be disabled
+//
+// When building with cargo
+//
+//   - a local sub-module will be generated in OUT_DIR and included in this file
+//   - the feature "cargo" will be enabled
+//
+// This module hides these differences from the rest of aconfig.
+
+// ---- When building with the Android tool-chain ----
+#[cfg(not(feature = "cargo"))]
+mod auto_generated {
+    pub use aconfig_rust_proto::aconfig::flag_metadata::Flag_purpose as ProtoFlagPurpose;
+    pub use aconfig_rust_proto::aconfig::Flag_declaration as ProtoFlagDeclaration;
+    pub use aconfig_rust_proto::aconfig::Flag_declarations as ProtoFlagDeclarations;
+    pub use aconfig_rust_proto::aconfig::Flag_metadata as ProtoFlagMetadata;
+    pub use aconfig_rust_proto::aconfig::Flag_permission as ProtoFlagPermission;
+    pub use aconfig_rust_proto::aconfig::Flag_state as ProtoFlagState;
+    pub use aconfig_rust_proto::aconfig::Flag_value as ProtoFlagValue;
+    pub use aconfig_rust_proto::aconfig::Flag_values as ProtoFlagValues;
+    pub use aconfig_rust_proto::aconfig::Parsed_flag as ProtoParsedFlag;
+    pub use aconfig_rust_proto::aconfig::Parsed_flags as ProtoParsedFlags;
+    pub use aconfig_rust_proto::aconfig::Tracepoint as ProtoTracepoint;
+}
+
+// ---- When building with cargo ----
+#[cfg(feature = "cargo")]
+mod auto_generated {
+    // include! statements should be avoided (because they import file contents verbatim), but
+    // because this is only used during local development, and only if using cargo instead of the
+    // Android tool-chain, we allow it
+    include!(concat!(env!("OUT_DIR"), "/aconfig_proto/mod.rs"));
+    pub use aconfig::flag_metadata::Flag_purpose as ProtoFlagPurpose;
+    pub use aconfig::Flag_declaration as ProtoFlagDeclaration;
+    pub use aconfig::Flag_declarations as ProtoFlagDeclarations;
+    pub use aconfig::Flag_metadata as ProtoFlagMetadata;
+    pub use aconfig::Flag_permission as ProtoFlagPermission;
+    pub use aconfig::Flag_state as ProtoFlagState;
+    pub use aconfig::Flag_value as ProtoFlagValue;
+    pub use aconfig::Flag_values as ProtoFlagValues;
+    pub use aconfig::Parsed_flag as ProtoParsedFlag;
+    pub use aconfig::Parsed_flags as ProtoParsedFlags;
+    pub use aconfig::Tracepoint as ProtoTracepoint;
+}
+
+// ---- Common for both the Android tool-chain and cargo ----
+pub use auto_generated::*;
+
+use anyhow::Result;
+use paste::paste;
+
+pub fn is_valid_name_ident(s: &str) -> bool {
+    // Identifiers must match [a-z][a-z0-9_]*, except consecutive underscores are not allowed
+    if s.contains("__") {
+        return false;
+    }
+    let mut chars = s.chars();
+    let Some(first) = chars.next() else {
+        return false;
+    };
+    if !first.is_ascii_lowercase() {
+        return false;
+    }
+    chars.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_')
+}
+
+pub fn is_valid_package_ident(s: &str) -> bool {
+    if !s.contains('.') {
+        return false;
+    }
+    s.split('.').all(is_valid_name_ident)
+}
+
+pub fn is_valid_container_ident(s: &str) -> bool {
+    s.split('.').all(is_valid_name_ident)
+}
+
+fn try_from_text_proto<T>(s: &str) -> Result<T>
+where
+    T: protobuf::MessageFull,
+{
+    protobuf::text_format::parse_from_str(s).map_err(|e| e.into())
+}
+
+macro_rules! ensure_required_fields {
+    ($type:expr, $struct:expr, $($field:expr),+) => {
+        $(
+        paste! {
+            ensure!($struct.[<has_ $field>](), "bad {}: missing {}", $type, $field);
+        }
+        )+
+    };
+}
+
+pub mod flag_declaration {
+    use super::*;
+    use anyhow::ensure;
+
+    pub fn verify_fields(pdf: &ProtoFlagDeclaration) -> Result<()> {
+        ensure_required_fields!("flag declaration", pdf, "name", "namespace", "description");
+
+        ensure!(is_valid_name_ident(pdf.name()), "bad flag declaration: bad name");
+        ensure!(is_valid_name_ident(pdf.namespace()), "bad flag declaration: bad name");
+        ensure!(!pdf.description().is_empty(), "bad flag declaration: empty description");
+        ensure!(pdf.bug.len() == 1, "bad flag declaration: exactly one bug required");
+
+        Ok(())
+    }
+}
+
+pub mod flag_declarations {
+    use super::*;
+    use anyhow::ensure;
+
+    pub fn try_from_text_proto(s: &str) -> Result<ProtoFlagDeclarations> {
+        let pdf: ProtoFlagDeclarations = super::try_from_text_proto(s)?;
+        verify_fields(&pdf)?;
+        Ok(pdf)
+    }
+
+    pub fn verify_fields(pdf: &ProtoFlagDeclarations) -> Result<()> {
+        ensure_required_fields!("flag declarations", pdf, "package");
+        // TODO(b/312769710): Make the container field required.
+
+        ensure!(
+            is_valid_package_ident(pdf.package()),
+            "bad flag declarations: bad package"
+        );
+        ensure!(
+            !pdf.has_container() || is_valid_container_ident(pdf.container()),
+            "bad flag declarations: bad container"
+        );
+        for flag_declaration in pdf.flag.iter() {
+            super::flag_declaration::verify_fields(flag_declaration)?;
+        }
+
+        Ok(())
+    }
+}
+
+pub mod flag_value {
+    use super::*;
+    use anyhow::ensure;
+
+    pub fn verify_fields(fv: &ProtoFlagValue) -> Result<()> {
+        ensure_required_fields!("flag value", fv, "package", "name", "state", "permission");
+
+        ensure!(is_valid_package_ident(fv.package()), "bad flag value: bad package");
+        ensure!(is_valid_name_ident(fv.name()), "bad flag value: bad name");
+
+        Ok(())
+    }
+}
+
+pub mod flag_values {
+    use super::*;
+
+    pub fn try_from_text_proto(s: &str) -> Result<ProtoFlagValues> {
+        let pfv: ProtoFlagValues = super::try_from_text_proto(s)?;
+        verify_fields(&pfv)?;
+        Ok(pfv)
+    }
+
+    pub fn verify_fields(pfv: &ProtoFlagValues) -> Result<()> {
+        for flag_value in pfv.flag_value.iter() {
+            super::flag_value::verify_fields(flag_value)?;
+        }
+        Ok(())
+    }
+}
+
+pub mod flag_permission {
+    use super::*;
+    use anyhow::bail;
+
+    pub fn parse_from_str(permission: &str) -> Result<ProtoFlagPermission> {
+        match permission.to_ascii_lowercase().as_str() {
+            "read_write" => Ok(ProtoFlagPermission::READ_WRITE),
+            "read_only" => Ok(ProtoFlagPermission::READ_ONLY),
+            _ => bail!("Permission needs to be read_only or read_write."),
+        }
+    }
+
+    pub fn to_string(permission: &ProtoFlagPermission) -> &str {
+        match permission {
+            ProtoFlagPermission::READ_WRITE => "read_write",
+            ProtoFlagPermission::READ_ONLY => "read_only",
+        }
+    }
+}
+
+pub mod tracepoint {
+    use super::*;
+    use anyhow::ensure;
+
+    pub fn verify_fields(tp: &ProtoTracepoint) -> Result<()> {
+        ensure_required_fields!("tracepoint", tp, "source", "state", "permission");
+
+        ensure!(!tp.source().is_empty(), "bad tracepoint: empty source");
+
+        Ok(())
+    }
+}
+
+pub mod parsed_flag {
+    use super::*;
+    use anyhow::ensure;
+
+    pub fn verify_fields(pf: &ProtoParsedFlag) -> Result<()> {
+        ensure_required_fields!(
+            "parsed flag",
+            pf,
+            "package",
+            "name",
+            "namespace",
+            "description",
+            "state",
+            "permission"
+        );
+
+        ensure!(is_valid_package_ident(pf.package()), "bad parsed flag: bad package");
+        ensure!(
+            !pf.has_container() || is_valid_container_ident(pf.container()),
+            "bad parsed flag: bad container"
+        );
+        ensure!(is_valid_name_ident(pf.name()), "bad parsed flag: bad name");
+        ensure!(is_valid_name_ident(pf.namespace()), "bad parsed flag: bad namespace");
+        ensure!(!pf.description().is_empty(), "bad parsed flag: empty description");
+        ensure!(!pf.trace.is_empty(), "bad parsed flag: empty trace");
+        for tp in pf.trace.iter() {
+            super::tracepoint::verify_fields(tp)?;
+        }
+        ensure!(pf.bug.len() == 1, "bad flag declaration: exactly one bug required");
+        if pf.is_fixed_read_only() {
+            ensure!(
+                pf.permission() == ProtoFlagPermission::READ_ONLY,
+                "bad parsed flag: flag is is_fixed_read_only but permission is not READ_ONLY"
+            );
+            for tp in pf.trace.iter() {
+                ensure!(tp.permission() == ProtoFlagPermission::READ_ONLY,
+                "bad parsed flag: flag is is_fixed_read_only but a tracepoint's permission is not READ_ONLY"
+                );
+            }
+        }
+
+        Ok(())
+    }
+
+    pub fn path_to_declaration(pf: &ProtoParsedFlag) -> &str {
+        debug_assert!(!pf.trace.is_empty());
+        pf.trace[0].source()
+    }
+}
+
+pub mod parsed_flags {
+    use super::*;
+    use anyhow::bail;
+    use std::cmp::Ordering;
+
+    pub fn try_from_binary_proto(bytes: &[u8]) -> Result<ProtoParsedFlags> {
+        let message: ProtoParsedFlags = protobuf::Message::parse_from_bytes(bytes)?;
+        verify_fields(&message)?;
+        Ok(message)
+    }
+
+    pub fn verify_fields(pf: &ProtoParsedFlags) -> Result<()> {
+        use crate::parsed_flag::path_to_declaration;
+
+        let mut previous: Option<&ProtoParsedFlag> = None;
+        for parsed_flag in pf.parsed_flag.iter() {
+            if let Some(prev) = previous {
+                let a = create_sorting_key(prev);
+                let b = create_sorting_key(parsed_flag);
+                match a.cmp(&b) {
+                    Ordering::Less => {}
+                    Ordering::Equal => bail!(
+                        "bad parsed flags: duplicate flag {} (defined in {} and {})",
+                        a,
+                        path_to_declaration(prev),
+                        path_to_declaration(parsed_flag)
+                    ),
+                    Ordering::Greater => {
+                        bail!("bad parsed flags: not sorted: {} comes before {}", a, b)
+                    }
+                }
+            }
+            super::parsed_flag::verify_fields(parsed_flag)?;
+            previous = Some(parsed_flag);
+        }
+        Ok(())
+    }
+
+    pub fn merge(parsed_flags: Vec<ProtoParsedFlags>, dedup: bool) -> Result<ProtoParsedFlags> {
+        let mut merged = ProtoParsedFlags::new();
+        for mut pfs in parsed_flags.into_iter() {
+            merged.parsed_flag.append(&mut pfs.parsed_flag);
+        }
+        merged.parsed_flag.sort_by_cached_key(create_sorting_key);
+        if dedup {
+            // Deduplicate identical protobuf messages.  Messages with the same sorting key but
+            // different fields (including the path to the original source file) will not be
+            // deduplicated and trigger an error in verify_fields.
+            merged.parsed_flag.dedup();
+        }
+        verify_fields(&merged)?;
+        Ok(merged)
+    }
+
+    pub fn sort_parsed_flags(pf: &mut ProtoParsedFlags) {
+        pf.parsed_flag.sort_by_key(create_sorting_key);
+    }
+
+    fn create_sorting_key(pf: &ProtoParsedFlag) -> String {
+        pf.fully_qualified_name()
+    }
+}
+
+pub trait ParsedFlagExt {
+    fn fully_qualified_name(&self) -> String;
+}
+
+impl ParsedFlagExt for ProtoParsedFlag {
+    fn fully_qualified_name(&self) -> String {
+        format!("{}.{}", self.package(), self.name())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_flag_declarations_try_from_text_proto() {
+        // valid input
+        let flag_declarations = flag_declarations::try_from_text_proto(
+            r#"
+package: "com.foo.bar"
+container: "system"
+flag {
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    bug: "123"
+    is_exported: true
+}
+flag {
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+    bug: "abc"
+    is_fixed_read_only: true
+}
+"#,
+        )
+        .unwrap();
+        assert_eq!(flag_declarations.package(), "com.foo.bar");
+        assert_eq!(flag_declarations.container(), "system");
+        let first = flag_declarations.flag.iter().find(|pf| pf.name() == "first").unwrap();
+        assert_eq!(first.name(), "first");
+        assert_eq!(first.namespace(), "first_ns");
+        assert_eq!(first.description(), "This is the description of the first flag.");
+        assert_eq!(first.bug, vec!["123"]);
+        assert!(!first.is_fixed_read_only());
+        assert!(first.is_exported());
+        let second = flag_declarations.flag.iter().find(|pf| pf.name() == "second").unwrap();
+        assert_eq!(second.name(), "second");
+        assert_eq!(second.namespace(), "second_ns");
+        assert_eq!(second.description(), "This is the description of the second flag.");
+        assert_eq!(second.bug, vec!["abc"]);
+        assert!(second.is_fixed_read_only());
+        assert!(!second.is_exported());
+
+        // valid input: missing container in flag declarations is supported
+        let flag_declarations = flag_declarations::try_from_text_proto(
+            r#"
+package: "com.foo.bar"
+flag {
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    bug: "123"
+}
+"#,
+        )
+        .unwrap();
+        assert_eq!(flag_declarations.container(), "");
+        assert!(!flag_declarations.has_container());
+
+        // bad input: missing package in flag declarations
+        let error = flag_declarations::try_from_text_proto(
+            r#"
+container: "system"
+flag {
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+}
+flag {
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+}
+"#,
+        )
+        .unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad flag declarations: missing package");
+
+        // bad input: missing namespace in flag declaration
+        let error = flag_declarations::try_from_text_proto(
+            r#"
+package: "com.foo.bar"
+container: "system"
+flag {
+    name: "first"
+    description: "This is the description of the first flag."
+}
+flag {
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+}
+"#,
+        )
+        .unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad flag declaration: missing namespace");
+
+        // bad input: bad package name in flag declarations
+        let error = flag_declarations::try_from_text_proto(
+            r#"
+package: "_com.FOO__BAR"
+container: "system"
+flag {
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+}
+flag {
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+}
+"#,
+        )
+        .unwrap_err();
+        assert!(format!("{:?}", error).contains("bad flag declarations: bad package"));
+
+        // bad input: bad name in flag declaration
+        let error = flag_declarations::try_from_text_proto(
+            r#"
+package: "com.foo.bar"
+container: "system"
+flag {
+    name: "FIRST"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+}
+flag {
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+}
+"#,
+        )
+        .unwrap_err();
+        assert!(format!("{:?}", error).contains("bad flag declaration: bad name"));
+
+        // bad input: no bug entries in flag declaration
+        let error = flag_declarations::try_from_text_proto(
+            r#"
+package: "com.foo.bar"
+container: "system"
+flag {
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+}
+"#,
+        )
+        .unwrap_err();
+        assert!(format!("{:?}", error).contains("bad flag declaration: exactly one bug required"));
+
+        // bad input: multiple bug entries in flag declaration
+        let error = flag_declarations::try_from_text_proto(
+            r#"
+package: "com.foo.bar"
+container: "system"
+flag {
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    bug: "123"
+    bug: "abc"
+}
+"#,
+        )
+        .unwrap_err();
+        assert!(format!("{:?}", error).contains("bad flag declaration: exactly one bug required"));
+
+        // bad input: invalid container name in flag declaration
+        let error = flag_declarations::try_from_text_proto(
+            r#"
+package: "com.foo.bar"
+container: "__bad_bad_container.com"
+flag {
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    bug: "123"
+    bug: "abc"
+}
+"#,
+        )
+        .unwrap_err();
+        assert!(format!("{:?}", error).contains("bad flag declarations: bad container"));
+
+        // TODO(b/312769710): Verify error when container is missing.
+    }
+
+    #[test]
+    fn test_flag_values_try_from_text_proto() {
+        // valid input
+        let flag_values = flag_values::try_from_text_proto(
+            r#"
+flag_value {
+    package: "com.first"
+    name: "first"
+    state: DISABLED
+    permission: READ_ONLY
+}
+flag_value {
+    package: "com.second"
+    name: "second"
+    state: ENABLED
+    permission: READ_WRITE
+}
+"#,
+        )
+        .unwrap();
+        let first = flag_values.flag_value.iter().find(|fv| fv.name() == "first").unwrap();
+        assert_eq!(first.package(), "com.first");
+        assert_eq!(first.name(), "first");
+        assert_eq!(first.state(), ProtoFlagState::DISABLED);
+        assert_eq!(first.permission(), ProtoFlagPermission::READ_ONLY);
+        let second = flag_values.flag_value.iter().find(|fv| fv.name() == "second").unwrap();
+        assert_eq!(second.package(), "com.second");
+        assert_eq!(second.name(), "second");
+        assert_eq!(second.state(), ProtoFlagState::ENABLED);
+        assert_eq!(second.permission(), ProtoFlagPermission::READ_WRITE);
+
+        // bad input: bad package in flag value
+        let error = flag_values::try_from_text_proto(
+            r#"
+flag_value {
+    package: "COM.FIRST"
+    name: "first"
+    state: DISABLED
+    permission: READ_ONLY
+}
+"#,
+        )
+        .unwrap_err();
+        assert!(format!("{:?}", error).contains("bad flag value: bad package"));
+
+        // bad input: bad name in flag value
+        let error = flag_values::try_from_text_proto(
+            r#"
+flag_value {
+    package: "com.first"
+    name: "FIRST"
+    state: DISABLED
+    permission: READ_ONLY
+}
+"#,
+        )
+        .unwrap_err();
+        assert!(format!("{:?}", error).contains("bad flag value: bad name"));
+
+        // bad input: missing state in flag value
+        let error = flag_values::try_from_text_proto(
+            r#"
+flag_value {
+    package: "com.first"
+    name: "first"
+    permission: READ_ONLY
+}
+"#,
+        )
+        .unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad flag value: missing state");
+
+        // bad input: missing permission in flag value
+        let error = flag_values::try_from_text_proto(
+            r#"
+flag_value {
+    package: "com.first"
+    name: "first"
+    state: DISABLED
+}
+"#,
+        )
+        .unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad flag value: missing permission");
+    }
+
+    fn try_from_binary_proto_from_text_proto(text_proto: &str) -> Result<ProtoParsedFlags> {
+        use protobuf::Message;
+
+        let parsed_flags: ProtoParsedFlags = try_from_text_proto(text_proto)?;
+        let mut binary_proto = Vec::new();
+        parsed_flags.write_to_vec(&mut binary_proto)?;
+        parsed_flags::try_from_binary_proto(&binary_proto)
+    }
+
+    #[test]
+    fn test_parsed_flags_try_from_text_proto() {
+        // valid input
+        let text_proto = r#"
+parsed_flag {
+    package: "com.first"
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    bug: "SOME_BUG"
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    container: "system"
+}
+parsed_flag {
+    package: "com.second"
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+    bug: "SOME_BUG"
+    state: ENABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    trace {
+        source: "flags.values"
+        state: ENABLED
+        permission: READ_ONLY
+    }
+    is_fixed_read_only: true
+    container: "system"
+}
+"#;
+        let parsed_flags = try_from_binary_proto_from_text_proto(text_proto).unwrap();
+        assert_eq!(parsed_flags.parsed_flag.len(), 2);
+        let second = parsed_flags.parsed_flag.iter().find(|fv| fv.name() == "second").unwrap();
+        assert_eq!(second.package(), "com.second");
+        assert_eq!(second.name(), "second");
+        assert_eq!(second.namespace(), "second_ns");
+        assert_eq!(second.description(), "This is the description of the second flag.");
+        assert_eq!(second.bug, vec!["SOME_BUG"]);
+        assert_eq!(second.state(), ProtoFlagState::ENABLED);
+        assert_eq!(second.permission(), ProtoFlagPermission::READ_ONLY);
+        assert_eq!(2, second.trace.len());
+        assert_eq!(second.trace[0].source(), "flags.declarations");
+        assert_eq!(second.trace[0].state(), ProtoFlagState::DISABLED);
+        assert_eq!(second.trace[0].permission(), ProtoFlagPermission::READ_ONLY);
+        assert_eq!(second.trace[1].source(), "flags.values");
+        assert_eq!(second.trace[1].state(), ProtoFlagState::ENABLED);
+        assert_eq!(second.trace[1].permission(), ProtoFlagPermission::READ_ONLY);
+        assert!(second.is_fixed_read_only());
+
+        // valid input: empty
+        let parsed_flags = try_from_binary_proto_from_text_proto("").unwrap();
+        assert!(parsed_flags.parsed_flag.is_empty());
+
+        // bad input: empty trace
+        let text_proto = r#"
+parsed_flag {
+    package: "com.first"
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    state: DISABLED
+    permission: READ_ONLY
+    container: "system"
+}
+"#;
+        let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad parsed flag: empty trace");
+
+        // bad input: missing namespace in parsed_flag
+        let text_proto = r#"
+parsed_flag {
+    package: "com.first"
+    name: "first"
+    description: "This is the description of the first flag."
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    container: "system"
+}
+"#;
+        let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad parsed flag: missing namespace");
+
+        // bad input: parsed_flag not sorted by package
+        let text_proto = r#"
+parsed_flag {
+    package: "bbb.bbb"
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    bug: ""
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    container: "system"
+}
+parsed_flag {
+    package: "aaa.aaa"
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+    bug: ""
+    state: ENABLED
+    permission: READ_WRITE
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    container: "system"
+}
+"#;
+        let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
+        assert_eq!(
+            format!("{:?}", error),
+            "bad parsed flags: not sorted: bbb.bbb.first comes before aaa.aaa.second"
+        );
+
+        // bad input: parsed_flag not sorted by name
+        let text_proto = r#"
+parsed_flag {
+    package: "com.foo"
+    name: "bbb"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    bug: ""
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    container: "system"
+}
+parsed_flag {
+    package: "com.foo"
+    name: "aaa"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+    bug: ""
+    state: ENABLED
+    permission: READ_WRITE
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    container: "system"
+}
+"#;
+        let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
+        assert_eq!(
+            format!("{:?}", error),
+            "bad parsed flags: not sorted: com.foo.bbb comes before com.foo.aaa"
+        );
+
+        // bad input: duplicate flags
+        let text_proto = r#"
+parsed_flag {
+    package: "com.foo"
+    name: "bar"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    bug: ""
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    container: "system"
+}
+parsed_flag {
+    package: "com.foo"
+    name: "bar"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+    bug: ""
+    state: ENABLED
+    permission: READ_WRITE
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    container: "system"
+}
+"#;
+        let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.foo.bar (defined in flags.declarations and flags.declarations)");
+    }
+
+    #[test]
+    fn test_parsed_flag_path_to_declaration() {
+        let text_proto = r#"
+parsed_flag {
+    package: "com.foo"
+    name: "bar"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    bug: "b/12345678"
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    trace {
+        source: "flags.values"
+        state: ENABLED
+        permission: READ_ONLY
+    }
+    container: "system"
+}
+"#;
+        let parsed_flags = try_from_binary_proto_from_text_proto(text_proto).unwrap();
+        let parsed_flag = &parsed_flags.parsed_flag[0];
+        assert_eq!(
+            crate::parsed_flag::path_to_declaration(parsed_flag),
+            "flags.declarations"
+        );
+    }
+
+    #[test]
+    fn test_parsed_flags_merge() {
+        let text_proto = r#"
+parsed_flag {
+    package: "com.first"
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    bug: "a"
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    container: "system"
+}
+parsed_flag {
+    package: "com.second"
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+    bug: "b"
+    state: ENABLED
+    permission: READ_WRITE
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    container: "system"
+}
+"#;
+        let expected = try_from_binary_proto_from_text_proto(text_proto).unwrap();
+
+        let text_proto = r#"
+parsed_flag {
+    package: "com.first"
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    bug: "a"
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    container: "system"
+}
+"#;
+        let first = try_from_binary_proto_from_text_proto(text_proto).unwrap();
+
+        let text_proto = r#"
+parsed_flag {
+    package: "com.second"
+    name: "second"
+    namespace: "second_ns"
+    bug: "b"
+    description: "This is the description of the second flag."
+    state: ENABLED
+    permission: READ_WRITE
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    container: "system"
+}
+"#;
+        let second = try_from_binary_proto_from_text_proto(text_proto).unwrap();
+
+        let text_proto = r#"
+parsed_flag {
+    package: "com.second"
+    name: "second"
+    namespace: "second_ns"
+    bug: "b"
+    description: "This is the description of the second flag."
+    state: ENABLED
+    permission: READ_WRITE
+    trace {
+        source: "duplicate/flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+"#;
+        let second_duplicate = try_from_binary_proto_from_text_proto(text_proto).unwrap();
+
+        // bad cases
+
+        // two of the same flag with dedup disabled
+        let error = parsed_flags::merge(vec![first.clone(), first.clone()], false).unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.first.first (defined in flags.declarations and flags.declarations)");
+
+        // two conflicting flags with dedup disabled
+        let error =
+            parsed_flags::merge(vec![second.clone(), second_duplicate.clone()], false).unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.second.second (defined in flags.declarations and duplicate/flags.declarations)");
+
+        // two conflicting flags with dedup enabled
+        let error =
+            parsed_flags::merge(vec![second.clone(), second_duplicate.clone()], true).unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.second.second (defined in flags.declarations and duplicate/flags.declarations)");
+
+        // valid cases
+        assert!(parsed_flags::merge(vec![], false).unwrap().parsed_flag.is_empty());
+        assert!(parsed_flags::merge(vec![], true).unwrap().parsed_flag.is_empty());
+        assert_eq!(first, parsed_flags::merge(vec![first.clone()], false).unwrap());
+        assert_eq!(first, parsed_flags::merge(vec![first.clone()], true).unwrap());
+        assert_eq!(
+            expected,
+            parsed_flags::merge(vec![first.clone(), second.clone()], false).unwrap()
+        );
+        assert_eq!(
+            expected,
+            parsed_flags::merge(vec![first.clone(), second.clone()], true).unwrap()
+        );
+        assert_eq!(
+            expected,
+            parsed_flags::merge(vec![second.clone(), first.clone()], false).unwrap()
+        );
+        assert_eq!(
+            expected,
+            parsed_flags::merge(vec![second.clone(), first.clone()], true).unwrap()
+        );
+
+        // two identical flags with dedup enabled
+        assert_eq!(first, parsed_flags::merge(vec![first.clone(), first.clone()], true).unwrap());
+    }
+}