cocina-models 0.122.0 → 0.123.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: a7ba361f4c17d3165dce9e5042739493896f798fe9a215ce20054b6f2c739c11
4
- data.tar.gz: 5ca9b3f5efdd6ab255dfe1f48b19be012638986f7126af70547d701bd5059b01
3
+ metadata.gz: 00e220a27c041178441f3a9372469f6f4aeacd6b9cf73bec45b36d0dfc8e8f1a
4
+ data.tar.gz: e6b6e939d33c2bbc9e88c99bdfef996b23d40df59667cf9f7c1b63b3b2a2b6ba
5
5
  SHA512:
6
- metadata.gz: cfd5524aa8e1ce0dc3510855b1c1cb9cadd875878192c71ecfb9fdd02fe74457e00a9a3766eefd3f399b2b3a539a013eb63e929b7b1a46b58fedb789ef170d6c
7
- data.tar.gz: 27d345e4bd13c181165a943a696ecbfe44c36fce1fcd1db4324f13c8d97fce029b953a36030fc9a71586d95a6458622300937990069b57fcae0baeed20398be3
6
+ metadata.gz: 8fa102a60d4d216b439f514051d34afd07e57105afefd751d1c1b8f07411b876c5475237a0a577fb1240ac6eb564918ebf9a372d3596107f2787d041de8c9f77
7
+ data.tar.gz: 7eb1f0771c848747c833437e3aeb4fb616de5716bae81a8f1ba83a048ba8364953eb47332bae622b91b830807eb39395d0a186a8347605bed662ebc4ecec4807
data/.circleci/config.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  version: 2.1
2
2
  orbs:
3
- ruby-rails: sul-dlss/ruby-rails@4.10.0
3
+ ruby-rails: sul-dlss/ruby-rails@4.11.0
4
4
  workflows:
5
5
  build:
6
6
  jobs:
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cocina-models (0.122.0)
4
+ cocina-models (0.123.0)
5
5
  activesupport
6
+ cocina_display
6
7
  deprecation
7
8
  dry-struct (~> 1.0)
8
9
  dry-types (~> 1.1)
@@ -36,6 +37,14 @@ GEM
36
37
  base64 (0.3.0)
37
38
  bigdecimal (4.1.2)
38
39
  builder (3.3.0)
40
+ cocina_display (2.6.0)
41
+ activesupport (>= 7)
42
+ edtf (~> 3.2)
43
+ geo_coord (~> 0.2)
44
+ i18n
45
+ iso8601 (~> 0.13.0)
46
+ janeway-jsonpath (>= 0.6, < 2)
47
+ zeitwerk (~> 2.7)
39
48
  concurrent-ruby (1.3.6)
40
49
  connection_pool (3.0.2)
41
50
  csv (3.3.5)
@@ -81,6 +90,7 @@ GEM
81
90
  logger
82
91
  faraday-net_http (3.4.4)
83
92
  net-http (~> 0.5)
93
+ geo_coord (0.2.0)
84
94
  i18n (1.14.8)
85
95
  concurrent-ruby (~> 1.0)
86
96
  ice_nine (0.11.2)
@@ -90,7 +100,9 @@ GEM
90
100
  prism (>= 1.3.0)
91
101
  rdoc (>= 4.0.0)
92
102
  reline (>= 0.4.2)
93
- json (2.19.8)
103
+ iso8601 (0.13.0)
104
+ janeway-jsonpath (1.0.0)
105
+ json (2.19.9)
94
106
  jsonschema_rs (0.46.5-arm64-darwin)
95
107
  bigdecimal (>= 3.1, < 5)
96
108
  jsonschema_rs (0.46.5-x86_64-linux)
@@ -219,8 +231,9 @@ CHECKSUMS
219
231
  base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
220
232
  bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
221
233
  builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
222
- bundler (4.0.13) sha256=19f08be7f27022cf0b89f27da0b044ae075e8270a9ef44ad248a932614e1ca3b
223
- cocina-models (0.122.0)
234
+ bundler (4.0.14) sha256=d09a0a965cf772266a7e49e83610be7c2f4e49e61134c42a56804bb383cc24b8
235
+ cocina-models (0.123.0)
236
+ cocina_display (2.6.0) sha256=a30e4bd638023371985ab641164e4dbbb12fe9a669a168c1b8e2eac2e37739f4
224
237
  concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
225
238
  connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
226
239
  csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
@@ -240,11 +253,14 @@ CHECKSUMS
240
253
  erb (6.0.4) sha256=38e3803694be357fe2bfe312487c74beaf9fb4e5beb3e22498952fe1645b95d9
241
254
  faraday (2.14.2) sha256=73ccb9994a9e8648f010e32eca2ae82e41c57860aa10932cda29418b9e0223ad
242
255
  faraday-net_http (3.4.4) sha256=0e78af151747ed1b00f33e25973b4bc220d7f16c00c39676817c8b12331eb588
256
+ geo_coord (0.2.0) sha256=ae4e2dc5799deafa4db9138f3d7e85cf5e7dbd00b3b8395f1e1dbd34e007d7e8
243
257
  i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
244
258
  ice_nine (0.11.2) sha256=5d506a7d2723d5592dc121b9928e4931742730131f22a1a37649df1c1e2e63db
245
259
  io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
246
260
  irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
247
- json (2.19.8) sha256=6354310fd76ef69b87d5bd1f38b40d730613baf90b6803d2d0a48f618d32dfaa
261
+ iso8601 (0.13.0) sha256=298c2b15b7be5fa95a1372813d36a2257656cd8e906dfbc1f5cb409851425aa2
262
+ janeway-jsonpath (1.0.0) sha256=c8293f009f2aea9487ddee3067ca735a1f758eb1c8834ff4fab3ffd06c56d0a3
263
+ json (2.19.9) sha256=9b9025b7cdddafa38d316eca0b2358488e42d417045c1b90d216a9fefe46b79a
248
264
  jsonschema_rs (0.46.5-arm64-darwin) sha256=e80414ed67f0956d3e06474a2fa076fc4a7b722f00e5d7142b70289c016ac6f1
249
265
  jsonschema_rs (0.46.5-x86_64-linux) sha256=345c65ec7a5abf8879b9c9356752f0fdf4c9926f6480458fc32803a871b5cbb3
250
266
  language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
@@ -295,4 +311,4 @@ CHECKSUMS
295
311
  zeitwerk (2.8.2) sha256=7212a61311083c604184b1ea2574b9aa05cd14f855a0841c06985cabe9181d12
296
312
 
297
313
  BUNDLED WITH
298
- 4.0.13
314
+ 4.0.14
data/README.md CHANGED
@@ -62,7 +62,7 @@ Beyond what is necessary to test the generator, the Cocina model classes are not
62
62
 
63
63
  ## Testing validation changes
64
64
 
65
- If there is a possibility that a model, mapping, or validation change will conflict with some existing objects then `bin/validate-data` should be used for testing. This operates on a sample of objects from the repository and reports any validation errors. You may get the sample by running the script [bin/export-cocina-head-versions](https://github.com/sul-dlss/dor-services-app?tab=readme-ov-file#export-data) and downloading the data file to your computer. Running a full validation takes about 2 hours.
65
+ If there is a possibility that a model, mapping, or validation change will conflict with some existing objects then `bin/validate-data` should be used for testing. This operates on an export of objects from the repository and reports any validation errors. You may get the file by running the script [bin/export-cocina-head-versions](https://github.com/sul-dlss/dor-services-app#export-cocina-json-data) and downloading the data file to your computer. See the [DSA README](https://github.com/sul-dlss/dor-services-app#scheduled-cocina-json-data-exports) for more info about the files and locations. Running a full validation takes about 2 hours.
66
66
 
67
67
 
68
68
  Alternatively, you can use [validate-cocina](https://github.com/sul-dlss/dor-services-app/blob/main/bin/validate-cocina) for testing. This must be run on the `sdr-infra` VM since it requires deploying a branch of cocina-models. It is slower than using `bin/validate-data`, but all of the data is completely up to date.
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.required_ruby_version = '>= 3.4'
26
26
 
27
27
  spec.add_dependency 'activesupport'
28
+ spec.add_dependency 'cocina_display'
28
29
  spec.add_dependency 'deprecation'
29
30
  spec.add_dependency 'dry-struct', '~> 1.0'
30
31
  spec.add_dependency 'dry-types', '~> 1.1'
@@ -46,8 +46,6 @@ contributor:
46
46
  description: An institution or other corporate or collective body.
47
47
  - value: person
48
48
  description: An individual identity.
49
- - value: unspecified others
50
- description: Designator for one or more additional contributors not named individually.
51
49
  contributor.identifier:
52
50
  - value: ORCID
53
51
  description: Identifier from orcid.org.
@@ -49,8 +49,6 @@ _Path: contributor.type_
49
49
  * An institution or other corporate or collective body.
50
50
  * person
51
51
  * An individual identity.
52
- * unspecified others
53
- * Designator for one or more additional contributors not named individually.
54
52
  ## Contributor identifier types
55
53
  _Path: contributor.identifier.type_
56
54
  * ORCID
@@ -0,0 +1,210 @@
1
+ # LC Standard Identifier Source Codes
2
+ # Source: https://www.loc.gov/standards/sourcelist/standard-identifier.html
3
+ # Additional codes: swets, apis
4
+ - agorha
5
+ - agrovoc
6
+ - allmovie
7
+ - allmusic
8
+ - allocine
9
+ - amnbo
10
+ - ansi
11
+ - apis
12
+ - archinl
13
+ - archinpe
14
+ - archinpr
15
+ - archna
16
+ - archns
17
+ - ark
18
+ - artsy
19
+ - artukart
20
+ - artukaw
21
+ - atg
22
+ - balat
23
+ - bbcth
24
+ - bdrc
25
+ - bdusc
26
+ - belvku
27
+ - belvwrk
28
+ - benezit
29
+ - bew
30
+ - bfi
31
+ - bibbi
32
+ - bigenc
33
+ - bnfcg
34
+ - bpn
35
+ - bsi
36
+ - cabt
37
+ - cana
38
+ - cantic
39
+ - cbwpid
40
+ - cerl
41
+ - cgndb
42
+ - clara
43
+ - cnbksy
44
+ - csfdcz
45
+ - danacode
46
+ - darome
47
+ - datoses
48
+ - discogs
49
+ - dkfilm
50
+ - dma
51
+ - doi
52
+ - dpb
53
+ - ean
54
+ - ecli
55
+ - eidr
56
+ - emlo
57
+ - fast
58
+ - fidecp
59
+ - filmaff
60
+ - filmport
61
+ - findagr
62
+ - fisa
63
+ - freebase
64
+ - fuoc
65
+ - gacsch
66
+ - gec
67
+ - geogndb
68
+ - geonames
69
+ - geprishisp
70
+ - gettyaat
71
+ - gettyart
72
+ - gettyobj
73
+ - gettytgn
74
+ - gettyulan
75
+ - gnd
76
+ - gnis
77
+ - goodra
78
+ - gtaa
79
+ - gtin-14
80
+ - hdl
81
+ - iaafa
82
+ - ibdb
83
+ - iconauth
84
+ - idref
85
+ - imdb
86
+ - isan
87
+ - isbn
88
+ - isbn-a
89
+ - isbnre
90
+ - isbnsbn
91
+ - isfdbau
92
+ - isfdbaw
93
+ - isfdbma
94
+ - isfdbpu
95
+ - isil
96
+ - ismn
97
+ - isni
98
+ - iso
99
+ - isrc
100
+ - issn
101
+ - issn-l
102
+ - issue-number
103
+ - istc
104
+ - iswc
105
+ - it-acnp
106
+ - itar
107
+ - kda
108
+ - kdw
109
+ - kinopo
110
+ - knpam
111
+ - kulturnav
112
+ - lattes
113
+ - lccn
114
+ - lcmd
115
+ - lcmpt
116
+ - lei
117
+ - libaus
118
+ - lmhl
119
+ - local
120
+ - margaz
121
+ - matrix-number
122
+ - mesh
123
+ - mocofo
124
+ - moma
125
+ - morana
126
+ - moviemetf
127
+ - moviemetr
128
+ - munzing
129
+ - muscl
130
+ - music-plate
131
+ - music-publisher
132
+ - musicb
133
+ - nacat
134
+ - nagb
135
+ - natgazfid
136
+ - nga
137
+ - ngva
138
+ - ngvw
139
+ - nipo
140
+ - nlg
141
+ - nndb
142
+ - npg
143
+ - nzggn
144
+ - odnb
145
+ - ofdb
146
+ - onix
147
+ - opensm
148
+ - orcid
149
+ - orgnr
150
+ - oxforddnb
151
+ - pcadbu
152
+ - pcadpe
153
+ - pcadpf
154
+ - permid
155
+ - picnypl
156
+ - pleiades
157
+ - pnta
158
+ - porthu
159
+ - prabook
160
+ - rbmsbt
161
+ - rbmsgt
162
+ - rbmspe
163
+ - rbmsppe
164
+ - rbmspt
165
+ - rbmsrd
166
+ - rbmste
167
+ - rid
168
+ - rkda
169
+ - ror
170
+ - s2a3bd
171
+ - saam
172
+ - scholaru
173
+ - scope
174
+ - scopus
175
+ - sici
176
+ - smgp
177
+ - snac
178
+ - spotify
179
+ - sprfbsb
180
+ - sprfbsk
181
+ - sprfcbb
182
+ - sprfcfb
183
+ - sprfhoc
184
+ - sprfoly
185
+ - sprfpfb
186
+ - ssaut
187
+ - stock-number
188
+ - strn
189
+ - stw
190
+ - svfilm
191
+ - swets
192
+ - tatearid
193
+ - theatr
194
+ - tpce
195
+ - trove
196
+ - unescot
197
+ - upc
198
+ - uri
199
+ - urn
200
+ - vd16
201
+ - vd17
202
+ - vd18
203
+ - vgmdb
204
+ - viaf
205
+ - videorecording-identifier
206
+ - wikidata
207
+ - wndla
208
+ - xgamea
209
+ - ysopai
210
+ - zbaut
@@ -7,8 +7,14 @@ module Cocina
7
7
  class CompositeDescriptionValidator
8
8
  VALIDATORS = [
9
9
  DescriptionTypesVisitorValidator,
10
+ DescriptionIdentifierSourceCodeVisitorValidator,
11
+ DescriptionRoleSourceCodeVisitorValidator,
12
+ DescriptionFormResourceTypeVisitorValidator,
10
13
  DescriptionValuesVisitorValidator,
11
- DescriptionDateTimeVisitorValidator
14
+ DescriptionDateTimeVisitorValidator,
15
+ DescriptionEventDateVisitorValidator,
16
+ DescriptionSubjectTemporalEncodingVisitorValidator,
17
+ DescriptionLocationSourceCodeVisitorValidator
12
18
  ].freeze
13
19
 
14
20
  def self.validate(clazz, attributes)
@@ -8,8 +8,9 @@ module Cocina
8
8
  # Validates that dates of known types are type-valid using the visitor pattern.
9
9
  class DescriptionDateTimeVisitorValidator < BaseDescriptionVisitorValidator
10
10
  VALIDATABLE_TYPES = %w[edtf iso8601 w3cdtf].freeze
11
+ VALID_ENCODING_CODES = %w[edtf iso8601 marc temper w3cdtf].freeze
11
12
 
12
- def visit_hash(hash:, path:) # rubocop:disable Metrics/CyclomaticComplexity
13
+ def visit_hash(hash:, path:) # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
13
14
  # Only dates nested under a `date` key are subject to validation.
14
15
  # For example, event.date is in scope but event.note is not.
15
16
  return unless in_date_path?(path)
@@ -32,7 +33,10 @@ module Cocina
32
33
  # recursing into its children, so the encoding is always registered
33
34
  # before any child value hashes are visited.
34
35
  code = hash.dig(:encoding, :code)
35
- encoding_paths[path.dup] = code if code && VALIDATABLE_TYPES.include?(code)
36
+ if code
37
+ encoding_paths[path.dup] = code if VALIDATABLE_TYPES.include?(code)
38
+ invalid_encoding_codes << "#{path_to_s(path)}.encoding.code (#{code})" unless VALID_ENCODING_CODES.include?(code)
39
+ end
36
40
 
37
41
  value = hash[:value]
38
42
  return unless value.is_a?(String)
@@ -62,17 +66,18 @@ module Cocina
62
66
  end
63
67
 
64
68
  def validate!
65
- return if invalid_groups.empty?
69
+ errors = []
66
70
 
67
- invalid_dates = invalid_groups.filter_map do |path, values|
68
- next if values.empty?
71
+ errors << "Unrecognized date encoding codes in description: #{invalid_encoding_codes.join(', ')}" if invalid_encoding_codes.any?
69
72
 
70
- [*values, encoding_paths[path]]
73
+ unless invalid_groups.empty?
74
+ invalid_dates = invalid_groups.filter_map do |path, values|
75
+ [*values, encoding_paths[path]] unless values.empty?
76
+ end
77
+ errors << "Invalid date(s) in description: #{invalid_dates}" if invalid_dates.any?
71
78
  end
72
79
 
73
- return if invalid_dates.empty?
74
-
75
- raise ValidationError, "Invalid date(s) in description: #{invalid_dates}"
80
+ raise ValidationError, errors.join('; ') if errors.any?
76
81
  end
77
82
 
78
83
  private
@@ -81,6 +86,10 @@ module Cocina
81
86
  @encoding_paths ||= {}
82
87
  end
83
88
 
89
+ def invalid_encoding_codes
90
+ @invalid_encoding_codes ||= []
91
+ end
92
+
84
93
  def invalid_groups
85
94
  @invalid_groups ||= {}
86
95
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ module Validators
6
+ # Validates that event date structuredValue entries with a type also have a value.
7
+ class DescriptionEventDateVisitorValidator < BaseDescriptionVisitorValidator
8
+ def visit_hash(hash:, path:)
9
+ return unless hash[:type] && !hash[:value]
10
+ return unless event_date_structured_value?(path)
11
+
12
+ error_paths << path_to_s(path)
13
+ end
14
+
15
+ def validate!
16
+ return if error_paths.empty?
17
+
18
+ raise ValidationError,
19
+ "Missing value for type in description: #{error_paths.join(', ')}"
20
+ end
21
+
22
+ private
23
+
24
+ def error_paths
25
+ @error_paths ||= []
26
+ end
27
+
28
+ def event_date_structured_value?(path)
29
+ return false unless path[0] == 'event' && path[2] == 'date'
30
+
31
+ # Direct: event.date.structuredValue, e.g. ["event", 0, "date", 0, "structuredValue", 0]
32
+ # Via parallelValue: event.date.parallelValue.structuredValue, e.g. ["event", 0, "date", 0, "parallelValue", 0, "structuredValue", 0]
33
+ path[4] == 'structuredValue' || (path[4] == 'parallelValue' && path[6] == 'structuredValue')
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ module Validators
6
+ # Validates form.value against allowed values when form.source.value is a known controlled vocabulary.
7
+ class DescriptionFormResourceTypeVisitorValidator < BaseDescriptionVisitorValidator
8
+ def validate!
9
+ return if error_paths.empty?
10
+
11
+ raise ValidationError, "Unrecognized resource type values in description: #{error_paths.join(', ')}"
12
+ end
13
+
14
+ def visit_hash(hash:, path:)
15
+ return unless form_entry_path?(path)
16
+ return unless hash[:type].to_s == 'resource type'
17
+
18
+ source_value = hash.dig(:source, :value)
19
+ return unless source_value && valid_values_by_source.key?(source_value)
20
+
21
+ value = hash[:value]
22
+ return unless value
23
+ return if valid_values_by_source[source_value].include?(value)
24
+
25
+ error_paths << "#{path_to_s(path)} (#{value})"
26
+ end
27
+
28
+ private
29
+
30
+ def error_paths
31
+ @error_paths ||= []
32
+ end
33
+
34
+ def form_entry_path?(path)
35
+ path.length >= 2 && path[-1].is_a?(Integer) && path[-2].to_s == 'form'
36
+ end
37
+
38
+ # rubocop:disable Style/ClassVars
39
+ def valid_values_by_source
40
+ @@valid_values_by_source ||= YAML.load_file(::File.expand_path('../../../../resource_type_values.yml', __dir__))
41
+ end
42
+ # rubocop:enable Style/ClassVars
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ module Validators
6
+ # Validates identifier.source.code values against identifier_source_codes.yml.
7
+ class DescriptionIdentifierSourceCodeVisitorValidator < BaseDescriptionVisitorValidator
8
+ def validate!
9
+ return if error_paths.empty?
10
+
11
+ raise ValidationError, "Unrecognized identifier source codes in description: #{error_paths.join(', ')}"
12
+ end
13
+
14
+ def visit_hash(hash:, path:)
15
+ return unless identifier_path?(path)
16
+
17
+ source_code = hash.dig(:source, :code)
18
+ return unless source_code
19
+
20
+ error_paths << "#{path_to_s(path)}.source.code (#{source_code})" unless valid_codes.include?(source_code.downcase)
21
+ end
22
+
23
+ private
24
+
25
+ def error_paths
26
+ @error_paths ||= []
27
+ end
28
+
29
+ def identifier_path?(path)
30
+ path.length >= 2 && path[-1].is_a?(Integer) && path[-2].to_s == 'identifier'
31
+ end
32
+
33
+ # rubocop:disable Style/ClassVars
34
+ def valid_codes
35
+ @@valid_codes ||= YAML.load_file(::File.expand_path('../../../../identifier_source_codes.yml', __dir__)).to_set(&:downcase)
36
+ end
37
+ # rubocop:enable Style/ClassVars
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ module Validators
6
+ # Validates location.source.code values against location_source_codes.yml.
7
+ class DescriptionLocationSourceCodeVisitorValidator < BaseDescriptionVisitorValidator
8
+ def validate!
9
+ return if error_paths.empty?
10
+
11
+ raise ValidationError, "Unrecognized location source codes in description: #{error_paths.join(', ')}"
12
+ end
13
+
14
+ def visit_hash(hash:, path:)
15
+ return unless location_path?(path)
16
+
17
+ source_code = hash.dig(:source, :code)
18
+ return unless source_code
19
+ return if valid_codes.include?(source_code.downcase)
20
+
21
+ error_paths << "#{path_to_s(path)}.source.code (#{source_code})"
22
+ end
23
+
24
+ private
25
+
26
+ def error_paths
27
+ @error_paths ||= []
28
+ end
29
+
30
+ def location_path?(path)
31
+ # Match entries in a location array (e.g., [:location, 0] or [:relatedResource, 0, :location, 0]).
32
+ path.length >= 2 && path[-1].is_a?(Integer) && path[-2].to_s == 'location'
33
+ end
34
+
35
+ # rubocop:disable Style/ClassVars
36
+ def valid_codes
37
+ @@valid_codes ||= YAML.load_file(::File.expand_path('../../../../location_source_codes.yml', __dir__)).to_set(&:downcase)
38
+ end
39
+ # rubocop:enable Style/ClassVars
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ module Validators
6
+ # Validates contributor.role.source.code values against role_source_codes.yml.
7
+ class DescriptionRoleSourceCodeVisitorValidator < BaseDescriptionVisitorValidator
8
+ def validate!
9
+ return if error_paths.empty?
10
+
11
+ raise ValidationError, "Unrecognized role source codes in description: #{error_paths.join(', ')}"
12
+ end
13
+
14
+ def visit_hash(hash:, path:)
15
+ return unless role_path?(path)
16
+
17
+ source_code = hash.dig(:source, :code)
18
+ return unless source_code
19
+ return if valid_codes.include?(source_code.downcase)
20
+
21
+ error_paths << "#{path_to_s(path)}.source.code (#{source_code})"
22
+ end
23
+
24
+ private
25
+
26
+ def error_paths
27
+ @error_paths ||= []
28
+ end
29
+
30
+ def role_path?(path)
31
+ path.length >= 2 && # ensure we have at least two elements (at minimum role and its index)
32
+ path[-1].is_a?(Integer) &&
33
+ path[-2].to_s == 'role' && # we have a nested role in the path
34
+ path.any? { |part| part.to_s == 'contributor' } # there is a contributor in the path (any? allows for nested roles)
35
+ end
36
+
37
+ # Source codes allowed for contributor.role.source.code
38
+ def valid_codes
39
+ %w[aat lcmpt marcrelator rbmsrel]
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ module Validators
6
+ # Validates encoding.code for subject entries with type "time" against
7
+ # temporal_subject_encoding_codes.yml (union of LOC date-time and temporal source lists).
8
+ class DescriptionSubjectTemporalEncodingVisitorValidator < BaseDescriptionVisitorValidator
9
+ def validate!
10
+ return if error_paths.empty?
11
+
12
+ raise ValidationError, "Unrecognized subject temporal encoding codes in description: #{error_paths.join(', ')}"
13
+ end
14
+
15
+ def visit_hash(hash:, path:)
16
+ return unless in_subject_path?(path)
17
+ return unless hash[:type].to_s == 'time'
18
+
19
+ encoding_code = hash.dig(:encoding, :code)
20
+ return unless encoding_code
21
+
22
+ error_paths << "#{path_to_s(path)}.encoding.code (#{encoding_code})" unless valid_codes.include?(encoding_code.downcase)
23
+ end
24
+
25
+ private
26
+
27
+ def error_paths
28
+ @error_paths ||= []
29
+ end
30
+
31
+ def in_subject_path?(path)
32
+ path.any? { |part| part.to_s == 'subject' }
33
+ end
34
+
35
+ # rubocop:disable Style/ClassVars
36
+ def valid_codes
37
+ @@valid_codes ||= YAML.load_file(::File.expand_path('../../../../temporal_subject_encoding_codes.yml', __dir__)).to_set(&:downcase)
38
+ end
39
+ # rubocop:enable Style/ClassVars
40
+ end
41
+ end
42
+ end
43
+ end