occi-core 4.1.3 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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