contentful_fork/
contentful_client.rs1use 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 }
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 } 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 } else if sys_type == "Entry" {
189 self.resolve_entry(value, &includes)?;
190 } else if sys_type == "Link" {
192 self.resolve_link(value, &includes)?;
193 } 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 }
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 }
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 Ok(())
286 }
287}