use std::fmt;
use std::path::PathBuf;
use serde::Deserialize;
use serde::Serialize;
use serde::Serializer;
use serde_bser::value::Value;
use crate::expr::Expr;
#[derive(Deserialize, Debug)]
pub struct GetVersionResponse {
pub version: String,
}
#[derive(Deserialize, Debug)]
pub struct WatchListResponse {
pub roots: Vec<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 From<SettleDurationMs> for i64 {
fn from(val: SettleDurationMs) -> Self {
val.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 From<SyncTimeout> for i64 {
fn from(val: SyncTimeout) -> Self {
match val {
SyncTimeout::Default => 60_000,
SyncTimeout::DisableCookie => 0,
SyncTimeout::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(Deserialize, Serialize, Default, Clone, Debug)]
pub struct TriggerRequest {
pub name: String,
pub command: Vec<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub append_files: bool,
#[serde(skip_serializing_if = "Option::is_none", skip_deserializing)]
pub expression: Option<Expr>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
serialize_with = "TriggerStdinConfig::serialize",
skip_deserializing
)]
pub stdin: Option<TriggerStdinConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stdout: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stderr: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_files_stdin: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chdir: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub relative_root: Option<PathBuf>,
}
#[derive(Clone, Debug)]
pub enum TriggerStdinConfig {
DevNull,
FieldNames(Vec<String>),
NamePerLine,
}
impl Default for TriggerStdinConfig {
fn default() -> Self {
Self::DevNull
}
}
impl TriggerStdinConfig {
fn serialize<S>(this: &Option<Self>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match this {
Some(Self::DevNull) => serializer.serialize_str("/dev/null"),
Some(Self::FieldNames(names)) => serializer.collect_seq(names.iter()),
Some(Self::NamePerLine) => serializer.serialize_str("NAME_PER_LINE"),
None => serializer.serialize_none(),
}
}
}
#[derive(Serialize, Clone, Debug)]
pub struct TriggerCommand(pub &'static str, pub PathBuf, pub TriggerRequest);
#[derive(Deserialize, Debug)]
pub struct TriggerResponse {
pub version: String,
pub disposition: String,
pub triggerid: String,
}
#[derive(Serialize, Clone, Debug)]
pub struct TriggerDelCommand(pub &'static str, pub PathBuf, pub String);
#[derive(Deserialize, Debug)]
pub struct TriggerDelResponse {
pub version: String,
pub deleted: bool,
pub trigger: String,
}
#[derive(Serialize, Clone, Debug)]
pub struct TriggerListCommand(pub &'static str, pub PathBuf);
#[derive(Deserialize, Debug)]
pub struct TriggerListResponse {
pub version: String,
pub triggers: Vec<TriggerRequest>,
}
#[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 From<ClockSpec> for Value {
fn from(val: ClockSpec) -> Self {
match val {
ClockSpec::StringClock(st) => Value::Utf8String(st),
ClockSpec::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 fmt::Display for FileType {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
f.write_str(&String::from(*self))
}
}
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 From<FileType> for String {
fn from(val: FileType) -> Self {
match val {
FileType::BlockSpecial => "b",
FileType::CharSpecial => "c",
FileType::Directory => "d",
FileType::Regular => "f",
FileType::Fifo => "p",
FileType::Symlink => "l",
FileType::Socket => "s",
FileType::SolarisDoor => "D",
FileType::Unknown => "?",
}
.to_string()
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use serde_bser::value::Value;
use super::*;
use crate::bunser;
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);
}
}