phabricator_api/project/
search.rs1use 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}