• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pulibrary / bibdata / 3165b919-56f4-4faa-baf4-b5a08621230c

18 Sep 2025 03:08PM UTC coverage: 90.866% (+0.2%) from 90.675%
3165b919-56f4-4faa-baf4-b5a08621230c

Pull #2927

circleci

Ryan Laddusaw
Add QA DSpace config
Pull Request #2927: Index theses from dspace 7+

1411 of 1519 new or added lines in 16 files covered. (92.89%)

41 existing lines in 7 files now uncovered.

8874 of 9766 relevant lines covered (90.87%)

338.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.77
/lib/bibdata_rs/src/solr/electronic_access.rs
1
use serde::{ser::Error, Deserialize, Deserializer, Serialize};
2

3
#[derive(Clone, Debug, PartialEq)]
4
pub struct ElectronicAccess {
5
    pub url: String,
6
    pub link_text: String,
7
    pub link_description: Option<String>,
8
    pub iiif_manifest_paths: Option<String>,
9
    pub digital_content: Option<DigitalContent>,
10
}
11

12
#[derive(Clone, Debug, PartialEq)]
13
pub struct DigitalContent {
14
    pub url: String,
15
    pub link_text: Vec<String>,
16
}
17

18
impl Serialize for ElectronicAccess {
19
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
40✔
20
    where
40✔
21
        S: serde::Serializer,
40✔
22
    {
23
        let mut hash = serde_json::Map::new();
40✔
24

25
        // Main URL
26
        let mut notes = vec![self.link_text.clone()];
40✔
27
        if let Some(desc) = &self.link_description {
40✔
28
            notes.push(desc.clone());
39✔
29
        }
39✔
30
        hash.shift_insert(
40✔
31
            0,
32
            self.url.clone(),
40✔
33
            serde_json::Value::Array(notes.into_iter().map(serde_json::Value::String).collect()),
40✔
34
        );
35

36
        // Digital Content
37
        if let Some(dc) = &self.digital_content {
40✔
38
            hash.shift_insert(
38✔
39
                1,
38✔
40
                dc.url.clone(),
38✔
41
                serde_json::Value::Array(
38✔
42
                    dc.link_text
38✔
43
                        .iter()
38✔
44
                        .cloned()
38✔
45
                        .map(serde_json::Value::String)
38✔
46
                        .collect(),
38✔
47
                ),
38✔
48
            );
38✔
49
        }
38✔
50

51
        // IIIF Manifest Paths
52
        if let Some(iiif_url) = &self.iiif_manifest_paths {
40✔
53
            let mut iiif_map = serde_json::Map::new();
38✔
54
            iiif_map.insert(
38✔
55
                "ephemera_ark".to_string(),
38✔
56
                serde_json::Value::String(iiif_url.clone()),
38✔
57
            );
38✔
58
            hash.shift_insert(
38✔
59
                2,
38✔
60
                "iiif_manifest_paths".to_string(),
38✔
61
                serde_json::Value::Object(iiif_map),
38✔
62
            );
38✔
63
        }
38✔
64

65
        serializer.serialize_str(&serde_json::to_string(&hash).map_err(S::Error::custom)?)
40✔
66
    }
40✔
67
}
68

69
impl<'de> Deserialize<'de> for ElectronicAccess {
70
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2✔
71
    where
2✔
72
        D: Deserializer<'de>,
2✔
73
    {
74
        // The input is a JSON string containing a map with possible keys:
75
        // {url: [link_text, link_description?], digital_content_url: [link_text], iiif_manifest_paths: {ephemera_ark: url}}
76
        let s = String::deserialize(deserializer)?;
2✔
77
        let map: serde_json::Map<String, serde_json::Value> =
2✔
78
            serde_json::from_str(&s).map_err(serde::de::Error::custom)?;
2✔
79

80
        // Main URL and notes
81
        let (url, notes) = map
2✔
82
            .iter()
2✔
83
            .find(|(k, _)| *k != "iiif_manifest_paths" && *k != "digital_content_url")
2✔
84
            .ok_or_else(|| serde::de::Error::custom("No url found in this ElectronicAccess"))?;
2✔
85

86
        let notes_arr = notes
2✔
87
            .as_array()
2✔
88
            .ok_or_else(|| serde::de::Error::custom("Notes are not an array"))?;
2✔
89

90
        let link_text = notes_arr
2✔
91
            .first()
2✔
92
            .and_then(|v| v.as_str())
2✔
93
            .ok_or_else(|| serde::de::Error::custom("No link text found in this ElectronicAccess"))?
2✔
94
            .to_owned();
2✔
95

96
        let link_description = notes_arr
2✔
97
            .get(1)
2✔
98
            .and_then(|v| v.as_str())
2✔
99
            .map(|s| s.to_owned());
2✔
100

101
        // Digital Content
102
        let digital_content = map
2✔
103
            .iter()
2✔
104
            .find(|(k, _)| *k != "iiif_manifest_paths" && *k != url)
2✔
105
            .and_then(|(dc_url, dc_notes)| {
2✔
UNCOV
106
                let dc_notes_arr = dc_notes.as_array()?;
×
UNCOV
107
                let dc_link_texts: Vec<String> = dc_notes_arr
×
UNCOV
108
                    .iter()
×
UNCOV
109
                    .filter_map(|v| v.as_str().map(|s| s.to_owned()))
×
UNCOV
110
                    .collect();
×
UNCOV
111
                if dc_link_texts.is_empty() {
×
UNCOV
112
                    None
×
113
                } else {
UNCOV
114
                    Some(DigitalContent {
×
UNCOV
115
                        url: dc_url.clone(),
×
UNCOV
116
                        link_text: dc_link_texts,
×
UNCOV
117
                    })
×
118
                }
UNCOV
119
            });
×
120

121
        // IIIF Manifest URL
122
        let iiif_manifest_url = map
2✔
123
            .get("iiif_manifest_paths")
2✔
124
            .and_then(|v| v.as_object())
2✔
125
            .and_then(|obj| obj.get("ephemera_ark"))
2✔
126
            .and_then(|v| v.as_str())
2✔
127
            .map(|s| s.to_owned());
2✔
128

129
        Ok(ElectronicAccess {
2✔
130
            url: url.clone(),
2✔
131
            link_text,
2✔
132
            link_description,
2✔
133
            iiif_manifest_paths: iiif_manifest_url,
2✔
134
            digital_content,
2✔
135
        })
2✔
136
    }
2✔
137
}
138
#[cfg(test)]
139
mod tests {
140
    use super::*;
141
    use pretty_assertions::assert_eq;
142

143
    #[test]
144
    fn it_can_serialize_digital_content_to_json() {
1✔
145
        let access = ElectronicAccess {
1✔
146
            url: "https://figgy-staging.princeton.edu/catalog/af4a941d-96a4-463e-9043-cfa512e5eddd".to_string(),
1✔
147
            link_text: "Online Content".to_string(),
1✔
148
            link_description: Some("Born Digital Monographic Reports and Papers".to_string()),
1✔
149
            iiif_manifest_paths: Some("https://figgy.princeton.edu/concern/ephemera_folders/af4a941d-96a4-463e-9043-cfa512e5eddd/manifest".to_string()),
1✔
150
            digital_content: Some(DigitalContent {
1✔
151
                link_text: vec!["Digital Content".to_string()],
1✔
152
                url: "https://catalog-staging.princeton.edu/catalog/af4a941d-96a4-463e-9043-cfa512e5eddd#view".to_string(),
1✔
153
            })
1✔
154
        };
1✔
155
        assert_eq!(
1✔
156
            serde_json::to_string(&access).unwrap(),
1✔
157
            r#""{\"https://figgy-staging.princeton.edu/catalog/af4a941d-96a4-463e-9043-cfa512e5eddd\":[\"Online Content\",\"Born Digital Monographic Reports and Papers\"],\"https://catalog-staging.princeton.edu/catalog/af4a941d-96a4-463e-9043-cfa512e5eddd#view\":[\"Digital Content\"],\"iiif_manifest_paths\":{\"ephemera_ark\":\"https://figgy.princeton.edu/concern/ephemera_folders/af4a941d-96a4-463e-9043-cfa512e5eddd/manifest\"}}""#
158
        );
159
    }
1✔
160
    #[test]
161
    fn it_can_serialize_to_json() {
1✔
162
        let access = ElectronicAccess {
1✔
163
            url: "http://arks.princeton.edu/ark:/88435/dch989rf19q".to_owned(),
1✔
164
            link_text: "Electronic Resource".to_owned(),
1✔
165
            link_description: None,
1✔
166
            iiif_manifest_paths: None,
1✔
167
            digital_content: None,
1✔
168
        };
1✔
169
        assert_eq!(
1✔
170
            serde_json::to_string(&access).unwrap(),
1✔
171
            r#""{\"http://arks.princeton.edu/ark:/88435/dch989rf19q\":[\"Electronic Resource\"]}""#
172
        );
173
    }
1✔
174

175
    #[test]
176
    fn it_can_deserialize_from_json() {
1✔
177
        let json =
1✔
178
            r#""{\"http://arks.princeton.edu/ark:/88435/dch989rf19q\":[\"Electronic Resource\"]}""#;
1✔
179
        let parsed: ElectronicAccess = serde_json::from_str(&json).unwrap();
1✔
180
        assert_eq!(
1✔
181
            parsed.url,
182
            "http://arks.princeton.edu/ark:/88435/dch989rf19q"
183
        );
184
        assert_eq!(parsed.link_text, "Electronic Resource");
1✔
185
        assert!(parsed.link_description.is_none());
1✔
186
    }
1✔
187

188
    #[test]
189
    fn it_can_deserialize_electronic_access_with_link_description_from_json() {
1✔
190
        let json = r#""{\"http://arks.princeton.edu/ark:/88435/dch989rf19q\":[\"Electronic Resource\",\"My nice description\"]}""#;
1✔
191
        let parsed: ElectronicAccess = serde_json::from_str(&json).unwrap();
1✔
192
        assert_eq!(
1✔
193
            parsed.url,
194
            "http://arks.princeton.edu/ark:/88435/dch989rf19q"
195
        );
196
        assert_eq!(parsed.link_text, "Electronic Resource");
1✔
197
        assert_eq!(parsed.link_description.unwrap(), "My nice description");
1✔
198
    }
1✔
199
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc