1#[cfg(feature = "token")]
2use crate::client::Client;
3#[cfg(feature = "token")]
4use crate::errors::Error;
5use crate::KeygenResponseData;
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct EntitlementAttributes {
12 pub name: Option<String>,
13 pub code: String,
14 pub metadata: Option<HashMap<String, serde_json::Value>>,
15 pub created: DateTime<Utc>,
16 pub updated: DateTime<Utc>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub(crate) struct EntitlementResponse {
21 pub data: KeygenResponseData<EntitlementAttributes>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub(crate) struct EntitlementsResponse {
26 pub data: Vec<KeygenResponseData<EntitlementAttributes>>,
27}
28
29#[cfg(feature = "token")]
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct CreateEntitlementRequest {
32 pub name: Option<String>,
33 pub code: String,
34 pub metadata: Option<HashMap<String, serde_json::Value>>,
35}
36
37#[cfg(feature = "token")]
38#[derive(Debug, Clone, Default, Serialize, Deserialize)]
39pub struct ListEntitlementsOptions {
40 pub limit: Option<u32>,
41 #[serde(rename = "page[size]")]
42 pub page_size: Option<u32>,
43 #[serde(rename = "page[number]")]
44 pub page_number: Option<u32>,
45}
46
47#[cfg(feature = "token")]
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct UpdateEntitlementRequest {
50 pub name: Option<String>,
51 pub code: Option<String>,
52 pub metadata: Option<HashMap<String, serde_json::Value>>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct Entitlement {
57 pub id: String,
58 pub name: Option<String>,
59 pub code: String,
60 pub metadata: Option<HashMap<String, serde_json::Value>>,
61 pub created: DateTime<Utc>,
62 pub updated: DateTime<Utc>,
63 pub account_id: Option<String>,
64}
65
66impl Entitlement {
67 pub(crate) fn from(data: KeygenResponseData<EntitlementAttributes>) -> Entitlement {
68 Entitlement {
69 id: data.id,
70 name: data.attributes.name,
71 code: data.attributes.code,
72 metadata: data.attributes.metadata,
73 created: data.attributes.created,
74 updated: data.attributes.updated,
75 account_id: data
76 .relationships
77 .account
78 .as_ref()
79 .and_then(|a| a.data.as_ref().map(|d| d.id.clone())),
80 }
81 }
82
83 #[cfg(feature = "token")]
85 pub async fn create(request: CreateEntitlementRequest) -> Result<Entitlement, Error> {
86 let client = Client::default()?;
87
88 let mut attributes = serde_json::Map::new();
89 if let Some(name) = request.name {
90 attributes.insert("name".to_string(), serde_json::Value::String(name));
91 }
92 attributes.insert("code".to_string(), serde_json::Value::String(request.code));
93 if let Some(metadata) = request.metadata {
94 attributes.insert("metadata".to_string(), serde_json::to_value(metadata)?);
95 }
96
97 let body = serde_json::json!({
98 "data": {
99 "type": "entitlements",
100 "attributes": attributes
101 }
102 });
103
104 let response = client
105 .post("entitlements", Some(&body), None::<&()>)
106 .await?;
107 let entitlement_response: EntitlementResponse = serde_json::from_value(response.body)?;
108 Ok(Entitlement::from(entitlement_response.data))
109 }
110
111 #[cfg(feature = "token")]
113 pub async fn list(options: Option<ListEntitlementsOptions>) -> Result<Vec<Entitlement>, Error> {
114 let client = Client::default()?;
115 let response = client.get("entitlements", options.as_ref()).await?;
116 let entitlements_response: EntitlementsResponse = serde_json::from_value(response.body)?;
117 Ok(entitlements_response
118 .data
119 .into_iter()
120 .map(Entitlement::from)
121 .collect())
122 }
123
124 #[cfg(feature = "token")]
126 pub async fn get(id: &str) -> Result<Entitlement, Error> {
127 let client = Client::default()?;
128 let endpoint = format!("entitlements/{id}");
129 let response = client.get(&endpoint, None::<&()>).await?;
130 let entitlement_response: EntitlementResponse = serde_json::from_value(response.body)?;
131 Ok(Entitlement::from(entitlement_response.data))
132 }
133
134 #[cfg(feature = "token")]
136 pub async fn update(&self, request: UpdateEntitlementRequest) -> Result<Entitlement, Error> {
137 let client = Client::default()?;
138 let endpoint = format!("entitlements/{}", self.id);
139
140 let mut attributes = serde_json::Map::new();
141 if let Some(name) = request.name {
142 attributes.insert("name".to_string(), serde_json::Value::String(name));
143 }
144 if let Some(code) = request.code {
145 attributes.insert("code".to_string(), serde_json::Value::String(code));
146 }
147 if let Some(metadata) = request.metadata {
148 attributes.insert("metadata".to_string(), serde_json::to_value(metadata)?);
149 }
150
151 let body = serde_json::json!({
152 "data": {
153 "type": "entitlements",
154 "attributes": attributes
155 }
156 });
157
158 let response = client.patch(&endpoint, Some(&body), None::<&()>).await?;
159 let entitlement_response: EntitlementResponse = serde_json::from_value(response.body)?;
160 Ok(Entitlement::from(entitlement_response.data))
161 }
162
163 #[cfg(feature = "token")]
165 pub async fn delete(&self) -> Result<(), Error> {
166 let client = Client::default()?;
167 let endpoint = format!("entitlements/{}", self.id);
168 client.delete::<(), ()>(&endpoint, None::<&()>).await?;
169 Ok(())
170 }
171}
172
173#[cfg(all(test, feature = "token"))]
174mod tests {
175 use super::*;
176 use crate::{
177 KeygenRelationship, KeygenRelationshipData, KeygenRelationships, KeygenResponseData,
178 };
179
180 #[test]
181 fn test_entitlement_account_relationship() {
182 let entitlement_data = KeygenResponseData {
183 id: "test-entitlement-id".to_string(),
184 r#type: "entitlements".to_string(),
185 attributes: EntitlementAttributes {
186 name: Some("Premium Feature".to_string()),
187 code: "premium".to_string(),
188 metadata: Some(HashMap::new()),
189 created: "2023-01-01T00:00:00Z".parse().unwrap(),
190 updated: "2023-01-01T00:00:00Z".parse().unwrap(),
191 },
192 relationships: KeygenRelationships {
193 account: Some(KeygenRelationship {
194 data: Some(KeygenRelationshipData {
195 r#type: "accounts".to_string(),
196 id: "test-account-id".to_string(),
197 }),
198 links: None,
199 }),
200 ..Default::default()
201 },
202 };
203
204 let entitlement = Entitlement::from(entitlement_data);
205
206 assert_eq!(entitlement.account_id, Some("test-account-id".to_string()));
207 assert_eq!(entitlement.id, "test-entitlement-id");
208 assert_eq!(entitlement.name, Some("Premium Feature".to_string()));
209 assert_eq!(entitlement.code, "premium");
210 }
211
212 #[test]
213 fn test_entitlement_without_account_relationship() {
214 let entitlement_data = KeygenResponseData {
215 id: "test-entitlement-id".to_string(),
216 r#type: "entitlements".to_string(),
217 attributes: EntitlementAttributes {
218 name: None,
219 code: "basic".to_string(),
220 metadata: None,
221 created: "2023-01-01T00:00:00Z".parse().unwrap(),
222 updated: "2023-01-01T00:00:00Z".parse().unwrap(),
223 },
224 relationships: KeygenRelationships::default(),
225 };
226
227 let entitlement = Entitlement::from(entitlement_data);
228
229 assert_eq!(entitlement.account_id, None);
230 assert_eq!(entitlement.name, None);
231 assert_eq!(entitlement.metadata, None);
232 }
233
234 #[test]
235 fn test_create_entitlement_request_serialization() {
236 let mut metadata = HashMap::new();
237 metadata.insert(
238 "feature_level".to_string(),
239 serde_json::Value::String("premium".to_string()),
240 );
241
242 let request = CreateEntitlementRequest {
243 name: Some("Advanced Features".to_string()),
244 code: "advanced".to_string(),
245 metadata: Some(metadata),
246 };
247
248 let serialized = serde_json::to_string(&request).unwrap();
249 assert!(serialized.contains("\"code\":\"advanced\""));
250 assert!(serialized.contains("\"name\":\"Advanced Features\""));
251 assert!(serialized.contains("\"metadata\""));
252 }
253
254 #[test]
255 fn test_list_entitlements_options_serialization() {
256 let options = ListEntitlementsOptions {
257 limit: Some(10),
258 page_size: Some(5),
259 page_number: Some(2),
260 };
261
262 let serialized = serde_json::to_string(&options).unwrap();
263 assert!(serialized.contains("\"limit\":10"));
264 assert!(serialized.contains("\"page[size]\":5"));
265 assert!(serialized.contains("\"page[number]\":2"));
266 }
267}