new enum instead of string in proto

This commit is contained in:
Priec
2026-09-07 09:54:32 +02:00
parent 6dc20bf7ad
commit e4364e1c44
16 changed files with 122 additions and 40 deletions

2
client

Submodule client updated: 4ef069e7b1...dc88add49e

View File

@@ -47,12 +47,18 @@ message ReindexResponse {
uint64 affected_tables = 2; uint64 affected_tables = 2;
} }
enum SearchIndexOperation {
SEARCH_INDEX_OPERATION_UNSPECIFIED = 0;
SEARCH_INDEX_OPERATION_UPSERT = 1;
SEARCH_INDEX_OPERATION_DELETE = 2;
}
message SearchIndexError { message SearchIndexError {
int64 job_id = 1; int64 job_id = 1;
string profile_name = 2; string profile_name = 2;
string table_name = 3; string table_name = 3;
int64 row_id = 4; int64 row_id = 4;
string operation = 5; SearchIndexOperation operation = 5;
int32 attempts = 6; int32 attempts = 6;
string last_error = 7; string last_error = 7;
} }

View File

@@ -437,7 +437,7 @@ message ProfileTreeResponse {
repeated string row_display_columns = 4; repeated string row_display_columns = 4;
// "dynamic" for user-defined tables, "system" for backend-managed tables. // "dynamic" for user-defined tables, "system" for backend-managed tables.
string table_kind = 5; ManagedTableKind table_kind = 5;
// True when this table is shared by every profile. // True when this table is shared by every profile.
bool global = 6; bool global = 6;
@@ -590,6 +590,12 @@ message GetAliasChangeHistoryResponse {
repeated AliasChangeHistoryEntry entries = 2; repeated AliasChangeHistoryEntry entries = 2;
} }
enum ManagedTableKind {
MANAGED_TABLE_KIND_UNSPECIFIED = 0;
MANAGED_TABLE_KIND_DYNAMIC = 1;
MANAGED_TABLE_KIND_SYSTEM = 2;
}
// Describes a table with its columns and associated scripts. // Describes a table with its columns and associated scripts.
message TableDetail { message TableDetail {
string name = 1; string name = 1;
@@ -598,7 +604,7 @@ message TableDetail {
repeated ScriptInfo scripts = 4; repeated ScriptInfo scripts = 4;
repeated string row_display_columns = 6; repeated string row_display_columns = 6;
map<string, ColumnBehavior> column_behaviors = 7; map<string, ColumnBehavior> column_behaviors = 7;
string table_kind = 8; ManagedTableKind table_kind = 8;
bool global = 9; bool global = 9;
// Revision of this table definition for optimistic concurrency control. // Revision of this table definition for optimistic concurrency control.
int64 row_version = 10; int64 row_version = 10;

Binary file not shown.

View File

@@ -42,8 +42,8 @@ pub struct SearchIndexError {
pub table_name: ::prost::alloc::string::String, pub table_name: ::prost::alloc::string::String,
#[prost(int64, tag = "4")] #[prost(int64, tag = "4")]
pub row_id: i64, pub row_id: i64,
#[prost(string, tag = "5")] #[prost(enumeration = "SearchIndexOperation", tag = "5")]
pub operation: ::prost::alloc::string::String, pub operation: i32,
#[prost(int32, tag = "6")] #[prost(int32, tag = "6")]
pub attempts: i32, pub attempts: i32,
#[prost(string, tag = "7")] #[prost(string, tag = "7")]
@@ -181,6 +181,36 @@ pub struct BatchCountResponse {
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)] #[repr(i32)]
pub enum SearchIndexOperation {
Unspecified = 0,
Upsert = 1,
Delete = 2,
}
impl SearchIndexOperation {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Unspecified => "SEARCH_INDEX_OPERATION_UNSPECIFIED",
Self::Upsert => "SEARCH_INDEX_OPERATION_UPSERT",
Self::Delete => "SEARCH_INDEX_OPERATION_DELETE",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"SEARCH_INDEX_OPERATION_UNSPECIFIED" => Some(Self::Unspecified),
"SEARCH_INDEX_OPERATION_UPSERT" => Some(Self::Upsert),
"SEARCH_INDEX_OPERATION_DELETE" => Some(Self::Delete),
_ => None,
}
}
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum MatchMode { pub enum MatchMode {
Unspecified = 0, Unspecified = 0,
Fuzzy = 1, Fuzzy = 1,

View File

@@ -389,8 +389,8 @@ pub mod profile_tree_response {
::prost::alloc::string::String, ::prost::alloc::string::String,
>, >,
/// "dynamic" for user-defined tables, "system" for backend-managed tables. /// "dynamic" for user-defined tables, "system" for backend-managed tables.
#[prost(string, tag = "5")] #[prost(enumeration = "super::ManagedTableKind", tag = "5")]
pub table_kind: ::prost::alloc::string::String, pub table_kind: i32,
/// True when this table is shared by every profile. /// True when this table is shared by every profile.
#[prost(bool, tag = "6")] #[prost(bool, tag = "6")]
pub global: bool, pub global: bool,
@@ -625,8 +625,8 @@ pub struct TableDetail {
::prost::alloc::string::String, ::prost::alloc::string::String,
ColumnBehavior, ColumnBehavior,
>, >,
#[prost(string, tag = "8")] #[prost(enumeration = "ManagedTableKind", tag = "8")]
pub table_kind: ::prost::alloc::string::String, pub table_kind: i32,
#[prost(bool, tag = "9")] #[prost(bool, tag = "9")]
pub global: bool, pub global: bool,
/// Revision of this table definition for optimistic concurrency control. /// Revision of this table definition for optimistic concurrency control.
@@ -1156,6 +1156,36 @@ impl AliasChangeKind {
} }
} }
} }
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum ManagedTableKind {
Unspecified = 0,
Dynamic = 1,
System = 2,
}
impl ManagedTableKind {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Unspecified => "MANAGED_TABLE_KIND_UNSPECIFIED",
Self::Dynamic => "MANAGED_TABLE_KIND_DYNAMIC",
Self::System => "MANAGED_TABLE_KIND_SYSTEM",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"MANAGED_TABLE_KIND_UNSPECIFIED" => Some(Self::Unspecified),
"MANAGED_TABLE_KIND_DYNAMIC" => Some(Self::Dynamic),
"MANAGED_TABLE_KIND_SYSTEM" => Some(Self::System),
_ => None,
}
}
}
/// How a column type is spelled in ColumnDefinition.field_type. /// How a column type is spelled in ColumnDefinition.field_type.
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]

2
server

Submodule server updated: c51ead9a19...8dc3686366

View File

@@ -112,12 +112,13 @@ pub(crate) async fn load_page(
// The profile's ledger accounts are not a link target of their own: an // The profile's ledger accounts are not a link target of their own: an
// ACCOUNTING column is how a row is posted to one. // ACCOUNTING column is how a row is posted to one.
.filter(|table| table.name != crate::schema::LEDGER_ACCOUNTS_TABLE) .filter(|table| table.name != crate::schema::LEDGER_ACCOUNTS_TABLE)
.map(|table| RelationTableOption { .map(|table| Ok(RelationTableOption {
name: table.name, name: table.name,
global: table.global, global: table.global,
system: table.table_kind == "system", system: table_scope::table_kind(table.table_kind).map_err(LoadError::Backend)?
}) == crate::definitions::table_definition::ManagedTableKind::System,
.collect::<Vec<_>>(); }))
.collect::<Result<Vec<_>, LoadError>>()?;
// Every table the new one would sit beside holds its name — including the // Every table the new one would sit beside holds its name — including the
// shared ones, which exist in every profile, and including in a profile // shared ones, which exist in every profile, and including in a profile

View File

@@ -101,7 +101,7 @@ pub(crate) async fn load_admin_page(
.map(|tables| { .map(|tables| {
tables tables
.into_iter() .into_iter()
.map(|table| TableView { .map(|table| Ok(TableView {
name: table.name, name: table.name,
// One entry per link, named by the column carrying it, so a // One entry per link, named by the column carrying it, so a
// table pointing at one target twice reads as two links. // table pointing at one target twice reads as two links.
@@ -113,10 +113,11 @@ pub(crate) async fn load_admin_page(
}) })
.collect(), .collect(),
row_display_columns: table.row_display_columns, row_display_columns: table.row_display_columns,
table_kind: table.table_kind, table_kind: table_scope::table_kind(table.table_kind).map_err(LoadError::Backend)?,
}) }))
.collect::<Vec<_>>() .collect::<Result<Vec<_>, LoadError>>()
}) })
.transpose()?
.unwrap_or_default(); .unwrap_or_default();
let selected_table = (!selection.table.is_empty()).then_some(selection.table); let selected_table = (!selection.table.is_empty()).then_some(selection.table);

View File

@@ -84,14 +84,14 @@ pub(crate) struct TableView {
pub name: String, pub name: String,
pub depends_on: Vec<String>, pub depends_on: Vec<String>,
pub row_display_columns: Vec<String>, pub row_display_columns: Vec<String>,
pub table_kind: String, pub table_kind: crate::definitions::table_definition::ManagedTableKind,
} }
impl TableView { impl TableView {
/// System tables are backend-managed: the server refuses every structural /// System tables are backend-managed: the server refuses every structural
/// write on them, so the pane offers none of the action links for one. /// write on them, so the pane offers none of the action links for one.
pub(crate) fn is_system(&self) -> bool { pub(crate) fn is_system(&self) -> bool {
self.table_kind == "system" self.table_kind == crate::definitions::table_definition::ManagedTableKind::System
} }
} }

View File

@@ -155,7 +155,7 @@ mod tests {
name: "invoice".to_string(), name: "invoice".to_string(),
depends_on: Vec::new(), depends_on: Vec::new(),
row_display_columns: vec!["number".to_string()], row_display_columns: vec!["number".to_string()],
table_kind: "dynamic".to_string(), table_kind: crate::definitions::table_definition::ManagedTableKind::Dynamic,
}], }],
selected_table: Some("invoice".to_string()), selected_table: Some("invoice".to_string()),
columns: Vec::new(), columns: Vec::new(),
@@ -195,7 +195,7 @@ mod tests {
name: "currencies".to_string(), name: "currencies".to_string(),
depends_on: Vec::new(), depends_on: Vec::new(),
row_display_columns: vec!["code".to_string()], row_display_columns: vec!["code".to_string()],
table_kind: "dynamic".to_string(), table_kind: crate::definitions::table_definition::ManagedTableKind::Dynamic,
}], }],
selected_table: Some("currencies".to_string()), selected_table: Some("currencies".to_string()),
columns: Vec::new(), columns: Vec::new(),

View File

@@ -146,9 +146,9 @@ pub(crate) async fn load_page(
} else { } else {
table_scope::linkable_tables(&tree.profiles, &catalog_tables, &inputs.selection.profile) table_scope::linkable_tables(&tree.profiles, &catalog_tables, &inputs.selection.profile)
}; };
let summary = |table: crate::definitions::table_definition::profile_tree_response::Table| TableSummary { let summary = |table: crate::definitions::table_definition::profile_tree_response::Table| -> Result<TableSummary, LoadError> { Ok(TableSummary {
name: table.name, name: table.name,
table_kind: table.table_kind, table_kind: table_scope::table_kind(table.table_kind).map_err(LoadError::Backend)?,
global: table.global, global: table.global,
// One entry per link, named by the column carrying it, so a table // One entry per link, named by the column carrying it, so a table
// pointing at one target twice reads as two links. // pointing at one target twice reads as two links.
@@ -157,9 +157,9 @@ pub(crate) async fn load_page(
.into_iter() .into_iter()
.map(|dependency| format!("{} ({})", dependency.table_name, dependency.column_name)) .map(|dependency| format!("{} ({})", dependency.table_name, dependency.column_name))
.collect(), .collect(),
}; }) };
let tables = selected_tables.into_iter().map(summary).collect::<Vec<_>>(); let tables = selected_tables.into_iter().map(summary).collect::<Result<Vec<_>, _>>()?;
let link_targets = link_targets.into_iter().map(summary).collect::<Vec<_>>(); let link_targets = link_targets.into_iter().map(summary).collect::<Result<Vec<_>, _>>()?;
// A table the scope does not hold is dropped rather than acted on. It is // A table the scope does not hold is dropped rather than acted on. It is
// said out loud, though: dropping it in silence is what left the panel // said out loud, though: dropping it in silence is what left the panel

View File

@@ -67,7 +67,7 @@ impl Selection {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct TableSummary { pub(crate) struct TableSummary {
pub name: String, pub name: String,
pub table_kind: String, pub table_kind: crate::definitions::table_definition::ManagedTableKind,
pub global: bool, pub global: bool,
pub depends_on: Vec<String>, pub depends_on: Vec<String>,
} }
@@ -76,7 +76,7 @@ impl TableSummary {
/// System tables are backend-managed: the server refuses every write below /// System tables are backend-managed: the server refuses every write below
/// on them, so the workspace does not offer the panels either. /// on them, so the workspace does not offer the panels either.
pub(crate) fn is_system(&self) -> bool { pub(crate) fn is_system(&self) -> bool {
self.table_kind == "system" self.table_kind == crate::definitions::table_definition::ManagedTableKind::System
} }
} }

View File

@@ -247,10 +247,10 @@ mod tests {
schema::ColumnDraft, schema::ColumnDraft,
}; };
fn table(name: &str, kind: &str) -> TableSummary { fn table(name: &str, kind: crate::definitions::table_definition::ManagedTableKind) -> TableSummary {
TableSummary { TableSummary {
name: name.to_string(), name: name.to_string(),
table_kind: kind.to_string(), table_kind: kind,
global: false, global: false,
depends_on: Vec::new(), depends_on: Vec::new(),
} }
@@ -268,8 +268,8 @@ mod tests {
profile: "billing".to_string(), profile: "billing".to_string(),
table: "invoice".to_string(), table: "invoice".to_string(),
}, },
tables: vec![table("invoice", "dynamic"), table("ledger_accounts", "system")], tables: vec![table("invoice", crate::definitions::table_definition::ManagedTableKind::Dynamic), table("ledger_accounts", crate::definitions::table_definition::ManagedTableKind::System)],
link_targets: vec![table("invoice", "dynamic"), table("ledger_accounts", "system")], link_targets: vec![table("invoice", crate::definitions::table_definition::ManagedTableKind::Dynamic), table("ledger_accounts", crate::definitions::table_definition::ManagedTableKind::System)],
detail: Some(TableDetailView { detail: Some(TableDetailView {
id: 7, id: 7,
row_version: 1, row_version: 1,
@@ -364,7 +364,7 @@ mod tests {
}; };
state.tables = vec![TableSummary { state.tables = vec![TableSummary {
name: "currencies".to_string(), name: "currencies".to_string(),
table_kind: "dynamic".to_string(), table_kind: crate::definitions::table_definition::ManagedTableKind::Dynamic,
global: true, global: true,
depends_on: Vec::new(), depends_on: Vec::new(),
}]; }];
@@ -598,12 +598,12 @@ mod tests {
state.columns.type_input = "link".to_string(); state.columns.type_input = "link".to_string();
// The link targets are their own list: a profile's table may point at // The link targets are their own list: a profile's table may point at
// a shared one, which is not among the profile's own tables. // a shared one, which is not among the profile's own tables.
state.link_targets.push(table("customer", "dynamic")); state.link_targets.push(table("customer", crate::definitions::table_definition::ManagedTableKind::Dynamic));
state.link_targets.push(TableSummary { state.link_targets.push(TableSummary {
global: true, global: true,
..table("currencies", "dynamic") ..table("currencies", crate::definitions::table_definition::ManagedTableKind::Dynamic)
}); });
state.link_targets.push(table("audit_log", "system")); state.link_targets.push(table("audit_log", crate::definitions::table_definition::ManagedTableKind::System));
let html = render_column_panel(&state); let html = render_column_panel(&state);

View File

@@ -13,6 +13,14 @@
//! profile's own tables are the tree's. Neither is derived from the other. //! profile's own tables are the tree's. Neither is derived from the other.
use crate::definitions::table_definition::profile_tree_response::{Profile, Table}; use crate::definitions::table_definition::profile_tree_response::{Profile, Table};
use crate::definitions::table_definition::ManagedTableKind;
pub(crate) fn table_kind(value: i32) -> Result<ManagedTableKind, String> {
match ManagedTableKind::try_from(value) {
Ok(kind @ (ManagedTableKind::Dynamic | ManagedTableKind::System)) => Ok(kind),
_ => Err(format!("Invalid managed table kind: {value}")),
}
}
/// The tables of the global scope: the catalog for `profile_name: None`, which /// The tables of the global scope: the catalog for `profile_name: None`, which
/// is every shared table in the deployment, whether or not any profile exists /// is every shared table in the deployment, whether or not any profile exists
@@ -74,7 +82,7 @@ pub(crate) mod tests {
name: name.to_string(), name: name.to_string(),
depends_on: Vec::new(), depends_on: Vec::new(),
row_display_columns: Vec::new(), row_display_columns: Vec::new(),
table_kind: "dynamic".to_string(), table_kind: ManagedTableKind::Dynamic.into(),
global, global,
profile_name: if global { profile_name: if global {
SHARED_PROFILE.to_string() SHARED_PROFILE.to_string()