phabricator_api/project/
search.rs

1use crate::types::Phid;
2use crate::utils::deserialize_timestamp;
3use crate::ApiRequest;
4use chrono::DateTime;
5use chrono::Utc;
6use serde::Deserialize;
7use serde::Serialize;
8use serde_json::value::Value as JsonValue;
9use std::collections::HashMap;
10use std::default::Default;
11use std::ops::Not;
12
13pub trait Serializable: erased_serde::Serialize + std::fmt::Debug {}
14impl<T: erased_serde::Serialize + std::fmt::Debug> Serializable for T {}
15erased_serde::serialize_trait_object!(Serializable);
16
17#[derive(Serialize, Debug, Default)]
18pub struct Constraints {
19    pub ids: Option<Vec<u32>>,
20    pub slugs: Option<Vec<String>>,
21    pub query: Option<String>,
22    pub phids: Option<Vec<Phid>>,
23    #[serde(flatten)]
24    pub custom: Option<HashMap<String, Box<dyn Serializable + Send + Sync>>>,
25}
26
27#[derive(Serialize, Debug, Default)]
28pub struct Attachments {
29    #[serde(skip_serializing_if = "<&bool>::not")]
30    pub members: bool,
31    #[serde(skip_serializing_if = "<&bool>::not")]
32    pub watchers: bool,
33    #[serde(skip_serializing_if = "<&bool>::not")]
34    pub ancestors: bool,
35}
36
37pub type Search = crate::types::Search<Attachments, Constraints>;
38pub type SearchCursor<'a> = crate::types::SearchCursor<'a, Attachments, Constraints>;
39
40pub type SearchData = crate::types::SearchData<AttachmentsResult, Fields>;
41pub type SearchResult = crate::types::SearchResult<AttachmentsResult, Fields>;
42
43#[derive(Deserialize, Debug)]
44pub struct Icon {
45    pub key: String,
46    pub name: String,
47    pub icon: String,
48}
49
50#[derive(Deserialize, Debug)]
51pub struct Color {
52    pub key: String,
53    pub name: Option<String>,
54}
55
56#[derive(Deserialize, Debug)]
57pub struct Fields {
58    pub name: String,
59    pub slug: Option<String>,
60    pub description: Option<String>,
61    milestone: Option<JsonValue>,
62    depth: u32,
63    parent: Option<JsonValue>,
64    pub icon: Icon,
65    pub color: Color,
66    #[serde(rename = "spacePHID")]
67    pub space: Option<Phid>,
68    #[serde(rename = "dateCreated", deserialize_with = "deserialize_timestamp")]
69    pub created: DateTime<Utc>,
70    #[serde(rename = "dateModified", deserialize_with = "deserialize_timestamp")]
71    pub modified: DateTime<Utc>,
72    policy: JsonValue,
73}
74
75#[derive(Deserialize, Debug)]
76pub struct AttachmentsResult {}
77
78impl ApiRequest for Search {
79    type Reply = SearchResult;
80    const ROUTE: &'static str = "api/project.search";
81}
82
83impl ApiRequest for SearchCursor<'_> {
84    type Reply = SearchResult;
85    const ROUTE: &'static str = "api/project.search";
86}
87
88#[cfg(test)]
89mod test {
90    use super::*;
91    use phabricator_mock::project::Project;
92    use phabricator_mock::PhabMockServer;
93
94    fn compare_project(mock: &Project, response: &SearchData) {
95        assert_eq!(mock.id, response.id);
96        assert_eq!(mock.phid, response.phid.0.as_str());
97    }
98
99    #[tokio::test]
100    async fn simple() {
101        let m = PhabMockServer::start().await;
102        let project = phabricator_mock::project()
103            .id(25)
104            .name("Project")
105            .build()
106            .unwrap();
107        m.add_project(project.clone());
108
109        let client = crate::Client::new(m.uri(), m.token().to_string());
110        let s = Search {
111            constraints: Constraints {
112                ids: Some(vec![25]),
113                ..Default::default()
114            },
115            ..Default::default()
116        };
117
118        let r = client.request(&s).await.unwrap();
119        assert_eq!(r.data.len(), 1);
120
121        assert_eq!(1, r.data.len());
122        let project = r.data.iter().find(|d| d.id == 25).unwrap();
123        let mock_project = m.get_project(25).unwrap();
124
125        compare_project(&mock_project, project);
126    }
127
128    #[tokio::test]
129    async fn no_result() {
130        let m = PhabMockServer::start().await;
131        let client = crate::Client::new(m.uri(), m.token().to_string());
132
133        let s = Search {
134            constraints: Constraints {
135                ids: Some(vec![100, 200]),
136                ..Default::default()
137            },
138            ..Default::default()
139        };
140
141        let r = client.request(&s).await.unwrap();
142        assert_eq!(0, r.data.len());
143    }
144}