contentful/
contentful_client.rs

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        //self.get_entries_by_query_string::<T>(Some(new_query_string)).await
161    }
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                // do nothing
174            } 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") // TODO: Can this ever happen?
208            }
209        } else {
210            // Do nothing, as it likely a json object
211        }
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                        // Regular string, number, etc, values. No need to do anything.
231                    }
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                //value["fields"] = entry["fields"].clone();
277                //*value = entry["fields"].clone();
278            }
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        //*value = value["fields"].clone();
296        Ok(())
297    }
298}