1use marc_rs_derive::MarcPaths;
2use serde::de::DeserializeOwned;
3use serde::{Deserialize, Serialize};
4
5mod types;
6pub use types::*;
7
8use crate::Encoding;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum PathKind {
14 VecPush,
15 VecStructCreator,
16 VecStructField,
17 OptionInit,
18 OptionSet,
19}
20
21pub trait MarcPaths: Sized {
22 const IS_LEAF: bool;
23 fn from_marc_str(s: &str) -> Self;
24 fn to_marc_str(&self) -> String;
25 fn marc_set(&mut self, path: &str, value: &str) -> bool;
26 fn marc_get_option(&self, path: &str) -> Option<String>;
27 fn marc_get_vec(&self, path: &str) -> Option<Vec<String>>;
28 fn marc_path_kind(path: &str) -> Option<PathKind>;
29 fn marc_has_path(path: &str) -> bool;
30 fn marc_is_vec_leaf(path: &str) -> bool;
31 fn marc_creator_field() -> &'static str;
32}
33
34pub trait FromRuleValue: Sized + DeserializeOwned + Serialize {
37 fn from_rule_value(s: &str) -> Self;
38 fn to_rule_value(&self) -> String;
39}
40
41macro_rules! impl_from_rule_value {
42 ($type:ty, $other:path) => {
43 impl FromRuleValue for $type {
44 fn from_rule_value(s: &str) -> Self {
45 serde_json::from_value(serde_json::Value::String(s.to_string())).unwrap_or_else(|_| $other(s.to_string()))
46 }
47 fn to_rule_value(&self) -> String {
48 match serde_json::to_value(self).ok() {
49 Some(serde_json::Value::String(s)) => s,
50 _ => match self {
51 $other(s) => s.clone(),
52 _ => unreachable!(),
53 },
54 }
55 }
56 }
57 };
58}
59
60impl_from_rule_value!(Language, Language::Other);
61impl_from_rule_value!(Country, Country::Other);
62impl_from_rule_value!(TargetAudience, TargetAudience::Other);
63impl_from_rule_value!(ClassificationScheme, ClassificationScheme::Other);
64impl_from_rule_value!(SubjectType, SubjectType::Other);
65impl_from_rule_value!(NoteType, NoteType::Other);
66impl_from_rule_value!(LinkType, LinkType::Other);
67impl_from_rule_value!(Relator, Relator::Other);
68
69macro_rules! impl_from_rule_value_char {
70 ($type:ty, $other:path) => {
71 impl FromRuleValue for $type {
72 fn from_rule_value(s: &str) -> Self {
73 serde_json::from_value(serde_json::Value::String(s.to_string())).unwrap_or_else(|_| $other(s.chars().next().unwrap_or(' ')))
74 }
75 fn to_rule_value(&self) -> String {
76 match serde_json::to_value(self).ok() {
77 Some(serde_json::Value::String(s)) => s,
78 _ => match self {
79 $other(c) => c.to_string(),
80 _ => unreachable!(),
81 },
82 }
83 }
84 }
85 };
86}
87
88impl_from_rule_value_char!(RecordStatus, RecordStatus::Other);
89impl_from_rule_value_char!(RecordType, RecordType::Other);
90impl_from_rule_value_char!(BibliographicLevel, BibliographicLevel::Other);
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct RecordValidationIssue {
96 pub tag: String,
97 pub subfield: Option<char>,
98 pub target_path: String,
99 pub value: String,
100 pub pattern: String,
101}
102
103fn default_record_valid() -> bool {
104 true
105}
106
107macro_rules! impl_marc_leaf {
110 ($ty:ty) => {
111 impl MarcPaths for $ty {
112 const IS_LEAF: bool = true;
113 fn from_marc_str(s: &str) -> Self {
114 <$ty as FromRuleValue>::from_rule_value(s)
115 }
116 fn to_marc_str(&self) -> String {
117 <$ty as FromRuleValue>::to_rule_value(self)
118 }
119 fn marc_set(&mut self, _: &str, _: &str) -> bool {
120 false
121 }
122 fn marc_get_option(&self, _: &str) -> Option<String> {
123 None
124 }
125 fn marc_get_vec(&self, _: &str) -> Option<Vec<String>> {
126 None
127 }
128 fn marc_path_kind(_: &str) -> Option<PathKind> {
129 None
130 }
131 fn marc_has_path(_: &str) -> bool {
132 false
133 }
134 fn marc_is_vec_leaf(_: &str) -> bool {
135 false
136 }
137 fn marc_creator_field() -> &'static str {
138 ""
139 }
140 }
141 };
142}
143
144impl_marc_leaf!(Language);
145impl_marc_leaf!(Country);
146impl_marc_leaf!(TargetAudience);
147impl_marc_leaf!(ClassificationScheme);
148impl_marc_leaf!(SubjectType);
149impl_marc_leaf!(NoteType);
150impl_marc_leaf!(LinkType);
151impl_marc_leaf!(Relator);
152
153#[derive(Debug, Clone, Serialize, Deserialize, MarcPaths)]
156#[serde(rename_all = "camelCase")]
157pub struct Record {
158 #[marc(skip)]
159 pub leader: Leader,
160 #[marc(skip)]
161 #[serde(skip)]
162 pub encoding: Option<Encoding>,
163 #[marc(skip)]
165 #[serde(default = "default_record_valid")]
166 pub valid: bool,
167 #[marc(skip)]
169 #[serde(default, skip_serializing_if = "Vec::is_empty")]
170 pub validation_issues: Vec<RecordValidationIssue>,
171 #[serde(default)]
173 pub identification: Identification,
174 #[serde(default)]
176 pub coded: Coded,
177 #[serde(default)]
179 pub description: Description,
180 #[serde(default)]
182 pub notes: Notes,
183 #[serde(default)]
185 pub links: Links,
186 #[serde(default)]
188 pub associated_titles: AssociatedTitles,
189 #[serde(default)]
191 pub indexing: Indexing,
192 #[serde(default)]
194 pub responsibility: Responsibility,
195 #[serde(default)]
197 pub international: International,
198 #[serde(default)]
200 pub local: Local,
201}
202
203impl Default for Record {
204 fn default() -> Self {
205 Self {
206 leader: Leader::default(),
207 encoding: None,
208 valid: true,
209 validation_issues: Vec::new(),
210 identification: Identification::default(),
211 coded: Coded::default(),
212 description: Description::default(),
213 notes: Notes::default(),
214 links: Links::default(),
215 associated_titles: AssociatedTitles::default(),
216 indexing: Indexing::default(),
217 responsibility: Responsibility::default(),
218 international: International::default(),
219 local: Local::default(),
220 }
221 }
222}
223
224impl Record {
225 pub fn validation_report(&self) -> String {
227 if self.validation_issues.is_empty() {
228 return String::new();
229 }
230 let mut s = String::from("catalog pattern validation failed:\n");
231 for issue in &self.validation_issues {
232 let sub = issue.subfield.map(|c| format!("${}", c)).unwrap_or_else(|| "-".to_string());
233 s.push_str(&format!(
234 " tag {} subfield {} path {} value {:?} pattern {}\n",
235 issue.tag, sub, issue.target_path, issue.value, issue.pattern
236 ));
237 }
238 s
239 }
240
241 pub fn authors(&self) -> impl Iterator<Item = &Agent> {
242 self.responsibility.main_entry.iter().chain(self.responsibility.added_entries.iter())
243 }
244
245 pub fn languages(&self) -> &[Language] {
246 &self.coded.languages
247 }
248
249 pub fn titles(&self) -> Vec<&Title> {
250 let mut out = Vec::new();
251 if let Some(t) = &self.description.title {
252 out.push(t);
253 }
254 if let Some(t) = &self.associated_titles.uniform_title {
255 out.push(t);
256 }
257 out
258 }
259
260 pub fn audience(&self) -> Option<&TargetAudience> {
261 self.coded.target_audience.as_ref()
262 }
263
264 pub fn isbn(&self) -> &[Isbn] {
265 &self.identification.isbn
266 }
267
268 pub fn items(&self) -> &[Item] {
269 &self.local.items
270 }
271
272 pub fn media_type(&self) -> &RecordType {
273 &self.leader.record_type
274 }
275
276 pub fn isbn_string(&self) -> Option<String> {
278 if self.identification.isbn.is_empty() {
279 return None;
280 }
281 Some(self.identification.isbn.iter().map(|i| i.value.as_str()).collect::<Vec<_>>().join(", "))
282 }
283
284 pub fn title_main(&self) -> Option<&str> {
286 self.description.title.as_ref().map(|t| t.main.as_str())
287 }
288
289 pub fn subject_main(&self) -> Option<&str> {
291 self.indexing.subjects.first().map(|s| s.value.as_str())
292 }
293
294 pub fn keywords(&self) -> &[String] {
296 &self.indexing.uncontrolled_terms
297 }
298
299 pub fn publication_date(&self) -> Option<&str> {
301 self.description.publication.first().and_then(|p| p.date.as_deref())
302 }
303
304 pub fn page_extent(&self) -> Option<&str> {
306 self.description.physical_description.as_ref().and_then(|p| p.extent.as_deref())
307 }
308
309 pub fn dimensions(&self) -> Option<&str> {
311 self.description.physical_description.as_ref().and_then(|p| p.dimensions.as_deref())
312 }
313
314 pub fn accompanying_material_text(&self) -> Option<&str> {
316 self.description.physical_description.as_ref().and_then(|p| p.accompanying_material.as_deref())
317 }
318
319 pub fn table_of_contents_text(&self) -> Option<&str> {
321 self.notes.items.iter().find_map(|n| matches!(n.note_type, Some(NoteType::Contents)).then(|| n.text.as_str()))
322 }
323
324 pub fn abstract_text(&self) -> Option<&str> {
326 self.notes.items.iter().find_map(|n| matches!(n.note_type, Some(NoteType::Summary)).then(|| n.text.as_str()))
327 }
328
329 pub fn general_note_text(&self) -> Option<&str> {
331 self.notes.items.iter().find_map(|n| matches!(n.note_type, Some(NoteType::General)).then(|| n.text.as_str()))
332 }
333
334 pub fn lang_primary(&self) -> Option<&Language> {
336 self.coded.languages.first()
337 }
338
339 pub fn lang_original(&self) -> Option<&Language> {
341 self.coded.original_languages.first()
342 }
343}
344
345#[derive(Debug, Clone, Default, Serialize, Deserialize, MarcPaths)]
347#[serde(rename_all = "camelCase")]
348pub struct Identification {
349 #[serde(skip_serializing_if = "Option::is_none")]
350 pub record_id: Option<String>,
351 #[serde(skip_serializing_if = "Option::is_none")]
352 pub agency_id: Option<String>,
353 #[serde(skip_serializing_if = "Option::is_none")]
354 pub record_version_date: Option<String>,
355 #[serde(default, skip_serializing_if = "Vec::is_empty")]
356 pub isbn: Vec<Isbn>,
357 #[serde(default, skip_serializing_if = "Vec::is_empty")]
358 pub issn: Vec<Issn>,
359 #[serde(default, skip_serializing_if = "Vec::is_empty")]
360 pub national_bibliography_numbers: Vec<String>,
361 #[serde(default, skip_serializing_if = "Vec::is_empty")]
362 pub national_library_record_numbers: Vec<String>,
363 #[serde(default, skip_serializing_if = "Vec::is_empty")]
364 pub legal_deposit_numbers: Vec<String>,
365 #[serde(default, skip_serializing_if = "Vec::is_empty")]
366 pub lccn: Vec<String>,
367 #[serde(default, skip_serializing_if = "Vec::is_empty")]
368 pub system_control_numbers: Vec<String>,
369 #[serde(default, skip_serializing_if = "Vec::is_empty")]
370 pub patent_numbers: Vec<PatentNumber>,
371 #[serde(default, skip_serializing_if = "Vec::is_empty")]
372 pub technical_report_numbers: Vec<TechnicalReportNumber>,
373 #[serde(default, skip_serializing_if = "Vec::is_empty")]
374 pub publisher_numbers: Vec<PublisherNumber>,
375 #[serde(default, skip_serializing_if = "Vec::is_empty")]
376 pub codens: Vec<Coden>,
377 #[serde(default, skip_serializing_if = "Vec::is_empty")]
378 pub original_study_numbers: Vec<OriginalStudyNumber>,
379 #[serde(default, skip_serializing_if = "Vec::is_empty")]
380 pub government_document_numbers: Vec<GovernmentDocumentNumber>,
381 #[serde(default, skip_serializing_if = "Vec::is_empty")]
382 pub report_numbers: Vec<ReportNumber>,
383}
384
385#[derive(Debug, Clone, Default, Serialize, Deserialize, MarcPaths)]
387#[serde(rename_all = "camelCase")]
388pub struct Coded {
389 #[serde(default, skip_serializing_if = "Vec::is_empty")]
390 pub languages: Vec<Language>,
391 #[serde(default, skip_serializing_if = "Vec::is_empty")]
392 pub original_languages: Vec<Language>,
393 #[serde(skip_serializing_if = "Option::is_none")]
394 pub country: Option<Country>,
395 #[serde(skip_serializing_if = "Option::is_none")]
396 pub publication_dates: Option<String>,
397 #[serde(skip_serializing_if = "Option::is_none")]
398 pub target_audience: Option<TargetAudience>,
399 #[serde(default, skip_serializing_if = "Vec::is_empty")]
400 pub geographic_area_codes: Vec<String>,
401 #[serde(default, skip_serializing_if = "Vec::is_empty")]
402 pub time_period_codes: Vec<String>,
403 #[serde(skip_serializing_if = "Option::is_none")]
404 pub date_entered_on_file: Option<String>,
405 #[serde(skip_serializing_if = "Option::is_none")]
406 pub type_of_date: Option<String>,
407 #[serde(skip_serializing_if = "Option::is_none")]
408 pub date1: Option<String>,
409 #[serde(skip_serializing_if = "Option::is_none")]
410 pub date2: Option<String>,
411 #[serde(skip_serializing_if = "Option::is_none")]
412 pub government_publication: Option<String>,
413 #[serde(skip_serializing_if = "Option::is_none")]
414 pub modified_record: Option<String>,
415 #[serde(skip_serializing_if = "Option::is_none")]
416 pub cataloging_language: Option<String>,
417 #[serde(skip_serializing_if = "Option::is_none")]
418 pub transliteration_code: Option<String>,
419 #[serde(skip_serializing_if = "Option::is_none")]
420 pub character_set: Option<String>,
421 #[serde(skip_serializing_if = "Option::is_none")]
422 pub additional_character_set: Option<String>,
423 #[serde(skip_serializing_if = "Option::is_none")]
424 pub script_of_title: Option<String>,
425 #[serde(skip_serializing_if = "Option::is_none")]
426 pub place_of_publication_code: Option<String>,
427 #[serde(skip_serializing_if = "Option::is_none")]
428 pub cataloging_source_code: Option<String>,
429}
430
431#[derive(Debug, Clone, Default, Serialize, Deserialize, MarcPaths)]
433#[serde(rename_all = "camelCase")]
434pub struct Description {
435 #[serde(skip_serializing_if = "Option::is_none")]
436 pub title: Option<Title>,
437 #[serde(skip_serializing_if = "Option::is_none")]
438 pub edition: Option<String>,
439 #[serde(default, skip_serializing_if = "Vec::is_empty")]
440 pub publication: Vec<Publication>,
441 #[serde(skip_serializing_if = "Option::is_none")]
442 pub physical_description: Option<PhysicalDescription>,
443 #[serde(default, skip_serializing_if = "Vec::is_empty")]
444 pub series: Vec<SeriesStatement>,
445 #[serde(default, skip_serializing_if = "Vec::is_empty")]
446 pub varying_titles: Vec<VaryingTitle>,
447 #[serde(skip_serializing_if = "Option::is_none")]
448 pub frequency: Option<String>,
449}
450
451#[derive(Debug, Clone, Default, Serialize, Deserialize, MarcPaths)]
453#[serde(rename_all = "camelCase")]
454pub struct Notes {
455 #[serde(default, skip_serializing_if = "Vec::is_empty")]
456 pub items: Vec<Note>,
457}
458
459#[derive(Debug, Clone, Default, Serialize, Deserialize, MarcPaths)]
461#[serde(rename_all = "camelCase")]
462pub struct Links {
463 #[serde(default, skip_serializing_if = "Vec::is_empty")]
464 pub records: Vec<LinkedRecord>,
465}
466
467#[derive(Debug, Clone, Default, Serialize, Deserialize, MarcPaths)]
469#[serde(rename_all = "camelCase")]
470pub struct AssociatedTitles {
471 #[serde(skip_serializing_if = "Option::is_none")]
472 pub uniform_title: Option<Title>,
473 #[serde(skip_serializing_if = "Option::is_none")]
474 pub key_title: Option<Title>,
475 #[serde(default, skip_serializing_if = "Vec::is_empty")]
476 pub former_titles: Vec<Title>,
477 #[serde(default, skip_serializing_if = "Vec::is_empty")]
478 pub variant_titles: Vec<Title>,
479 #[serde(skip_serializing_if = "Option::is_none")]
480 pub abbreviated_title: Option<String>,
481}
482
483#[derive(Debug, Clone, Default, Serialize, Deserialize, MarcPaths)]
485#[serde(rename_all = "camelCase")]
486pub struct Indexing {
487 #[serde(default, skip_serializing_if = "Vec::is_empty")]
488 pub subjects: Vec<Subject>,
489 #[serde(default, skip_serializing_if = "Vec::is_empty")]
490 pub classifications: Vec<Classification>,
491 #[serde(default, skip_serializing_if = "Vec::is_empty")]
492 pub uncontrolled_terms: Vec<String>,
493}
494
495#[derive(Debug, Clone, Default, Serialize, Deserialize, MarcPaths)]
497#[serde(rename_all = "camelCase")]
498pub struct Responsibility {
499 #[marc(skip)]
500 #[serde(skip_serializing_if = "Option::is_none")]
501 pub main_entry: Option<Agent>,
502 #[marc(skip)]
503 #[serde(default, skip_serializing_if = "Vec::is_empty")]
504 pub added_entries: Vec<Agent>,
505}
506
507#[derive(Debug, Clone, Default, Serialize, Deserialize, MarcPaths)]
509#[serde(rename_all = "camelCase")]
510pub struct International {
511 #[serde(default, skip_serializing_if = "Vec::is_empty")]
512 pub cataloging_sources: Vec<CatalogingSource>,
513 #[serde(default, skip_serializing_if = "Vec::is_empty")]
514 pub location_call_numbers: Vec<LocationCallNumber>,
515 #[serde(default, skip_serializing_if = "Vec::is_empty")]
516 pub electronic_locations: Vec<ElectronicLocation>,
517 #[serde(default, skip_serializing_if = "Vec::is_empty")]
519 pub holding_institutions: Vec<String>,
520}
521
522#[derive(Debug, Clone, Default, Serialize, Deserialize, MarcPaths)]
524#[serde(rename_all = "camelCase")]
525pub struct Local {
526 #[serde(default, skip_serializing_if = "Vec::is_empty")]
527 pub items: Vec<Item>,
528}