use std::path::{Path, PathBuf};
use std::time::Duration;
use serde_json::json;
use time::format_description::well_known::Rfc3339;
use crate::{
acp::state::ACP_LOOK_PRIME_TIMEOUT_MS,
configuration::BundleConfiguration,
relay::{LookFreshness, LookSnapshotPayload, LookSnapshotSource},
runtime::inscriptions::emit_inscription,
transports::{LookMode, LookSnapshotPayload as TransportLookSnapshotPayload, TransportError},
};
use super::super::connection::BundleCatalog;
use super::super::delivery::get_output_view;
use super::super::routing::{
Addressing, Capability, OperationProfile, ResolvedRoute, requester_home_namespace,
resolve_look_route,
};
use super::super::stream::lookup_registry_session_type;
use super::super::{
RelayError, RelayRequest, RelayResponse, RequestPrincipal, SCHEMA_VERSION, bare_session_id,
canonical_session_id, relay_error, unsupported_operation,
};
use super::routed::{load_home_context, resolve_target_bundle, run_target_operation};
use super::sender::resolve_sender_in_namespace;
const LOOK_LINES_MAX: usize = 1000;
struct LookPrepared {
bundle: BundleConfiguration,
runtime_directory: PathBuf,
}
pub(in crate::relay) fn handle_look_routed(
home_namespace: &str,
home_runtime_directory: Option<&Path>,
request: RelayRequest,
configuration_root: &Path,
bundle_catalog: &BundleCatalog,
principal: Option<&RequestPrincipal>,
) -> Result<RelayResponse, RelayError> {
let RelayRequest::Look {
requester_session,
target_session,
lines,
offset,
} = request
else {
return Err(relay_error(
"internal_unexpected_request",
"non-look request routed to the look dispatcher",
None,
));
};
if let Some(requested_lines) = lines
&& !(1..=LOOK_LINES_MAX).contains(&requested_lines)
{
return Err(relay_error(
"validation_invalid_lines",
"lines must be between 1 and 1000",
Some(json!({
"lines": requested_lines,
"min": 1,
"max": LOOK_LINES_MAX,
})),
));
}
let offset = offset.unwrap_or(0);
let (home_bundle, authorization) = load_home_context(home_namespace, configuration_root)?;
let requester_session = bare_session_id(requester_session.as_str(), home_namespace);
let requester = resolve_sender_in_namespace(
home_bundle.as_ref(),
&authorization,
requester_session.as_str(),
"requester_session",
)?;
run_target_operation(
home_namespace,
&authorization,
OperationProfile {
capability: Capability::Look,
addressing: Addressing::SingleTarget,
},
|| {
resolve_look_route(
requester_home_namespace(requester.session_id.as_str(), home_namespace),
requester.session_id.as_str(),
target_session.as_str(),
)
},
|route| {
prepare_look(
route,
home_namespace,
home_bundle.as_ref(),
home_runtime_directory,
configuration_root,
bundle_catalog,
)
},
|route, prepared| {
execute_look(
route,
prepared,
requester.session_id.as_str(),
home_namespace,
lines,
offset,
principal,
)
},
)
}
fn prepare_look(
route: &ResolvedRoute,
home_namespace: &str,
home_bundle: Option<&BundleConfiguration>,
home_runtime_directory: Option<&Path>,
configuration_root: &Path,
bundle_catalog: &BundleCatalog,
) -> Result<LookPrepared, RelayError> {
let target_route = &route.targets[0];
let target_namespace = target_route.namespace.as_str();
let target_session_id = target_route
.session_id
.as_deref()
.expect("look target carries a session id");
if target_route.is_relay_wide() {
let Some(session_type) = lookup_registry_session_type(target_session_id) else {
return Err(relay_error(
"validation_unknown_target",
"target_session is not a registered principal",
Some(json!({ "target_session": target_session_id })),
));
};
return Err(unsupported_operation(
target_session_id,
session_type,
"can_be_looked",
));
}
let (bundle, runtime_directory) = resolve_target_bundle(
home_namespace,
home_bundle,
home_runtime_directory,
target_namespace,
configuration_root,
bundle_catalog,
)?;
let Some(member) = bundle
.members
.iter()
.find(|member| member.id == target_session_id)
else {
return Err(relay_error(
"validation_unknown_target",
"target_session is not in bundle configuration",
Some(json!({
"target_session": canonical_session_id(target_session_id, target_namespace),
})),
));
};
let target_principal = canonical_session_id(target_session_id, target_namespace);
let session_type = lookup_registry_session_type(target_principal.as_str())
.unwrap_or_else(|| member.target.session_type());
if !session_type.can_be_looked() {
return Err(unsupported_operation(
target_principal.as_str(),
session_type,
"can_be_looked",
));
}
Ok(LookPrepared {
bundle,
runtime_directory,
})
}
fn transport_snapshot_to_wire(snapshot: TransportLookSnapshotPayload) -> LookSnapshotPayload {
match snapshot {
TransportLookSnapshotPayload::StructuredEntries {
snapshot_entries,
entries_total,
returned_entries_count,
freshness,
snapshot_source,
stale_reason_code,
snapshot_age_ms,
} => LookSnapshotPayload::StructuredEntriesV1 {
snapshot_entries,
entries_total,
returned_entries_count,
freshness,
snapshot_source,
stale_reason_code,
snapshot_age_ms,
},
TransportLookSnapshotPayload::Lines { snapshot_lines } => {
LookSnapshotPayload::Lines { snapshot_lines }
}
}
}
fn unavailable_acp_look_snapshot() -> TransportLookSnapshotPayload {
TransportLookSnapshotPayload::StructuredEntries {
snapshot_entries: Vec::new(),
entries_total: 0,
returned_entries_count: 0,
freshness: LookFreshness::Stale,
snapshot_source: LookSnapshotSource::None,
stale_reason_code: Some("acp_worker_unavailable".to_string()),
snapshot_age_ms: None,
}
}
fn map_look_transport_error(target_session: &str, error: TransportError) -> RelayError {
let details = Some(json!({
"target_session": target_session,
"code": error.code,
"details": error.details,
}));
if error.code.starts_with("validation_") {
relay_error(error.code.as_str(), error.reason.as_str(), details)
} else {
relay_error(
"internal_unexpected_failure",
error.reason.as_str(),
details,
)
}
}
fn execute_look(
route: &ResolvedRoute,
prepared: LookPrepared,
requester_session_id: &str,
home_namespace: &str,
lines: Option<usize>,
offset: usize,
principal: Option<&RequestPrincipal>,
) -> Result<RelayResponse, RelayError> {
let LookPrepared {
bundle: look_bundle,
runtime_directory: look_runtime_directory,
} = prepared;
let target_session_id = route.targets[0]
.session_id
.as_deref()
.expect("look target carries a session id");
let target = look_bundle
.members
.iter()
.find(|member| member.id == target_session_id)
.expect("look target existence validated in prepare_look");
let mode = LookMode {
lines: lines.map(|lines| lines as u64),
offset: Some(offset as u64),
prime_timeout: Duration::from_millis(ACP_LOOK_PRIME_TIMEOUT_MS),
};
let snapshot = match get_output_view(
look_bundle.bundle_name.as_str(),
look_runtime_directory.as_path(),
target,
) {
Some(view) => view
.look(mode)
.map_err(|error| map_look_transport_error(target.id.as_str(), error))?,
None => unavailable_acp_look_snapshot(),
};
let snapshot = transport_snapshot_to_wire(snapshot);
let response = RelayResponse::Look {
schema_version: SCHEMA_VERSION.to_string(),
requester_session: canonical_session_id(requester_session_id, home_namespace),
target_session: canonical_session_id(target.id.as_str(), look_bundle.bundle_name.as_str()),
captured_at: time::OffsetDateTime::now_utc()
.format(&Rfc3339)
.unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_string()),
authenticated_identity: principal
.and_then(|principal| principal.authenticated_identity.clone()),
on_behalf_of: None,
snapshot,
};
if let RelayResponse::Look {
requester_session,
target_session,
snapshot,
..
} = &response
{
let (
snapshot_format,
snapshot_count,
entries_total,
freshness_label,
snapshot_source_label,
stale_reason_code,
snapshot_age_ms,
) = match snapshot {
LookSnapshotPayload::Lines { snapshot_lines } => {
("lines", snapshot_lines.len(), None, None, None, None, None)
}
LookSnapshotPayload::StructuredEntriesV1 {
snapshot_entries,
entries_total,
freshness,
snapshot_source,
stale_reason_code,
snapshot_age_ms,
..
} => (
"structured_entries_v1",
snapshot_entries.len(),
Some(*entries_total),
Some(match freshness {
LookFreshness::Fresh => "fresh",
LookFreshness::Stale => "stale",
}),
Some(match snapshot_source {
LookSnapshotSource::LiveBuffer => "live_buffer",
LookSnapshotSource::None => "none",
}),
stale_reason_code.as_deref(),
*snapshot_age_ms,
),
};
emit_inscription(
"relay.look.response",
&json!({
"namespace": look_bundle.bundle_name,
"requester_session": requester_session,
"target_session": target_session,
"snapshot_format": snapshot_format,
"snapshot_count": snapshot_count,
"entries_total": entries_total,
"lines_requested": lines,
"offset": offset,
"freshness": freshness_label,
"snapshot_source": snapshot_source_label,
"stale_reason_code": stale_reason_code,
"snapshot_age_ms": snapshot_age_ms,
}),
);
}
Ok(response)
}