Skip to main content

keygen_rs/
entitlement.rs

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