cocina-models 0.81.0 → 0.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d8392fadece0bb4ec12ed93e958c99abf04e29ad0a78c14ee8651ec8ecd1e37
4
- data.tar.gz: db4a5de344bb53d98b2b71f725e8b1d1b9e0d5e0b28416989ad55f19df826376
3
+ metadata.gz: ebc24ff8dbe8eb1fe740eb977abb73abc504409aa510905395e6660adcd05b3b
4
+ data.tar.gz: 62ad4e7cafce859250323e92cc2173257957b16775e767224a4de2e9fd639bae
5
5
  SHA512:
6
- metadata.gz: 4880e4eb86935116e0bec017926aeab5e5aa12d8792ee5066d0aeef5f514e6f13f63c8bc68c03035fdff2748f95aa7215f236384e109a8e532bd76e9677ae53c
7
- data.tar.gz: 22154804ee4b103bdbd3aaaee449327bb4a6c9bc1dce9431a5311ed06c5e76039ccb420634d275f56d644a60ffa529bd9cb822f9c549be057f4972d791a34bf7
6
+ metadata.gz: 7a3b0ed8ab383f945450d42343dbaed6e709854ff0f3deca1f62b93c79883d3157b42f14cdaafd9392ef858620a0c58ae6867c16037c627499efddf3cb33880b
7
+ data.tar.gz: 9e1084ea73c6f547eab7b8a076d9e2a61a46361c9da1541effca8529d48d032621b6cd5868bd2886308aea1f834b4c586ae1058ca6f67f0315393e4eb632bbb9
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
@@ -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,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
@@ -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)
@@ -0,0 +1,102 @@
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
+ false
72
+ end
73
+
74
+ def valid_iso8601?(value)
75
+ DateTime.iso8601(value)
76
+ true
77
+ rescue StandardError
78
+ false
79
+ end
80
+
81
+ def valid_w3cdtf?(value)
82
+ Time.w3cdtf(value)
83
+ true
84
+ rescue StandardError
85
+ # NOTE: the upstream W3CDTF implementation in the `rss` gem does not
86
+ # allow two patterns that should be valid per the specification:
87
+ #
88
+ # * YYYY
89
+ # * YYYY-MM
90
+ #
91
+ # So we catch the false positives from the upstream gem and allow
92
+ # these two patterns to validate
93
+ /\A\d{4}(-0[1-9]|1[0-2])?\Z/.match?(value)
94
+ end
95
+
96
+ def druid
97
+ @druid ||= attributes[:externalIdentifier]
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- VERSION = '0.81.0'
5
+ VERSION = '0.82.0'
6
6
  end
7
7
  end
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.82.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-06-17 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
@@ -377,6 +419,7 @@ files:
377
419
  - lib/cocina/models/location_based_access.rb
378
420
  - lib/cocina/models/location_based_download_access.rb
379
421
  - lib/cocina/models/mapping/error_notifier.rb
422
+ - lib/cocina/models/mapping/escape_html.rb
380
423
  - lib/cocina/models/mapping/from_mods/access.rb
381
424
  - lib/cocina/models/mapping/from_mods/admin_metadata.rb
382
425
  - lib/cocina/models/mapping/from_mods/alt_rep_group.rb
@@ -461,6 +504,7 @@ files:
461
504
  - lib/cocina/models/validators/associated_name_validator.rb
462
505
  - lib/cocina/models/validators/catalog_links_validator.rb
463
506
  - lib/cocina/models/validators/dark_validator.rb
507
+ - lib/cocina/models/validators/date_time_validator.rb
464
508
  - lib/cocina/models/validators/description_types_validator.rb
465
509
  - lib/cocina/models/validators/description_values_validator.rb
466
510
  - lib/cocina/models/validators/open_api_validator.rb
@@ -492,7 +536,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
492
536
  - !ruby/object:Gem::Version
493
537
  version: '0'
494
538
  requirements: []
495
- rubygems_version: 3.3.9
539
+ rubygems_version: 3.3.7
496
540
  signing_key:
497
541
  specification_version: 4
498
542
  summary: Data models for the SDR