1use chrono::{DateTime, Utc};
4use serde::Serialize;
5use serde_json::{json, Value};
6
7pub trait ToJsonLd {
9 fn json_ld(&self) -> Value;
10}
11
12fn wrap<T: Serialize>(ty: &str, value: &T) -> Value {
13 let mut v = serde_json::to_value(value).unwrap_or(json!({}));
14 if let Value::Object(ref mut map) = v {
15 map.insert("@type".into(), Value::String(ty.into()));
16 map.insert(
17 "@context".into(),
18 Value::String("https://schema.org".into()),
19 );
20 }
21 v
22}
23
24#[derive(Debug, Clone, Serialize, Default)]
25pub struct Article {
26 pub headline: String,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub author: Option<String>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub date_published: Option<DateTime<Utc>>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub image: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub description: Option<String>,
35}
36
37impl ToJsonLd for Article {
38 fn json_ld(&self) -> Value {
39 wrap("Article", self)
40 }
41}
42
43#[derive(Debug, Clone, Serialize, Default)]
44pub struct Product {
45 pub name: String,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub description: Option<String>,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub image: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub sku: Option<String>,
52}
53
54impl ToJsonLd for Product {
55 fn json_ld(&self) -> Value {
56 wrap("Product", self)
57 }
58}
59
60#[derive(Debug, Clone, Serialize, Default)]
61pub struct Offer {
62 pub price: String,
63 pub price_currency: String,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub availability: Option<String>,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub url: Option<String>,
68}
69
70impl ToJsonLd for Offer {
71 fn json_ld(&self) -> Value {
72 wrap("Offer", self)
73 }
74}
75
76#[derive(Debug, Clone, Serialize, Default)]
77pub struct ListItem {
78 pub position: u32,
79 pub name: String,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub item: Option<String>,
82}
83
84#[derive(Debug, Clone, Serialize, Default)]
85pub struct BreadcrumbList {
86 pub item_list_element: Vec<ListItem>,
87}
88
89impl BreadcrumbList {
90 pub fn from_pairs(crumbs: &[(&str, &str)]) -> Self {
91 Self {
92 item_list_element: crumbs
93 .iter()
94 .enumerate()
95 .map(|(i, (name, url))| ListItem {
96 position: (i + 1) as u32,
97 name: (*name).to_string(),
98 item: Some((*url).to_string()),
99 })
100 .collect(),
101 }
102 }
103}
104
105impl ToJsonLd for BreadcrumbList {
106 fn json_ld(&self) -> Value {
107 wrap("BreadcrumbList", self)
108 }
109}
110
111#[derive(Debug, Clone, Serialize, Default)]
112pub struct Organization {
113 pub name: String,
114 #[serde(skip_serializing_if = "Option::is_none")]
115 pub url: Option<String>,
116 #[serde(skip_serializing_if = "Option::is_none")]
117 pub logo: Option<String>,
118}
119
120impl ToJsonLd for Organization {
121 fn json_ld(&self) -> Value {
122 wrap("Organization", self)
123 }
124}
125
126#[derive(Debug, Clone, Serialize, Default)]
127pub struct WebSite {
128 pub name: String,
129 pub url: String,
130}
131
132impl ToJsonLd for WebSite {
133 fn json_ld(&self) -> Value {
134 wrap("WebSite", self)
135 }
136}
137
138#[derive(Debug, Clone, Serialize, Default)]
139pub struct FAQPage {
140 pub main_entity: Vec<FaqEntry>,
141}
142
143#[derive(Debug, Clone, Serialize, Default)]
144pub struct FaqEntry {
145 pub name: String,
146 pub accepted_answer: FaqAnswer,
147}
148
149#[derive(Debug, Clone, Serialize, Default)]
150pub struct FaqAnswer {
151 pub text: String,
152}
153
154impl ToJsonLd for FAQPage {
155 fn json_ld(&self) -> Value {
156 let entities: Vec<Value> = self
157 .main_entity
158 .iter()
159 .map(|e| {
160 json!({
161 "@type": "Question",
162 "name": e.name,
163 "acceptedAnswer": {
164 "@type": "Answer",
165 "text": e.accepted_answer.text,
166 }
167 })
168 })
169 .collect();
170 json!({
171 "@context": "https://schema.org",
172 "@type": "FAQPage",
173 "mainEntity": entities,
174 })
175 }
176}
177
178#[derive(Debug, Clone, Serialize, Default)]
179pub struct LocalBusiness {
180 pub name: String,
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub address: Option<String>,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub telephone: Option<String>,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub url: Option<String>,
187}
188
189impl ToJsonLd for LocalBusiness {
190 fn json_ld(&self) -> Value {
191 wrap("LocalBusiness", self)
192 }
193}