use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Query {
pub path: Vec<String>,
#[serde(default)]
pub args: Vec<serde_json::Value>,
}
impl Query {
#[must_use]
pub fn field(path: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self {
path: path.into_iter().map(Into::into).collect(),
args: vec![],
}
}
#[must_use]
pub fn call(
path: impl IntoIterator<Item = impl Into<String>>,
args: impl IntoIterator<Item = serde_json::Value>,
) -> Self {
Self {
path: path.into_iter().map(Into::into).collect(),
args: args.into_iter().collect(),
}
}
}
pub type QueryResult = Result<serde_json::Value, QueryError>;
#[derive(Debug, Clone, Serialize, Deserialize, Error, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum QueryError {
#[error("unknown field: {field}")]
UnknownField { field: String },
#[error("unknown method: {method}")]
UnknownMethod { method: String },
#[error("type mismatch at {path}: expected {expected}, got {actual}")]
TypeMismatch {
path: String,
expected: String,
actual: String,
},
#[error("argument count mismatch: expected {expected}, got {actual}")]
BadArity { expected: usize, actual: usize },
#[error("internal error: {reason}")]
Internal { reason: String },
}
impl QueryError {
#[must_use]
pub fn unknown_field(field: impl Into<String>) -> Self {
Self::UnknownField {
field: field.into(),
}
}
#[must_use]
pub fn unknown_method(method: impl Into<String>) -> Self {
Self::UnknownMethod {
method: method.into(),
}
}
#[must_use]
pub fn internal(reason: impl Into<String>) -> Self {
Self::Internal {
reason: reason.into(),
}
}
}
pub trait Introspect: Send + Sync {
fn query(&self, q: &Query) -> QueryResult;
fn schema(&self) -> &'static [&'static str] {
&[]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn query_field_constructor() {
let q = Query::field(["sessions"]);
assert_eq!(q.path, vec!["sessions".to_string()]);
assert!(q.args.is_empty());
}
#[test]
fn query_call_constructor() {
let q = Query::call(["snapshot_grid"], [serde_json::json!("sid1")]);
assert_eq!(q.path, vec!["snapshot_grid".to_string()]);
assert_eq!(q.args, vec![serde_json::json!("sid1")]);
}
#[test]
fn query_serde_roundtrip() {
let q = Query::call(["nested", "field"], [serde_json::json!(42)]);
let json = serde_json::to_string(&q).unwrap();
let parsed: Query = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, q);
}
#[test]
fn query_error_serde() {
let e = QueryError::unknown_field("foo");
let json = serde_json::to_string(&e).unwrap();
assert!(json.contains("unknown-field"));
assert!(json.contains("foo"));
let parsed: QueryError = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, e);
}
#[test]
fn query_error_display() {
assert_eq!(
QueryError::unknown_field("bar").to_string(),
"unknown field: bar"
);
assert_eq!(
QueryError::TypeMismatch {
path: "a.b".into(),
expected: "u64".into(),
actual: "string".into(),
}
.to_string(),
"type mismatch at a.b: expected u64, got string"
);
}
}