coarnotify 0.1.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.
@@ -0,0 +1,833 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'set'
5
+ require_relative 'activity_streams2'
6
+ require_relative '../validate'
7
+ require_relative '../exceptions'
8
+
9
+ module Coarnotify
10
+ module Core
11
+ # This module is home to all the core model objects from which the notify patterns extend
12
+ module Notify
13
+ # Namespace for COAR Notify, to be used to construct namespaced properties used in COAR Notify Patterns
14
+ NOTIFY_NAMESPACE = "https://coar-notify.net"
15
+
16
+ # COAR Notify properties used in COAR Notify Patterns
17
+ #
18
+ # Most of these are provided as arrays, where the first element is the property name, and the second element is the namespace.
19
+ # Some are provided as plain strings without namespaces
20
+ #
21
+ # These are suitable to be used as property names in all the property getters/setters in the notify pattern objects
22
+ # and in the validation configuration.
23
+ module NotifyProperties
24
+ # inbox property
25
+ INBOX = ["inbox", NOTIFY_NAMESPACE].freeze
26
+
27
+ # ietf:cite-as property
28
+ CITE_AS = ["ietf:cite-as", NOTIFY_NAMESPACE].freeze
29
+
30
+ # ietf:item property
31
+ ITEM = ["ietf:item", NOTIFY_NAMESPACE].freeze
32
+
33
+ # name property
34
+ NAME = "name"
35
+
36
+ # mediaType property
37
+ MEDIA_TYPE = "mediaType"
38
+ end
39
+
40
+ # List of all the COAR Notify types patterns may use.
41
+ #
42
+ # These are in addition to the base Activity Streams types, which are in ActivityStreams2::ActivityStreamsTypes
43
+ module NotifyTypes
44
+ ENDORSEMENT_ACTION = "coar-notify:EndorsementAction"
45
+ INGEST_ACTION = "coar-notify:IngestAction"
46
+ RELATIONSHIP_ACTION = "coar-notify:RelationshipAction"
47
+ REVIEW_ACTION = "coar-notify:ReviewAction"
48
+ UNPROCESSABLE_NOTIFICATION = "coar-notify:UnprocessableNotification"
49
+
50
+ ABOUT_PAGE = "sorg:AboutPage"
51
+ end
52
+
53
+ # Validation rules for notify patterns
54
+ VALIDATION_RULES = {
55
+ ActivityStreams2::Properties::ID => {
56
+ "default" => proc { |obj, uri| Validate.absolute_uri(obj, uri) },
57
+ "context" => {
58
+ ActivityStreams2::Properties::CONTEXT => {
59
+ "default" => proc { |obj, url| Validate.url(obj, url) }
60
+ },
61
+ ActivityStreams2::Properties::ORIGIN => {
62
+ "default" => proc { |obj, url| Validate.url(obj, url) }
63
+ },
64
+ ActivityStreams2::Properties::TARGET => {
65
+ "default" => proc { |obj, url| Validate.url(obj, url) }
66
+ },
67
+ NotifyProperties::ITEM => {
68
+ "default" => proc { |obj, url| Validate.url(obj, url) }
69
+ }
70
+ }
71
+ },
72
+ ActivityStreams2::Properties::TYPE => {
73
+ "default" => proc { |obj, value| Validate.type_checker(obj, value) },
74
+ "context" => {
75
+ ActivityStreams2::Properties::ACTOR => {
76
+ "default" => Validate.one_of([
77
+ ActivityStreams2::ActivityStreamsTypes::SERVICE,
78
+ ActivityStreams2::ActivityStreamsTypes::APPLICATION,
79
+ ActivityStreams2::ActivityStreamsTypes::GROUP,
80
+ ActivityStreams2::ActivityStreamsTypes::ORGANIZATION,
81
+ ActivityStreams2::ActivityStreamsTypes::PERSON
82
+ ])
83
+ },
84
+ ActivityStreams2::Properties::OBJECT => {
85
+ "default" => Validate.at_least_one_of(ActivityStreams2::ACTIVITY_STREAMS_OBJECTS)
86
+ },
87
+ ActivityStreams2::Properties::CONTEXT => {
88
+ "default" => Validate.at_least_one_of(ActivityStreams2::ACTIVITY_STREAMS_OBJECTS)
89
+ },
90
+ NotifyProperties::ITEM => {
91
+ "default" => Validate.at_least_one_of(ActivityStreams2::ACTIVITY_STREAMS_OBJECTS + [NotifyTypes::ABOUT_PAGE])
92
+ }
93
+ }
94
+ },
95
+ NotifyProperties::CITE_AS => {
96
+ "default" => proc { |obj, url| Validate.url(obj, url) }
97
+ },
98
+ NotifyProperties::INBOX => {
99
+ "default" => proc { |obj, url| Validate.url(obj, url) }
100
+ },
101
+ ActivityStreams2::Properties::IN_REPLY_TO => {
102
+ "default" => proc { |obj, uri| Validate.absolute_uri(obj, uri) }
103
+ },
104
+ ActivityStreams2::Properties::SUBJECT_TRIPLE => {
105
+ "default" => proc { |obj, uri| Validate.absolute_uri(obj, uri) }
106
+ },
107
+ ActivityStreams2::Properties::OBJECT_TRIPLE => {
108
+ "default" => proc { |obj, uri| Validate.absolute_uri(obj, uri) }
109
+ },
110
+ ActivityStreams2::Properties::RELATIONSHIP_TRIPLE => {
111
+ "default" => proc { |obj, uri| Validate.absolute_uri(obj, uri) }
112
+ }
113
+ }.freeze
114
+
115
+ # Default Validator object for all pattern types
116
+ VALIDATORS = Validate::Validator.new(VALIDATION_RULES)
117
+
118
+ # Base class from which all Notify objects extend.
119
+ #
120
+ # There are two kinds of Notify objects:
121
+ #
122
+ # 1. Patterns, which are the notifications themselves
123
+ # 2. Pattern Parts, which are nested elements in the Patterns, such as objects, contexts, actors, etc
124
+ #
125
+ # This class forms the basis for both of those types, and provides essential services,
126
+ # such as construction, accessors and validation, as well as supporting the essential
127
+ # properties "id" and "type"
128
+ class NotifyBase
129
+ attr_reader :validate_stream_on_construct, :validate_properties, :validators, :properties_by_reference
130
+
131
+ # Base constructor that all subclasses should call
132
+ #
133
+ # @param stream [ActivityStreams2::ActivityStream, Hash] The activity stream object, or a hash from which one can be created
134
+ # @param validate_stream_on_construct [Boolean] should the incoming stream be validated at construction-time
135
+ # @param validate_properties [Boolean] should individual properties be validated as they are set
136
+ # @param validators [Validate::Validator] the validator object for this class and all nested elements
137
+ # @param validation_context [String, Array] the context in which this object is being validated
138
+ # @param properties_by_reference [Boolean] should properties be get and set by reference (the default) or by value
139
+ def initialize(stream: nil, validate_stream_on_construct: true, validate_properties: true,
140
+ validators: nil, validation_context: nil, properties_by_reference: true)
141
+ @validate_stream_on_construct = validate_stream_on_construct
142
+ @validate_properties = validate_properties
143
+ @validators = validators || VALIDATORS
144
+ @validation_context = validation_context
145
+ @properties_by_reference = properties_by_reference
146
+ validate_now = false
147
+
148
+ if stream.nil?
149
+ @stream = ActivityStreams2::ActivityStream.new
150
+ elsif stream.is_a?(Hash)
151
+ validate_now = validate_stream_on_construct
152
+ @stream = ActivityStreams2::ActivityStream.new(stream)
153
+ else
154
+ validate_now = validate_stream_on_construct
155
+ @stream = stream
156
+ end
157
+
158
+ if @stream.get_property(ActivityStreams2::Properties::ID).nil?
159
+ @stream.set_property(ActivityStreams2::Properties::ID, "urn:uuid:#{SecureRandom.hex}")
160
+ end
161
+
162
+ validate if validate_now
163
+ end
164
+
165
+ # The underlying ActivityStream object, excluding the JSON-LD @context
166
+ #
167
+ # @return [Hash] the document hash
168
+ def doc
169
+ @stream.doc
170
+ end
171
+
172
+ # The id of the object
173
+ #
174
+ # @return [String] the id
175
+ def id
176
+ get_property(ActivityStreams2::Properties::ID)
177
+ end
178
+
179
+ # Set the id of the object
180
+ #
181
+ # @param value [String] the id to set
182
+ def id=(value)
183
+ set_property(ActivityStreams2::Properties::ID, value)
184
+ end
185
+
186
+ # The type of the object
187
+ #
188
+ # @return [String, Array<String>] the type
189
+ def type
190
+ get_property(ActivityStreams2::Properties::TYPE)
191
+ end
192
+
193
+ # Set the type of the object
194
+ #
195
+ # @param types [String, Array<String>] the type(s) to set
196
+ def type=(types)
197
+ set_property(ActivityStreams2::Properties::TYPE, types)
198
+ end
199
+
200
+ # Generic property getter. It is strongly recommended that all accessors proxy for this method
201
+ # as this enforces by-reference/by-value accessing, and mediates directly with the underlying
202
+ # activity stream object.
203
+ #
204
+ # @param prop_name [String, Array] The property to retrieve
205
+ # @param by_reference [Boolean] Whether to retrieve by_reference or by_value
206
+ # @return [Object] the property value
207
+ def get_property(prop_name, by_reference: nil)
208
+ by_reference = @properties_by_reference if by_reference.nil?
209
+ val = @stream.get_property(prop_name)
210
+ if by_reference
211
+ val
212
+ else
213
+ val.dup rescue val # Deep copy where possible
214
+ end
215
+ end
216
+
217
+ # Generic property setter. It is strongly recommended that all accessors proxy for this method
218
+ # as this enforces by-reference/by-value accessing, and mediates directly with the underlying
219
+ # activity stream object.
220
+ #
221
+ # @param prop_name [String, Array] The property to set
222
+ # @param value [Object] The value to set
223
+ # @param by_reference [Boolean] Whether to set by_reference or by_value
224
+ def set_property(prop_name, value, by_reference: nil)
225
+ by_reference = @properties_by_reference if by_reference.nil?
226
+ validate_property(prop_name, value)
227
+ value = value.dup rescue value unless by_reference # Deep copy where possible
228
+ @stream.set_property(prop_name, value)
229
+ end
230
+
231
+ # Validate the object. This provides the basic validation on id and type.
232
+ # Subclasses should override this method with their own validation, and call this method via super first to ensure
233
+ # the basic properties are validated.
234
+ #
235
+ # @return [Boolean] true or raise a ValidationError if there are errors
236
+ def validate
237
+ ve = ValidationError.new
238
+
239
+ required_and_validate(ve, ActivityStreams2::Properties::ID, id)
240
+ required_and_validate(ve, ActivityStreams2::Properties::TYPE, type)
241
+
242
+ raise ve if ve.has_errors?
243
+ true
244
+ end
245
+
246
+ # Validate a single property. This is used internally by set_property.
247
+ #
248
+ # If the object has validate_properties set to false then that behaviour may be overridden by setting force_validate to true
249
+ #
250
+ # The validator applied to the property will be determined according to the validators property of the object
251
+ # and the validation_context of the object.
252
+ #
253
+ # @param prop_name [String, Array] The property to validate
254
+ # @param value [Object] the value to validate
255
+ # @param force_validate [Boolean] whether to validate anyway, even if property validation is turned off at the object level
256
+ # @param raise_error [Boolean] raise an exception on validation failure, or return a tuple with the result
257
+ # @return [Array] A tuple of whether validation was successful, and the error message if it was not
258
+ def validate_property(prop_name, value, force_validate: false, raise_error: true)
259
+ return [true, ""] if value.nil?
260
+ if @validate_properties || force_validate
261
+ validator = @validators.get(prop_name, @validation_context)
262
+ if validator
263
+ begin
264
+ validator.call(self, value)
265
+ rescue ArgumentError => ve
266
+ if raise_error
267
+ raise ve
268
+ else
269
+ return [false, ve.message]
270
+ end
271
+ end
272
+ end
273
+ end
274
+ [true, ""]
275
+ end
276
+
277
+ # Force validate the property and if an error is found, add it to the validation error
278
+ #
279
+ # @param ve [ValidationError] the validation error to add to
280
+ # @param prop_name [String, Array] the property name
281
+ # @param value [Object] the value to validate
282
+ def register_property_validation_error(ve, prop_name, value)
283
+ success, msg = validate_property(prop_name, value, force_validate: true, raise_error: false)
284
+ ve.add_error(prop_name, msg) unless success
285
+ end
286
+
287
+ # Add a required error to the validation error if the value is nil
288
+ #
289
+ # @param ve [ValidationError] The validation error to which to add the message
290
+ # @param prop_name [String, Array] The property to check
291
+ # @param value [Object] The value
292
+ def required(ve, prop_name, value)
293
+ if value.nil?
294
+ pn = prop_name.is_a?(Array) ? prop_name[0] : prop_name
295
+ ve.add_error(prop_name, Validate::REQUIRED_MESSAGE % pn)
296
+ end
297
+ end
298
+
299
+ # Add a required error to the validation error if the value is nil, and then validate the value if not.
300
+ #
301
+ # Any error messages are added to the ValidationError object
302
+ #
303
+ # @param ve [ValidationError] the validation error to which to add the message
304
+ # @param prop_name [String, Array] The property to check
305
+ # @param value [Object] the value to check
306
+ def required_and_validate(ve, prop_name, value)
307
+ if value.nil?
308
+ pn = prop_name.is_a?(Array) ? prop_name[0] : prop_name
309
+ ve.add_error(prop_name, Validate::REQUIRED_MESSAGE % pn)
310
+ else
311
+ if value.is_a?(NotifyBase)
312
+ begin
313
+ value.validate
314
+ rescue ValidationError => subve
315
+ ve.add_nested_errors(prop_name, subve)
316
+ end
317
+ else
318
+ register_property_validation_error(ve, prop_name, value)
319
+ end
320
+ end
321
+ end
322
+
323
+ # Validate the value if it is not nil, but do not raise a validation error if it is nil
324
+ #
325
+ # @param ve [ValidationError] the validation error to add to
326
+ # @param prop_name [String, Array] the property name
327
+ # @param value [Object] the value to validate
328
+ def optional_and_validate(ve, prop_name, value)
329
+ if value
330
+ if value.is_a?(NotifyBase)
331
+ begin
332
+ value.validate
333
+ rescue ValidationError => subve
334
+ ve.add_nested_errors(prop_name, subve)
335
+ end
336
+ else
337
+ register_property_validation_error(ve, prop_name, value)
338
+ end
339
+ end
340
+ end
341
+
342
+ # Get the notification pattern as JSON-LD
343
+ #
344
+ # @return [Hash] JSON-LD representation of the pattern
345
+ def to_jsonld
346
+ @stream.to_jsonld
347
+ end
348
+ end
349
+
350
+ # Base class for all notification patterns
351
+ class NotifyPattern < NotifyBase
352
+ # The type of the pattern. This should be overridden by subclasses, otherwise defaults to Object
353
+ def self.type_constant
354
+ ActivityStreams2::ActivityStreamsTypes::OBJECT
355
+ end
356
+
357
+ # Constructor for the NotifyPattern
358
+ #
359
+ # This constructor will ensure that the pattern has its mandated type in the type property
360
+ #
361
+ # @param stream [ActivityStreams2::ActivityStream, Hash] The activity stream object, or a hash from which one can be created
362
+ # @param validate_stream_on_construct [Boolean] should the incoming stream be validated at construction-time
363
+ # @param validate_properties [Boolean] should individual properties be validated as they are set
364
+ # @param validators [Validate::Validator] the validator object for this class and all nested elements
365
+ # @param validation_context [String, Array] the context in which this object is being validated
366
+ # @param properties_by_reference [Boolean] should properties be get and set by reference (the default) or by value
367
+ def initialize(stream: nil, validate_stream_on_construct: true, validate_properties: true,
368
+ validators: nil, validation_context: nil, properties_by_reference: true)
369
+ super(stream: stream, validate_stream_on_construct: validate_stream_on_construct,
370
+ validate_properties: validate_properties, validators: validators,
371
+ validation_context: validation_context, properties_by_reference: properties_by_reference)
372
+ ensure_type_contains(self.class.type_constant)
373
+ end
374
+
375
+ # Ensure that the type field contains the given types
376
+ #
377
+ # @param types [String, Array<String>] the types to ensure are present
378
+ def ensure_type_contains(types)
379
+ existing = @stream.get_property(ActivityStreams2::Properties::TYPE)
380
+ if existing.nil?
381
+ set_property(ActivityStreams2::Properties::TYPE, types)
382
+ else
383
+ existing = [existing] unless existing.is_a?(Array)
384
+ types = [types] unless types.is_a?(Array)
385
+ types.each do |t|
386
+ existing << t unless existing.include?(t)
387
+ end
388
+ existing = existing.length == 1 ? existing[0] : existing
389
+ set_property(ActivityStreams2::Properties::TYPE, existing)
390
+ end
391
+ end
392
+
393
+ # Get the origin property of the notification
394
+ #
395
+ # @return [NotifyService, nil] the origin service
396
+ def origin
397
+ o = get_property(ActivityStreams2::Properties::ORIGIN)
398
+ if o
399
+ NotifyService.new(stream: o, validate_stream_on_construct: false,
400
+ validate_properties: @validate_properties, validators: @validators,
401
+ validation_context: ActivityStreams2::Properties::ORIGIN,
402
+ properties_by_reference: @properties_by_reference)
403
+ end
404
+ end
405
+
406
+ # Set the origin property of the notification
407
+ #
408
+ # @param value [NotifyService] the origin service to set
409
+ def origin=(value)
410
+ set_property(ActivityStreams2::Properties::ORIGIN, value.doc)
411
+ end
412
+
413
+ # Get the target property of the notification
414
+ #
415
+ # @return [NotifyService, nil] the target service
416
+ def target
417
+ t = get_property(ActivityStreams2::Properties::TARGET)
418
+ if t
419
+ NotifyService.new(stream: t, validate_stream_on_construct: false,
420
+ validate_properties: @validate_properties, validators: @validators,
421
+ validation_context: ActivityStreams2::Properties::TARGET,
422
+ properties_by_reference: @properties_by_reference)
423
+ end
424
+ end
425
+
426
+ # Set the target property of the notification
427
+ #
428
+ # @param value [NotifyService] the target service to set
429
+ def target=(value)
430
+ set_property(ActivityStreams2::Properties::TARGET, value.doc)
431
+ end
432
+
433
+ # Get the object property of the notification
434
+ #
435
+ # @return [NotifyObject, nil] the object
436
+ def object
437
+ o = get_property(ActivityStreams2::Properties::OBJECT)
438
+ if o
439
+ NotifyObject.new(stream: o, validate_stream_on_construct: false,
440
+ validate_properties: @validate_properties, validators: @validators,
441
+ validation_context: ActivityStreams2::Properties::OBJECT,
442
+ properties_by_reference: @properties_by_reference)
443
+ end
444
+ end
445
+
446
+ # Set the object property of the notification
447
+ #
448
+ # @param value [NotifyObject] the object to set
449
+ def object=(value)
450
+ set_property(ActivityStreams2::Properties::OBJECT, value.doc)
451
+ end
452
+
453
+ # Get the inReplyTo property of the notification
454
+ #
455
+ # @return [String] the inReplyTo value
456
+ def in_reply_to
457
+ get_property(ActivityStreams2::Properties::IN_REPLY_TO)
458
+ end
459
+
460
+ # Set the inReplyTo property of the notification
461
+ #
462
+ # @param value [String] the inReplyTo value to set
463
+ def in_reply_to=(value)
464
+ set_property(ActivityStreams2::Properties::IN_REPLY_TO, value)
465
+ end
466
+
467
+ # Get the actor property of the notification
468
+ #
469
+ # @return [NotifyActor, nil] the actor
470
+ def actor
471
+ a = get_property(ActivityStreams2::Properties::ACTOR)
472
+ if a
473
+ NotifyActor.new(stream: a, validate_stream_on_construct: false,
474
+ validate_properties: @validate_properties, validators: @validators,
475
+ validation_context: ActivityStreams2::Properties::ACTOR,
476
+ properties_by_reference: @properties_by_reference)
477
+ end
478
+ end
479
+
480
+ # Set the actor property of the notification
481
+ #
482
+ # @param value [NotifyActor] the actor to set
483
+ def actor=(value)
484
+ set_property(ActivityStreams2::Properties::ACTOR, value.doc)
485
+ end
486
+
487
+ # Get the context property of the notification
488
+ #
489
+ # @return [NotifyObject, nil] the context
490
+ def context
491
+ c = get_property(ActivityStreams2::Properties::CONTEXT)
492
+ if c
493
+ NotifyObject.new(stream: c, validate_stream_on_construct: false,
494
+ validate_properties: @validate_properties, validators: @validators,
495
+ validation_context: ActivityStreams2::Properties::CONTEXT,
496
+ properties_by_reference: @properties_by_reference)
497
+ end
498
+ end
499
+
500
+ # Set the context property of the notification
501
+ #
502
+ # @param value [NotifyObject] the context to set
503
+ def context=(value)
504
+ set_property(ActivityStreams2::Properties::CONTEXT, value.doc)
505
+ end
506
+
507
+ # Base validator for all notification patterns. This extends the validate function on the superclass.
508
+ #
509
+ # In addition to the base class's constraints, this applies the following validation:
510
+ #
511
+ # * The origin, target and object properties are required and must be valid
512
+ # * The actor inReplyTo and context properties are optional, but if present must be valid
513
+ #
514
+ # @return [Boolean] true if valid, otherwise raises ValidationError
515
+ def validate
516
+ ve = ValidationError.new
517
+ begin
518
+ super
519
+ rescue ValidationError => superve
520
+ ve = superve
521
+ end
522
+
523
+ required_and_validate(ve, ActivityStreams2::Properties::ORIGIN, origin)
524
+ required_and_validate(ve, ActivityStreams2::Properties::TARGET, target)
525
+ required_and_validate(ve, ActivityStreams2::Properties::OBJECT, object)
526
+ optional_and_validate(ve, ActivityStreams2::Properties::ACTOR, actor)
527
+ optional_and_validate(ve, ActivityStreams2::Properties::IN_REPLY_TO, in_reply_to)
528
+ optional_and_validate(ve, ActivityStreams2::Properties::CONTEXT, context)
529
+
530
+ raise ve if ve.has_errors?
531
+ true
532
+ end
533
+ end
534
+
535
+ # Base class for all pattern parts, such as objects, contexts, actors, etc
536
+ #
537
+ # If there is a default type specified, and a type is not given at construction, then
538
+ # the default type will be added
539
+ class NotifyPatternPart < NotifyBase
540
+ # The default type for this object, if none is provided on construction
541
+ def self.default_type
542
+ nil
543
+ end
544
+
545
+ # The list of types that are permissible for this object. If the list is empty, then any type is allowed
546
+ def self.allowed_types
547
+ []
548
+ end
549
+
550
+ # Constructor for the NotifyPatternPart
551
+ #
552
+ # If there is a default type specified, and a type is not given at construction, then
553
+ # the default type will be added
554
+ #
555
+ # @param stream [ActivityStreams2::ActivityStream, Hash] The activity stream object, or a hash from which one can be created
556
+ # @param validate_stream_on_construct [Boolean] should the incoming stream be validated at construction-time
557
+ # @param validate_properties [Boolean] should individual properties be validated as they are set
558
+ # @param validators [Validate::Validator] the validator object for this class and all nested elements
559
+ # @param validation_context [String, Array] the context in which this object is being validated
560
+ # @param properties_by_reference [Boolean] should properties be get and set by reference (the default) or by value
561
+ def initialize(stream: nil, validate_stream_on_construct: true, validate_properties: true,
562
+ validators: nil, validation_context: nil, properties_by_reference: true)
563
+ super(stream: stream, validate_stream_on_construct: validate_stream_on_construct,
564
+ validate_properties: validate_properties, validators: validators,
565
+ validation_context: validation_context, properties_by_reference: properties_by_reference)
566
+ self.type = self.class.default_type if self.class.default_type && type.nil?
567
+ end
568
+
569
+ # Get the allowed types for this object
570
+ #
571
+ # @return [Array<String>] the allowed types
572
+ def allowed_types
573
+ self.class.allowed_types
574
+ end
575
+
576
+ # Set the type of the object, and validate that it is one of the allowed types if present
577
+ #
578
+ # @param types [String, Array<String>] the type(s) to set
579
+ def type=(types)
580
+ types = [types] unless types.is_a?(Array)
581
+
582
+ if !allowed_types.empty?
583
+ types.each do |t|
584
+ unless allowed_types.include?(t)
585
+ raise ArgumentError, "Type value #{t} is not one of the permitted values"
586
+ end
587
+ end
588
+ end
589
+
590
+ # keep single values as single values, not arrays
591
+ types = types.length == 1 ? types[0] : types
592
+
593
+ set_property(ActivityStreams2::Properties::TYPE, types)
594
+ end
595
+ end
596
+
597
+ # Default class to represent a service in the COAR Notify pattern.
598
+ #
599
+ # Services are used to represent origin and target properties in the notification patterns
600
+ #
601
+ # Specific patterns may need to extend this class to provide their specific behaviours and validation
602
+ class NotifyService < NotifyPatternPart
603
+ # The default type for a service is Service, but the type can be set to any value
604
+ def self.default_type
605
+ ActivityStreams2::ActivityStreamsTypes::SERVICE
606
+ end
607
+
608
+ # Get the inbox property of the service
609
+ #
610
+ # @return [String] the inbox URL
611
+ def inbox
612
+ get_property(NotifyProperties::INBOX)
613
+ end
614
+
615
+ # Set the inbox property of the service
616
+ #
617
+ # @param value [String] the inbox URL to set
618
+ def inbox=(value)
619
+ set_property(NotifyProperties::INBOX, value)
620
+ end
621
+ end
622
+
623
+ # Default class to represent an object in the COAR Notify pattern. Objects can be used for object or context properties
624
+ # in notify patterns
625
+ #
626
+ # Specific patterns may need to extend this class to provide their specific behaviours and validation
627
+ class NotifyObject < NotifyPatternPart
628
+ # Get the ietf:cite-as property of the object
629
+ #
630
+ # @return [String] the cite-as value
631
+ def cite_as
632
+ get_property(NotifyProperties::CITE_AS)
633
+ end
634
+
635
+ # Set the ietf:cite-as property of the object
636
+ #
637
+ # @param value [String] the cite-as value to set
638
+ def cite_as=(value)
639
+ set_property(NotifyProperties::CITE_AS, value)
640
+ end
641
+
642
+ # Get the ietf:item property of the object
643
+ #
644
+ # @return [NotifyItem, nil] the item
645
+ def item
646
+ i = get_property(NotifyProperties::ITEM)
647
+ if i
648
+ NotifyItem.new(stream: i, validate_stream_on_construct: false,
649
+ validate_properties: @validate_properties, validators: @validators,
650
+ validation_context: NotifyProperties::ITEM,
651
+ properties_by_reference: @properties_by_reference)
652
+ end
653
+ end
654
+
655
+ # Set the ietf:item property of the object
656
+ #
657
+ # @param value [NotifyItem] the item to set
658
+ def item=(value)
659
+ set_property(NotifyProperties::ITEM, value)
660
+ end
661
+
662
+ # Get object, relationship and subject properties as a relationship triple
663
+ #
664
+ # @return [Array<String>] array of [object, relationship, subject]
665
+ def triple
666
+ obj = get_property(ActivityStreams2::Properties::OBJECT_TRIPLE)
667
+ rel = get_property(ActivityStreams2::Properties::RELATIONSHIP_TRIPLE)
668
+ subj = get_property(ActivityStreams2::Properties::SUBJECT_TRIPLE)
669
+ [obj, rel, subj]
670
+ end
671
+
672
+ # Set object, relationship and subject properties as a relationship triple
673
+ #
674
+ # @param value [Array<String>] array of [object, relationship, subject]
675
+ def triple=(value)
676
+ obj, rel, subj = value
677
+ set_property(ActivityStreams2::Properties::OBJECT_TRIPLE, obj)
678
+ set_property(ActivityStreams2::Properties::RELATIONSHIP_TRIPLE, rel)
679
+ set_property(ActivityStreams2::Properties::SUBJECT_TRIPLE, subj)
680
+ end
681
+
682
+ # Validate the object. This overrides the base validation, as objects only absolutely require an id property,
683
+ # so the base requirement for a type is relaxed.
684
+ #
685
+ # @return [Boolean] true if valid, otherwise raises ValidationError
686
+ def validate
687
+ ve = ValidationError.new
688
+
689
+ required_and_validate(ve, ActivityStreams2::Properties::ID, id)
690
+
691
+ raise ve if ve.has_errors?
692
+ true
693
+ end
694
+ end
695
+
696
+ # Default class to represents an actor in the COAR Notify pattern.
697
+ # Actors are used to represent the actor property in the notification patterns
698
+ #
699
+ # Specific patterns may need to extend this class to provide their specific behaviours and validation
700
+ class NotifyActor < NotifyPatternPart
701
+ # Default type is Service, but can also be set as any one of the other allowed types
702
+ def self.default_type
703
+ ActivityStreams2::ActivityStreamsTypes::SERVICE
704
+ end
705
+
706
+ # The allowed types for an actor: Service, Application, Group, Organisation, Person
707
+ def self.allowed_types
708
+ [
709
+ ActivityStreams2::ActivityStreamsTypes::SERVICE,
710
+ ActivityStreams2::ActivityStreamsTypes::APPLICATION,
711
+ ActivityStreams2::ActivityStreamsTypes::GROUP,
712
+ ActivityStreams2::ActivityStreamsTypes::ORGANIZATION,
713
+ ActivityStreams2::ActivityStreamsTypes::PERSON
714
+ ]
715
+ end
716
+
717
+ # Get the name property of the actor
718
+ #
719
+ # @return [String] the name
720
+ def name
721
+ get_property(NotifyProperties::NAME)
722
+ end
723
+
724
+ # Set the name property of the actor
725
+ #
726
+ # @param value [String] the name to set
727
+ def name=(value)
728
+ set_property(NotifyProperties::NAME, value)
729
+ end
730
+ end
731
+
732
+ # Default class to represent an item in the COAR Notify pattern.
733
+ # Items are used to represent the ietf:item property in the notification patterns
734
+ #
735
+ # Specific patterns may need to extend this class to provide their specific behaviours and validation
736
+ class NotifyItem < NotifyPatternPart
737
+ # Get the mediaType property of the item
738
+ #
739
+ # @return [String] the media type
740
+ def media_type
741
+ get_property(NotifyProperties::MEDIA_TYPE)
742
+ end
743
+
744
+ # Set the mediaType property of the item
745
+ #
746
+ # @param value [String] the media type to set
747
+ def media_type=(value)
748
+ set_property(NotifyProperties::MEDIA_TYPE, value)
749
+ end
750
+
751
+ # Validate the item. This overrides the base validation, as objects only absolutely require an id property,
752
+ # so the base requirement for a type is relaxed.
753
+ #
754
+ # @return [Boolean] true if valid, otherwise raises ValidationError
755
+ def validate
756
+ ve = ValidationError.new
757
+
758
+ required_and_validate(ve, ActivityStreams2::Properties::ID, id)
759
+
760
+ raise ve if ve.has_errors?
761
+ true
762
+ end
763
+ end
764
+
765
+ # Mixins
766
+ ##########################################################
767
+
768
+ # A mixin to add to a pattern which can override the default object property to return a full
769
+ # nested pattern from the object property, rather than the default NotifyObject
770
+ #
771
+ # This mixin needs to be included first, as it overrides the object property
772
+ # of the NotifyPattern class.
773
+ #
774
+ # For example:
775
+ #
776
+ # class MySpecialPattern < NotifyPattern
777
+ # include NestedPatternObjectMixin
778
+ # end
779
+ module NestedPatternObjectMixin
780
+ # Retrieve an object as it's correctly typed pattern, falling back to a default NotifyObject if no pattern matches
781
+ #
782
+ # @return [NotifyPattern, NotifyObject, nil] the object
783
+ def object
784
+ o = get_property(ActivityStreams2::Properties::OBJECT)
785
+ if o
786
+ # Try to get the factory class if it's available
787
+ if defined?(::Coarnotify::Factory::COARNotifyFactory)
788
+ begin
789
+ nested = ::Coarnotify::Factory::COARNotifyFactory.get_by_object(o.dup,
790
+ validate_stream_on_construct: false,
791
+ validate_properties: @validate_properties,
792
+ validators: @validators,
793
+ validation_context: nil) # don't supply a validation context, as these objects are not typical nested objects
794
+ return nested if nested
795
+ rescue => e
796
+ # Fall back to generic object if factory fails
797
+ end
798
+ end
799
+
800
+ # if we are unable to construct the typed nested object, just return a generic object
801
+ NotifyObject.new(stream: o.dup, validate_stream_on_construct: false,
802
+ validate_properties: @validate_properties, validators: @validators,
803
+ validation_context: ActivityStreams2::Properties::OBJECT)
804
+ end
805
+ end
806
+
807
+ # Set the object property
808
+ #
809
+ # @param value [NotifyObject, NotifyPattern] the object to set
810
+ def object=(value)
811
+ set_property(ActivityStreams2::Properties::OBJECT, value.doc)
812
+ end
813
+ end
814
+
815
+ # Mixin to provide an API for setting and getting the summary property of a pattern
816
+ module SummaryMixin
817
+ # The summary property of the pattern
818
+ #
819
+ # @return [String] the summary
820
+ def summary
821
+ get_property(ActivityStreams2::Properties::SUMMARY)
822
+ end
823
+
824
+ # Set the summary property of the pattern
825
+ #
826
+ # @param summary [String] the summary to set
827
+ def summary=(summary)
828
+ set_property(ActivityStreams2::Properties::SUMMARY, summary)
829
+ end
830
+ end
831
+ end
832
+ end
833
+ end