1use super::client::DiscourseClient;
2use super::error::http_error;
3use super::models::{
4 CategoriesResponse, CategoryDefinition, CategoryDefinitionsResponse, CategoryInfo,
5 CategoryResponse, CreateCategoryResponse,
6};
7use anyhow::{Context, Result, anyhow};
8use reqwest::StatusCode;
9use serde_json::Value;
10use std::collections::HashMap;
11
12impl DiscourseClient {
13 pub fn fetch_category(&self, category_id: u64) -> Result<CategoryResponse> {
15 let path = format!("/c/{}.json", category_id);
16 let response = self.get(&path)?;
17 let status = response.status();
18 let text = response.text().context("reading category response body")?;
19 if !status.is_success() {
20 if status == StatusCode::NOT_FOUND {
21 return Err(anyhow!("category not found: {}", category_id));
22 }
23 return Err(http_error("category request", status, &text));
24 }
25 let body: CategoryResponse =
26 serde_json::from_str(&text).context("reading category json")?;
27 Ok(body)
28 }
29
30 pub fn fetch_categories(&self) -> Result<Vec<CategoryInfo>> {
32 let response = self.get("/categories.json?include_subcategories=true")?;
33 let status = response.status();
34 let text = response
35 .text()
36 .context("reading categories response body")?;
37 if !status.is_success() {
38 return Err(http_error("categories request", status, &text));
39 }
40 let body: CategoriesResponse =
41 serde_json::from_str(&text).context("reading categories json")?;
42 let mut categories = body.category_list.categories;
43 if let Ok(site_categories) = self.fetch_site_categories() {
44 let mut seen = HashMap::new();
45 for (idx, cat) in categories.iter().enumerate() {
46 if let Some(id) = cat.id {
47 seen.insert(id, idx);
48 }
49 }
50 for cat in site_categories {
51 if let Some(id) = cat.id
52 && !seen.contains_key(&id)
53 {
54 categories.push(cat);
55 }
56 }
57 }
58 Ok(categories)
59 }
60
61 pub fn create_category(&self, category: &CategoryInfo) -> Result<u64> {
63 let mut payload = vec![("name", category.name.clone())];
64 if !category.slug.is_empty() {
65 payload.push(("slug", category.slug.clone()));
66 }
67 if let Some(color) = category.color.clone() {
68 payload.push(("color", color));
69 }
70 if let Some(text_color) = category.text_color.clone() {
71 payload.push(("text_color", text_color));
72 }
73 let response = self.send_retrying(|| Ok(self.post("/categories")?.form(&payload)))?;
74 let status = response.status();
75 let text = response.text().context("reading category response body")?;
76 if !status.is_success() {
77 return Err(http_error("create category request", status, &text));
78 }
79 let body: CreateCategoryResponse =
80 serde_json::from_str(&text).context("reading category response")?;
81 Ok(body.category.id)
82 }
83
84 pub fn fetch_category_definitions(&self) -> Result<Vec<CategoryDefinition>> {
89 let response =
90 self.get("/categories.json?show_permissions=true&include_subcategories=true")?;
91 let status = response.status();
92 let text = response
93 .text()
94 .context("reading category definitions response body")?;
95 if !status.is_success() {
96 return Err(http_error("category definitions request", status, &text));
97 }
98 let body: CategoryDefinitionsResponse =
99 serde_json::from_str(&text).context("reading category definitions json")?;
100 Ok(body.category_list.categories)
101 }
102
103 pub fn create_category_def(&self, params: &[(String, String)]) -> Result<u64> {
108 let response = self.send_retrying(|| Ok(self.post("/categories")?.form(params)))?;
109 let status = response.status();
110 let text = response
111 .text()
112 .context("reading create category response body")?;
113 if !status.is_success() {
114 return Err(http_error("create category request", status, &text));
115 }
116 let body: CreateCategoryResponse =
117 serde_json::from_str(&text).context("reading create category response")?;
118 Ok(body.category.id)
119 }
120
121 pub fn update_category(&self, id: u64, params: &[(String, String)]) -> Result<()> {
124 let path = format!("/categories/{}.json", id);
125 let response = self.send_retrying(|| Ok(self.put(&path)?.form(params)))?;
126 let status = response.status();
127 let text = response
128 .text()
129 .context("reading update category response body")?;
130 if !status.is_success() {
131 return Err(http_error("update category request", status, &text));
132 }
133 Ok(())
134 }
135
136 fn fetch_site_categories(&self) -> Result<Vec<CategoryInfo>> {
137 let response = self.get("/site.json")?;
138 let status = response.status();
139 let text = response.text().context("reading site.json response body")?;
140 if !status.is_success() {
141 return Err(http_error("site.json request", status, &text));
142 }
143 let value: Value = serde_json::from_str(&text).context("parsing site.json")?;
144 let array = value
145 .get("categories")
146 .and_then(|v| v.as_array())
147 .or_else(|| {
148 value
149 .get("site")
150 .and_then(|v| v.get("categories"))
151 .and_then(|v| v.as_array())
152 })
153 .ok_or_else(|| anyhow!("site.json missing categories list"))?;
154 let mut categories = Vec::new();
155 for item in array {
156 if let Ok(cat) = serde_json::from_value::<CategoryInfo>(item.clone()) {
157 categories.push(cat);
158 }
159 }
160 Ok(categories)
161 }
162}