mod administration_protocol;
mod authenticated_rpc;
pub mod canonical_base64_bytes;
pub mod canonical_event_json;
mod catalog_intent;
mod cluster_control_protocol;
mod core_api_authority;
mod engine_api;
mod gateway_security;
mod kubernetes_identity;
mod orchestration_intent;
mod public_session_protocol;
mod realtime_read_protocol;
mod retrieval;
mod rollout_protocol;
mod rpc;
mod runtime_material;
pub mod search;
pub mod search_projection;
mod subscription_protocol;
pub mod transport;
mod vector_mapping;
pub use administration_protocol::*;
pub use authenticated_rpc::{
MetastoreCommandFamily, RPC_AUTH_PROFILE_V1, RPC_MAX_CONTROL_PAYLOAD_BYTES, RPC_MAX_PAYLOAD_BYTES,
RPC_METASTORE_SNAPSHOT_REQUEST_FRAME_BYTES, RPC_METASTORE_SNAPSHOT_RESPONSE_FRAME_BYTES, RPC_PRELUDE_BYTES,
RPC_PRELUDE_FORMAT_VERSION, RPC_SEARCH_REQUEST_FRAME_BYTES, RPC_SEARCH_RESPONSE_FRAME_BYTES, RPC_WIRE_VERSION_MAX,
RPC_WIRE_VERSION_MIN, RpcAuthorizationError, RpcMethod, RpcNegotiatedProfile, RpcPrelude, RpcVersionOffer,
WORKLOAD_IDENTITY_FORMAT_VERSION, WorkloadIdentity, authorize_rpc,
};
pub use catalog_intent::*;
pub use cluster_control_protocol::*;
pub use core_api_authority::*;
pub use engine_api::{
EngineApiBatchWriteResult, EngineApiCanonicalPlan, EngineApiClosePitRequest, EngineApiClosePitResult,
EngineApiCompositeContinuation, EngineApiContinuation, EngineApiCreateIndexRequest, EngineApiCreateIndexResult,
EngineApiDeleteIndexRequest, EngineApiDeleteIndexResult, EngineApiDeleteIndexState, EngineApiFailureRecovery,
EngineApiFailureStage, EngineApiGetDocument, EngineApiGetRequest, EngineApiGetResult, EngineApiIndexSelector,
EngineApiIngestDocument, EngineApiIngestRequest, EngineApiMutateRequest, EngineApiMutationCondition,
EngineApiMutationItem, EngineApiMutationOperation, EngineApiNestedInnerHitsResult, EngineApiNestedSearchHit,
EngineApiOpenPitRequest, EngineApiPitLeaseResult, EngineApiPitRouteEntry, EngineApiPutRequest, EngineApiPutResult,
EngineApiRenewPitRequest, EngineApiResolvedRouteEntry, EngineApiResolvedRouteVector, EngineApiResolvedShardRoute,
EngineApiRouteIdentity, EngineApiScopedSearchHit, EngineApiScopedTiebreak, EngineApiScopedWriteToken,
EngineApiSearchFailure, EngineApiSearchFailureReason, EngineApiSearchRequest, EngineApiSearchResult,
EngineApiShardCounts, EngineApiTotal, EngineApiTotalRelation, EngineApiUpdateMappingRequest,
EngineApiUpdateMappingResult, EngineApiWriteOutcome, EngineVectorAchievedModeV1, EngineVectorBranchDiagnosticsV1,
MAX_ENGINE_API_INDEX_EXPRESSION_BYTES, MAX_ENGINE_API_INDEXES, MAX_ENGINE_API_ROUTING_VALUES,
MAX_ENGINE_API_SEARCH_WINDOW, SCOPED_CATALOG_FORMAT_VERSION, SCOPED_DOCUMENT_FORMAT_VERSION,
SCOPED_PIT_FORMAT_VERSION, SCOPED_SEARCH_FORMAT_VERSION, ScopedSearchContractError,
validate_scoped_composite_continuation, validate_scoped_search_continuation, validate_scoped_search_plan,
};
pub use gateway_security::*;
pub use kubernetes_identity::*;
pub use lodestone_wire::*;
pub use orchestration_intent::*;
pub use public_session_protocol::*;
pub use realtime_read_protocol::{
REALTIME_READ_ENVELOPE_FORMAT_VERSION, RealtimeReadEnvelopeError, RealtimeReadKind, decode_realtime_read_request,
decode_realtime_read_response, encode_realtime_read_request, encode_realtime_read_response,
};
pub use retrieval::*;
pub use rollout_protocol::*;
pub use rpc::{
INTERNAL_RPC_FORMAT_VERSION, InternalRpcError, InternalRpcFrame, InternalRpcMethod, InternalRpcStatus,
MAX_INTERNAL_RPC_FRAME_BYTES, MAX_METASTORE_RPC_PAYLOAD_BYTES,
};
pub use runtime_material::*;
pub use search_projection::SearchSourceProjection;
pub use subscription_protocol::*;
pub use vector_mapping::*;
use std::collections::BTreeMap;
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use lodestone_types::{
DocumentId, DurabilityBoundary, DurabilityClass, IdempotencyKey, PitId, PrimaryTerm, SeqNo, ShardId, TaskId,
VisibilityMode, WriteStage,
};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::value::RawValue;
pub const INTERNAL_REQUEST_FORMAT_VERSION: u16 = 3;
pub const MAX_COMPOSITE_SOURCES: usize = 16;
pub const MAX_COMPOSITE_NAME_BYTES: usize = 512;
pub const MAX_COMPOSITE_AGGREGATION_PATH_DEPTH: usize = 16;
pub const MAX_COMPOSITE_SCALAR_STRING_BYTES: usize = 65_536;
pub const MAX_INGEST_DOCUMENTS: usize = 10_000;
pub const MAX_REQUEST_ID_BYTES: usize = 128;
pub const MAX_REINDEX_BATCH_SIZE: usize = 1_000;
pub const MAX_PIT_KEEP_ALIVE_MILLIS: u64 = 24 * 60 * 60 * 1_000;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(transparent)]
pub struct RequestId(String);
impl RequestId {
pub fn new(value: impl Into<String>) -> Result<Self, ProtocolError> {
let value = value.into();
if value.is_empty() {
return Err(ProtocolError::EmptyRequestId);
}
if value.len() > MAX_REQUEST_ID_BYTES {
return Err(ProtocolError::RequestIdTooLong(value.len()));
}
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl<'de> Deserialize<'de> for RequestId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::new(value).map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct IngestRequest {
pub format_version: u16,
pub shard_id: Option<ShardId>,
pub documents: Vec<IngestDocument>,
pub visibility: VisibilityMode,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct IngestDocument {
pub id: DocumentId,
#[serde(default)]
pub routing: Option<String>,
pub source: Box<RawValue>,
}
impl PartialEq for IngestDocument {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.routing == other.routing && self.source.get() == other.source.get()
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ShardWriteToken {
pub shard_id: ShardId,
pub first_seq_no: SeqNo,
pub last_seq_no: SeqNo,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum MutationCondition {
#[default]
Any,
IfSeqNo {
seq_no: SeqNo,
},
IfSeqNoAndPrimaryTerm {
seq_no: SeqNo,
primary_term: PrimaryTerm,
},
MustNotExist,
}
#[derive(Clone, Debug, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum MutationOperation {
Put {
source: Box<RawValue>,
#[serde(default)]
routing: Option<String>,
},
Update {
patch: Box<RawValue>,
#[serde(default)]
routing: Option<String>,
},
Delete,
}
impl PartialEq for MutationOperation {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
Self::Put {
source: left,
routing: left_routing,
},
Self::Put {
source: right,
routing: right_routing,
},
) => left.get() == right.get() && left_routing == right_routing,
(
Self::Update {
patch: left,
routing: left_routing,
},
Self::Update {
patch: right,
routing: right_routing,
},
) => left.get() == right.get() && left_routing == right_routing,
(Self::Delete, Self::Delete) => true,
_ => false,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct MutationItem {
pub id: DocumentId,
pub operation: MutationOperation,
pub condition: MutationCondition,
#[serde(skip_serializing_if = "Option::is_none")]
pub idempotency_key: Option<IdempotencyKey>,
}
impl<'de> Deserialize<'de> for MutationItem {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct RawMutationItem {
id: DocumentId,
operation: Box<RawValue>,
#[serde(default)]
condition: MutationCondition,
#[serde(default)]
idempotency_key: Option<IdempotencyKey>,
}
#[derive(Deserialize)]
struct OperationHeader {
kind: String,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct PutOperation {
kind: String,
source: Box<RawValue>,
#[serde(default)]
routing: Option<String>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct UpdateOperation {
kind: String,
patch: Box<RawValue>,
#[serde(default)]
routing: Option<String>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct DeleteOperation {
kind: String,
}
let raw = RawMutationItem::deserialize(deserializer)?;
let header: OperationHeader = serde_json::from_str(raw.operation.get()).map_err(serde::de::Error::custom)?;
let operation = match header.kind.as_str() {
"put" => {
let decoded: PutOperation =
serde_json::from_str(raw.operation.get()).map_err(serde::de::Error::custom)?;
if decoded.kind != "put" {
return Err(serde::de::Error::custom("mutation operation kind changed"));
}
MutationOperation::Put {
source: decoded.source,
routing: decoded.routing,
}
}
"update" => {
let decoded: UpdateOperation =
serde_json::from_str(raw.operation.get()).map_err(serde::de::Error::custom)?;
if decoded.kind != "update" {
return Err(serde::de::Error::custom("mutation operation kind changed"));
}
MutationOperation::Update {
patch: decoded.patch,
routing: decoded.routing,
}
}
"delete" => {
let decoded: DeleteOperation =
serde_json::from_str(raw.operation.get()).map_err(serde::de::Error::custom)?;
if decoded.kind != "delete" {
return Err(serde::de::Error::custom("mutation operation kind changed"));
}
MutationOperation::Delete
}
other => {
return Err(serde::de::Error::custom(format!(
"unknown mutation operation '{other}'"
)));
}
};
Ok(Self {
id: raw.id,
operation,
condition: raw.condition,
idempotency_key: raw.idempotency_key,
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MutateRequest {
pub format_version: u16,
pub shard_id: Option<ShardId>,
pub mutations: Vec<MutationItem>,
pub visibility: VisibilityMode,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MutationResult {
Indexed,
Created,
Updated,
Deleted,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MutationItemOutcome {
pub id: DocumentId,
pub seq_no: SeqNo,
pub primary_term: PrimaryTerm,
pub result: MutationResult,
pub deduplicated: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MutateResponse {
pub durability_class: DurabilityClass,
pub durability_boundary: DurabilityBoundary,
pub visibility_mode: VisibilityMode,
pub response_stage: WriteStage,
pub shard_tokens: Vec<ShardWriteToken>,
pub outcomes: Vec<MutationItemOutcome>,
pub deduplicated: u64,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RemoteReindexRequest {
pub format_version: u16,
pub source_host: String,
pub source_index: String,
pub destination_index: String,
pub batch_size: usize,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RemoteReindexResponse {
pub format_version: u16,
pub task_id: TaskId,
pub total: u64,
pub created: u64,
pub updated: u64,
pub noops: u64,
pub batches: u64,
pub completed: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ReprocessPipelineSelectionV1 {
pub name: String,
pub expected_pipeline_id: Option<lodestone_types::IngestPipelineId>,
pub expected_revision: Option<lodestone_types::IngestPipelineRevision>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ReprocessByQueryRequestV1 {
pub format_version: u16,
pub action_id: [u8; 32],
pub index: lodestone_types::IndexName,
pub index_id: Option<lodestone_types::IndexId>,
pub query: Query,
pub batch_size: usize,
pub max_docs: u64,
pub proceed_on_conflict: bool,
pub slices: u8,
pub requests_per_second: u32,
pub pipeline: Option<ReprocessPipelineSelectionV1>,
pub wait_for_completion: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ReprocessByQueryResponseV1 {
pub format_version: u16,
pub task_id: TaskId,
pub scanned: u64,
pub updated: u64,
pub noops: u64,
pub version_conflicts: u64,
pub batches: u64,
pub completed: bool,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SearchRequest {
pub format_version: u16,
pub shard_id: Option<ShardId>,
pub query: Query,
#[serde(default)]
pub aggregations: BTreeMap<String, Aggregation>,
#[serde(default)]
pub from: usize,
pub limit: usize,
#[serde(default)]
pub sort: Vec<SortSpec>,
#[serde(default)]
pub search_after: Option<SearchAfter>,
#[serde(default)]
pub freshness: SearchFreshness,
#[serde(default)]
pub read_after: Vec<ShardWriteToken>,
#[serde(default)]
pub pit_id: Option<PitId>,
#[serde(default)]
pub highlight: Option<HighlightSpec>,
#[serde(skip)]
pub execution_highlight_query: Option<Query>,
#[serde(default)]
pub explain: bool,
#[serde(skip)]
pub execution_source_projection: SearchSourceProjection,
#[serde(skip)]
pub execution_aggregation_only: bool,
}
impl Default for SearchRequest {
fn default() -> Self {
Self {
format_version: INTERNAL_REQUEST_FORMAT_VERSION,
shard_id: None,
query: Query::MatchAll,
aggregations: BTreeMap::new(),
from: 0,
limit: default_limit(),
sort: Vec::new(),
search_after: None,
freshness: SearchFreshness::Auto,
read_after: Vec::new(),
pit_id: None,
highlight: None,
execution_highlight_query: None,
explain: false,
execution_source_projection: SearchSourceProjection::ParsedAndExact,
execution_aggregation_only: false,
}
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SearchFreshness {
#[default]
Auto,
Realtime,
Committed,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TextQueryOperator {
#[default]
Or,
And,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TextQueryAnalyzer {
#[default]
Mapping,
Standard,
Simple,
Whitespace,
Keyword,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ZeroTermsQuery {
#[default]
None,
All,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct TextQueryOptions {
#[serde(default)]
pub operator: TextQueryOperator,
#[serde(default)]
pub minimum_should_match: Option<usize>,
#[serde(default)]
pub analyzer: TextQueryAnalyzer,
#[serde(default)]
pub zero_terms_query: ZeroTermsQuery,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MultiMatchMode {
#[default]
BestFields,
MostFields,
Phrase,
PhrasePrefix,
CrossFields,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum NestedScoreMode {
#[default]
Avg,
Max,
Min,
None,
Sum,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct NestedInnerHits {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default)]
pub from: usize,
pub size: usize,
#[serde(default)]
pub sort: Vec<SortSpec>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct MultiMatchField {
pub field: String,
#[serde(default = "default_text_field_boost")]
pub boost: f32,
}
const fn default_text_field_boost() -> f32 {
1.0
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct MultiMatchOptions {
#[serde(default)]
pub mode: MultiMatchMode,
#[serde(default)]
pub operator: TextQueryOperator,
#[serde(default)]
pub minimum_should_match: Option<usize>,
#[serde(default)]
pub analyzer: TextQueryAnalyzer,
#[serde(default)]
pub zero_terms_query: ZeroTermsQuery,
#[serde(default)]
pub slop: u32,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum Query {
#[default]
MatchAll,
MatchNone,
Term {
field: String,
value: serde_json::Value,
},
Terms {
field: String,
values: Vec<serde_json::Value>,
},
Range {
field: String,
#[serde(default)]
gt: Option<serde_json::Value>,
#[serde(default)]
gte: Option<serde_json::Value>,
#[serde(default)]
lt: Option<serde_json::Value>,
#[serde(default)]
lte: Option<serde_json::Value>,
},
Exists {
field: String,
},
Ids {
values: Vec<DocumentId>,
},
Match {
field: String,
text: String,
#[serde(default)]
options: TextQueryOptions,
},
MatchPhrase {
field: String,
text: String,
#[serde(default)]
slop: u32,
},
MultiMatch {
fields: Vec<MultiMatchField>,
text: String,
#[serde(default)]
options: MultiMatchOptions,
},
Prefix {
field: String,
value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
analyzer: Option<TextQueryAnalyzer>,
#[serde(default)]
analyze_wildcard: bool,
},
Fuzzy {
field: String,
value: String,
#[serde(default = "default_fuzzy_distance")]
distance: u8,
#[serde(default, skip_serializing_if = "Option::is_none")]
analyzer: Option<TextQueryAnalyzer>,
},
Wildcard {
field: String,
value: String,
},
Boost {
query: Box<Query>,
boost: f32,
},
Bool {
#[serde(default)]
must: Vec<Query>,
#[serde(default)]
should: Vec<Query>,
#[serde(default)]
filter: Vec<Query>,
#[serde(default)]
must_not: Vec<Query>,
#[serde(default)]
minimum_should_match: Option<usize>,
},
Nested {
path: String,
query: Box<Query>,
#[serde(default)]
score_mode: NestedScoreMode,
#[serde(default)]
ignore_unmapped: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
inner_hits: Option<NestedInnerHits>,
},
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SortOrder {
Asc,
#[default]
Desc,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MissingOrder {
First,
Last,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SortMode {
#[default]
Auto,
Min,
Max,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum SortSpec {
Score {
#[serde(default)]
order: SortOrder,
},
Field {
field: String,
#[serde(default)]
order: SortOrder,
#[serde(default)]
missing: Option<MissingOrder>,
#[serde(default)]
mode: SortMode,
},
NestedField {
path: String,
field: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
filter: Option<Box<Query>>,
#[serde(default)]
order: SortOrder,
#[serde(default)]
missing: Option<MissingOrder>,
#[serde(default)]
mode: SortMode,
},
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SearchAfter {
#[serde(default)]
pub values: Vec<serde_json::Value>,
pub shard_id: ShardId,
pub seq_no: SeqNo,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct HighlightSpec {
pub fields: Vec<String>,
#[serde(default = "default_highlight_fragment_size")]
pub fragment_size: usize,
#[serde(default = "default_highlight_max_fragments")]
pub max_fragments: usize,
#[serde(default)]
pub boundary: FragmentBoundary,
#[serde(default)]
pub offset_encoding: FragmentOffsetEncoding,
#[serde(default)]
pub ranker: FragmentRanker,
}
pub const MAX_HIGHLIGHT_FIELDS: usize = 16;
pub const MAX_HIGHLIGHT_FRAGMENTS_PER_FIELD: usize = 8;
pub const MAX_HIGHLIGHT_FRAGMENT_BYTES: usize = 8 * 1024;
pub const MAX_HIGHLIGHT_TOTAL_FRAGMENT_BYTES: usize = 64 * 1024;
pub const MAX_RESULT_FEATURES: usize = 16;
pub const MAX_RESULT_FEATURE_NAME_BYTES: usize = 128;
pub const MAX_RESULT_FEATURE_HITS: usize = 100;
pub const MAX_DIVERSIFICATION_FIELDS: usize = 4;
pub const MAX_DIVERSIFICATION_PER_GROUP: usize = 64;
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum FragmentBoundary {
#[default]
Word,
Sentence,
Paragraph,
WholeField,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum FragmentOffsetEncoding {
#[default]
Utf8Bytes,
Utf16CodeUnits,
UnicodeCodepoints,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum FragmentRanker {
#[default]
LexicalMatchCountV1,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SearchFragment {
pub field: String,
pub text: String,
pub start: u32,
pub end: u32,
pub offset_encoding: FragmentOffsetEncoding,
pub rank: u32,
pub match_count: u32,
pub truncated: bool,
pub partial: bool,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum NumericFeatureTransform {
Identity,
Log1p,
Clamp { minimum: f64, maximum: f64 },
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct NumericResultFeatureSpec {
pub name: String,
pub field: String,
pub transform: NumericFeatureTransform,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ResultFeatureRequest {
pub top_n: usize,
#[serde(default)]
pub include_branch_components: bool,
#[serde(default)]
pub numeric: Vec<NumericResultFeatureSpec>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ResultDiversification {
pub fields: Vec<String>,
pub max_per_group: usize,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BranchScoreKind {
LexicalScore,
DenseSimilarity,
SparseDotProduct,
LateInteractionMaxsim,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum ResultFeature {
Branch {
branch: String,
score_kind: BranchScoreKind,
rank: u32,
score: f64,
},
Numeric {
name: String,
field: String,
value: f64,
},
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AggregationRange {
#[serde(default)]
pub key: Option<String>,
#[serde(default)]
pub from: Option<f64>,
#[serde(default)]
pub to: Option<f64>,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct HistogramBounds {
#[serde(default)]
pub min: Option<f64>,
#[serde(default)]
pub max: Option<f64>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DateHistogramBounds {
#[serde(default)]
pub min: Option<i64>,
#[serde(default)]
pub max: Option<i64>,
}
const fn default_histogram_min_doc_count() -> u64 {
1
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CompositeOrder {
Asc,
Desc,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(tag = "kind", content = "value", rename_all = "snake_case", deny_unknown_fields)]
pub enum CompositeScalar {
Bool(bool),
I64(i64),
U64(u64),
F64(f64),
String(String),
}
impl CompositeScalar {
#[must_use]
pub fn is_valid(&self) -> bool {
match self {
Self::F64(value) => value.is_finite(),
Self::String(value) => value.len() <= MAX_COMPOSITE_SCALAR_STRING_BYTES,
Self::Bool(_) | Self::I64(_) | Self::U64(_) => true,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum CompositeSource {
Terms {
name: String,
field: String,
order: CompositeOrder,
},
DateHistogram {
name: String,
field: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
fixed_interval_millis: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
calendar_interval: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
time_zone: Option<String>,
offset_millis: i64,
order: CompositeOrder,
},
}
impl CompositeSource {
#[must_use]
pub fn name(&self) -> &str {
match self {
Self::Terms { name, .. } | Self::DateHistogram { name, .. } => name,
}
}
#[must_use]
pub fn field(&self) -> &str {
match self {
Self::Terms { field, .. } | Self::DateHistogram { field, .. } => field,
}
}
#[must_use]
pub const fn order(&self) -> CompositeOrder {
match self {
Self::Terms { order, .. } | Self::DateHistogram { order, .. } => *order,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum Aggregation {
Terms {
field: String,
#[serde(default = "default_aggregation_size")]
size: usize,
#[serde(default)]
aggregations: BTreeMap<String, Aggregation>,
},
Histogram {
field: String,
interval: f64,
#[serde(default = "default_histogram_min_doc_count")]
min_doc_count: u64,
#[serde(default)]
extended_bounds: Option<HistogramBounds>,
#[serde(default)]
hard_bounds: Option<HistogramBounds>,
#[serde(default)]
offset: f64,
#[serde(default)]
missing: Option<f64>,
#[serde(default)]
aggregations: BTreeMap<String, Aggregation>,
},
DateHistogram {
field: String,
#[serde(default)]
fixed_interval_millis: Option<u64>,
#[serde(default)]
calendar_interval: Option<String>,
#[serde(default)]
time_zone: Option<String>,
#[serde(default = "default_histogram_min_doc_count")]
min_doc_count: u64,
#[serde(default)]
extended_bounds: Option<DateHistogramBounds>,
#[serde(default)]
hard_bounds: Option<DateHistogramBounds>,
#[serde(default)]
offset_millis: i64,
#[serde(default)]
missing: Option<i64>,
#[serde(default)]
aggregations: BTreeMap<String, Aggregation>,
},
Composite {
size: usize,
sources: Vec<CompositeSource>,
#[serde(default, skip_serializing_if = "Option::is_none")]
after: Option<EngineApiCompositeContinuation>,
#[serde(default)]
aggregations: BTreeMap<String, Aggregation>,
},
Range {
field: String,
ranges: Vec<AggregationRange>,
#[serde(default)]
aggregations: BTreeMap<String, Aggregation>,
},
Filters {
filters: BTreeMap<String, Query>,
#[serde(default)]
aggregations: BTreeMap<String, Aggregation>,
},
Stats {
field: String,
},
Min {
field: String,
},
Max {
field: String,
},
Avg {
field: String,
},
Sum {
field: String,
},
ValueCount {
field: String,
},
Cardinality {
field: String,
},
ApproximateCardinality {
field: String,
precision: u8,
},
Percentiles {
field: String,
#[serde(default = "default_percentiles")]
percents: Vec<f64>,
},
ApproximatePercentiles {
field: String,
#[serde(default = "default_percentiles")]
percents: Vec<f64>,
precision: u8,
},
Nested {
path: String,
#[serde(default)]
aggregations: BTreeMap<String, Aggregation>,
},
ReverseNested {
#[serde(default, skip_serializing_if = "Option::is_none")]
path: Option<String>,
#[serde(default)]
aggregations: BTreeMap<String, Aggregation>,
},
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct SearchResponse {
pub format_version: u16,
pub total: u64,
pub hits: Vec<SearchHit>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub aggregations: BTreeMap<String, AggregationResult>,
pub freshness: SearchExecutionFreshness,
pub degraded: bool,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct SearchHit {
pub id: DocumentId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub routing: Option<String>,
pub shard_id: ShardId,
pub seq_no: SeqNo,
pub primary_term: PrimaryTerm,
pub score: f32,
pub source: serde_json::Value,
#[serde(skip)]
pub exact_source: Option<Vec<u8>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub sort: Vec<serde_json::Value>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub highlight: BTreeMap<String, Vec<String>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub fragments: Vec<SearchFragment>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub features: Vec<ResultFeature>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub group_values: Vec<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub explanation: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub inner_hits: BTreeMap<String, NestedInnerHitsResult>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct NestedInnerHitsResult {
pub total: u64,
pub hits: Vec<NestedSearchHit>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct NestedSearchHit {
pub path: String,
pub ordinal: u32,
pub score: f32,
pub source: serde_json::Value,
#[serde(skip)]
pub exact_source: Option<Vec<u8>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub sort: Vec<serde_json::Value>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SearchExecutionFreshness {
Realtime,
Committed,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum AggregationResult {
Buckets {
buckets: Vec<AggregationBucket>,
},
Composite {
buckets: Vec<AggregationBucket>,
#[serde(default, skip_serializing_if = "Option::is_none")]
after_key: Option<Vec<CompositeScalar>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
after: Option<EngineApiCompositeContinuation>,
},
Stats {
count: u64,
min: Option<f64>,
max: Option<f64>,
avg: Option<f64>,
sum: f64,
},
Metric {
value: Option<f64>,
},
Percentiles {
values: BTreeMap<String, Option<f64>>,
},
ApproximateCardinality {
value: f64,
algorithm_version: u16,
precision: u8,
state_bytes: u64,
relative_standard_error: f64,
},
ApproximatePercentiles {
values: BTreeMap<String, ApproximatePercentileResult>,
algorithm_version: u16,
precision: u8,
state_bytes: u64,
},
SingleBucket {
doc_count: u64,
aggregations: BTreeMap<String, AggregationResult>,
},
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ApproximatePercentileResult {
pub value: f64,
pub lower_bound: f64,
pub upper_bound: f64,
pub maximum_rank_error: f64,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct AggregationBucket {
pub key: serde_json::Value,
pub doc_count: u64,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub aggregations: BTreeMap<String, AggregationResult>,
}
const fn default_limit() -> usize {
10
}
const fn default_fuzzy_distance() -> u8 {
1
}
const fn default_aggregation_size() -> usize {
10
}
fn default_percentiles() -> Vec<f64> {
vec![1.0, 5.0, 25.0, 50.0, 75.0, 95.0, 99.0]
}
const fn default_highlight_fragment_size() -> usize {
150
}
const fn default_highlight_max_fragments() -> usize {
1
}
#[derive(Debug)]
pub enum ProtocolError {
EmptyRequestId,
RequestIdTooLong(usize),
}
impl ProtocolError {
#[must_use]
pub const fn code(&self) -> &'static str {
match self {
Self::EmptyRequestId => "empty_request_id",
Self::RequestIdTooLong(_) => "request_id_too_long",
}
}
}
impl Display for ProtocolError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyRequestId => formatter.write_str("request_id must not be empty"),
Self::RequestIdTooLong(length) => write!(
formatter,
"request_id is {length} bytes; maximum is {MAX_REQUEST_ID_BYTES}"
),
}
}
}
impl Error for ProtocolError {}
#[cfg(test)]
mod tests {
use super::{ProtocolError, RequestId};
#[test]
fn request_ids_are_bounded() {
assert!(RequestId::new("request-7").is_ok());
assert!(matches!(RequestId::new(""), Err(ProtocolError::EmptyRequestId)));
assert!(matches!(
RequestId::new("x".repeat(super::MAX_REQUEST_ID_BYTES + 1)),
Err(ProtocolError::RequestIdTooLong(_))
));
}
}