cocina-models 0.81.0 → 0.84.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: 0d8392fadece0bb4ec12ed93e958c99abf04e29ad0a78c14ee8651ec8ecd1e37
4
- data.tar.gz: db4a5de344bb53d98b2b71f725e8b1d1b9e0d5e0b28416989ad55f19df826376
3
+ metadata.gz: 326a7390c3ec14bfbc4ec9e4e79f26528b9dfa214f38aefd0646baddfbad96d5
4
+ data.tar.gz: cce43d9334fd16e47e790a082bbc999cf969b18ab78dc3d5e7015f757f6f4ea0
5
5
  SHA512:
6
- metadata.gz: 4880e4eb86935116e0bec017926aeab5e5aa12d8792ee5066d0aeef5f514e6f13f63c8bc68c03035fdff2748f95aa7215f236384e109a8e532bd76e9677ae53c
7
- data.tar.gz: 22154804ee4b103bdbd3aaaee449327bb4a6c9bc1dce9431a5311ed06c5e76039ccb420634d275f56d644a60ffa529bd9cb822f9c549be057f4972d791a34bf7
6
+ metadata.gz: d1a83488b4c5a07ae64073693c0a77424ce10fbfdada5e769fca6794d75cddc5c789d04ab92e35fa80a30eae82a0f8c284753df16ddab044bcd1ad11030b22f5
7
+ data.tar.gz: 6604a503733106b82f2e0155a2632a1eb8d00881dc762b52779179a96d962fd5e5143be6ebbe2738cb5d35eac88ee95d039c7f358d2664f10f6a82f71626b058
data/.circleci/config.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  version: 2.1
2
2
  orbs:
3
- ruby-rails: sul-dlss/ruby-rails@2.0.0
3
+ ruby-rails: sul-dlss/ruby-rails@3.1.2
4
4
  workflows:
5
5
  build:
6
6
  jobs:
data/.gitignore CHANGED
@@ -9,5 +9,3 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
-
13
- Gemfile.lock
data/.rubocop.yml CHANGED
@@ -184,7 +184,7 @@ Style/SoleNestedConditional: # (new in 0.89)
184
184
  Style/StringConcatenation:
185
185
  Enabled: true
186
186
 
187
- Gemspec/DateAssignment: # new in 1.10
187
+ Gemspec/DeprecatedAttributeAssignment: # new in 1.10
188
188
  Enabled: true
189
189
  Gemspec/RequireMFA: # new in 1.23
190
190
  Enabled: true
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config --auto-gen-only-exclude`
3
- # on 2022-04-27 19:20:35 UTC using RuboCop version 1.28.2.
3
+ # on 2022-06-08 23:20:02 UTC using RuboCop version 1.28.2.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -35,7 +35,7 @@ Lint/UnusedMethodArgument:
35
35
  - 'lib/cocina/models/mapping/from_mods/subject.rb'
36
36
  - 'lib/cocina/models/mapping/to_mods/event.rb'
37
37
 
38
- # Offense count: 95
38
+ # Offense count: 96
39
39
  # Configuration parameters: IgnoredMethods, CountRepeatedAttributes.
40
40
  Metrics/AbcSize:
41
41
  Max: 40
@@ -56,16 +56,17 @@ Metrics/ParameterLists:
56
56
  RSpec/DescribeClass:
57
57
  Enabled: false
58
58
 
59
- # Offense count: 89
59
+ # Offense count: 87
60
60
  # Configuration parameters: CountAsOne.
61
61
  RSpec/ExampleLength:
62
62
  Max: 128
63
63
 
64
- # Offense count: 9
64
+ # Offense count: 10
65
65
  # Configuration parameters: Max.
66
66
  RSpec/NestedGroups:
67
67
  Exclude:
68
68
  - 'spec/cocina/models/mapping/normalizers/mods/origin_info_normalizer_spec.rb'
69
+ - 'spec/cocina/models/validators/date_time_validator_spec.rb'
69
70
 
70
71
  # Offense count: 1
71
72
  # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals.
@@ -87,7 +88,7 @@ Style/MultilineBlockChain:
87
88
  - 'lib/cocina/models/mapping/to_mods/form.rb'
88
89
  - 'lib/cocina/models/mapping/to_mods/subject.rb'
89
90
 
90
- # Offense count: 206
91
+ # Offense count: 223
91
92
  # This cop supports safe auto-correction (--auto-correct).
92
93
  # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns.
93
94
  # URISchemes: http, https
data/Gemfile.lock ADDED
@@ -0,0 +1,158 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ cocina-models (0.84.0)
5
+ activesupport
6
+ deprecation
7
+ dry-struct (~> 1.0)
8
+ dry-types (~> 1.1)
9
+ edtf
10
+ equivalent-xml
11
+ jsonpath
12
+ nokogiri
13
+ openapi3_parser
14
+ openapi_parser (>= 0.11.1, < 1.0)
15
+ rss
16
+ super_diff
17
+ thor
18
+ zeitwerk (~> 2.1)
19
+
20
+ GEM
21
+ remote: https://rubygems.org/
22
+ specs:
23
+ activesupport (7.0.3.1)
24
+ concurrent-ruby (~> 1.0, >= 1.0.2)
25
+ i18n (>= 1.6, < 2)
26
+ minitest (>= 5.1)
27
+ tzinfo (~> 2.0)
28
+ ast (2.4.2)
29
+ attr_extras (6.2.5)
30
+ byebug (11.1.3)
31
+ committee (4.4.0)
32
+ json_schema (~> 0.14, >= 0.14.3)
33
+ openapi_parser (>= 0.11.1, < 1.0)
34
+ rack (>= 1.5)
35
+ commonmarker (0.23.5)
36
+ concurrent-ruby (1.1.10)
37
+ deprecation (1.1.0)
38
+ activesupport
39
+ diff-lcs (1.5.0)
40
+ docile (1.4.0)
41
+ dry-container (0.10.1)
42
+ concurrent-ruby (~> 1.0)
43
+ dry-core (0.8.1)
44
+ concurrent-ruby (~> 1.0)
45
+ dry-inflector (0.3.0)
46
+ dry-logic (1.2.0)
47
+ concurrent-ruby (~> 1.0)
48
+ dry-core (~> 0.5, >= 0.5)
49
+ dry-struct (1.4.0)
50
+ dry-core (~> 0.5, >= 0.5)
51
+ dry-types (~> 1.5)
52
+ ice_nine (~> 0.11)
53
+ dry-types (1.5.1)
54
+ concurrent-ruby (~> 1.0)
55
+ dry-container (~> 0.3)
56
+ dry-core (~> 0.5, >= 0.5)
57
+ dry-inflector (~> 0.1, >= 0.1.2)
58
+ dry-logic (~> 1.0, >= 1.0.2)
59
+ edtf (3.1.0)
60
+ activesupport (>= 3.0, < 8.0)
61
+ equivalent-xml (0.6.0)
62
+ nokogiri (>= 1.4.3)
63
+ i18n (1.12.0)
64
+ concurrent-ruby (~> 1.0)
65
+ ice_nine (0.11.2)
66
+ json (2.6.2)
67
+ json_schema (0.21.0)
68
+ jsonpath (1.1.2)
69
+ multi_json
70
+ mini_portile2 (2.8.0)
71
+ minitest (5.16.3)
72
+ multi_json (1.15.0)
73
+ nokogiri (1.13.8)
74
+ mini_portile2 (~> 2.8.0)
75
+ racc (~> 1.4)
76
+ openapi3_parser (0.9.2)
77
+ commonmarker (~> 0.17)
78
+ openapi_parser (0.15.0)
79
+ optimist (3.0.1)
80
+ parallel (1.22.1)
81
+ parser (3.1.2.1)
82
+ ast (~> 2.4.1)
83
+ patience_diff (1.2.0)
84
+ optimist (~> 3.0)
85
+ racc (1.6.0)
86
+ rack (2.2.4)
87
+ rainbow (3.1.1)
88
+ rake (13.0.6)
89
+ regexp_parser (2.5.0)
90
+ rexml (3.2.5)
91
+ rspec (3.11.0)
92
+ rspec-core (~> 3.11.0)
93
+ rspec-expectations (~> 3.11.0)
94
+ rspec-mocks (~> 3.11.0)
95
+ rspec-core (3.11.0)
96
+ rspec-support (~> 3.11.0)
97
+ rspec-expectations (3.11.0)
98
+ diff-lcs (>= 1.2.0, < 2.0)
99
+ rspec-support (~> 3.11.0)
100
+ rspec-mocks (3.11.1)
101
+ diff-lcs (>= 1.2.0, < 2.0)
102
+ rspec-support (~> 3.11.0)
103
+ rspec-support (3.11.0)
104
+ rspec_junit_formatter (0.5.1)
105
+ rspec-core (>= 2, < 4, != 2.12.0)
106
+ rss (0.2.9)
107
+ rexml
108
+ rubocop (1.35.1)
109
+ json (~> 2.3)
110
+ parallel (~> 1.10)
111
+ parser (>= 3.1.2.1)
112
+ rainbow (>= 2.2.2, < 4.0)
113
+ regexp_parser (>= 1.8, < 3.0)
114
+ rexml (>= 3.2.5, < 4.0)
115
+ rubocop-ast (>= 1.20.1, < 2.0)
116
+ ruby-progressbar (~> 1.7)
117
+ unicode-display_width (>= 1.4.0, < 3.0)
118
+ rubocop-ast (1.21.0)
119
+ parser (>= 3.1.1.0)
120
+ rubocop-rake (0.6.0)
121
+ rubocop (~> 1.0)
122
+ rubocop-rspec (2.12.1)
123
+ rubocop (~> 1.31)
124
+ ruby-progressbar (1.11.0)
125
+ simplecov (0.21.2)
126
+ docile (~> 1.1)
127
+ simplecov-html (~> 0.11)
128
+ simplecov_json_formatter (~> 0.1)
129
+ simplecov-html (0.12.3)
130
+ simplecov_json_formatter (0.1.4)
131
+ super_diff (0.9.0)
132
+ attr_extras (>= 6.2.4)
133
+ diff-lcs
134
+ patience_diff
135
+ thor (1.2.1)
136
+ tzinfo (2.0.5)
137
+ concurrent-ruby (~> 1.0)
138
+ unicode-display_width (2.2.0)
139
+ zeitwerk (2.6.0)
140
+
141
+ PLATFORMS
142
+ ruby
143
+
144
+ DEPENDENCIES
145
+ bundler (~> 2.0)
146
+ byebug
147
+ cocina-models!
148
+ committee
149
+ rake (~> 13.0)
150
+ rspec (~> 3.0)
151
+ rspec_junit_formatter
152
+ rubocop (~> 1.24)
153
+ rubocop-rake
154
+ rubocop-rspec (~> 2.1)
155
+ simplecov
156
+
157
+ BUNDLED WITH
158
+ 2.3.17
data/README.md CHANGED
@@ -51,10 +51,10 @@ Beyond what is necessary to test the generator, the Cocina model classes are not
51
51
 
52
52
  ## Testing validation changes
53
53
 
54
- If there is a possibility that a model or validation change will conflict with some existing objects then [validate-cocina](https://github.com/sul-dlss/dor-services-app/blob/main/bin/validate-cocina) should be used for testing. This must be run on sdr-deploy since it requires deploying a branch of cocina-models.
54
+ If there is a possibility that a model or validation change will conflict with some existing objects then [validate-cocina](https://github.com/sul-dlss/dor-services-app/blob/main/bin/validate-cocina) should be used for testing. This must be run on sdr-infra since it requires deploying a branch of cocina-models.
55
55
 
56
- 1. Create a cocina-models branch containing the proposed change and push to Github.
57
- 2. On sdr-deploy, check out `main`, update the `Gemfile` so that cocina-models references the branch, and `bundle install`.
56
+ 1. Create a cocina-models branch containing the proposed change and push to GitHub.
57
+ 2. On sdr-infra, check out `main`, update the `Gemfile` so that cocina-models references the branch, and `bundle install`.
58
58
  3. Select the appropriate database.
59
59
  For QA:
60
60
  ```
@@ -124,18 +124,9 @@ At the same, we have found it convenient to use these PRs to also bump the versi
124
124
 
125
125
  ### Step 4: Update other dependent applications
126
126
 
127
- Once the above listed steps have been completed, all the following applications that use cocina-models should be updated and released at the same time. "Cocina Level 2" describes this set of updates.
127
+ Once the above listed steps have been completed, all applications that use cocina-models should be updated and released at the same time. "Cocina Level 2" describes this set of updates. The applications that use cocina-models are those in [this list](https://github.com/sul-dlss/access-update-scripts/blob/master/infrastructure/projects.yml) that are NOT marked with `cocina_level2: false`.
128
128
 
129
- * [sul-dlss/argo](https://github.com/sul-dlss/argo/)
130
- * [sul-dlss/common-accessioning](https://github.com/sul-dlss/common-accessioning/)
131
- * [sul-dlss/dor_indexing_app](https://github.com/sul-dlss/dor_indexing_app/)
132
- * [sul-dlss/google-books](https://github.com/sul-dlss/google-books/)
133
- * [sul-dlss/happy-heron](https://github.com/sul-dlss/happy-heron/)
134
- * [sul-dlss/hydra_etd](https://github.com/sul-dlss/hydra_etd/)
135
- * [sul-dlss/infrastructure-integration-test](https://github.com/sul-dlss/infrastructure-integration-test/)
136
- * [sul-dlss/pre-assembly](https://github.com/sul-dlss/pre-assembly/)
137
-
138
- There are scripts to help with this:
129
+ There are scripts to help with updating other dependent applications:
139
130
 
140
131
  #### Step 4A: Create the PRs
141
132
 
@@ -28,12 +28,15 @@ Gem::Specification.new do |spec|
28
28
  spec.add_dependency 'deprecation'
29
29
  spec.add_dependency 'dry-struct', '~> 1.0'
30
30
  spec.add_dependency 'dry-types', '~> 1.1'
31
+ spec.add_dependency 'edtf' # used for date/time validation
31
32
  spec.add_dependency 'equivalent-xml' # for diffing MODS
33
+ spec.add_dependency 'jsonpath' # used for date/time validation
32
34
  spec.add_dependency 'nokogiri'
33
35
  spec.add_dependency 'openapi3_parser' # Parsing openapi doc
34
36
  # Match these version requirements to what committee wants,
35
37
  # so that our client (non-committee) users have the same dependencies.
36
38
  spec.add_dependency 'openapi_parser', '>= 0.11.1', '< 1.0'
39
+ spec.add_dependency 'rss' # used for date/time validation
37
40
  spec.add_dependency 'super_diff'
38
41
  spec.add_dependency 'thor'
39
42
  spec.add_dependency 'zeitwerk', '~> 2.1'
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ DoiExceptions = Types::String.constrained(
6
+ format: %r{^10\.(25740/([vV][aA]90-[cC][tT]15|[sS][yY][xX][aA]-[mM]256|12[qQ][fF]-5243|65[jJ]8-6114)|25936/629[tT]-[bB][xX]79)$}i
7
+ )
8
+ end
9
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- DOI = Types::String.constrained(
5
+ DoiPattern = Types::String.constrained(
6
6
  format: %r{^10\.(25740|80343)/[b-df-hjkmnp-tv-z]{2}[0-9]{3}[b-df-hjkmnp-tv-z]{2}[0-9]{4}$}i
7
7
  )
8
8
  end
@@ -7,8 +7,7 @@ module Cocina
7
7
  attribute? :barcode, Types::Nominal::Any
8
8
  attribute :catalogLinks, Types::Strict::Array.of(CatalogLink).default([].freeze)
9
9
  # Digital Object Identifier (https://www.doi.org)
10
- # example: 10.25740/bc123df4567
11
- attribute? :doi, Types::Strict::String
10
+ attribute? :doi, Types::Nominal::Any
12
11
  # 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
12
 
14
13
  # example: sul:PC0170_s3_Fiesta_Bowl_2012-01-02_210609_2026
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ module Mapping
6
+ # Escaps HTML entities as CDATA for MODS since HTML is not permitted in MODS
7
+ class EscapeHtml
8
+ # @param [String] data
9
+ # @param [Nokogiri::XML::Builder]
10
+ def self.with_cdata(data, builder)
11
+ tokens = data.split(%r{(</?(?:i|cite)>)})
12
+ tokens.map do |token|
13
+ if /\A<.+>\z/.match? token
14
+ builder.cdata(token)
15
+ else
16
+ builder.text(token)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -339,7 +339,8 @@ module Cocina
339
339
  end
340
340
  notes = name_notes_for(full_name[:role], node)
341
341
  name_attrs[:note] = notes unless notes.empty?
342
- name_attrs.merge(attrs)
342
+ name_attrs[:identifier] = full_name[:identifier]
343
+ name_attrs.compact.merge(attrs)
343
344
  end
344
345
 
345
346
  def name_notes_for(roles, name_node)
@@ -70,12 +70,10 @@ module Cocina
70
70
  end
71
71
 
72
72
  def extract_type(geo)
73
- type = geo[:form].find do |form|
74
- form[:type].match(TYPE_REGEX) || (form[:type].match(MEDIA_REGEX) && form[:value] == 'Image')
73
+ type = geo.form.find do |form|
74
+ form.type.match?(TYPE_REGEX) || (form.type.match?(MEDIA_REGEX) && form.value == 'Image')
75
75
  end
76
- return type[:value] if type
77
-
78
- nil
76
+ type&.value
79
77
  end
80
78
 
81
79
  def about(druid)
@@ -68,7 +68,9 @@ module Cocina
68
68
  else
69
69
  note.value
70
70
  end
71
- xml.public_send tag_name, value, attributes
71
+ xml.public_send tag_name, attributes do |builder|
72
+ EscapeHtml.with_cdata(value, builder) if value
73
+ end
72
74
  end
73
75
 
74
76
  def write_basic(note)
@@ -230,8 +230,11 @@ module Cocina
230
230
  # Write nodes within MODS subject
231
231
  def write_topic(subject, subject_value, is_parallel: false, type: nil, subject_values_have_same_authority: true)
232
232
  type ||= subject_value.type
233
- topic_attributes = topic_attributes_for(subject, subject_value, type, is_parallel: is_parallel,
234
- subject_values_have_same_authority: subject_values_have_same_authority)
233
+ topic_attributes = topic_attributes_for(subject,
234
+ subject_value,
235
+ type,
236
+ is_parallel: is_parallel,
237
+ subject_values_have_same_authority: subject_values_have_same_authority)
235
238
  case type
236
239
  when 'person'
237
240
  xml.name topic_attributes.merge(type: 'personal') do
@@ -395,6 +398,7 @@ module Cocina
395
398
  write_name_part(subject_value)
396
399
  write_display_form(display_values)
397
400
  write_roles(subject_value.note)
401
+ write_identifier(subject.identifier)
398
402
  write_other_notes(subject.note, 'description')
399
403
  write_other_notes(subject.note, 'affiliation')
400
404
  end
@@ -427,6 +431,12 @@ module Cocina
427
431
  end.each { |role| RoleWriter.write(xml: xml, role: role) }
428
432
  end
429
433
 
434
+ def write_identifier(identifiers)
435
+ identifiers.each do |identifier|
436
+ xml.nameIdentifier identifier.value, type: identifier.source.code
437
+ end
438
+ end
439
+
430
440
  def write_other_notes(notes, type)
431
441
  Array(notes).filter { |note| note.type == type }.each { |note| xml.public_send(type, note.value) }
432
442
  end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'edtf'
4
+ require 'jsonpath'
5
+ require 'rss'
6
+
7
+ module Cocina
8
+ module Models
9
+ module Validators
10
+ # Validates that dates of known types are type-valid
11
+ class DateTimeValidator
12
+ VALIDATABLE_TYPES = %w[edtf iso8601 w3cdtf].freeze
13
+
14
+ def self.validate(_clazz, attributes)
15
+ new(attributes).validate
16
+ end
17
+
18
+ def initialize(attributes)
19
+ @attributes = attributes
20
+ end
21
+
22
+ def validate
23
+ return unless meets_preconditions?
24
+
25
+ return if invalid_dates.empty?
26
+
27
+ raise ValidationError, "Invalid date(s) for #{druid}: #{invalid_dates}"
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :attributes
33
+
34
+ def meets_preconditions?
35
+ attributes.key?(:description)
36
+ end
37
+
38
+ def invalid_dates
39
+ @invalid_dates ||= validatable_dates.filter_map do |date_hash|
40
+ code = date_hash.dig('encoding', 'code')
41
+ bad_values = JsonPath.new('$..value').on(date_hash.to_json).reject do |value|
42
+ send("valid_#{code}?", value)
43
+ end
44
+
45
+ next if bad_values.empty?
46
+
47
+ [*bad_values, code]
48
+ end
49
+ end
50
+
51
+ def validatable_dates
52
+ # Why is the `uniq` needed below? Odd behavior when handling highly nested use cases:
53
+ #
54
+ # > JsonPath.new("$..date..[?(@.encoding.code =~ /#{VALIDATABLE_TYPES.join('|')}/)]").on(attributes[:description].to_json)
55
+ # > [
56
+ # {"structuredValue"=>[{"value"=>"1996", "type"=>"start"}, {"value"=>"1998", "type"=>"end"}], "encoding"=>{"code"=>"iso8601"}},
57
+ # {"structuredValue"=>[{"value"=>"1996", "type"=>"start"}, {"value"=>"1998", "type"=>"end"}], "encoding"=>{"code"=>"iso8601"}}
58
+ # ]
59
+ #
60
+ # Notice how the JSONPath expression returns the *same exact* structure twice despite only being present once in the data.
61
+ JsonPath
62
+ .new("$..date..[?(@.encoding.code =~ /#{VALIDATABLE_TYPES.join('|')}/)]")
63
+ .on(attributes[:description].to_json)
64
+ .uniq
65
+ end
66
+
67
+ def valid_edtf?(value)
68
+ Date.edtf!(value)
69
+ true
70
+ rescue StandardError
71
+ # NOTE: the upstream EDTF implementation in the `edtf` gem does not
72
+ # allow a valid pattern that we use (possibly because only level
73
+ # 0 of the spec was implemented?):
74
+ #
75
+ # * Y-20555
76
+ #
77
+ # So we catch the false positives from the upstream gem and allow
78
+ # this pattern to validate
79
+ /\AY-?\d{5,}\Z/.match?(value)
80
+ end
81
+
82
+ def valid_iso8601?(value)
83
+ DateTime.iso8601(value)
84
+ true
85
+ rescue StandardError
86
+ false
87
+ end
88
+
89
+ def valid_w3cdtf?(value)
90
+ Time.w3cdtf(value)
91
+ true
92
+ rescue StandardError
93
+ # NOTE: the upstream W3CDTF implementation in the `rss` gem does not
94
+ # allow two patterns that should be valid per the specification:
95
+ #
96
+ # * YYYY
97
+ # * YYYY-MM
98
+ #
99
+ # So we catch the false positives from the upstream gem and allow
100
+ # these two patterns to validate
101
+ /\A\d{4}(-0[1-9]|-1[0-2])?\Z/.match?(value)
102
+ end
103
+
104
+ def druid
105
+ @druid ||= attributes[:externalIdentifier]
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -12,7 +12,8 @@ module Cocina
12
12
  CatalogLinksValidator,
13
13
  AssociatedNameValidator,
14
14
  DescriptionTypesValidator,
15
- DescriptionValuesValidator
15
+ DescriptionValuesValidator,
16
+ DateTimeValidator
16
17
  ].freeze
17
18
 
18
19
  def self.validate(clazz, attributes)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- VERSION = '0.81.0'
5
+ VERSION = '0.84.0'
6
6
  end
7
7
  end
data/lib/cocina/models.rb CHANGED
@@ -40,7 +40,7 @@ loader = Zeitwerk::Loader.new
40
40
  loader.inflector = CocinaModelsInflector.new
41
41
  loader.push_dir(File.absolute_path("#{__FILE__}/../.."))
42
42
  loader.ignore("#{__dir__}/rspec.rb")
43
- loader.ignore("#{__dir__}/rspec/**/*.rb")
43
+ loader.ignore("#{__dir__}/rspec")
44
44
  loader.setup
45
45
 
46
46
  module Cocina
@@ -153,5 +153,12 @@ module Cocina
153
153
  raise ValidationError, 'Type field not found'
154
154
  end
155
155
  private_class_method :type_for
156
+
157
+ def self.druid_regex
158
+ @druid_regex ||= begin
159
+ str = Openapi3Parser.load_file('openapi.yml').components.schemas['Druid'].pattern
160
+ Regexp.new(str)
161
+ end
162
+ end
156
163
  end
157
164
  end
data/openapi.yml CHANGED
@@ -390,7 +390,7 @@ components:
390
390
  - catalogRecordId
391
391
  - refresh
392
392
  CatkeyBarcode:
393
- description: The barcode associated with a DRO object based on catkey, prefixed with 36105
393
+ description: The barcode associated with a DRO object based on catkey, prefixed with a catkey followed by a hyphen
394
394
  type: string
395
395
  pattern: '^[0-9]+-[0-9]+$'
396
396
  example: '6772719-1001'
@@ -896,8 +896,18 @@ components:
896
896
  # description: An alphabet or other notation used to represent a
897
897
  # language or other symbolic system of the descriptive element value.
898
898
  DOI:
899
- type: string
900
899
  description: Digital Object Identifier (https://www.doi.org)
900
+ oneOf:
901
+ - $ref: '#/components/schemas/DoiPattern'
902
+ - $ref: '#/components/schemas/DoiExceptions'
903
+ DoiExceptions:
904
+ type: string
905
+ description: pre-existing Digital Object Identifiers (https://www.doi.org) not matching the pattern (case insensitive)
906
+ pattern: '^10\.(25740\/([vV][aA]90-[cC][tT]15|[sS][yY][xX][aA]-[mM]256|12[qQ][fF]-5243|65[jJ]8-6114)|25936\/629[tT]-[bB][xX]79)$'
907
+ example: '10.25740/12qF-5243'
908
+ DoiPattern:
909
+ type: string
910
+ description: Digital Object Identifier (https://www.doi.org) regex pattern
901
911
  # The prod and test prefixes are permitted
902
912
  pattern: '^10\.(25740|80343)\/[b-df-hjkmnp-tv-z]{2}[0-9]{3}[b-df-hjkmnp-tv-z]{2}[0-9]{4}$'
903
913
  example: '10.25740/bc123df4567'
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.81.0
4
+ version: 0.84.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-06-01 00:00:00.000000000 Z
11
+ date: 2022-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: edtf
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: equivalent-xml
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,20 @@ dependencies:
80
94
  - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: jsonpath
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: nokogiri
85
113
  requirement: !ruby/object:Gem::Requirement
@@ -128,6 +156,20 @@ dependencies:
128
156
  - - "<"
129
157
  - !ruby/object:Gem::Version
130
158
  version: '1.0'
159
+ - !ruby/object:Gem::Dependency
160
+ name: rss
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :runtime
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
131
173
  - !ruby/object:Gem::Dependency
132
174
  name: super_diff
133
175
  requirement: !ruby/object:Gem::Requirement
@@ -297,6 +339,7 @@ files:
297
339
  - ".rubocop.yml"
298
340
  - ".rubocop_todo.yml"
299
341
  - Gemfile
342
+ - Gemfile.lock
300
343
  - README.md
301
344
  - Rakefile
302
345
  - bin/console
@@ -355,7 +398,8 @@ files:
355
398
  - lib/cocina/models/descriptive_structured_value.rb
356
399
  - lib/cocina/models/descriptive_value.rb
357
400
  - lib/cocina/models/descriptive_value_language.rb
358
- - lib/cocina/models/doi.rb
401
+ - lib/cocina/models/doi_exceptions.rb
402
+ - lib/cocina/models/doi_pattern.rb
359
403
  - lib/cocina/models/dro.rb
360
404
  - lib/cocina/models/dro_access.rb
361
405
  - lib/cocina/models/dro_structural.rb
@@ -377,6 +421,7 @@ files:
377
421
  - lib/cocina/models/location_based_access.rb
378
422
  - lib/cocina/models/location_based_download_access.rb
379
423
  - lib/cocina/models/mapping/error_notifier.rb
424
+ - lib/cocina/models/mapping/escape_html.rb
380
425
  - lib/cocina/models/mapping/from_mods/access.rb
381
426
  - lib/cocina/models/mapping/from_mods/admin_metadata.rb
382
427
  - lib/cocina/models/mapping/from_mods/alt_rep_group.rb
@@ -461,6 +506,7 @@ files:
461
506
  - lib/cocina/models/validators/associated_name_validator.rb
462
507
  - lib/cocina/models/validators/catalog_links_validator.rb
463
508
  - lib/cocina/models/validators/dark_validator.rb
509
+ - lib/cocina/models/validators/date_time_validator.rb
464
510
  - lib/cocina/models/validators/description_types_validator.rb
465
511
  - lib/cocina/models/validators/description_values_validator.rb
466
512
  - lib/cocina/models/validators/open_api_validator.rb
@@ -492,7 +538,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
492
538
  - !ruby/object:Gem::Version
493
539
  version: '0'
494
540
  requirements: []
495
- rubygems_version: 3.3.9
541
+ rubygems_version: 3.2.32
496
542
  signing_key:
497
543
  specification_version: 4
498
544
  summary: Data models for the SDR