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.
- checksums.yaml +7 -0
- data/lib/coarnotify/client.rb +88 -0
- data/lib/coarnotify/core/activity_streams2.rb +234 -0
- data/lib/coarnotify/core/notify.rb +833 -0
- data/lib/coarnotify/exceptions.rb +106 -0
- data/lib/coarnotify/factory.rb +114 -0
- data/lib/coarnotify/http.rb +148 -0
- data/lib/coarnotify/patterns/accept.rb +50 -0
- data/lib/coarnotify/patterns/announce_endorsement.rb +103 -0
- data/lib/coarnotify/patterns/announce_relationship.rb +82 -0
- data/lib/coarnotify/patterns/announce_review.rb +145 -0
- data/lib/coarnotify/patterns/announce_service_result.rb +145 -0
- data/lib/coarnotify/patterns/reject.rb +51 -0
- data/lib/coarnotify/patterns/request_endorsement.rb +83 -0
- data/lib/coarnotify/patterns/request_review.rb +71 -0
- data/lib/coarnotify/patterns/tentatively_accept.rb +51 -0
- data/lib/coarnotify/patterns/tentatively_reject.rb +40 -0
- data/lib/coarnotify/patterns/undo_offer.rb +48 -0
- data/lib/coarnotify/patterns/unprocessable_notification.rb +42 -0
- data/lib/coarnotify/server.rb +112 -0
- data/lib/coarnotify/validate.rb +256 -0
- data/lib/coarnotify/version.rb +8 -0
- data/lib/coarnotify.rb +78 -0
- metadata +121 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coarnotify
|
|
4
|
+
# Base class for all exceptions in the coarnotifyrb library
|
|
5
|
+
class NotifyException < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Exception class for validation errors.
|
|
9
|
+
#
|
|
10
|
+
# This class is designed to be thrown and caught and to collect validation errors
|
|
11
|
+
# as it passes through the validation pipeline.
|
|
12
|
+
#
|
|
13
|
+
# For example an object validator may do something like this:
|
|
14
|
+
#
|
|
15
|
+
# def validate
|
|
16
|
+
# ve = ValidationError.new
|
|
17
|
+
# ve.add_error(prop_name, "#{prop_name} is a required field")
|
|
18
|
+
# raise ve if ve.has_errors?
|
|
19
|
+
# true
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# If this is called by a subclass which is also validating, then this may be used
|
|
23
|
+
# like this:
|
|
24
|
+
#
|
|
25
|
+
# def validate
|
|
26
|
+
# ve = ValidationError.new
|
|
27
|
+
# begin
|
|
28
|
+
# super
|
|
29
|
+
# rescue ValidationError => superve
|
|
30
|
+
# ve = superve
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# ve.add_error(prop_name, "#{prop_name} is a required field")
|
|
34
|
+
# raise ve if ve.has_errors?
|
|
35
|
+
# true
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# By the time the ValidationError is finally raised to the top, it will contain
|
|
39
|
+
# all the validation errors from the various levels of validation that have been
|
|
40
|
+
# performed.
|
|
41
|
+
#
|
|
42
|
+
# The errors are stored as a multi-level hash with the keys at the top level
|
|
43
|
+
# being the fields in the data structure which have errors, and within the value
|
|
44
|
+
# for each key there are two possible keys:
|
|
45
|
+
#
|
|
46
|
+
# * errors: an array of error messages for this field
|
|
47
|
+
# * nested: a hash of further errors for nested fields
|
|
48
|
+
#
|
|
49
|
+
# {
|
|
50
|
+
# "key1" => {
|
|
51
|
+
# "errors" => ["error1", "error2"],
|
|
52
|
+
# "nested" => {
|
|
53
|
+
# "key2" => {
|
|
54
|
+
# "errors" => ["error3"]
|
|
55
|
+
# }
|
|
56
|
+
# }
|
|
57
|
+
# }
|
|
58
|
+
# }
|
|
59
|
+
class ValidationError < NotifyException
|
|
60
|
+
attr_reader :errors
|
|
61
|
+
|
|
62
|
+
# Create a new ValidationError with the given errors hash
|
|
63
|
+
#
|
|
64
|
+
# @param errors [Hash] The errors hash to initialize with
|
|
65
|
+
def initialize(errors = {})
|
|
66
|
+
super()
|
|
67
|
+
@errors = errors
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Record an error on the supplied key with the message value
|
|
71
|
+
#
|
|
72
|
+
# @param key [String] the key for which an error is to be recorded
|
|
73
|
+
# @param value [String] the error message
|
|
74
|
+
def add_error(key, value)
|
|
75
|
+
@errors[key] ||= { "errors" => [] }
|
|
76
|
+
@errors[key]["errors"] << value
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Take an existing ValidationError and add it as a nested set of errors under the supplied key
|
|
80
|
+
#
|
|
81
|
+
# @param key [String] the key under which all the nested validation errors should go
|
|
82
|
+
# @param subve [ValidationError] the existing ValidationError object
|
|
83
|
+
def add_nested_errors(key, subve)
|
|
84
|
+
@errors[key] ||= { "errors" => [] }
|
|
85
|
+
@errors[key]["nested"] ||= {}
|
|
86
|
+
|
|
87
|
+
subve.errors.each do |k, v|
|
|
88
|
+
@errors[key]["nested"][k] = v
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Are there any errors registered
|
|
93
|
+
#
|
|
94
|
+
# @return [Boolean] true if there are errors, false otherwise
|
|
95
|
+
def has_errors?
|
|
96
|
+
!@errors.empty?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# String representation of the errors
|
|
100
|
+
#
|
|
101
|
+
# @return [String] string representation of the errors hash
|
|
102
|
+
def to_s
|
|
103
|
+
@errors.to_s
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
require_relative 'core/activity_streams2'
|
|
5
|
+
require_relative 'core/notify'
|
|
6
|
+
require_relative 'patterns/accept'
|
|
7
|
+
require_relative 'patterns/announce_endorsement'
|
|
8
|
+
require_relative 'patterns/announce_relationship'
|
|
9
|
+
require_relative 'patterns/announce_review'
|
|
10
|
+
require_relative 'patterns/announce_service_result'
|
|
11
|
+
require_relative 'patterns/reject'
|
|
12
|
+
require_relative 'patterns/request_endorsement'
|
|
13
|
+
require_relative 'patterns/request_review'
|
|
14
|
+
require_relative 'patterns/tentatively_accept'
|
|
15
|
+
require_relative 'patterns/tentatively_reject'
|
|
16
|
+
require_relative 'patterns/unprocessable_notification'
|
|
17
|
+
require_relative 'patterns/undo_offer'
|
|
18
|
+
require_relative 'exceptions'
|
|
19
|
+
|
|
20
|
+
module Coarnotify
|
|
21
|
+
# Factory for producing the correct model based on the type or data within a payload
|
|
22
|
+
module Factory
|
|
23
|
+
# Factory for producing the correct model based on the type or data within a payload
|
|
24
|
+
class COARNotifyFactory
|
|
25
|
+
# The list of model classes recognised by this factory
|
|
26
|
+
MODELS = [
|
|
27
|
+
Patterns::Accept,
|
|
28
|
+
Patterns::AnnounceEndorsement,
|
|
29
|
+
Patterns::AnnounceRelationship,
|
|
30
|
+
Patterns::AnnounceReview,
|
|
31
|
+
Patterns::AnnounceServiceResult,
|
|
32
|
+
Patterns::Reject,
|
|
33
|
+
Patterns::RequestEndorsement,
|
|
34
|
+
Patterns::RequestReview,
|
|
35
|
+
Patterns::TentativelyAccept,
|
|
36
|
+
Patterns::TentativelyReject,
|
|
37
|
+
Patterns::UnprocessableNotification,
|
|
38
|
+
Patterns::UndoOffer
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
# Get the model class based on the supplied types. The returned value is the class, not an instance.
|
|
42
|
+
#
|
|
43
|
+
# This is achieved by inspecting all of the known types in MODELS, and performing the following
|
|
44
|
+
# calculation:
|
|
45
|
+
#
|
|
46
|
+
# 1. If the supplied types are a subset of the model types, then this is a candidate, keep a reference to it
|
|
47
|
+
# 2. If the candidate fit is exact (supplied types and model types are the same), return the class
|
|
48
|
+
# 3. If the class is a better fit than the last candidate, update the candidate. If the fit is exact, return the class
|
|
49
|
+
# 4. Once we have run out of models to check, return the best candidate (or nil if none found)
|
|
50
|
+
#
|
|
51
|
+
# @param incoming_types [String, Array<String>] a single type or array of types
|
|
52
|
+
# @return [Class, nil] A class representing the best fit for the supplied types, or nil if no match
|
|
53
|
+
def self.get_by_types(incoming_types)
|
|
54
|
+
incoming_types = [incoming_types] unless incoming_types.is_a?(Array)
|
|
55
|
+
|
|
56
|
+
candidate = nil
|
|
57
|
+
candidate_fit = nil
|
|
58
|
+
|
|
59
|
+
MODELS.each do |m|
|
|
60
|
+
document_types = m.type_constant
|
|
61
|
+
document_types = [document_types] unless document_types.is_a?(Array)
|
|
62
|
+
|
|
63
|
+
if document_types.to_set.subset?(incoming_types.to_set)
|
|
64
|
+
if candidate_fit.nil?
|
|
65
|
+
candidate = m
|
|
66
|
+
candidate_fit = incoming_types.length - document_types.length
|
|
67
|
+
return candidate if candidate_fit == 0
|
|
68
|
+
else
|
|
69
|
+
fit = incoming_types.length - document_types.length
|
|
70
|
+
return m if fit == 0
|
|
71
|
+
if fit.abs < candidate_fit.abs
|
|
72
|
+
candidate = m
|
|
73
|
+
candidate_fit = fit
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
candidate
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Get an instance of a model based on the data provided.
|
|
83
|
+
#
|
|
84
|
+
# Internally this calls get_by_types to determine the class to instantiate, and then creates an instance of that
|
|
85
|
+
# using the supplied options.
|
|
86
|
+
#
|
|
87
|
+
# If a model cannot be found that matches the data, a NotifyException is raised.
|
|
88
|
+
#
|
|
89
|
+
# @param data [Hash] The raw stream data to parse and instantiate around
|
|
90
|
+
# @param options [Hash] any options to pass to the object constructor
|
|
91
|
+
# @return [Core::Notify::NotifyPattern] A NotifyPattern of the correct type, wrapping the data
|
|
92
|
+
def self.get_by_object(data, **options)
|
|
93
|
+
stream = Core::ActivityStreams2::ActivityStream.new(data)
|
|
94
|
+
|
|
95
|
+
types = stream.get_property(Core::ActivityStreams2::Properties::TYPE)
|
|
96
|
+
raise NotifyException, "No type found in object" if types.nil?
|
|
97
|
+
|
|
98
|
+
klazz = get_by_types(types)
|
|
99
|
+
raise NotifyException, "No matching pattern found for types: #{types}" if klazz.nil?
|
|
100
|
+
|
|
101
|
+
klazz.new(stream: data, **options)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Register a new model with the factory
|
|
105
|
+
#
|
|
106
|
+
# @param model [Class] the model class to register
|
|
107
|
+
def self.register(model)
|
|
108
|
+
existing = get_by_types(model.type_constant)
|
|
109
|
+
MODELS.delete(existing) if existing
|
|
110
|
+
MODELS << model
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
|
|
6
|
+
module Coarnotify
|
|
7
|
+
# HTTP layer interface and default implementation using Net::HTTP
|
|
8
|
+
module Http
|
|
9
|
+
# Interface for the HTTP layer
|
|
10
|
+
#
|
|
11
|
+
# This defines the methods which need to be implemented in order for the client to fully operate
|
|
12
|
+
class HttpLayer
|
|
13
|
+
# Make an HTTP POST request to the supplied URL with the given body data, and headers
|
|
14
|
+
#
|
|
15
|
+
# args and kwargs can be used to pass implementation-specific parameters
|
|
16
|
+
#
|
|
17
|
+
# @param url [String] the request URL
|
|
18
|
+
# @param data [String] the body data
|
|
19
|
+
# @param headers [Hash] HTTP headers as a hash to include in the request
|
|
20
|
+
# @param args [Array] argument list to pass on to the implementation
|
|
21
|
+
# @param kwargs [Hash] keyword arguments to pass on to the implementation
|
|
22
|
+
# @return [HttpResponse] the HTTP response
|
|
23
|
+
def post(url, data, headers = {}, *args, **kwargs)
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Make an HTTP GET request to the supplied URL with the given headers
|
|
28
|
+
#
|
|
29
|
+
# args and kwargs can be used to pass implementation-specific parameters
|
|
30
|
+
#
|
|
31
|
+
# @param url [String] the request URL
|
|
32
|
+
# @param headers [Hash] HTTP headers as a hash to include in the request
|
|
33
|
+
# @param args [Array] argument list to pass on to the implementation
|
|
34
|
+
# @param kwargs [Hash] keyword arguments to pass on to the implementation
|
|
35
|
+
# @return [HttpResponse] the HTTP response
|
|
36
|
+
def get(url, headers = {}, *args, **kwargs)
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Interface for the HTTP response object
|
|
42
|
+
#
|
|
43
|
+
# This defines the methods which need to be implemented in order for the client to fully operate
|
|
44
|
+
class HttpResponse
|
|
45
|
+
# Get the value of a header from the response
|
|
46
|
+
#
|
|
47
|
+
# @param header_name [String] the name of the header
|
|
48
|
+
# @return [String] the header value
|
|
49
|
+
def header(header_name)
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Get the status code of the response
|
|
54
|
+
#
|
|
55
|
+
# @return [Integer] the status code
|
|
56
|
+
def status_code
|
|
57
|
+
raise NotImplementedError
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
#######################################
|
|
62
|
+
## Implementations using Net::HTTP
|
|
63
|
+
|
|
64
|
+
# Implementation of the HTTP layer using Net::HTTP. This is the default implementation
|
|
65
|
+
# used when no other implementation is supplied
|
|
66
|
+
class NetHttpLayer < HttpLayer
|
|
67
|
+
# Make an HTTP POST request to the supplied URL with the given body data, and headers
|
|
68
|
+
#
|
|
69
|
+
# args and kwargs can be used to pass additional parameters to the Net::HTTP request,
|
|
70
|
+
# such as authentication credentials, etc.
|
|
71
|
+
#
|
|
72
|
+
# @param url [String] the request URL
|
|
73
|
+
# @param data [String] the body data
|
|
74
|
+
# @param headers [Hash] HTTP headers as a hash to include in the request
|
|
75
|
+
# @param args [Array] argument list (unused in this implementation)
|
|
76
|
+
# @param kwargs [Hash] keyword arguments (unused in this implementation)
|
|
77
|
+
# @return [NetHttpResponse] the HTTP response
|
|
78
|
+
def post(url, data, headers = {}, *args, **kwargs)
|
|
79
|
+
uri = URI.parse(url)
|
|
80
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
81
|
+
http.use_ssl = (uri.scheme == 'https')
|
|
82
|
+
|
|
83
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
|
84
|
+
headers.each { |key, value| request[key] = value }
|
|
85
|
+
request.body = data
|
|
86
|
+
|
|
87
|
+
response = http.request(request)
|
|
88
|
+
NetHttpResponse.new(response)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Make an HTTP GET request to the supplied URL with the given headers
|
|
92
|
+
#
|
|
93
|
+
# args and kwargs can be used to pass additional parameters to the Net::HTTP request,
|
|
94
|
+
# such as authentication credentials, etc.
|
|
95
|
+
#
|
|
96
|
+
# @param url [String] the request URL
|
|
97
|
+
# @param headers [Hash] HTTP headers as a hash to include in the request
|
|
98
|
+
# @param args [Array] argument list (unused in this implementation)
|
|
99
|
+
# @param kwargs [Hash] keyword arguments (unused in this implementation)
|
|
100
|
+
# @return [NetHttpResponse] the HTTP response
|
|
101
|
+
def get(url, headers = {}, *args, **kwargs)
|
|
102
|
+
uri = URI.parse(url)
|
|
103
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
104
|
+
http.use_ssl = (uri.scheme == 'https')
|
|
105
|
+
|
|
106
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
|
107
|
+
headers.each { |key, value| request[key] = value }
|
|
108
|
+
|
|
109
|
+
response = http.request(request)
|
|
110
|
+
NetHttpResponse.new(response)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Implementation of the HTTP response object using Net::HTTP
|
|
115
|
+
#
|
|
116
|
+
# This wraps the Net::HTTP response object and provides the interface required by the client
|
|
117
|
+
class NetHttpResponse < HttpResponse
|
|
118
|
+
# Construct the object as a wrapper around the original Net::HTTP response object
|
|
119
|
+
#
|
|
120
|
+
# @param resp [Net::HTTPResponse] response object from Net::HTTP
|
|
121
|
+
def initialize(resp)
|
|
122
|
+
@resp = resp
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Get the value of a header from the response
|
|
126
|
+
#
|
|
127
|
+
# @param header_name [String] the name of the header
|
|
128
|
+
# @return [String] the header value
|
|
129
|
+
def header(header_name)
|
|
130
|
+
@resp[header_name]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Get the status code of the response
|
|
134
|
+
#
|
|
135
|
+
# @return [Integer] the status code
|
|
136
|
+
def status_code
|
|
137
|
+
@resp.code.to_i
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Get the original Net::HTTP response object
|
|
141
|
+
#
|
|
142
|
+
# @return [Net::HTTPResponse] the original response object
|
|
143
|
+
def net_http_response
|
|
144
|
+
@resp
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../core/notify'
|
|
4
|
+
require_relative '../core/activity_streams2'
|
|
5
|
+
require_relative '../exceptions'
|
|
6
|
+
|
|
7
|
+
module Coarnotify
|
|
8
|
+
module Patterns
|
|
9
|
+
# Pattern to represent an Accept notification
|
|
10
|
+
# https://coar-notify.net/specification/1.0.0/accept/
|
|
11
|
+
class Accept < Core::Notify::NotifyPattern
|
|
12
|
+
include Core::Notify::NestedPatternObjectMixin
|
|
13
|
+
|
|
14
|
+
# The Accept type
|
|
15
|
+
def self.type_constant
|
|
16
|
+
Core::ActivityStreams2::ActivityStreamsTypes::ACCEPT
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Validate the Accept pattern.
|
|
20
|
+
#
|
|
21
|
+
# In addition to the base validation, this:
|
|
22
|
+
#
|
|
23
|
+
# * Makes inReplyTo required
|
|
24
|
+
# * Requires the inReplyTo value to be the same as the object.id value
|
|
25
|
+
#
|
|
26
|
+
# @return [Boolean] true if valid, otherwise raises ValidationError
|
|
27
|
+
def validate
|
|
28
|
+
ve = ValidationError.new
|
|
29
|
+
begin
|
|
30
|
+
super
|
|
31
|
+
rescue ValidationError => superve
|
|
32
|
+
ve = superve
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Technically, no need to validate the value, as this is handled by the superclass,
|
|
36
|
+
# but leaving it in for completeness
|
|
37
|
+
required_and_validate(ve, Core::ActivityStreams2::Properties::IN_REPLY_TO, in_reply_to)
|
|
38
|
+
|
|
39
|
+
objid = object&.id
|
|
40
|
+
if in_reply_to != objid
|
|
41
|
+
ve.add_error(Core::ActivityStreams2::Properties::IN_REPLY_TO,
|
|
42
|
+
"Expected inReplyTo id to be the same as the nested object id. inReplyTo: #{in_reply_to}, object.id: #{objid}")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
raise ve if ve.has_errors?
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../core/notify'
|
|
4
|
+
require_relative '../core/activity_streams2'
|
|
5
|
+
|
|
6
|
+
module Coarnotify
|
|
7
|
+
module Patterns
|
|
8
|
+
# Pattern to represent an AnnounceEndorsement notification
|
|
9
|
+
class AnnounceEndorsement < Core::Notify::NotifyPattern
|
|
10
|
+
def self.type_constant
|
|
11
|
+
[Core::ActivityStreams2::ActivityStreamsTypes::ANNOUNCE, Core::Notify::NotifyTypes::ENDORSEMENT_ACTION]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Get a context specific to Announce Endorsement
|
|
15
|
+
#
|
|
16
|
+
# @return [AnnounceEndorsementContext, nil] The Announce Endorsement context object
|
|
17
|
+
def context
|
|
18
|
+
c = get_property(Core::ActivityStreams2::Properties::CONTEXT)
|
|
19
|
+
if c
|
|
20
|
+
AnnounceEndorsementContext.new(stream: c, validate_stream_on_construct: false,
|
|
21
|
+
validate_properties: @validate_properties, validators: @validators,
|
|
22
|
+
validation_context: Core::ActivityStreams2::Properties::CONTEXT,
|
|
23
|
+
properties_by_reference: @properties_by_reference)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Set the context property of the notification
|
|
28
|
+
#
|
|
29
|
+
# @param value [AnnounceEndorsementContext] the context to set
|
|
30
|
+
def context=(value)
|
|
31
|
+
set_property(Core::ActivityStreams2::Properties::CONTEXT, value.doc)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Extends the base validation to make `context` required
|
|
35
|
+
#
|
|
36
|
+
# @return [Boolean] true if valid, otherwise raises ValidationError
|
|
37
|
+
def validate
|
|
38
|
+
ve = Core::Notify::ValidationError.new
|
|
39
|
+
|
|
40
|
+
begin
|
|
41
|
+
super
|
|
42
|
+
rescue Core::Notify::ValidationError => superve
|
|
43
|
+
ve = superve
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
required_and_validate(ve, Core::ActivityStreams2::Properties::CONTEXT, context)
|
|
47
|
+
|
|
48
|
+
raise ve if ve.has_errors?
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Announce Endorsement context object, which extends the base NotifyObject
|
|
54
|
+
# to allow us to pass back a custom AnnounceEndorsementItem
|
|
55
|
+
class AnnounceEndorsementContext < Core::Notify::NotifyObject
|
|
56
|
+
# Get a custom AnnounceEndorsementItem
|
|
57
|
+
#
|
|
58
|
+
# @return [AnnounceEndorsementItem, nil] the Announce Endorsement Item
|
|
59
|
+
def item
|
|
60
|
+
i = get_property(Core::Notify::NotifyProperties::ITEM)
|
|
61
|
+
if i
|
|
62
|
+
AnnounceEndorsementItem.new(stream: i, validate_stream_on_construct: false,
|
|
63
|
+
validate_properties: @validate_properties, validators: @validators,
|
|
64
|
+
validation_context: Core::Notify::NotifyProperties::ITEM,
|
|
65
|
+
properties_by_reference: @properties_by_reference)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Set the item property
|
|
70
|
+
#
|
|
71
|
+
# @param value [AnnounceEndorsementItem] the item to set
|
|
72
|
+
def item=(value)
|
|
73
|
+
set_property(Core::Notify::NotifyProperties::ITEM, value.doc)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Announce Endorsement Item, which extends the base NotifyItem to provide
|
|
78
|
+
# additional validation
|
|
79
|
+
class AnnounceEndorsementItem < Core::Notify::NotifyItem
|
|
80
|
+
# Extends the base validation with validation custom to Announce Endorsement notifications
|
|
81
|
+
#
|
|
82
|
+
# * Adds type validation, which the base NotifyItem does not apply
|
|
83
|
+
# * Requires the mediaType value
|
|
84
|
+
#
|
|
85
|
+
# @return [Boolean] true if valid, otherwise raises a ValidationError
|
|
86
|
+
def validate
|
|
87
|
+
ve = Core::Notify::ValidationError.new
|
|
88
|
+
|
|
89
|
+
begin
|
|
90
|
+
super
|
|
91
|
+
rescue Core::Notify::ValidationError => superve
|
|
92
|
+
ve = superve
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
required_and_validate(ve, Core::ActivityStreams2::Properties::TYPE, type)
|
|
96
|
+
required(ve, Core::Notify::NotifyProperties::MEDIA_TYPE, media_type)
|
|
97
|
+
|
|
98
|
+
raise ve if ve.has_errors?
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../core/notify'
|
|
4
|
+
require_relative '../core/activity_streams2'
|
|
5
|
+
|
|
6
|
+
module Coarnotify
|
|
7
|
+
module Patterns
|
|
8
|
+
# Pattern to represent an AnnounceRelationship notification
|
|
9
|
+
class AnnounceRelationship < Core::Notify::NotifyPattern
|
|
10
|
+
def self.type_constant
|
|
11
|
+
[Core::ActivityStreams2::ActivityStreamsTypes::ANNOUNCE, Core::Notify::NotifyTypes::RELATIONSHIP_ACTION]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Custom getter to retrieve the object property as an AnnounceRelationshipObject
|
|
15
|
+
#
|
|
16
|
+
# @return [AnnounceRelationshipObject, nil] the object
|
|
17
|
+
def object
|
|
18
|
+
o = get_property(Core::ActivityStreams2::Properties::OBJECT)
|
|
19
|
+
if o
|
|
20
|
+
AnnounceRelationshipObject.new(stream: o, validate_stream_on_construct: false,
|
|
21
|
+
validate_properties: @validate_properties, validators: @validators,
|
|
22
|
+
validation_context: Core::ActivityStreams2::Properties::OBJECT,
|
|
23
|
+
properties_by_reference: @properties_by_reference)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Set the object property of the notification
|
|
28
|
+
#
|
|
29
|
+
# @param value [AnnounceRelationshipObject] the object to set
|
|
30
|
+
def object=(value)
|
|
31
|
+
set_property(Core::ActivityStreams2::Properties::OBJECT, value.doc)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Extends the base validation to make `context` required
|
|
35
|
+
#
|
|
36
|
+
# @return [Boolean] true if valid, otherwise raises ValidationError
|
|
37
|
+
def validate
|
|
38
|
+
ve = Core::Notify::ValidationError.new
|
|
39
|
+
|
|
40
|
+
begin
|
|
41
|
+
super
|
|
42
|
+
rescue Core::Notify::ValidationError => superve
|
|
43
|
+
ve = superve
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
required_and_validate(ve, Core::ActivityStreams2::Properties::CONTEXT, context)
|
|
47
|
+
|
|
48
|
+
raise ve if ve.has_errors?
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Custom object class for Announce Relationship to apply the custom validation
|
|
54
|
+
class AnnounceRelationshipObject < Core::Notify::NotifyObject
|
|
55
|
+
# Extend the base validation to include the following constraints:
|
|
56
|
+
#
|
|
57
|
+
# * The object type is required and must validate
|
|
58
|
+
# * The as:subject property is required
|
|
59
|
+
# * The as:object property is required
|
|
60
|
+
# * The as:relationship property is required
|
|
61
|
+
#
|
|
62
|
+
# @return [Boolean] true if validation passes, otherwise raise a ValidationError
|
|
63
|
+
def validate
|
|
64
|
+
ve = Core::Notify::ValidationError.new
|
|
65
|
+
|
|
66
|
+
begin
|
|
67
|
+
super
|
|
68
|
+
rescue Core::Notify::ValidationError => superve
|
|
69
|
+
ve = superve
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
required_and_validate(ve, Core::ActivityStreams2::Properties::TYPE, type)
|
|
73
|
+
required_and_validate(ve, Core::ActivityStreams2::Properties::SUBJECT_TRIPLE, subject)
|
|
74
|
+
required_and_validate(ve, Core::ActivityStreams2::Properties::OBJECT_TRIPLE, object_triple)
|
|
75
|
+
required_and_validate(ve, Core::ActivityStreams2::Properties::RELATIONSHIP_TRIPLE, relationship)
|
|
76
|
+
|
|
77
|
+
raise ve if ve.has_errors?
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|