1use std::marker::PhantomData;
2use std::sync::Arc;
3
4use chrono::NaiveDate;
5use reqwest::RequestBuilder;
6use serde::{Deserialize, Deserializer, Serialize};
7use z_osmf_macros::{Endpoint, Getters};
8
9use crate::convert::TryFromResponse;
10use crate::restfiles::get_transaction_id;
11use crate::{ClientCore, Result};
12
13use super::{de_optional_y_n, ser_optional_y_n};
14
15#[derive(Clone, Debug, Deserialize, Eq, Getters, Hash, Ord, PartialEq, PartialOrd, Serialize)]
16pub struct DatasetAttributesBase {
17 #[serde(rename = "dsname")]
18 name: Arc<str>,
19 #[serde(rename = "blksz")]
20 block_size: Option<Arc<str>>,
21 #[serde(rename = "catnm")]
22 catalog: Option<Arc<str>>,
23 #[getter(copy)]
24 #[serde(default, deserialize_with = "de_optional_date", rename = "cdate")]
25 creation_date: Option<NaiveDate>,
26 #[serde(rename = "dev")]
27 device_type: Option<Arc<str>>,
28 #[serde(rename = "dsntp")]
29 dataset_type: Option<Arc<str>>,
30 #[serde(rename = "dsorg")]
31 organization: Option<Arc<str>>,
32 #[getter(copy)]
33 #[serde(default, deserialize_with = "de_optional_date", rename = "edate")]
34 expiration_date: Option<NaiveDate>,
35 #[serde(rename = "extx")]
36 extents_used: Option<Arc<str>>,
37 #[serde(rename = "lrecl")]
38 record_length: Option<Arc<str>>,
39 #[getter(copy)]
40 #[serde(
41 rename = "migr",
42 deserialize_with = "de_yes_no",
43 serialize_with = "ser_yes_no"
44 )]
45 migrated: bool,
46 #[getter(copy)]
47 #[serde(
48 default,
49 rename = "mvol",
50 deserialize_with = "de_optional_y_n",
51 serialize_with = "ser_optional_y_n"
52 )]
53 multi_volume: Option<bool>,
54 #[getter(copy)]
55 #[serde(
56 default,
57 rename = "ovf",
58 deserialize_with = "de_optional_yes_no",
59 serialize_with = "ser_optional_yes_no"
60 )]
61 space_overflow: Option<bool>,
62 #[getter(copy)]
63 #[serde(default, deserialize_with = "de_optional_date", rename = "rdate")]
64 last_referenced_date: Option<NaiveDate>,
65 #[serde(rename = "recfm")]
66 record_format: Option<Arc<str>>,
67 #[serde(rename = "sizex")]
68 size_in_tracks: Option<Arc<str>>,
69 #[serde(rename = "spacu")]
70 space_units: Option<Arc<str>>,
71 #[serde(rename = "used")]
72 percent_used: Option<Arc<str>>,
73 #[serde(rename = "vol")]
74 volume: DatasetVolume,
75 #[serde(rename = "vols")]
76 volumes: Option<Arc<str>>,
77}
78
79#[derive(Clone, Debug, Deserialize, Eq, Getters, Hash, Ord, PartialEq, PartialOrd, Serialize)]
80pub struct DatasetAttributesName {
81 #[serde(rename = "dsname")]
82 name: Arc<str>,
83}
84
85#[derive(Clone, Debug, Deserialize, Eq, Getters, Hash, Ord, PartialEq, PartialOrd, Serialize)]
86pub struct DatasetAttributesVolume {
87 #[serde(rename = "dsname")]
88 name: Arc<str>,
89 #[serde(rename = "vol")]
90 volume: DatasetVolume,
91}
92
93#[derive(Clone, Debug, Deserialize, Eq, Getters, Hash, Ord, PartialEq, PartialOrd, Serialize)]
94pub struct DatasetList<T> {
95 items: Arc<[T]>,
96 #[getter(copy)]
97 json_version: i32,
98 #[getter(copy)]
99 more_rows: Option<bool>,
100 #[getter(copy)]
101 returned_rows: i32,
102 #[getter(copy)]
103 total_rows: Option<i32>,
104 transaction_id: Arc<str>,
105}
106
107impl<T> TryFromResponse for DatasetList<T>
108where
109 T: for<'de> Deserialize<'de>,
110{
111 async fn try_from_response(value: reqwest::Response) -> Result<Self> {
112 let transaction_id = get_transaction_id(&value)?;
113
114 let ResponseJson {
115 items,
116 json_version,
117 more_rows,
118 returned_rows,
119 total_rows,
120 } = value.json().await?;
121
122 Ok(DatasetList {
123 items,
124 json_version,
125 more_rows,
126 returned_rows,
127 total_rows,
128 transaction_id,
129 })
130 }
131}
132
133#[derive(Clone, Debug, Endpoint)]
134#[endpoint(method = get, path = "/zosmf/restfiles/ds")]
135pub struct DatasetListBuilder<T>
136where
137 T: TryFromResponse,
138{
139 core: Arc<ClientCore>,
140
141 #[endpoint(query = "dslevel")]
142 level: Arc<str>,
143 #[endpoint(query = "volser")]
144 volume: Option<Arc<str>>,
145 #[endpoint(query = "start")]
146 start: Option<Arc<str>>,
147 #[endpoint(header = "X-IBM-Max-Items")]
148 max_items: Option<i32>,
149 #[endpoint(skip_setter, builder_fn = build_attributes)]
150 attributes: Option<Attrs>,
151 #[endpoint(skip_builder)]
152 include_total: Option<bool>,
153
154 target_type: PhantomData<T>,
155}
156
157impl<T> DatasetListBuilder<T>
158where
159 T: TryFromResponse,
160{
161 pub fn attributes_base(self) -> DatasetListBuilder<DatasetList<DatasetAttributesBase>> {
162 DatasetListBuilder {
163 core: self.core,
164 level: self.level,
165 volume: self.volume,
166 start: self.start,
167 max_items: self.max_items,
168 attributes: Some(Attrs::Base),
169 include_total: self.include_total,
170 target_type: PhantomData,
171 }
172 }
173
174 pub fn attributes_dsname(self) -> DatasetListBuilder<DatasetList<DatasetAttributesName>> {
175 DatasetListBuilder {
176 core: self.core,
177 level: self.level,
178 volume: self.volume,
179 start: self.start,
180 max_items: self.max_items,
181 attributes: Some(Attrs::Dsname),
182 include_total: self.include_total,
183 target_type: PhantomData,
184 }
185 }
186
187 pub fn attributes_vol(self) -> DatasetListBuilder<DatasetList<DatasetAttributesVolume>> {
188 DatasetListBuilder {
189 core: self.core,
190 level: self.level,
191 volume: self.volume,
192 start: self.start,
193 max_items: self.max_items,
194 attributes: Some(Attrs::Vol),
195 include_total: self.include_total,
196 target_type: PhantomData,
197 }
198 }
199}
200
201#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
202pub enum DatasetVolume {
203 Alias,
204 Migrated,
205 Vsam,
206 Volume(String),
207}
208
209impl From<String> for DatasetVolume {
210 fn from(value: String) -> Self {
211 match value.as_str() {
212 "*ALIAS" => DatasetVolume::Alias,
213 "MIGRAT" => DatasetVolume::Migrated,
214 "*VSAM*" => DatasetVolume::Vsam,
215 _ => DatasetVolume::Volume(value),
216 }
217 }
218}
219
220impl From<&str> for DatasetVolume {
221 fn from(value: &str) -> Self {
222 DatasetVolume::from(value.to_string())
223 }
224}
225
226impl std::str::FromStr for DatasetVolume {
227 type Err = std::convert::Infallible;
228
229 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
230 Ok(s.into())
231 }
232}
233
234impl std::fmt::Display for DatasetVolume {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 let s = match self {
237 DatasetVolume::Alias => "*ALIAS",
238 DatasetVolume::Migrated => "MIGRAT",
239 DatasetVolume::Volume(vol) => vol.as_ref(),
240 DatasetVolume::Vsam => "*VSAM*",
241 };
242
243 write!(f, "{}", s)
244 }
245}
246
247impl<'de> Deserialize<'de> for DatasetVolume {
248 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
249 where
250 D: serde::Deserializer<'de>,
251 {
252 let s = String::deserialize(deserializer)?;
253
254 Ok(s.parse().unwrap())
255 }
256}
257
258impl Serialize for DatasetVolume {
259 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
260 where
261 S: serde::Serializer,
262 {
263 serializer.serialize_str(&self.to_string())
264 }
265}
266
267#[derive(Clone, Copy, Debug)]
268enum Attrs {
269 Base,
270 Dsname,
271 Vol,
272}
273
274impl std::fmt::Display for Attrs {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 write!(
277 f,
278 "{}",
279 match self {
280 Attrs::Base => "base",
281 Attrs::Dsname => "dsname",
282 Attrs::Vol => "vol",
283 }
284 )
285 }
286}
287
288#[derive(Deserialize)]
289#[serde(rename_all = "camelCase")]
290struct ResponseJson<T> {
291 items: Arc<[T]>,
292 returned_rows: i32,
293 #[serde(default)]
294 more_rows: Option<bool>,
295 #[serde(default)]
296 total_rows: Option<i32>,
297 #[serde(rename = "JSONversion")]
298 json_version: i32,
299}
300
301fn build_attributes<T>(
302 request_builder: RequestBuilder,
303 list_builder: &DatasetListBuilder<T>,
304) -> RequestBuilder
305where
306 T: TryFromResponse,
307{
308 match (list_builder.attributes, list_builder.include_total) {
309 (None, Some(true)) => request_builder.header("X-IBM-Attributes", "dsname,total"),
310 (Some(attributes), include_total) => request_builder.header(
311 "X-IBM-Attributes",
312 format!(
313 "{}{}",
314 attributes,
315 if include_total == Some(true) {
316 ",total"
317 } else {
318 ""
319 }
320 ),
321 ),
322 _ => request_builder,
323 }
324}
325
326pub fn de_optional_date<'de, D: Deserializer<'de>>(
327 deserializer: D,
328) -> std::result::Result<Option<NaiveDate>, D::Error> {
329 let s: String = Deserialize::deserialize(deserializer)?;
330
331 match s.as_str() {
332 "***None***" => Ok(None),
333 s => Ok(Some(
334 NaiveDate::parse_from_str(s, "%Y/%m/%d").map_err(serde::de::Error::custom)?,
335 )),
336 }
337}
338
339fn de_optional_yes_no<'de, D>(deserializer: D) -> std::result::Result<Option<bool>, D::Error>
340where
341 D: serde::Deserializer<'de>,
342{
343 Option::<String>::deserialize(deserializer)?
344 .map(|s| match s.as_str() {
345 "YES" => Ok(true),
346 "NO" => Ok(false),
347 _ => Err(serde::de::Error::unknown_variant(&s, &["YES", "NO"])),
348 })
349 .transpose()
350}
351
352fn de_yes_no<'de, D>(deserializer: D) -> std::result::Result<bool, D::Error>
353where
354 D: serde::Deserializer<'de>,
355{
356 let s = String::deserialize(deserializer)?;
357
358 match s.as_str() {
359 "YES" => Ok(true),
360 "NO" => Ok(false),
361 _ => Err(serde::de::Error::unknown_variant(&s, &["YES", "NO"])),
362 }
363}
364
365fn ser_optional_yes_no<S>(v: &Option<bool>, serializer: S) -> std::result::Result<S::Ok, S::Error>
366where
367 S: serde::Serializer,
368{
369 match v {
370 Some(value) => serializer.serialize_str(if *value { "YES" } else { "NO" }),
371 None => serializer.serialize_none(),
372 }
373}
374
375fn ser_yes_no<S>(v: &bool, serializer: S) -> std::result::Result<S::Ok, S::Error>
376where
377 S: serde::Serializer,
378{
379 serializer.serialize_str(if *v { "YES" } else { "NO" })
380}
381
382#[cfg(test)]
383mod tests {
384 use serde::de::value::StrDeserializer;
385 use serde::de::IntoDeserializer;
386
387 use crate::tests::*;
388
389 use super::*;
390
391 #[test]
392 fn example_1() {
393 let zosmf = get_zosmf();
394
395 let manual_request = zosmf
396 .core
397 .client
398 .get("https://test.com/zosmf/restfiles/ds")
399 .query(&[("dslevel", "IBMUSER.CONFIG.*")])
400 .build()
401 .unwrap();
402
403 let list_datasets = zosmf
404 .datasets()
405 .list("IBMUSER.CONFIG.*")
406 .get_request()
407 .unwrap();
408
409 assert_eq!(
410 format!("{:?}", manual_request),
411 format!("{:?}", list_datasets)
412 );
413 }
414
415 #[test]
416 fn example_2() {
417 let zosmf = get_zosmf();
418
419 let manual_request = zosmf
420 .core
421 .client
422 .get("https://test.com/zosmf/restfiles/ds")
423 .query(&[("dslevel", "**"), ("volser", "PEVTS2")])
424 .header("X-IBM-Attributes", "base")
425 .build()
426 .unwrap();
427
428 let list_datasets_base = zosmf
429 .datasets()
430 .list("**")
431 .volume("PEVTS2")
432 .attributes_base()
433 .get_request()
434 .unwrap();
435
436 assert_eq!(
437 format!("{:?}", manual_request),
438 format!("{:?}", list_datasets_base)
439 );
440 }
441
442 #[test]
443 fn test_de_optional_yes_no() {
444 #[derive(Debug, Deserialize, PartialEq)]
445 struct Test {
446 #[serde(default, deserialize_with = "de_optional_yes_no")]
447 value: Option<bool>,
448 }
449
450 assert_eq!(
451 serde_json::from_str::<Test>(r#"{"value": "YES"}"#).unwrap(),
452 Test { value: Some(true) }
453 );
454
455 assert_eq!(
456 serde_json::from_str::<Test>(r#"{"value": "NO"}"#).unwrap(),
457 Test { value: Some(false) }
458 );
459
460 assert_eq!(
461 serde_json::from_str::<Test>(r#"{"value": null}"#).unwrap(),
462 Test { value: None }
463 );
464
465 assert_eq!(
466 serde_json::from_str::<Test>(r#"{}"#).unwrap(),
467 Test { value: None }
468 );
469
470 assert!(serde_json::from_str::<Test>(r#"{"value": "N"}"#).is_err());
471 }
472
473 #[test]
474 fn test_de_yes_no() {
475 let deserializer: StrDeserializer<serde::de::value::Error> = "YES".into_deserializer();
476 assert!(de_yes_no(deserializer).unwrap());
477
478 let deserializer: StrDeserializer<serde::de::value::Error> = "NO".into_deserializer();
479 assert!(!de_yes_no(deserializer).unwrap());
480
481 let deserializer: StrDeserializer<serde::de::value::Error> = "NONSENSE".into_deserializer();
482 assert!(de_yes_no(deserializer).is_err());
483 }
484
485 #[test]
486 fn test_ser_yes_no() {
487 let mut serializer = serde_json::Serializer::new(Vec::new());
488 ser_yes_no(&true, &mut serializer).unwrap();
489 let serialized = String::from_utf8(serializer.into_inner()).unwrap();
490 assert_eq!(serialized, r#""YES""#);
491
492 let mut serializer = serde_json::Serializer::new(Vec::new());
493 ser_yes_no(&false, &mut serializer).unwrap();
494 let serialized = String::from_utf8(serializer.into_inner()).unwrap();
495 assert_eq!(serialized, r#""NO""#);
496 }
497
498 #[test]
499 fn test_ser_optional_yes_no() {
500 let mut serializer = serde_json::Serializer::new(Vec::new());
501 ser_optional_yes_no(&Some(true), &mut serializer).unwrap();
502 let serialized = String::from_utf8(serializer.into_inner()).unwrap();
503 assert_eq!(serialized, r#""YES""#);
504
505 let mut serializer = serde_json::Serializer::new(Vec::new());
506 ser_optional_yes_no(&Some(false), &mut serializer).unwrap();
507 let serialized = String::from_utf8(serializer.into_inner()).unwrap();
508 assert_eq!(serialized, r#""NO""#);
509
510 let mut serializer = serde_json::Serializer::new(Vec::new());
511 ser_optional_yes_no(&None, &mut serializer).unwrap();
512 let serialized = String::from_utf8(serializer.into_inner()).unwrap();
513 assert_eq!(serialized, r#"null"#);
514 }
515}