Skip to main content

shopify_sdk/rest/resources/v2025_10/
comment.rs

1//! Comment resource implementation.
2//!
3//! This module provides the [`Comment`] resource for managing blog article
4//! comments in a Shopify store. Comments can be moderated, approved, marked
5//! as spam, or removed.
6//!
7//! # Comment Moderation
8//!
9//! Comments have several moderation methods:
10//! - `approve()` - Approve a pending comment for publication
11//! - `spam()` - Mark a comment as spam
12//! - `not_spam()` - Mark a comment as not spam
13//! - `remove()` - Remove a comment from publication
14//! - `restore()` - Restore a removed comment
15//!
16//! # Example
17//!
18//! ```rust,ignore
19//! use shopify_sdk::rest::{RestResource, ResourceResponse};
20//! use shopify_sdk::rest::resources::v2025_10::{Comment, CommentListParams};
21//!
22//! // List all comments
23//! let comments = Comment::all(&client, None).await?;
24//!
25//! // Moderate a comment
26//! let comment = Comment::find(&client, 653537639, None).await?.into_inner();
27//! let approved = comment.approve(&client).await?;
28//!
29//! // Mark a comment as spam
30//! let marked = comment.spam(&client).await?;
31//!
32//! // Filter comments by status
33//! let params = CommentListParams {
34//!     status: Some("pending".to_string()),
35//!     ..Default::default()
36//! };
37//! let pending = Comment::all(&client, Some(params)).await?;
38//! ```
39
40use std::collections::HashMap;
41
42use chrono::{DateTime, Utc};
43use serde::{Deserialize, Serialize};
44
45use crate::clients::RestClient;
46use crate::rest::{
47    build_path, get_path, ResourceError, ResourceOperation, ResourcePath, ResourceResponse,
48    RestResource,
49};
50use crate::HttpMethod;
51
52/// A comment on a blog article.
53///
54/// Comments can be moderated through various methods. The status
55/// determines the visibility and state of the comment.
56///
57/// # Moderation Methods
58///
59/// - `approve()` - Change status to "published"
60/// - `spam()` - Mark as spam
61/// - `not_spam()` - Remove spam flag
62/// - `remove()` - Change status to "removed"
63/// - `restore()` - Change status from "removed" to previous state
64///
65/// # Fields
66///
67/// ## Read-Only Fields
68/// - `id` - The unique identifier
69/// - `article_id` - The article the comment belongs to
70/// - `blog_id` - The blog the article belongs to
71/// - `status` - The comment status (pending, published, spam, removed)
72/// - `ip` - The IP address of the commenter
73/// - `user_agent` - The browser user agent of the commenter
74/// - `published_at` - When the comment was published
75/// - `created_at` - When the comment was created
76/// - `updated_at` - When the comment was last updated
77///
78/// ## Writable Fields
79/// - `author` - The name of the comment author
80/// - `email` - The email of the comment author
81/// - `body` - The comment text
82/// - `body_html` - The comment text in HTML
83#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
84pub struct Comment {
85    /// The unique identifier of the comment.
86    /// Read-only field.
87    #[serde(skip_serializing)]
88    pub id: Option<u64>,
89
90    /// The ID of the article this comment belongs to.
91    /// Read-only field.
92    #[serde(skip_serializing)]
93    pub article_id: Option<u64>,
94
95    /// The ID of the blog this comment belongs to.
96    /// Read-only field.
97    #[serde(skip_serializing)]
98    pub blog_id: Option<u64>,
99
100    /// The name of the comment author.
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub author: Option<String>,
103
104    /// The email address of the comment author.
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub email: Option<String>,
107
108    /// The text of the comment.
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub body: Option<String>,
111
112    /// The text of the comment in HTML format.
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub body_html: Option<String>,
115
116    /// The status of the comment: pending, published, spam, removed.
117    /// Read-only field - use moderation methods to change.
118    #[serde(skip_serializing)]
119    pub status: Option<String>,
120
121    /// The IP address of the commenter.
122    /// Read-only field.
123    #[serde(skip_serializing)]
124    pub ip: Option<String>,
125
126    /// The browser user agent of the commenter.
127    /// Read-only field.
128    #[serde(skip_serializing)]
129    pub user_agent: Option<String>,
130
131    /// When the comment was published.
132    /// Read-only field.
133    #[serde(skip_serializing)]
134    pub published_at: Option<DateTime<Utc>>,
135
136    /// When the comment was created.
137    /// Read-only field.
138    #[serde(skip_serializing)]
139    pub created_at: Option<DateTime<Utc>>,
140
141    /// When the comment was last updated.
142    /// Read-only field.
143    #[serde(skip_serializing)]
144    pub updated_at: Option<DateTime<Utc>>,
145}
146
147impl Comment {
148    /// Approves a comment for publication.
149    ///
150    /// Changes the comment status to "published".
151    ///
152    /// # Errors
153    ///
154    /// Returns an error if the comment has no ID.
155    ///
156    /// # Example
157    ///
158    /// ```rust,ignore
159    /// let comment = Comment::find(&client, 653537639, None).await?.into_inner();
160    /// let approved = comment.approve(&client).await?;
161    /// ```
162    pub async fn approve(&self, client: &RestClient) -> Result<ResourceResponse<Self>, ResourceError> {
163        let id = self.get_id().ok_or(ResourceError::PathResolutionFailed {
164            resource: Self::NAME,
165            operation: "approve",
166        })?;
167
168        let url = format!("comments/{id}/approve");
169        let response = client.post(&url, serde_json::json!({}), None).await?;
170
171        if !response.is_ok() {
172            return Err(ResourceError::from_http_response(
173                response.code,
174                &response.body,
175                Self::NAME,
176                Some(&id.to_string()),
177                response.request_id(),
178            ));
179        }
180
181        let key = Self::resource_key();
182        ResourceResponse::from_http_response(response, &key)
183    }
184
185    /// Marks a comment as spam.
186    ///
187    /// # Errors
188    ///
189    /// Returns an error if the comment has no ID.
190    ///
191    /// # Example
192    ///
193    /// ```rust,ignore
194    /// let comment = Comment::find(&client, 653537639, None).await?.into_inner();
195    /// let marked = comment.spam(&client).await?;
196    /// ```
197    pub async fn spam(&self, client: &RestClient) -> Result<ResourceResponse<Self>, ResourceError> {
198        let id = self.get_id().ok_or(ResourceError::PathResolutionFailed {
199            resource: Self::NAME,
200            operation: "spam",
201        })?;
202
203        let url = format!("comments/{id}/spam");
204        let response = client.post(&url, serde_json::json!({}), None).await?;
205
206        if !response.is_ok() {
207            return Err(ResourceError::from_http_response(
208                response.code,
209                &response.body,
210                Self::NAME,
211                Some(&id.to_string()),
212                response.request_id(),
213            ));
214        }
215
216        let key = Self::resource_key();
217        ResourceResponse::from_http_response(response, &key)
218    }
219
220    /// Marks a comment as not spam.
221    ///
222    /// # Errors
223    ///
224    /// Returns an error if the comment has no ID.
225    ///
226    /// # Example
227    ///
228    /// ```rust,ignore
229    /// let comment = Comment::find(&client, 653537639, None).await?.into_inner();
230    /// let marked = comment.not_spam(&client).await?;
231    /// ```
232    pub async fn not_spam(&self, client: &RestClient) -> Result<ResourceResponse<Self>, ResourceError> {
233        let id = self.get_id().ok_or(ResourceError::PathResolutionFailed {
234            resource: Self::NAME,
235            operation: "not_spam",
236        })?;
237
238        let url = format!("comments/{id}/not_spam");
239        let response = client.post(&url, serde_json::json!({}), None).await?;
240
241        if !response.is_ok() {
242            return Err(ResourceError::from_http_response(
243                response.code,
244                &response.body,
245                Self::NAME,
246                Some(&id.to_string()),
247                response.request_id(),
248            ));
249        }
250
251        let key = Self::resource_key();
252        ResourceResponse::from_http_response(response, &key)
253    }
254
255    /// Removes a comment from publication.
256    ///
257    /// Changes the comment status to "removed".
258    ///
259    /// # Errors
260    ///
261    /// Returns an error if the comment has no ID.
262    ///
263    /// # Example
264    ///
265    /// ```rust,ignore
266    /// let comment = Comment::find(&client, 653537639, None).await?.into_inner();
267    /// let removed = comment.remove(&client).await?;
268    /// ```
269    pub async fn remove(&self, client: &RestClient) -> Result<ResourceResponse<Self>, ResourceError> {
270        let id = self.get_id().ok_or(ResourceError::PathResolutionFailed {
271            resource: Self::NAME,
272            operation: "remove",
273        })?;
274
275        let url = format!("comments/{id}/remove");
276        let response = client.post(&url, serde_json::json!({}), None).await?;
277
278        if !response.is_ok() {
279            return Err(ResourceError::from_http_response(
280                response.code,
281                &response.body,
282                Self::NAME,
283                Some(&id.to_string()),
284                response.request_id(),
285            ));
286        }
287
288        let key = Self::resource_key();
289        ResourceResponse::from_http_response(response, &key)
290    }
291
292    /// Restores a removed comment.
293    ///
294    /// Returns the comment to its previous state before removal.
295    ///
296    /// # Errors
297    ///
298    /// Returns an error if the comment has no ID.
299    ///
300    /// # Example
301    ///
302    /// ```rust,ignore
303    /// let comment = Comment::find(&client, 653537639, None).await?.into_inner();
304    /// let restored = comment.restore(&client).await?;
305    /// ```
306    pub async fn restore(&self, client: &RestClient) -> Result<ResourceResponse<Self>, ResourceError> {
307        let id = self.get_id().ok_or(ResourceError::PathResolutionFailed {
308            resource: Self::NAME,
309            operation: "restore",
310        })?;
311
312        let url = format!("comments/{id}/restore");
313        let response = client.post(&url, serde_json::json!({}), None).await?;
314
315        if !response.is_ok() {
316            return Err(ResourceError::from_http_response(
317                response.code,
318                &response.body,
319                Self::NAME,
320                Some(&id.to_string()),
321                response.request_id(),
322            ));
323        }
324
325        let key = Self::resource_key();
326        ResourceResponse::from_http_response(response, &key)
327    }
328
329    /// Counts comments under a specific article.
330    ///
331    /// # Arguments
332    ///
333    /// * `client` - The REST client
334    /// * `article_id` - The article ID
335    /// * `params` - Optional count parameters
336    pub async fn count_for_article(
337        client: &RestClient,
338        article_id: u64,
339        params: Option<CommentCountParams>,
340    ) -> Result<u64, ResourceError> {
341        let mut ids: HashMap<&str, String> = HashMap::new();
342        ids.insert("article_id", article_id.to_string());
343
344        let available_ids: Vec<&str> = ids.keys().copied().collect();
345        let path = get_path(Self::PATHS, ResourceOperation::Count, &available_ids).ok_or(
346            ResourceError::PathResolutionFailed {
347                resource: Self::NAME,
348                operation: "count",
349            },
350        )?;
351
352        let url = build_path(path.template, &ids);
353
354        // Build query params
355        let query = params
356            .map(|p| {
357                let value = serde_json::to_value(&p).map_err(|e| {
358                    ResourceError::Http(crate::clients::HttpError::Response(
359                        crate::clients::HttpResponseError {
360                            code: 400,
361                            message: format!("Failed to serialize params: {e}"),
362                            error_reference: None,
363                        },
364                    ))
365                })?;
366
367                let mut query = HashMap::new();
368                if let serde_json::Value::Object(map) = value {
369                    for (key, val) in map {
370                        match val {
371                            serde_json::Value::String(s) => {
372                                query.insert(key, s);
373                            }
374                            serde_json::Value::Number(n) => {
375                                query.insert(key, n.to_string());
376                            }
377                            serde_json::Value::Bool(b) => {
378                                query.insert(key, b.to_string());
379                            }
380                            _ => {}
381                        }
382                    }
383                }
384                Ok::<_, ResourceError>(query)
385            })
386            .transpose()?
387            .filter(|q| !q.is_empty());
388
389        let response = client.get(&url, query).await?;
390
391        if !response.is_ok() {
392            return Err(ResourceError::from_http_response(
393                response.code,
394                &response.body,
395                Self::NAME,
396                None,
397                response.request_id(),
398            ));
399        }
400
401        let count = response
402            .body
403            .get("count")
404            .and_then(serde_json::Value::as_u64)
405            .ok_or_else(|| {
406                ResourceError::Http(crate::clients::HttpError::Response(
407                    crate::clients::HttpResponseError {
408                        code: response.code,
409                        message: "Missing 'count' in response".to_string(),
410                        error_reference: response.request_id().map(ToString::to_string),
411                    },
412                ))
413            })?;
414
415        Ok(count)
416    }
417}
418
419impl RestResource for Comment {
420    type Id = u64;
421    type FindParams = CommentFindParams;
422    type AllParams = CommentListParams;
423    type CountParams = CommentCountParams;
424
425    const NAME: &'static str = "Comment";
426    const PLURAL: &'static str = "comments";
427
428    /// Paths for the Comment resource.
429    ///
430    /// Full CRUD operations plus article-specific paths.
431    const PATHS: &'static [ResourcePath] = &[
432        ResourcePath::new(
433            HttpMethod::Get,
434            ResourceOperation::Find,
435            &["id"],
436            "comments/{id}",
437        ),
438        ResourcePath::new(HttpMethod::Get, ResourceOperation::All, &[], "comments"),
439        ResourcePath::new(
440            HttpMethod::Get,
441            ResourceOperation::Count,
442            &[],
443            "comments/count",
444        ),
445        ResourcePath::new(
446            HttpMethod::Post,
447            ResourceOperation::Create,
448            &[],
449            "comments",
450        ),
451        ResourcePath::new(
452            HttpMethod::Put,
453            ResourceOperation::Update,
454            &["id"],
455            "comments/{id}",
456        ),
457        ResourcePath::new(
458            HttpMethod::Delete,
459            ResourceOperation::Delete,
460            &["id"],
461            "comments/{id}",
462        ),
463        // Article-specific paths
464        ResourcePath::new(
465            HttpMethod::Get,
466            ResourceOperation::All,
467            &["article_id"],
468            "articles/{article_id}/comments",
469        ),
470        ResourcePath::new(
471            HttpMethod::Get,
472            ResourceOperation::Count,
473            &["article_id"],
474            "articles/{article_id}/comments/count",
475        ),
476    ];
477
478    fn get_id(&self) -> Option<Self::Id> {
479        self.id
480    }
481}
482
483/// Parameters for finding a single comment.
484#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
485pub struct CommentFindParams {
486    /// Comma-separated list of fields to include in the response.
487    #[serde(skip_serializing_if = "Option::is_none")]
488    pub fields: Option<String>,
489}
490
491/// Parameters for listing comments.
492#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
493pub struct CommentListParams {
494    /// Maximum number of results to return (default: 50, max: 250).
495    #[serde(skip_serializing_if = "Option::is_none")]
496    pub limit: Option<u32>,
497
498    /// Return comments after this ID.
499    #[serde(skip_serializing_if = "Option::is_none")]
500    pub since_id: Option<u64>,
501
502    /// Show comments created after this date.
503    #[serde(skip_serializing_if = "Option::is_none")]
504    pub created_at_min: Option<DateTime<Utc>>,
505
506    /// Show comments created before this date.
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub created_at_max: Option<DateTime<Utc>>,
509
510    /// Show comments updated after this date.
511    #[serde(skip_serializing_if = "Option::is_none")]
512    pub updated_at_min: Option<DateTime<Utc>>,
513
514    /// Show comments updated before this date.
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub updated_at_max: Option<DateTime<Utc>>,
517
518    /// Show comments published after this date.
519    #[serde(skip_serializing_if = "Option::is_none")]
520    pub published_at_min: Option<DateTime<Utc>>,
521
522    /// Show comments published before this date.
523    #[serde(skip_serializing_if = "Option::is_none")]
524    pub published_at_max: Option<DateTime<Utc>>,
525
526    /// Filter comments by status: pending, published, spam, removed.
527    #[serde(skip_serializing_if = "Option::is_none")]
528    pub status: Option<String>,
529
530    /// Comma-separated list of fields to include in the response.
531    #[serde(skip_serializing_if = "Option::is_none")]
532    pub fields: Option<String>,
533}
534
535/// Parameters for counting comments.
536#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
537pub struct CommentCountParams {
538    /// Show comments created after this date.
539    #[serde(skip_serializing_if = "Option::is_none")]
540    pub created_at_min: Option<DateTime<Utc>>,
541
542    /// Show comments created before this date.
543    #[serde(skip_serializing_if = "Option::is_none")]
544    pub created_at_max: Option<DateTime<Utc>>,
545
546    /// Show comments updated after this date.
547    #[serde(skip_serializing_if = "Option::is_none")]
548    pub updated_at_min: Option<DateTime<Utc>>,
549
550    /// Show comments updated before this date.
551    #[serde(skip_serializing_if = "Option::is_none")]
552    pub updated_at_max: Option<DateTime<Utc>>,
553
554    /// Show comments published after this date.
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub published_at_min: Option<DateTime<Utc>>,
557
558    /// Show comments published before this date.
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub published_at_max: Option<DateTime<Utc>>,
561
562    /// Filter comments by status: pending, published, spam, removed.
563    #[serde(skip_serializing_if = "Option::is_none")]
564    pub status: Option<String>,
565}
566
567#[cfg(test)]
568mod tests {
569    use super::*;
570    use crate::rest::{get_path, ResourceOperation};
571
572    #[test]
573    fn test_comment_serialization() {
574        let comment = Comment {
575            id: Some(653537639),
576            article_id: Some(134645308),
577            blog_id: Some(241253187),
578            author: Some("John Doe".to_string()),
579            email: Some("john@example.com".to_string()),
580            body: Some("Great article!".to_string()),
581            body_html: Some("<p>Great article!</p>".to_string()),
582            status: Some("published".to_string()),
583            ip: Some("192.168.1.1".to_string()),
584            user_agent: Some("Mozilla/5.0".to_string()),
585            published_at: Some(
586                DateTime::parse_from_rfc3339("2024-06-15T10:30:00Z")
587                    .unwrap()
588                    .with_timezone(&Utc),
589            ),
590            created_at: Some(
591                DateTime::parse_from_rfc3339("2024-06-15T10:00:00Z")
592                    .unwrap()
593                    .with_timezone(&Utc),
594            ),
595            updated_at: Some(
596                DateTime::parse_from_rfc3339("2024-06-15T10:30:00Z")
597                    .unwrap()
598                    .with_timezone(&Utc),
599            ),
600        };
601
602        let json = serde_json::to_string(&comment).unwrap();
603        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
604
605        // Writable fields should be present
606        assert_eq!(parsed["author"], "John Doe");
607        assert_eq!(parsed["email"], "john@example.com");
608        assert_eq!(parsed["body"], "Great article!");
609        assert_eq!(parsed["body_html"], "<p>Great article!</p>");
610
611        // Read-only fields should be omitted
612        assert!(parsed.get("id").is_none());
613        assert!(parsed.get("article_id").is_none());
614        assert!(parsed.get("blog_id").is_none());
615        assert!(parsed.get("status").is_none());
616        assert!(parsed.get("ip").is_none());
617        assert!(parsed.get("user_agent").is_none());
618        assert!(parsed.get("published_at").is_none());
619        assert!(parsed.get("created_at").is_none());
620        assert!(parsed.get("updated_at").is_none());
621    }
622
623    #[test]
624    fn test_comment_deserialization() {
625        let json = r#"{
626            "id": 653537639,
627            "article_id": 134645308,
628            "blog_id": 241253187,
629            "author": "John Doe",
630            "email": "john@example.com",
631            "body": "Great article!",
632            "body_html": "<p>Great article!</p>",
633            "status": "published",
634            "ip": "192.168.1.1",
635            "user_agent": "Mozilla/5.0",
636            "published_at": "2024-06-15T10:30:00Z",
637            "created_at": "2024-06-15T10:00:00Z",
638            "updated_at": "2024-06-15T10:30:00Z"
639        }"#;
640
641        let comment: Comment = serde_json::from_str(json).unwrap();
642
643        assert_eq!(comment.id, Some(653537639));
644        assert_eq!(comment.article_id, Some(134645308));
645        assert_eq!(comment.blog_id, Some(241253187));
646        assert_eq!(comment.author, Some("John Doe".to_string()));
647        assert_eq!(comment.email, Some("john@example.com".to_string()));
648        assert_eq!(comment.body, Some("Great article!".to_string()));
649        assert_eq!(comment.status, Some("published".to_string()));
650        assert_eq!(comment.ip, Some("192.168.1.1".to_string()));
651        assert!(comment.published_at.is_some());
652        assert!(comment.created_at.is_some());
653    }
654
655    #[test]
656    fn test_comment_moderation_methods_path_construction() {
657        // The moderation methods use URLs like:
658        // POST /comments/{id}/approve
659        // POST /comments/{id}/spam
660        // POST /comments/{id}/not_spam
661        // POST /comments/{id}/remove
662        // POST /comments/{id}/restore
663        //
664        // These are implemented as instance methods that construct URLs directly
665
666        let comment = Comment {
667            id: Some(653537639),
668            ..Default::default()
669        };
670
671        // Verify that the ID is available for URL construction
672        assert_eq!(comment.id, Some(653537639));
673        // URLs would be: comments/653537639/approve, etc.
674    }
675
676    #[test]
677    fn test_comment_full_crud_paths() {
678        // Find by ID
679        let find_path = get_path(Comment::PATHS, ResourceOperation::Find, &["id"]);
680        assert!(find_path.is_some());
681        assert_eq!(find_path.unwrap().template, "comments/{id}");
682
683        // List all
684        let all_path = get_path(Comment::PATHS, ResourceOperation::All, &[]);
685        assert!(all_path.is_some());
686        assert_eq!(all_path.unwrap().template, "comments");
687
688        // Count
689        let count_path = get_path(Comment::PATHS, ResourceOperation::Count, &[]);
690        assert!(count_path.is_some());
691        assert_eq!(count_path.unwrap().template, "comments/count");
692
693        // Create
694        let create_path = get_path(Comment::PATHS, ResourceOperation::Create, &[]);
695        assert!(create_path.is_some());
696        assert_eq!(create_path.unwrap().template, "comments");
697
698        // Update
699        let update_path = get_path(Comment::PATHS, ResourceOperation::Update, &["id"]);
700        assert!(update_path.is_some());
701        assert_eq!(update_path.unwrap().template, "comments/{id}");
702
703        // Delete
704        let delete_path = get_path(Comment::PATHS, ResourceOperation::Delete, &["id"]);
705        assert!(delete_path.is_some());
706        assert_eq!(delete_path.unwrap().template, "comments/{id}");
707    }
708
709    #[test]
710    fn test_comment_article_specific_paths() {
711        // Comments for an article
712        let article_comments = get_path(Comment::PATHS, ResourceOperation::All, &["article_id"]);
713        assert!(article_comments.is_some());
714        assert_eq!(
715            article_comments.unwrap().template,
716            "articles/{article_id}/comments"
717        );
718
719        // Count comments for an article
720        let article_count = get_path(Comment::PATHS, ResourceOperation::Count, &["article_id"]);
721        assert!(article_count.is_some());
722        assert_eq!(
723            article_count.unwrap().template,
724            "articles/{article_id}/comments/count"
725        );
726    }
727
728    #[test]
729    fn test_comment_list_params() {
730        let params = CommentListParams {
731            limit: Some(50),
732            status: Some("pending".to_string()),
733            since_id: Some(100),
734            ..Default::default()
735        };
736
737        let json = serde_json::to_value(&params).unwrap();
738
739        assert_eq!(json["limit"], 50);
740        assert_eq!(json["status"], "pending");
741        assert_eq!(json["since_id"], 100);
742    }
743
744    #[test]
745    fn test_comment_constants() {
746        assert_eq!(Comment::NAME, "Comment");
747        assert_eq!(Comment::PLURAL, "comments");
748    }
749
750    #[test]
751    fn test_comment_get_id() {
752        let comment_with_id = Comment {
753            id: Some(653537639),
754            ..Default::default()
755        };
756        assert_eq!(comment_with_id.get_id(), Some(653537639));
757
758        let comment_without_id = Comment::default();
759        assert_eq!(comment_without_id.get_id(), None);
760    }
761}