z_osmf/datasets/
list.rs

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}