use std::io;
use serde::{Deserialize, Serialize};
use serde_json::{Value, value::RawValue};
use super::storage::Failure;
mod equality;
pub use equality::IndexDeclaration;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CountReason {
Withheld,
CountsNotReady,
}
#[derive(Debug, Serialize)]
pub struct KeptCount {
pub value: Option<u64>,
pub reason: Option<CountReason>,
}
impl KeptCount {
pub(in crate::serve) fn insert(self, answer: &mut Value, member: &str) -> Result<(), Failure> {
let object = answer
.as_object_mut()
.ok_or_else(|| Failure::new("answer_shape", "count answer must be an object"))?;
object.insert(
member.to_owned(),
serde_json::to_value(self.value).map_err(Failure::storage)?,
);
if let Some(reason) = self.reason {
object.insert(
format!("{member}_reason"),
serde_json::to_value(reason).map_err(Failure::storage)?,
);
}
Ok(())
}
}
#[derive(Serialize)]
pub struct RelationTotal {
#[serde(flatten)]
pub declaration: Relation,
pub relationships: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub relationships_reason: Option<CountReason>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CountsRebuild {
pub branch: String,
pub request_id: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EmbedRewalk {
pub branch: String,
pub collection: String,
pub field: String,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum CountBucket {
Documents { collection: String },
Relationships { relation: String },
Vectors { collection: String, field: String },
}
#[derive(Debug, Serialize)]
#[serde(tag = "result", rename_all = "snake_case")]
pub enum AuditVerdict {
Agree,
Differ { kept: Option<u64>, counted: u64 },
}
#[derive(Debug, Serialize)]
pub struct AuditedBucket {
#[serde(flatten)]
pub bucket: CountBucket,
#[serde(flatten)]
pub verdict: AuditVerdict,
}
#[derive(Debug, Serialize)]
#[serde(tag = "result", rename_all = "snake_case")]
pub enum CountsAudit {
Compared { buckets: Vec<AuditedBucket> },
CannotTell { reason: CountReason },
}
#[derive(Clone, Copy, Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MutationOutcome {
NotApplied,
Unknown,
}
#[derive(Clone, Debug, Serialize)]
pub struct MutationContext {
pub outcome: MutationOutcome,
pub request_id: Value,
pub receipt_branch: Value,
}
impl MutationContext {
pub(super) fn from_operation(operation: &Operation) -> Option<Self> {
if let Operation::CountsRebuild(request) = operation {
return Some(Self {
outcome: MutationOutcome::Unknown,
request_id: Value::String(request.request_id.clone()),
receipt_branch: Value::String(request.branch.clone()),
});
}
let Operation::Mutate(request) = operation else {
return None;
};
let branch = match &request.change {
Change::Fork(fork) => &fork.name,
_ => &request.branch,
};
Some(Self {
outcome: MutationOutcome::Unknown,
request_id: Value::String(request.request_id.clone()),
receipt_branch: Value::String(branch.clone()),
})
}
}
#[derive(Clone, Debug, Serialize)]
pub struct ResponseFailure {
#[serde(flatten)]
failure: Failure,
#[serde(flatten)]
mutation: Option<Box<MutationContext>>,
}
impl ResponseFailure {
pub(super) fn new(failure: Failure, mut mutation: Option<MutationContext>) -> Self {
if let Some(context) = &mut mutation {
context.outcome = if proves_not_applied(&failure.code) {
MutationOutcome::NotApplied
} else {
MutationOutcome::Unknown
};
}
Self {
failure,
mutation: mutation.map(Box::new),
}
}
pub(super) fn after_dispatch(failure: Failure, mutation: Option<MutationContext>) -> Self {
Self {
failure,
mutation: mutation.map(Box::new),
}
}
pub(super) fn mutation_context(&self) -> Option<MutationContext> {
self.mutation.as_deref().cloned()
}
pub(super) const fn failure(&self) -> &Failure {
&self.failure
}
}
impl std::ops::Deref for ResponseFailure {
type Target = Failure;
fn deref(&self) -> &Self::Target {
self.failure()
}
}
impl std::fmt::Display for ResponseFailure {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.failure, formatter)
}
}
impl std::error::Error for ResponseFailure {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.failure)
}
}
pub const NOT_APPLIED_REFUSALS: &[&str] = &[
"invalid_identifier",
"version_conflict",
"main_protected",
"discard_target_mismatch",
"request_conflict",
"branch_exists",
"collection_missing",
"document_missing",
"relation_missing",
"relation_collection",
"relation_cardinality",
"edge_properties",
"vector_dimension_missing",
"vector_dimension",
"vector_empty",
"vector_non_finite",
"schema_refused",
"json_text_invalid",
"import_empty",
"import_duplicate",
"invalid_document",
"import_limit",
"import_limit_required",
"sequence_exhausted",
"invalid_operation",
"busy",
"writer_unavailable",
"embed_admission_wait_exceeded",
"invalid_params",
"unknown_change_kind",
"hello_required",
"access_unavailable",
"write_refused",
"counts_corrupt",
"count_overflow",
"count_underflow",
"counts_merge_pending",
"counts_discard_pending",
"counts_scan_refused",
"index_field_not_top_level",
"index_already_declared",
"index_vector_field",
"index_declaration_corrupt",
"vector_field_embedded",
"vector_field_supplied",
"embed_model_change_not_available",
"embed_manifest_invalid",
"counts_access_refused",
"discard_access_refused",
"counts_branch_missing",
"append_prune_scope_components_invalid",
"append_prune_scope_component_invalid",
"append_prune_suffix_invalid",
"append_prune_document_id_invalid",
"append_prune_keep_zero",
"append_prune_keep_limit",
"append_prune_key_exists",
"append_prune_vector_field",
"append_prune_limit_overflow",
"append_prune_key_would_be_removed",
"append_prune_outgoing_edge",
"append_prune_incoming_edge",
"append_prune_revision_missing",
"append_prune_preparation_required",
"route_unclassified",
"permission_refused",
"held_for_approval",
"pass_missing",
"lys_could_not_be_asked",
"wrong_audience",
"wrong_issuer",
"pass_expired",
"pass_not_yet_valid",
"unpublished_key",
"signature_refused",
"pass_encoding_refused",
"contract_refused",
"rights_outside_audience",
"rights_truncated",
"unknown_method",
"discard_pending",
"branch_missing",
"edge_key",
];
fn proves_not_applied(code: &str) -> bool {
NOT_APPLIED_REFUSALS.contains(&code)
}
macro_rules! tagged_vocabulary {
(
$(#[$enum_doc:meta])*
$name:ident ($tag:literal, $content:literal, $known:ident) {
$( $(#[$doc:meta])* $variant:ident $(($parameter:ty))? => $wire:literal, )*
}
) => {
$(#[$enum_doc])*
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = $tag, content = $content, deny_unknown_fields)]
pub enum $name {
$( $(#[$doc])* #[serde(rename = $wire)] $variant $(($parameter))?, )*
}
impl $name {
pub const WIRE_NAMES: &'static [&'static str] = &[$($wire),*];
fn $known(name: &str) -> bool {
matches!(name, $($wire)|*)
}
}
};
}
pub const PROTOCOL: &str = "haem/1";
pub const RECEIPT_CAPABILITY: &str = "typed_receipt_fragments";
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Request {
pub id: String,
pub operation: Operation,
}
tagged_vocabulary! {
Operation ("method", "params", known_method) {
Hello(HelloAgreement) => "hello",
Present(PassPresent) => "session.pass",
DraftsRun(DraftsRun) => "drafts.run",
Branches(BranchPage) => "branches.list",
Collections(CollectionPage) => "collections.list",
Get(DocumentRead) => "documents.get",
List(DocumentPage) => "documents.list",
History(HistoryPage) => "history.list",
Follow(HeadFollow) => "head.follow",
Review(ReviewPage) => "branch.review",
ReviewCounts(ReviewPage) => "branch.review_counts",
MergePrepare(ReviewPage) => "branch.merge_prepare",
MergeDangling(ReviewPage) => "branch.merge_dangling",
Mutate(Mutation) => "mutation.apply",
Receipt(ReceiptQuery) => "mutation.receipt",
CountsRebuild(CountsRebuild) => "counts.rebuild",
CountsAudit(Selection) => "counts.audit",
EdgesOut(EdgePage) => "edge.out",
EdgesIn(EdgePage) => "edge.in",
GraphExport(GraphPage) => "graph.export",
VectorGet(VectorRead) => "vectors.get",
VectorList(VectorPage) => "vectors.list",
VectorSearch(VectorSearch) => "vectors.search",
VectorSearchText(VectorSearchText) => "vectors.search_text",
StoreStats(StoreRead) => "store.stats",
VectorFields(StoreRead) => "vectors.fields",
EmbedRewalk(EmbedRewalk) => "embed.rewalk_longer",
Relations(RelationsRead) => "relations.list",
QueryRun(QueryRequest) => "query.run",
QueryCheck(crate::serve::query::check::CheckRequest) => "query.check",
QueryWords(crate::serve::query::check::WordsRequest) => "query.words",
ChangesList(crate::serve::changes::ChangesRequest) => "changes.list",
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PassPresent {
pub pass: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DraftsRun {
#[serde(default)]
pub after: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct QueryRequest {
pub selection: Selection,
pub query: String,
#[serde(default)]
pub parameters: std::collections::BTreeMap<String, Box<RawValue>>,
pub page: QueryPage,
#[serde(default)]
pub continuation: Option<String>,
#[serde(default)]
pub rows: crate::serve::query::Rows,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct QueryPage {
pub rows: std::num::NonZeroU64,
pub bytes: std::num::NonZeroU64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Version {
#[serde(with = "decimal")]
pub generation: u64,
#[serde(with = "decimal")]
pub sequence: u64,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Selection {
pub branch: String,
pub version: Option<Version>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BranchPage {
pub after: Option<String>,
pub limit: std::num::NonZeroU32,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CollectionPage {
pub selection: Selection,
pub after: Option<String>,
pub limit: std::num::NonZeroU32,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DocumentRead {
pub selection: Selection,
pub collection: String,
pub document_id: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DocumentPage {
pub selection: Selection,
pub collection: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prefix: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
pub after: Option<String>,
pub limit: std::num::NonZeroU32,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HistoryPage {
pub branch: String,
pub after: Option<Version>,
pub limit: std::num::NonZeroU32,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HeadFollow {
pub branch: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ReviewPage {
pub branch: String,
pub after: Option<String>,
pub limit: std::num::NonZeroU32,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ReceiptQuery {
pub branch: String,
pub request_id: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Mutation {
pub request_id: String,
pub branch: String,
pub expected: Version,
pub change: Change,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DocumentAppendPrune {
pub collection: String,
pub scope: Vec<String>,
pub suffix: String,
pub document: Box<RawValue>,
pub keep: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RetentionResult {
pub document_id: String,
pub removed: u32,
pub more: bool,
}
tagged_vocabulary! {
Change ("kind", "value", known_kind) {
Collection(CollectionWrite) => "collection.put",
Put(DocumentWrite) => "document.put",
AppendPrune(DocumentAppendPrune) => "document.append_prune",
Delete(DocumentDelete) => "document.delete",
DeleteMany(DocumentDeleteMany) => "documents.delete_many",
Fork(Fork) => "branch.fork",
Discard(BranchDiscard) => "branch.discard",
Merge(BranchMerge) => "branch.merge",
EdgePut(Edge) => "edge.put",
EdgeDelete(EdgeDelete) => "edge.delete",
IndexDeclare(IndexDeclaration) => "index.declare",
VectorDeclare(VectorDeclaration) => "vector.declare",
VectorPut(VectorWrite) => "vector.put",
VectorPutMany(VectorImport) => "vectors.put_many",
VectorDelete(VectorDelete) => "vector.delete",
PutMany(DocumentImport) => "documents.put_many",
EdgePutMany(EdgeImport) => "edges.put_many",
RelationPut(Relation) => "relation.put",
RelationDelete(RelationDelete) => "relation.delete",
}
}
pub(super) enum DecodedRequest {
Request(Request),
Refused {
id: String,
failure: ResponseFailure,
},
}
#[derive(Deserialize)]
struct Correlation {
id: String,
}
#[derive(Deserialize)]
struct Envelope<'a> {
#[serde(borrow)]
operation: Option<&'a RawValue>,
}
#[derive(Deserialize)]
struct OperationProbe<'a> {
method: Option<String>,
#[serde(borrow)]
params: Option<&'a RawValue>,
}
#[derive(Deserialize)]
struct MutationProbe<'a> {
request_id: Option<Value>,
branch: Option<Value>,
#[serde(borrow)]
change: Option<&'a RawValue>,
}
#[derive(Deserialize)]
struct ChangeProbe {
kind: Option<String>,
}
#[derive(Deserialize)]
struct ReceiptBranchProbe<'a> {
kind: Option<String>,
#[serde(borrow)]
value: Option<&'a RawValue>,
}
#[derive(Deserialize)]
struct ForkNameProbe {
name: Option<Value>,
}
pub(super) fn decode_request(bytes: &[u8]) -> io::Result<DecodedRequest> {
if let Ok(request) = serde_json::from_slice::<Request>(bytes) {
validate_id(&request.id)?;
return Ok(DecodedRequest::Request(request));
}
let correlation: Correlation = serde_json::from_slice(bytes)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
validate_id(&correlation.id)?;
let failure = serde_json::from_slice::<Envelope<'_>>(bytes).map_or_else(
|_| invalid_params(),
|envelope| classify_refusal(envelope.operation),
);
Ok(DecodedRequest::Refused {
id: correlation.id,
failure: ResponseFailure::new(failure, malformed_mutation_context(bytes)),
})
}
fn malformed_mutation_context(bytes: &[u8]) -> Option<MutationContext> {
let envelope = serde_json::from_slice::<Envelope<'_>>(bytes).ok()?;
let operation = serde_json::from_str::<OperationProbe<'_>>(envelope.operation?.get()).ok()?;
let rebuild = operation.method.as_deref() == Some("counts.rebuild");
if !rebuild && operation.method.as_deref() != Some("mutation.apply") {
return None;
}
let mutation = operation
.params
.and_then(|params| serde_json::from_str::<MutationProbe<'_>>(params.get()).ok());
let (request_id, mut receipt_branch, change) = match mutation {
Some(mutation) => (
mutation.request_id.unwrap_or(Value::Null),
mutation.branch.unwrap_or(Value::Null),
mutation.change,
),
None => (Value::Null, Value::Null, None),
};
if !rebuild
&& let Some(change) = change
&& let Ok(change) = serde_json::from_str::<ReceiptBranchProbe<'_>>(change.get())
&& change.kind.as_deref() == Some("branch.fork")
{
receipt_branch = change
.value
.and_then(|value| serde_json::from_str::<ForkNameProbe>(value.get()).ok())
.and_then(|fork| fork.name)
.unwrap_or(Value::Null);
}
Some(MutationContext {
outcome: MutationOutcome::NotApplied,
request_id,
receipt_branch,
})
}
fn validate_id(id: &str) -> io::Result<()> {
if id.is_empty() || id.len() > 512 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid correlation ID",
));
}
Ok(())
}
fn classify_refusal(operation: Option<&RawValue>) -> Failure {
let Some(operation) = operation else {
return invalid_params();
};
let Ok(operation) = serde_json::from_str::<OperationProbe<'_>>(operation.get()) else {
return invalid_params();
};
let Some(method) = operation.method else {
return invalid_params();
};
if !Operation::known_method(&method) {
return Failure::new("unknown_method", "method is not in the served vocabulary");
}
if method == "hello" {
return Failure::new(
"typed_receipt_capability_missing",
format!("hello must agree {RECEIPT_CAPABILITY} and {PROTOCOL}"),
);
}
if method == "mutation.apply"
&& let Some(params) = operation.params
&& let Ok(mutation) = serde_json::from_str::<MutationProbe<'_>>(params.get())
&& let Some(change) = mutation.change
&& let Ok(change) = serde_json::from_str::<ChangeProbe>(change.get())
&& let Some(kind) = change.kind
&& !Change::known_kind(&kind)
{
return Failure::new(
"unknown_change_kind",
"change kind is not in the served vocabulary",
);
}
invalid_params()
}
fn invalid_params() -> Failure {
Failure::new(
"invalid_params",
"request does not match the operation shape",
)
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BranchDiscard {
pub name: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BranchMerge {
pub from: String,
pub reviewed: Version,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Relation {
pub relation: String,
pub from_collection: String,
pub to_collection: String,
pub cardinality: Cardinality,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Cardinality {
OneToOne,
OneToMany,
ManyToMany,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RelationDelete {
pub relation: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RelationsRead {
pub selection: Selection,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CollectionWrite {
pub collection: String,
#[serde(deserialize_with = "explicit_schema")]
pub schema: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DocumentWrite {
pub collection: String,
pub document_id: String,
pub document: Box<RawValue>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DocumentImport {
pub collection: String,
pub documents: Vec<ImportedDocument>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ImportedDocument {
pub document_id: String,
pub document: Box<RawValue>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StoreRead {
pub selection: Selection,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DocumentDelete {
pub collection: String,
pub document_id: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DocumentDeleteMany {
pub collection: String,
pub document_ids: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Fork {
pub name: String,
pub from: Version,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Node {
pub collection: String,
pub document_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Edge {
pub relation: String,
pub from: Node,
pub to: Node,
pub properties: Value,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EdgeImport {
pub edges: Vec<Edge>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EdgeDelete {
pub relation: String,
pub from: Node,
pub to: Node,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EdgePage {
pub selection: Selection,
pub node: Node,
pub relation: Option<String>,
pub after: Option<String>,
pub limit: std::num::NonZeroU32,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct GraphPage {
pub selection: Selection,
pub after: Option<String>,
pub limit: std::num::NonZeroU32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub relations: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub collections: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub members: Option<std::collections::BTreeMap<String, Vec<String>>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub records: Option<Vec<Node>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub unrelated: Option<Unrelated>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Unrelated {
pub per_collection: std::num::NonZeroU64,
pub most: std::num::NonZeroU64,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VectorDeclaration {
pub collection: String,
pub field: String,
pub declaration: super::storage::embedded::Declaration,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VectorWrite {
pub collection: String,
pub document_id: String,
pub field: String,
pub vector: Vec<f32>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VectorImport {
pub vectors: Vec<VectorWrite>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VectorDelete {
pub collection: String,
pub document_id: String,
pub field: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VectorRead {
pub selection: Selection,
pub collection: String,
pub document_id: String,
pub field: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VectorPage {
pub selection: Selection,
pub collection: String,
pub field: String,
pub after: Option<String>,
pub limit: std::num::NonZeroU32,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VectorSearch {
pub selection: Selection,
pub collection: String,
pub field: String,
pub query: Vec<f32>,
pub k: std::num::NonZeroU32,
pub metric: Metric,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VectorSearchText {
pub selection: Selection,
pub collection: String,
pub field: String,
pub text: String,
pub k: std::num::NonZeroU32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Metric {
Cosine,
L2,
Dot,
}
mod decimal {
use serde::{Deserialize, Deserializer, Serializer, de::Error};
pub fn serialize<S: Serializer>(
value: impl std::fmt::Display,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&value.to_string())
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
let text = String::deserialize(deserializer)?;
let value = text.parse::<u64>().map_err(D::Error::custom)?;
if text != value.to_string() {
return Err(D::Error::custom(
"expected a canonical unsigned decimal string",
));
}
Ok(value)
}
}
fn explicit_schema<'de, D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Option<Value>, D::Error> {
Option::<Value>::deserialize(deserializer)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum WireOwnerKind {
Database,
Api,
LeafLog,
Observer,
Branch,
BranchCommit,
BranchRef,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WireOwnedCause {
pub kind: WireOwnerKind,
pub message: String,
pub database: Option<super::receipt_wire::WireCause>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WireRefusal {
pub code: String,
pub message: String,
pub path: Option<String>,
pub cause: Option<WireOwnedCause>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum WirePacket {
Receipt {
receipt: Box<super::receipt_wire::WireReceipt>,
refusal: Option<WireRefusal>,
},
Refusal {
refusal: WireRefusal,
},
}
impl WirePacket {
#[must_use]
pub fn receipt(&self) -> Option<&super::receipt_wire::WireReceipt> {
match self {
Self::Receipt { receipt, .. } => Some(receipt.as_ref()),
Self::Refusal { .. } => None,
}
}
#[must_use]
pub const fn refusal(&self) -> Option<&WireRefusal> {
match self {
Self::Receipt { refusal, .. } => refusal.as_ref(),
Self::Refusal { refusal } => Some(refusal),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HelloAgreement {
pub protocol: String,
pub capabilities: Vec<String>,
pub max_frame: std::num::NonZeroU32,
}
impl HelloAgreement {
pub(crate) fn client() -> Self {
Self {
protocol: PROTOCOL.to_owned(),
capabilities: vec![RECEIPT_CAPABILITY.to_owned()],
max_frame: std::num::NonZeroU32::MAX,
}
}
pub(crate) fn validate(
&self,
server_cap: std::num::NonZeroU32,
) -> Result<std::num::NonZeroU32, Failure> {
if self.protocol != PROTOCOL {
return Err(Failure::new(
"receipt_protocol_mismatch",
format!("expected {PROTOCOL}, received {}", self.protocol),
));
}
if !self
.capabilities
.iter()
.any(|capability| capability == RECEIPT_CAPABILITY)
{
return Err(Failure::new(
"typed_receipt_capability_missing",
format!("hello must agree {RECEIPT_CAPABILITY}"),
));
}
let cap = self.max_frame.min(server_cap);
let minimum = super::receipt_fragments::minimum_cap().map_err(Failure::storage)?;
if u64::from(cap.get()) < u64::try_from(minimum).map_err(Failure::storage)? {
return Err(Failure::new(
"receipt_frame_too_small",
format!("offered {}, minimum {minimum}", cap.get()),
));
}
Ok(cap)
}
}
#[cfg(test)]
mod receipt_agreement_tests {
use super::*;
type TestResult = Result<(), Box<dyn std::error::Error>>;
#[test]
fn missing_capability_and_derived_floor_are_named_before_dispatch() -> TestResult {
let mut agreement = HelloAgreement::client();
agreement.capabilities.clear();
let cap = std::num::NonZeroU32::MAX;
let Err(missing) = agreement.validate(cap) else {
return Err("a hello without the receipt capability was agreed".into());
};
assert_eq!(missing.code, "typed_receipt_capability_missing");
agreement.capabilities.push(RECEIPT_CAPABILITY.to_owned());
let minimum = super::super::receipt_fragments::minimum_cap()?;
let below = u32::try_from(minimum - 1)?;
agreement.max_frame = std::num::NonZeroU32::new(below).ok_or("floor is one")?;
let Err(error) = agreement.validate(cap) else {
return Err("a frame below the receipt floor was agreed".into());
};
assert_eq!(error.code, "receipt_frame_too_small");
assert!(error.message.contains(&minimum.to_string()));
assert!(error.message.contains(&agreement.max_frame.to_string()));
Ok(())
}
}