209 lines
6.5 KiB
Rust
209 lines
6.5 KiB
Rust
//! Read-only protocol discovery and value-query responses.
|
|
|
|
use kaptan_config::{
|
|
ConfigManager, ConfigModel, ConfigState, ConfigValue, FieldDescriptor, FieldId, Generation,
|
|
ProfileStore,
|
|
};
|
|
|
|
use super::{import::SnapshotImporter, session::ConfigProtocol};
|
|
use crate::{ProtocolError, ValueSource};
|
|
|
|
/// Bounded descriptor page returned by a schema discovery request.
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct DescriptorPage {
|
|
/// Request being answered.
|
|
pub request_id: u32,
|
|
/// First requested descriptor index.
|
|
pub first: u16,
|
|
/// Number of descriptors present in `descriptors`.
|
|
pub len: u8,
|
|
/// Fixed-capacity response page; `None` slots are unused.
|
|
pub descriptors: [Option<&'static FieldDescriptor>; 4],
|
|
}
|
|
|
|
/// One typed value read from an explicitly selected configuration snapshot.
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct ValueResponse {
|
|
/// Request being answered.
|
|
pub request_id: u32,
|
|
/// Transaction session for transaction values, otherwise zero.
|
|
pub session_id: u32,
|
|
/// Current transaction revision, otherwise zero.
|
|
pub revision: u32,
|
|
/// Requested stable field identity.
|
|
pub field: FieldId,
|
|
/// Declared scalar value.
|
|
pub value: ConfigValue,
|
|
}
|
|
|
|
/// Correlated protocol/schema discovery response.
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub struct ConfigInfo {
|
|
/// Request being answered.
|
|
pub request_id: u32,
|
|
/// Boot-active generation, or `None` for the factory profile.
|
|
pub active_generation: Option<Generation>,
|
|
/// Last committed generation, or `None` for the factory profile.
|
|
pub committed_generation: Option<Generation>,
|
|
/// Exact schema identifier expected by profile import.
|
|
pub schema_id: [u8; 16],
|
|
/// Version carried inside the one raw CBOR profile blob.
|
|
pub profile_format: u16,
|
|
/// Number of editable fields in the registry.
|
|
pub field_count: u16,
|
|
/// Current configuration lifecycle state.
|
|
pub state: ConfigState,
|
|
/// Deployment capabilities such as bounded CBOR import.
|
|
pub capability_flags: u8,
|
|
}
|
|
|
|
/// Exact response produced by the custom configuration service.
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub enum ServiceResponse {
|
|
/// Correlated protocol/schema discovery response.
|
|
Info(ConfigInfo),
|
|
/// Completion for a mutating operation or rejected query.
|
|
Result(crate::ConfigReply),
|
|
/// Bounded self-describing schema page.
|
|
Descriptors(DescriptorPage),
|
|
/// One typed scalar value.
|
|
Value(ValueResponse),
|
|
}
|
|
|
|
impl<I> ConfigProtocol<I> {
|
|
/// Returns static descriptor metadata by stable schema index.
|
|
pub fn descriptor<C, S>(
|
|
&self,
|
|
manager: &ConfigManager<C, S>,
|
|
index: u16,
|
|
) -> Option<&'static FieldDescriptor>
|
|
where
|
|
C: ConfigModel,
|
|
S: ProfileStore<C>,
|
|
{
|
|
manager.descriptor(index)
|
|
}
|
|
|
|
/// Reads one typed value without opening or changing a transaction.
|
|
pub fn value<C, S>(
|
|
&self,
|
|
manager: &ConfigManager<C, S>,
|
|
source: ValueSource,
|
|
field: FieldId,
|
|
) -> Option<ConfigValue>
|
|
where
|
|
C: ConfigModel,
|
|
S: ProfileStore<C>,
|
|
{
|
|
match source {
|
|
ValueSource::Active => manager.active_value(field),
|
|
ValueSource::Committed => manager.committed_value(field),
|
|
ValueSource::Transaction => manager.transaction_value(field),
|
|
}
|
|
}
|
|
|
|
pub(super) fn descriptors<C, S>(
|
|
&self,
|
|
manager: &ConfigManager<C, S>,
|
|
request_id: u32,
|
|
first: u16,
|
|
requested: u16,
|
|
) -> DescriptorPage
|
|
where
|
|
C: ConfigModel,
|
|
S: ProfileStore<C>,
|
|
{
|
|
let mut descriptors = [None; 4];
|
|
let count = requested.min(descriptors.len() as u16);
|
|
let mut len = 0;
|
|
for offset in 0..count {
|
|
let Some(index) = first.checked_add(offset) else {
|
|
break;
|
|
};
|
|
let Some(descriptor) = manager.descriptor(index) else {
|
|
break;
|
|
};
|
|
descriptors[usize::from(len)] = Some(descriptor);
|
|
len += 1;
|
|
}
|
|
DescriptorPage {
|
|
request_id,
|
|
first,
|
|
len,
|
|
descriptors,
|
|
}
|
|
}
|
|
|
|
pub(super) fn info<C, S>(&self, manager: &ConfigManager<C, S>, request_id: u32) -> ConfigInfo
|
|
where
|
|
C: ConfigModel,
|
|
S: ProfileStore<C>,
|
|
I: SnapshotImporter<C>,
|
|
{
|
|
ConfigInfo {
|
|
request_id,
|
|
active_generation: manager.active_generation(),
|
|
committed_generation: manager.committed_generation(),
|
|
schema_id: C::SCHEMA_ID,
|
|
profile_format: I::PROFILE_FORMAT,
|
|
field_count: manager.field_count(),
|
|
state: self.state,
|
|
capability_flags: self.capability_flags_for::<C>(),
|
|
}
|
|
}
|
|
|
|
pub(super) fn value_response<C, S>(
|
|
&self,
|
|
manager: &ConfigManager<C, S>,
|
|
owner: u64,
|
|
request_id: u32,
|
|
session_id: u32,
|
|
field: FieldId,
|
|
source: ValueSource,
|
|
) -> ServiceResponse
|
|
where
|
|
C: ConfigModel,
|
|
S: ProfileStore<C>,
|
|
{
|
|
let (value, reply_session, revision) = match source {
|
|
ValueSource::Active => (manager.active_value(field), 0, 0),
|
|
ValueSource::Committed => (manager.committed_value(field), 0, 0),
|
|
ValueSource::Transaction => {
|
|
let Some(session) = self.session else {
|
|
return ServiceResponse::Result(self.rejected_for(
|
|
request_id,
|
|
session_id,
|
|
ProtocolError::InvalidSession,
|
|
));
|
|
};
|
|
if session.id != session_id || session.owner != owner {
|
|
return ServiceResponse::Result(self.rejected_for(
|
|
request_id,
|
|
session_id,
|
|
ProtocolError::InvalidSession,
|
|
));
|
|
}
|
|
(
|
|
manager.transaction_value(field),
|
|
session.id,
|
|
session.revision,
|
|
)
|
|
}
|
|
};
|
|
let Some(value) = value else {
|
|
return ServiceResponse::Result(self.rejected_for(
|
|
request_id,
|
|
session_id,
|
|
ProtocolError::UnknownField(field),
|
|
));
|
|
};
|
|
ServiceResponse::Value(ValueResponse {
|
|
request_id,
|
|
session_id: reply_session,
|
|
revision,
|
|
field,
|
|
value,
|
|
})
|
|
}
|
|
}
|