cocina-models 0.71.0 → 0.73.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ module Validators
6
+ # Validates types for description against description_types.yml.
7
+ class DescriptionTypesValidator
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
+ @error_paths = []
16
+ end
17
+
18
+ def validate
19
+ return unless meets_preconditions?
20
+
21
+ validate_obj(attributes, [])
22
+
23
+ return if error_paths.empty?
24
+
25
+ raise ValidationError, "Unrecognized types in description: #{error_paths.join(', ')}"
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :clazz, :attributes, :error_paths
31
+
32
+ def meets_preconditions?
33
+ attributes.key?(:description) || [Cocina::Models::Description,
34
+ Cocina::Models::RequestDescription].include?(clazz)
35
+ end
36
+
37
+ def validate_hash(hash, path)
38
+ hash.each do |key, obj|
39
+ if key == :type
40
+ validate_type(obj, path)
41
+ else
42
+ validate_obj(obj, path + [key])
43
+ end
44
+ end
45
+ end
46
+
47
+ def validate_array(array, path)
48
+ array.each_with_index do |obj, index|
49
+ validate_obj(obj, path + [index])
50
+ end
51
+ end
52
+
53
+ def validate_obj(obj, path)
54
+ validate_hash(obj, path) if obj.is_a?(Hash)
55
+ validate_array(obj, path) if obj.is_a?(Array)
56
+ end
57
+
58
+ def validate_type(type, path)
59
+ valid_types.each do |type_signature, types|
60
+ next unless match?(path, type_signature)
61
+ break if types.include?(type.downcase)
62
+
63
+ error_paths << path_to_s(path)
64
+ break
65
+ end
66
+ end
67
+
68
+ def match?(path, type_signature)
69
+ clean_path(path).last(type_signature.length) == type_signature
70
+ end
71
+
72
+ # Some part of the path are ignored for the purpose of matching.
73
+ def clean_path(path)
74
+ new_path = path.reject do |part|
75
+ part.is_a?(Integer) || %i[parallelValue parallelContributor parallelEvent].include?(part)
76
+ end
77
+ # This needs to happen after parallelValue is removed
78
+ # to handle structuredValue > parallelValue > structuredValue
79
+ new_path.reject.with_index do |part, index|
80
+ part == :structuredValue && new_path[index - 1] == :structuredValue
81
+ end
82
+ end
83
+
84
+ # rubocop:disable Style/ClassVars
85
+ def valid_types
86
+ # Class var to minimize loading from disk.
87
+ @@valid_types ||= begin
88
+ types = types_yaml.map do |type_signature_str, type_objs|
89
+ type_signature = type_signature_str.split('.').map(&:to_sym)
90
+ types = type_objs.map { |type_obj| type_obj['value'].downcase }
91
+ [type_signature, types]
92
+ end
93
+ # Sorting so that longer signatures match first.
94
+ types.sort { |a, b| b.first.length <=> a.first.length }
95
+ end
96
+ end
97
+ # rubocop:enable Style/ClassVars
98
+
99
+ def types_yaml
100
+ YAML.load_file(::File.expand_path('../../../../description_types.yml', __dir__))
101
+ end
102
+
103
+ def path_to_s(path)
104
+ # This matches the format used by descriptive spreadsheets
105
+ path_str = ''
106
+ path.each_with_index do |part, index|
107
+ if part.is_a?(Integer)
108
+ path_str += (part + 1).to_s
109
+ else
110
+ path_str += '.' if index.positive?
111
+ path_str += part.to_s
112
+ end
113
+ end
114
+ path_str
115
+ end
116
+ end
117
+ end
118
+ end
119
+ 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,21 @@
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 = [
9
+ OpenApiValidator,
10
+ DarkValidator,
11
+ CatalogLinksValidator,
12
+ DescriptionTypesValidator
13
+ ].freeze
14
+
15
+ def self.validate(clazz, attributes)
16
+ VALIDATORS.each { |validator| validator.validate(clazz, attributes) }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- VERSION = '0.71.0'
5
+ VERSION = '0.73.1'
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/openapi.yml CHANGED
@@ -161,11 +161,8 @@ components:
161
161
  description: Name of role
162
162
  type: string
163
163
  enum:
164
- - 'dor-apo-creator'
165
164
  - 'dor-apo-depositor'
166
165
  - 'dor-apo-manager'
167
- - 'dor-apo-metadata'
168
- - 'dor-apo-reviewer'
169
166
  - 'dor-apo-viewer'
170
167
  - 'sdr-administrator'
171
168
  - 'sdr-viewer'
@@ -369,18 +366,28 @@ components:
369
366
  CatalogLink:
370
367
  type: object
371
368
  additionalProperties: false
372
- required:
373
- - catalog
374
- - catalogRecordId
375
369
  properties:
376
370
  catalog:
377
371
  description: Catalog that is the source of the linked record.
378
372
  type: string
373
+ enum:
374
+ - symphony
375
+ - previous symphony
379
376
  example: symphony
377
+ refresh:
378
+ description: Only one of the catkeys should be designated for refreshing.
379
+ This means that this key is the one used to pull metadata from the catalog
380
+ if there is more than one key present.
381
+ type: boolean
382
+ default: false
380
383
  catalogRecordId:
381
384
  description: Record identifier that is unique within the context of the linked record's catalog.
382
385
  type: string
383
386
  example: '11403803'
387
+ required:
388
+ - catalog
389
+ - catalogRecordId
390
+ - refresh
384
391
  CatkeyBarcode:
385
392
  description: The barcode associated with a DRO object based on catkey, prefixed with 36105
386
393
  type: string
@@ -516,7 +523,7 @@ components:
516
523
  items:
517
524
  $ref: "#/components/schemas/DescriptiveValue"
518
525
  type:
519
- description: Entity type of the contributor (person, organization, etc.).
526
+ description: Entity type of the contributor (person, organization, etc.). See https://sul-dlss.github.io/cocina-models/description_types.html for valid types.
520
527
  type: string
521
528
  status:
522
529
  description: Status of the contributor relative to other parallel contributors
@@ -705,7 +712,7 @@ components:
705
712
  # https://github.com/interagent/committee/issues/286
706
713
  # - type: integer
707
714
  type:
708
- description: Type of value provided by the descriptive element.
715
+ description: Type of value provided by the descriptive element. See https://sul-dlss.github.io/cocina-models/description_types.html for valid types.
709
716
  type: string
710
717
  status:
711
718
  description: Status of the descriptive element value relative to other instances
@@ -781,7 +788,7 @@ components:
781
788
  items:
782
789
  $ref: "#/components/schemas/DescriptiveValue"
783
790
  type:
784
- description: Entity type of the contributor (person, organization, etc.).
791
+ description: Entity type of the contributor (person, organization, etc.). See https://sul-dlss.github.io/cocina-models/description_types.html for valid types.
785
792
  type: string
786
793
  status:
787
794
  description: Status of the contributor relative to other parallel contributors (e.g. the primary author among a group of contributors).
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.71.0
4
+ version: 0.73.1
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-31 00:00:00.000000000 Z
11
+ date: 2022-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -274,7 +274,10 @@ files:
274
274
  - bin/console
275
275
  - bin/setup
276
276
  - cocina-models.gemspec
277
+ - description_types.yml
278
+ - docs/_config.yml
277
279
  - docs/cocina-base.jsonld
280
+ - docs/description_types.md
278
281
  - docs/index.html
279
282
  - docs/maps/Agent.json
280
283
  - docs/maps/Collection.json
@@ -384,7 +387,11 @@ files:
384
387
  - lib/cocina/models/title.rb
385
388
  - lib/cocina/models/title_builder.rb
386
389
  - lib/cocina/models/validatable.rb
387
- - lib/cocina/models/validator.rb
390
+ - lib/cocina/models/validators/catalog_links_validator.rb
391
+ - lib/cocina/models/validators/dark_validator.rb
392
+ - lib/cocina/models/validators/description_types_validator.rb
393
+ - lib/cocina/models/validators/open_api_validator.rb
394
+ - lib/cocina/models/validators/validator.rb
388
395
  - lib/cocina/models/version.rb
389
396
  - lib/cocina/models/vocabulary.rb
390
397
  - lib/cocina/models/world_access.rb
@@ -410,7 +417,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
410
417
  - !ruby/object:Gem::Version
411
418
  version: '0'
412
419
  requirements: []
413
- rubygems_version: 3.2.32
420
+ rubygems_version: 3.3.9
414
421
  signing_key:
415
422
  specification_version: 4
416
423
  summary: Data models for the SDR
@@ -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