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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab6c36807c8585e07d8fc5529e6ef1aa08e88f7fc93d3dc7605aecb87e9138db
4
- data.tar.gz: 8a5c302070e2beab4c3606124e1b68e64a1ab1063c8c32d05e47a66db3325d78
3
+ metadata.gz: b929b4e87bdb86010e2915c19628cb96dcafd101805e3c2dcee7ce56554d492f
4
+ data.tar.gz: 7aa988aa05b14e1e0521369ae1124acb0e4433195d7cd0011b239d63fa01b97b
5
5
  SHA512:
6
- metadata.gz: 33fa2bd17896f666da8f4bdc1249a1e216f0344c0dc6d95e47b76a97b801a2ab1abf05fd1ca287e7bd23e134a34de8900e985e7db3bfea2e91b9c359be209722
7
- data.tar.gz: 93ac31afd9d5da0587d4798c8e3ae571853f0e2b3c85d6edcda231ab886d9fd4f3559e7616a70f2290629028faa745bc40406f47f0bbe5ec95574bbe084bb532
6
+ metadata.gz: a3ab493e7a4f41d48a1d583b695a6689bb5dac3117fd03d343c75ab7407a25df58755899b28a171fcdc3425a6a204582a4f112f31e51d70ef3ac766bd27d4107
7
+ data.tar.gz: 45f9b2c0d9fd7b9e9d542f12d20e90ef0aacf536fa6477f691888e8a60dfcef96efce4a5c62793f8cd7eb7e27c30286360e036413bc975ff5bf316b271adf40f
data/.rubocop.yml CHANGED
@@ -107,7 +107,7 @@ RSpec/StubbedMock: # (new in 1.44)
107
107
  Enabled: true
108
108
 
109
109
  RSpec/MultipleMemoizedHelpers:
110
- Max: 6
110
+ Enabled: false
111
111
 
112
112
  # ----- Style ------
113
113
 
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
@@ -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.
@@ -78,7 +78,6 @@ module Cocina
78
78
  'rights_description_builder.rb',
79
79
  'title_builder.rb',
80
80
  'validatable.rb',
81
- 'validator.rb',
82
81
  'version.rb',
83
82
  'vocabulary.rb'
84
83
  ].freeze
@@ -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-creator', 'dor-apo-depositor', 'dor-apo-manager', 'dor-apo-metadata', 'dor-apo-reviewer', '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')
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.meta(omittable: true)
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
- # @param [Cocina::Models::D*] cocina_object is the object to extract the title for
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(cocina_object, strategy: :first, add_punctuation: true)
13
- new(cocina_object, strategy: strategy, add_punctuation: add_punctuation).build_title
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(cocina_object, strategy:, add_punctuation:)
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 build_title
24
- @titles = cocina_object.description.title
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 :cocina_object, :strategy, :titles
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.structuredValue,
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.first.value
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 [Array<Cocina::Models::StructuredValue>] structured_values - the pieces of a structuredValue
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(structured_values, non_sorting_char_count)
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
- structured_values.each do |structured_value|
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
- non_sorting_size = [non_sorting_char_count - (value&.size || 0), 0].max
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(structured_values)
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 non_sorting_char_count(title)
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
- return 0 unless non_sort_note
169
-
170
- non_sort_note.value.to_i
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 && name
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 (validate || validate.nil?) && self.class.name
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- VERSION = '0.70.0'
5
+ VERSION = '0.73.0'
6
6
  end
7
7
  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.70.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-03-22 00:00:00.000000000 Z
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/validator.rb
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