use std::collections::HashMap;
use std::ops::Deref;
use std::ops::DerefMut;
use serde::Deserialize;
use serde::Serialize;
use tower::BoxError;
/// The full identifier for an operation in a PQ list consists of an operation
/// ID and an optional client name.
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct FullPersistedQueryOperationId {
/// The operation ID (usually a hash).
pub operation_id: String,
/// The client name associated with the operation; if None, can be any client.
pub client_name: Option<String>,
}
/// A single operation containing an ID and a body.
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ManifestOperation {
/// The operation ID (usually a hash).
pub id: String,
/// The operation body.
pub body: String,
/// The client name associated with the operation. If None, can be any client.
pub client_name: Option<String>,
}
/// The format of each persisted query chunk returned from uplink.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SignedUrlChunk {
pub format: String,
pub version: u64,
pub operations: Vec<ManifestOperation>,
}
impl SignedUrlChunk {
pub fn validate(self) -> Result<Self, BoxError> {
if self.format != "apollo-persisted-query-manifest" {
return Err("chunk format is not 'apollo-persisted-query-manifest'".into());
}
if self.version != 1 {
return Err("persisted query manifest chunk version is not 1".into());
}
Ok(self)
}
pub fn parse_and_validate(raw_chunk: &str) -> Result<Self, BoxError> {
let parsed_chunk =
serde_json::from_str::<SignedUrlChunk>(raw_chunk).map_err(|e| -> BoxError {
format!("Could not parse persisted query manifest chunk: {e}").into()
})?;
parsed_chunk.validate()
}
}
/// An in memory cache of persisted queries.
#[derive(Debug, Clone, Default)]
pub struct PersistedQueryManifest {
inner: HashMap<FullPersistedQueryOperationId, String>,
}
impl PersistedQueryManifest {
/// Add a chunk to the manifest.
pub fn add_chunk(&mut self, chunk: &SignedUrlChunk) {
for operation in &chunk.operations {
self.inner.insert(
FullPersistedQueryOperationId {
operation_id: operation.id.clone(),
client_name: operation.client_name.clone(),
},
operation.body.clone(),
);
}
}
}
impl From<Vec<ManifestOperation>> for PersistedQueryManifest {
fn from(operations: Vec<ManifestOperation>) -> Self {
let mut manifest = PersistedQueryManifest::default();
for operation in operations {
manifest.insert(
FullPersistedQueryOperationId {
operation_id: operation.id,
client_name: operation.client_name,
},
operation.body,
);
}
manifest
}
}
impl Deref for PersistedQueryManifest {
type Target = HashMap<FullPersistedQueryOperationId, String>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for PersistedQueryManifest {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}