cocina-models 0.67.0 → 0.69.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +12 -0
- data/README.md +19 -0
- data/cocina-models.gemspec +4 -1
- data/lib/cocina/generator/generator.rb +15 -12
- data/lib/cocina/generator/vocab.rb +46 -42
- data/lib/cocina/models/access.rb +3 -3
- data/lib/cocina/models/admin_policy.rb +1 -1
- data/lib/cocina/models/{admin_policy_default_access.rb → admin_policy_access_template.rb} +4 -4
- data/lib/cocina/models/admin_policy_administrative.rb +1 -1
- data/lib/cocina/models/administrative.rb +0 -3
- data/lib/cocina/models/citation_only_access.rb +3 -3
- data/lib/cocina/models/collection.rb +5 -5
- data/lib/cocina/models/collection_access.rb +1 -1
- data/lib/cocina/models/controlled_digital_lending_access.rb +3 -3
- data/lib/cocina/models/dark_access.rb +3 -3
- data/lib/cocina/models/dro.rb +15 -15
- data/lib/cocina/models/dro_access.rb +4 -4
- data/lib/cocina/models/dro_rights_description_builder.rb +67 -0
- data/lib/cocina/models/embargo.rb +3 -3
- data/lib/cocina/models/file.rb +1 -1
- data/lib/cocina/models/file_access.rb +3 -3
- data/lib/cocina/models/file_set.rb +16 -16
- data/lib/cocina/models/file_set_type.rb +25 -0
- data/lib/cocina/models/license.rb +10 -0
- data/lib/cocina/models/location_based_access.rb +3 -3
- data/lib/cocina/models/location_based_download_access.rb +3 -3
- data/lib/cocina/models/object_type.rb +31 -0
- data/lib/cocina/models/request_admin_policy.rb +1 -1
- data/lib/cocina/models/request_administrative.rb +14 -0
- data/lib/cocina/models/request_collection.rb +6 -6
- data/lib/cocina/models/request_dro.rb +16 -16
- data/lib/cocina/models/request_file.rb +1 -1
- data/lib/cocina/models/request_file_set.rb +16 -16
- data/lib/cocina/models/rights_description_builder.rb +81 -0
- data/lib/cocina/models/stanford_access.rb +3 -3
- data/lib/cocina/models/title_builder.rb +203 -0
- data/lib/cocina/models/version.rb +1 -1
- data/lib/cocina/models/vocabulary.rb +30 -0
- data/lib/cocina/models/world_access.rb +3 -3
- data/lib/cocina/models.rb +26 -7
- data/lib/cocina/rspec/matchers.rb +103 -0
- data/lib/cocina/rspec.rb +14 -0
- data/openapi.yml +132 -124
- metadata +39 -10
- data/lib/cocina/models/vocab.rb +0 -162
data/lib/cocina/models.rb
CHANGED
@@ -22,6 +22,7 @@ class CocinaModelsInflector < Zeitwerk::Inflector
|
|
22
22
|
'dro_access' => 'DROAccess',
|
23
23
|
'dro_structural' => 'DROStructural',
|
24
24
|
'request_dro_structural' => 'RequestDROStructural',
|
25
|
+
'rspec' => 'RSpec',
|
25
26
|
'version' => 'VERSION'
|
26
27
|
}.freeze
|
27
28
|
|
@@ -42,7 +43,7 @@ module Cocina
|
|
42
43
|
# Raised when the type attribute is not valid.
|
43
44
|
class UnknownTypeError < Error; end
|
44
45
|
|
45
|
-
# Raised when an error occurs validating against openapi.
|
46
|
+
# Raised when the type attribute is missing or an error occurs validating against openapi.
|
46
47
|
class ValidationError < Error; end
|
47
48
|
|
48
49
|
# Base class for Cocina Structs
|
@@ -56,14 +57,25 @@ module Cocina
|
|
56
57
|
include Dry.Types()
|
57
58
|
end
|
58
59
|
|
60
|
+
##
|
61
|
+
# Alias for `Cocina::Models::Vocabulary.create`.
|
62
|
+
#
|
63
|
+
# @param (see Cocina::Models::Vocabulary#initialize)
|
64
|
+
# @return [Class]
|
65
|
+
# rubocop:disable Naming/MethodName
|
66
|
+
def self.Vocabulary(uri)
|
67
|
+
Vocabulary.create(uri)
|
68
|
+
end
|
69
|
+
# rubocop:enable Naming/MethodName
|
70
|
+
|
59
71
|
# @param [Hash] dyn a ruby hash representation of the JSON serialization of a collection or DRO
|
60
72
|
# @param [boolean] validate
|
61
73
|
# @return [DRO,Collection,AdminPolicy]
|
62
74
|
# @raises [UnknownTypeError] if a valid type is not found in the data
|
63
|
-
# @raises [
|
75
|
+
# @raises [ValidationError] if a type field cannot be found in the data
|
64
76
|
# @raises [ValidationError] if hash representation fails openapi validation
|
65
77
|
def self.build(dyn, validate: true)
|
66
|
-
clazz = case dyn
|
78
|
+
clazz = case type_for(dyn)
|
67
79
|
when *DRO::TYPES
|
68
80
|
DRO
|
69
81
|
when *Collection::TYPES
|
@@ -71,7 +83,7 @@ module Cocina
|
|
71
83
|
when *AdminPolicy::TYPES
|
72
84
|
AdminPolicy
|
73
85
|
else
|
74
|
-
raise UnknownTypeError, "Unknown type: '#{dyn.fetch('type')}'"
|
86
|
+
raise UnknownTypeError, "Unknown type: '#{dyn.with_indifferent_access.fetch('type')}'"
|
75
87
|
end
|
76
88
|
clazz.new(dyn, false, validate)
|
77
89
|
end
|
@@ -80,10 +92,10 @@ module Cocina
|
|
80
92
|
# @param [boolean] validate
|
81
93
|
# @return [RequestDRO,RequestCollection,RequestAdminPolicy]
|
82
94
|
# @raises [UnknownTypeError] if a valid type is not found in the data
|
83
|
-
# @raises [
|
95
|
+
# @raises [ValidationError] if a type field cannot be found in the data
|
84
96
|
# @raises [ValidationError] if hash representation fails openapi validation
|
85
97
|
def self.build_request(dyn, validate: true)
|
86
|
-
clazz = case dyn
|
98
|
+
clazz = case type_for(dyn)
|
87
99
|
when *DRO::TYPES
|
88
100
|
RequestDRO
|
89
101
|
when *Collection::TYPES
|
@@ -91,9 +103,16 @@ module Cocina
|
|
91
103
|
when *AdminPolicy::TYPES
|
92
104
|
RequestAdminPolicy
|
93
105
|
else
|
94
|
-
raise UnknownTypeError, "Unknown type: '#{dyn.fetch('type')}'"
|
106
|
+
raise UnknownTypeError, "Unknown type: '#{dyn.with_indifferent_access.fetch('type')}'"
|
95
107
|
end
|
96
108
|
clazz.new(dyn, false, validate)
|
97
109
|
end
|
110
|
+
|
111
|
+
def self.type_for(dyn)
|
112
|
+
dyn.with_indifferent_access.fetch('type')
|
113
|
+
rescue KeyError
|
114
|
+
raise ValidationError, 'Type field not found'
|
115
|
+
end
|
116
|
+
private_class_method :type_for
|
98
117
|
end
|
99
118
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Provides RSpec matchers for Cocina models
|
4
|
+
module Cocina
|
5
|
+
module RSpec
|
6
|
+
# Cocina-related RSpec matchers
|
7
|
+
module Matchers
|
8
|
+
extend ::RSpec::Matchers::DSL
|
9
|
+
|
10
|
+
# NOTE: each k/v pair in the hash passed to this matcher will need to be present in actual
|
11
|
+
matcher :cocina_object_with do |**kwargs|
|
12
|
+
kwargs.each do |cocina_section, expected|
|
13
|
+
match do |actual|
|
14
|
+
expected.all? do |expected_key, expected_value|
|
15
|
+
# NOTE: there's no better method on Hash that I could find for this.
|
16
|
+
# #include? and #member? only check keys, not k/v pairs
|
17
|
+
actual.public_send(cocina_section).to_h.any? do |actual_key, actual_value|
|
18
|
+
if expected_value.is_a?(Hash) && actual_value.is_a?(Hash)
|
19
|
+
expected_value.all? { |pair| actual_value.to_a.include?(pair) }
|
20
|
+
else
|
21
|
+
actual_key == expected_key && actual_value == expected_value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_matcher :match_cocina_object_with, :cocina_object_with
|
30
|
+
|
31
|
+
# The `equal_cocina_model` matcher compares a JSON string as actual value
|
32
|
+
# against a Cocina model coerced to JSON as expected value. We want to compare
|
33
|
+
# these as JSON rather than as hashes, else we'll have to start
|
34
|
+
# deep-converting some values within the hashes, thinking of date/time values
|
35
|
+
# in particular. This matching behavior continues what dor-services-app was
|
36
|
+
# already doing before this custom matcher was written.
|
37
|
+
#
|
38
|
+
# Note, though, that when actual and expected values do *not* match, we coerce
|
39
|
+
# both values to hashes to allow the `super_diff` gem to highlight the areas
|
40
|
+
# that differ. This is easier to scan than two giant JSON strings.
|
41
|
+
matcher :equal_cocina_model do |expected|
|
42
|
+
match do |actual|
|
43
|
+
Cocina::Models.build(JSON.parse(actual)).to_json == expected.to_json
|
44
|
+
rescue NoMethodError
|
45
|
+
warn "Could not match cocina models because expected is not a valid JSON string: #{expected}"
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
failure_message do |actual|
|
50
|
+
SuperDiff::EqualityMatchers::Hash.new(
|
51
|
+
expected: expected.to_h.deep_symbolize_keys,
|
52
|
+
actual: JSON.parse(actual, symbolize_names: true)
|
53
|
+
).fail
|
54
|
+
rescue StandardError => e
|
55
|
+
"ERROR in CocinaMatchers: #{e}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
matcher :cocina_object_with_types do |expected|
|
60
|
+
match do |actual|
|
61
|
+
return false if expected[:without_order] && !match_no_orders?
|
62
|
+
|
63
|
+
if expected[:content_type] && expected[:resource_types]
|
64
|
+
match_cocina_type?(actual, expected) && match_contained_cocina_types?(actual, expected)
|
65
|
+
elsif expected[:content_type] && expected[:viewing_direction]
|
66
|
+
match_cocina_type?(actual, expected) && match_cocina_viewing_direction?(actual, expected)
|
67
|
+
elsif expected[:content_type]
|
68
|
+
match_cocina_type?(actual, expected)
|
69
|
+
elsif expected[:resource_types]
|
70
|
+
match_contained_cocina_types?(actual, expected)
|
71
|
+
else
|
72
|
+
raise ArgumentError, 'must provide content_type and/or resource_types keyword args'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def match_no_orders?
|
77
|
+
actual.structural.hasMemberOrders.blank?
|
78
|
+
end
|
79
|
+
|
80
|
+
def match_cocina_type?(actual, expected)
|
81
|
+
actual.type == expected[:content_type]
|
82
|
+
end
|
83
|
+
|
84
|
+
def match_cocina_viewing_direction?(actual, expected)
|
85
|
+
actual.structural.hasMemberOrders.map(&:viewingDirection).all? do |viewing_direction|
|
86
|
+
viewing_direction == expected[:viewing_direction]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def match_contained_cocina_types?(actual, expected)
|
91
|
+
Array(actual.structural.contains).map(&:type).all? { |type| type.in?(expected[:resource_types]) }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
matcher :cocina_admin_policy_with_registration_collections do |expected|
|
96
|
+
match do |actual|
|
97
|
+
actual.type == Cocina::Models::ObjectType.admin_policy &&
|
98
|
+
expected.all? { |collection_id| collection_id.in?(actual.administrative.collectionsForRegistration) }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/cocina/rspec.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/core'
|
4
|
+
require 'rspec/matchers'
|
5
|
+
if defined?(Rails)
|
6
|
+
require 'super_diff/rspec-rails'
|
7
|
+
else
|
8
|
+
require 'super_diff/rspec'
|
9
|
+
end
|
10
|
+
require 'cocina/rspec/matchers'
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.include Cocina::RSpec::Matchers
|
14
|
+
end
|