acorn_lib/schema/
raid.rs

1//! ## Research activity identifier (RAiD) metadata schema
2//!
3//! See <https://metadata.raid.org/en/v1.6/index.html> for official documentation on schema.
4//!
5//! Use ACORN to generate JSON schema for RAiD metadata with `acorn schema --raid`
6use crate::schema::validate::{is_iso8601_date, is_iso8601_year, is_orcid, is_raid, is_ror, is_unix_epoch};
7use crate::util::{read_file, Label};
8use crate::License;
9use bon::{builder, Builder};
10use derive_more::Display;
11use schemars::{schema_for, JsonSchema};
12use serde::{Deserialize, Serialize};
13use serde_with::skip_serializing_none;
14use std::path::PathBuf;
15use tracing::error;
16use validator::Validate;
17
18/// Allowed values for access types
19#[derive(Clone, Debug, Default, Deserialize, Display, JsonSchema, Serialize)]
20#[serde(rename = "kebab-case")]
21pub enum AccessType {
22    /// Open access
23    #[default]
24    #[display("open-access")]
25    #[serde(alias = "https://vocabularies.coar-repositories.org/access_rights/c_abf2/")]
26    OpenAccess,
27    /// Embargoed access
28    #[display("embargoed-access")]
29    #[serde(alias = "https://vocabularies.coar-repositories.org/access_rights/c_f1cf/")]
30    EmbargoedAccess,
31}
32/// CRediT role
33///
34/// Taxonomy of 14 roles that can be used to describe the key types of contributions typically made to the production and publication of research output such as research articles.
35///
36/// See <https://www.niso.org/publications/z39104-2022-credit>
37#[derive(Clone, Debug, Deserialize, Display, JsonSchema, Serialize)]
38#[serde(rename = "kebab-case")]
39pub enum CreditRole {
40    /// Ideas; formulation or evolution of overarching research goals and aims.
41    #[display("conceptualization")]
42    #[serde(alias = "https://credit.niso.org/contributor-roles/conceptualization/")]
43    Conceptualization,
44    /// Management activities to annotate (produce metadata), scrub data and maintain research data (including software code, where it is necessary for interpreting the data itself) for initial use and later re-use.
45    #[display("data-curation")]
46    #[serde(alias = "https://credit.niso.org/contributor-roles/data-curation/")]
47    DataCuration,
48    /// Application of statistical, mathematical, computational, or other formal techniques to analyze or synthesize study data.
49    #[display("formal-analysis")]
50    #[serde(alias = "https://credit.niso.org/contributor-roles/formal-analysis/")]
51    FormalAnalysis,
52    /// Acquisition of the financial support for the project leading to this publication.
53    #[display("funding-acquisition")]
54    #[serde(alias = "https://credit.niso.org/contributor-roles/funding-acquisition/")]
55    FundingAcquisition,
56    /// Conducting a research and investigation process, specifically performing the experiments, or data/evidence collection.
57    #[display("investigation")]
58    #[serde(alias = "https://credit.niso.org/contributor-roles/investigation/")]
59    Investigation,
60    /// Development or design of methodology; creation of models.
61    #[display("methodology")]
62    #[serde(alias = "https://credit.niso.org/contributor-roles/methodology/")]
63    Methodology,
64    /// Management and coordination responsibility for the research activity planning and execution.
65    #[display("project-administration")]
66    #[serde(alias = "https://credit.niso.org/contributor-roles/project-administration/")]
67    ProjectAdministration,
68    /// Provision of study materials, reagents, materials, patients, laboratory samples, animals, instrumentation, computing resources, or other analysis tools.
69    #[display("resources")]
70    #[serde(alias = "https://credit.niso.org/contributor-roles/resources/")]
71    Resources,
72    /// Programming, software development; designing computer programs; implementation of the computer code and supporting algorithms; testing of existing code components.
73    #[display("software")]
74    #[serde(alias = "https://credit.niso.org/contributor-roles/software/")]
75    Software,
76    /// Oversight and leadership responsibility for the research activity planning and execution, including mentorship external to the core team.
77    #[display("supervision")]
78    #[serde(alias = "https://credit.niso.org/contributor-roles/supervision/")]
79    Supervision,
80    /// Verification, whether as a part of the activity or separate, of the overall replication/reproducibility of results/experiments and other research outputs.
81    #[display("validation")]
82    #[serde(alias = "https://credit.niso.org/contributor-roles/validation/")]
83    Validation,
84    /// Preparation, creation and/or presentation of the published work, specifically visualization/data presentation.
85    #[display("visualization")]
86    #[serde(alias = "https://credit.niso.org/contributor-roles/visualization/")]
87    Visualization,
88    /// Preparation, creation and/or presentation of the published work, specifically writing the initial draft (including substantive translation).
89    #[display("writing-original-draft")]
90    #[serde(alias = "https://credit.niso.org/contributor-roles/writing-original-draft/")]
91    WritingOriginalDraft,
92    /// Preparation, creation and/or presentation of the published work by those from the original research group, specifically critical review, commentary or revision - including pre- or post-publication stages
93    #[display("writing-review-editing")]
94    #[serde(alias = "https://credit.niso.org/contributor-roles/writing-review-editing/")]
95    WritingReviewEditing,
96}
97/// Description types
98#[derive(Clone, Debug, Deserialize, Display, JsonSchema, Serialize)]
99#[serde(rename = "kebab-case")]
100pub enum DescriptionType {
101    /// Primary description (i.e., a preferred full description or abstract)
102    #[serde(alias = "https://vocabulary.raid.org/description.type.schema/318")]
103    Primary,
104    /// An alternative description (i.e., an additional or supplementary full description or abstract)
105    #[serde(alias = "https://vocabulary.raid.org/description.type.schema/319")]
106    Alternative,
107    /// Brief description (i.e., a shorter version of the primary description)
108    #[serde(alias = "https://vocabulary.raid.org/description.type.schema/3")]
109    Brief,
110    /// Significance statement
111    #[display("significance-statement")]
112    #[serde(alias = "https://vocabulary.raid.org/description.type.schema/9")]
113    SignificanceStatement,
114    /// Methods
115    #[serde(alias = "https://vocabulary.raid.org/description.type.schema/8")]
116    Methods,
117    /// Objectives
118    #[serde(alias = "https://vocabulary.raid.org/description.type.schema/7")]
119    Objectives,
120    /// Acknowledgements (i.e., for recognition of people not listed as Contributors or organizations not listed as organizations)
121    #[serde(alias = "https://vocabulary.raid.org/description.type.schema/392")]
122    Acknowledgements,
123    /// Other (i.e., any other descriptive information such as a note)
124    #[serde(alias = "https://vocabulary.raid.org/description.type.schema/6")]
125    Other,
126}
127/// Category of input, output, or process document
128#[derive(Clone, Debug, Deserialize, Display, JsonSchema, Serialize)]
129#[serde(rename = "kebab-case")]
130pub enum ObjectCategoryType {
131    /// Output
132    #[display("output")]
133    #[serde(alias = "https://vocabulary.raid.org/relatedObject.category.id/190")]
134    Output,
135    /// Input
136    #[display("input")]
137    #[serde(alias = "https://vocabulary.raid.org/relatedObject.category.id/191")]
138    Input,
139    /// Internal process document or artifact
140    #[display("internal-process-document")]
141    #[serde(alias = "https://vocabulary.raid.org/relatedObject.category.id/192")]
142    InternalProcessDocument,
143}
144/// Type of input, output, or process document
145#[derive(Clone, Debug, Deserialize, Display, JsonSchema, Serialize)]
146#[serde(rename = "kebab-case")]
147pub enum ObjectType {
148    /// Output management plan
149    #[display("output-management-plan")]
150    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/247")]
151    OutputManagementPlan,
152    /// Conference poster
153    #[display("conference-poster")]
154    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/248")]
155    ConferencePoster,
156    /// Workflow
157    #[display("workflow")]
158    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/249")]
159    Workflow,
160    /// Journal article
161    #[display("journal-article")]
162    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/250")]
163    JournalArticle,
164    /// Standard
165    #[display("standard")]
166    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/251")]
167    Standard,
168    /// Report
169    #[display("report")]
170    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/252")]
171    Report,
172    /// Dissertation
173    #[display("dissertation")]
174    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/253")]
175    Dissertation,
176    /// Preprint
177    #[display("preprint")]
178    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/254")]
179    Preprint,
180    /// Data paper
181    #[display("data-paper")]
182    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/255")]
183    DataPaper,
184    /// Computational notebook (e.g., Jupyter notebook)
185    #[display("computational-notebook")]
186    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/256")]
187    ComputationalNotebook,
188    /// Image
189    #[display("image")]
190    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/257")]
191    Image,
192    /// Book
193    #[display("book")]
194    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/258")]
195    Book,
196    /// Software
197    #[display("software")]
198    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/259")]
199    Software,
200    /// Event
201    #[display("event")]
202    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/260")]
203    Event,
204    /// Sound
205    #[display("sound")]
206    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/261")]
207    Sound,
208    /// Conference proceeding
209    #[display("conference-proceeding")]
210    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/262")]
211    ConferenceProceeding,
212    /// Model
213    #[display("model")]
214    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/263")]
215    Model,
216    /// Conference paper
217    #[display("conference-paper")]
218    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/264")]
219    ConferencePaper,
220    /// Text
221    #[display("text")]
222    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/265")]
223    Text,
224    /// Instrument
225    #[display("instrument")]
226    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/266")]
227    Instrument,
228    /// Learning object
229    #[display("learning-object")]
230    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/267")]
231    LearningObject,
232    /// Prize (excluding funded awards)
233    #[display("prize")]
234    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/268")]
235    Prize,
236    /// Dataset
237    #[display("dataset")]
238    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/269")]
239    Dataset,
240    /// Physical object
241    #[display("physical-object")]
242    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/270")]
243    PhysicalObject,
244    /// Book chapter
245    #[display("book-chapter")]
246    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/271")]
247    BookChapter,
248    /// Funding
249    ///
250    /// *Note*
251    /// > Includes grants or other cash or in-kind awards, but not prizes
252    #[display("funding")]
253    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/272")]
254    Funding,
255    /// Audiovisual
256    #[display("audiovisual")]
257    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/273")]
258    Audiovisual,
259    /// Service
260    #[display("service")]
261    #[serde(alias = "https://vocabulary.raid.org/relatedObject.type.schema/274")]
262    Service,
263}
264/// Organization role identifier
265#[derive(Clone, Debug, Deserialize, Display, JsonSchema, Serialize)]
266#[serde(rename = "kebab-case")]
267pub enum OrganizationRoleType {
268    /// Lead research organization
269    #[display("lead-research-organization")]
270    #[serde(alias = "https://vocabulary.raid.org/organisation.role.schema/182")]
271    LeadResearchOrganization,
272    /// Other research organization
273    #[display("other-research-organization")]
274    #[serde(alias = "https://vocabulary.raid.org/organisation.role.schema/183")]
275    OtherResearchOrganization,
276    /// Partner organization (i.e., a non-research organization, such as an industry, government, or community partner that is collaborating on the project or activity, as a research partner rather than a hired consultant or contractor)
277    #[display("partner-organization")]
278    #[serde(alias = "https://vocabulary.raid.org/organisation.role.schema/184")]
279    PartnerOrganization,
280    /// Contractor (i.e., a consulting organization hired by the project)
281    #[display("contractor")]
282    #[serde(alias = "https://vocabulary.raid.org/organisation.role.schema/185")]
283    Contractor,
284    /// Funder (i.e., an organization underwriting the research via a cash or in-kind grant, prize, or investment, but not otherwise listed as a research organization, partner organization or contractor)
285    #[display("funder")]
286    #[serde(alias = "https://vocabulary.raid.org/organisation.role.schema/186")]
287    Funder,
288    /// Facility (i.e., an organization providing access to physical or digital infrastructure, but not otherwise listed as a research organization, partner organization or contractor)
289    #[display("facility")]
290    #[serde(alias = "https://vocabulary.raid.org/organisation.role.schema/187")]
291    Facility,
292    /// Other Organiation not covered by the roles above
293    #[display("other-organization")]
294    #[serde(alias = "https://vocabulary.raid.org/organisation.role.schema/188")]
295    OtherOrganization,
296}
297/// Represents a contributor's administrative position on a project (such as their position on a grant application)
298///
299/// <div class="warning">Use contributor role to define scientific or scholarly contributions</div>
300#[derive(Clone, Debug, Deserialize, Display, JsonSchema, Serialize)]
301#[serde(rename = "kebab-case")]
302pub enum PositionType {
303    /// Principal Investigator
304    #[display("principal-investigator")]
305    #[serde(alias = "ChiefInvestigator", alias = "https://vocabulary.raid.org/contributor.position.schema/307")]
306    PrincipalInvestigator,
307    /// Co-Investigator
308    #[display("co-investigator")]
309    #[serde(alias = "collaborator", alias = "https://vocabulary.raid.org/contributor.position.schema/308")]
310    CoInvestigator,
311    /// Partner Investigator (e.g., industry, government, or community collaborator)
312    #[display("partner-investigator")]
313    #[serde(alias = "https://vocabulary.raid.org/contributor.position.schema/309")]
314    PartnerInvestigator,
315    /// Consultant (e.g., someone hired as a contract researcher by the project)
316    #[display("consultant")]
317    #[serde(alias = "https://vocabulary.raid.org/contributor.position.schema/310")]
318    Consultant,
319    /// Other Participant not covered by one of the positions above, e.g., "member" or "other significant contributor"
320    #[display("other")]
321    #[serde(alias = "https://vocabulary.raid.org/contributor.position.schema/311")]
322    Other,
323}
324/// RAiD Relation Type
325///
326/// Describes the relationship being one activity and another
327#[derive(Clone, Debug, Deserialize, Display, JsonSchema, Serialize)]
328#[serde(rename = "kebab-case")]
329pub enum RelatedRaidType {
330    /// Obsoletes
331    /// > For resolving duplicate RAiDs
332    #[serde(alias = "https://vocabulary.raid.org/relatedRaid.type.schema/198")]
333    Obsoletes,
334    /// Is source of
335    #[display("is-source-of")]
336    #[serde(alias = "https://vocabulary.raid.org/relatedRaid.type.schema/199")]
337    IsSourceOf,
338    /// Is derived from
339    #[display("is-derived-from")]
340    #[serde(alias = "https://vocabulary.raid.org/relatedRaid.type.schema/200")]
341    IsDerivedFrom,
342    /// Has part
343    #[display("has-part")]
344    #[serde(alias = "https://vocabulary.raid.org/relatedRaid.type.schema/201")]
345    HasPart,
346    /// Is part of
347    #[display("is-part-of")]
348    #[serde(alias = "https://vocabulary.raid.org/relatedRaid.type.schema/202")]
349    IsPartOf,
350    /// Is continued by
351    #[display("is-continued-by")]
352    #[serde(alias = "https://vocabulary.raid.org/relatedRaid.type.schema/203")]
353    IsContinuedBy,
354    /// Continues
355    #[serde(alias = "https://vocabulary.raid.org/relatedRaid.type.schema/204")]
356    Continues,
357    /// Is obsoleted by
358    /// > For resolving duplicate RAiDs
359    #[display("is-obsoleted-by")]
360    #[serde(alias = "https://vocabulary.raid.org/relatedRaid.type.schema/205")]
361    IsObsoletedBy,
362}
363/// Allowed values for title identifiers
364#[derive(Clone, Debug, Deserialize, Display, JsonSchema, Serialize)]
365pub enum TitleType {
366    /// Title acronym
367    #[serde(alias = "https://vocabulary.raid.org/title.type.schema/156")]
368    Acronym,
369    /// Alternative title, including subtitle or other supplemental title
370    #[serde(alias = "https://vocabulary.raid.org/title.type.schema/4")]
371    Alternative,
372    /// Preferred full or long title
373    #[serde(alias = "https://vocabulary.raid.org/title.type.schema/5")]
374    Primary,
375    /// Abreviated title
376    #[serde(alias = "https://vocabulary.raid.org/title.type.schema/157")]
377    Short,
378}
379/// Metadata schema block containing RAiD access information
380///
381/// See <https://metadata.raid.org/en/v1.6/core/access.html>
382#[derive(Builder, Clone, Debug, Deserialize, JsonSchema, Serialize, Validate)]
383#[serde(deny_unknown_fields, rename_all = "camelCase")]
384pub struct Access {
385    /// Access type
386    #[validate(nested)]
387    #[serde(rename = "type")]
388    pub access_type: AccessIdentifier,
389    /// Date an embargo on access to the RAiD metadata ends
390    /// ### Format
391    /// > [ISO 8601] standard date (e.g., `YYYY-MM-DD`)
392    ///
393    /// <div class="warning">Mandatory if access type is "embargoed"</div>
394    ///
395    /// <div class="warning">Embargo expiration dates may not lay more than 18 months from the date the RAiD was registered. Year, month, and day mush be specified.</div>
396    ///
397    /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
398    #[validate(custom(function = "is_iso8601_date"))]
399    pub embargo_expiry: Option<String>,
400    /// Access statement
401    ///
402    /// <div class="warning">Mandatory if access type is not "open"</div>
403    #[validate(nested)]
404    pub statement: Option<AccessStatement>,
405}
406/// Access type identifier
407#[derive(Builder, Clone, Debug, Deserialize, JsonSchema, Serialize, Validate)]
408#[serde(deny_unknown_fields, rename_all = "camelCase")]
409pub struct AccessIdentifier {
410    /// Type of access granted to a RAiD metadata record
411    pub id: AccessType,
412    /// URI of the access type schema
413    #[validate(url)]
414    pub schema_uri: Option<String>,
415}
416/// Metadata schema block containing an explanation for any access type that is not "open", with the explanation's associated properties
417#[derive(Builder, Clone, Debug, Deserialize, JsonSchema, Serialize, Validate)]
418#[serde(deny_unknown_fields, rename_all = "camelCase")]
419pub struct AccessStatement {
420    /// The text of an access statement that explains any restrictions on access
421    #[validate(length(min = 1, max = 1000))]
422    pub text: Option<String>,
423    /// The language of the access statement
424    #[validate(nested)]
425    pub language: Option<Language>,
426}
427/// Metadata schema block containing alternative local or global identifiers for the project or activity associated with the RAiD
428#[derive(Builder, Clone, Debug, Deserialize, JsonSchema, Serialize, Validate)]
429#[serde(deny_unknown_fields, rename_all = "camelCase")]
430pub struct AlternateIdentifier {
431    /// Identifier other than the RAiD applied to the project or activity
432    /// ### Example
433    /// > ACORN research activity data (RAD) [identifier]
434    ///
435    /// [identifier]: ./struct.Metadata.html#structfield.identifier
436    pub id: String,
437    /// Free text description of the type of alternate identifier supplied
438    #[serde(rename = "type")]
439    pub alternate_identifier_type: String,
440}
441/// Link to another website related to the project or activity
442#[derive(Builder, Clone, Debug, Deserialize, JsonSchema, Serialize, Validate)]
443#[serde(deny_unknown_fields, rename_all = "camelCase")]
444pub struct AlternateUrl {
445    #[validate(url)]
446    url: String,
447}
448/// Metadata schema block containing a contributor to a RAiD and their associated properties
449///
450/// See <https://metadata.raid.org/en/v1.6/core/contributors.html>
451#[skip_serializing_none]
452#[derive(Builder, Clone, Debug, Deserialize, JsonSchema, Serialize, Validate)]
453#[serde(deny_unknown_fields, rename_all = "camelCase")]
454pub struct Contributor {
455    /// Contributor (person) associated with a project or activity identified by a persistent identifier (PID)
456    ///
457    /// Should be a valid *full* ORCiD
458    /// ### Example
459    /// > "<https://orcid.org/0000-0000-0000-0000>"
460    #[validate(custom(function = "is_orcid"))]
461    pub id: String,
462    /// URI of the contributor identifier schema
463    ///
464    /// <div class="warning">PID is required and (currently) only [ORCID] and [ISNI] are allowed</div>
465    ///
466    /// [ISNI]: https://isni.org/
467    /// [ORCID]: https://orcid.org/
468    #[validate(url)]
469    pub schema_uri: Option<String>,
470    /// Contibutor status
471    pub status: String,
472    /// Text describing status
473    pub status_message: Option<String>,
474    /// Contributor's administrative position on a project or activity
475    #[validate(nested)]
476    pub position: Vec<ContributorPosition>,
477    /// Flag indicating that the contributor as a project leader
478    ///
479    /// Allowed values: `Yes` or `Null`
480    pub leader: bool,
481    /// Flag indicating that the contributor as a project contact
482    ///
483    /// Allowed values: `Yes` or `Null`
484    pub contact: bool,
485    /// Contributor email
486    #[validate(email)]
487    pub email: Option<String>,
488    /// Contributor's role(s) on a project or activity
489    #[validate(nested)]
490    pub role: Option<Vec<Role>>,
491    /// Contributor UUID
492    pub uuid: Option<String>,
493}
494/// Metadata schema sub-block describing a contributor's administrative position on a project or activity
495///
496/// See <https://metadata.raid.org/en/v1.6/core/contributors.html#contributor-position>
497#[derive(Builder, Clone, Debug, Deserialize, JsonSchema, Serialize, Validate)]
498#[serde(deny_unknown_fields, rename_all = "camelCase")]
499pub struct ContributorPosition {
500    /// Contributor's administrative position in the project
501    /// ### Example
502    /// > "Principal Investigator"
503    pub id: PositionType,
504    /// URI of the position schema used
505    ///
506    /// <div class="warning">Controlled list of schemas is informed by Simon Cox's [Project Ontology], [OpenAIRE] "Project" guidelines, NIH definitions, ARC definitions, and DataCite Metadata Schema 4.4 Appendix 1 Table 5 "Description of contributorType".</div>
507    ///
508    /// [OpenAIRE]: https://guidelines.openaire.eu/en/latest/
509    /// [Project Ontology]: http://linked.data.gov.au/def/project
510    #[validate(url)]
511    pub schema_uri: Option<String>,
512    /// Dates associated with contributor's involvement in a project or activity
513    #[validate(nested)]
514    #[serde(flatten)]
515    pub date: Date,
516}
517///  Start and end dates for the associated metadata
518#[skip_serializing_none]
519#[derive(Builder, Clone, Debug, Deserialize, JsonSchema, Serialize, Validate)]
520#[serde(deny_unknown_fields, rename_all = "camelCase")]
521pub struct Date {
522    /// Associated data start date
523    /// ### Format
524    /// > [ISO 8601] standard date (e.g., `YYYY-MM-DD`)
525    ///
526    /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
527    #[validate(custom(function = "is_iso8601_date"))]
528    pub start_date: String,
529    /// Associated data end date
530    /// ### Format
531    /// > [ISO 8601] standard date (e.g., `YYYY-MM-DD`)
532    ///
533    /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
534    #[validate(custom(function = "is_iso8601_date"))]
535    pub end_date: Option<String>,
536}
537/// Metadata schema block containing the description of the RAiD and associated properties
538///
539/// See <https://metadata.raid.org/en/v1.6/core/descriptions.html>
540#[skip_serializing_none]
541#[derive(Builder, Clone, Debug, Deserialize, JsonSchema, Serialize, Validate)]
542#[serde(deny_unknown_fields, rename_all = "camelCase")]
543pub struct Description {
544    /// Description text
545    #[validate(length(min = 3, max = 1000))]
546    pub text: String,
547    /// Description type information
548    #[validate(nested)]
549    #[serde(rename = "type")]
550    pub description_type: DescriptionIdentifier,
551    /// Language of the description text
552    #[validate(nested)]
553    pub language: Option<Language>,
554}
555/// Metadata schema block declaring the type of description
556#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, Validate)]
557#[serde(deny_unknown_fields, rename_all = "camelCase")]
558pub struct DescriptionIdentifier {
559    /// Description identifier
560    pub id: DescriptionType,
561    /// URI of the associated description schema
562    #[validate(url)]
563    pub schema_uri: Option<String>,
564}
565/// Metadata schema block containing information about the associated type
566#[derive(Builder, Clone, Debug, Deserialize, Serialize, JsonSchema, Validate)]
567#[serde(deny_unknown_fields, rename_all = "camelCase")]
568pub struct Identifier {
569    /// Type identifier
570    pub id: String,
571    /// URI of the associated type schema
572    #[validate(url)]
573    pub schema_uri: Option<String>,
574}
575/// Metadata schema sub-block containing free-text keyword describing a project plus associated properties
576#[derive(Builder, Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
577#[serde(deny_unknown_fields, rename_all = "camelCase")]
578pub struct Keyword {
579    /// Unconstrained keyword or key phrase describing the project or activity
580    pub text: String,
581    /// Language of the keyword
582    #[validate(nested)]
583    pub language: Option<Language>,
584}
585/// Metadata schema block declaring the language of the associated text
586#[derive(Builder, Clone, Debug, Deserialize, Serialize, JsonSchema, Validate)]
587#[serde(deny_unknown_fields, rename_all = "camelCase")]
588pub struct Language {
589    /// Language used for the associated text, identified by a code or another identifier
590    /// ### Examples
591    /// - "eng"
592    /// - "fra"
593    /// - "jpn"
594    ///
595    /// <div class="warning">Limited to <a href="https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes">ISO 639:2023 (Set 3)</a></div>
596    #[validate(length(equal = 3))]
597    pub id: String,
598    /// URI of the associated type schema
599    #[validate(url)]
600    pub schema_uri: Option<String>,
601}
602/// Research Activity Identifier (RAiD) Metadata
603#[skip_serializing_none]
604#[derive(Builder, Clone, Debug, Display, Deserialize, Serialize, JsonSchema, Validate)]
605#[builder(start_fn = init)]
606#[display("({identifier})")]
607#[serde(deny_unknown_fields, rename_all = "camelCase")]
608pub struct Metadata {
609    /// RAiD metadata metadata
610    #[validate(nested)]
611    pub metadata: Option<MetadataMetadata>,
612    /// Metadata schema block containing the RAiD name and associated properties
613    #[validate(nested)]
614    pub identifier: MetadataIdentifier,
615    /// Dates associated with the RAiD metadata
616    #[validate(nested)]
617    pub date: Option<Date>,
618    /// Title metadata of the RAiD
619    ///
620    /// <div class="warning">One and only one title should be identified as "primary"</div>
621    #[validate(nested, length(min = 1))]
622    pub title: Option<Vec<Title>>,
623    /// Description metadata of the RAiD
624    #[validate(nested)]
625    pub description: Option<Vec<Description>>,
626    /// Contributors to the RAiD
627    #[validate(nested, length(min = 1))]
628    pub contributor: Vec<Contributor>,
629    /// Organizations associated with the RAiD
630    ///
631    /// <div class="warning">If only one organization is listed, it's role defaults to "Lead Research Organization"</div>
632    ///
633    /// <div class="warning">One and only one organization should be identified as "Lead Research Organization"</div>
634    #[validate(nested, length(min = 1))]
635    #[serde(alias = "organisation")]
636    pub organization: Vec<Organization>,
637    /// Related objects associated with the RAiD
638    #[validate(nested)]
639    pub related_object: Option<Vec<RelatedObject>>,
640    /// Alternate identifiers associated with the RAiD
641    #[validate(nested)]
642    pub alternate_identifier: Option<Vec<AlternateIdentifier>>,
643    /// Alternate URLs associated with the RAiD
644    #[validate(nested)]
645    pub alternate_url: Option<Vec<AlternateUrl>>,
646    /// Related RAiD(s) associated with the RAiD
647    #[validate(nested)]
648    pub related_raid: Option<Vec<RelatedRaid>>,
649    /// Access for the RAiD metadata
650    #[validate(nested)]
651    pub access: Access,
652    /// Traditional knowledge information
653    #[validate(nested)]
654    pub traditional_knowledge_label: Option<Vec<TraditionalKnowledgeLabel>>,
655    /// Spatial coverage
656    #[validate(nested)]
657    pub spatial_coverage: Option<Vec<SpatialCoverage>>,
658    /// Subjects
659    #[validate(nested)]
660    pub subject: Option<Vec<Subject>>,
661}
662/// Metadata schema block containing the RAiD name and associated properties
663///
664/// See <https://metadata.raid.org/en/v1.6/core/identifier.html#identifier>
665#[derive(Builder, Clone, Debug, Serialize, Deserialize, Display, JsonSchema, Validate)]
666#[display("{id}")]
667#[serde(deny_unknown_fields, rename_all = "camelCase")]
668pub struct MetadataIdentifier {
669    /// Unique alphanumeric character string that identifies a Research Activity Identifier (RAiD) name
670    /// ### Format
671    /// > `https://raid.org/prefix/suffix`
672    #[validate(custom(function = "is_raid"))]
673    pub id: String,
674    /// URI of the identifier scheme used to identify RAiDs
675    /// ### Example
676    /// > `https://raid.org/`
677    #[validate(url)]
678    pub schema_uri: Option<String>,
679    /// RAiD owner
680    #[validate(nested)]
681    pub owner: Owner,
682    /// RAiD agency URL
683    #[validate(url)]
684    pub raid_agency_url: String,
685    /// Mtadata schema sub-block declaring the Registration Agency that minted the RAiD
686    #[validate(nested)]
687    pub registration_agency: RegistrationAgency,
688    /// The licence, or licence waiver, under which the RAiD metadata record associated with this Identifier has been issued
689    ///
690    /// <div class="warning">Only supports CC-0 (?)</div>
691    pub license: License,
692    /// Version number of the RAiD
693    #[validate(range(min = 0))]
694    pub version: u32,
695}
696/// Information about edit history of associated RAiD
697#[derive(Builder, Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
698#[serde(deny_unknown_fields, rename_all = "camelCase")]
699pub struct MetadataMetadata {
700    /// Date and time the RAiD metadata record was created
701    ///
702    /// Should be Unix epoch timestamp
703    #[validate(custom(function = "is_unix_epoch"))]
704    pub created: usize,
705    /// Date and time the RAiD metadata record was last updated
706    ///
707    /// Should be Unix epoch timestamp
708    #[validate(custom(function = "is_unix_epoch"))]
709    pub updated: usize,
710}
711/// Metadata schema block containing the organization associated with a RAiD and its associated properties
712///
713/// See <https://metadata.raid.org/en/v1.6/core/organisations.html#organisation>
714#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
715#[serde(deny_unknown_fields, rename_all = "camelCase")]
716pub struct Organization {
717    /// Organization identifier
718    /// ### Example
719    /// > `https://ror.org/01qz5mb56`
720    ///
721    /// <div class="warning">Should be <a href="https://ror.org">ROR</a>, if available</div>
722    #[validate(custom(function = "is_ror"))]
723    pub id: String,
724    /// URI of the organization identifier schema
725    ///
726    /// Only allowed value: `https://ror.org/`
727    #[validate(url, contains(pattern = "https://ror.org"))]
728    pub schema_uri: Option<String>,
729    /// Organization role
730    #[validate(nested)]
731    pub role: Vec<OrganizationRole>,
732}
733/// Organization role
734#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
735#[serde(deny_unknown_fields, rename_all = "camelCase")]
736pub struct OrganizationRole {
737    /// Organization role identifier
738    pub id: OrganizationRoleType,
739    /// URI of the organization role identifier schema
740    #[validate(url)]
741    pub schema_uri: Option<String>,
742    /// Date information associated with the organization role
743    #[validate(nested)]
744    #[serde(flatten)]
745    pub date: Date,
746}
747/// Metadata schema sub-block that declares the owner of the RAiD (i.e. the organization requesting the RAiD)
748///
749/// See <https://metadata.raid.org/en/v1.6/core/identifier.html#identifier-owner>
750#[derive(Builder, Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
751#[serde(deny_unknown_fields, rename_all = "camelCase")]
752pub struct Owner {
753    /// Persistent identifier of the legal entity responsible for the RAiD
754    ///
755    /// *Default* ROR of the organization requesting the RAiD
756    /// ### Example
757    /// > `https://ror.org/01qz5mb56` (ORNL)
758    #[validate(custom(function = "is_ror"))]
759    pub id: String,
760    /// URI of the identifier scheme used to identify RAiDs
761    /// ### Example
762    /// > `https://ror.org/`
763    #[validate(url)]
764    pub schema_uri: Option<String>,
765    /// Service point (SP) that requested the RAiD
766    /// ### Example
767    /// > `20000003`
768    /// ### Notes
769    /// - RAiD owners can have multiple SPs
770    /// - SPs do not need to be legal entities
771    /// - List of SPs is maintained by each [`RegistrationAgency`]
772    pub service_point: usize,
773}
774/// Metadata schema sub-block containing free-text place names or descriptions plus associated metadata properties
775#[derive(Builder, Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
776#[serde(deny_unknown_fields, rename_all = "camelCase")]
777pub struct Place {
778    /// Free text description of one or more geographic locations that are the subject or target of the project or activity; use to specify or describe a geographic location in a manner not covered by [`SpatialCoverage`].id
779    /// ### Warning
780    /// > Do not duplicate information from [`SpatialCoverage`].id above; do not use for organisational locations (which are derived from the organisation's ROR)
781    pub text: Option<String>,
782    /// Language of the text
783    #[validate(nested)]
784    pub language: Option<Language>,
785}
786/// Metadata schema block containing inputs, outputs, and process documents related to a RAiD plus associated properties
787///
788/// See <https://metadata.raid.org/en/v1.6/core/relatedObjects.html>
789#[derive(Builder, Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
790#[serde(deny_unknown_fields, rename_all = "camelCase")]
791pub struct RelatedObject {
792    /// Persistent identifier (PID) of related object
793    ///
794    /// The object can be any combination of
795    /// - input or resource used by a project or activity
796    /// - output or product created by a project or activity
797    /// - internal process documentation used within a project or activity
798    pub id: String,
799    /// URI of the relatedObject identifier schema
800    #[validate(url)]
801    pub schema_uri: Option<String>,
802    /// Type information of related object
803    #[validate(nested)]
804    #[serde(rename = "type")]
805    pub related_object_type: RelatedObjectIdentifier,
806    /// Category information of related object
807    #[validate(nested, length(min = 1))]
808    pub category: Vec<RelatedObjectCategory>,
809}
810/// Related object category information
811#[derive(Builder, Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
812#[serde(deny_unknown_fields, rename_all = "camelCase")]
813pub struct RelatedObjectCategory {
814    /// Related object category identifier
815    pub id: ObjectCategoryType,
816    /// URI of the category schema used
817    #[validate(url)]
818    pub schema_uri: Option<String>,
819}
820/// Related object identifier
821#[derive(Builder, Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
822#[serde(deny_unknown_fields, rename_all = "camelCase")]
823pub struct RelatedObjectIdentifier {
824    /// Related object type identifier
825    pub id: ObjectType,
826    /// URI of the related object type identifier schema
827    #[validate(url)]
828    pub schema_uri: Option<String>,
829}
830/// Metadata schema block containing related RAiDs and qualifying the relationship
831///
832/// See <https://metadata.raid.org/en/v1.6/core/relatedRaids.html>
833#[derive(Builder, Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
834#[serde(deny_unknown_fields, rename_all = "camelCase")]
835pub struct RelatedRaid {
836    /// Subsidiary or otherwise related RAiD
837    pub id: String,
838    /// Related RAiD type
839    #[validate(nested)]
840    #[serde(rename = "type")]
841    pub related_raid_type: RelatedRaidIdentifier,
842}
843/// Related RAiD identifier
844#[derive(Builder, Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
845#[serde(deny_unknown_fields, rename_all = "camelCase")]
846pub struct RelatedRaidIdentifier {
847    /// Related RAiD type identifier
848    pub id: RelatedRaidType,
849    /// URI of the related RAiD type identifier schema
850    #[validate(url)]
851    pub schema_uri: Option<String>,
852}
853/// Metadata schema block containing the RAiD name and associated properties
854///
855/// See <https://metadata.raid.org/en/v1.6/core/identifier.html#identifier-registrationagency>
856#[derive(Builder, Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
857#[serde(deny_unknown_fields, rename_all = "camelCase")]
858pub struct RegistrationAgency {
859    /// Persistent identifier of the RAiD Registration Agency that minted the RAiD
860    ///
861    /// *Default* ROR of the RAiD Registration Agency
862    #[validate(custom(function = "is_ror"))]
863    pub id: String,
864    /// URI of the identifier scheme used to identify RAiDs
865    /// ### Example
866    /// > `https://raid.org/`
867    #[validate(url)]
868    pub schema_uri: Option<String>,
869}
870/// Metadata schema sub-block describing a contributor's scientific or scholarly role on a project using the [CRediT] vocabulary
871///
872/// See <https://metadata.raid.org/en/v1.6/core/contributors.html#contributor-role>
873///
874/// [CRediT]: https://credit.niso.org/
875#[skip_serializing_none]
876#[derive(Builder, Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
877#[serde(deny_unknown_fields, rename_all = "camelCase")]
878pub struct Role {
879    /// Contributor role on a project or activity
880    pub id: Option<CreditRole>,
881    /// URI of the role schema used
882    #[validate(url)]
883    pub schema_uri: Option<String>,
884}
885/// Metadata schema block containing information about any spatial region(s) or named place(s) targeted by the project
886/// ### Note
887/// > Part of "extended" metadata that allows some customization by Registration Agencies
888#[derive(Builder, Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
889#[serde(deny_unknown_fields, rename_all = "camelCase")]
890pub struct SpatialCoverage {
891    /// Spatial region or named place that is the subject or target of the project or activity. Repeat this property as necessary to indicate different locations. Do not duplicate organisational locations
892    pub id: String,
893    /// URI of the geolocation schema used for spatial coverage
894    #[validate(url)]
895    pub schema_uri: Option<String>,
896    /// Places of associated spatial coverage
897    #[validate(nested)]
898    pub place: Vec<Place>,
899}
900/// Metadata schema block containing the subject area of the RAiD plus associated properties
901/// ### Note
902/// > Part of "extended" metadata that allows some customization by Registration Agencies
903#[derive(Builder, Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
904#[serde(deny_unknown_fields, rename_all = "camelCase")]
905pub struct Subject {
906    /// URI for a subject area or classification code describing the project or activity
907    pub id: String,
908    /// URI of the subject identifier schema
909    #[validate(url)]
910    pub schema_uri: Option<String>,
911    /// Subject keywords
912    pub keyword: Option<Vec<Keyword>>,
913}
914/// Metadata schema block containing the title of RAiD and associated properties
915///
916/// See <https://metadata.raid.org/en/v1.6/core/titles.html>
917#[skip_serializing_none]
918#[derive(Builder, Clone, Debug, Display, Serialize, Deserialize, JsonSchema, Validate)]
919#[display("{text} ({title_type})")]
920#[serde(deny_unknown_fields, rename_all = "camelCase")]
921pub struct Title {
922    /// Name or title by which the project or activity is known
923    #[validate(length(min = 3, max = 100))]
924    pub text: String,
925    /// Metadata schema block containing information about the title type
926    #[validate(nested)]
927    #[serde(rename = "type")]
928    pub title_type: TitleIdentifier,
929    /// Language of the title
930    #[validate(nested)]
931    pub language: Option<Language>,
932    /// Date the project or activity's title began being used
933    /// ### Format
934    /// > [ISO 8601] standard date (e.g., `YYYY-MM-DD`)
935    ///
936    /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
937    #[validate(custom(function = "is_iso8601_date"))]
938    pub start_date: String,
939    /// Date the project or activity title was changed or stopped being used
940    /// ### Format
941    /// > [ISO 8601] standard date (e.g., `YYYY-MM-DD`)
942    ///
943    /// <div class="warning">Only the year is required, month and day are optional</div>
944    ///
945    /// <div class="warning">Listed as "recommended" (optional) and "required"</div>
946    ///
947    /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
948    // TODO: Add support for month and day(?)
949    #[validate(custom(function = "is_iso8601_year"))]
950    pub end_date: String,
951}
952/// Metadata schema block containing information about Traditional Knowledge / Biocultural Labels and Notices
953/// ### Note
954/// > Part of "extended" metadata that allows some customization by Registration Agencies
955#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
956#[serde(deny_unknown_fields, rename_all = "camelCase")]
957pub struct TraditionalKnowledgeLabel {
958    /// Identifier (URI) linking to a verified source for Traditional Knowledge (TK) or Biocultural (BC) Labels or Notices pertaining to a project or activity
959    /// ### Note
960    /// > Currently only Local Contexts Hub Projects are allowed as a source for validated TK/BC Labels and Notices.
961    pub id: String,
962    /// URI of the Traditional Knowledge or Biocultural label identifier schema
963    /// ### Note
964    /// > Currently only Local Contexts Hub is supported for validated TK/BC Labels and Notices.
965    #[validate(url)]
966    pub schema_uri: Option<String>,
967}
968/// Metadata schema block containing information about the title type
969#[derive(Clone, Debug, Serialize, Deserialize, Display, JsonSchema, Validate)]
970#[display("{id}")]
971#[serde(deny_unknown_fields, rename_all = "camelCase")]
972pub struct TitleIdentifier {
973    /// Title type
974    ///
975    /// <div class="warning">Only one title should be identified as "Primary"</div>
976    pub id: TitleType,
977    /// URI of the title type schema
978    #[validate(url)]
979    pub schema_uri: Option<String>,
980}
981impl Metadata {
982    /// Print research activity identifier (RAiD) metadata schema as JSON schema
983    pub fn to_schema() {
984        let schema = schema_for!(Metadata);
985        println!("{}", serde_json::to_string_pretty(&schema).unwrap());
986    }
987    /// Read RAiD metadata from a file
988    pub fn read(path: PathBuf) -> Option<Metadata> {
989        match read_file(path) {
990            | Ok(data) => match serde_json::from_str(&data) {
991                | Ok(value) => Some(value),
992                | Err(why) => {
993                    println!("=> {} Parse RAiD metadata - {why}", Label::fail());
994                    error!("=> {} Parse RAiD metadata - {why}", Label::fail());
995                    None
996                }
997            },
998            | Err(why) => {
999                println!("=> {} Read RAiD metadata file - {why}", Label::fail());
1000                error!("=> {} Read RAiD metadata file - {why}", Label::fail());
1001                None
1002            }
1003        }
1004    }
1005}