cocina-models 0.128.0 → 0.130.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 +4 -4
- data/Gemfile.lock +22 -31
- data/form_source_codes.yml +600 -0
- data/lib/cocina/models/event.rb +0 -3
- data/lib/cocina/models/mapping/from_mods/event.rb +3 -52
- data/lib/cocina/models/mapping/from_mods/form.rb +1 -1
- data/lib/cocina/models/request_admin_policy.rb +1 -1
- data/lib/cocina/models/validatable.rb +20 -2
- data/lib/cocina/models/validators/composite_description_validator.rb +2 -1
- data/lib/cocina/models/validators/composite_structural_validator.rb +1 -1
- data/lib/cocina/models/validators/description_form_source_code_visitor_validator.rb +41 -0
- data/lib/cocina/models/validators/description_types_visitor_validator.rb +1 -1
- data/lib/cocina/models/validators/json_schema_validator.rb +50 -5
- data/lib/cocina/models/validators/validator.rb +13 -3
- data/lib/cocina/models/version.rb +1 -1
- data/schema.json +94 -82
- metadata +3 -2
- data/lib/cocina/models/descriptive_parallel_event.rb +0 -27
|
@@ -18,7 +18,7 @@ module Cocina
|
|
|
18
18
|
def initialize(clazz, attributes, validators: VALIDATORS)
|
|
19
19
|
@clazz = clazz
|
|
20
20
|
@attributes = attributes
|
|
21
|
-
@validators = validators.map { |v| v.new(attributes) }
|
|
21
|
+
@validators = meets_preconditions? ? validators.map { |v| v.new(attributes) } : []
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def validate
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cocina
|
|
4
|
+
module Models
|
|
5
|
+
module Validators
|
|
6
|
+
# Validates form.source.code values against form_source_codes.yml.
|
|
7
|
+
class DescriptionFormSourceCodeVisitorValidator < BaseDescriptionVisitorValidator
|
|
8
|
+
def validate!
|
|
9
|
+
return if error_paths.empty?
|
|
10
|
+
|
|
11
|
+
raise ValidationError, "Unrecognized form source codes in description: #{error_paths.join(', ')}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def visit_hash(hash:, path:)
|
|
15
|
+
return unless form_entry_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 form_entry_path?(path)
|
|
30
|
+
path.length >= 2 && path[-1].is_a?(Integer) && path[-2].to_s == 'form'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# rubocop:disable Style/ClassVars
|
|
34
|
+
def valid_codes
|
|
35
|
+
@@valid_codes ||= YAML.load_file(::File.expand_path('../../../../form_source_codes.yml', __dir__)).to_set(&:downcase)
|
|
36
|
+
end
|
|
37
|
+
# rubocop:enable Style/ClassVars
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -41,7 +41,7 @@ module Cocina
|
|
|
41
41
|
# Some part of the path are ignored for the purpose of matching.
|
|
42
42
|
def clean_path(path)
|
|
43
43
|
new_path = path.reject do |part|
|
|
44
|
-
part.is_a?(Integer) || %i[parallelValue
|
|
44
|
+
part.is_a?(Integer) || %i[parallelValue].include?(part.to_sym)
|
|
45
45
|
end.map(&:to_sym)
|
|
46
46
|
# This needs to happen after parallelValue is removed
|
|
47
47
|
# to handle structuredValue > parallelValue > structuredValue
|
|
@@ -25,6 +25,11 @@ module Cocina
|
|
|
25
25
|
|
|
26
26
|
# @see #validate
|
|
27
27
|
def self.validate(...)
|
|
28
|
+
# Skip when nested inside another Validatable's construction: the enclosing object's own
|
|
29
|
+
# schema check already covers this, so re-evaluating it here would be
|
|
30
|
+
# redundant. See Validatable#new.
|
|
31
|
+
return if Thread.current[:cocina_construction_depth].to_i.positive?
|
|
32
|
+
|
|
28
33
|
new(...).validate
|
|
29
34
|
end
|
|
30
35
|
|
|
@@ -67,7 +72,7 @@ module Cocina
|
|
|
67
72
|
evaluation = self.class.validator_for(method_name).evaluate(attributes.as_json)
|
|
68
73
|
return if evaluation.valid?
|
|
69
74
|
|
|
70
|
-
raise ValidationError, "When validating #{method_name}: " + filtered_error_messages(evaluation).join('
|
|
75
|
+
raise ValidationError, "When validating #{method_name}: " + filtered_error_messages(evaluation).join('; ')
|
|
71
76
|
end
|
|
72
77
|
|
|
73
78
|
private
|
|
@@ -90,7 +95,7 @@ module Cocina
|
|
|
90
95
|
denoised.flat_map { |d| format_detail(d) }.uniq
|
|
91
96
|
end
|
|
92
97
|
|
|
93
|
-
#
|
|
98
|
+
# Removes three categories of cascade noise from errors:
|
|
94
99
|
#
|
|
95
100
|
# 1. falseSchema errors — every `unevaluatedProperties: false` on a sub-path emits
|
|
96
101
|
# a "False schema does not allow …" entry for each matched value. These are
|
|
@@ -100,8 +105,28 @@ module Cocina
|
|
|
100
105
|
# is a known model attribute — these arise because allOf/$ref composition means
|
|
101
106
|
# the validator can't prove top-level properties were "evaluated", even though
|
|
102
107
|
# they are valid model fields.
|
|
108
|
+
#
|
|
109
|
+
# 3. anyOf branch noise — when each branch requires a different property, all failing
|
|
110
|
+
# branches emit a separate "required" error at the same path. Collapse into one
|
|
111
|
+
# "needs to include at least one of: …" message.
|
|
103
112
|
def denoise(details)
|
|
104
|
-
details.reject { |
|
|
113
|
+
filtered = details.reject { |detail| false_schema_noise?(detail) || root_unevaluated_noise?(detail) }
|
|
114
|
+
collapse_anyof_required(filtered)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def collapse_anyof_required(details)
|
|
118
|
+
# instanceLocation is the input data that failed. Group errors for each failing data location.
|
|
119
|
+
details.group_by { |detail| detail[:instanceLocation] }.flat_map do |loc, group|
|
|
120
|
+
# splits errors into "required" anyOf errors and others (minItems, unevaluatedProperties)
|
|
121
|
+
required, others = group.partition { |detail| detail[:errors].keys == ['required'] }
|
|
122
|
+
next group if required.size <= 1
|
|
123
|
+
|
|
124
|
+
# Extract property name from "required" messages such as, '"url" is a required property'
|
|
125
|
+
props = required.filter_map { |detail| detail[:errors]['required'][/"([^"]+)"/, 1] }.uniq
|
|
126
|
+
# Put the data path at the start of the message; blank instanceLocation so format_detail won't also append it
|
|
127
|
+
msg = "#{loc} needs to include at least one of the following: #{props.join(', ')}"
|
|
128
|
+
others + [required.first.merge(instanceLocation: '', errors: { 'required' => msg })]
|
|
129
|
+
end
|
|
105
130
|
end
|
|
106
131
|
|
|
107
132
|
def false_schema_noise?(detail)
|
|
@@ -120,12 +145,32 @@ module Cocina
|
|
|
120
145
|
unexpected.all? { |prop| known_root_properties.include?(prop) }
|
|
121
146
|
end
|
|
122
147
|
|
|
148
|
+
# Formats error details hash into more user-friendly messages
|
|
149
|
+
# A detail can carry multiple errors (one per JSON Schema keyword), so this returns an array.
|
|
150
|
+
# minItems errors get a dedicated formatter; everything else uses the generic one.
|
|
123
151
|
def format_detail(detail)
|
|
124
152
|
loc = detail[:instanceLocation]
|
|
125
|
-
detail[:errors].map do |
|
|
126
|
-
|
|
153
|
+
detail[:errors].map do |keyword, message|
|
|
154
|
+
keyword == 'minItems' ? format_min_items(message, loc) : format_generic(message, loc)
|
|
127
155
|
end
|
|
128
156
|
end
|
|
157
|
+
|
|
158
|
+
# Rewrites messages regarding minItems validation
|
|
159
|
+
# "[] has less than 1 item at /event/0/date " is rewritten to "/event/0/date is empty but should have at least 1 item"
|
|
160
|
+
# Arrays with items but fewer than the minimum will be rewritten to "/event/0/date should have at least 2 items"
|
|
161
|
+
def format_min_items(message, loc)
|
|
162
|
+
# extract minimum number of items from message
|
|
163
|
+
min = message[/less than (\d+)/, 1] || '1'
|
|
164
|
+
# pluralize "item" when appropriate
|
|
165
|
+
items = min == '1' ? 'item' : 'items'
|
|
166
|
+
# determine if the message is about an empty array or an non-empty array without enough items
|
|
167
|
+
prefix = message.start_with?('[]') ? "#{loc} is empty but" : loc.to_s
|
|
168
|
+
"#{prefix} should have at least #{min} #{items}"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def format_generic(message, loc)
|
|
172
|
+
loc.empty? ? message : "#{message} at #{loc}"
|
|
173
|
+
end
|
|
129
174
|
end
|
|
130
175
|
end
|
|
131
176
|
end
|
|
@@ -16,12 +16,22 @@ module Cocina
|
|
|
16
16
|
].freeze
|
|
17
17
|
|
|
18
18
|
def self.validate(clazz, attributes, validators: VALIDATORS)
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
hash = attributes_hash(attributes)
|
|
20
|
+
validators.each { |validator| validator.validate(clazz, hash) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Hash] attributes with any embedded Cocina model instances flattened to hashes
|
|
24
|
+
def self.attributes_hash(attributes)
|
|
25
|
+
# Dry::Struct#to_h is already fully recursive (Dry::Struct::Hashify flattens nested
|
|
26
|
+
# structs all the way down), so a Cocina model instance's #to_h has no embedded
|
|
27
|
+
# Cocina model instances left to flatten and the deep walk below would be a no-op.
|
|
28
|
+
return attributes.to_h.with_indifferent_access if attributes.class.name.starts_with?('Cocina::Models')
|
|
29
|
+
|
|
30
|
+
attributes.to_h.deep_transform_values do |value|
|
|
21
31
|
value.class.name.starts_with?('Cocina::Models') ? value.to_h : value
|
|
22
32
|
end.with_indifferent_access
|
|
23
|
-
validators.each { |validator| validator.validate(clazz, attributes_hash) }
|
|
24
33
|
end
|
|
34
|
+
private_class_method :attributes_hash
|
|
25
35
|
|
|
26
36
|
def self.deep_transform_values(object, ...)
|
|
27
37
|
case object
|
data/schema.json
CHANGED
|
@@ -1270,70 +1270,6 @@
|
|
|
1270
1270
|
}
|
|
1271
1271
|
}
|
|
1272
1272
|
},
|
|
1273
|
-
"DescriptiveParallelEvent": {
|
|
1274
|
-
"description": "Value model for multiple representations of information about the same event (e.g. in different languages).",
|
|
1275
|
-
"type": "object",
|
|
1276
|
-
"allOf": [
|
|
1277
|
-
{
|
|
1278
|
-
"$ref": "#/$defs/DescriptiveStructuredValue"
|
|
1279
|
-
},
|
|
1280
|
-
{
|
|
1281
|
-
"type": "object",
|
|
1282
|
-
"properties": {
|
|
1283
|
-
"type": {
|
|
1284
|
-
"description": "Description of the event (creation, publication, etc.).",
|
|
1285
|
-
"type": "string"
|
|
1286
|
-
},
|
|
1287
|
-
"displayLabel": {
|
|
1288
|
-
"description": "The preferred display label to use for the event in access systems.",
|
|
1289
|
-
"type": [
|
|
1290
|
-
"string",
|
|
1291
|
-
"null"
|
|
1292
|
-
]
|
|
1293
|
-
},
|
|
1294
|
-
"date": {
|
|
1295
|
-
"description": "Dates associated with the event.",
|
|
1296
|
-
"type": "array",
|
|
1297
|
-
"items": {
|
|
1298
|
-
"$ref": "#/$defs/DescriptiveValue"
|
|
1299
|
-
}
|
|
1300
|
-
},
|
|
1301
|
-
"contributor": {
|
|
1302
|
-
"description": "Contributors associated with the event.",
|
|
1303
|
-
"type": "array",
|
|
1304
|
-
"items": {
|
|
1305
|
-
"$ref": "#/$defs/Contributor"
|
|
1306
|
-
}
|
|
1307
|
-
},
|
|
1308
|
-
"location": {
|
|
1309
|
-
"description": "Locations associated with the event.",
|
|
1310
|
-
"type": "array",
|
|
1311
|
-
"items": {
|
|
1312
|
-
"$ref": "#/$defs/DescriptiveValue"
|
|
1313
|
-
}
|
|
1314
|
-
},
|
|
1315
|
-
"identifier": {
|
|
1316
|
-
"description": "Identifiers and URIs associated with the event.",
|
|
1317
|
-
"type": "array",
|
|
1318
|
-
"items": {
|
|
1319
|
-
"$ref": "#/$defs/DescriptiveIdentifier"
|
|
1320
|
-
}
|
|
1321
|
-
},
|
|
1322
|
-
"note": {
|
|
1323
|
-
"description": "Other information about the event.",
|
|
1324
|
-
"type": "array",
|
|
1325
|
-
"items": {
|
|
1326
|
-
"$ref": "#/$defs/DescriptiveValue"
|
|
1327
|
-
}
|
|
1328
|
-
},
|
|
1329
|
-
"valueLanguage": {
|
|
1330
|
-
"$ref": "#/$defs/DescriptiveValueLanguage"
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
],
|
|
1335
|
-
"unevaluatedProperties": false
|
|
1336
|
-
},
|
|
1337
1273
|
"DescriptiveParallelValue": {
|
|
1338
1274
|
"description": "Value model for multiple representations of the same information (e.g. in different languages).",
|
|
1339
1275
|
"type": "object",
|
|
@@ -1529,13 +1465,6 @@
|
|
|
1529
1465
|
},
|
|
1530
1466
|
"valueLanguage": {
|
|
1531
1467
|
"$ref": "#/$defs/DescriptiveValueLanguage"
|
|
1532
|
-
},
|
|
1533
|
-
"parallelEvent": {
|
|
1534
|
-
"description": "For multiple representations of information about the same event (e.g. in different languages)",
|
|
1535
|
-
"type": "array",
|
|
1536
|
-
"items": {
|
|
1537
|
-
"$ref": "#/$defs/DescriptiveParallelEvent"
|
|
1538
|
-
}
|
|
1539
1468
|
}
|
|
1540
1469
|
}
|
|
1541
1470
|
}
|
|
@@ -1589,14 +1518,6 @@
|
|
|
1589
1518
|
"minItems": 1
|
|
1590
1519
|
}
|
|
1591
1520
|
}
|
|
1592
|
-
},
|
|
1593
|
-
{
|
|
1594
|
-
"required": ["parallelEvent"],
|
|
1595
|
-
"properties": {
|
|
1596
|
-
"parallelEvent": {
|
|
1597
|
-
"minItems": 1
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
1521
|
}
|
|
1601
1522
|
]
|
|
1602
1523
|
},
|
|
@@ -2546,9 +2467,21 @@
|
|
|
2546
2467
|
"required": [
|
|
2547
2468
|
"cocinaVersion",
|
|
2548
2469
|
"administrative",
|
|
2470
|
+
"description",
|
|
2549
2471
|
"type",
|
|
2550
2472
|
"version"
|
|
2551
2473
|
],
|
|
2474
|
+
"allOf": [
|
|
2475
|
+
{
|
|
2476
|
+
"properties": {
|
|
2477
|
+
"description": {
|
|
2478
|
+
"required": [
|
|
2479
|
+
"title"
|
|
2480
|
+
]
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
],
|
|
2552
2485
|
"unevaluatedProperties": false
|
|
2553
2486
|
},
|
|
2554
2487
|
"RequestAdministrative": {
|
|
@@ -2616,6 +2549,47 @@
|
|
|
2616
2549
|
"type",
|
|
2617
2550
|
"version"
|
|
2618
2551
|
],
|
|
2552
|
+
"anyOf": [
|
|
2553
|
+
{
|
|
2554
|
+
"required": [
|
|
2555
|
+
"description"
|
|
2556
|
+
],
|
|
2557
|
+
"properties": {
|
|
2558
|
+
"description": {
|
|
2559
|
+
"required": [
|
|
2560
|
+
"title"
|
|
2561
|
+
]
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
},
|
|
2565
|
+
{
|
|
2566
|
+
"required": [
|
|
2567
|
+
"identification"
|
|
2568
|
+
],
|
|
2569
|
+
"properties": {
|
|
2570
|
+
"identification": {
|
|
2571
|
+
"required": [
|
|
2572
|
+
"catalogLinks"
|
|
2573
|
+
],
|
|
2574
|
+
"properties": {
|
|
2575
|
+
"catalogLinks": {
|
|
2576
|
+
"contains": {
|
|
2577
|
+
"type": "object",
|
|
2578
|
+
"required": [
|
|
2579
|
+
"refresh"
|
|
2580
|
+
],
|
|
2581
|
+
"properties": {
|
|
2582
|
+
"refresh": {
|
|
2583
|
+
"const": true
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
],
|
|
2619
2593
|
"unevaluatedProperties": false
|
|
2620
2594
|
},
|
|
2621
2595
|
"RequestDRO": {
|
|
@@ -2682,6 +2656,47 @@
|
|
|
2682
2656
|
"type",
|
|
2683
2657
|
"version"
|
|
2684
2658
|
],
|
|
2659
|
+
"anyOf": [
|
|
2660
|
+
{
|
|
2661
|
+
"required": [
|
|
2662
|
+
"description"
|
|
2663
|
+
],
|
|
2664
|
+
"properties": {
|
|
2665
|
+
"description": {
|
|
2666
|
+
"required": [
|
|
2667
|
+
"title"
|
|
2668
|
+
]
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
},
|
|
2672
|
+
{
|
|
2673
|
+
"required": [
|
|
2674
|
+
"identification"
|
|
2675
|
+
],
|
|
2676
|
+
"properties": {
|
|
2677
|
+
"identification": {
|
|
2678
|
+
"required": [
|
|
2679
|
+
"catalogLinks"
|
|
2680
|
+
],
|
|
2681
|
+
"properties": {
|
|
2682
|
+
"catalogLinks": {
|
|
2683
|
+
"contains": {
|
|
2684
|
+
"type": "object",
|
|
2685
|
+
"required": [
|
|
2686
|
+
"refresh"
|
|
2687
|
+
],
|
|
2688
|
+
"properties": {
|
|
2689
|
+
"refresh": {
|
|
2690
|
+
"const": true
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
],
|
|
2685
2700
|
"unevaluatedProperties": false
|
|
2686
2701
|
},
|
|
2687
2702
|
"RequestDROStructural": {
|
|
@@ -2803,9 +2818,6 @@
|
|
|
2803
2818
|
"type": "string"
|
|
2804
2819
|
}
|
|
2805
2820
|
},
|
|
2806
|
-
"required": [
|
|
2807
|
-
"title"
|
|
2808
|
-
],
|
|
2809
2821
|
"unevaluatedProperties": false
|
|
2810
2822
|
},
|
|
2811
2823
|
"RequestFile": {
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cocina-models
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.130.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Coyne
|
|
@@ -326,6 +326,7 @@ files:
|
|
|
326
326
|
- docs/cocina-base.jsonld
|
|
327
327
|
- docs/description_types.md
|
|
328
328
|
- exe/generator
|
|
329
|
+
- form_source_codes.yml
|
|
329
330
|
- identifier_source_codes.yml
|
|
330
331
|
- iso15924_codes.yml
|
|
331
332
|
- language_uri_iso639_2_codes.yml
|
|
@@ -381,7 +382,6 @@ files:
|
|
|
381
382
|
- lib/cocina/models/descriptive_geographic_metadata.rb
|
|
382
383
|
- lib/cocina/models/descriptive_grouped_value.rb
|
|
383
384
|
- lib/cocina/models/descriptive_identifier.rb
|
|
384
|
-
- lib/cocina/models/descriptive_parallel_event.rb
|
|
385
385
|
- lib/cocina/models/descriptive_parallel_value.rb
|
|
386
386
|
- lib/cocina/models/descriptive_structured_value.rb
|
|
387
387
|
- lib/cocina/models/descriptive_value.rb
|
|
@@ -485,6 +485,7 @@ files:
|
|
|
485
485
|
- lib/cocina/models/validators/description_date_time_visitor_validator.rb
|
|
486
486
|
- lib/cocina/models/validators/description_event_date_visitor_validator.rb
|
|
487
487
|
- lib/cocina/models/validators/description_form_resource_type_visitor_validator.rb
|
|
488
|
+
- lib/cocina/models/validators/description_form_source_code_visitor_validator.rb
|
|
488
489
|
- lib/cocina/models/validators/description_identifier_source_code_visitor_validator.rb
|
|
489
490
|
- lib/cocina/models/validators/description_language_code_visitor_validator.rb
|
|
490
491
|
- lib/cocina/models/validators/description_language_uri_visitor_validator.rb
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Cocina
|
|
4
|
-
module Models
|
|
5
|
-
# Value model for multiple representations of information about the same event (e.g.
|
|
6
|
-
# in different languages).
|
|
7
|
-
class DescriptiveParallelEvent < Struct
|
|
8
|
-
attribute :structuredValue, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
|
|
9
|
-
# Description of the event (creation, publication, etc.).
|
|
10
|
-
attribute? :type, Types::Strict::String
|
|
11
|
-
# The preferred display label to use for the event in access systems.
|
|
12
|
-
attribute? :displayLabel, Types::Strict::String.optional
|
|
13
|
-
# Dates associated with the event.
|
|
14
|
-
attribute :date, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
|
|
15
|
-
# Contributors associated with the event.
|
|
16
|
-
attribute :contributor, Types::Strict::Array.of(Contributor).default([].freeze)
|
|
17
|
-
# Locations associated with the event.
|
|
18
|
-
attribute :location, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
|
|
19
|
-
# Identifiers and URIs associated with the event.
|
|
20
|
-
attribute :identifier, Types::Strict::Array.of(DescriptiveIdentifier).default([].freeze)
|
|
21
|
-
# Other information about the event.
|
|
22
|
-
attribute :note, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
|
|
23
|
-
# Language of the descriptive element value
|
|
24
|
-
attribute? :valueLanguage, DescriptiveValueLanguage.optional
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|