1use crate::query_builder::QueryBuilder;
2use crate::{http_client, models::Entry};
3use serde::{Deserialize, Serialize};
4use serde_json::{json, Value};
5
6pub struct ContentfulClient {
7 delivery_api_access_token: String,
8 space_id: String,
9 base_url: String,
10 environment_id: String,
11}
12
13impl ContentfulClient {
14 pub fn new(delivery_api_access_token: &str, space_id: &str) -> ContentfulClient {
15 let environment_id = "master".into();
16 ContentfulClient {
17 base_url: "https://cdn.contentful.com/spaces".into(),
18 delivery_api_access_token: delivery_api_access_token.into(),
19 space_id: space_id.into(),
20 environment_id,
21 }
22 }
23
24 pub fn with_environment<S>(
25 delivery_api_access_token: &str,
26 space_id: &str,
27 environment_id: &str,
28 ) -> ContentfulClient {
29 ContentfulClient {
30 base_url: "https://cdn.contentful.com/spaces".into(),
31 delivery_api_access_token: delivery_api_access_token.into(),
32 space_id: space_id.into(),
33 environment_id: environment_id.into(),
34 }
35 }
36
37 fn get_entry_url(&self, entry_id: &str) -> String {
38 let url = format!(
39 "{base_url}/{space_id}/environments/{environment_id}/entries/{entry_id}",
40 base_url = &self.base_url,
41 space_id = &self.space_id,
42 environment_id = &self.environment_id,
43 entry_id = &entry_id
44 );
45 url
46 }
47
48 fn get_query_string_url(&self, query_string: &str) -> String {
49 let url = format!(
50 "{base_url}/{space_id}/environments/{environment}/entries{query_string}",
51 base_url = &self.base_url,
52 space_id = &self.space_id,
53 environment = &self.environment_id,
54 query_string = &query_string
55 );
56 url
57 }
58
59 pub async fn get_entry<T>(
60 &self,
61 entry_id: &str,
62 ) -> Result<Option<T>, Box<dyn std::error::Error>>
63 where
64 for<'a> T: Serialize + Deserialize<'a>,
65 {
66 if let Some(entry) = self.get_contentful_entry(entry_id).await? {
67 let mut entry_json_value = entry.fields.clone();
68 entry_json_value["sys"] = json!(entry.sys);
69 let entry_string = entry_json_value.to_string();
70 let entry = serde_json::from_str::<T>(entry_string.as_str())?;
71 Ok(Some(entry))
72 } else {
73 Ok(None)
74 }
75 }
76
77 pub async fn get_contentful_entry(
78 &self,
79 entry_id: &str,
80 ) -> Result<Option<Entry<Value>>, Box<dyn std::error::Error>> {
81 let url = self.get_entry_url(entry_id);
82 let json_value =
83 http_client::get::<Entry<Value>>(&url, &self.delivery_api_access_token).await?;
84 Ok(json_value)
85 }
86
87 pub async fn get_entry_json_value(
88 &self,
89 entry_id: &str,
90 ) -> Result<Option<Value>, Box<dyn std::error::Error>> {
91 let url = self.get_entry_url(entry_id);
92 let json_value = http_client::get::<Value>(&url, &self.delivery_api_access_token).await?;
93 Ok(json_value)
94 }
95
96 pub async fn get_entries<T>(
97 &self,
98 query_builder: Option<QueryBuilder>,
99 ) -> Result<Vec<T>, Box<dyn std::error::Error>>
100 where
101 for<'a> T: Serialize + Deserialize<'a>,
102 {
103 let query_string = if let Some(query_builder) = query_builder {
104 query_builder.build()
105 } else {
106 "".to_string()
107 };
108
109 self.get_entries_by_query_string::<T>(query_string.as_str())
110 .await
111 }
112
113 pub async fn get_entries_by_query_string<T>(
114 &self,
115 query_string: &str,
116 ) -> Result<Vec<T>, Box<dyn std::error::Error>>
117 where
118 for<'a> T: Serialize + Deserialize<'a>,
119 {
120 log::debug!("query_string: {:?}", &query_string);
121 let url = self.get_query_string_url(query_string);
122 if let Some(json) = http_client::get::<Value>(&url, &self.delivery_api_access_token).await?
123 {
124 if let Some(items) = json.clone().get_mut("items") {
125 if items.is_array() {
126 if let Some(includes) = json.get("includes") {
127 self.resolve_array(items, includes)?;
128 } else {
129 let includes = Value::default();
130 self.resolve_array(items, &includes)?;
131 }
132
133 let ar_string = items.to_string();
134 let entries = serde_json::from_str::<Vec<T>>(ar_string.as_str())?;
135 Ok(entries)
136 } else {
137 unimplemented!();
138 }
139 } else {
140 unimplemented!();
141 }
142 } else {
143 unimplemented!();
144 }
145 }
146
147 pub async fn get_entries_by_type<T>(
148 &self,
149 content_type: &str,
150 query_builder: Option<QueryBuilder>,
151 ) -> Result<Vec<T>, Box<dyn std::error::Error>>
152 where
153 for<'a> T: Serialize + Deserialize<'a>,
154 {
155 let query_builder = query_builder
156 .unwrap_or(QueryBuilder::new())
157 .content_type_is(content_type);
158
159 self.get_entries(Some(query_builder)).await
160 }
162
163 fn resolve_array(
164 &self,
165 value: &mut Value,
166 includes: &Value,
167 ) -> Result<(), Box<dyn std::error::Error>> {
168 let items = value.as_array_mut().unwrap();
169 for item in items {
170 if item.is_object() {
171 self.resolve_object(item, includes)?;
172 } else if item.is_string() || item.is_number() {
173 } else {
175 log::error!("Unimplemented item {}", &item);
176 unimplemented!();
177 }
178 }
179 Ok(())
180 }
181
182 fn resolve_object(
183 &self,
184 value: &mut Value,
185 includes: &Value,
186 ) -> Result<(), Box<dyn std::error::Error>> {
187 if let Some(sys) = value.get("sys") {
188 if let Some(sys_type) = sys.get("type") {
189 if sys_type == "Entry" {
190 self.resolve_entry(value, includes)?;
191 } else if sys_type == "Link" {
192 self.resolve_link(value, includes)?;
193 } else {
194 let node_type = value["nodeType"].clone();
195 if node_type == "document" {
196 log::warn!("TODO: Richtext is not yet implemented");
197 } else {
198 unimplemented!(
199 "{} - {} not implemented for {}",
200 &sys_type,
201 &node_type,
202 &value
203 );
204 }
205 }
206 } else {
207 unimplemented!("sys.type do not exist, though sys exists") }
209 } else {
210 }
212
213 Ok(())
214 }
215
216 fn resolve_entry(
217 &self,
218 value: &mut Value,
219 includes: &Value,
220 ) -> Result<(), Box<dyn std::error::Error>> {
221 if let Some(fields) = value.get_mut("fields") {
222 if fields.is_object() {
223 let entry_object = fields.as_object_mut().unwrap();
224 for (_field_name, field_value) in entry_object {
225 if field_value.is_object() {
226 self.resolve_object(field_value, includes)?;
227 } else if field_value.is_array() {
228 self.resolve_array(field_value, includes)?;
229 } else {
230 }
232 }
233 *value = fields.clone();
234 } else {
235 unimplemented!();
236 }
237 } else {
238 unimplemented!();
239 }
240
241 Ok(())
242 }
243
244 fn resolve_asset(&self, value: &mut Value) -> Result<(), Box<dyn std::error::Error>> {
245 if let Some(fields) = value.get_mut("fields") {
246 if fields.is_object() {
247 *value = fields.clone();
248 } else {
249 unimplemented!();
250 }
251 } else {
252 unimplemented!();
253 }
254
255 Ok(())
256 }
257
258 fn resolve_link(
259 &self,
260 value: &mut Value,
261 includes: &Value,
262 ) -> Result<(), Box<dyn std::error::Error>> {
263 let link_type = value["sys"]["linkType"].clone();
264 let link_id = value["sys"]["id"].clone();
265 if link_type == "Entry" {
266 let included_entries = includes["Entry"].as_array().unwrap();
267 let mut filtered_entries = included_entries
268 .iter()
269 .filter(|entry| entry["sys"]["id"] == link_id)
270 .take(1);
271 let linked_entry = filtered_entries.next();
272 if let Some(entry) = linked_entry {
273 let mut entry = entry.clone();
274 self.resolve_entry(&mut entry, includes)?;
275 *value = entry;
276 }
279 } else if link_type == "Asset" {
280 let included_assets = includes["Asset"].as_array().unwrap();
281 let mut filtered_assets = included_assets
282 .iter()
283 .filter(|entry| entry["sys"]["id"] == link_id)
284 .take(1);
285 let linked_asset = filtered_assets.next();
286 if let Some(asset) = linked_asset {
287 let mut asset = asset.clone();
288 self.resolve_asset(&mut asset)?;
289 *value = asset;
290 }
291 } else {
292 unimplemented!();
293 }
294
295 Ok(())
297 }
298}