use crate::control::{Projection, ProjectionBinding, SchemaDef, SchemaSource};
use crate::query::QueryError;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ProjectionInfo {
pub projection: Projection,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub bindings: Vec<ProjectionBinding>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SchemaInfo {
pub schema: SchemaDef,
#[serde(default)]
pub dropped: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GetProjection {
pub v: u32,
pub id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ListProjections {
pub v: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub topics: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name_contains: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id_prefix: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub search: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GetSchema {
pub v: u32,
pub id: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stream: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ListSchemas {
pub v: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name_contains: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stream: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RegisterSchema {
pub v: u32,
pub source: SchemaSource,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stream: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DecodeRecord {
pub v: u32,
pub id: u32,
#[serde(with = "crate::encoding::bin_bytes")]
pub payload: Vec<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stream: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub enum BrowseReply {
Ok(BrowseOutcome),
Err(QueryError),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub enum BrowseOutcome {
Projections(Vec<ProjectionInfo>),
Projection(Option<ProjectionInfo>),
Schemas(Vec<SchemaInfo>),
Schema(Option<SchemaInfo>),
SchemaRegistered(u32),
Decoded(Option<serde_json::Value>),
}
#[cfg(all(test, feature = "cbor"))]
mod tests {
use super::*;
use crate::codes::QUERY_OP_VERSION;
use crate::content::ContentType;
use crate::control::ProjectionBinding;
use crate::framing::{decode_named, encode_named};
#[test]
fn given_a_browse_reply_when_round_tripped_then_should_preserve_projection_details() {
let info = ProjectionInfo {
projection: Projection::builder("reading.v1")
.name("reading")
.version(1)
.content_type(ContentType::Json)
.fields(["reading_id", "cpu"])
.build(),
bindings: vec![
ProjectionBinding::builder()
.source("fleet", "readings")
.allow("reading.v1")
.default_projection("reading.v1")
.index("readings_rows")
.build(),
],
};
let reply = BrowseReply::Ok(BrowseOutcome::Projections(vec![info]));
let bytes = encode_named(&reply).expect("the reply serializes");
let back: BrowseReply = decode_named(&bytes).expect("the reply deserializes");
let BrowseReply::Ok(BrowseOutcome::Projections(list)) = back else {
panic!("expected an Ok(Projections) browse reply");
};
assert_eq!(list.len(), 1);
assert_eq!(list[0].projection.id.as_str(), "reading.v1");
assert_eq!(list[0].projection.extraction.fields.len(), 2);
assert_eq!(list[0].bindings[0].index, "readings_rows");
}
#[test]
fn given_a_decode_record_when_round_tripped_then_should_preserve_payload_bytes() {
let request = DecodeRecord {
v: QUERY_OP_VERSION,
id: 7,
payload: vec![0xff, 0x00, 0x10],
stream: None,
};
let bytes = encode_named(&request).expect("serializes");
let back: DecodeRecord = decode_named(&bytes).expect("deserializes");
assert_eq!(back.id, 7);
assert_eq!(back.payload, vec![0xff, 0x00, 0x10]);
assert_eq!(back.stream, None);
}
#[test]
fn given_schema_requests_with_a_stream_when_round_tripped_then_should_preserve_it() {
let get = GetSchema {
v: QUERY_OP_VERSION,
id: 7,
stream: Some("acme".to_owned()),
};
let back: GetSchema =
decode_named(&encode_named(&get).expect("serializes")).expect("deserializes");
assert_eq!((back.id, back.stream.as_deref()), (7, Some("acme")));
let list = ListSchemas {
v: QUERY_OP_VERSION,
name_contains: None,
stream: Some("acme".to_owned()),
};
let back: ListSchemas =
decode_named(&encode_named(&list).expect("serializes")).expect("deserializes");
assert_eq!(back.stream.as_deref(), Some("acme"));
let register = RegisterSchema {
v: QUERY_OP_VERSION,
source: SchemaSource::JsonSchema {
schema: "{}".to_owned(),
},
name: None,
version: None,
stream: Some("acme".to_owned()),
};
let back: RegisterSchema =
decode_named(&encode_named(®ister).expect("serializes")).expect("deserializes");
assert_eq!(back.stream.as_deref(), Some("acme"));
let decode = DecodeRecord {
v: QUERY_OP_VERSION,
id: 7,
payload: vec![1],
stream: Some("acme".to_owned()),
};
let back: DecodeRecord =
decode_named(&encode_named(&decode).expect("serializes")).expect("deserializes");
assert_eq!(back.stream.as_deref(), Some("acme"));
}
#[test]
fn given_schema_requests_without_a_stream_when_encoded_then_should_omit_it() {
let get = GetSchema {
v: QUERY_OP_VERSION,
id: 7,
stream: None,
};
let list = ListSchemas {
v: QUERY_OP_VERSION,
name_contains: None,
stream: None,
};
for json in [
serde_json::to_string(&get).expect("serializes"),
serde_json::to_string(&list).expect("serializes"),
] {
assert!(
!json.contains("stream"),
"an unset stream is omitted: {json}"
);
}
}
}