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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 191508cff01be391223c83aba3e7c085e99b581a0f12a95dcd627572fbe5be36
4
- data.tar.gz: b4eb3927706c3a5316f30d03d0352e742b3b0a72bf966cb9b0ed6bb593a0d48d
3
+ metadata.gz: b5516cbeff2267e688f6a09b34e4a0c715a1aea91ddae03d10060bed3d364c5a
4
+ data.tar.gz: f47bd2a4ca35c95d3566883707d39a2480c0b7e18ee28bc49042aa90b24e104c
5
5
  SHA512:
6
- metadata.gz: 8a4e9c462003e94e13992d20e2adc1a88515185820c11f851b0e709913addb541efadb35d11a0c841cb563e26d0b98cea15b0c0cc9d14aeddeb93f5bc953b147
7
- data.tar.gz: 501f90b85f9386ff832b4147483d6e46fd6e8735fe87de6b868e7f68e2c6a02f1cf71759cb649103bcff93fb9e5830c11cd3ac0387e85336821237f8add0ee71
6
+ metadata.gz: 262cdfa4049c7325751645a578b2129f0f5cc4737198b82bea5728a6d693398bf2ab589dea2ae912de2de09e6abd1e3a581b84828f3ebf85ed64ba4c3a71dc25
7
+ data.tar.gz: 977677a8f5de44340117f3d2fb6ba8553e21c3c61c1770b18d44652008de6e26a6ada747de9546e993a0e4231e2263ad531d97491c5467d8aa8e18f2a303f340
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
@@ -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
- 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.
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:
@@ -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
@@ -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
@@ -47,10 +47,13 @@ module Cocina
47
47
  end
48
48
 
49
49
  def types
50
- type_properties_doc = schema_doc.properties['type']
51
- return '' if type_properties_doc.nil? || type_properties_doc.enum.nil?
50
+ type_schema_property = schema_properties.find { |schema_property| schema_property.key == 'type' }
51
+ return '' if type_schema_property.nil?
52
52
 
53
- types_list = type_properties_doc.enum.map { |item| "'#{item}'" }.join(",\n ")
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 :administrative, Administrative.optional.meta(omittable: true)
28
+ attribute(:administrative, Administrative.default { Administrative.new })
29
29
  attribute(:description, Description.default { Description.new })
30
- attribute :identification, CollectionIdentification.optional.meta(omittable: true)
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
@@ -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 :identification, Identification.optional.meta(omittable: true)
41
- attribute :structural, DROStructural.optional.meta(omittable: true)
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 :structural, FileSetStructural.optional.meta(omittable: true)
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.meta(omittable: true)
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
- # @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,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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- VERSION = '0.69.2'
5
+ VERSION = '0.72.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/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
- expected.all? do |expected_key, expected_value|
15
- # NOTE: there's no better method on Hash that I could find for this.
16
- # #include? and #member? only check keys, not k/v pairs
17
- actual.public_send(cocina_section).to_h.any? do |actual_key, actual_value|
18
- if expected_value.is_a?(Hash) && actual_value.is_a?(Hash)
19
- expected_value.all? { |pair| actual_value.to_a.include?(pair) }
20
- else
21
- actual_key == expected_key && actual_value == expected_value
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.69.2
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-03-15 00:00:00.000000000 Z
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/validator.rb
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