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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/lib/diaspora_federation.rb +103 -18
  3. data/lib/diaspora_federation/discovery/discovery.rb +1 -1
  4. data/lib/diaspora_federation/discovery/h_card.rb +4 -5
  5. data/lib/diaspora_federation/discovery/host_meta.rb +1 -1
  6. data/lib/diaspora_federation/discovery/web_finger.rb +8 -8
  7. data/lib/diaspora_federation/discovery/xrd_document.rb +6 -7
  8. data/lib/diaspora_federation/entities.rb +21 -10
  9. data/lib/diaspora_federation/entities/account_deletion.rb +7 -3
  10. data/lib/diaspora_federation/entities/comment.rb +13 -10
  11. data/lib/diaspora_federation/entities/contact.rb +29 -0
  12. data/lib/diaspora_federation/entities/conversation.rb +5 -6
  13. data/lib/diaspora_federation/entities/like.rb +10 -18
  14. data/lib/diaspora_federation/entities/message.rb +6 -12
  15. data/lib/diaspora_federation/entities/participation.rb +8 -16
  16. data/lib/diaspora_federation/entities/person.rb +6 -2
  17. data/lib/diaspora_federation/entities/photo.rb +3 -3
  18. data/lib/diaspora_federation/entities/poll_participation.rb +6 -12
  19. data/lib/diaspora_federation/entities/post.rb +37 -0
  20. data/lib/diaspora_federation/entities/profile.rb +7 -3
  21. data/lib/diaspora_federation/entities/relayable.rb +169 -65
  22. data/lib/diaspora_federation/entities/relayable_retraction.rb +33 -32
  23. data/lib/diaspora_federation/entities/request.rb +20 -6
  24. data/lib/diaspora_federation/entities/reshare.rb +5 -27
  25. data/lib/diaspora_federation/entities/retraction.rb +6 -6
  26. data/lib/diaspora_federation/entities/signed_retraction.rb +32 -26
  27. data/lib/diaspora_federation/entities/status_message.rb +2 -22
  28. data/lib/diaspora_federation/entity.rb +137 -38
  29. data/lib/diaspora_federation/federation.rb +9 -0
  30. data/lib/diaspora_federation/federation/fetcher.rb +26 -0
  31. data/lib/diaspora_federation/federation/receiver.rb +41 -0
  32. data/lib/diaspora_federation/federation/receiver/abstract_receiver.rb +35 -0
  33. data/lib/diaspora_federation/federation/receiver/exceptions.rb +13 -0
  34. data/lib/diaspora_federation/federation/receiver/private.rb +15 -0
  35. data/lib/diaspora_federation/federation/receiver/public.rb +9 -0
  36. data/lib/diaspora_federation/federation/sender.rb +33 -0
  37. data/lib/diaspora_federation/federation/sender/hydra_wrapper.rb +92 -0
  38. data/lib/diaspora_federation/{fetcher.rb → http_client.rb} +6 -6
  39. data/lib/diaspora_federation/properties_dsl.rb +51 -14
  40. data/lib/diaspora_federation/salmon.rb +2 -1
  41. data/lib/diaspora_federation/salmon/aes.rb +1 -1
  42. data/lib/diaspora_federation/salmon/encrypted_magic_envelope.rb +61 -0
  43. data/lib/diaspora_federation/salmon/encrypted_slap.rb +69 -50
  44. data/lib/diaspora_federation/salmon/exceptions.rb +8 -14
  45. data/lib/diaspora_federation/salmon/magic_envelope.rb +80 -39
  46. data/lib/diaspora_federation/salmon/slap.rb +20 -51
  47. data/lib/diaspora_federation/salmon/xml_payload.rb +5 -104
  48. data/lib/diaspora_federation/validators.rb +22 -16
  49. data/lib/diaspora_federation/validators/account_deletion_validator.rb +1 -1
  50. data/lib/diaspora_federation/validators/comment_validator.rb +0 -4
  51. data/lib/diaspora_federation/validators/contact_validator.rb +13 -0
  52. data/lib/diaspora_federation/validators/conversation_validator.rb +2 -2
  53. data/lib/diaspora_federation/validators/like_validator.rb +1 -3
  54. data/lib/diaspora_federation/validators/message_validator.rb +0 -4
  55. data/lib/diaspora_federation/validators/participation_validator.rb +1 -5
  56. data/lib/diaspora_federation/validators/person_validator.rb +1 -1
  57. data/lib/diaspora_federation/validators/photo_validator.rb +2 -2
  58. data/lib/diaspora_federation/validators/poll_participation_validator.rb +0 -4
  59. data/lib/diaspora_federation/validators/profile_validator.rb +1 -1
  60. data/lib/diaspora_federation/validators/relayable_retraction_validator.rb +1 -1
  61. data/lib/diaspora_federation/validators/relayable_validator.rb +2 -0
  62. data/lib/diaspora_federation/validators/request_validator.rb +3 -2
  63. data/lib/diaspora_federation/validators/reshare_validator.rb +3 -3
  64. data/lib/diaspora_federation/validators/retraction_validator.rb +2 -2
  65. data/lib/diaspora_federation/validators/rules/guid.rb +16 -7
  66. data/lib/diaspora_federation/validators/signed_retraction_validator.rb +1 -1
  67. data/lib/diaspora_federation/validators/status_message_validator.rb +2 -2
  68. data/lib/diaspora_federation/version.rb +1 -1
  69. metadata +20 -11
  70. data/lib/diaspora_federation/receiver.rb +0 -28
  71. data/lib/diaspora_federation/receiver/private.rb +0 -19
  72. data/lib/diaspora_federation/receiver/public.rb +0 -13
  73. 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] diaspora_id
41
+ # @!attribute [r] author
42
42
  # The diaspora ID of the person who deletes a relayable
43
- # @see Person#diaspora_id
43
+ # @see Person#author
44
44
  # @return [String] diaspora ID
45
- property :diaspora_id, xml_name: :sender_handle
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
- # Generates XML and updates signatures
57
- # @see Entity#to_xml
58
- # @return [Nokogiri::XML::Element] root element containing properties as child elements
59
- def to_xml
60
- entity_xml.tap do |xml|
61
- hash = to_h
62
- RelayableRetraction.update_signatures!(hash)
63
- xml.at_xpath("target_author_signature").content = hash[:target_author_signature]
64
- xml.at_xpath("parent_author_signature").content = hash[:parent_author_signature]
65
- end
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
- # Adds signatures to a given hash with the keys of the author and the parent
69
- # if the signatures are not in the hash yet and if the keys are available.
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
- # @param [Hash] hash hash given for a signing
72
- def self.update_signatures!(hash)
73
- target_author = DiasporaFederation.callbacks.trigger(
74
- :fetch_entity_author_id_by_guid,
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
- fill_required_signature(target_author, pkey, hash) unless pkey.nil?
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
- def self.fill_required_signature(target_author, pkey, hash)
84
- if target_author == hash[:diaspora_id] && hash[:target_author_signature].nil?
85
- hash[:target_author_signature] =
86
- Signing.sign_with_key(SignedRetraction.apply_signable_exceptions(hash), pkey)
87
- elsif target_author != hash[:diaspora_id] && hash[:parent_author_signature].nil?
88
- hash[:parent_author_signature] =
89
- Signing.sign_with_key(SignedRetraction.apply_signable_exceptions(hash), pkey)
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] sender_id
9
+ # @!attribute [r] author
9
10
  # The diaspora ID of the person who shares his profile
10
- # @see Person#diaspora_id
11
+ # @see Person#author
11
12
  # @return [String] sender ID
12
- property :sender_id, xml_name: :sender_handle
13
+ property :author, xml_name: :sender_handle
13
14
 
14
- # @!attribute [r] recipient_id
15
+ # @!attribute [r] recipient
15
16
  # The diaspora ID of the person who will be shared with
16
- # @see Person#diaspora_id
17
+ # @see Validation::Rule::DiasporaId
17
18
  # @return [String] recipient ID
18
- property :recipient_id, xml_name: :recipient_handle
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
- # @!attribute [r] diaspora_id
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#diaspora_id
11
+ # @see Person#author
10
12
  # @return [String] diaspora ID
11
- property :root_diaspora_id # inconsistent, everywhere else it's "handle"
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] diaspora_id
21
+ # @!attribute [r] author
22
22
  # The diaspora ID of the person who deletes a post
23
- # @see Person#diaspora_id
23
+ # @see Person#author
24
24
  # @return [String] diaspora ID
25
- property :diaspora_id, xml_name: :sender_handle
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
- # Generates XML and updates signatures
34
- # @see Entity#to_xml
35
- # @return [Nokogiri::XML::Element] root element containing properties as child elements
36
- def to_xml
37
- entity_xml.tap do |xml|
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
- xml.at_xpath("target_author_signature").content = hash[:target_author_signature]
42
- end
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
- # Adds signature to a given hash with the key of the author
46
- # if the signature is not in the hash yet and if the key is available.
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
- # @param [Hash] data hash given for a signing
49
- def self.update_signatures!(data)
50
- if data[:target_author_signature].nil?
51
- pkey = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_diaspora_id, data[:diaspora_id])
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
- # Deletes :diaspora_id (xml_name: sender_handle) from the hash in order to compute
57
- # a signature since it is included from signable_string for SignedRetraction and RelayableRetraction
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
- missing_props = self.class.missing_props(data)
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(data).each do |k, v|
60
- instance_variable_set("@#{k}", nilify(v)) if setable?(k, v)
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.class_prop_names.each_with_object({}) do |prop, hash|
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
- entity_xml
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
- prop_def = self.class.class_props.find {|p| p[:name] == name }
99
- return false if prop_def.nil? # property undefined
137
+ type = self.class.class_props[name]
138
+ return false if type.nil? # property undefined
100
139
 
101
- setable_string?(prop_def, val) || setable_nested?(prop_def, val) || setable_multi?(prop_def, val)
140
+ setable_string?(type, val) || setable_nested?(type, val) || setable_multi?(type, val)
102
141
  end
103
142
 
104
- def setable_string?(definition, val)
105
- (definition[:type] == String && val.respond_to?(:to_s))
143
+ def setable_string?(type, val)
144
+ type == String && val.respond_to?(:to_s)
106
145
  end
107
146
 
108
- def setable_nested?(definition, val)
109
- t = definition[:type]
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?(definition, val)
114
- t = definition[:type]
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
- # Serialize the Entity into XML elements
142
- # @return [Nokogiri::XML::Element] root node
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, prop_def, root_element)
153
- property = prop_def[:name]
154
- type = prop_def[:type]
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
- [*public_send(property)].compact.each do |item|
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, property)
167
- Nokogiri::XML::Element.new(name.to_s, doc).tap do |node|
168
- data = public_send(property).to_s
169
- node.content = data unless data.empty?
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