occi-core 4.1.3 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +8 -0
  3. data/README.md +49 -17
  4. data/lib/occi/collection.rb +37 -21
  5. data/lib/occi/core/action.rb +5 -5
  6. data/lib/occi/core/action_instance.rb +45 -3
  7. data/lib/occi/core/actions.rb +2 -1
  8. data/lib/occi/core/attributes.rb +253 -73
  9. data/lib/occi/core/categories.rb +1 -0
  10. data/lib/occi/core/category.rb +25 -8
  11. data/lib/occi/core/entities.rb +1 -0
  12. data/lib/occi/core/entity.rb +51 -74
  13. data/lib/occi/core/kind.rb +15 -11
  14. data/lib/occi/core/kinds.rb +1 -1
  15. data/lib/occi/core/link.rb +14 -15
  16. data/lib/occi/core/links.rb +1 -1
  17. data/lib/occi/core/mixin.rb +5 -5
  18. data/lib/occi/core/mixins.rb +2 -2
  19. data/lib/occi/core/properties.rb +90 -12
  20. data/lib/occi/core/resource.rb +7 -3
  21. data/lib/occi/core/resources.rb +2 -2
  22. data/lib/occi/errors/attribute_definitions_converted_error.rb +5 -0
  23. data/lib/occi/errors/attribute_missing_error.rb +5 -0
  24. data/lib/occi/errors/attribute_name_invalid_error.rb +5 -0
  25. data/lib/occi/errors/attribute_not_defined_error.rb +5 -0
  26. data/lib/occi/errors/attribute_property_type_error.rb +5 -0
  27. data/lib/occi/errors/attribute_type_error.rb +5 -0
  28. data/lib/occi/errors/kind_not_defined_error.rb +5 -0
  29. data/lib/occi/errors/parser_input_error.rb +5 -0
  30. data/lib/occi/errors/parser_type_error.rb +5 -0
  31. data/lib/occi/errors.rb +1 -0
  32. data/lib/occi/extensions/hashie.rb +25 -0
  33. data/lib/occi/helpers/comparators/action_instance.rb +22 -0
  34. data/lib/occi/helpers/comparators/attributes.rb +22 -0
  35. data/lib/occi/helpers/comparators/categories.rb +22 -0
  36. data/lib/occi/helpers/comparators/category.rb +22 -0
  37. data/lib/occi/helpers/comparators/collection.rb +40 -0
  38. data/lib/occi/helpers/comparators/entities.rb +22 -0
  39. data/lib/occi/helpers/comparators/entity.rb +22 -0
  40. data/lib/occi/helpers/comparators/properties.rb +26 -0
  41. data/lib/occi/helpers/comparators.rb +1 -0
  42. data/lib/occi/infrastructure/compute.rb +11 -9
  43. data/lib/occi/infrastructure/network.rb +27 -27
  44. data/lib/occi/infrastructure/networkinterface.rb +22 -23
  45. data/lib/occi/infrastructure/os_tpl.rb +1 -1
  46. data/lib/occi/infrastructure/resource_tpl.rb +1 -1
  47. data/lib/occi/infrastructure/storage.rb +7 -6
  48. data/lib/occi/infrastructure/storagelink.rb +4 -4
  49. data/lib/occi/log.rb +13 -10
  50. data/lib/occi/model.rb +9 -8
  51. data/lib/occi/parser/json.rb +11 -9
  52. data/lib/occi/parser/ova.rb +12 -6
  53. data/lib/occi/parser/ovf.rb +173 -116
  54. data/lib/occi/parser/text/constants.rb +87 -0
  55. data/lib/occi/parser/text.rb +161 -200
  56. data/lib/occi/parser/xml.rb +10 -8
  57. data/lib/occi/parser.rb +100 -50
  58. data/lib/occi/settings.rb +2 -1
  59. data/lib/occi/version.rb +1 -1
  60. data/lib/occi-core.rb +6 -4
  61. data/occi-core.gemspec +0 -7
  62. data/spec/occi/collection_samples/collection1.json +1 -0
  63. data/spec/occi/collection_samples/directory2/collection2.json +1 -0
  64. data/spec/occi/collection_spec.rb +961 -31
  65. data/spec/occi/core/action_instance_spec.rb +317 -0
  66. data/spec/occi/core/action_spec.rb +71 -0
  67. data/spec/occi/core/attributes_spec.rb +582 -27
  68. data/spec/occi/core/category_spec.rb +194 -18
  69. data/spec/occi/core/entities_spec.rb +96 -0
  70. data/spec/occi/core/entity_spec.rb +317 -28
  71. data/spec/occi/core/kind_spec.rb +127 -16
  72. data/spec/occi/core/link_spec.rb +35 -0
  73. data/spec/occi/core/links_spec.rb +130 -0
  74. data/spec/occi/core/mixins_spec.rb +107 -0
  75. data/spec/occi/core/properties_spec.rb +167 -0
  76. data/spec/occi/core/resource_spec.rb +23 -9
  77. data/spec/occi/core_spec.rb +12 -0
  78. data/spec/occi/infrastructure/compute_spec.rb +218 -18
  79. data/spec/occi/infrastructure/network_spec.rb +96 -0
  80. data/spec/occi/infrastructure/networkinterface_spec.rb +96 -0
  81. data/spec/occi/infrastructure/storage_spec.rb +33 -0
  82. data/spec/occi/infrastructure/storagelink_spec.rb +45 -0
  83. data/spec/occi/log_spec.rb +104 -1
  84. data/spec/occi/model_spec.rb +251 -39
  85. data/spec/occi/{test.json → parser/json_samples/test.json} +0 -0
  86. data/spec/occi/parser/ova_samples/test.dump +0 -0
  87. data/spec/occi/{test.ova → parser/ova_samples/test.ova} +0 -0
  88. data/spec/occi/parser/ovf_samples/test.dump +0 -0
  89. data/spec/occi/{test.ovf → parser/ovf_samples/test.ovf} +0 -0
  90. data/spec/occi/parser/text_samples/occi_categories.dump +0 -0
  91. data/spec/occi/parser/text_samples/occi_categories.text +2 -0
  92. data/spec/occi/parser/text_samples/occi_compute_rocci_server.dump +0 -0
  93. data/spec/occi/parser/text_samples/occi_compute_rocci_server.resource.dump +0 -0
  94. data/spec/occi/parser/text_samples/occi_compute_rocci_server.text +10 -0
  95. data/spec/occi/parser/text_samples/occi_link_resource_instance.dump +0 -0
  96. data/spec/occi/parser/text_samples/occi_link_resource_instance.text +7 -0
  97. data/spec/occi/parser/text_samples/occi_link_simple.dump +0 -0
  98. data/spec/occi/parser/text_samples/occi_link_simple.link_string.dump +0 -0
  99. data/spec/occi/parser/text_samples/occi_link_simple.text +1 -0
  100. data/spec/occi/parser/text_samples/occi_link_w_attributes.dump +0 -0
  101. data/spec/occi/parser/text_samples/occi_link_w_attributes.text +7 -0
  102. data/spec/occi/parser/text_samples/occi_link_w_category.dump +0 -0
  103. data/spec/occi/parser/text_samples/occi_link_w_category.text +3 -0
  104. data/spec/occi/parser/text_samples/occi_model_rocci_server.dump +0 -0
  105. data/spec/occi/parser/text_samples/occi_model_rocci_server.text +51 -0
  106. data/spec/occi/parser/text_samples/occi_network_rocci_server.dump +0 -0
  107. data/spec/occi/parser/text_samples/occi_network_rocci_server.resource.dump +0 -0
  108. data/spec/occi/parser/text_samples/occi_network_rocci_server.text +11 -0
  109. data/spec/occi/parser/text_samples/occi_resource_w_attributes.dump +0 -0
  110. data/spec/occi/parser/text_samples/occi_resource_w_attributes.text +11 -0
  111. data/spec/occi/parser/text_samples/occi_resource_w_inline_links.dump +0 -0
  112. data/spec/occi/parser/text_samples/occi_resource_w_inline_links.text +16 -0
  113. data/spec/occi/parser/text_samples/occi_resource_w_inline_links_only.dump +0 -0
  114. data/spec/occi/parser/text_samples/occi_resource_w_inline_links_only.text +13 -0
  115. data/spec/occi/parser/text_samples/occi_storage_rocci_server.dump +0 -0
  116. data/spec/occi/parser/text_samples/occi_storage_rocci_server.resource.dump +0 -0
  117. data/spec/occi/parser/text_samples/occi_storage_rocci_server.text +9 -0
  118. data/spec/occi/parser/text_spec.rb +274 -78
  119. data/spec/occi/parser/xml_samples/test.xml +352 -0
  120. data/spec/occi/parser_spec.rb +255 -104
  121. data/spec/occi-core_spec.rb +31 -0
  122. data/spec/spec_helper.rb +6 -2
  123. metadata +110 -111
  124. checksums.yaml +0 -7
  125. data/spec/occi/core/attribute_spec.rb +0 -0
@@ -3,8 +3,10 @@ module Occi
3
3
  class Entity
4
4
 
5
5
  include Occi::Helpers::Inspect
6
+ include Occi::Helpers::Comparators::Entity
6
7
 
7
- attr_accessor :mixins, :attributes, :actions, :id, :model, :kind, :location
8
+ attr_accessor :mixins, :attributes, :actions, :id, :model, :location
9
+ attr_reader :kind
8
10
 
9
11
  class_attribute :kind, :mixins, :attributes, :actions
10
12
 
@@ -64,27 +66,25 @@ module Occi
64
66
  @kind = self.class.kind.clone
65
67
  @mixins = Occi::Core::Mixins.new mixins
66
68
  @mixins.entity = self
69
+
67
70
  attributes = self.class.attribute_properties if attributes.blank?
68
71
  if attributes.kind_of? Occi::Core::Attributes
69
72
  @attributes = attributes.convert
70
73
  else
71
74
  @attributes = Occi::Core::Attributes.new attributes
72
75
  end
76
+ @attributes['occi.core.id'] ||= UUIDTools::UUID.random_create.to_s
77
+
73
78
  @actions = Occi::Core::Actions.new actions
74
79
  @location = location
75
80
  end
76
81
 
77
- # @return [Occi::Core::Kind]
78
- def kind
79
- @kind
80
- end
81
-
82
82
  # @param [Occi::Core::Kind,String] kind
83
83
  # @return [Occi::Core::Kind]
84
84
  def kind=(kind)
85
85
  if kind.kind_of? String
86
86
  scheme, term = kind.split '#'
87
- kind = Occi::Core::Category.get_class scheme, term
87
+ kind = Occi::Core::Kind.get_class scheme, term
88
88
  end
89
89
  @kind = kind
90
90
  end
@@ -115,7 +115,7 @@ module Occi
115
115
 
116
116
  # @return [UUIDTools::UUID] id of the entity
117
117
  def id
118
- @id ||= @attributes.occi.core.id if @attributes.occi.core if @attributes.occi
118
+ @id ||= @attributes.occi_.core_.id
119
119
  @id
120
120
  end
121
121
 
@@ -127,17 +127,20 @@ module Occi
127
127
 
128
128
  # @return [String] title attribute of entity
129
129
  def title
130
- @attributes.occi.core.title if @attributes.occi.core if @attributes.occi
130
+ @attributes.occi_.core_.title
131
131
  end
132
132
 
133
133
  # @param [Occi::Model] model
134
134
  # @return [Occi::Model]
135
135
  def model=(model)
136
136
  @model = model
137
+
137
138
  @kind = (model.get_by_id(@kind.type_identifier) || @kind)
138
139
  @kind.entities << self
140
+
139
141
  @mixins.model = model
140
142
  @mixins.each { |mixin| mixin.entities << self }
143
+
141
144
  @actions.model = model
142
145
  end
143
146
 
@@ -150,56 +153,29 @@ module Occi
150
153
  # @return [String] location of the entity
151
154
  def location
152
155
  return @location if @location
153
- kind.location + id.gsub('urn:uuid:', '') if id
156
+ "#{kind.location}#{id.gsub('urn:uuid:', '')}" if id
154
157
  end
155
158
 
156
159
  # check attributes against their definitions and set defaults
157
- # @param [Occi::Model] model representation of the Occi model to check the attributes against
158
- def check
159
- raise "No model associated" unless @model
160
+ # @param [true,false] set default values for all empty attributes
161
+ def check(set_defaults = false)
162
+
163
+ raise ArgumentError, 'No model has been assigned to this entity' unless @model # XXX: Needs error type
164
+
165
+ kind = @model.get_by_id(@kind.to_s)
166
+ raise Occi::Errors::KindNotDefinedError, "Kind not found for entity #{self.to_s}!" unless kind # XXX: Needs error type
167
+
160
168
  definitions = Occi::Core::Attributes.new
161
- definitions.merge!(@model.get_by_id(@kind.to_s).attributes)
162
- @mixins.each do |mixin_id|
163
- mixin = @model.get_by_id(mixin_id)
169
+ definitions.merge! kind.attributes
170
+
171
+ @mixins.each do |mxn|
172
+ mixin = @model.get_by_id(mxn.to_s)
164
173
  next if mixin.nil?
174
+
165
175
  definitions.merge!(mixin.attributes) if mixin.attributes
166
176
  end if @mixins
167
-
168
- @attributes = Entity.check(@attributes, definitions) if definitions
169
- end
170
-
171
- # @param [Occi::Core::Attributes] attributes
172
- # @param [Occi::Core::Attributes] definitions
173
- # @param [true,false] set_defaults
174
- # @return [Occi::Core::Attributes] attributes with their defaults set
175
- def self.check(attributes, definitions, set_defaults=false)
176
- attributes = Occi::Core::Attributes.new(attributes)
177
- definitions.each_key do |key|
178
- next if definitions.key?(key[1..-1])
179
- if definitions[key].kind_of? Occi::Core::Attributes
180
- attributes[key] = check(attributes[key], definitions[key])
181
- else
182
- properties = definitions[key]
183
- value = attributes[key]
184
- value ||= properties.default if set_defaults or properties.required?
185
- raise "required attribute #{key} not found" if value.nil? && properties.required?
186
- next if value.blank? and not properties.required?
187
- case properties.type
188
- when 'number'
189
- raise "attribute #{key} value #{value} from class #{value.class.name} does not match attribute property type #{properties.type}" unless value.kind_of?(Numeric)
190
- when 'boolean'
191
- raise "attribute #{key} value #{value} from class #{value.class.name} does not match attribute property type #{properties.type}" unless !!value == value
192
- when 'string'
193
- raise "attribute #{key} with value #{value} from class #{value.class.name} does not match attribute property type #{properties.type}" unless value.kind_of?(String)
194
- else
195
- raise "property type #{properties.type} is not one of the allowed types number, boolean or string"
196
- end
197
- Occi::Log.warn "attribute #{key} with value #{value} does not match pattern #{properties.pattern}" if value.to_s.scan(Regexp.new(properties.pattern)).blank? if properties.pattern
198
- attributes[key] = value
199
- end
200
- end
201
- attributes.delete_if { |_, v| v.blank? } # remove empty attributes
202
- attributes
177
+
178
+ @attributes.check!(definitions, set_defaults)
203
179
  end
204
180
 
205
181
  # @param [Hash] options
@@ -208,7 +184,7 @@ module Occi
208
184
  entity = Hashie::Mash.new
209
185
  entity.kind = @kind.to_s if @kind
210
186
  entity.mixins = @mixins.join(' ').split(' ') if @mixins.any?
211
- entity.actions = @actions if @actions.any?
187
+ entity.actions = @actions.as_json if @actions.any?
212
188
  entity.attributes = @attributes.as_json if @attributes.as_json.any?
213
189
  entity.id = id.to_s if id
214
190
  entity
@@ -216,42 +192,38 @@ module Occi
216
192
 
217
193
  # @return [String] text representation
218
194
  def to_text
219
- text = 'Category: ' + self.kind.term + ';scheme=' + self.kind.scheme.inspect + ';class="kind"'
195
+ text = "Category: #{self.kind.term};scheme=#{self.kind.scheme.inspect};class=\"kind\""
220
196
  @mixins.each do |mixin|
221
197
  scheme, term = mixin.to_s.split('#')
222
198
  scheme << '#'
223
- text << "\n" + 'Category: ' + term + ';scheme=' + scheme.inspect + ';class="mixin"'
224
- end
225
- @attributes.names.each_pair do |name, value|
226
- value = value.inspect
227
- text << "\n" + 'X-OCCI-Attribute: ' + name + '=' + value
228
- end
229
- @actions.each do |action|
230
- _, term = action.split('#')
231
- text << "\n" + 'Link: <' + self.location + '?action=' + term + '>;rel=' + action.inspect
199
+ text << "\nCategory: #{term};scheme=#{scheme.inspect};class=\"mixin\""
232
200
  end
201
+
202
+ text << @attributes.to_text
203
+
204
+ @actions.each { |action| text << "\nLink: <#{self.location}?action=#{action.term}>;rel=#{action.to_s}" }
205
+
233
206
  text
234
207
  end
235
208
 
236
209
  # @return [Hash] hash containing the HTTP headers of the text/occi rendering
237
210
  def to_header
238
211
  header = Hashie::Mash.new
239
- header['Category'] = self.kind.term + ';scheme=' + self.kind.scheme.inspect + ';class="kind"'
212
+ header['Category'] = "#{self.kind.term};scheme=#{self.kind.scheme.inspect};class=\"kind\""
213
+
240
214
  @mixins.each do |mixin|
241
215
  scheme, term = mixin.to_s.split('#')
242
216
  scheme << '#'
243
- header['Category'] += ',' + term + ';scheme=' + scheme.inspect + ';class="mixin"'
244
- end
245
- attributes = []
246
- @attributes.names.each_pair do |name, value|
247
- attributes << name + '=' + value.to_s.inspect
217
+ header['Category'] << ",#{term};scheme=#{scheme.inspect};class=\"mixin\""
248
218
  end
249
- header['X-OCCI-Attribute'] = attributes.join(',') if attributes.any?
219
+
220
+ attributes = @attributes.to_header
221
+ header['X-OCCI-Attribute'] = attributes unless attributes.blank?
222
+
250
223
  links = []
251
- @actions.each do |action|
252
- _, term = action.split('#')
253
- links << self.location + '?action=' + term + '>;rel=' + action.inspect
254
- end
224
+ @actions.each { |action| links << "<#{self.location}?action=#{action.term}>;rel=#{action.to_s}" }
225
+ header['Link'] = links.join(',') if links.any?
226
+
255
227
  header
256
228
  end
257
229
 
@@ -260,6 +232,11 @@ module Occi
260
232
  self.location
261
233
  end
262
234
 
235
+ # @return [Bool] Indicating whether this entity is "empty", i.e. required attributes are blank
236
+ def empty?
237
+ kind.blank? || attributes['occi.core.id'].blank?
238
+ end
239
+
263
240
  end
264
241
  end
265
242
  end
@@ -21,7 +21,7 @@ module Occi
21
21
  @parent = [parent].flatten.first
22
22
  @actions = Occi::Core::Actions.new(actions)
23
23
  @entities = Occi::Core::Entities.new
24
- location.blank? ? @location = '/' + term + '/' : @location = location
24
+ location.blank? ? @location = "/#{term}/" : @location = location
25
25
  end
26
26
 
27
27
  # @param scheme [String] The categorisation scheme.
@@ -30,9 +30,12 @@ module Occi
30
30
  # @return [Class] Ruby class with scheme as namespace, term as name and parent kind as super class.
31
31
  def self.get_class(scheme, term, parent=Occi::Core::Entity.kind)
32
32
  parent ||= Occi::Core::Entity.kind
33
+ raise ArgumentError, 'Mandatory argument cannot be nil' unless scheme && term
34
+
33
35
  if parent.kind_of? Array
34
36
  parent = parent.first
35
37
  end
38
+
36
39
  if parent.to_s == 'http://schemas.ogf.org/occi/core#entity'
37
40
  parent = Occi::Core::Entity.kind
38
41
  elsif parent.kind_of? Occi::Core::Kind
@@ -41,8 +44,11 @@ module Occi
41
44
  parent = self.get_class(*parent.to_s.split('#')).kind
42
45
  end
43
46
 
47
+ term = self.sanitize_term(term) if Occi::Settings.compatibility
48
+ raise ArgumentError, "Invalid characters in term #{term}" unless Occi::Core::Category.valid_term?(term)
49
+
44
50
  unless scheme.end_with? '#'
45
- scheme += '#'
51
+ scheme << '#'
46
52
  end
47
53
 
48
54
  uri = URI.parse(scheme)
@@ -60,7 +66,7 @@ module Occi
60
66
  end
61
67
  end
62
68
 
63
- class_name = self.sanitize_term_before_classify(term).classify
69
+ class_name = term.classify
64
70
  if namespace.const_defined? class_name
65
71
  klass = namespace.const_get class_name
66
72
  unless klass.ancestors.include? Occi::Core::Entity
@@ -113,20 +119,18 @@ module Occi
113
119
  # @return [String] string representation of the kind
114
120
  def to_string
115
121
  string = super
116
- string << ';rel=' + self.related.first.to_s.inspect if self.related.any?
117
- string << ';location=' + self.location.inspect
118
- string << ';attributes=' + self.attributes.names.keys.join(' ').inspect if self.attributes.any?
119
- string << ';actions=' + self.actions.join(' ').inspect if self.actions.any?
122
+ string << ";rel=#{self.related.first.to_s.inspect}" if self.related.any?
123
+ string << ";location=#{self.location.inspect}"
124
+ string << self.attributes.to_string_short
125
+ string << ";actions=#{self.actions.join(' ').inspect}" if self.actions.any?
120
126
  string
121
127
  end
122
128
 
123
129
  private
124
130
 
125
131
  # Relaxed parser rules require additional checks on terms.
126
- # TODO: a better solution?
127
- # TODO: check for more characters
128
- def self.sanitize_term_before_classify(term)
129
- sanitized = term.downcase.gsub(/[\s\(\)\.\{\}\-;,\\\/\?\!\|\*\<\>]/, '_').gsub(/_+/, '_').chomp('_').reverse.chomp('_').reverse
132
+ def self.sanitize_term(term)
133
+ sanitized = term.downcase.gsub(/[^a-z0-9-]/, '_').gsub(/_+/, '_').gsub(/^_|_$/, '')
130
134
  sanitized = "uuid_#{sanitized}" if sanitized.match(/^[0-9]/)
131
135
 
132
136
  sanitized
@@ -9,7 +9,7 @@ module Occi
9
9
 
10
10
  if category.kind_of? String
11
11
  scheme, term = category.split '#'
12
- scheme += '#'
12
+ scheme << '#'
13
13
 
14
14
  klass = Occi::Core::Kind.get_class scheme, term, [Occi::Core::Kind.new]
15
15
  category = klass.kind
@@ -4,6 +4,7 @@ module Occi
4
4
 
5
5
  attr_accessor :rel, :source, :target
6
6
 
7
+ self.attributes = Occi::Core::Attributes.new(Occi::Core::Entity.attributes)
7
8
  self.attributes['occi.core.target'] = {:mutable => true}
8
9
  self.attributes['occi.core.source'] = {:mutable => true}
9
10
 
@@ -30,7 +31,7 @@ module Occi
30
31
 
31
32
  # @return [String] target attribute of the link
32
33
  def target
33
- @target ||= self.attributes.occi.core.target if @attributes.occi.core if @attributes.occi
34
+ @target ||= self.attributes.occi_.core_.target
34
35
  @target
35
36
  end
36
37
 
@@ -43,7 +44,7 @@ module Occi
43
44
 
44
45
  # @return [String] source attribute of the link
45
46
  def source
46
- @source ||= self.attributes.occi.core.source if @attributes.occi.core if @attributes.occi
47
+ @source ||= self.attributes.occi_.core_.source
47
48
  @source
48
49
  end
49
50
 
@@ -54,9 +55,9 @@ module Occi
54
55
  @source = source
55
56
  end
56
57
 
57
- # @param [Occi::Model] model
58
+ # Runs check on attributes
58
59
  def check
59
- raise "rel must be provided" unless @rel
60
+ raise ArgumentError, "Cannot run check on #{self.to_s} without relation (@rel attribute) set" unless @rel
60
61
  super
61
62
  end
62
63
 
@@ -72,25 +73,23 @@ module Occi
72
73
 
73
74
  # @return [String] text representation of link reference
74
75
  def to_string
75
- string = '<' + self.target.to_s + '>'
76
- string << ';rel=' + @rel.to_s.inspect
77
- string << ';self=' + self.location.inspect if self.location
76
+ string = "<#{self.target.to_s}>"
77
+ string << ";rel=#{@rel.to_s.inspect}"
78
+ string << ";self=#{self.location.inspect}" if self.location
79
+
78
80
  categories = [@kind] + @mixins.join(',').split(',')
79
- string << ';category=' + categories.join(' ').inspect
80
- @attributes.names.each_pair do |name, value|
81
- next if value.to_s.blank?
82
- value = value.to_s.inspect
83
- string << ';' + name + '=' + value
84
- end
81
+ string << ";category=#{categories.join(' ').inspect}"
82
+
83
+ string << @attributes.to_string
85
84
 
86
85
  string
87
86
  end
88
87
 
89
88
  # @return [String] text representation of link
90
89
  def to_text_link
91
- 'Link: ' + self.to_string
90
+ "Link: #{self.to_string}"
92
91
  end
93
92
 
94
93
  end
95
94
  end
96
- end
95
+ end
@@ -13,7 +13,7 @@ module Occi
13
13
 
14
14
  def create(*args)
15
15
  link = Occi::Core::Link.new(*args)
16
- link.model = @model
16
+ link.model = @model if @model
17
17
  self << link
18
18
  link
19
19
  end
@@ -23,7 +23,7 @@ module Occi
23
23
  @depends = Occi::Core::Dependencies.new depends
24
24
  @actions = Occi::Core::Actions.new actions
25
25
  @entities = Occi::Core::Entities.new
26
- location.blank? ? @location = '/mixins/' + term + '/' : @location = location
26
+ location.blank? ? @location = "/mixins/#{term}/" : @location = location
27
27
  @applies = Occi::Core::Kinds.new applies
28
28
  end
29
29
 
@@ -59,10 +59,10 @@ module Occi
59
59
  # @return [String] text representation
60
60
  def to_string
61
61
  string = super
62
- string << ';rel=' + self.related.join(' ').inspect if self.related.any?
63
- string << ';location=' + self.location.inspect
64
- string << ';attributes=' + self.attributes.names.keys.join(' ').inspect if self.attributes.any?
65
- string << ';actions=' + self.actions.join(' ').inspect if self.actions.any?
62
+ string << ";rel=#{self.related.join(' ').inspect}" if self.related.any?
63
+ string << ";location=#{self.location.inspect}"
64
+ string << self.attributes.to_string_short
65
+ string << ";actions=#{self.actions.join(' ').inspect}" if self.actions.any?
66
66
  string
67
67
  end
68
68
 
@@ -11,7 +11,7 @@ module Occi
11
11
 
12
12
  def remove(mixin)
13
13
  mixin = convert mixin
14
- @entity.attributes.remove mixin.attributes
14
+ @entity.attributes.remove mixin.attributes if @entity
15
15
  self.delete mixin
16
16
  end
17
17
 
@@ -34,4 +34,4 @@ module Occi
34
34
 
35
35
  end
36
36
  end
37
- end
37
+ end
@@ -3,33 +3,111 @@ module Occi
3
3
  class Properties
4
4
 
5
5
  include Occi::Helpers::Inspect
6
+ include Occi::Helpers::Comparators::Properties
6
7
 
7
- attr_accessor :default, :type, :required, :mutable, :pattern, :description
8
+ PROPERTY_KEYS = [:type, :required, :mutable, :default, :description, :pattern]
9
+ attr_accessor :required, :mutable, :default, :description, :pattern
10
+ attr_reader :type
8
11
  alias_method :required?, :required
9
12
  alias_method :mutable?, :mutable
10
13
 
11
- # @param [Hash] properties
12
- # @param [Hash] default
13
- def initialize(properties={})
14
- self.default = properties[:default]
15
- self.type = properties[:type] ||= 'string'
16
- self.required = properties[:required] ||= false
17
- self.mutable = properties[:mutable] ||= false
18
- self.pattern = properties[:pattern] ||= '.*'
19
- self.description = properties[:description]
14
+ # Types supported in properties, and their mapping to Ruby Classes
15
+ SUPPORTED_TYPES = Hash.new
16
+ SUPPORTED_TYPES["string"] = [ String ]
17
+ SUPPORTED_TYPES["number"] = [ Numeric ]
18
+ SUPPORTED_TYPES["boolean"] = [ TrueClass, FalseClass ]
19
+
20
+ # @param source_hash [Hash]
21
+ def initialize(source_hash = {})
22
+ raise ArgumentError, 'Source_hash must be initialized from a hash-like structure!' unless source_hash.kind_of?(Hash)
23
+ raise ArgumentError, 'Source_hash must not be a Hashie::Mash instance!' if source_hash.kind_of?(Hashie::Mash)
24
+ source_hash = Occi::Core::Properties.normalize_props(source_hash)
25
+
26
+ self.type = source_hash[:type] ||= 'string'
27
+ raise Occi::Errors::AttributePropertyTypeError,
28
+ "Type \"#{self.type}\" unsupported in properties. " \
29
+ "Supported types are: #{Properties.supported_type_names}." unless SUPPORTED_TYPES.key?(self.type)
30
+ self.required = source_hash[:required] = source_hash[:required].nil? ? false : source_hash[:required]
31
+ self.mutable = source_hash[:mutable] = source_hash[:mutable].nil? ? false : source_hash[:mutable]
32
+ self.pattern = source_hash[:pattern] ||= '.*'
33
+ self.description = source_hash[:description]
34
+ self.default = source_hash[:default]
35
+ end
36
+
37
+ # @param type [String] Requested attribute type
38
+ def type=(type)
39
+ raise Occi::Errors::AttributePropertyTypeError,
40
+ "Type \"#{type}\" unsupported in properties. Supported " \
41
+ "types are: #{Properties.supported_type_names}." unless SUPPORTED_TYPES.key?(type)
42
+ @type = type
43
+ end
44
+
45
+ # @param value [Object] Object whose class will be checked against definition
46
+ def check_value_for_type(value, key_name = nil)
47
+ raise Occi::Errors::AttributePropertyTypeError,
48
+ "Property type #{@type} for #{key_name.inspect} is not one of the allowed " \
49
+ "types: #{Properties.supported_type_names}" unless SUPPORTED_TYPES.key?(@type)
50
+ raise Occi::Errors::AttributeTypeError,
51
+ "Attribute value #{value} for #{key_name.inspect} is class #{value.class.name}. " \
52
+ "It does not match attribute property type #{@type}" unless SUPPORTED_TYPES[@type].any? { |klasse| value.kind_of?(klasse) }
53
+ end
54
+
55
+ def to_hash
56
+ as_json.to_hash
57
+ end
58
+
59
+ def to_json(*a)
60
+ as_json(*a).to_json(*a)
20
61
  end
21
62
 
22
63
  def as_json(options={})
23
64
  hash = Hashie::Mash.new
24
65
  hash.default = self.default if self.default
25
66
  hash.type = self.type if self.type
26
- hash.required = self.required if self.required
27
- hash.mutable = self.mutable if self.mutable
67
+ hash.required = self.required unless self.required.nil?
68
+ hash.mutable = self.mutable unless self.mutable.nil?
28
69
  hash.pattern = self.pattern if self.pattern
29
70
  hash.description = self.description if self.description
71
+
30
72
  hash
31
73
  end
32
74
 
75
+ # @return [Bool] Indicating whether this set of properties is "empty", i.e. no attributes are set
76
+ def empty?
77
+ as_json.empty?
78
+ end
79
+
80
+ def self.normalize_props(hash)
81
+ props = {}
82
+
83
+ PROPERTY_KEYS.each do |key|
84
+ found = hash.keys.select { |k| k.to_s.downcase.to_sym == key }.first
85
+ props[key] = hash[found] if found
86
+ end
87
+
88
+ props
89
+ end
90
+
91
+ def self.contains_props?(hash)
92
+ # Not a hash == doesn't contain Properties
93
+ return false unless hash.kind_of? Hash
94
+ hash = normalize_props(hash)
95
+
96
+ # Are there any Property keys?
97
+ return false if hash.empty?
98
+
99
+ # Do all Property keys point to simple values?
100
+ complx_keys = hash.keys.select { |k| hash[k].kind_of?(Hash) }
101
+ return false unless complx_keys.empty?
102
+
103
+ true
104
+ end
105
+
106
+ private
107
+
108
+ def self.supported_type_names()
109
+ SUPPORTED_TYPES.keys.join(', ')
110
+ end
33
111
  end
34
112
  end
35
113
  end
@@ -4,7 +4,7 @@ module Occi
4
4
 
5
5
  attr_accessor :links
6
6
 
7
- self.attributes = Occi::Core::Attributes.new
7
+ self.attributes = Occi::Core::Attributes.new(Occi::Core::Entity.attributes)
8
8
 
9
9
  self.attributes['occi.core.summary'] = {:mutable => true}
10
10
 
@@ -31,7 +31,7 @@ module Occi
31
31
 
32
32
  # @return [String] summary attribute of the resource
33
33
  def summary
34
- self.attributes.occi.core.summary if @attributes.occi.core if @attributes.occi
34
+ self.attributes.occi_.core_.summary
35
35
  end
36
36
 
37
37
  # set summary attribute of resource
@@ -42,19 +42,23 @@ module Occi
42
42
 
43
43
  def link(target, kind=Occi::Core::Link.kind, mixins=[], attributes=Occi::Core::Attributes.new, rel=Occi::Core::Resource.type_identifier)
44
44
  link = kind.entity_type.new
45
+
45
46
  link.rel = rel
46
47
  link.attributes = attributes
48
+ link.id ||= UUIDTools::UUID.random_create.to_s
47
49
  link.target = target
48
50
  link.source = self
49
51
  link.mixins = mixins
52
+
50
53
  @links << link
54
+
51
55
  link
52
56
  end
53
57
 
54
58
  # @return [String] text representation
55
59
  def to_text
56
60
  text = super
57
- @links.each { |link| text << "\n" + link.to_text_link }
61
+ @links.each { |link| text << "\n#{link.to_text_link}" }
58
62
  text
59
63
  end
60
64
 
@@ -4,11 +4,11 @@ module Occi
4
4
 
5
5
  def create(*args)
6
6
  resource = Occi::Core::Resource.new(*args)
7
- resource.model = @model
7
+ resource.model = @model if @model
8
8
  self << resource
9
9
  resource
10
10
  end
11
11
 
12
12
  end
13
13
  end
14
- end
14
+ end
@@ -0,0 +1,5 @@
1
+ module Occi
2
+ module Errors
3
+ class AttributeDefinitionsConvrertedError < ArgumentError; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Occi
2
+ module Errors
3
+ class AttributeMissingError < ArgumentError; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Occi
2
+ module Errors
3
+ class AttributeNameInvalidError < ArgumentError; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Occi
2
+ module Errors
3
+ class AttributeNotDefinedError < ArgumentError; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Occi
2
+ module Errors
3
+ class AttributePropertyTypeError < ArgumentError; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Occi
2
+ module Errors
3
+ class AttributeTypeError < ArgumentError; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Occi
2
+ module Errors
3
+ class KindNotDefinedError < ArgumentError; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Occi
2
+ module Errors
3
+ class ParserInputError < ArgumentError; end
4
+ end
5
+ end