diaspora_federation 0.0.12 → 0.0.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/diaspora_federation.rb +103 -18
- data/lib/diaspora_federation/discovery/discovery.rb +1 -1
- data/lib/diaspora_federation/discovery/h_card.rb +4 -5
- data/lib/diaspora_federation/discovery/host_meta.rb +1 -1
- data/lib/diaspora_federation/discovery/web_finger.rb +8 -8
- data/lib/diaspora_federation/discovery/xrd_document.rb +6 -7
- data/lib/diaspora_federation/entities.rb +21 -10
- data/lib/diaspora_federation/entities/account_deletion.rb +7 -3
- data/lib/diaspora_federation/entities/comment.rb +13 -10
- data/lib/diaspora_federation/entities/contact.rb +29 -0
- data/lib/diaspora_federation/entities/conversation.rb +5 -6
- data/lib/diaspora_federation/entities/like.rb +10 -18
- data/lib/diaspora_federation/entities/message.rb +6 -12
- data/lib/diaspora_federation/entities/participation.rb +8 -16
- data/lib/diaspora_federation/entities/person.rb +6 -2
- data/lib/diaspora_federation/entities/photo.rb +3 -3
- data/lib/diaspora_federation/entities/poll_participation.rb +6 -12
- data/lib/diaspora_federation/entities/post.rb +37 -0
- data/lib/diaspora_federation/entities/profile.rb +7 -3
- data/lib/diaspora_federation/entities/relayable.rb +169 -65
- data/lib/diaspora_federation/entities/relayable_retraction.rb +33 -32
- data/lib/diaspora_federation/entities/request.rb +20 -6
- data/lib/diaspora_federation/entities/reshare.rb +5 -27
- data/lib/diaspora_federation/entities/retraction.rb +6 -6
- data/lib/diaspora_federation/entities/signed_retraction.rb +32 -26
- data/lib/diaspora_federation/entities/status_message.rb +2 -22
- data/lib/diaspora_federation/entity.rb +137 -38
- data/lib/diaspora_federation/federation.rb +9 -0
- data/lib/diaspora_federation/federation/fetcher.rb +26 -0
- data/lib/diaspora_federation/federation/receiver.rb +41 -0
- data/lib/diaspora_federation/federation/receiver/abstract_receiver.rb +35 -0
- data/lib/diaspora_federation/federation/receiver/exceptions.rb +13 -0
- data/lib/diaspora_federation/federation/receiver/private.rb +15 -0
- data/lib/diaspora_federation/federation/receiver/public.rb +9 -0
- data/lib/diaspora_federation/federation/sender.rb +33 -0
- data/lib/diaspora_federation/federation/sender/hydra_wrapper.rb +92 -0
- data/lib/diaspora_federation/{fetcher.rb → http_client.rb} +6 -6
- data/lib/diaspora_federation/properties_dsl.rb +51 -14
- data/lib/diaspora_federation/salmon.rb +2 -1
- data/lib/diaspora_federation/salmon/aes.rb +1 -1
- data/lib/diaspora_federation/salmon/encrypted_magic_envelope.rb +61 -0
- data/lib/diaspora_federation/salmon/encrypted_slap.rb +69 -50
- data/lib/diaspora_federation/salmon/exceptions.rb +8 -14
- data/lib/diaspora_federation/salmon/magic_envelope.rb +80 -39
- data/lib/diaspora_federation/salmon/slap.rb +20 -51
- data/lib/diaspora_federation/salmon/xml_payload.rb +5 -104
- data/lib/diaspora_federation/validators.rb +22 -16
- data/lib/diaspora_federation/validators/account_deletion_validator.rb +1 -1
- data/lib/diaspora_federation/validators/comment_validator.rb +0 -4
- data/lib/diaspora_federation/validators/contact_validator.rb +13 -0
- data/lib/diaspora_federation/validators/conversation_validator.rb +2 -2
- data/lib/diaspora_federation/validators/like_validator.rb +1 -3
- data/lib/diaspora_federation/validators/message_validator.rb +0 -4
- data/lib/diaspora_federation/validators/participation_validator.rb +1 -5
- data/lib/diaspora_federation/validators/person_validator.rb +1 -1
- data/lib/diaspora_federation/validators/photo_validator.rb +2 -2
- data/lib/diaspora_federation/validators/poll_participation_validator.rb +0 -4
- data/lib/diaspora_federation/validators/profile_validator.rb +1 -1
- data/lib/diaspora_federation/validators/relayable_retraction_validator.rb +1 -1
- data/lib/diaspora_federation/validators/relayable_validator.rb +2 -0
- data/lib/diaspora_federation/validators/request_validator.rb +3 -2
- data/lib/diaspora_federation/validators/reshare_validator.rb +3 -3
- data/lib/diaspora_federation/validators/retraction_validator.rb +2 -2
- data/lib/diaspora_federation/validators/rules/guid.rb +16 -7
- data/lib/diaspora_federation/validators/signed_retraction_validator.rb +1 -1
- data/lib/diaspora_federation/validators/status_message_validator.rb +2 -2
- data/lib/diaspora_federation/version.rb +1 -1
- metadata +20 -11
- data/lib/diaspora_federation/receiver.rb +0 -28
- data/lib/diaspora_federation/receiver/private.rb +0 -19
- data/lib/diaspora_federation/receiver/public.rb +0 -13
- data/lib/diaspora_federation/signing.rb +0 -56
@@ -38,11 +38,11 @@ module DiasporaFederation
|
|
38
38
|
# @return [String] target type
|
39
39
|
property :target_type
|
40
40
|
|
41
|
-
# @!attribute [r]
|
41
|
+
# @!attribute [r] author
|
42
42
|
# The diaspora ID of the person who deletes a relayable
|
43
|
-
# @see Person#
|
43
|
+
# @see Person#author
|
44
44
|
# @return [String] diaspora ID
|
45
|
-
property :
|
45
|
+
property :author, xml_name: :sender_handle
|
46
46
|
|
47
47
|
# @!attribute [r] target_author_signature
|
48
48
|
# Contains a signature of the entity using the private key of the
|
@@ -53,43 +53,44 @@ module DiasporaFederation
|
|
53
53
|
# @return [String] target author signature
|
54
54
|
property :target_author_signature, default: nil
|
55
55
|
|
56
|
-
#
|
57
|
-
# @
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
56
|
+
# use only {Retraction} for receive
|
57
|
+
# @return [Retraction] instance as normal retraction
|
58
|
+
def to_retraction
|
59
|
+
Retraction.new(author: author, target_guid: target_guid, target_type: target_type)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# @param [Nokogiri::XML::Element] root_node xml nodes
|
65
|
+
# @return [Retraction] instance
|
66
|
+
def self.populate_entity(root_node)
|
67
|
+
super(root_node).to_retraction
|
66
68
|
end
|
69
|
+
private_class_method :populate_entity
|
67
70
|
|
68
|
-
#
|
69
|
-
# if the signatures are not
|
71
|
+
# It updates also the signatures with the keys of the author and the parent
|
72
|
+
# if the signatures are not there yet and if the keys are available.
|
70
73
|
#
|
71
|
-
# @
|
72
|
-
def
|
73
|
-
target_author = DiasporaFederation.callbacks.trigger(
|
74
|
-
|
75
|
-
hash[:target_type],
|
76
|
-
hash[:target_guid]
|
77
|
-
)
|
78
|
-
pkey = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_diaspora_id, hash[:diaspora_id])
|
74
|
+
# @return [Hash] xml elements with updated signatures
|
75
|
+
def xml_elements
|
76
|
+
target_author = DiasporaFederation.callbacks.trigger(:fetch_entity_author_id_by_guid, target_type, target_guid)
|
77
|
+
privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_diaspora_id, author)
|
79
78
|
|
80
|
-
|
79
|
+
super.tap do |xml_elements|
|
80
|
+
fill_required_signature(target_author, privkey, xml_elements) unless privkey.nil?
|
81
|
+
end
|
81
82
|
end
|
82
83
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
hash[:
|
89
|
-
|
84
|
+
# @param [String] target_author the author of the entity to retract
|
85
|
+
# @param [OpenSSL::PKey::RSA] privkey private key of sender
|
86
|
+
# @param [Hash] hash hash given for a signing
|
87
|
+
def fill_required_signature(target_author, privkey, hash)
|
88
|
+
if target_author == author && target_author_signature.nil?
|
89
|
+
hash[:target_author_signature] = SignedRetraction.sign_with_key(privkey, self)
|
90
|
+
elsif target_author != author && parent_author_signature.nil?
|
91
|
+
hash[:parent_author_signature] = SignedRetraction.sign_with_key(privkey, self)
|
90
92
|
end
|
91
93
|
end
|
92
|
-
private_class_method :fill_required_signature
|
93
94
|
end
|
94
95
|
end
|
95
96
|
end
|
@@ -4,18 +4,32 @@ module DiasporaFederation
|
|
4
4
|
# when he starts sharing with another user.
|
5
5
|
#
|
6
6
|
# @see Validators::RequestValidator
|
7
|
+
# @deprecated will be replaced with {Contact}
|
7
8
|
class Request < Entity
|
8
|
-
# @!attribute [r]
|
9
|
+
# @!attribute [r] author
|
9
10
|
# The diaspora ID of the person who shares his profile
|
10
|
-
# @see Person#
|
11
|
+
# @see Person#author
|
11
12
|
# @return [String] sender ID
|
12
|
-
property :
|
13
|
+
property :author, xml_name: :sender_handle
|
13
14
|
|
14
|
-
# @!attribute [r]
|
15
|
+
# @!attribute [r] recipient
|
15
16
|
# The diaspora ID of the person who will be shared with
|
16
|
-
# @see
|
17
|
+
# @see Validation::Rule::DiasporaId
|
17
18
|
# @return [String] recipient ID
|
18
|
-
property :
|
19
|
+
property :recipient, xml_name: :recipient_handle
|
20
|
+
|
21
|
+
# use only {Contact} for receive
|
22
|
+
# @return [Contact] instance as contact
|
23
|
+
def to_contact
|
24
|
+
Contact.new(author: author, recipient: recipient)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [Nokogiri::XML::Element] root_node xml nodes
|
28
|
+
# @return [Retraction] instance
|
29
|
+
def self.populate_entity(root_node)
|
30
|
+
super(root_node).to_contact
|
31
|
+
end
|
32
|
+
private_class_method :populate_entity
|
19
33
|
end
|
20
34
|
end
|
21
35
|
end
|
@@ -4,11 +4,13 @@ module DiasporaFederation
|
|
4
4
|
#
|
5
5
|
# @see Validators::ReshareValidator
|
6
6
|
class Reshare < Entity
|
7
|
-
|
7
|
+
include Post
|
8
|
+
|
9
|
+
# @!attribute [r] root_author
|
8
10
|
# The diaspora ID of the person who posted the original post
|
9
|
-
# @see Person#
|
11
|
+
# @see Person#author
|
10
12
|
# @return [String] diaspora ID
|
11
|
-
property :
|
13
|
+
property :root_author, xml_name: :root_diaspora_id
|
12
14
|
|
13
15
|
# @!attribute [r] root_guid
|
14
16
|
# guid of the original post
|
@@ -16,34 +18,10 @@ module DiasporaFederation
|
|
16
18
|
# @return [String] root guid
|
17
19
|
property :root_guid
|
18
20
|
|
19
|
-
# @!attribute [r] guid
|
20
|
-
# a random string of at least 16 chars.
|
21
|
-
# @see Validation::Rule::Guid
|
22
|
-
# @see StatusMessage#guid
|
23
|
-
# @return [String] guid
|
24
|
-
property :guid
|
25
|
-
|
26
|
-
# @!attribute [r] diaspora_id
|
27
|
-
# The diaspora ID of the person who reshares a post
|
28
|
-
# @see Person#diaspora_id
|
29
|
-
# @return [String] diaspora ID
|
30
|
-
property :diaspora_id, xml_name: :diaspora_handle
|
31
|
-
|
32
21
|
# @!attribute [r] public
|
33
22
|
# has no meaning at the moment
|
34
23
|
# @return [Boolean] public
|
35
24
|
property :public, default: true # always true? (we only reshare public posts)
|
36
|
-
|
37
|
-
# @!attribute [r] created_at
|
38
|
-
# reshare entity creation time
|
39
|
-
# @return [Time] creation time
|
40
|
-
property :created_at, default: -> { Time.now.utc }
|
41
|
-
|
42
|
-
# @!attribute [r] provider_display_name
|
43
|
-
# a string that describes a means by which a user has posted the reshare
|
44
|
-
# @see StatusMessage#provider_display_name
|
45
|
-
# @return [String] provider display name
|
46
|
-
property :provider_display_name, default: nil
|
47
25
|
end
|
48
26
|
end
|
49
27
|
end
|
@@ -4,17 +4,17 @@ module DiasporaFederation
|
|
4
4
|
#
|
5
5
|
# @see Validators::RetractionValidator
|
6
6
|
class Retraction < Entity
|
7
|
+
# @!attribute [r] author
|
8
|
+
# The diaspora ID of the person who deletes the entity
|
9
|
+
# @see Person#author
|
10
|
+
# @return [String] diaspora ID
|
11
|
+
property :author, xml_name: :diaspora_handle
|
12
|
+
|
7
13
|
# @!attribute [r] target_guid
|
8
14
|
# guid of the entity to be deleted
|
9
15
|
# @return [String] target guid
|
10
16
|
property :target_guid, xml_name: :post_guid
|
11
17
|
|
12
|
-
# @!attribute [r] diaspora_id
|
13
|
-
# The diaspora ID of the person who deletes the entity
|
14
|
-
# @see Person#diaspora_id
|
15
|
-
# @return [String] diaspora ID
|
16
|
-
property :diaspora_id, xml_name: :diaspora_handle
|
17
|
-
|
18
18
|
# @!attribute [r] target_type
|
19
19
|
# A string describing the type of the target.
|
20
20
|
# @return [String] target type
|
@@ -18,11 +18,11 @@ module DiasporaFederation
|
|
18
18
|
# @return [String] target type
|
19
19
|
property :target_type
|
20
20
|
|
21
|
-
# @!attribute [r]
|
21
|
+
# @!attribute [r] author
|
22
22
|
# The diaspora ID of the person who deletes a post
|
23
|
-
# @see Person#
|
23
|
+
# @see Person#author
|
24
24
|
# @return [String] diaspora ID
|
25
|
-
property :
|
25
|
+
property :author, xml_name: :sender_handle
|
26
26
|
|
27
27
|
# @!attribute [r] author_signature
|
28
28
|
# Contains a signature of the entity using the private key of the author of a post
|
@@ -30,36 +30,42 @@ module DiasporaFederation
|
|
30
30
|
# @return [String] author signature
|
31
31
|
property :target_author_signature, default: nil
|
32
32
|
|
33
|
-
#
|
34
|
-
# @
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
hash = to_h
|
39
|
-
SignedRetraction.update_signatures!(hash)
|
33
|
+
# use only {Retraction} for receive
|
34
|
+
# @return [Retraction] instance as normal retraction
|
35
|
+
def to_retraction
|
36
|
+
Retraction.new(author: author, target_guid: target_guid, target_type: target_type)
|
37
|
+
end
|
40
38
|
|
41
|
-
|
42
|
-
|
39
|
+
# Create signature for a retraction
|
40
|
+
# @param [OpenSSL::PKey::RSA] privkey private key of sender
|
41
|
+
# @param [SignedRetraction, RelayableRetraction] ret the retraction to sign
|
42
|
+
# @return [String] a Base64 encoded signature of the retraction with the key
|
43
|
+
def self.sign_with_key(privkey, ret)
|
44
|
+
Base64.strict_encode64(privkey.sign(Relayable::DIGEST, [ret.target_guid, ret.target_type].join(";")))
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# @param [Nokogiri::XML::Element] root_node xml nodes
|
50
|
+
# @return [Retraction] instance
|
51
|
+
def self.populate_entity(root_node)
|
52
|
+
super(root_node).to_retraction
|
43
53
|
end
|
54
|
+
private_class_method :populate_entity
|
44
55
|
|
45
|
-
#
|
46
|
-
# if the
|
56
|
+
# It updates also the signatures with the keys of the author and the parent
|
57
|
+
# if the signatures are not there yet and if the keys are available.
|
47
58
|
#
|
48
|
-
# @
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
data[:target_author_signature] = Signing.sign_with_key(apply_signable_exceptions(data), pkey) unless pkey.nil?
|
59
|
+
# @return [Hash] xml elements with updated signatures
|
60
|
+
def xml_elements
|
61
|
+
super.tap do |xml_elements|
|
62
|
+
xml_elements[:target_author_signature] = target_author_signature || sign_with_author.to_s
|
53
63
|
end
|
54
64
|
end
|
55
65
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
# @param [Hash] data hash of the retraction properties
|
60
|
-
# @return [Hash] hash copy without :diaspora_id member
|
61
|
-
def self.apply_signable_exceptions(data)
|
62
|
-
data.dup.tap {|data| data.delete(:diaspora_id) }
|
66
|
+
def sign_with_author
|
67
|
+
privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_diaspora_id, author)
|
68
|
+
SignedRetraction.sign_with_key(privkey, self) unless privkey.nil?
|
63
69
|
end
|
64
70
|
end
|
65
71
|
end
|
@@ -4,6 +4,8 @@ module DiasporaFederation
|
|
4
4
|
#
|
5
5
|
# @see Validators::StatusMessageValidator
|
6
6
|
class StatusMessage < Entity
|
7
|
+
include Post
|
8
|
+
|
7
9
|
# @!attribute [r] raw_message
|
8
10
|
# text of the status message composed by the user
|
9
11
|
# @return [String] text of the status message
|
@@ -24,32 +26,10 @@ module DiasporaFederation
|
|
24
26
|
# @return [Entities::Poll] poll
|
25
27
|
entity :poll, Entities::Poll, default: nil
|
26
28
|
|
27
|
-
# @!attribute [r] guid
|
28
|
-
# a random string of at least 16 chars.
|
29
|
-
# @see Validation::Rule::Guid
|
30
|
-
# @return [String] status message guid
|
31
|
-
property :guid
|
32
|
-
|
33
|
-
# @!attribute [r] diaspora_id
|
34
|
-
# The diaspora ID of the person who posts the status message
|
35
|
-
# @see Person#diaspora_id
|
36
|
-
# @return [String] diaspora ID
|
37
|
-
property :diaspora_id, xml_name: :diaspora_handle
|
38
|
-
|
39
29
|
# @!attribute [r] public
|
40
30
|
# shows whether the status message is visible to everyone or only to some aspects
|
41
31
|
# @return [Boolean] is it public
|
42
32
|
property :public, default: false
|
43
|
-
|
44
|
-
# @!attribute [r] created_at
|
45
|
-
# status message entity creation time
|
46
|
-
# @return [Time] creation time
|
47
|
-
property :created_at, default: -> { Time.now.utc }
|
48
|
-
|
49
|
-
# @!attribute [r] provider_display_name
|
50
|
-
# a string that describes a means by which a user has posted the status message
|
51
|
-
# @return [String] provider display name
|
52
|
-
property :provider_display_name, default: nil
|
53
33
|
end
|
54
34
|
end
|
55
35
|
end
|
@@ -47,17 +47,18 @@ module DiasporaFederation
|
|
47
47
|
# @note Attributes not defined as part of the class definition ({PropertiesDSL#property},
|
48
48
|
# {PropertiesDSL#entity}) get discarded silently.
|
49
49
|
#
|
50
|
-
# @param [Hash] data
|
50
|
+
# @param [Hash] data entity data
|
51
51
|
# @return [Entity] new instance
|
52
52
|
def initialize(data)
|
53
53
|
raise ArgumentError, "expected a Hash" unless data.is_a?(Hash)
|
54
|
-
|
54
|
+
entity_data = self.class.resolv_aliases(data)
|
55
|
+
missing_props = self.class.missing_props(entity_data)
|
55
56
|
unless missing_props.empty?
|
56
57
|
raise ArgumentError, "missing required properties: #{missing_props.join(', ')}"
|
57
58
|
end
|
58
59
|
|
59
|
-
self.class.default_values.merge(
|
60
|
-
instance_variable_set("@#{
|
60
|
+
self.class.default_values.merge(entity_data).each do |name, value|
|
61
|
+
instance_variable_set("@#{name}", nilify(value)) if setable?(name, value)
|
61
62
|
end
|
62
63
|
|
63
64
|
freeze
|
@@ -67,7 +68,7 @@ module DiasporaFederation
|
|
67
68
|
# Returns a Hash representing this Entity (attributes => values)
|
68
69
|
# @return [Hash] entity data (mostly equal to the hash used for initialization).
|
69
70
|
def to_h
|
70
|
-
self.class.
|
71
|
+
self.class.class_props.keys.each_with_object({}) do |prop, hash|
|
71
72
|
hash[prop] = public_send(prop)
|
72
73
|
end
|
73
74
|
end
|
@@ -80,10 +81,31 @@ module DiasporaFederation
|
|
80
81
|
#
|
81
82
|
# @return [Nokogiri::XML::Element] root element containing properties as child elements
|
82
83
|
def to_xml
|
83
|
-
|
84
|
+
doc = Nokogiri::XML::DocumentFragment.new(Nokogiri::XML::Document.new)
|
85
|
+
Nokogiri::XML::Element.new(self.class.entity_name, doc).tap do |root_element|
|
86
|
+
xml_elements.each do |name, value|
|
87
|
+
add_property_to_xml(doc, root_element, name, value)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Construct a new instance of the given Entity and populate the properties
|
93
|
+
# with the attributes found in the XML.
|
94
|
+
# Works recursively on nested Entities and Arrays thereof.
|
95
|
+
#
|
96
|
+
# @param [Nokogiri::XML::Element] root_node xml nodes
|
97
|
+
# @return [Entity] instance
|
98
|
+
def self.from_xml(root_node)
|
99
|
+
raise ArgumentError, "only Nokogiri::XML::Element allowed" unless root_node.instance_of?(Nokogiri::XML::Element)
|
100
|
+
raise InvalidRootNode, "'#{root_node.name}' can't be parsed by #{name}" unless root_node.name == entity_name
|
101
|
+
|
102
|
+
populate_entity(root_node)
|
84
103
|
end
|
85
104
|
|
86
105
|
# Makes an underscored, lowercase form of the class name
|
106
|
+
#
|
107
|
+
# @see .entity_class
|
108
|
+
#
|
87
109
|
# @return [String] entity name
|
88
110
|
def self.entity_name
|
89
111
|
name.rpartition("::").last.tap do |word|
|
@@ -92,29 +114,42 @@ module DiasporaFederation
|
|
92
114
|
end
|
93
115
|
end
|
94
116
|
|
117
|
+
# Transform the given String from the lowercase underscored version to a
|
118
|
+
# camelized variant and returns the Class constant.
|
119
|
+
#
|
120
|
+
# @see .entity_name
|
121
|
+
#
|
122
|
+
# @param [String] entity_name "snake_case" class name
|
123
|
+
# @return [Class] entity class
|
124
|
+
def self.entity_class(entity_name)
|
125
|
+
raise InvalidEntityName, "'#{entity_name}' is invalid" unless entity_name =~ /^[a-z]*(_[a-z]*)*$/
|
126
|
+
class_name = entity_name.sub(/^[a-z]/, &:upcase)
|
127
|
+
class_name.gsub!(/_([a-z])/) { Regexp.last_match[1].upcase }
|
128
|
+
|
129
|
+
raise UnknownEntity, "'#{class_name}' not found" unless Entities.const_defined?(class_name)
|
130
|
+
|
131
|
+
Entities.const_get(class_name)
|
132
|
+
end
|
133
|
+
|
95
134
|
private
|
96
135
|
|
97
136
|
def setable?(name, val)
|
98
|
-
|
99
|
-
return false if
|
137
|
+
type = self.class.class_props[name]
|
138
|
+
return false if type.nil? # property undefined
|
100
139
|
|
101
|
-
setable_string?(
|
140
|
+
setable_string?(type, val) || setable_nested?(type, val) || setable_multi?(type, val)
|
102
141
|
end
|
103
142
|
|
104
|
-
def setable_string?(
|
105
|
-
|
143
|
+
def setable_string?(type, val)
|
144
|
+
type == String && val.respond_to?(:to_s)
|
106
145
|
end
|
107
146
|
|
108
|
-
def setable_nested?(
|
109
|
-
|
110
|
-
(t.is_a?(Class) && t.ancestors.include?(Entity) && val.is_a?(Entity))
|
147
|
+
def setable_nested?(type, val)
|
148
|
+
type.is_a?(Class) && type.ancestors.include?(Entity) && val.is_a?(Entity)
|
111
149
|
end
|
112
150
|
|
113
|
-
def setable_multi?(
|
114
|
-
|
115
|
-
(t.instance_of?(Array) &&
|
116
|
-
val.instance_of?(Array) &&
|
117
|
-
val.all? {|v| v.instance_of?(t.first) })
|
151
|
+
def setable_multi?(type, val)
|
152
|
+
type.instance_of?(Array) && val.instance_of?(Array) && val.all? {|v| v.instance_of?(type.first) }
|
118
153
|
end
|
119
154
|
|
120
155
|
def nilify(value)
|
@@ -138,40 +173,104 @@ module DiasporaFederation
|
|
138
173
|
"Failed validation for properties: #{errors.join(' | ')}"
|
139
174
|
end
|
140
175
|
|
141
|
-
|
142
|
-
|
143
|
-
def entity_xml
|
144
|
-
doc = Nokogiri::XML::DocumentFragment.new(Nokogiri::XML::Document.new)
|
145
|
-
Nokogiri::XML::Element.new(self.class.entity_name, doc).tap do |root_element|
|
146
|
-
self.class.class_props.each do |prop_def|
|
147
|
-
add_property_to_xml(doc, prop_def, root_element)
|
148
|
-
end
|
149
|
-
end
|
176
|
+
def xml_elements
|
177
|
+
Hash[to_h.map {|name, value| [name, self.class.class_props[name] == String ? value.to_s : value] }]
|
150
178
|
end
|
151
179
|
|
152
|
-
def add_property_to_xml(doc,
|
153
|
-
|
154
|
-
|
155
|
-
if type == String
|
156
|
-
root_element << simple_node(doc, prop_def[:xml_name], property)
|
180
|
+
def add_property_to_xml(doc, root_element, name, value)
|
181
|
+
if value.is_a? String
|
182
|
+
root_element << simple_node(doc, name, value)
|
157
183
|
else
|
158
184
|
# call #to_xml for each item and append to root
|
159
|
-
[*
|
185
|
+
[*value].compact.each do |item|
|
160
186
|
root_element << item.to_xml
|
161
187
|
end
|
162
188
|
end
|
163
189
|
end
|
164
190
|
|
165
191
|
# create simple node, fill it with text and append to root
|
166
|
-
def simple_node(doc, name,
|
167
|
-
|
168
|
-
|
169
|
-
node.content =
|
192
|
+
def simple_node(doc, name, value)
|
193
|
+
xml_name = self.class.xml_names[name]
|
194
|
+
Nokogiri::XML::Element.new(xml_name ? xml_name.to_s : name, doc).tap do |node|
|
195
|
+
node.content = value unless value.empty?
|
170
196
|
end
|
171
197
|
end
|
172
198
|
|
199
|
+
# @param [Nokogiri::XML::Element] root_node xml nodes
|
200
|
+
# @return [Entity] instance
|
201
|
+
def self.populate_entity(root_node)
|
202
|
+
entity_data = Hash[class_props.map {|name, type|
|
203
|
+
[name, parse_element_from_node(name, type, root_node)]
|
204
|
+
}]
|
205
|
+
|
206
|
+
new(entity_data)
|
207
|
+
end
|
208
|
+
private_class_method :populate_entity
|
209
|
+
|
210
|
+
# @param [String] name property name to parse
|
211
|
+
# @param [Class] type target type to parse
|
212
|
+
# @param [Nokogiri::XML::Element] root_node XML node to parse
|
213
|
+
# @return [Object] parsed data
|
214
|
+
def self.parse_element_from_node(name, type, root_node)
|
215
|
+
if type == String
|
216
|
+
parse_string_from_node(name, root_node)
|
217
|
+
elsif type.instance_of?(Array)
|
218
|
+
parse_array_from_node(type.first, root_node)
|
219
|
+
elsif type.ancestors.include?(Entity)
|
220
|
+
parse_entity_from_node(type, root_node)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
private_class_method :parse_element_from_node
|
224
|
+
|
225
|
+
# create simple entry in data hash
|
226
|
+
#
|
227
|
+
# @param [String] name xml tag to parse
|
228
|
+
# @param [Nokogiri::XML::Element] root_node XML root_node to parse
|
229
|
+
# @return [String] data
|
230
|
+
def self.parse_string_from_node(name, root_node)
|
231
|
+
node = root_node.xpath(name.to_s)
|
232
|
+
node = root_node.xpath(xml_names[name].to_s) if node.empty?
|
233
|
+
node.first.text if node.any?
|
234
|
+
end
|
235
|
+
private_class_method :parse_string_from_node
|
236
|
+
|
237
|
+
# create an entry in the data hash for the nested entity
|
238
|
+
#
|
239
|
+
# @param [Class] type target type to parse
|
240
|
+
# @param [Nokogiri::XML::Element] root_node XML node to parse
|
241
|
+
# @return [Entity] parsed child entity
|
242
|
+
def self.parse_entity_from_node(type, root_node)
|
243
|
+
node = root_node.xpath(type.entity_name)
|
244
|
+
type.from_xml(node.first) if node.any?
|
245
|
+
end
|
246
|
+
private_class_method :parse_entity_from_node
|
247
|
+
|
248
|
+
# collect all nested children of that type and create an array in the data hash
|
249
|
+
#
|
250
|
+
# @param [Class] type target type to parse
|
251
|
+
# @param [Nokogiri::XML::Element] root_node XML node to parse
|
252
|
+
# @return [Array<Entity>] array with parsed child entities
|
253
|
+
def self.parse_array_from_node(type, root_node)
|
254
|
+
node = root_node.xpath(type.entity_name)
|
255
|
+
node.map {|child| type.from_xml(child) }
|
256
|
+
end
|
257
|
+
private_class_method :parse_array_from_node
|
258
|
+
|
173
259
|
# Raised, if entity is not valid
|
174
260
|
class ValidationError < RuntimeError
|
175
261
|
end
|
262
|
+
|
263
|
+
# Raised, if the root node doesn't match the class name
|
264
|
+
class InvalidRootNode < RuntimeError
|
265
|
+
end
|
266
|
+
|
267
|
+
# Raised, if the entity name in the XML is invalid
|
268
|
+
class InvalidEntityName < RuntimeError
|
269
|
+
end
|
270
|
+
|
271
|
+
# Raised, if the entity contained within the XML cannot be mapped to a
|
272
|
+
# defined {Entity} subclass.
|
273
|
+
class UnknownEntity < RuntimeError
|
274
|
+
end
|
176
275
|
end
|
177
276
|
end
|