diaspora_federation 0.0.12 → 0.0.13
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/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
|