contentful_fork/
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(mut items) = json.clone().get_mut("items") {
125                if items.is_array() {
126                    if let Some(includes) = json.get("includes") {
127                        self.resolve_array(&mut items, &includes)?;
128                    }
129
130                    let ar_string = items.to_string();
131                    let entries = serde_json::from_str::<Vec<T>>(&ar_string.as_str())?;
132                    Ok(entries)
133                } else {
134                    unimplemented!();
135                }
136            } else {
137                unimplemented!();
138            }
139        } else {
140            unimplemented!();
141        }
142    }
143
144    pub async fn get_entries_by_type<T>(
145        &self,
146        content_type: &str,
147        query_builder: Option<QueryBuilder>,
148    ) -> Result<Vec<T>, Box<dyn std::error::Error>>
149    where
150        for<'a> T: Serialize + Deserialize<'a>,
151    {
152        let query_builder = query_builder
153            .unwrap_or(QueryBuilder::new())
154            .content_type_is(content_type);
155
156        self.get_entries(Some(query_builder)).await
157        //self.get_entries_by_query_string::<T>(Some(new_query_string)).await
158    }
159
160    fn resolve_array(
161        &self,
162        value: &mut Value,
163        includes: &Value,
164    ) -> Result<(), Box<dyn std::error::Error>> {
165        let items = value.as_array_mut().unwrap();
166        for item in items {
167            if item.is_object() {
168                self.resolve_object(item, &includes)?;
169            } else if item.is_string() || item.is_number() {
170                // do nothing
171            } else {
172                log::error!("Unimplemented item {}", &item);
173                unimplemented!();
174            }
175        }
176        Ok(())
177    }
178
179    fn resolve_object(
180        &self,
181        value: &mut Value,
182        includes: &Value,
183    ) -> Result<(), Box<dyn std::error::Error>> {
184        let sys_type = value["sys"]["type"].clone();
185
186        if sys_type == Value::Null {
187            // Do nothing as this field is meant to be raw JSON.
188        } else if sys_type == "Entry" {
189            self.resolve_entry(value, &includes)?;
190        //*value = value["fields"].clone();
191        } else if sys_type == "Link" {
192            self.resolve_link(value, &includes)?;
193        //*value = value["fields"].clone();
194        } else {
195            let node_type = value["nodeType"].clone();
196            if node_type == "document" {
197                log::warn!("TODO: richtext");
198            } else {
199                unimplemented!("{} - {} not implemented", &sys_type, &node_type);
200            }
201        }
202        Ok(())
203    }
204
205    fn resolve_entry(
206        &self,
207        value: &mut Value,
208        includes: &Value,
209    ) -> Result<(), Box<dyn std::error::Error>> {
210        if let Some(fields) = value.get_mut("fields") {
211            if fields.is_object() {
212                let entry_object = fields.as_object_mut().unwrap();
213                for (_field_name, field_value) in entry_object {
214                    if field_value.is_object() {
215                        self.resolve_object(field_value, &includes)?;
216                    } else if field_value.is_array() {
217                        self.resolve_array(field_value, &includes)?;
218                    } else {
219                        // Regular string, number, etc, values. No need to do anything.
220                    }
221                }
222                *value = fields.clone();
223            } else {
224                unimplemented!();
225            }
226        } else {
227            unimplemented!();
228        }
229
230        Ok(())
231    }
232
233    fn resolve_asset(&self, value: &mut Value) -> Result<(), Box<dyn std::error::Error>> {
234        if let Some(fields) = value.get_mut("fields") {
235            if fields.is_object() {
236                *value = fields.clone();
237            } else {
238                unimplemented!();
239            }
240        } else {
241            unimplemented!();
242        }
243
244        Ok(())
245    }
246
247    fn resolve_link(
248        &self,
249        value: &mut Value,
250        includes: &Value,
251    ) -> Result<(), Box<dyn std::error::Error>> {
252        let link_type = value["sys"]["linkType"].clone();
253        let link_id = value["sys"]["id"].clone();
254        if link_type == "Entry" {
255            let included_entries = includes["Entry"].as_array().unwrap();
256            let mut filtered_entries = included_entries
257                .iter()
258                .filter(|entry| entry["sys"]["id"].to_string() == link_id.to_string())
259                .take(1);
260            let linked_entry = filtered_entries.next();
261            if let Some(entry) = linked_entry {
262                let mut entry = entry.clone();
263                self.resolve_entry(&mut entry, &includes)?;
264                *value = entry;
265                //value["fields"] = entry["fields"].clone();
266                //*value = entry["fields"].clone();
267            }
268        } else if link_type == "Asset" {
269            let included_assets = includes["Asset"].as_array().unwrap();
270            let mut filtered_assets = included_assets
271                .iter()
272                .filter(|entry| entry["sys"]["id"].to_string() == link_id.to_string())
273                .take(1);
274            let linked_asset = filtered_assets.next();
275            if let Some(asset) = linked_asset {
276                let mut asset = asset.clone();
277                self.resolve_asset(&mut asset)?;
278                *value = asset;
279            }
280        } else {
281            unimplemented!();
282        }
283
284        //*value = value["fields"].clone();
285        Ok(())
286    }
287}