use crate::expr::Expr;
use serde::{Deserialize, Serialize};
use serde_bser::value::Value;
use std::path::PathBuf;
#[derive(Deserialize, Debug)]
pub struct GetSockNameResponse {
pub version: String,
pub sockname: Option<PathBuf>,
pub error: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct ClockResponse {
pub version: String,
pub clock: ClockSpec,
}
#[derive(Serialize, Debug)]
pub struct ClockRequest(pub &'static str, pub PathBuf, pub ClockRequestParams);
#[derive(Serialize, Debug)]
pub struct ClockRequestParams {
#[serde(skip_serializing_if = "SyncTimeout::is_disabled", default)]
pub sync_timeout: SyncTimeout,
}
#[derive(Serialize, Debug)]
pub struct GetConfigRequest(pub &'static str, pub PathBuf);
#[derive(Deserialize, Debug)]
pub struct WatchmanConfig {
pub ignore_dirs: Option<Vec<PathBuf>>,
}
#[derive(Deserialize, Debug)]
pub struct GetConfigResponse {
pub version: String,
pub config: WatchmanConfig,
}
#[derive(Serialize, Debug)]
pub struct StateEnterLeaveParams<'a> {
pub name: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
#[serde(skip_serializing_if = "SyncTimeout::is_default")]
pub sync_timeout: SyncTimeout,
}
#[derive(Serialize, Debug)]
pub struct StateEnterLeaveRequest<'a>(pub &'static str, pub PathBuf, pub StateEnterLeaveParams<'a>);
#[derive(Deserialize, Debug)]
pub struct StateEnterLeaveResponse {
pub version: String,
}
#[derive(Serialize, Debug)]
pub struct WatchProjectRequest(pub &'static str, pub PathBuf);
#[derive(Deserialize, Debug)]
pub struct WatchProjectResponse {
pub version: String,
pub relative_path: Option<PathBuf>,
pub watch: PathBuf,
pub watcher: String,
}
#[derive(Serialize, Clone, Debug)]
#[serde(untagged)]
pub enum PathGeneratorElement {
RecursivePath(PathBuf),
ConstrainedDepth { path: PathBuf, depth: i64 },
}
#[derive(Serialize, Clone, Debug)]
pub struct QueryRequest(pub &'static str, pub PathBuf, pub QueryRequestCommon);
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_false(v: &bool) -> bool {
!*v
}
#[derive(Serialize, Clone, Debug)]
#[serde(into = "i64")]
pub struct SettleDurationMs(pub std::time::Duration);
impl From<std::time::Duration> for SettleDurationMs {
fn from(duration: std::time::Duration) -> Self {
Self(duration)
}
}
impl Into<i64> for SettleDurationMs {
fn into(self) -> i64 {
self.0.as_millis() as i64
}
}
#[derive(Serialize, Clone, Debug)]
#[serde(into = "i64")]
pub enum SyncTimeout {
Default,
DisableCookie,
Duration(std::time::Duration),
}
impl Default for SyncTimeout {
fn default() -> Self {
Self::Default
}
}
impl SyncTimeout {
fn is_default(&self) -> bool {
match self {
Self::Default => true,
_ => false,
}
}
fn is_disabled(&self) -> bool {
match self {
Self::DisableCookie => true,
_ => false,
}
}
}
impl From<std::time::Duration> for SyncTimeout {
fn from(duration: std::time::Duration) -> Self {
let millis = duration.as_millis();
if millis == 0 {
Self::DisableCookie
} else {
Self::Duration(duration)
}
}
}
impl Into<i64> for SyncTimeout {
fn into(self) -> i64 {
match self {
Self::Default => 60_000,
Self::DisableCookie => 0,
Self::Duration(d) => d.as_millis() as i64,
}
}
}
#[derive(Serialize, Default, Clone, Debug)]
pub struct QueryRequestCommon {
#[serde(skip_serializing_if = "Option::is_none")]
pub glob: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "is_false")]
pub glob_noescape: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub glob_includedotfiles: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<Vec<PathGeneratorElement>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suffix: Option<Vec<PathBuf>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub since: Option<Clock>,
#[serde(skip_serializing_if = "Option::is_none")]
pub relative_root: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expression: Option<Expr>,
pub fields: Vec<&'static str>,
#[serde(default, skip_serializing_if = "is_false")]
pub empty_on_fresh_instance: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub omit_changed_files: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub fail_if_no_saved_state: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub case_sensitive: bool,
#[serde(skip_serializing_if = "SyncTimeout::is_default", default)]
pub sync_timeout: SyncTimeout,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub settle_period: Option<SettleDurationMs>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub settle_timeout: Option<SettleDurationMs>,
#[serde(default, skip_serializing_if = "is_false")]
pub dedup_results: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub lock_timeout: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_id: Option<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub always_include_directories: bool,
}
#[derive(Deserialize, Clone, Debug)]
pub struct QueryDebugInfo {
pub cookie_files: Option<Vec<PathBuf>>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct QueryResult<F>
where
F: std::fmt::Debug + Clone,
{
pub version: String,
#[serde(default)]
pub is_fresh_instance: bool,
pub files: Option<Vec<F>>,
pub clock: Clock,
#[serde(rename = "state-enter")]
#[doc(hidden)]
pub state_enter: Option<String>,
#[serde(rename = "state-leave")]
#[doc(hidden)]
pub state_leave: Option<String>,
#[serde(rename = "metadata")]
pub state_metadata: Option<Value>,
#[serde(rename = "saved-state-info")]
pub saved_state_info: Option<Value>,
pub debug: Option<QueryDebugInfo>,
}
#[derive(Serialize, Default, Clone, Debug)]
pub struct SubscribeRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub since: Option<Clock>,
#[serde(skip_serializing_if = "Option::is_none")]
pub relative_root: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expression: Option<Expr>,
pub fields: Vec<&'static str>,
#[serde(default, skip_serializing_if = "is_false")]
pub empty_on_fresh_instance: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub case_sensitive: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub defer_vcs: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub defer: Vec<&'static str>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub drop: Vec<&'static str>,
}
#[derive(Serialize, Clone, Debug)]
pub struct SubscribeCommand(
pub &'static str,
pub PathBuf,
pub String,
pub SubscribeRequest,
);
#[derive(Deserialize, Debug)]
pub struct SubscribeResponse {
pub version: String,
#[allow(unused)] subscribe: String,
pub clock: Clock,
#[serde(default, rename = "asserted-states")]
pub asserted_states: Vec<String>,
#[serde(rename = "saved-state-info")]
pub saved_state_info: Option<Value>,
}
#[derive(Serialize, Debug)]
pub struct Unsubscribe(pub &'static str, pub PathBuf, pub String);
#[derive(Deserialize, Debug)]
pub struct UnsubscribeResponse {
pub version: String,
pub unsubscribe: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Clock {
Spec(ClockSpec),
ScmAware(FatClockData),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum ClockSpec {
StringClock(String),
UnixTimestamp(i64),
}
impl Default for ClockSpec {
fn default() -> Self {
Self::null()
}
}
impl ClockSpec {
pub fn null() -> Self {
Self::StringClock("c:0:0".to_string())
}
pub fn named_cursor(cursor: &str) -> Self {
Self::StringClock(format!("n:{}", cursor))
}
pub fn unix_timestamp(time_t: i64) -> Self {
Self::UnixTimestamp(time_t)
}
}
impl Into<Value> for ClockSpec {
fn into(self) -> Value {
match self {
Self::StringClock(st) => Value::Utf8String(st),
Self::UnixTimestamp(ts) => Value::Integer(ts),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FatClockData {
pub clock: ClockSpec,
#[serde(skip_serializing_if = "Option::is_none")]
pub scm: Option<ScmAwareClockData>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ScmAwareClockData {
#[serde(skip_serializing_if = "Option::is_none")]
pub mergebase: Option<String>,
#[serde(rename = "mergebase-with")]
#[serde(skip_serializing_if = "Option::is_none")]
pub mergebase_with: Option<String>,
#[serde(rename = "saved-state")]
#[serde(skip_serializing_if = "Option::is_none")]
pub saved_state: Option<SavedStateClockData>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SavedStateClockData {
#[serde(skip_serializing_if = "Option::is_none")]
pub storage: Option<String>,
#[serde(rename = "commit-id")]
#[serde(skip_serializing_if = "Option::is_none")]
pub commit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<Value>,
}
#[derive(Deserialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum ContentSha1Hex {
Hash(String),
Error { error: String },
None,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[serde(from = "String", into = "String")]
pub enum FileType {
BlockSpecial,
CharSpecial,
Directory,
Regular,
Fifo,
Symlink,
Socket,
SolarisDoor,
Unknown,
}
impl std::string::ToString for FileType {
fn to_string(&self) -> String {
(*self).into()
}
}
impl From<String> for FileType {
fn from(s: String) -> Self {
match s.as_ref() {
"b" => Self::BlockSpecial,
"c" => Self::CharSpecial,
"d" => Self::Directory,
"f" => Self::Regular,
"p" => Self::Fifo,
"l" => Self::Symlink,
"s" => Self::Socket,
"D" => Self::SolarisDoor,
"?" => Self::Unknown,
unknown => panic!("Watchman Server returned impossible file type {}", unknown),
}
}
}
impl Into<String> for FileType {
fn into(self) -> String {
match self {
Self::BlockSpecial => "b",
Self::CharSpecial => "c",
Self::Directory => "d",
Self::Regular => "f",
Self::Fifo => "p",
Self::Symlink => "l",
Self::Socket => "s",
Self::SolarisDoor => "D",
Self::Unknown => "?",
}
.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bunser;
use serde_bser::value::Value;
use std::collections::HashMap;
fn convert_bser_value<T>(input: Value) -> T
where
T: serde::de::DeserializeOwned,
{
let binary = serde_bser::ser::serialize(Vec::new(), input).unwrap();
bunser(&binary).unwrap()
}
#[test]
fn test_content_sha1hex_hash() {
let value: ContentSha1Hex =
convert_bser_value("e820c2c600a36f05ba905cf1bf32c4834e804e22".into());
assert_eq!(
value,
ContentSha1Hex::Hash("e820c2c600a36f05ba905cf1bf32c4834e804e22".into())
);
}
#[test]
fn test_content_sha1hex_error() {
let mut error_obj: HashMap<String, Value> = HashMap::new();
error_obj.insert("error".to_string(), "out of cookies".into());
let value: ContentSha1Hex = convert_bser_value(error_obj.into());
assert_eq!(
value,
ContentSha1Hex::Error {
error: "out of cookies".into()
}
);
}
#[test]
fn test_content_sha1hex_none() {
let value: ContentSha1Hex = convert_bser_value(Value::Null);
assert_eq!(value, ContentSha1Hex::None);
}
}