use crate::runtime::descriptor_manifest::descriptor_contract_manifest;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RpcDescriptor {
pub service_name: String,
pub service_pkg: String,
pub method: String,
pub method_snake: String,
pub input_short: String,
pub input_pkg: String,
pub output_short: String,
pub output_pkg: String,
pub client_streaming: bool,
pub server_streaming: bool,
pub native_service_id: String,
pub logical_service_id: String,
pub sdk_facade_name: String,
pub cli_scaffold_group: String,
pub auth_mode: String,
pub roles: Vec<String>,
pub scopes: Vec<String>,
pub policy_ref: String,
pub tenant_required: bool,
pub tenant_field: String,
pub project_field: String,
pub credential_types: Vec<String>,
pub requires_postgres: bool,
pub requires_redis: bool,
pub requires_object_store: bool,
pub requires_kafka: bool,
pub requires_feature: String,
pub default_enabled: bool,
pub surface: String,
pub listener_kind: String,
pub global_enablement_key: String,
pub service_enablement_key: String,
pub required_dependencies: Vec<String>,
pub disabled_service_error_contract: String,
pub browser_safe: bool,
pub server_only: bool,
pub default_deadline_ms: i32,
pub default_max_attempts: i32,
pub csrf_required: bool,
pub internal_grpc_only: bool,
pub public_listener_allowed: bool,
pub control_plane_listener_allowed: bool,
pub peer_listener_allowed: bool,
pub operation_kind: String,
pub read_only: bool,
}
impl RpcDescriptor {
pub fn service_full(&self) -> String {
format!("{}.{}", self.service_pkg, self.service_name)
}
pub fn grpc_path(&self) -> String {
format!("/{}/{}", self.service_full(), self.method)
}
pub fn kind(&self) -> &'static str {
match (self.client_streaming, self.server_streaming) {
(false, false) => "unary",
(false, true) => "server_streaming",
(true, false) => "client_streaming",
(true, true) => "bidi",
}
}
}
pub fn rpc_manifest() -> Vec<RpcDescriptor> {
let manifest = descriptor_contract_manifest();
let registry = crate::runtime::service::native_registry::native_service_registry();
let mut out = Vec::new();
for service in &manifest.services {
let native = service.native_service.as_ref();
let registry_entry = native.and_then(|_| {
registry.iter().find(|entry| {
entry
.proto_services
.iter()
.any(|full| full == &service.full_name())
})
});
for method in &service.methods {
let endpoint = method.endpoint_security.as_ref();
let sdk = method.sdk_surface.as_ref().or(service.sdk_surface.as_ref());
out.push(RpcDescriptor {
service_name: service.name.clone(),
service_pkg: service.package.clone(),
method: method.method.clone(),
method_snake: method.method_snake.clone(),
input_short: method.input_short.clone(),
input_pkg: method.input_pkg.clone(),
output_short: method.output_short.clone(),
output_pkg: method.output_pkg.clone(),
client_streaming: method.client_streaming,
server_streaming: method.server_streaming,
native_service_id: native
.map(|n| {
crate::runtime::service::native_registry::canonical_service_id(
&n.service_id,
)
})
.unwrap_or_default(),
logical_service_id: native
.map(|n| n.logical_service_id.clone())
.unwrap_or_default(),
sdk_facade_name: native
.map(|n| n.sdk_facade_name.clone())
.unwrap_or_default(),
cli_scaffold_group: native
.map(|n| n.cli_scaffold_group.clone())
.unwrap_or_default(),
auth_mode: endpoint
.map(|security| security.auth_mode_name().to_string())
.unwrap_or_default(),
roles: endpoint
.map(|security| security.roles.clone())
.unwrap_or_default(),
scopes: endpoint
.map(|security| security.scopes.clone())
.unwrap_or_default(),
policy_ref: endpoint
.map(|security| security.policy_ref.clone())
.unwrap_or_default(),
tenant_required: endpoint
.map(|security| security.tenant_required)
.unwrap_or(false),
tenant_field: endpoint
.map(|security| security.tenant_field.clone())
.unwrap_or_default(),
project_field: endpoint
.map(|security| security.project_field.clone())
.unwrap_or_default(),
credential_types: endpoint
.map(|security| {
security
.allowed_credential_types
.iter()
.map(|value| credential_type_name(*value).to_string())
.collect()
})
.unwrap_or_default(),
requires_postgres: native.map(|n| n.requires_postgres).unwrap_or(false),
requires_redis: native.map(|n| n.requires_redis).unwrap_or(false),
requires_object_store: native.map(|n| n.requires_object_store).unwrap_or(false),
requires_kafka: native.map(|n| n.requires_kafka).unwrap_or(false),
requires_feature: native
.map(|n| n.requires_feature.clone())
.unwrap_or_default(),
default_enabled: native.map(|n| n.default_enabled).unwrap_or(true),
surface: registry_entry
.map(|entry| entry.surface.as_str().to_string())
.unwrap_or_else(|| "data_plane".to_string()),
listener_kind: registry_entry
.map(|entry| entry.listener_kind.as_str().to_string())
.unwrap_or_else(|| "public".to_string()),
global_enablement_key: if native.is_some() {
"UDB_NATIVE_SERVICES_ENABLED".to_string()
} else {
String::new()
},
service_enablement_key: native
.map(|n| {
let service_id =
crate::runtime::service::native_registry::canonical_service_id(
&n.service_id,
);
format!("UDB_NATIVE_{}_ENABLED", service_id.to_ascii_uppercase())
})
.unwrap_or_default(),
required_dependencies: registry_entry
.map(|entry| entry.required_backends.clone())
.unwrap_or_default(),
disabled_service_error_contract: if native.is_some() {
"UNIMPLEMENTED: native service '<service_id>' is disabled".to_string()
} else {
String::new()
},
browser_safe: sdk.map(|s| s.browser_safe).unwrap_or(false),
server_only: sdk.map(|s| s.server_only).unwrap_or(false),
default_deadline_ms: sdk.map(|s| s.default_deadline_ms).unwrap_or_default(),
default_max_attempts: sdk.map(|s| s.default_max_attempts).unwrap_or_default(),
csrf_required: endpoint
.map(|security| security.csrf_required)
.unwrap_or(false),
internal_grpc_only: endpoint
.map(|security| security.internal_grpc_only)
.unwrap_or(false),
public_listener_allowed: native.map(|n| n.public_listener_allowed).unwrap_or(false),
control_plane_listener_allowed: native
.map(|n| n.control_plane_listener_allowed)
.unwrap_or(false),
peer_listener_allowed: native.map(|n| n.peer_listener_allowed).unwrap_or(false),
operation_kind: crate::runtime::descriptor_manifest::operation_kind_name(
method.operation_kind,
)
.to_string(),
read_only: method.operation_kind == 1,
});
}
}
out.sort_by(|a, b| {
a.service_full()
.cmp(&b.service_full())
.then_with(|| a.method.cmp(&b.method))
});
out
}
fn credential_type_name(value: i32) -> &'static str {
match value {
1 => "bearer_jwt",
2 => "session",
3 => "api_key",
4 => "service_account",
5 => "mtls",
6 => "oidc",
7 => "saml",
8 => "webauthn",
9 => "external_jwt",
_ => "unspecified",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn manifest_is_derived_from_embedded_descriptors() {
let manifest = rpc_manifest();
assert!(
!manifest.is_empty(),
"embedded descriptor set yielded no RPCs"
);
for rpc in &manifest {
assert!(rpc.service_pkg.starts_with("udb"), "non-udb service leaked");
assert!(!rpc.method.is_empty());
assert!(!rpc.input_short.is_empty());
assert!(!rpc.output_short.is_empty());
}
}
#[test]
fn data_broker_select_surface_is_present() {
let manifest = rpc_manifest();
let select = manifest
.iter()
.find(|r| r.service_name == "DataBroker" && r.method == "Select")
.expect("DataBroker/Select must be in the manifest");
assert_eq!(select.service_pkg, "udb.services.v1");
assert_eq!(select.method_snake, "select");
assert_eq!(select.input_short, "SelectRequest");
assert_eq!(select.kind(), "unary");
assert_eq!(select.grpc_path(), "/udb.services.v1.DataBroker/Select");
}
#[test]
fn native_control_plane_services_are_present() {
let manifest = rpc_manifest();
let services: std::collections::BTreeSet<String> =
manifest.iter().map(|r| r.service_full()).collect();
for expected in [
"udb.services.v1.DataBroker",
"udb.core.authn.services.v1.AuthnService",
"udb.core.authz.services.v1.AuthzService",
"udb.core.apikey.services.v1.ApiKeyService",
"udb.core.tenant.services.v1.TenantService",
"udb.core.notification.services.v1.NotificationService",
"udb.core.analytics.services.v1.AnalyticsService",
"udb.core.storage.services.v1.StorageService",
"udb.core.asset.services.v1.AssetService",
"udb.core.webrtc.services.v1.RoomService",
"udb.core.webrtc.services.v1.PeerService",
"udb.core.webrtc.services.v1.TrackService",
"udb.core.webrtc.services.v1.TurnService",
"udb.core.webrtc.services.v1.SignalingService",
] {
assert!(
services.iter().any(|s| s == expected),
"expected service {expected} not found in manifest; present: {services:?}"
);
}
}
#[test]
fn every_descriptor_native_service_id_reaches_sdk_manifest() {
let descriptor = descriptor_contract_manifest();
let expected: std::collections::BTreeSet<String> = descriptor
.services
.iter()
.filter_map(|service| service.native_service.as_ref())
.map(|native| {
crate::runtime::service::native_registry::canonical_service_id(&native.service_id)
})
.collect();
let actual: std::collections::BTreeSet<String> = rpc_manifest()
.into_iter()
.filter_map(|rpc| {
(!rpc.native_service_id.trim().is_empty()).then_some(rpc.native_service_id)
})
.collect();
assert!(
expected.is_subset(&actual),
"descriptor native service ids must all appear in SDK manifest; missing: {:?}",
expected.difference(&actual).collect::<Vec<_>>()
);
}
#[test]
fn native_services_include_phase_o_selection_metadata() {
let manifest = rpc_manifest();
let storage = manifest
.iter()
.find(|r| r.native_service_id == "storage" && r.service_name == "StorageService")
.expect("StorageService RPC must be in the SDK manifest");
assert_eq!(storage.surface, "native_control_plane");
assert_eq!(storage.listener_kind, "control_plane");
assert_eq!(storage.global_enablement_key, "UDB_NATIVE_SERVICES_ENABLED");
assert_eq!(storage.service_enablement_key, "UDB_NATIVE_STORAGE_ENABLED");
assert!(
storage
.required_dependencies
.iter()
.any(|dep| dep == "postgres")
);
assert!(
storage
.disabled_service_error_contract
.contains("UNIMPLEMENTED")
);
let webrtc = manifest
.iter()
.find(|r| r.native_service_id == "webrtc_signaling")
.expect("SignalingService RPC must be in the SDK manifest");
assert_eq!(webrtc.surface, "webrtc_peer_plane");
assert_eq!(webrtc.listener_kind, "webrtc_peer");
}
#[test]
fn server_streaming_select_v2_is_flagged() {
let manifest = rpc_manifest();
if let Some(v2) = manifest
.iter()
.find(|r| r.service_name == "DataBroker" && r.method == "SelectV2")
{
assert!(v2.server_streaming, "SelectV2 should be server-streaming");
assert_eq!(v2.kind(), "server_streaming");
}
}
#[test]
fn every_manifest_service_has_a_generated_client_in_every_language() {
let repo = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
if !repo.join("sdk").is_dir() {
return;
}
let clients = [
("typescript", "sdk/typescript/generatedClient.ts"),
("python", "sdk/python/udb_client/generated_client.py"),
("go", "sdk/go/udbclient/generated_client.go"),
(
"java",
"sdk/java/src/main/java/dev/udb/client/generated/GeneratedUdbClient.java",
),
("csharp", "sdk/csharp/Udb.Client/GeneratedClient.cs"),
("php", "sdk/php/src/Generated/GeneratedClient.php"),
];
let mut services: std::collections::BTreeSet<(String, String)> =
std::collections::BTreeSet::new();
for rpc in rpc_manifest() {
services.insert((rpc.service_full(), rpc.service_name.clone()));
}
assert!(!services.is_empty(), "rpc_manifest yielded no services");
let mut missing: Vec<String> = Vec::new();
for (lang, rel) in clients {
let path = repo.join(rel);
let body = std::fs::read_to_string(&path)
.unwrap_or_else(|err| panic!("cannot read generated client {rel}: {err}"));
for (full, short) in &services {
if !body.contains(full.as_str()) && !body.contains(short.as_str()) {
missing.push(format!("{lang}: {full}"));
}
}
}
assert!(
missing.is_empty(),
"generated SDK clients are missing services (run `udb sdk generate all` and commit): {missing:?}"
);
}
}