blob: 696e1546bccb95d8e16bf6bab4249950fa0d6ff3 [file] [log] [blame]
/*
* 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.
*/
use anyhow::{anyhow, bail, Context, Error, Result};
use protobuf::{Enum, EnumOrUnknown};
use serde::{Deserialize, Serialize};
use crate::cache::{Cache, Item, Tracepoint};
use crate::protos::{
ProtoFlagDeclaration, ProtoFlagDeclarations, ProtoFlagPermission, ProtoFlagState,
ProtoFlagValue, ProtoFlagValues, ProtoParsedFlag, ProtoParsedFlags, ProtoTracepoint,
};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
pub enum FlagState {
Enabled,
Disabled,
}
impl TryFrom<EnumOrUnknown<ProtoFlagState>> for FlagState {
type Error = Error;
fn try_from(proto: EnumOrUnknown<ProtoFlagState>) -> Result<Self, Self::Error> {
match ProtoFlagState::from_i32(proto.value()) {
Some(ProtoFlagState::ENABLED) => Ok(FlagState::Enabled),
Some(ProtoFlagState::DISABLED) => Ok(FlagState::Disabled),
None => Err(anyhow!("unknown flag state enum value {}", proto.value())),
}
}
}
impl From<FlagState> for ProtoFlagState {
fn from(state: FlagState) -> Self {
match state {
FlagState::Enabled => ProtoFlagState::ENABLED,
FlagState::Disabled => ProtoFlagState::DISABLED,
}
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
pub enum Permission {
ReadOnly,
ReadWrite,
}
impl TryFrom<EnumOrUnknown<ProtoFlagPermission>> for Permission {
type Error = Error;
fn try_from(proto: EnumOrUnknown<ProtoFlagPermission>) -> Result<Self, Self::Error> {
match ProtoFlagPermission::from_i32(proto.value()) {
Some(ProtoFlagPermission::READ_ONLY) => Ok(Permission::ReadOnly),
Some(ProtoFlagPermission::READ_WRITE) => Ok(Permission::ReadWrite),
None => Err(anyhow!("unknown permission enum value {}", proto.value())),
}
}
}
impl From<Permission> for ProtoFlagPermission {
fn from(permission: Permission) -> Self {
match permission {
Permission::ReadOnly => ProtoFlagPermission::READ_ONLY,
Permission::ReadWrite => ProtoFlagPermission::READ_WRITE,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct FlagDeclaration {
pub name: String,
pub description: String,
}
impl FlagDeclaration {
#[allow(dead_code)] // only used in unit tests
pub fn try_from_text_proto(text_proto: &str) -> Result<FlagDeclaration> {
let proto: ProtoFlagDeclaration = crate::protos::try_from_text_proto(text_proto)
.with_context(|| text_proto.to_owned())?;
proto.try_into()
}
}
impl TryFrom<ProtoFlagDeclaration> for FlagDeclaration {
type Error = Error;
fn try_from(proto: ProtoFlagDeclaration) -> Result<Self, Self::Error> {
let Some(name) = proto.name else {
bail!("missing 'name' field");
};
let Some(description) = proto.description else {
bail!("missing 'description' field");
};
Ok(FlagDeclaration { name, description })
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct FlagDeclarations {
pub package: String,
pub flags: Vec<FlagDeclaration>,
}
impl FlagDeclarations {
pub fn try_from_text_proto(text_proto: &str) -> Result<FlagDeclarations> {
let proto: ProtoFlagDeclarations = crate::protos::try_from_text_proto(text_proto)
.with_context(|| text_proto.to_owned())?;
let Some(package) = proto.package else {
bail!("missing 'package' field");
};
let mut flags = vec![];
for proto_flag in proto.flag.into_iter() {
flags.push(proto_flag.try_into()?);
}
Ok(FlagDeclarations { package, flags })
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct FlagValue {
pub package: String,
pub name: String,
pub state: FlagState,
pub permission: Permission,
}
impl FlagValue {
#[allow(dead_code)] // only used in unit tests
pub fn try_from_text_proto(text_proto: &str) -> Result<FlagValue> {
let proto: ProtoFlagValue = crate::protos::try_from_text_proto(text_proto)?;
proto.try_into()
}
pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<FlagValue>> {
let proto: ProtoFlagValues = crate::protos::try_from_text_proto(text_proto)?;
proto.flag_value.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
}
}
impl TryFrom<ProtoFlagValue> for FlagValue {
type Error = Error;
fn try_from(proto: ProtoFlagValue) -> Result<Self, Self::Error> {
let Some(package) = proto.package else {
bail!("missing 'package' field");
};
let Some(name) = proto.name else {
bail!("missing 'name' field");
};
let Some(proto_state) = proto.state else {
bail!("missing 'state' field");
};
let state = proto_state.try_into()?;
let Some(proto_permission) = proto.permission else {
bail!("missing 'permission' field");
};
let permission = proto_permission.try_into()?;
Ok(FlagValue { package, name, state, permission })
}
}
impl From<Cache> for ProtoParsedFlags {
fn from(cache: Cache) -> Self {
let mut proto = ProtoParsedFlags::new();
for item in cache.into_iter() {
proto.parsed_flag.push(item.into());
}
proto
}
}
impl From<Item> for ProtoParsedFlag {
fn from(item: Item) -> Self {
let mut proto = crate::protos::ProtoParsedFlag::new();
proto.set_package(item.package.to_owned());
proto.set_name(item.name.clone());
proto.set_description(item.description.clone());
proto.set_state(item.state.into());
proto.set_permission(item.permission.into());
for trace in item.trace.into_iter() {
proto.trace.push(trace.into());
}
proto
}
}
impl From<Tracepoint> for ProtoTracepoint {
fn from(tracepoint: Tracepoint) -> Self {
let mut proto = ProtoTracepoint::new();
proto.set_source(format!("{}", tracepoint.source));
proto.set_state(tracepoint.state.into());
proto.set_permission(tracepoint.permission.into());
proto
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_flag_try_from_text_proto() {
let expected = FlagDeclaration {
name: "1234".to_owned(),
description: "Description of the flag".to_owned(),
};
let s = r#"
name: "1234"
description: "Description of the flag"
"#;
let actual = FlagDeclaration::try_from_text_proto(s).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn test_flag_try_from_text_proto_bad_input() {
let s = r#"
name: "a"
"#;
let error = FlagDeclaration::try_from_text_proto(s).unwrap_err();
assert!(format!("{:?}", error).contains("Message not initialized"));
let s = r#"
description: "Description of the flag"
"#;
let error = FlagDeclaration::try_from_text_proto(s).unwrap_err();
assert!(format!("{:?}", error).contains("Message not initialized"));
}
#[test]
fn test_package_try_from_text_proto() {
let expected = FlagDeclarations {
package: "com.example".to_owned(),
flags: vec![
FlagDeclaration { name: "a".to_owned(), description: "A".to_owned() },
FlagDeclaration { name: "b".to_owned(), description: "B".to_owned() },
],
};
let s = r#"
package: "com.example"
flag {
name: "a"
description: "A"
}
flag {
name: "b"
description: "B"
}
"#;
let actual = FlagDeclarations::try_from_text_proto(s).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn test_flag_declaration_try_from_text_proto_list() {
let expected = FlagValue {
package: "com.example".to_owned(),
name: "1234".to_owned(),
state: FlagState::Enabled,
permission: Permission::ReadOnly,
};
let s = r#"
package: "com.example"
name: "1234"
state: ENABLED
permission: READ_ONLY
"#;
let actual = FlagValue::try_from_text_proto(s).unwrap();
assert_eq!(expected, actual);
}
}