skytool 0.1.0-pre.2

an experimental API client for BlueSky / ATProto
Documentation
use std::str::FromStr;

pub(crate) const LONG_HELP: &str = "
Accepts RFC6901 (JSON Pointer), RFC9535 (JSONPath), or JMESPath syntax.

If the expression starts with '/', it is a JSON Pointer.
If the expression starts with '~', _the rest_ is a JMESPath expression.
Otherwise, it is a JSONPath expression.
";

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
#[error(transparent)]
#[remain::sorted]
pub(crate) enum Error {
  ExactlyOne(#[from] serde_json_path::ExactlyOneError),
  Jmespath(#[from] jmespath::JmespathError),
  Json(#[from] serde_json::Error),
  JsonPath(#[from] serde_json_path::ParseError),
  JsonPointer(#[from] jsonptr::ParseError),
  Resolve(#[from] jsonptr::resolve::ResolveError),
}

#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub(crate) enum Query {
  #[serde(skip)]
  JMESPath(jmespath::Expression<'static>),
  #[serde(rename = "jsonptr")]
  RFC6901(jsonptr::PointerBuf),
  #[serde(rename = "jsonpath")]
  RFC9535(serde_json_path::JsonPath),
}

impl Query {
  pub(crate) fn evaluate<'value>(&self, value: &serde_json::Value) -> Result<serde_json::Value, Error> {
    match self {
      Self::RFC6901(jsonptr) => Ok(jsonptr.resolve(value)?.to_owned()),
      Self::RFC9535(jsonpath) => Ok(jsonpath.query(value).exactly_one()?.to_owned()),
      Self::JMESPath(jmespath) => Ok(serde_json::to_value(jmespath.search(value)?)?),
    }
  }
}

impl FromStr for Query {
  type Err = Error;
  fn from_str(s: &str) -> Result<Self, Self::Err> {
    let query = if s.is_empty() || s.starts_with('/') {
      Self::RFC6901(s.parse()?)
    } else {
      match s.strip_prefix("~") {
        Some(expression) => Self::JMESPath(jmespath::compile(expression)?),
        None => Self::RFC9535(s.parse()?),
      }
    };

    Ok(query)
  }
}