cocina-models 0.70.0 → 0.73.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/README.md +11 -0
- data/cocina-models.gemspec +1 -0
- data/description_types.yml +550 -0
- data/lib/cocina/generator/generator.rb +0 -1
- data/lib/cocina/models/access_role.rb +1 -1
- data/lib/cocina/models/catalog_link.rb +3 -1
- data/lib/cocina/models/identification.rb +1 -1
- data/lib/cocina/models/title_builder.rb +36 -31
- data/lib/cocina/models/validatable.rb +2 -2
- data/lib/cocina/models/validators/catalog_links_validator.rb +54 -0
- data/lib/cocina/models/validators/dark_validator.rb +79 -0
- data/lib/cocina/models/validators/description_types_validator.rb +119 -0
- data/lib/cocina/models/validators/open_api_validator.rb +53 -0
- data/lib/cocina/models/validators/validator.rb +21 -0
- data/lib/cocina/models/version.rb +1 -1
- data/lib/cocina/models/vocabulary.rb +9 -0
- data/openapi.yml +16 -7
- metadata +22 -3
- data/lib/cocina/models/validator.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b929b4e87bdb86010e2915c19628cb96dcafd101805e3c2dcee7ce56554d492f
|
4
|
+
data.tar.gz: 7aa988aa05b14e1e0521369ae1124acb0e4433195d7cd0011b239d63fa01b97b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a3ab493e7a4f41d48a1d583b695a6689bb5dac3117fd03d343c75ab7407a25df58755899b28a171fcdc3425a6a204582a4f112f31e51d70ef3ac766bd27d4107
|
7
|
+
data.tar.gz: 45f9b2c0d9fd7b9e9d542f12d20e90ef0aacf536fa6477f691888e8a60dfcef96efce4a5c62793f8cd7eb7e27c30286360e036413bc975ff5bf316b271adf40f
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -101,6 +101,16 @@ If for some reason the above method does not work, the sul-dlss/access-update-sc
|
|
101
101
|
|
102
102
|
[sul-dlss/sdr-deploy](https://github.com/sul-dlss/sdr-deploy) has a flag in the deploy script to limit deploys to cocina dependent applications. Refer to instructions in the [sdr-deploy/README](https://github.com/sul-dlss/sdr-deploy/blob/main/README.md#only-deploy-repos-related-to-cocina-models-update).
|
103
103
|
|
104
|
+
Note that running the integration tests is currently the best way we have to check for unintended effects and/or bugs when rolling out cocina-models changes.
|
105
|
+
|
106
|
+
#### Step 5A: Deploy to QA and/or Stage
|
107
|
+
|
108
|
+
#### Step 5B: Run infrastructure_integration_tests
|
109
|
+
|
110
|
+
It is safest to ensure _all_ the integration tests run cleanly. However, patch releases of cocina-models may only warrant running individual tests that exercise the changes.
|
111
|
+
|
112
|
+
#### Step 5C: Deploy to Production
|
113
|
+
|
104
114
|
**[Turn off Google Books](https://sul-gbooks-prod.stanford.edu/features) when deploying to production.** This avoids failed deposit due to a temporary Cocina model mismatch. Unlike other applications, the deposits will fail without retry and require manual remediation.
|
105
115
|
|
106
116
|
## Usage conventions
|
@@ -108,6 +118,7 @@ If for some reason the above method does not work, the sul-dlss/access-update-sc
|
|
108
118
|
The following are the recommended naming conventions for code using Cocina models:
|
109
119
|
|
110
120
|
* `cocina_item`: `Cocina::Models::DRO` instance
|
121
|
+
* `cocina_agreement`: `Cocina::Models::DRO` with type of Cocina::Models::ObjectType.agreement
|
111
122
|
* `cocina_admin_policy`: `Cocina::Models::AdminPolicy` instance
|
112
123
|
* `cocina_collection`: `Cocina::Models::Collection` instance
|
113
124
|
* `cocina_object`: `Cocina::Models::DRO` or `Cocina::Models::AdminPolicy` or `Cocina::Models::Collection` instance
|
data/cocina-models.gemspec
CHANGED
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.required_ruby_version = '>= 2.7'
|
26
26
|
|
27
27
|
spec.add_dependency 'activesupport'
|
28
|
+
spec.add_dependency 'deprecation'
|
28
29
|
spec.add_dependency 'dry-struct', '~> 1.0'
|
29
30
|
spec.add_dependency 'dry-types', '~> 1.1'
|
30
31
|
spec.add_dependency 'openapi3_parser' # Parsing openapi doc
|
@@ -0,0 +1,550 @@
|
|
1
|
+
# Description type validation
|
2
|
+
|
3
|
+
access.accessContact:
|
4
|
+
- value: email
|
5
|
+
description: Email address for a contact person or institution concerning the resource.
|
6
|
+
- value: repository
|
7
|
+
description: Institution providing access to the resource.
|
8
|
+
access.digitalLocation:
|
9
|
+
- value: discovery
|
10
|
+
description: Online location for the purpose of discovering the resource.
|
11
|
+
access.note:
|
12
|
+
- value: access restriction
|
13
|
+
description: Restrictions on or conditions for gaining access to the resource.
|
14
|
+
- value: display label
|
15
|
+
description: Display label for the purl.
|
16
|
+
- value: license
|
17
|
+
description: License describing allowed uses of the resource.
|
18
|
+
- value: use and reproduction
|
19
|
+
description: Information related to allowed uses of the resource in other contexts.
|
20
|
+
access.physicalLocation:
|
21
|
+
- value: discovery
|
22
|
+
description: Location where a user may find the resource.
|
23
|
+
- value: location
|
24
|
+
description: Physical location of the resource, or path to the resource on a hard drive or disk.
|
25
|
+
- value: repository
|
26
|
+
description: The institution holding the resource.
|
27
|
+
- value: series
|
28
|
+
description: Archival series of the resource.
|
29
|
+
status: deprecated
|
30
|
+
- value: shelf locator
|
31
|
+
description: Identifier or shelfmark indicating the location of the resource.
|
32
|
+
adminMetadata.note:
|
33
|
+
- value: record information
|
34
|
+
description: General information about the metadata record.
|
35
|
+
- value: record origin
|
36
|
+
description: The source of the record, such as another record transformed to generate the current record.
|
37
|
+
contributor:
|
38
|
+
- value: conference
|
39
|
+
description: An event focusing on a particular topic or discipline.
|
40
|
+
- value: event
|
41
|
+
description: A time-bound occurrence.
|
42
|
+
- value: family
|
43
|
+
description: A group of individuals related by blood or personal alliance.
|
44
|
+
- value: organization
|
45
|
+
description: An institution or other corporate or collective body.
|
46
|
+
- value: person
|
47
|
+
description: An individual identity.
|
48
|
+
- value: unspecified others
|
49
|
+
description: Designator for one or more additional contributors not named individually.
|
50
|
+
contributor.identifier:
|
51
|
+
- value: ORCID
|
52
|
+
description: Identifier from orcid.org.
|
53
|
+
- value: Wikidata
|
54
|
+
description: Identifier from wikidata.org.
|
55
|
+
contributor.name:
|
56
|
+
- value: alternative
|
57
|
+
description: Additional nonpreferred form of name.
|
58
|
+
- value: display
|
59
|
+
description: Preferred form of the name for display.
|
60
|
+
- value: forename
|
61
|
+
description: First or given name or names.
|
62
|
+
- value: inverted full name
|
63
|
+
description: Name given in last name, first name order.
|
64
|
+
- value: pseudonym
|
65
|
+
description: Name used that differs from legal or primary form of name.
|
66
|
+
- value: surname
|
67
|
+
description: Last or family name.
|
68
|
+
- value: transliteration
|
69
|
+
description: Name originally in non-Latin script presented phonetically using Latin characters.
|
70
|
+
contributor.name.structuredValue:
|
71
|
+
- value: activity dates
|
72
|
+
description: The date or dates when someone was producing work.
|
73
|
+
- value: forename
|
74
|
+
description: First or given name or names.
|
75
|
+
- value: life dates
|
76
|
+
description: Birth and death dates, or dates when an entity was in existence.
|
77
|
+
- value: name
|
78
|
+
description: Name provided alongside additional information.
|
79
|
+
- value: ordinal
|
80
|
+
description: Indicator that the name is one in a series (e.g. Elizabeth I, Martin Luther King, Jr.).
|
81
|
+
- value: surname
|
82
|
+
description: Last or family name.
|
83
|
+
- value: term of address
|
84
|
+
description: Title or other signifier associated with name.
|
85
|
+
contributor.name.groupedValue:
|
86
|
+
- value: alternative
|
87
|
+
description: Additional nonpreferred form of name.
|
88
|
+
- value: name
|
89
|
+
description: Primary form of name within group of values.
|
90
|
+
- value: pseudonym
|
91
|
+
description: Name used that differs from legal or primary form of name.
|
92
|
+
contributor.note:
|
93
|
+
- value: affiliation
|
94
|
+
description: Institution with which the contributor is associated.
|
95
|
+
- value: citation status
|
96
|
+
description: Indicator of whether the contributor should be included in the citation.
|
97
|
+
- value: description
|
98
|
+
description: Biographical information about the contributor.
|
99
|
+
event:
|
100
|
+
- value: acquisition
|
101
|
+
description: The transferral of ownership of a resource to a repository.
|
102
|
+
- value: capture
|
103
|
+
description: A record of the resource in a fixed form at a specific time.
|
104
|
+
- value: collection
|
105
|
+
description: The addition of a resource to a set of other resources.
|
106
|
+
- value: copyright
|
107
|
+
description: The activity by which a resource may be considered subject to copyright law.
|
108
|
+
- value: copyright notice
|
109
|
+
description: An explicit statement that a resource is under copyright.
|
110
|
+
- value: creation
|
111
|
+
description: The coming into being of a resource.
|
112
|
+
- value: degree conferral
|
113
|
+
description: The institutional approval of a thesis or other resource leading to an academic degree.
|
114
|
+
- value: development
|
115
|
+
description: The creation of a print from a photographic negative or other source medium.
|
116
|
+
- value: distribution
|
117
|
+
description: The delivery of the resource to an external audience.
|
118
|
+
- value: generation
|
119
|
+
description: The creation of a resource by an automatic or natural process.
|
120
|
+
- value: manufacture
|
121
|
+
description: The physical assembly of a resource, often in multiple copies, for publication or other distribution.
|
122
|
+
- value: modification
|
123
|
+
description: A change to an existing resource.
|
124
|
+
- value: performance
|
125
|
+
description: The enactment of an artistic or cultural work for an audience, such as a play.
|
126
|
+
- value: presentation
|
127
|
+
description: The discussion of an academic or intellectual work for an audience, such as a seminar.
|
128
|
+
- value: production
|
129
|
+
description: The physical assembly of a resource not considered published, such as page proofs for a book.
|
130
|
+
- value: publication
|
131
|
+
description: The publishing or issuing of a resource.
|
132
|
+
- value: recording
|
133
|
+
description: The initial fixation to a medium of live audio and/or visual activity.
|
134
|
+
- value: release
|
135
|
+
description: Making a resource available to a broader audience.
|
136
|
+
- value: submission
|
137
|
+
description: The provision of a resource for review or evaluation.
|
138
|
+
- value: validity
|
139
|
+
description: When a resource takes effect, such as a revised train schedule.
|
140
|
+
- value: withdrawal
|
141
|
+
description: The removal of previous access to a resource, often due to its obsolescence.
|
142
|
+
event.date:
|
143
|
+
- value: acquisition
|
144
|
+
description: The transferral of ownership of a resource to a repository.
|
145
|
+
- value: capture
|
146
|
+
description: A record of the resource in a fixed form at a specific time.
|
147
|
+
- value: collection
|
148
|
+
description: The addition of a resource to a set of other resources.
|
149
|
+
- value: copyright
|
150
|
+
description: The activity by which a resource may be considered subject to copyright law.
|
151
|
+
- value: creation
|
152
|
+
description: The coming into being of a resource.
|
153
|
+
- value: degree conferral
|
154
|
+
description: The institutional approval of a thesis or other resource leading to an academic degree.
|
155
|
+
- value: developed
|
156
|
+
description: The creation of a print from a photographic negative or other source medium.
|
157
|
+
status: deprecated
|
158
|
+
use: development
|
159
|
+
- value: development
|
160
|
+
description: The creation of a print from a photographic negative or other source medium.
|
161
|
+
- value: distribution
|
162
|
+
description: The delivery of the resource to an external audience.
|
163
|
+
- value: generation
|
164
|
+
description: The creation of a resource by an automatic or natural process.
|
165
|
+
- value: manufacture
|
166
|
+
description: The physical assembly of a resource, often in multiple copies, for publication or other distribution.
|
167
|
+
- value: modification
|
168
|
+
description: A change to an existing resource.
|
169
|
+
- value: performance
|
170
|
+
description: The enactment of an artistic or cultural work for an audience, such as a play.
|
171
|
+
- value: presentation
|
172
|
+
description: The discussion of an academic or intellectual work for an audience, such as a seminar.
|
173
|
+
- value: production
|
174
|
+
description: The physical assembly of a resource not considered published, such as page proofs for a book.
|
175
|
+
- value: publication
|
176
|
+
description: The publishing or issuing of a resource.
|
177
|
+
- value: recording
|
178
|
+
description: The initial fixation to a medium of live audio and/or visual activity.
|
179
|
+
- value: release
|
180
|
+
description: Making a resource available to a broader audience.
|
181
|
+
- value: submission
|
182
|
+
description: The provision of a resource for review or evaluation.
|
183
|
+
- value: validity
|
184
|
+
description: When a resource takes effect, such as a revised train schedule.
|
185
|
+
- value: withdrawal
|
186
|
+
description: The removal of previous access to a resource, often due to its obsolescence.
|
187
|
+
event.date.structuredValue:
|
188
|
+
- value: start
|
189
|
+
description: The start date in a range.
|
190
|
+
- value: end
|
191
|
+
description: The end date in a range.
|
192
|
+
event.note:
|
193
|
+
- value: copyright statement
|
194
|
+
description: A formal declaration of copyright on a resource.
|
195
|
+
- value: edition
|
196
|
+
- value: frequency
|
197
|
+
description: How often a resource is issued, such as monthly.
|
198
|
+
- value: issuance
|
199
|
+
description: How the resource is issued, such as serially.
|
200
|
+
form:
|
201
|
+
- value: carrier
|
202
|
+
- value: data format
|
203
|
+
- value: digital original
|
204
|
+
- value: extent
|
205
|
+
- value: form
|
206
|
+
- value: genre
|
207
|
+
- value: map projection
|
208
|
+
- value: map scale
|
209
|
+
- value: material
|
210
|
+
- value: media
|
211
|
+
- value: media type
|
212
|
+
- value: reformatting quality
|
213
|
+
- value: resource type
|
214
|
+
- value: technique
|
215
|
+
- value: type
|
216
|
+
form.note:
|
217
|
+
- value: additions
|
218
|
+
- value: arrangement
|
219
|
+
- value: binding
|
220
|
+
- value: codicology
|
221
|
+
- value: collation
|
222
|
+
- value: colophon
|
223
|
+
- value: condition
|
224
|
+
- value: decoNote
|
225
|
+
status: deprecated
|
226
|
+
use: decoration
|
227
|
+
- value: decoration
|
228
|
+
- value: dimensions
|
229
|
+
- value: explicit
|
230
|
+
- value: foliation
|
231
|
+
- value: genre type
|
232
|
+
- value: hand note
|
233
|
+
- value: handNote
|
234
|
+
status: deprecated
|
235
|
+
use: hand note
|
236
|
+
- value: incipit
|
237
|
+
- value: instrumentation
|
238
|
+
- value: layout
|
239
|
+
- value: material
|
240
|
+
- value: medium of performance
|
241
|
+
- value: provenance
|
242
|
+
- value: reassembly
|
243
|
+
- value: reproduction
|
244
|
+
- value: research
|
245
|
+
- value: rubric
|
246
|
+
- value: secfol
|
247
|
+
status: deprecated
|
248
|
+
use: second folio
|
249
|
+
- value: second folio
|
250
|
+
- value: secondFolio
|
251
|
+
status: deprecated
|
252
|
+
use: second folio
|
253
|
+
- value: unit
|
254
|
+
- value: writing
|
255
|
+
form.structuredValue:
|
256
|
+
- value: type
|
257
|
+
- value: subtype
|
258
|
+
geographic.form:
|
259
|
+
- value: data format
|
260
|
+
- value: media type
|
261
|
+
- value: type
|
262
|
+
geographic.subject:
|
263
|
+
- value: bounding box coordinates
|
264
|
+
- value: coverage
|
265
|
+
- value: point coordinates
|
266
|
+
geographic.subject.structuredValue:
|
267
|
+
- value: east
|
268
|
+
- value: latitude
|
269
|
+
- value: longitude
|
270
|
+
- value: north
|
271
|
+
- value: south
|
272
|
+
- value: west
|
273
|
+
identifier:
|
274
|
+
- value: accession number
|
275
|
+
- value: alternate case number
|
276
|
+
- value: Apis ID
|
277
|
+
code: apis
|
278
|
+
- value: ARK
|
279
|
+
code: ark
|
280
|
+
- value: arXiv
|
281
|
+
code: arxiv
|
282
|
+
- value: case identifier
|
283
|
+
- value: case number
|
284
|
+
- value: document number
|
285
|
+
- value: DOI
|
286
|
+
code: doi
|
287
|
+
- value: druid
|
288
|
+
- value: GTIN-14 ID
|
289
|
+
code: gtin-14
|
290
|
+
- value: Handle
|
291
|
+
code: hdl
|
292
|
+
- value: inventory number
|
293
|
+
- value: ISBN
|
294
|
+
code: isbn
|
295
|
+
- value: ISMN
|
296
|
+
code: ismn
|
297
|
+
- value: ISRC
|
298
|
+
code: isrc
|
299
|
+
- value: ISSN
|
300
|
+
code: issn
|
301
|
+
- value: ISSN-L
|
302
|
+
code: issn-l
|
303
|
+
- value: issue number
|
304
|
+
code: issue-number
|
305
|
+
- value: LCCN
|
306
|
+
code: lccn
|
307
|
+
- value: local
|
308
|
+
code: local
|
309
|
+
- value: Local ID
|
310
|
+
- value: matrix number
|
311
|
+
code: matrix-number
|
312
|
+
- value: music plate
|
313
|
+
code: music-plate
|
314
|
+
- value: music publisher
|
315
|
+
code: music-publisher
|
316
|
+
- value: OCLC
|
317
|
+
- value: PMCID
|
318
|
+
- value: PMID
|
319
|
+
- value: record id
|
320
|
+
- value: Senate Number
|
321
|
+
- value: Series
|
322
|
+
- value: SIRSI
|
323
|
+
- value: Source ID
|
324
|
+
- value: sourceID
|
325
|
+
status: deprecated
|
326
|
+
use: Source ID
|
327
|
+
- value: stock number
|
328
|
+
code: stock-number
|
329
|
+
- value: Swets (Netherlands) ID
|
330
|
+
code: swets
|
331
|
+
- value: UPC
|
332
|
+
code: upc
|
333
|
+
- value: URI
|
334
|
+
code: uri
|
335
|
+
- value: URN
|
336
|
+
code: urn
|
337
|
+
- value: videorecording identifier
|
338
|
+
code: videorecording-identifier
|
339
|
+
- value: West Mat \#
|
340
|
+
- value: Wikidata
|
341
|
+
code: wikidata
|
342
|
+
note:
|
343
|
+
- value: abstract
|
344
|
+
- value: access
|
345
|
+
- value: access note
|
346
|
+
status: deprecated
|
347
|
+
use: access
|
348
|
+
- value: acquisition
|
349
|
+
- value: action
|
350
|
+
- value: additional physical form
|
351
|
+
- value: additions
|
352
|
+
- value: admin
|
353
|
+
- value: affiliation
|
354
|
+
- value: bibliographic
|
355
|
+
- value: bibliography
|
356
|
+
- value: biographical/historical
|
357
|
+
- value: biographical/historical note
|
358
|
+
status: deprecated
|
359
|
+
use: biographical/historical
|
360
|
+
- value: biography
|
361
|
+
- value: boat note
|
362
|
+
- value: citation/reference
|
363
|
+
- value: contact
|
364
|
+
- value: content
|
365
|
+
- value: content note
|
366
|
+
status: deprecated
|
367
|
+
use: content
|
368
|
+
- value: content warning
|
369
|
+
- value: contents
|
370
|
+
status: deprecated
|
371
|
+
use: content
|
372
|
+
- value: copyright
|
373
|
+
- value: creation/production credits
|
374
|
+
- value: date
|
375
|
+
- value: date/sequential designation
|
376
|
+
- value: description
|
377
|
+
- value: digitization
|
378
|
+
- value: duration
|
379
|
+
- value: event
|
380
|
+
- value: exhibitions
|
381
|
+
- value: funding
|
382
|
+
- value: general
|
383
|
+
- value: genre type
|
384
|
+
- value: geography
|
385
|
+
- value: host
|
386
|
+
- value: language
|
387
|
+
- value: local
|
388
|
+
- value: location
|
389
|
+
- value: medium of performance
|
390
|
+
- value: names
|
391
|
+
- value: numbering
|
392
|
+
- value: original location
|
393
|
+
- value: other relation type
|
394
|
+
- value: ownership
|
395
|
+
- value: part
|
396
|
+
- value: performer
|
397
|
+
- value: performers
|
398
|
+
- value: preferred citation
|
399
|
+
- value: provenance
|
400
|
+
- value: publications
|
401
|
+
- value: qualifications
|
402
|
+
- value: quote
|
403
|
+
- value: reassembly
|
404
|
+
- value: reference
|
405
|
+
- value: references
|
406
|
+
- value: related publication
|
407
|
+
- value: reproduction
|
408
|
+
- value: research
|
409
|
+
- value: restriction
|
410
|
+
- value: scope and content
|
411
|
+
- value: source characteristics
|
412
|
+
- value: source identifier
|
413
|
+
- value: statement of responsibility
|
414
|
+
- value: summary
|
415
|
+
- value: system details
|
416
|
+
- value: system requirements
|
417
|
+
- value: table of contents
|
418
|
+
- value: target audience
|
419
|
+
- value: technical note
|
420
|
+
- value: thesis
|
421
|
+
- value: transcript
|
422
|
+
- value: translation
|
423
|
+
- value: update
|
424
|
+
- value: use and reproduction
|
425
|
+
- value: venue
|
426
|
+
- value: version
|
427
|
+
- value: version identification
|
428
|
+
- value: writing
|
429
|
+
note.groupedValue:
|
430
|
+
- value: caption
|
431
|
+
- value: date
|
432
|
+
- value: detail type
|
433
|
+
- value: extent unit
|
434
|
+
- value: list
|
435
|
+
- value: marker
|
436
|
+
- value: number
|
437
|
+
- value: title
|
438
|
+
- value: text
|
439
|
+
relatedResource:
|
440
|
+
- value: has original version
|
441
|
+
description: An initial form of the resource.
|
442
|
+
- value: has other format
|
443
|
+
description: A version of the resource in a different physical or digital format.
|
444
|
+
- value: has part
|
445
|
+
description: A constituent unit of the resource.
|
446
|
+
- value: has version
|
447
|
+
description: A version of the resource with different intellectual content.
|
448
|
+
- value: in series
|
449
|
+
description: The name of a series of publications to which the resource belongs.
|
450
|
+
- value: other relation type
|
451
|
+
description: Resource type not otherwise described.
|
452
|
+
- value: part of
|
453
|
+
description: A larger resource to which the resource belongs, such as a collection.
|
454
|
+
- value: preceded by
|
455
|
+
description: A predecessor to the resource, such as a preceding journal title.
|
456
|
+
- value: referenced by
|
457
|
+
description: Other resources that cite the resource, such as a catalog.
|
458
|
+
- value: references
|
459
|
+
description: A resource which the resource references or cites.
|
460
|
+
- value: related to
|
461
|
+
description: A generically related resource.
|
462
|
+
- value: reviewed by
|
463
|
+
description: A review of the resource.
|
464
|
+
- value: succeeded by
|
465
|
+
description: A successor to the resource, such as a subsequent journal title.
|
466
|
+
subject:
|
467
|
+
- value: classification
|
468
|
+
- value: conference
|
469
|
+
- value: display
|
470
|
+
- value: event
|
471
|
+
- value: family
|
472
|
+
- value: genre
|
473
|
+
- value: map coordinates
|
474
|
+
- value: name
|
475
|
+
- value: occupation
|
476
|
+
- value: organization
|
477
|
+
- value: person
|
478
|
+
- value: place
|
479
|
+
- value: point coordinates
|
480
|
+
- value: time
|
481
|
+
- value: title
|
482
|
+
- value: topic
|
483
|
+
subject.note:
|
484
|
+
- value: role
|
485
|
+
subject.structuredValue:
|
486
|
+
- value: activity dates
|
487
|
+
- value: city
|
488
|
+
- value: continent
|
489
|
+
- value: country
|
490
|
+
- value: end
|
491
|
+
- value: east
|
492
|
+
- value: display
|
493
|
+
- value: forename
|
494
|
+
- value: genre
|
495
|
+
- value: latitude
|
496
|
+
- value: life dates
|
497
|
+
- value: longitude
|
498
|
+
- value: main title
|
499
|
+
- value: name
|
500
|
+
- value: north
|
501
|
+
- value: occupation
|
502
|
+
- value: ordinal
|
503
|
+
- value: part name
|
504
|
+
- value: person
|
505
|
+
- value: place
|
506
|
+
- value: south
|
507
|
+
- value: start
|
508
|
+
- value: surname
|
509
|
+
- value: term of address
|
510
|
+
- value: time
|
511
|
+
- value: title
|
512
|
+
- value: topic
|
513
|
+
- value: west
|
514
|
+
subject.structuredValue.note:
|
515
|
+
- value: role
|
516
|
+
description: The relation of the subject entity to the resource.
|
517
|
+
subject.groupedValue:
|
518
|
+
- value: uniform
|
519
|
+
description: Form of title in Library of Congress title authority.
|
520
|
+
title:
|
521
|
+
- value: abbreviated
|
522
|
+
description: Abbreviated form of title for indexing or identification.
|
523
|
+
- value: alternative
|
524
|
+
description: Variant title.
|
525
|
+
- value: parallel
|
526
|
+
description: Title transcribed from the resource in multiple languages or scripts.
|
527
|
+
- value: supplied
|
528
|
+
description: Title provided by metadata creator rather than transcribed from the resource.
|
529
|
+
- value: translated
|
530
|
+
description: Title translated into another language.
|
531
|
+
- value: transliterated
|
532
|
+
description: Title transliterated from non-Latin script to Latin script.
|
533
|
+
- value: uniform
|
534
|
+
description: Form of title in Library of Congress title authority.
|
535
|
+
title.note:
|
536
|
+
- value: associated name
|
537
|
+
description: A name linked to the title, such as for a name-title heading.
|
538
|
+
- value: nonsorting character count
|
539
|
+
description: The number of characters at the beginning of the string to be disregarded when sorting.
|
540
|
+
title.structuredValue:
|
541
|
+
- value: main title
|
542
|
+
description: The primary part of a multipart title.
|
543
|
+
- value: nonsorting characters
|
544
|
+
description: A string at the beginning of the title to be disregarded when sorting.
|
545
|
+
- value: part name
|
546
|
+
description: The distinct name of a resource as part of a series or multivolume set.
|
547
|
+
- value: part number
|
548
|
+
description: The distinct number of a resource as part of a series or multivolume set.
|
549
|
+
- value: subtitle
|
550
|
+
description: The secondary part of a title.
|
@@ -4,7 +4,7 @@ module Cocina
|
|
4
4
|
module Models
|
5
5
|
class AccessRole < Struct
|
6
6
|
# Name of role
|
7
|
-
attribute :name, Types::Strict::String.enum('dor-apo-
|
7
|
+
attribute :name, Types::Strict::String.enum('dor-apo-depositor', 'dor-apo-manager', 'dor-apo-viewer', 'sdr-administrator', 'sdr-viewer', 'hydrus-collection-creator', 'hydrus-collection-manager', 'hydrus-collection-depositor', 'hydrus-collection-item-depositor', 'hydrus-collection-reviewer', 'hydrus-collection-viewer')
|
8
8
|
attribute :members, Types::Strict::Array.of(AccessRoleMember).default([].freeze)
|
9
9
|
end
|
10
10
|
end
|
@@ -5,7 +5,9 @@ module Cocina
|
|
5
5
|
class CatalogLink < Struct
|
6
6
|
# Catalog that is the source of the linked record.
|
7
7
|
# example: symphony
|
8
|
-
attribute :catalog, Types::Strict::String
|
8
|
+
attribute :catalog, Types::Strict::String.enum('symphony', 'previous symphony')
|
9
|
+
# Only one of the catkeys should be designated for refreshing. This means that this key is the one used to pull metadata from the catalog if there is more than one key present.
|
10
|
+
attribute :refresh, Types::Strict::Bool.default(false)
|
9
11
|
# Record identifier that is unique within the context of the linked record's catalog.
|
10
12
|
# example: 11403803
|
11
13
|
attribute :catalogRecordId, Types::Strict::String
|
@@ -12,7 +12,7 @@ module Cocina
|
|
12
12
|
# Unique identifier in some other system. This is because a large proportion of what is deposited in SDR, historically and currently, are representations of objects that are also represented in other systems. For example, digitized paper and A/V collections have physical manifestations, and those physical objects are managed in systems that have their own identifiers. Similarly, books have barcodes, archival materials have collection numbers and physical locations, etc. The sourceId allows determining if an item has been deposited before and where to look for the original item if you're looking at its SDR representation. The format is: "namespace:identifier"
|
13
13
|
|
14
14
|
# example: sul:PC0170_s3_Fiesta_Bowl_2012-01-02_210609_2026
|
15
|
-
attribute :sourceId, Types::Strict::String
|
15
|
+
attribute :sourceId, Types::Strict::String
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -1,30 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'deprecation'
|
4
|
+
|
3
5
|
module Cocina
|
4
6
|
module Models
|
5
7
|
# TitleBuilder selects the prefered title from the cocina object for solr indexing
|
6
8
|
# rubocop:disable Metrics/ClassLength
|
7
9
|
class TitleBuilder
|
8
|
-
|
10
|
+
extend Deprecation
|
11
|
+
# @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
|
9
12
|
# @param [Symbol] strategy ":first" is the strategy for selection when primary or display title are missing
|
10
13
|
# @param [Boolean] add_punctuation determines if the title should be formmated with punctuation
|
11
14
|
# @return [String] the title value for Solr
|
12
|
-
def self.build(
|
13
|
-
|
15
|
+
def self.build(titles, strategy: :first, add_punctuation: true)
|
16
|
+
if titles.respond_to?(:description)
|
17
|
+
Deprecation.warn(self, "Calling TitleBuilder.build with a #{titles.class} is deprecated. It must be called with an array of titles")
|
18
|
+
titles = titles.description.title
|
19
|
+
end
|
20
|
+
new(strategy: strategy, add_punctuation: add_punctuation).build(titles)
|
14
21
|
end
|
15
22
|
|
16
|
-
def initialize(
|
17
|
-
@cocina_object = cocina_object
|
23
|
+
def initialize(strategy:, add_punctuation:)
|
18
24
|
@strategy = strategy
|
19
25
|
@add_punctuation = add_punctuation
|
20
26
|
end
|
21
27
|
|
28
|
+
# @param [[Array<Cocina::Models::Title>] titles the titles to consider
|
22
29
|
# @return [String] the title value for Solr
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
cocina_title = primary_title || untyped_title
|
27
|
-
cocina_title = other_title if cocina_title.blank?
|
30
|
+
def build(titles)
|
31
|
+
cocina_title = primary_title(titles) || untyped_title(titles)
|
32
|
+
cocina_title = other_title(titles) if cocina_title.blank?
|
28
33
|
|
29
34
|
if strategy == :first
|
30
35
|
extract_title(cocina_title)
|
@@ -35,16 +40,15 @@ module Cocina
|
|
35
40
|
|
36
41
|
private
|
37
42
|
|
38
|
-
attr_reader :
|
43
|
+
attr_reader :strategy
|
39
44
|
|
40
45
|
def extract_title(cocina_title)
|
41
46
|
result = if cocina_title.value
|
42
47
|
cocina_title.value
|
43
48
|
elsif cocina_title.structuredValue.present?
|
44
|
-
title_from_structured_values(cocina_title
|
45
|
-
non_sorting_char_count(cocina_title))
|
49
|
+
title_from_structured_values(cocina_title)
|
46
50
|
elsif cocina_title.parallelValue.present?
|
47
|
-
return cocina_title.parallelValue
|
51
|
+
return build(cocina_title.parallelValue)
|
48
52
|
end
|
49
53
|
remove_trailing_punctuation(result.strip) if result.present?
|
50
54
|
end
|
@@ -54,7 +58,7 @@ module Cocina
|
|
54
58
|
end
|
55
59
|
|
56
60
|
# @return [Cocina::Models::Title, nil] title that has status=primary
|
57
|
-
def primary_title
|
61
|
+
def primary_title(titles)
|
58
62
|
primary_title = titles.find do |title|
|
59
63
|
title.status == 'primary'
|
60
64
|
end
|
@@ -69,7 +73,7 @@ module Cocina
|
|
69
73
|
end
|
70
74
|
end
|
71
75
|
|
72
|
-
def untyped_title
|
76
|
+
def untyped_title(titles)
|
73
77
|
method = strategy == :first ? :find : :select
|
74
78
|
untyped_title_for(titles.public_send(method))
|
75
79
|
end
|
@@ -86,7 +90,7 @@ module Cocina
|
|
86
90
|
end
|
87
91
|
|
88
92
|
# This handles 'main title', 'uniform' or 'translated'
|
89
|
-
def other_title
|
93
|
+
def other_title(titles)
|
90
94
|
if strategy == :first
|
91
95
|
titles.first
|
92
96
|
else
|
@@ -99,20 +103,17 @@ module Cocina
|
|
99
103
|
# rubocop:disable Metrics/PerceivedComplexity
|
100
104
|
# rubocop:disable Metrics/MethodLength
|
101
105
|
# rubocop:disable Metrics/AbcSize
|
102
|
-
# @param [
|
103
|
-
# @param [Integer] the length of the non_sorting_characters
|
106
|
+
# @param [Cocina::Models::Title] title with structured values
|
104
107
|
# @return [String] the title value from combining the pieces of the structured_values by type and order
|
105
108
|
# with desired punctuation per specs
|
106
|
-
def title_from_structured_values(
|
109
|
+
def title_from_structured_values(title)
|
107
110
|
structured_title = ''
|
108
111
|
part_name_number = ''
|
109
112
|
# combine pieces of the cocina structuredValue into a single title
|
110
|
-
|
113
|
+
title.structuredValue.each do |structured_value|
|
111
114
|
# There can be a structuredValue inside a structuredValue. For example,
|
112
115
|
# a uniform title where both the name and the title have internal StructuredValue
|
113
|
-
if structured_value.structuredValue.present?
|
114
|
-
return title_from_structured_values(structured_value.structuredValue, non_sorting_char_count)
|
115
|
-
end
|
116
|
+
return title_from_structured_values(structured_value) if structured_value.structuredValue.present?
|
116
117
|
|
117
118
|
value = structured_value.value&.strip
|
118
119
|
next unless value
|
@@ -120,8 +121,7 @@ module Cocina
|
|
120
121
|
# additional types: name, uniform ...
|
121
122
|
case structured_value.type&.downcase
|
122
123
|
when 'nonsorting characters'
|
123
|
-
|
124
|
-
non_sort_value = "#{value}#{' ' * non_sorting_size}"
|
124
|
+
non_sort_value = "#{value}#{non_sorting_padding(title, value)}"
|
125
125
|
structured_title = if structured_title.present?
|
126
126
|
"#{structured_title}#{non_sort_value}"
|
127
127
|
else
|
@@ -129,7 +129,7 @@ module Cocina
|
|
129
129
|
end
|
130
130
|
when 'part name', 'part number'
|
131
131
|
if part_name_number.blank?
|
132
|
-
part_name_number = part_name_number(
|
132
|
+
part_name_number = part_name_number(title.structuredValue)
|
133
133
|
structured_title = if !add_punctuation?
|
134
134
|
[structured_title, part_name_number].join(' ')
|
135
135
|
elsif structured_title.present?
|
@@ -163,11 +163,16 @@ module Cocina
|
|
163
163
|
title.sub(%r{[ .,;:/\\]+$}, '')
|
164
164
|
end
|
165
165
|
|
166
|
-
def
|
166
|
+
def non_sorting_padding(title, non_sorting_value)
|
167
167
|
non_sort_note = title.note&.find { |note| note.type&.downcase == 'nonsorting character count' }
|
168
|
-
|
169
|
-
|
170
|
-
|
168
|
+
if non_sort_note
|
169
|
+
padding_count = [non_sort_note.value.to_i - non_sorting_value.length, 0].max
|
170
|
+
' ' * padding_count
|
171
|
+
elsif ['\'', '-'].include?(non_sorting_value.last)
|
172
|
+
''
|
173
|
+
else
|
174
|
+
' '
|
175
|
+
end
|
171
176
|
end
|
172
177
|
|
173
178
|
# combine part name and part number:
|
@@ -8,7 +8,7 @@ module Cocina
|
|
8
8
|
|
9
9
|
class_methods do
|
10
10
|
def new(attributes = default_attributes, safe = false, validate = true, &block)
|
11
|
-
Validator.validate(self, attributes.with_indifferent_access) if validate
|
11
|
+
Validators::Validator.validate(self, attributes.with_indifferent_access) if validate
|
12
12
|
super(attributes, safe, &block)
|
13
13
|
end
|
14
14
|
end
|
@@ -16,7 +16,7 @@ module Cocina
|
|
16
16
|
def new(*args)
|
17
17
|
validate = args.first.delete(:validate) if args.present?
|
18
18
|
new_model = super(*args)
|
19
|
-
Validator.validate(new_model.class, new_model.to_h) if
|
19
|
+
Validators::Validator.validate(new_model.class, new_model.to_h) if validate || validate.nil?
|
20
20
|
new_model
|
21
21
|
end
|
22
22
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
module Validators
|
6
|
+
# Validates that only a single CatalogLink has refresh set to true
|
7
|
+
class CatalogLinksValidator
|
8
|
+
MAX_REFRESH_CATALOG_LINKS = 1
|
9
|
+
|
10
|
+
def self.validate(clazz, attributes)
|
11
|
+
new(clazz, attributes).validate
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(clazz, attributes)
|
15
|
+
@clazz = clazz
|
16
|
+
@attributes = attributes
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate
|
20
|
+
return unless meets_preconditions?
|
21
|
+
|
22
|
+
return if refresh_catalog_links.length <= MAX_REFRESH_CATALOG_LINKS
|
23
|
+
|
24
|
+
raise ValidationError, "Multiple catalog links have 'refresh' property set to true " \
|
25
|
+
"(only one allowed) #{refresh_catalog_links}"
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :clazz, :attributes
|
31
|
+
|
32
|
+
def meets_preconditions?
|
33
|
+
(dro? || collection?) && Array(attributes.dig(:identification, :catalogLinks)).any?
|
34
|
+
end
|
35
|
+
|
36
|
+
def refresh_catalog_links
|
37
|
+
attributes.dig(:identification, :catalogLinks).select { |catalog_link| catalog_link[:refresh] }
|
38
|
+
end
|
39
|
+
|
40
|
+
def dro?
|
41
|
+
(clazz::TYPES & DRO::TYPES).any?
|
42
|
+
rescue NameError
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
def collection?
|
47
|
+
(clazz::TYPES & Collection::TYPES).any?
|
48
|
+
rescue NameError
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
module Validators
|
6
|
+
# Validates that shelve and publish file attributes are set to false for dark DRO objects.
|
7
|
+
class DarkValidator
|
8
|
+
def self.validate(clazz, attributes)
|
9
|
+
new(clazz, attributes).validate
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(clazz, attributes)
|
13
|
+
@clazz = clazz
|
14
|
+
@attributes = attributes
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate
|
18
|
+
return unless meets_preconditions?
|
19
|
+
|
20
|
+
return if invalid_files.empty?
|
21
|
+
|
22
|
+
raise ValidationError, 'Not all files have dark access and/or are unshelved ' \
|
23
|
+
"when object access is dark: #{invalid_filenames}"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :clazz, :attributes
|
29
|
+
|
30
|
+
def meets_preconditions?
|
31
|
+
dro? && attributes.dig(:access, :view) == 'dark'
|
32
|
+
end
|
33
|
+
|
34
|
+
def dro?
|
35
|
+
(clazz::TYPES & DRO::TYPES).any?
|
36
|
+
rescue NameError
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def invalid_files
|
41
|
+
@invalid_files ||=
|
42
|
+
[].tap do |invalid_files|
|
43
|
+
files.each do |file|
|
44
|
+
invalid_files << file if invalid?(file)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def invalid_filenames
|
50
|
+
invalid_files.map { |invalid_file| invalid_file[:filename] || invalid_file[:label] }
|
51
|
+
end
|
52
|
+
|
53
|
+
def invalid?(file)
|
54
|
+
# Ignore if a WARC
|
55
|
+
return false if file[:hasMimeType] == 'application/warc'
|
56
|
+
|
57
|
+
return true if file.dig(:administrative, :shelve)
|
58
|
+
return true if file.dig(:access, :view) != 'dark'
|
59
|
+
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
def files
|
64
|
+
[].tap do |files|
|
65
|
+
next if attributes.dig(:structural, :contains).nil?
|
66
|
+
|
67
|
+
attributes[:structural][:contains].each do |fileset|
|
68
|
+
next if fileset.dig(:structural, :contains).nil?
|
69
|
+
|
70
|
+
fileset[:structural][:contains].each do |file|
|
71
|
+
files << file
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
module Validators
|
6
|
+
# Validates types for description against description_types.yml.
|
7
|
+
class DescriptionTypesValidator
|
8
|
+
def self.validate(clazz, attributes)
|
9
|
+
new(clazz, attributes).validate
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(clazz, attributes)
|
13
|
+
@clazz = clazz
|
14
|
+
@attributes = attributes
|
15
|
+
@error_paths = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate
|
19
|
+
return unless meets_preconditions?
|
20
|
+
|
21
|
+
validate_obj(attributes, [])
|
22
|
+
|
23
|
+
return if error_paths.empty?
|
24
|
+
|
25
|
+
raise ValidationError, "Unrecognized types in description: #{error_paths.join(', ')}"
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :clazz, :attributes, :error_paths
|
31
|
+
|
32
|
+
def meets_preconditions?
|
33
|
+
attributes.key?(:description) || [Cocina::Models::Description,
|
34
|
+
Cocina::Models::RequestDescription].include?(clazz)
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_hash(hash, path)
|
38
|
+
hash.each do |key, obj|
|
39
|
+
if key == :type
|
40
|
+
validate_type(obj, path)
|
41
|
+
else
|
42
|
+
validate_obj(obj, path + [key])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate_array(array, path)
|
48
|
+
array.each_with_index do |obj, index|
|
49
|
+
validate_obj(obj, path + [index])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate_obj(obj, path)
|
54
|
+
validate_hash(obj, path) if obj.is_a?(Hash)
|
55
|
+
validate_array(obj, path) if obj.is_a?(Array)
|
56
|
+
end
|
57
|
+
|
58
|
+
def validate_type(type, path)
|
59
|
+
valid_types.each do |type_signature, types|
|
60
|
+
next unless match?(path, type_signature)
|
61
|
+
break if types.include?(type.downcase)
|
62
|
+
|
63
|
+
error_paths << path_to_s(path)
|
64
|
+
break
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def match?(path, type_signature)
|
69
|
+
clean_path(path).last(type_signature.length) == type_signature
|
70
|
+
end
|
71
|
+
|
72
|
+
# Some part of the path are ignored for the purpose of matching.
|
73
|
+
def clean_path(path)
|
74
|
+
new_path = path.reject do |part|
|
75
|
+
part.is_a?(Integer) || part == :parallelValue
|
76
|
+
end
|
77
|
+
# This needs to happen after parallelValue is removed
|
78
|
+
# to handle structuredValue > parallelValue > structuredValue
|
79
|
+
new_path.reject.with_index do |part, index|
|
80
|
+
part == :structuredValue && new_path[index - 1] == :structuredValue
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# rubocop:disable Style/ClassVars
|
85
|
+
def valid_types
|
86
|
+
# Class var to minimize loading from disk.
|
87
|
+
@@valid_types ||= begin
|
88
|
+
types = types_yaml.map do |type_signature_str, type_objs|
|
89
|
+
type_signature = type_signature_str.split('.').map(&:to_sym)
|
90
|
+
types = type_objs.map { |type_obj| type_obj['value'].downcase }
|
91
|
+
[type_signature, types]
|
92
|
+
end
|
93
|
+
# Sorting so that longer signatures match first.
|
94
|
+
types.sort { |a, b| b.first.length <=> a.first.length }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
# rubocop:enable Style/ClassVars
|
98
|
+
|
99
|
+
def types_yaml
|
100
|
+
YAML.load_file(::File.expand_path('../../../../description_types.yml', __dir__))
|
101
|
+
end
|
102
|
+
|
103
|
+
def path_to_s(path)
|
104
|
+
# This matches the format used by descriptive spreadsheets
|
105
|
+
path_str = ''
|
106
|
+
path.each_with_index do |part, index|
|
107
|
+
if part.is_a?(Integer)
|
108
|
+
path_str += (part + 1).to_s
|
109
|
+
else
|
110
|
+
path_str += '.' if index.positive?
|
111
|
+
path_str += part.to_s
|
112
|
+
end
|
113
|
+
end
|
114
|
+
path_str
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
module Validators
|
6
|
+
# Perform validation against openapi
|
7
|
+
class OpenApiValidator
|
8
|
+
def self.validate(clazz, attributes)
|
9
|
+
return unless clazz.name
|
10
|
+
|
11
|
+
method_name = clazz.name.split('::').last
|
12
|
+
request_operation = root.request_operation(:post, "/validate/#{method_name}")
|
13
|
+
|
14
|
+
# JSON.parse forces serialization of objects like DateTime.
|
15
|
+
json_attributes = JSON.parse(attributes.to_json)
|
16
|
+
# Inject cocinaVersion if needed and not present.
|
17
|
+
if operation_has_cocina_version?(request_operation) && !json_attributes.include?('cocinaVersion')
|
18
|
+
json_attributes['cocinaVersion'] = Cocina::Models::VERSION
|
19
|
+
end
|
20
|
+
|
21
|
+
request_operation.validate_request_body('application/json', json_attributes)
|
22
|
+
rescue OpenAPIParser::OpenAPIError => e
|
23
|
+
raise ValidationError, e.message
|
24
|
+
end
|
25
|
+
|
26
|
+
# rubocop:disable Metrics/AbcSize
|
27
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
28
|
+
def self.operation_has_cocina_version?(request_operation)
|
29
|
+
schema = request_operation.operation_object.request_body.content['application/json'].schema
|
30
|
+
all_of_properties = Array(schema.all_of&.flat_map { |all_of| all_of.properties&.keys }).compact
|
31
|
+
one_of_properties = Array(schema.one_of&.flat_map { |one_of| one_of.properties&.keys }).compact
|
32
|
+
properties = Array(schema.properties&.keys)
|
33
|
+
(properties + all_of_properties + one_of_properties).include?('cocinaVersion')
|
34
|
+
end
|
35
|
+
# rubocop:enable Metrics/AbcSize
|
36
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
37
|
+
private_class_method :operation_has_cocina_version?
|
38
|
+
|
39
|
+
# rubocop:disable Style/ClassVars
|
40
|
+
def self.root
|
41
|
+
@@root ||= OpenAPIParser.parse(YAML.load_file(openapi_path), strict_reference_validation: true)
|
42
|
+
end
|
43
|
+
# rubocop:enable Style/ClassVars
|
44
|
+
private_class_method :root
|
45
|
+
|
46
|
+
def self.openapi_path
|
47
|
+
::File.expand_path('../../../../openapi.yml', __dir__)
|
48
|
+
end
|
49
|
+
private_class_method :openapi_path
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
module Validators
|
6
|
+
# Perform validation against all other Validators
|
7
|
+
class Validator
|
8
|
+
VALIDATORS = [
|
9
|
+
OpenApiValidator,
|
10
|
+
DarkValidator,
|
11
|
+
CatalogLinksValidator,
|
12
|
+
DescriptionTypesValidator
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
def self.validate(clazz, attributes)
|
16
|
+
VALIDATORS.each { |validator| validator.validate(clazz, attributes) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -25,6 +25,15 @@ module Cocina
|
|
25
25
|
def self.properties
|
26
26
|
@properties ||= {}
|
27
27
|
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Returns the URI for the term `property` in this vocabulary.
|
31
|
+
#
|
32
|
+
# @param [#to_sym] property
|
33
|
+
# @return [String]
|
34
|
+
def self.[](property)
|
35
|
+
properties[property.to_sym]
|
36
|
+
end
|
28
37
|
end
|
29
38
|
end
|
30
39
|
end
|
data/openapi.yml
CHANGED
@@ -161,11 +161,8 @@ components:
|
|
161
161
|
description: Name of role
|
162
162
|
type: string
|
163
163
|
enum:
|
164
|
-
- 'dor-apo-creator'
|
165
164
|
- 'dor-apo-depositor'
|
166
165
|
- 'dor-apo-manager'
|
167
|
-
- 'dor-apo-metadata'
|
168
|
-
- 'dor-apo-reviewer'
|
169
166
|
- 'dor-apo-viewer'
|
170
167
|
- 'sdr-administrator'
|
171
168
|
- 'sdr-viewer'
|
@@ -369,18 +366,28 @@ components:
|
|
369
366
|
CatalogLink:
|
370
367
|
type: object
|
371
368
|
additionalProperties: false
|
372
|
-
required:
|
373
|
-
- catalog
|
374
|
-
- catalogRecordId
|
375
369
|
properties:
|
376
370
|
catalog:
|
377
371
|
description: Catalog that is the source of the linked record.
|
378
372
|
type: string
|
373
|
+
enum:
|
374
|
+
- symphony
|
375
|
+
- previous symphony
|
379
376
|
example: symphony
|
377
|
+
refresh:
|
378
|
+
description: Only one of the catkeys should be designated for refreshing.
|
379
|
+
This means that this key is the one used to pull metadata from the catalog
|
380
|
+
if there is more than one key present.
|
381
|
+
type: boolean
|
382
|
+
default: false
|
380
383
|
catalogRecordId:
|
381
384
|
description: Record identifier that is unique within the context of the linked record's catalog.
|
382
385
|
type: string
|
383
386
|
example: '11403803'
|
387
|
+
required:
|
388
|
+
- catalog
|
389
|
+
- catalogRecordId
|
390
|
+
- refresh
|
384
391
|
CatkeyBarcode:
|
385
392
|
description: The barcode associated with a DRO object based on catkey, prefixed with 36105
|
386
393
|
type: string
|
@@ -1256,6 +1263,8 @@ components:
|
|
1256
1263
|
$ref: '#/components/schemas/DOI'
|
1257
1264
|
sourceId:
|
1258
1265
|
$ref: '#/components/schemas/SourceId'
|
1266
|
+
required:
|
1267
|
+
- sourceId
|
1259
1268
|
Language:
|
1260
1269
|
description: Languages, scripts, symbolic systems, and notations used in all
|
1261
1270
|
or part of a resource or its descriptive metadata.
|
@@ -1424,7 +1433,7 @@ components:
|
|
1424
1433
|
format: date-time
|
1425
1434
|
lock:
|
1426
1435
|
description: Key for optimistic locking. The contents of the key is not specified.
|
1427
|
-
type: string
|
1436
|
+
type: string
|
1428
1437
|
required:
|
1429
1438
|
- lock
|
1430
1439
|
Presentation:
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cocina-models
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.73.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Coyne
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: deprecation
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: dry-struct
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -260,6 +274,7 @@ files:
|
|
260
274
|
- bin/console
|
261
275
|
- bin/setup
|
262
276
|
- cocina-models.gemspec
|
277
|
+
- description_types.yml
|
263
278
|
- docs/cocina-base.jsonld
|
264
279
|
- docs/index.html
|
265
280
|
- docs/maps/Agent.json
|
@@ -370,7 +385,11 @@ files:
|
|
370
385
|
- lib/cocina/models/title.rb
|
371
386
|
- lib/cocina/models/title_builder.rb
|
372
387
|
- lib/cocina/models/validatable.rb
|
373
|
-
- lib/cocina/models/
|
388
|
+
- lib/cocina/models/validators/catalog_links_validator.rb
|
389
|
+
- lib/cocina/models/validators/dark_validator.rb
|
390
|
+
- lib/cocina/models/validators/description_types_validator.rb
|
391
|
+
- lib/cocina/models/validators/open_api_validator.rb
|
392
|
+
- lib/cocina/models/validators/validator.rb
|
374
393
|
- lib/cocina/models/version.rb
|
375
394
|
- lib/cocina/models/vocabulary.rb
|
376
395
|
- lib/cocina/models/world_access.rb
|
@@ -1,49 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Cocina
|
4
|
-
module Models
|
5
|
-
# Perform validation against openapi
|
6
|
-
class Validator
|
7
|
-
def self.validate(clazz, attributes)
|
8
|
-
method_name = clazz.name.split('::').last
|
9
|
-
request_operation = root.request_operation(:post, "/validate/#{method_name}")
|
10
|
-
|
11
|
-
# JSON.parse forces serialization of objects like DateTime.
|
12
|
-
json_attributes = JSON.parse(attributes.to_json)
|
13
|
-
# Inject cocinaVersion if needed and not present.
|
14
|
-
if operation_has_cocina_version?(request_operation) && !json_attributes.include?('cocinaVersion')
|
15
|
-
json_attributes['cocinaVersion'] = Cocina::Models::VERSION
|
16
|
-
end
|
17
|
-
|
18
|
-
request_operation.validate_request_body('application/json', json_attributes)
|
19
|
-
rescue OpenAPIParser::OpenAPIError => e
|
20
|
-
raise ValidationError, e.message
|
21
|
-
end
|
22
|
-
|
23
|
-
# rubocop:disable Metrics/AbcSize
|
24
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
25
|
-
def self.operation_has_cocina_version?(request_operation)
|
26
|
-
schema = request_operation.operation_object.request_body.content['application/json'].schema
|
27
|
-
all_of_properties = Array(schema.all_of&.flat_map { |all_of| all_of.properties&.keys }).compact
|
28
|
-
one_of_properties = Array(schema.one_of&.flat_map { |one_of| one_of.properties&.keys }).compact
|
29
|
-
properties = Array(schema.properties&.keys)
|
30
|
-
(properties + all_of_properties + one_of_properties).include?('cocinaVersion')
|
31
|
-
end
|
32
|
-
# rubocop:enable Metrics/AbcSize
|
33
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
34
|
-
private_class_method :operation_has_cocina_version?
|
35
|
-
|
36
|
-
# rubocop:disable Style/ClassVars
|
37
|
-
def self.root
|
38
|
-
@@root ||= OpenAPIParser.parse(YAML.load_file(openapi_path), strict_reference_validation: true)
|
39
|
-
end
|
40
|
-
# rubocop:enable Style/ClassVars
|
41
|
-
private_class_method :root
|
42
|
-
|
43
|
-
def self.openapi_path
|
44
|
-
::File.expand_path('../../../openapi.yml', __dir__)
|
45
|
-
end
|
46
|
-
private_class_method :openapi_path
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|