restfulie 0.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. data/Gemfile +28 -0
  2. data/Gemfile.lock +128 -0
  3. data/LICENSE +17 -0
  4. data/README.textile +138 -0
  5. data/Rakefile +146 -0
  6. data/lib/restfulie/client/base.rb +36 -0
  7. data/lib/restfulie/client/cache/basic.rb +76 -0
  8. data/lib/restfulie/client/cache/fake.rb +15 -0
  9. data/lib/restfulie/client/cache/http_ext.rb +123 -0
  10. data/lib/restfulie/client/cache/restrictions.rb +13 -0
  11. data/lib/restfulie/client/cache.rb +11 -0
  12. data/lib/restfulie/client/configuration.rb +67 -0
  13. data/lib/restfulie/client/dsl.rb +66 -0
  14. data/lib/restfulie/client/entry_point.rb +61 -0
  15. data/lib/restfulie/client/ext/atom_ext.rb +14 -0
  16. data/lib/restfulie/client/ext/http_ext.rb +22 -0
  17. data/lib/restfulie/client/ext/json_ext.rb +16 -0
  18. data/lib/restfulie/client/feature/base.rb +75 -0
  19. data/lib/restfulie/client/feature/base_request.rb +35 -0
  20. data/lib/restfulie/client/feature/cache.rb +16 -0
  21. data/lib/restfulie/client/feature/enhance_response.rb +12 -0
  22. data/lib/restfulie/client/feature/follow_request.rb +41 -0
  23. data/lib/restfulie/client/feature/history.rb +26 -0
  24. data/lib/restfulie/client/feature/history_request.rb +19 -0
  25. data/lib/restfulie/client/feature/open_search/pattern_matcher.rb +25 -0
  26. data/lib/restfulie/client/feature/open_search.rb +21 -0
  27. data/lib/restfulie/client/feature/serialize_body.rb +32 -0
  28. data/lib/restfulie/client/feature/setup_header.rb +22 -0
  29. data/lib/restfulie/client/feature/throw_error.rb +41 -0
  30. data/lib/restfulie/client/feature/verb.rb +119 -0
  31. data/lib/restfulie/client/feature.rb +5 -0
  32. data/lib/restfulie/client/http/cache.rb +28 -0
  33. data/lib/restfulie/client/http/error.rb +77 -0
  34. data/lib/restfulie/client/http/response_holder.rb +29 -0
  35. data/lib/restfulie/client/http.rb +7 -0
  36. data/lib/restfulie/client/master_delegator.rb +31 -0
  37. data/lib/restfulie/client/mikyung/concatenator.rb +18 -0
  38. data/lib/restfulie/client/mikyung/core.rb +70 -0
  39. data/lib/restfulie/client/mikyung/languages/german.rb +24 -0
  40. data/lib/restfulie/client/mikyung/languages/portuguese.rb +23 -0
  41. data/lib/restfulie/client/mikyung/languages.rb +11 -0
  42. data/lib/restfulie/client/mikyung/rest_process_model.rb +191 -0
  43. data/lib/restfulie/client/mikyung/steady_state_walker.rb +38 -0
  44. data/lib/restfulie/client/mikyung/then_condition.rb +39 -0
  45. data/lib/restfulie/client/mikyung/when_condition.rb +57 -0
  46. data/lib/restfulie/client/mikyung.rb +15 -0
  47. data/lib/restfulie/client.rb +26 -0
  48. data/lib/restfulie/common/converter/atom/base.rb +91 -0
  49. data/lib/restfulie/common/converter/atom/builder.rb +111 -0
  50. data/lib/restfulie/common/converter/atom/helpers.rb +17 -0
  51. data/lib/restfulie/common/converter/atom.rb +12 -0
  52. data/lib/restfulie/common/converter/json/base.rb +87 -0
  53. data/lib/restfulie/common/converter/json/builder.rb +102 -0
  54. data/lib/restfulie/common/converter/json/helpers.rb +17 -0
  55. data/lib/restfulie/common/converter/json.rb +12 -0
  56. data/lib/restfulie/common/converter/open_search/descriptor.rb +32 -0
  57. data/lib/restfulie/common/converter/open_search.rb +16 -0
  58. data/lib/restfulie/common/converter/values.rb +33 -0
  59. data/lib/restfulie/common/converter/xml/base.rb +63 -0
  60. data/lib/restfulie/common/converter/xml/builder.rb +113 -0
  61. data/lib/restfulie/common/converter/xml/helpers.rb +17 -0
  62. data/lib/restfulie/common/converter/xml/link.rb +30 -0
  63. data/lib/restfulie/common/converter/xml/links.rb +21 -0
  64. data/lib/restfulie/common/converter/xml.rb +14 -0
  65. data/lib/restfulie/common/converter.rb +43 -0
  66. data/lib/restfulie/common/core_ext/hash.rb +18 -0
  67. data/lib/restfulie/common/core_ext.rb +1 -0
  68. data/lib/restfulie/common/error.rb +19 -0
  69. data/lib/restfulie/common/links.rb +9 -0
  70. data/lib/restfulie/common/logger.rb +19 -0
  71. data/lib/restfulie/common/representation/atom/atom.rng +597 -0
  72. data/lib/restfulie/common/representation/atom/base.rb +142 -0
  73. data/lib/restfulie/common/representation/atom/category.rb +41 -0
  74. data/lib/restfulie/common/representation/atom/entry.rb +59 -0
  75. data/lib/restfulie/common/representation/atom/factory.rb +43 -0
  76. data/lib/restfulie/common/representation/atom/feed.rb +110 -0
  77. data/lib/restfulie/common/representation/atom/link.rb +68 -0
  78. data/lib/restfulie/common/representation/atom/person.rb +48 -0
  79. data/lib/restfulie/common/representation/atom/source.rb +59 -0
  80. data/lib/restfulie/common/representation/atom/tag_collection.rb +38 -0
  81. data/lib/restfulie/common/representation/atom/xml.rb +90 -0
  82. data/lib/restfulie/common/representation/atom.rb +20 -0
  83. data/lib/restfulie/common/representation/generic.rb +22 -0
  84. data/lib/restfulie/common/representation/json/base.rb +27 -0
  85. data/lib/restfulie/common/representation/json/keys_as_methods.rb +74 -0
  86. data/lib/restfulie/common/representation/json/link.rb +29 -0
  87. data/lib/restfulie/common/representation/json/link_collection.rb +23 -0
  88. data/lib/restfulie/common/representation/json.rb +13 -0
  89. data/lib/restfulie/common/representation/links.rb +11 -0
  90. data/lib/restfulie/common/representation.rb +3 -0
  91. data/lib/restfulie/common.rb +18 -0
  92. data/lib/restfulie/server/action_controller/base.rb +48 -0
  93. data/lib/restfulie/server/action_controller/params_parser.rb +100 -0
  94. data/lib/restfulie/server/action_controller/patch.rb +6 -0
  95. data/lib/restfulie/server/action_controller/restful_responder.rb +12 -0
  96. data/lib/restfulie/server/action_controller/trait/cacheable.rb +81 -0
  97. data/lib/restfulie/server/action_controller/trait/created.rb +17 -0
  98. data/lib/restfulie/server/action_controller/trait.rb +9 -0
  99. data/lib/restfulie/server/action_controller.rb +11 -0
  100. data/lib/restfulie/server/action_view/helpers.rb +50 -0
  101. data/lib/restfulie/server/action_view/template_handlers/tokamak.rb +21 -0
  102. data/lib/restfulie/server/action_view/template_handlers.rb +30 -0
  103. data/lib/restfulie/server/action_view.rb +10 -0
  104. data/lib/restfulie/server/configuration.rb +24 -0
  105. data/lib/restfulie/server/controller.rb +74 -0
  106. data/lib/restfulie/server/core_ext/array.rb +61 -0
  107. data/lib/restfulie/server/core_ext.rb +1 -0
  108. data/lib/restfulie/server.rb +25 -0
  109. data/lib/restfulie/version.rb +14 -0
  110. data/lib/restfulie.rb +34 -0
  111. metadata +242 -0
@@ -0,0 +1,191 @@
1
+ # a configuration error
2
+ module Restfulie
3
+ module Client
4
+ module Mikyung
5
+ class ConfigurationError < Restfulie::Common::Error::RestfulieError
6
+ end
7
+
8
+ class CompletionCriteriaMissingError < Restfulie::Common::Error::RestfulieError
9
+ end
10
+
11
+ class StepsFileNotFoundError < Restfulie::Common::Error::RestfulieError
12
+ end
13
+
14
+ class ScenarioFileNotFoundError < StepsFileNotFoundError
15
+ end
16
+
17
+ # Provides a DSL to build your process in a human readable way.
18
+ #
19
+ # Example:
20
+ # When there is a machine
21
+ # And already installed
22
+ # Then reboot
23
+ #
24
+ # Before creating your DSL you should provide your method content:
25
+ #
26
+ # When /there (are|is an|is a|is) (.*)/ do |resource, regex|
27
+ # resource.keys.first==regex[2]
28
+ # end
29
+ #
30
+ # When "already installed" do |resource|
31
+ # @installed
32
+ # end
33
+ #
34
+ # Then "reboot" do |resource|
35
+ # resource.machine.boot.post! :boot => {:reason => "Installed #{@software[:name]}"}
36
+ # end
37
+ class RestProcessModel
38
+ @@at = ""
39
+ @@follow = false
40
+ @@accept = "application/atom+xml"
41
+ @@current_dir = ""
42
+
43
+ def self.at(uri)
44
+ @@at = uri
45
+ end
46
+
47
+ def self.follow(bool)
48
+ @@follow = bool
49
+ end
50
+
51
+ def self.accept(type)
52
+ @@accept = type
53
+ end
54
+
55
+ def self.current_dir(dir)
56
+ @@current_dir = dir
57
+ end
58
+
59
+ def self.get_restfulie
60
+ Restfulie.at(@@at).tap do |client|
61
+ client.follow if @@follow
62
+ client.accepts(@@accept) if @@accept
63
+ end
64
+ end
65
+
66
+ # concatenates anything to a current expression
67
+ def method_missing(sym, *args)
68
+ Restfulie::Client::Mikyung::Concatenator.new(sym.to_s, *args)
69
+ end
70
+
71
+ # the list of results
72
+ def then_rules
73
+ @then_rules ||= []
74
+ end
75
+
76
+ # the list of conditions
77
+ def conditions
78
+ @conditions ||= []
79
+ end
80
+
81
+ # the list of conditional rules
82
+ def when_rules
83
+ @when_rules ||= []
84
+ end
85
+
86
+ # creates a When rule or block
87
+ #
88
+ # When blocks should return true or false whether the current resource matches what you expect:
89
+ # When /there (are|is an|is a|is) (.*)/ do |resource, regex|
90
+ # resource.keys.first==regex[2]
91
+ # end
92
+ #
93
+ # When rules will group conditions and rules together:
94
+ # When there is a machine
95
+ # And already installed
96
+ # Then reboot
97
+ def When(concat, &block)
98
+ if concat.respond_to? :content
99
+ @condition = when_factory(concat)
100
+ conditions << @condition
101
+ else
102
+ when_rules << [concat, block]
103
+ end
104
+ end
105
+
106
+ # Adds a constraint to the current scenario
107
+ def And(concat)
108
+ @condition.and when_factory(concat)
109
+ end
110
+
111
+ # Adds a negative constraint to the current scenario
112
+ def But(concat)
113
+ @condition.but when_factory(concat)
114
+ end
115
+
116
+ # Creates a result rule
117
+ #
118
+ # example:
119
+ # Then "reboot" do |resource|
120
+ # resource.machine.boot.post! :boot => {:reason => "Installed #{@software[:name]}"}
121
+ # end
122
+ def Then(concat, &block)
123
+ if concat.respond_to? :content
124
+ @condition.results_on Restfulie::Client::Mikyung::ThenCondition.new(concat.content)
125
+ else
126
+ then_rules << [concat, block]
127
+ end
128
+ end
129
+
130
+ # Goes through every scenario and finds which one fits the current server steady state.
131
+ # Picks this step and executes the business rule attached to it.
132
+ def next_step(resource, mikyung)
133
+ conditions.each do |c|
134
+ if c.should_run_for(resource, self)
135
+ return c.execute(resource, self, mikyung)
136
+ end
137
+ end
138
+ nil
139
+ end
140
+
141
+ # load step definitions from the 'steps/[class_name].rb'
142
+ # otherwise you can simply override this method with a module
143
+ def steps
144
+ unless @steps_loaded
145
+ step_file = File.expand_path("./steps/#{self.class.name.underscore}.rb", @@current_dir)
146
+ if File.exists?(step_file)
147
+ self.instance_eval File.read(step_file), __FILE__, __LINE__ + 1
148
+ else
149
+ raise StepsFileNotFoundError.new("File #{step_file} not found")
150
+ end
151
+ @steps_loaded = true
152
+ end
153
+ end
154
+
155
+ # load scenario definition from 'scenarios/[class_name].scenario'
156
+ # otherwise you can simply override this method with a module
157
+ def scenario
158
+ unless @scenarios_loaded
159
+ scenario_file = File.expand_path("./scenarios/#{self.class.name.underscore}.scenario", @@current_dir)
160
+ if File.exists?(scenario_file)
161
+ self.instance_eval File.read(scenario_file), __FILE__, __LINE__ + 1
162
+ else
163
+ raise ScenarioFileNotFoundError.new("File #{scenario_file} not found")
164
+ end
165
+ @scenarios_loaded = true
166
+ end
167
+ end
168
+
169
+ # you need to override this method to provide a completion
170
+ # criteria. Will raise an error otherwise
171
+ def completed?(resource)
172
+ raise CompletionCriteriaMissingError.new
173
+ end
174
+
175
+ private
176
+
177
+ def when_factory(concat)
178
+ rule = when_rules.find do |rule|
179
+ concat.content.match(rule[0])
180
+ end
181
+ if rule.nil?
182
+ raise Restfulie::Client::Mikyung::ConfigurationError, "You forgot to create '#{concat.content}' prior to its usage."
183
+ end
184
+ Restfulie::Client::Mikyung::WhenCondition.new(concat.content, rule, concat.content.match(rule[0]))
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+
191
+
@@ -0,0 +1,38 @@
1
+ # A steady walker that tries 3 times each step
2
+ module Restfulie
3
+ module Client
4
+ module Mikyung
5
+ class SteadyStateWalker
6
+ def move(goal, current, mikyung)
7
+ step = goal.next_step(current, mikyung)
8
+ raise UnableToAchieveGoalError, "No step was found for #{current}" unless step
9
+ Common::Logger.logger.debug "Mikyung > next step will be #{step}"
10
+ step = step.new if step.kind_of? Class
11
+ try_to_execute(step, current, 3, mikyung)
12
+ end
13
+
14
+ private
15
+
16
+ def try_to_execute(step, current, max_attempts, mikyung)
17
+ raise "Unable to proceed when trying to #{step}" if max_attempts == 0
18
+
19
+ resource = step
20
+ raise "Step returned 'give up'" if resource.nil?
21
+
22
+ if step.respond_to?(:execute)
23
+ resource = step.execute(current, mikyung)
24
+ end
25
+
26
+ # TODO: should it really retry if it is not 200? Or only if it is on the 50x family?
27
+ # for instance the result could be a 302 redirection
28
+ unless resource.response.code == 200
29
+ try_to_execute(step, current, max_attempts-1, mikyung)
30
+ else
31
+ Common::Logger.logger.debug resource.response.body
32
+ resource
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ # A conclusion to a step.
2
+ #
3
+ # Whenever a step rule matches, there are a series of conditions to be executed.
4
+ module Restfulie
5
+ module Client
6
+ module Mikyung
7
+ class ThenCondition
8
+ attr_reader :description
9
+
10
+ # creates a new result, based on this description
11
+ def initialize(description)
12
+ @description = description
13
+ end
14
+
15
+ # finds the rule for this result and executes it
16
+ def execute(resource, goal, mikyung)
17
+ goal.then_rules.each do |rule|
18
+ if (matches = Regexp.new(rule[0]).match(@description))
19
+ return invoke_rule(rule[1], resource, matches, mikyung)
20
+ end
21
+ end
22
+ nil
23
+ end
24
+
25
+ private
26
+ def invoke_rule(rule, resource, matches, mikyung)
27
+ case rule.arity
28
+ when 1
29
+ rule.call(resource)
30
+ when 2
31
+ rule.call(resource, matches)
32
+ else
33
+ rule.call(resource, matches, mikyung)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,57 @@
1
+ # Checks whether one or more rule holds and is capable of executing results.
2
+ module Restfulie
3
+ module Client
4
+ module Mikyung
5
+ # Creates a conditional execution based on a description.
6
+ # Its rule is an array where its first element represents a rule name and second a lambda that returns true or false
7
+ # Its params are extra params that might be passed to the rule
8
+ # Example
9
+ # WhenCondition.new("when running", ["", lambda { |resource| resource.human.state=='running' }], "")
10
+ class WhenCondition
11
+ def initialize(description, rule, params)
12
+ @description = description
13
+ @results = []
14
+ @extra = []
15
+ @rule = rule
16
+ @params = params
17
+ end
18
+
19
+ # will execute the first attached result
20
+ def execute(resource, goal, mikyung)
21
+ @results.each do |result|
22
+ Restfulie::Common::Logger.logger.info("will '#{result.description}'")
23
+ return result.execute(resource, goal, mikyung)
24
+ end
25
+ end
26
+
27
+ # checks whether this step should execute for a specific resource
28
+ def should_run_for(resource, goal)
29
+ if @rule[1].arity == 2
30
+ rule_accepts = @rule[1].call(resource, @params)
31
+ else
32
+ rule_accepts = @rule[1].call(resource)
33
+ end
34
+ return false unless rule_accepts
35
+ !@extra.find do |condition|
36
+ !condition.should_run_for(resource, goal)
37
+ end
38
+ end
39
+
40
+ # adds an extra condition to this step
41
+ def and(condition)
42
+ @extra << condition
43
+ end
44
+
45
+ # adds an extra condition to this step
46
+ def but(condition)
47
+ @extra << condition
48
+ end
49
+
50
+ # adds an extra result to this step
51
+ def results_on(result)
52
+ @results << result
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,15 @@
1
+ module Restfulie
2
+ module Client
3
+ module Mikyung
4
+ Dir["#{File.dirname(__FILE__)}/mikyung/*.rb"].each {|f| autoload File.basename(f)[0..-4].camelize.to_sym, f }
5
+ end
6
+ end
7
+ end
8
+
9
+ # Restfulie::Mikyung entry point is based on its core
10
+ # implementation.
11
+ module Restfulie
12
+ class Mikyung < Restfulie::Client::Mikyung::Core
13
+ Restfulie::Common::Logger.logger.level = Logger::INFO
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ require 'restfulie/common'
2
+
3
+ module Restfulie
4
+ module Client
5
+ autoload :MasterDelegator, 'restfulie/client/master_delegator'
6
+ autoload :HTTP, 'restfulie/client/http'
7
+ autoload :Configuration, 'restfulie/client/configuration'
8
+ autoload :EntryPoint, 'restfulie/client/entry_point'
9
+ autoload :Base, 'restfulie/client/base'
10
+ autoload :Mikyung, 'restfulie/client/mikyung'
11
+ autoload :Cache, 'restfulie/client/cache'
12
+ autoload :Feature, 'restfulie/client/feature'
13
+ autoload :Dsl, 'restfulie/client/dsl'
14
+
15
+ mattr_accessor :cache_provider, :cache_store
16
+
17
+ Restfulie::Client.cache_store = ActiveSupport::Cache::MemoryStore.new
18
+ Restfulie::Client.cache_provider = Restfulie::Client::Cache::Basic.new
19
+
20
+ end
21
+ end
22
+
23
+ require 'restfulie/client/ext/http_ext'
24
+ require 'restfulie/client/ext/atom_ext'
25
+ require 'restfulie/client/ext/json_ext'
26
+
@@ -0,0 +1,91 @@
1
+ require 'active_support/core_ext/hash/conversions'
2
+
3
+ module Restfulie
4
+ module Common
5
+ module Converter
6
+ module Atom
7
+ module Base
8
+ module ClassMethods
9
+ mattr_reader :media_type_name
10
+ @@media_type_name = 'application/atom+xml'
11
+
12
+ mattr_reader :headers
13
+ @@headers = {
14
+ :get => { 'Accept' => media_type_name },
15
+ :post => { 'Content-Type' => media_type_name }
16
+ }
17
+
18
+ mattr_reader :recipes
19
+ @@recipes = {}
20
+
21
+ def helper
22
+ Restfulie::Common::Converter::Atom::Helpers
23
+ end
24
+
25
+ def describe_recipe(recipe_name, options={}, &block)
26
+ raise 'Undefined recipe' unless block_given?
27
+ raise 'Undefined recipe_name' unless recipe_name
28
+ @@recipes[recipe_name] = block
29
+ end
30
+
31
+ def to_atom(obj = nil, options = {}, &block)
32
+ # just instantiate the string with the atom factory
33
+ return Restfulie::Common::Representation::Atom::Factory.create(obj) if obj.kind_of?(String)
34
+
35
+ if block_given?
36
+ recipe = block
37
+ elsif options[:recipe]
38
+ recipe = @@recipes[options[:recipe]]
39
+ else
40
+ return obj if obj.respond_to?(:atom_type) && (obj.atom_type == "feed" || obj.atom_type == "entry")
41
+ raise Restfulie::Common::Error::ConverterError.new("Recipe required")
42
+ end
43
+
44
+ # execute with the builder if a recipe is set (even if the obj is an atom)
45
+ options[:atom_type] ||= obj.respond_to?(:each) ? :feed : :entry
46
+ raise Restfulie::Common::Error::ConverterError.new("Undefined atom type #{options[:atom_type]}") unless [:entry,:feed].include?(options[:atom_type])
47
+
48
+ # Create representation and proxy
49
+ builder = Builder.new(options[:atom_type], obj)
50
+
51
+ # Check recipe arity size before calling it
52
+ recipe.call(*[builder, obj, options][0,recipe.arity])
53
+
54
+ builder.representation
55
+ end
56
+
57
+ alias_method :unmarshal, :to_atom
58
+
59
+ def to_hash(obj)
60
+ return obj if obj.kind_of?(Hash)
61
+
62
+ xml = nil
63
+
64
+ if obj.kind_of?(::String)
65
+ xml = obj
66
+ elsif obj.respond_to?(:to_xml)
67
+ xml = obj.to_xml
68
+ end
69
+
70
+ Hash.from_xml(xml).with_indifferent_access unless xml.nil?
71
+ end
72
+
73
+
74
+ def to_s(obj)
75
+ return obj if obj.kind_of?(String)
76
+ if obj.respond_to?(:to_xml)
77
+ obj.to_xml.to_s
78
+ else
79
+ obj.to_s
80
+ end
81
+ end
82
+
83
+ def marshal(obj, options = nil)
84
+ to_atom(obj, options).to_xml
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,111 @@
1
+ module Restfulie
2
+ module Common
3
+ module Converter
4
+ module Atom
5
+ class Builder
6
+ attr_accessor :atom_type
7
+ def initialize(atom_type, obj)
8
+ @doc = Nokogiri::XML::Document.new
9
+ @obj = obj
10
+ @parent = @doc.create_element(atom_type.to_s)
11
+ @parent.add_namespace_definition(nil, "http://www.w3.org/2005/Atom")
12
+ @parent.parent = @doc
13
+ end
14
+
15
+ def values(options = {}, &block)
16
+ options.each do |key,value|
17
+ attr = key.to_s
18
+ if attr =~ /^xmlns(:\w+)?$/
19
+ ns = attr.split(":", 2)[1]
20
+ @parent.add_namespace_definition(ns, value)
21
+ end
22
+ end
23
+ yield Values.new(self)
24
+ end
25
+
26
+ def members(options = {}, &block)
27
+ collection = options[:collection] || @obj
28
+ raise Error::BuilderError.new("Members method require a collection to execute") unless collection.respond_to?(:each)
29
+ collection.each do |member|
30
+ entry = @doc.create_element("entry")
31
+ entry.parent = @parent
32
+ @parent = entry
33
+ block.call(self, member)
34
+ @parent = entry.parent
35
+ end
36
+ end
37
+
38
+ def link(relationship, uri, options = {})
39
+ options["rel"] = relationship.to_s
40
+ options["href"] = uri
41
+ options["type"] ||= "application/atom+xml"
42
+ insert_value("link", nil, options)
43
+ end
44
+
45
+ def insert_value(name, prefix, *args, &block)
46
+ node = create_element(name.to_s, prefix, *args)
47
+ node.parent = @parent
48
+ if block_given?
49
+ @parent = node
50
+ block.call
51
+ @parent = node.parent
52
+ end
53
+ end
54
+
55
+ def representation
56
+ Representation::Atom::Factory.create(@doc)
57
+ end
58
+
59
+ private
60
+
61
+ def create_element(node, prefix, *args)
62
+ node = @doc.create_element(node) do |n|
63
+ if prefix
64
+ if namespace = prefix_valid?(prefix)
65
+ # Adding namespace prefix
66
+ n.namespace = namespace
67
+ namespace = nil
68
+ end
69
+ end
70
+
71
+ args.each do |arg|
72
+ case arg
73
+ # Adding XML attributes
74
+ when Hash
75
+ arg.each { |k,v|
76
+ key = k.to_s
77
+ if key =~ /^xmlns(:\w+)?$/
78
+ ns_name = key.split(":", 2)[1]
79
+ n.add_namespace_definition(ns_name, v)
80
+ next
81
+ end
82
+ n[k.to_s] = v.to_s
83
+ }
84
+ # Adding XML node content
85
+ else
86
+ arg.kind_of?(Time) || arg.kind_of?(DateTime) ? content = arg.xmlschema : content = arg
87
+ n.content = content
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def prefix_valid?(prefix)
94
+ ns = @parent.namespace_definitions.find { |x| x.prefix == prefix.to_s }
95
+
96
+ unless ns
97
+ @parent.ancestors.each do |a|
98
+ next if a == @doc
99
+ ns = a.namespace_definitions.find { |x| x.prefix == prefix.to_s }
100
+ break if ns
101
+ end
102
+ end
103
+
104
+ return ns
105
+ #TODO: raise ArgumentError, "Namespace #{prefix} has not been defined" if wanted
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,17 @@
1
+ module Restfulie
2
+ module Common
3
+ module Converter
4
+ module Atom
5
+ module Helpers
6
+ def collection(obj, *args, &block)
7
+ Restfulie::Common::Converter::Atom.to_atom(obj, :atom_type => :feed, &block)
8
+ end
9
+
10
+ def member(obj, *args, &block)
11
+ Restfulie::Common::Converter::Atom.to_atom(obj, :atom_type => :entry, &block)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module Restfulie
2
+ module Common
3
+ module Converter
4
+ module Atom
5
+ autoload :Base, 'restfulie/common/converter/atom/base'
6
+ autoload :Builder, 'restfulie/common/converter/atom/builder'
7
+ autoload :Helpers, 'restfulie/common/converter/atom/helpers'
8
+ extend Base::ClassMethods
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,87 @@
1
+ module Restfulie
2
+ module Common
3
+ module Converter
4
+ module Json
5
+ module Base
6
+ module ClassMethods
7
+ mattr_reader :media_type_name
8
+ @@media_type_name = 'application/json'
9
+
10
+ mattr_reader :headers
11
+ @@headers = {
12
+ :get => { 'Accept' => media_type_name },
13
+ :post => { 'Content-Type' => media_type_name }
14
+ }
15
+
16
+ mattr_reader :recipes
17
+ @@recipes = {}
18
+
19
+ def describe_recipe(recipe_name, options={}, &block)
20
+ raise 'Undefined recipe' unless block_given?
21
+ raise 'Undefined recipe_name' unless recipe_name
22
+ @@recipes[recipe_name] = block
23
+ end
24
+
25
+ def to_json(obj = nil, options = {:root => {}}, &block)
26
+ # just instantiate the string with the atom factory
27
+ return Restfulie::Common::Representation::Json.create(obj) if obj.kind_of?(String)
28
+
29
+ if block_given?
30
+ recipe = block
31
+ else
32
+ recipe = options[:recipe]
33
+ end
34
+
35
+ # Check if the object is already an json
36
+ unless recipe
37
+ if obj.kind_of?(Hash) || obj.kind_of?(Array)
38
+ return Restfulie::Common::Representation::Json.create(obj)
39
+ else
40
+ raise Restfulie::Common::Error::ConverterError.new("Recipe required")
41
+ end
42
+ end
43
+
44
+ # Get recipe already described
45
+ recipe = @@recipes[recipe] unless recipe.respond_to?(:call)
46
+
47
+ # Create representation and proxy
48
+ builder = Restfulie::Common::Converter::Json::Builder.new(obj, (options[:root] || {}))
49
+
50
+ # Check recipe arity size before calling it
51
+ recipe.call(*[builder, obj, options][0,recipe.arity])
52
+
53
+ builder.representation
54
+ end
55
+
56
+ alias_method :unmarshal, :to_json
57
+
58
+ def to_hash(obj)
59
+ return obj if obj.kind_of?(Hash) || obj.kind_of?(Array)
60
+
61
+ if obj.kind_of?(::String)
62
+ Restfulie::Common::Representation::Json.create(obj)
63
+ else
64
+ raise Restfulie::Common::Error::ConverterError.new("It was not possible to convert this object to a Hash")
65
+ end
66
+ end
67
+
68
+ def to_s(obj)
69
+ return obj if obj.kind_of?(String)
70
+ Restfulie::Common::Representation::Json.create(obj).to_s
71
+ end
72
+
73
+ def marshal(obj, options = nil)
74
+ return obj if obj.kind_of? String
75
+ to_json(obj, options).to_json
76
+ end
77
+
78
+ # returns the current helper
79
+ def helper
80
+ Helpers
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end