cocina-models 0.81.0 → 0.82.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: 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