1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_with::{DefaultOnNull, serde_as, skip_serializing_none};
4use std::{fmt::Display, str::FromStr};
5use validator::Validate;
6
7use crate::{
8 ClientId, Identifier, IdentifierError, target::Target, values_map::ValuesMap, ven::VenId,
9};
10
11#[skip_serializing_none]
13#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
14#[serde(rename_all = "camelCase", tag = "objectType", rename = "RESOURCE")]
15pub struct Resource {
16 pub id: ResourceId,
18 #[serde(with = "crate::serde_rfc3339")]
20 pub created_date_time: DateTime<Utc>,
21 #[serde(with = "crate::serde_rfc3339")]
23 pub modification_date_time: DateTime<Utc>,
24 #[serde(rename = "clientID")]
28 pub client_id: ClientId,
29 #[serde(flatten)]
30 #[validate(nested)]
31 pub content: BlResourceRequest,
32}
33
34#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
35#[serde(rename_all = "SCREAMING_SNAKE_CASE", tag = "objectType")]
36pub enum ResourceRequest {
37 BlResourceRequest(BlResourceRequest),
38 VenResourceRequest(VenResourceRequest),
39}
40
41impl Validate for ResourceRequest {
42 fn validate(&self) -> Result<(), validator::ValidationErrors> {
43 match self {
44 ResourceRequest::BlResourceRequest(x) => x.validate(),
45 ResourceRequest::VenResourceRequest(x) => x.validate(),
46 }
47 }
48}
49
50impl ResourceRequest {
51 pub fn resource_name(&self) -> &str {
52 match self {
53 ResourceRequest::BlResourceRequest(r) => &r.resource_name,
54 ResourceRequest::VenResourceRequest(r) => &r.resource_name,
55 }
56 }
57
58 pub fn attributes(&self) -> Option<&[ValuesMap]> {
59 match self {
60 ResourceRequest::BlResourceRequest(r) => r.attributes.as_deref(),
61 ResourceRequest::VenResourceRequest(r) => r.attributes.as_deref(),
62 }
63 }
64}
65
66#[skip_serializing_none]
67#[serde_as]
68#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
69#[serde(rename_all = "camelCase")]
70pub struct BlResourceRequest {
71 #[serde(default)]
73 #[serde_as(deserialize_as = "DefaultOnNull")]
74 pub targets: Vec<Target>,
75 #[serde(deserialize_with = "crate::string_within_range_inclusive::<1, 128, _>")]
77 pub resource_name: String,
78 #[serde(rename = "venID")]
80 pub ven_id: VenId,
81 pub attributes: Option<Vec<ValuesMap>>,
83}
84
85#[skip_serializing_none]
86#[serde_as]
87#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
88#[serde(rename_all = "camelCase")]
89pub struct VenResourceRequest {
90 #[serde(deserialize_with = "crate::string_within_range_inclusive::<1, 128, _>")]
92 pub resource_name: String,
93 pub attributes: Option<Vec<ValuesMap>>,
95}
96
97#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Hash, Eq)]
98pub struct ResourceId(pub(crate) Identifier);
99
100impl Display for ResourceId {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 write!(f, "{}", self.0)
103 }
104}
105
106impl ResourceId {
107 pub fn as_str(&self) -> &str {
108 self.0.as_str()
109 }
110
111 pub fn new(identifier: &str) -> Option<Self> {
112 Some(Self(identifier.parse().ok()?))
113 }
114}
115
116impl FromStr for ResourceId {
117 type Err = IdentifierError;
118
119 fn from_str(s: &str) -> Result<Self, Self::Err> {
120 Ok(Self(s.parse()?))
121 }
122}
123
124#[cfg(test)]
125mod test {
126 use super::{BlResourceRequest, Resource, ResourceId, ResourceRequest, VenResourceRequest};
127 use crate::ven::VenId;
128
129 #[test]
130 fn example_roundtrip() {
131 let example = r#"
132 {
133 "id": "test-resource",
134 "createdDateTime": "2023-06-15T09:30:00Z",
135 "modificationDateTime": "2023-06-15T09:30:00Z",
136 "resourceName": "RESOURCE_0999",
137 "objectType": "RESOURCE",
138 "clientID": "ven_client",
139 "venID": "0",
140 "targets": [
141 "resource_0999"
142 ]
143 }"#;
144
145 let parsed = serde_json::from_str::<Resource>(example).unwrap();
146
147 let expected = Resource {
148 id: ResourceId("test-resource".parse().unwrap()),
149 client_id: "ven_client".parse().unwrap(),
150 created_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
151 modification_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
152 content: BlResourceRequest {
153 attributes: None,
154 targets: vec!["resource_0999".parse().unwrap()],
155 resource_name: "RESOURCE_0999".to_string(),
156 ven_id: VenId("0".parse().unwrap()),
157 },
158 };
159
160 assert_eq!(expected, parsed);
161
162 let serialized = serde_json::to_string_pretty(&expected).unwrap();
163
164 let num_object_type = serialized
166 .as_bytes()
167 .windows("objectType".len())
168 .filter(|&w| w == "objectType".as_bytes())
169 .count();
170
171 assert_eq!(num_object_type, 1);
172
173 assert!(serialized.contains(r#""objectType": "RESOURCE""#));
175
176 let parsed = serde_json::from_str::<Resource>(&serialized).unwrap();
177 assert_eq!(expected, parsed);
178 }
179
180 #[test]
181 fn request_discriminator() {
182 let ven_request = r#"
183 {
184 "resourceName": "RESOURCE_0999",
185 "objectType": "VEN_RESOURCE_REQUEST",
186 "venID": "0"
187 }"#;
188
189 let parsed_ven_request = serde_json::from_str::<ResourceRequest>(ven_request).unwrap();
190
191 assert_eq!(
192 parsed_ven_request,
193 ResourceRequest::VenResourceRequest(VenResourceRequest {
194 resource_name: "RESOURCE_0999".to_string(),
195 attributes: None,
196 })
197 );
198
199 let bl_request = r#"
200 {
201 "resourceName": "RESOURCE_0999",
202 "objectType": "BL_RESOURCE_REQUEST",
203 "venID": "0",
204 "clientID": "ven_client"
205 }"#;
206
207 let parsed_bl_request = serde_json::from_str::<ResourceRequest>(bl_request).unwrap();
208
209 assert_eq!(
210 parsed_bl_request,
211 ResourceRequest::BlResourceRequest(BlResourceRequest {
212 targets: vec![],
213 resource_name: "RESOURCE_0999".to_string(),
214 ven_id: VenId("0".parse().unwrap()),
215 attributes: None,
216 })
217 );
218
219 let ven_request_with_bl_fields = r#"
220 {
221 "resourceName": "RESOURCE_0999",
222 "objectType": "VEN_RESOURCE_REQUEST",
223 "venID": "0",
224 "clientID": "ven_client"
225 }"#;
226
227 let parsed_ven_request_with_bl_fields =
228 serde_json::from_str::<ResourceRequest>(ven_request_with_bl_fields).unwrap();
229
230 assert_eq!(
231 parsed_ven_request_with_bl_fields,
232 ResourceRequest::VenResourceRequest(VenResourceRequest {
233 resource_name: "RESOURCE_0999".to_string(),
234 attributes: None,
235 })
236 );
237 }
238}