use serde::{de::DeserializeOwned, Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub struct Revision(Option<i64>);
impl Revision {
pub fn as_i64(&self) -> Option<i64> {
self.0
}
}
impl std::fmt::Display for Revision {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
Some(n) => write!(f, "{}", n),
None => write!(f, ""),
}
}
}
impl AsRef<Option<i64>> for Revision {
fn as_ref(&self) -> &Option<i64> {
&self.0
}
}
impl Revision {
pub const HEAD: Revision = Revision(Some(-1));
pub const INIT: Revision = Revision(Some(1));
pub const DEFAULT: Revision = Revision(None);
pub fn from(i: i64) -> Self {
Revision(Some(i))
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Author {
pub name: String,
pub email: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Project {
pub name: String,
pub creator: Author,
pub url: Option<String>,
pub created_at: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Repository {
pub name: String,
pub creator: Author,
pub head_revision: Revision,
pub url: Option<String>,
pub created_at: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[serde(tag = "type", content = "content")]
pub enum EntryContent {
Json(serde_json::Value),
Text(String),
Directory,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Entry {
pub path: String,
#[serde(flatten)]
pub content: EntryContent,
pub revision: Revision,
pub url: String,
pub modified_at: Option<String>,
}
impl Entry {
pub fn entry_type(&self) -> EntryType {
match self.content {
EntryContent::Json(_) => EntryType::Json,
EntryContent::Text(_) => EntryType::Text,
EntryContent::Directory => EntryType::Directory,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum EntryType {
Json,
Text,
Directory,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ListEntry {
pub path: String,
pub r#type: EntryType,
}
#[derive(Debug, PartialEq, Eq)]
pub enum QueryType {
Identity,
IdentityJson,
IdentityText,
JsonPath(Vec<String>),
}
#[derive(Debug)]
pub struct Query {
pub(crate) path: String,
pub(crate) r#type: QueryType,
}
impl Query {
fn normalize_path(path: &str) -> String {
if path.starts_with('/') {
path.to_owned()
} else {
format!("/{}", path)
}
}
pub fn identity(path: &str) -> Option<Self> {
if path.is_empty() {
return None;
}
Some(Query {
path: Self::normalize_path(path),
r#type: QueryType::Identity,
})
}
pub fn of_text(path: &str) -> Option<Self> {
if path.is_empty() {
return None;
}
Some(Query {
path: Self::normalize_path(path),
r#type: QueryType::IdentityText,
})
}
pub fn of_json(path: &str) -> Option<Self> {
if path.is_empty() {
return None;
}
Some(Query {
path: Self::normalize_path(path),
r#type: QueryType::IdentityJson,
})
}
pub fn of_json_path(path: &str, exprs: Vec<String>) -> Option<Self> {
if !path.to_lowercase().ends_with("json") {
return None;
}
if exprs.iter().any(|expr| expr.is_empty()) {
return None;
}
Some(Query {
path: Self::normalize_path(path),
r#type: QueryType::JsonPath(exprs),
})
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[serde(tag = "markup", content = "detail")]
pub enum CommitDetail {
Markdown(String),
Plaintext(String),
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CommitMessage {
pub summary: String,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub detail: Option<CommitDetail>,
}
impl CommitMessage {
pub fn only_summary(summary: &str) -> Self {
CommitMessage {
summary: summary.to_owned(),
detail: None,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PushResult {
pub revision: Revision,
pub pushed_at: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Commit {
pub revision: Revision,
pub author: Author,
pub commit_message: CommitMessage,
pub pushed_at: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[serde(tag = "type", content = "content")]
pub enum ChangeContent {
UpsertJson(serde_json::Value),
UpsertText(String),
Remove,
Rename(String),
ApplyJsonPatch(serde_json::Value),
ApplyTextPatch(String),
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Change {
pub path: String,
#[serde(flatten)]
pub content: ChangeContent,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct WatchFileResult {
pub revision: Revision,
pub entry: Entry,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct WatchRepoResult {
pub revision: Revision,
}
pub(crate) trait Watchable: DeserializeOwned + Send {
fn revision(&self) -> Revision;
}
impl Watchable for WatchFileResult {
fn revision(&self) -> Revision {
self.revision
}
}
impl Watchable for WatchRepoResult {
fn revision(&self) -> Revision {
self.revision
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_query_identity() {
let query = Query::identity("/a.json").unwrap();
assert_eq!(query.path, "/a.json");
assert_eq!(query.r#type, QueryType::Identity);
}
#[test]
fn test_query_identity_auto_fix_path() {
let query = Query::identity("a.json").unwrap();
assert_eq!(query.path, "/a.json");
assert_eq!(query.r#type, QueryType::Identity);
}
#[test]
fn test_query_reject_empty_path() {
let query = Query::identity("");
assert!(query.is_none());
}
}