cocina-models 0.69.2 → 0.72.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 +21 -2
- data/cocina-models.gemspec +1 -0
- data/lib/cocina/generator/generator.rb +0 -1
- data/lib/cocina/generator/schema.rb +6 -3
- data/lib/cocina/models/admin_policy_with_metadata.rb +30 -0
- data/lib/cocina/models/catalog_link.rb +3 -1
- data/lib/cocina/models/collection.rb +2 -2
- data/lib/cocina/models/collection_with_metadata.rb +39 -0
- data/lib/cocina/models/dro.rb +2 -2
- data/lib/cocina/models/dro_with_metadata.rb +51 -0
- data/lib/cocina/models/file_set.rb +1 -1
- data/lib/cocina/models/identification.rb +1 -1
- data/lib/cocina/models/object_metadata.rb +14 -0
- 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/open_api_validator.rb +53 -0
- data/lib/cocina/models/validators/validator.rb +16 -0
- data/lib/cocina/models/version.rb +1 -1
- data/lib/cocina/models/vocabulary.rb +9 -0
- data/lib/cocina/models.rb +33 -0
- data/lib/cocina/rspec/matchers.rb +13 -8
- data/openapi.yml +99 -3
- metadata +24 -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: b5516cbeff2267e688f6a09b34e4a0c715a1aea91ddae03d10060bed3d364c5a
|
4
|
+
data.tar.gz: f47bd2a4ca35c95d3566883707d39a2480c0b7e18ee28bc49042aa90b24e104c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 262cdfa4049c7325751645a578b2129f0f5cc4737198b82bea5728a6d693398bf2ab589dea2ae912de2de09e6abd1e3a581b84828f3ebf85ed64ba4c3a71dc25
|
7
|
+
data.tar.gz: 977677a8f5de44340117f3d2fb6ba8553e21c3c61c1770b18d44652008de6e26a6ada747de9546e993a0e4231e2263ad531d97491c5467d8aa8e18f2a303f340
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -53,10 +53,14 @@ which pushes the gem to rubygems.org. Next write up the release notes: https://
|
|
53
53
|
|
54
54
|
### Step 2: Update client gems coupled to the models
|
55
55
|
|
56
|
+
**NOTE**: You can skip this step if the new release is a patch-level bump only.
|
57
|
+
|
56
58
|
Next, you should release versions of [sdr-client](https://github.com/sul-dlss/sdr-client) and [dor-services-client](https://github.com/sul-dlss/dor-services-client/) pinned to this version because applications such as [Argo](https://github.com/sul-dlss/argo) depend on both of these gems using the same models.
|
57
59
|
|
58
60
|
### Step 3: Update service API specifications and gems
|
59
61
|
|
62
|
+
**NOTE**: You can skip this first half of the step if there have not been any changes to the `cocina-models` OpenAPI spec since the prior release.
|
63
|
+
|
60
64
|
The cocina-models gem is used in applications that have an API specification that accepts Cocina models. Next, make sure that the `openapi.yml` for these applications include the `openapi.yml` schema changes made in cocina-models. This list of services is known to include:
|
61
65
|
|
62
66
|
* [sul-dlss/sdr-api](https://github.com/sul-dlss/sdr-api)
|
@@ -85,7 +89,9 @@ There are scripts to help with this:
|
|
85
89
|
|
86
90
|
#### Step 4A: Create the PRs
|
87
91
|
|
88
|
-
|
92
|
+
There is a Jenkins CI job that you can run manually to create all the PRs you need. Head to https://sul-ci-prod.stanford.edu/job/SUL-DLSS/job/access-update-scripts/job/cocina-level2-updates/ and then click `Build Now`. Click the new build that is created and then `Console Output` to watch the build. Once it has completed, you can proceed with the next step.
|
93
|
+
|
94
|
+
If for some reason the above method does not work, the sul-dlss/access-update-scripts repo has a script for this: `cocina_level2_prs.rb`. You will need a github access token with scopes of "read:org" and "repo" (see https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) to run it, as noted in the comments at the top of that script.
|
89
95
|
|
90
96
|
#### Step 4B: Merge the PRs
|
91
97
|
|
@@ -95,11 +101,24 @@ access-update-scripts repo has a script for this: `cocina_level2_prs.rb`. You
|
|
95
101
|
|
96
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).
|
97
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
|
+
|
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.
|
115
|
+
|
98
116
|
## Usage conventions
|
99
117
|
|
100
118
|
The following are the recommended naming conventions for code using Cocina models:
|
101
119
|
|
102
120
|
* `cocina_item`: `Cocina::Models::DRO` instance
|
121
|
+
* `cocina_agreement`: `Cocina::Models::DRO` with type of Cocina::Models::ObjectType.agreement
|
103
122
|
* `cocina_admin_policy`: `Cocina::Models::AdminPolicy` instance
|
104
123
|
* `cocina_collection`: `Cocina::Models::Collection` instance
|
105
124
|
* `cocina_object`: `Cocina::Models::DRO` or `Cocina::Models::AdminPolicy` or `Cocina::Models::Collection` instance
|
@@ -112,7 +131,7 @@ As of the 0.69.0 release, the `cocina-models` gem provides RSpec matchers for do
|
|
112
131
|
* `expect(http_response_body_with_cocina_json).to equal_cocina_model(cocina_instance)`
|
113
132
|
* `cocina_object_with` (AKA `match_cocina_object_with`): Compare a Cocina model instance with a hash containining part of the structure of a Cocina object. Example usage:
|
114
133
|
* `expect(CocinaObjectStore).to have_received(:save).with(cocina_object_with(access: { view: 'world' }, structural: { contains: [...] }))`
|
115
|
-
* expect(updated_cocina_item).to match_cocina_object_with(structural: { hasMemberOrders: [] })
|
134
|
+
* `expect(updated_cocina_item).to match_cocina_object_with(structural: { hasMemberOrders: [] })`
|
116
135
|
* `cocina_object_with_types`: Check a Cocina object's type information. Example usage:
|
117
136
|
* `expect(object_client).to have_received(:update).with(params: cocina_object_with_types(content_type: Cocina::Models::ObjectType.book, viewing_direction: 'left-to-right'))`
|
118
137
|
* `cocina_admin_policy_with_registration_collections`: Check a Cocina admin policy's collections. Example usage:
|
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
|
@@ -47,10 +47,13 @@ module Cocina
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def types
|
50
|
-
|
51
|
-
return '' if
|
50
|
+
type_schema_property = schema_properties.find { |schema_property| schema_property.key == 'type' }
|
51
|
+
return '' if type_schema_property.nil?
|
52
52
|
|
53
|
-
|
53
|
+
type_schema_doc = type_schema_property.schema_doc
|
54
|
+
return '' if type_schema_doc.enum.nil?
|
55
|
+
|
56
|
+
types_list = type_schema_doc.enum.map { |item| "'#{item}'" }.join(",\n ")
|
54
57
|
|
55
58
|
<<~RUBY
|
56
59
|
include Checkable
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
class AdminPolicyWithMetadata < Struct
|
6
|
+
include Validatable
|
7
|
+
|
8
|
+
include Checkable
|
9
|
+
|
10
|
+
TYPES = ['https://cocina.sul.stanford.edu/models/admin_policy'].freeze
|
11
|
+
|
12
|
+
# The version of Cocina with which this object conforms.
|
13
|
+
# example: 1.2.3
|
14
|
+
attribute :cocinaVersion, Types::Strict::String.default(Cocina::Models::VERSION)
|
15
|
+
attribute :type, Types::Strict::String.enum(*AdminPolicyWithMetadata::TYPES)
|
16
|
+
# example: druid:bc123df4567
|
17
|
+
attribute :externalIdentifier, Types::Strict::String
|
18
|
+
attribute :label, Types::Strict::String
|
19
|
+
attribute :version, Types::Strict::Integer
|
20
|
+
attribute(:administrative, AdminPolicyAdministrative.default { AdminPolicyAdministrative.new })
|
21
|
+
attribute :description, Description.optional.meta(omittable: true)
|
22
|
+
# When the object was created.
|
23
|
+
attribute :created, Types::Params::DateTime.meta(omittable: true)
|
24
|
+
# When the object was modified.
|
25
|
+
attribute :modified, Types::Params::DateTime.meta(omittable: true)
|
26
|
+
# Key for optimistic locking. The contents of the key is not specified.
|
27
|
+
attribute :lock, Types::Strict::String
|
28
|
+
end
|
29
|
+
end
|
30
|
+
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
|
@@ -25,9 +25,9 @@ module Cocina
|
|
25
25
|
# Version for the Collection within SDR.
|
26
26
|
attribute :version, Types::Strict::Integer
|
27
27
|
attribute(:access, CollectionAccess.default { CollectionAccess.new })
|
28
|
-
attribute
|
28
|
+
attribute(:administrative, Administrative.default { Administrative.new })
|
29
29
|
attribute(:description, Description.default { Description.new })
|
30
|
-
attribute
|
30
|
+
attribute(:identification, CollectionIdentification.default { CollectionIdentification.new })
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
class CollectionWithMetadata < Struct
|
6
|
+
include Validatable
|
7
|
+
|
8
|
+
include Checkable
|
9
|
+
|
10
|
+
TYPES = ['https://cocina.sul.stanford.edu/models/collection',
|
11
|
+
'https://cocina.sul.stanford.edu/models/curated-collection',
|
12
|
+
'https://cocina.sul.stanford.edu/models/user-collection',
|
13
|
+
'https://cocina.sul.stanford.edu/models/exhibit',
|
14
|
+
'https://cocina.sul.stanford.edu/models/series'].freeze
|
15
|
+
|
16
|
+
# The version of Cocina with which this object conforms.
|
17
|
+
# example: 1.2.3
|
18
|
+
attribute :cocinaVersion, Types::Strict::String.default(Cocina::Models::VERSION)
|
19
|
+
# The content type of the Collection. Selected from an established set of values.
|
20
|
+
attribute :type, Types::Strict::String.enum(*CollectionWithMetadata::TYPES)
|
21
|
+
# example: druid:bc123df4567
|
22
|
+
attribute :externalIdentifier, Types::Strict::String
|
23
|
+
# Primary processing label (can be same as title) for a Collection.
|
24
|
+
attribute :label, Types::Strict::String
|
25
|
+
# Version for the Collection within SDR.
|
26
|
+
attribute :version, Types::Strict::Integer
|
27
|
+
attribute(:access, CollectionAccess.default { CollectionAccess.new })
|
28
|
+
attribute(:administrative, Administrative.default { Administrative.new })
|
29
|
+
attribute(:description, Description.default { Description.new })
|
30
|
+
attribute(:identification, CollectionIdentification.default { CollectionIdentification.new })
|
31
|
+
# When the object was created.
|
32
|
+
attribute :created, Types::Params::DateTime.meta(omittable: true)
|
33
|
+
# When the object was modified.
|
34
|
+
attribute :modified, Types::Params::DateTime.meta(omittable: true)
|
35
|
+
# Key for optimistic locking. The contents of the key is not specified.
|
36
|
+
attribute :lock, Types::Strict::String
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/cocina/models/dro.rb
CHANGED
@@ -37,8 +37,8 @@ module Cocina
|
|
37
37
|
attribute(:access, DROAccess.default { DROAccess.new })
|
38
38
|
attribute(:administrative, Administrative.default { Administrative.new })
|
39
39
|
attribute(:description, Description.default { Description.new })
|
40
|
-
attribute
|
41
|
-
attribute
|
40
|
+
attribute(:identification, Identification.default { Identification.new })
|
41
|
+
attribute(:structural, DROStructural.default { DROStructural.new })
|
42
42
|
attribute :geographic, Geographic.optional.meta(omittable: true)
|
43
43
|
end
|
44
44
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
class DROWithMetadata < Struct
|
6
|
+
include Validatable
|
7
|
+
|
8
|
+
include Checkable
|
9
|
+
|
10
|
+
TYPES = ['https://cocina.sul.stanford.edu/models/object',
|
11
|
+
'https://cocina.sul.stanford.edu/models/3d',
|
12
|
+
'https://cocina.sul.stanford.edu/models/agreement',
|
13
|
+
'https://cocina.sul.stanford.edu/models/book',
|
14
|
+
'https://cocina.sul.stanford.edu/models/document',
|
15
|
+
'https://cocina.sul.stanford.edu/models/geo',
|
16
|
+
'https://cocina.sul.stanford.edu/models/image',
|
17
|
+
'https://cocina.sul.stanford.edu/models/page',
|
18
|
+
'https://cocina.sul.stanford.edu/models/photograph',
|
19
|
+
'https://cocina.sul.stanford.edu/models/manuscript',
|
20
|
+
'https://cocina.sul.stanford.edu/models/map',
|
21
|
+
'https://cocina.sul.stanford.edu/models/media',
|
22
|
+
'https://cocina.sul.stanford.edu/models/track',
|
23
|
+
'https://cocina.sul.stanford.edu/models/webarchive-binary',
|
24
|
+
'https://cocina.sul.stanford.edu/models/webarchive-seed'].freeze
|
25
|
+
|
26
|
+
# The version of Cocina with which this object conforms.
|
27
|
+
# example: 1.2.3
|
28
|
+
attribute :cocinaVersion, Types::Strict::String.default(Cocina::Models::VERSION)
|
29
|
+
# The content type of the DRO. Selected from an established set of values.
|
30
|
+
attribute :type, Types::Strict::String.enum(*DROWithMetadata::TYPES)
|
31
|
+
# example: druid:bc123df4567
|
32
|
+
attribute :externalIdentifier, Types::Strict::String
|
33
|
+
# Primary processing label (can be same as title) for a DRO.
|
34
|
+
attribute :label, Types::Strict::String
|
35
|
+
# Version for the DRO within SDR.
|
36
|
+
attribute :version, Types::Strict::Integer
|
37
|
+
attribute(:access, DROAccess.default { DROAccess.new })
|
38
|
+
attribute(:administrative, Administrative.default { Administrative.new })
|
39
|
+
attribute(:description, Description.default { Description.new })
|
40
|
+
attribute(:identification, Identification.default { Identification.new })
|
41
|
+
attribute(:structural, DROStructural.default { DROStructural.new })
|
42
|
+
attribute :geographic, Geographic.optional.meta(omittable: true)
|
43
|
+
# When the object was created.
|
44
|
+
attribute :created, Types::Params::DateTime.meta(omittable: true)
|
45
|
+
# When the object was modified.
|
46
|
+
attribute :modified, Types::Params::DateTime.meta(omittable: true)
|
47
|
+
# Key for optimistic locking. The contents of the key is not specified.
|
48
|
+
attribute :lock, Types::Strict::String
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -29,7 +29,7 @@ module Cocina
|
|
29
29
|
attribute :label, Types::Strict::String
|
30
30
|
# Version for the Fileset within SDR.
|
31
31
|
attribute :version, Types::Strict::Integer
|
32
|
-
attribute
|
32
|
+
attribute(:structural, FileSetStructural.default { FileSetStructural.new })
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -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
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
class ObjectMetadata < Struct
|
6
|
+
# When the object was created.
|
7
|
+
attribute :created, Types::Params::DateTime.meta(omittable: true)
|
8
|
+
# When the object was modified.
|
9
|
+
attribute :modified, Types::Params::DateTime.meta(omittable: true)
|
10
|
+
# Key for optimistic locking. The contents of the key is not specified.
|
11
|
+
attribute :lock, Types::Strict::String
|
12
|
+
end
|
13
|
+
end
|
14
|
+
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,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,16 @@
|
|
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 = [OpenApiValidator, DarkValidator, CatalogLinksValidator].freeze
|
9
|
+
|
10
|
+
def self.validate(clazz, attributes)
|
11
|
+
VALIDATORS.each { |validator| validator.validate(clazz, attributes) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
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/lib/cocina/models.rb
CHANGED
@@ -21,6 +21,7 @@ class CocinaModelsInflector < Zeitwerk::Inflector
|
|
21
21
|
'request_dro' => 'RequestDRO',
|
22
22
|
'dro_access' => 'DROAccess',
|
23
23
|
'dro_structural' => 'DROStructural',
|
24
|
+
'dro_with_metadata' => 'DROWithMetadata',
|
24
25
|
'request_dro_structural' => 'RequestDROStructural',
|
25
26
|
'rspec' => 'RSpec',
|
26
27
|
'version' => 'VERSION'
|
@@ -110,6 +111,38 @@ module Cocina
|
|
110
111
|
clazz.new(dyn, false, validate)
|
111
112
|
end
|
112
113
|
|
114
|
+
# Coerces DROWithMetadata, CollectionWithMetadata, AdminPolicyWithMetadata to DRO, Collection, AdminPolicy
|
115
|
+
# @param [DROWithMetadata,CollectionWithMetadata,AdminPolicyWithMetadata] cocina_object
|
116
|
+
# @return [DRO,Collection,AdminPolicy]
|
117
|
+
def self.without_metadata(cocina_object)
|
118
|
+
build(cocina_object.to_h.except(:created, :modified, :lock))
|
119
|
+
end
|
120
|
+
|
121
|
+
# Adds metadata to a DRO, Collection, AdminPolicy
|
122
|
+
# or updates for a DROWithMetadata, CollectionWithMetadata, AdminPolicyWithMetadata
|
123
|
+
# @param [DROWithMetadata,CollectionWithMetadata,
|
124
|
+
# AdminPolicyWithMetadata,DRO,Collection,AdminPolicy] cocina_object
|
125
|
+
# @param [String] lock
|
126
|
+
# @param [DateTime] created
|
127
|
+
# @param [DateTime] modified
|
128
|
+
# @return [DROWithMetadata,CollectionWithMetadata,AdminPolicyWithMetadata]
|
129
|
+
def self.with_metadata(cocina_object, lock, created: nil, modified: nil)
|
130
|
+
props = cocina_object.to_h
|
131
|
+
props[:created] = created.iso8601 if created
|
132
|
+
props[:modified] = modified.iso8601 if modified
|
133
|
+
props[:lock] = lock
|
134
|
+
|
135
|
+
clazz = case cocina_object.type
|
136
|
+
when *DRO::TYPES
|
137
|
+
DROWithMetadata
|
138
|
+
when *Collection::TYPES
|
139
|
+
CollectionWithMetadata
|
140
|
+
else
|
141
|
+
AdminPolicyWithMetadata
|
142
|
+
end
|
143
|
+
clazz.new(props)
|
144
|
+
end
|
145
|
+
|
113
146
|
def self.type_for(dyn)
|
114
147
|
dyn.with_indifferent_access.fetch('type')
|
115
148
|
rescue KeyError
|
@@ -11,16 +11,21 @@ module Cocina
|
|
11
11
|
matcher :cocina_object_with do |**kwargs|
|
12
12
|
kwargs.each do |cocina_section, expected|
|
13
13
|
match do |actual|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
# created, modified, lock for *WithMetadata don't respond to all?
|
15
|
+
if expected.respond_to?(:all?)
|
16
|
+
expected.all? do |expected_key, expected_value|
|
17
|
+
# NOTE: there's no better method on Hash that I could find for this.
|
18
|
+
# #include? and #member? only check keys, not k/v pairs
|
19
|
+
actual.public_send(cocina_section).to_h.any? do |actual_key, actual_value|
|
20
|
+
if expected_value.is_a?(Hash) && actual_value.is_a?(Hash)
|
21
|
+
expected_value.all? { |pair| actual_value.to_a.include?(pair) }
|
22
|
+
else
|
23
|
+
actual_key == expected_key && actual_value == expected_value
|
24
|
+
end
|
22
25
|
end
|
23
26
|
end
|
27
|
+
else
|
28
|
+
expected == actual.public_send(cocina_section)
|
24
29
|
end
|
25
30
|
end
|
26
31
|
end
|
data/openapi.yml
CHANGED
@@ -31,6 +31,18 @@ paths:
|
|
31
31
|
responses:
|
32
32
|
'200':
|
33
33
|
description: noop
|
34
|
+
/validate/DROWithMetadata:
|
35
|
+
post:
|
36
|
+
summary: Validate a DRO with object metadata
|
37
|
+
requestBody:
|
38
|
+
required: true
|
39
|
+
content:
|
40
|
+
application/json:
|
41
|
+
schema:
|
42
|
+
$ref: '#/components/schemas/DROWithMetadata'
|
43
|
+
responses:
|
44
|
+
'200':
|
45
|
+
description: noop
|
34
46
|
/validate/Collection:
|
35
47
|
post:
|
36
48
|
summary: Validate a Collection
|
@@ -55,6 +67,18 @@ paths:
|
|
55
67
|
responses:
|
56
68
|
'200':
|
57
69
|
description: noop
|
70
|
+
/validate/CollectionWithMetadata:
|
71
|
+
post:
|
72
|
+
summary: Validate a Collection with object metadata
|
73
|
+
requestBody:
|
74
|
+
required: true
|
75
|
+
content:
|
76
|
+
application/json:
|
77
|
+
schema:
|
78
|
+
$ref: '#/components/schemas/CollectionWithMetadata'
|
79
|
+
responses:
|
80
|
+
'200':
|
81
|
+
description: noop
|
58
82
|
/validate/AdminPolicy:
|
59
83
|
post:
|
60
84
|
summary: Validate an AdminPolicy
|
@@ -79,6 +103,18 @@ paths:
|
|
79
103
|
responses:
|
80
104
|
'200':
|
81
105
|
description: noop
|
106
|
+
/validate/AdminPolicyWithMetadata:
|
107
|
+
post:
|
108
|
+
summary: Validate an AdminPolicy with object metadata
|
109
|
+
requestBody:
|
110
|
+
required: true
|
111
|
+
content:
|
112
|
+
application/json:
|
113
|
+
schema:
|
114
|
+
$ref: '#/components/schemas/AdminPolicyWithMetadata'
|
115
|
+
responses:
|
116
|
+
'200':
|
117
|
+
description: noop
|
82
118
|
/validate/Description:
|
83
119
|
post:
|
84
120
|
summary: Validate a Description
|
@@ -295,6 +331,14 @@ components:
|
|
295
331
|
description: The license governing reuse of the Collection. Should be an IRI for known licenses (i.e. CC, RightsStatement.org URI, etc.).
|
296
332
|
type: string
|
297
333
|
nullable: true
|
334
|
+
# AdminPolicyWithMetadata schema should not be copied to sdr-api and dor-services-app.
|
335
|
+
AdminPolicyWithMetadata:
|
336
|
+
description: Admin Policy with addition object metadata.
|
337
|
+
type: object
|
338
|
+
additionalProperties: false
|
339
|
+
allOf:
|
340
|
+
- $ref: "#/components/schemas/AdminPolicy"
|
341
|
+
- $ref: "#/components/schemas/ObjectMetadata"
|
298
342
|
AppliesTo:
|
299
343
|
description: Property model for indicating the parts, aspects, or versions of the resource to which a
|
300
344
|
descriptive element is applicable.
|
@@ -325,18 +369,28 @@ components:
|
|
325
369
|
CatalogLink:
|
326
370
|
type: object
|
327
371
|
additionalProperties: false
|
328
|
-
required:
|
329
|
-
- catalog
|
330
|
-
- catalogRecordId
|
331
372
|
properties:
|
332
373
|
catalog:
|
333
374
|
description: Catalog that is the source of the linked record.
|
334
375
|
type: string
|
376
|
+
enum:
|
377
|
+
- symphony
|
378
|
+
- previous symphony
|
335
379
|
example: symphony
|
380
|
+
refresh:
|
381
|
+
description: Only one of the catkeys should be designated for refreshing.
|
382
|
+
This means that this key is the one used to pull metadata from the catalog
|
383
|
+
if there is more than one key present.
|
384
|
+
type: boolean
|
385
|
+
default: false
|
336
386
|
catalogRecordId:
|
337
387
|
description: Record identifier that is unique within the context of the linked record's catalog.
|
338
388
|
type: string
|
339
389
|
example: '11403803'
|
390
|
+
required:
|
391
|
+
- catalog
|
392
|
+
- catalogRecordId
|
393
|
+
- refresh
|
340
394
|
CatkeyBarcode:
|
341
395
|
description: The barcode associated with a DRO object based on catkey, prefixed with 36105
|
342
396
|
type: string
|
@@ -414,6 +468,8 @@ components:
|
|
414
468
|
- type
|
415
469
|
- version
|
416
470
|
- access
|
471
|
+
- administrative
|
472
|
+
- identification
|
417
473
|
CollectionAccess:
|
418
474
|
description: Access metadata for collections
|
419
475
|
type: object
|
@@ -450,6 +506,14 @@ components:
|
|
450
506
|
$ref: '#/components/schemas/CatalogLink'
|
451
507
|
sourceId:
|
452
508
|
$ref: '#/components/schemas/SourceId'
|
509
|
+
# CollectionWithMetadata schema should not be copied to sdr-api and dor-services-app.
|
510
|
+
CollectionWithMetadata:
|
511
|
+
description: Collection with addition object metadata.
|
512
|
+
type: object
|
513
|
+
additionalProperties: false
|
514
|
+
allOf:
|
515
|
+
- $ref: "#/components/schemas/Collection"
|
516
|
+
- $ref: "#/components/schemas/ObjectMetadata"
|
453
517
|
Contributor:
|
454
518
|
description: Property model for describing agents contributing in some way to
|
455
519
|
the creation and history of the resource.
|
@@ -894,6 +958,8 @@ components:
|
|
894
958
|
- label
|
895
959
|
- type
|
896
960
|
- version
|
961
|
+
- identification
|
962
|
+
- structural
|
897
963
|
DROAccess:
|
898
964
|
type: object
|
899
965
|
additionalProperties: false
|
@@ -968,6 +1034,14 @@ components:
|
|
968
1034
|
type: array
|
969
1035
|
items:
|
970
1036
|
$ref: '#/components/schemas/Druid'
|
1037
|
+
# DROWithMetadata schema should not be copied to sdr-api and dor-services-app.
|
1038
|
+
DROWithMetadata:
|
1039
|
+
description: DRO with addition object metadata.
|
1040
|
+
type: object
|
1041
|
+
additionalProperties: false
|
1042
|
+
allOf:
|
1043
|
+
- $ref: "#/components/schemas/DRO"
|
1044
|
+
- $ref: "#/components/schemas/ObjectMetadata"
|
971
1045
|
Druid:
|
972
1046
|
type: string
|
973
1047
|
pattern: '^druid:[b-df-hjkmnp-tv-z]{2}[0-9]{3}[b-df-hjkmnp-tv-z]{2}[0-9]{4}$'
|
@@ -1158,6 +1232,7 @@ components:
|
|
1158
1232
|
- label
|
1159
1233
|
- type
|
1160
1234
|
- version
|
1235
|
+
- structural
|
1161
1236
|
FileSetStructural:
|
1162
1237
|
description: Structural metadata
|
1163
1238
|
type: object
|
@@ -1191,6 +1266,8 @@ components:
|
|
1191
1266
|
$ref: '#/components/schemas/DOI'
|
1192
1267
|
sourceId:
|
1193
1268
|
$ref: '#/components/schemas/SourceId'
|
1269
|
+
required:
|
1270
|
+
- sourceId
|
1194
1271
|
Language:
|
1195
1272
|
description: Languages, scripts, symbolic systems, and notations used in all
|
1196
1273
|
or part of a resource or its descriptive metadata.
|
@@ -1343,6 +1420,25 @@ components:
|
|
1343
1420
|
required:
|
1344
1421
|
- type
|
1345
1422
|
- digest
|
1423
|
+
# ObjectMetadata schema should not be copied to sdr-api and dor-services-app.
|
1424
|
+
ObjectMetadata:
|
1425
|
+
description: Metadata for a cocina object.
|
1426
|
+
type: object
|
1427
|
+
additionalProperties: false
|
1428
|
+
properties:
|
1429
|
+
created:
|
1430
|
+
description: When the object was created.
|
1431
|
+
type: string
|
1432
|
+
format: date-time
|
1433
|
+
modified:
|
1434
|
+
description: When the object was modified.
|
1435
|
+
type: string
|
1436
|
+
format: date-time
|
1437
|
+
lock:
|
1438
|
+
description: Key for optimistic locking. The contents of the key is not specified.
|
1439
|
+
type: string
|
1440
|
+
required:
|
1441
|
+
- lock
|
1346
1442
|
Presentation:
|
1347
1443
|
description: Presentation data for the File.
|
1348
1444
|
type: object
|
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.72.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-11 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
|
@@ -293,6 +307,7 @@ files:
|
|
293
307
|
- lib/cocina/models/admin_policy.rb
|
294
308
|
- lib/cocina/models/admin_policy_access_template.rb
|
295
309
|
- lib/cocina/models/admin_policy_administrative.rb
|
310
|
+
- lib/cocina/models/admin_policy_with_metadata.rb
|
296
311
|
- lib/cocina/models/administrative.rb
|
297
312
|
- lib/cocina/models/applies_to.rb
|
298
313
|
- lib/cocina/models/business_barcode.rb
|
@@ -304,6 +319,7 @@ files:
|
|
304
319
|
- lib/cocina/models/collection.rb
|
305
320
|
- lib/cocina/models/collection_access.rb
|
306
321
|
- lib/cocina/models/collection_identification.rb
|
322
|
+
- lib/cocina/models/collection_with_metadata.rb
|
307
323
|
- lib/cocina/models/contributor.rb
|
308
324
|
- lib/cocina/models/controlled_digital_lending_access.rb
|
309
325
|
- lib/cocina/models/dark_access.rb
|
@@ -324,6 +340,7 @@ files:
|
|
324
340
|
- lib/cocina/models/dro_access.rb
|
325
341
|
- lib/cocina/models/dro_rights_description_builder.rb
|
326
342
|
- lib/cocina/models/dro_structural.rb
|
343
|
+
- lib/cocina/models/dro_with_metadata.rb
|
327
344
|
- lib/cocina/models/druid.rb
|
328
345
|
- lib/cocina/models/embargo.rb
|
329
346
|
- lib/cocina/models/event.rb
|
@@ -341,6 +358,7 @@ files:
|
|
341
358
|
- lib/cocina/models/location_based_access.rb
|
342
359
|
- lib/cocina/models/location_based_download_access.rb
|
343
360
|
- lib/cocina/models/message_digest.rb
|
361
|
+
- lib/cocina/models/object_metadata.rb
|
344
362
|
- lib/cocina/models/object_type.rb
|
345
363
|
- lib/cocina/models/presentation.rb
|
346
364
|
- lib/cocina/models/purl.rb
|
@@ -366,7 +384,10 @@ files:
|
|
366
384
|
- lib/cocina/models/title.rb
|
367
385
|
- lib/cocina/models/title_builder.rb
|
368
386
|
- lib/cocina/models/validatable.rb
|
369
|
-
- lib/cocina/models/
|
387
|
+
- lib/cocina/models/validators/catalog_links_validator.rb
|
388
|
+
- lib/cocina/models/validators/dark_validator.rb
|
389
|
+
- lib/cocina/models/validators/open_api_validator.rb
|
390
|
+
- lib/cocina/models/validators/validator.rb
|
370
391
|
- lib/cocina/models/version.rb
|
371
392
|
- lib/cocina/models/vocabulary.rb
|
372
393
|
- 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
|