use sim_kernel::{CapabilityName, Cx, Value};
use crate::{Doc, DocKind, OfficeError, fidelity::FidelityReport};
#[derive(Clone, Debug, PartialEq)]
pub struct DocCodecOptions(pub Value);
impl DocCodecOptions {
#[must_use]
pub fn new(value: Value) -> Self {
Self(value)
}
#[must_use]
pub fn value(&self) -> &Value {
&self.0
}
}
pub trait DocCodec {
fn codec_id(&self) -> &'static str;
fn kinds(&self) -> &'static [DocKind];
fn decode(
&self,
cx: &mut Cx,
bytes: &[u8],
options: &DocCodecOptions,
) -> Result<(Doc, FidelityReport), OfficeError>;
fn encode(
&self,
cx: &mut Cx,
doc: &Doc,
options: &DocCodecOptions,
) -> Result<(Vec<u8>, FidelityReport), OfficeError>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DocSite {
pub site_id: String,
pub kinds: Vec<DocKind>,
pub required_caps: Vec<CapabilityName>,
pub default_modeled: bool,
}
impl DocSite {
#[must_use]
pub fn new(
site_id: impl Into<String>,
kinds: Vec<DocKind>,
required_caps: Vec<CapabilityName>,
default_modeled: bool,
) -> Self {
Self {
site_id: site_id.into(),
kinds,
required_caps,
default_modeled,
}
}
pub fn authorize(&self, cx: &Cx) -> Result<(), OfficeError> {
if self.default_modeled {
return Ok(());
}
cx.require_all(&self.required_caps)
.map_err(OfficeError::from)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Placement {
LocalStore,
Codec(String),
Site(String),
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use sim_kernel::{DefaultFactory, NoopEvalPolicy};
use crate::{DocId, caps::NET_CONNECT_CAPABILITY};
use super::*;
struct EchoCodec;
impl DocCodec for EchoCodec {
fn codec_id(&self) -> &'static str {
"codec/echo"
}
fn kinds(&self) -> &'static [DocKind] {
static KINDS: std::sync::OnceLock<Vec<DocKind>> = std::sync::OnceLock::new();
KINDS.get_or_init(|| vec![DocKind::new("report")])
}
fn decode(
&self,
_cx: &mut Cx,
bytes: &[u8],
options: &DocCodecOptions,
) -> Result<(Doc, FidelityReport), OfficeError> {
let body = options.value().clone();
let doc = Doc::new(
DocKind::new(String::from_utf8_lossy(bytes).to_string()),
DocId::new("decoded"),
body,
vec![],
);
Ok((
doc,
FidelityReport::new(self.codec_id()).with_warning("used options body"),
))
}
fn encode(
&self,
_cx: &mut Cx,
doc: &Doc,
options: &DocCodecOptions,
) -> Result<(Vec<u8>, FidelityReport), OfficeError> {
let uses_options = options.value() == &doc.body;
let report = if uses_options {
FidelityReport::new(self.codec_id()).with_warning("options matched body")
} else {
FidelityReport::new(self.codec_id())
};
Ok((doc.kind.as_str().as_bytes().to_vec(), report))
}
}
#[test]
fn codec_options_select_behavior() {
let mut cx = Cx::new(
Arc::new(NoopEvalPolicy),
Arc::new(DefaultFactory),
sim_kernel::HandleSeed::new(0x6032_f288_5915_ed19),
);
let option_value = cx.factory().string("option-body".to_owned()).unwrap();
let options = DocCodecOptions::new(option_value.clone());
let codec = EchoCodec;
let (doc, decode_report) = codec.decode(&mut cx, b"report", &options).unwrap();
let (encoded, encode_report) = codec.encode(&mut cx, &doc, &options).unwrap();
assert_eq!(doc.body, option_value);
assert_eq!(encoded, b"report");
assert_eq!(decode_report.warnings, vec!["used options body"]);
assert_eq!(encode_report.warnings, vec!["options matched body"]);
}
#[test]
fn live_site_requiring_network_is_denied_by_default() {
let cx = Cx::new(
Arc::new(NoopEvalPolicy),
Arc::new(DefaultFactory),
sim_kernel::HandleSeed::new(0x90d5_087a_2e1d_712b),
);
let site = DocSite::new(
"site/msgraph",
vec![DocKind::new("sheet")],
vec![CapabilityName::new(NET_CONNECT_CAPABILITY)],
false,
);
let denied = site.authorize(&cx).unwrap_err();
assert!(
matches!(denied, OfficeError::CapabilityDenied(capability) if capability.as_str() == NET_CONNECT_CAPABILITY)
);
}
#[test]
fn modeled_site_is_allowed_without_live_capabilities() {
let cx = Cx::new(
Arc::new(NoopEvalPolicy),
Arc::new(DefaultFactory),
sim_kernel::HandleSeed::new(0x4b15_b1ee_0a29_abd7),
);
let site = DocSite::new(
"site/msgraph",
vec![DocKind::new("sheet")],
vec![CapabilityName::new(NET_CONNECT_CAPABILITY)],
true,
);
site.authorize(&cx).unwrap();
}
}