praxis 0.14.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -1
- data/Gemfile +5 -0
- data/bin/praxis +7 -4
- data/lib/api_browser/package.json +1 -1
- data/lib/praxis/action_definition.rb +10 -5
- data/lib/praxis/application.rb +30 -0
- data/lib/praxis/bootloader_stages/subgroup_loader.rb +2 -2
- data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +7 -2
- data/lib/praxis/file_group.rb +1 -1
- data/lib/praxis/handlers/json.rb +32 -0
- data/lib/praxis/handlers/www_form.rb +20 -0
- data/lib/praxis/handlers/xml.rb +78 -0
- data/lib/praxis/links.rb +4 -1
- data/lib/praxis/media_type.rb +55 -0
- data/lib/praxis/media_type_identifier.rb +198 -0
- data/lib/praxis/multipart/part.rb +16 -0
- data/lib/praxis/request.rb +34 -25
- data/lib/praxis/response.rb +22 -3
- data/lib/praxis/response_definition.rb +11 -36
- data/lib/praxis/restful_doc_generator.rb +1 -1
- data/lib/praxis/simple_media_type.rb +6 -1
- data/lib/praxis/types/media_type_common.rb +8 -3
- data/lib/praxis/types/multipart.rb +6 -6
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +7 -1
- data/praxis.gemspec +1 -1
- data/spec/functional_spec.rb +3 -1
- data/spec/praxis/application_spec.rb +58 -1
- data/spec/praxis/handlers/json_spec.rb +37 -0
- data/spec/praxis/handlers/xml_spec.rb +155 -0
- data/spec/praxis/media_type_identifier_spec.rb +209 -0
- data/spec/praxis/media_type_spec.rb +50 -3
- data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +2 -2
- data/spec/praxis/request_spec.rb +39 -1
- data/spec/praxis/response_definition_spec.rb +12 -9
- data/spec/praxis/response_spec.rb +37 -6
- data/spec/praxis/types/collection_spec.rb +2 -2
- data/spec/praxis/types/multipart_spec.rb +17 -0
- data/spec/spec_app/app/controllers/instances.rb +1 -1
- data/spec/support/spec_media_types.rb +19 -0
- metadata +11 -6
- data/lib/praxis/content_type_parser.rb +0 -62
- data/spec/praxis/content_type_parser_spec.rb +0 -91
data/lib/praxis/request.rb
CHANGED
@@ -12,8 +12,8 @@ module Praxis
|
|
12
12
|
API_VERSION_HEADER_NAME = "HTTP_X_API_VERSION".freeze
|
13
13
|
API_VERSION_PARAM_NAME = 'api_version'.freeze
|
14
14
|
API_NO_VERSION_NAME = 'n/a'.freeze
|
15
|
-
VERSION_USING_DEFAULTS = [:header
|
16
|
-
|
15
|
+
VERSION_USING_DEFAULTS = [:header, :params].freeze
|
16
|
+
|
17
17
|
def initialize(env)
|
18
18
|
@env = env
|
19
19
|
@query = Rack::Utils.parse_nested_query(env[QUERY_STRING_NAME])
|
@@ -21,18 +21,25 @@ module Praxis
|
|
21
21
|
@path_version_matcher = path_version_matcher
|
22
22
|
end
|
23
23
|
|
24
|
+
# Determine the content type of this request as indicated by the Content-Type header.
|
25
|
+
#
|
26
|
+
# @return [nil,MediaTypeIdentifier] nil if the header is missing, else a media-type identifier
|
24
27
|
def content_type
|
25
|
-
@env[CONTENT_TYPE_NAME]
|
28
|
+
header = @env[CONTENT_TYPE_NAME]
|
29
|
+
@content_type ||= (header && MediaTypeIdentifier.load(header)).freeze
|
26
30
|
end
|
27
31
|
|
28
|
-
# The media type (type/subtype) portion of the
|
29
|
-
# without any media type parameters. e.g., when
|
30
|
-
# "text/plain;charset=utf-8", the media-type is "text/plain".
|
32
|
+
# The media type (type/subtype+suffix) portion of the Content-Type
|
33
|
+
# header without any media type parameters. e.g., when Content-Type
|
34
|
+
# is "text/plain;charset=utf-8", the media-type is "text/plain".
|
31
35
|
#
|
32
36
|
# For more information on the use of media types in HTTP, see:
|
33
37
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
38
|
+
#
|
39
|
+
# @return [String]
|
40
|
+
# @see MediaTypeIdentifier#without_parameters
|
34
41
|
def media_type
|
35
|
-
content_type
|
42
|
+
content_type.without_parameters.to_s
|
36
43
|
end
|
37
44
|
|
38
45
|
def path
|
@@ -71,27 +78,27 @@ module Praxis
|
|
71
78
|
self.raw_params
|
72
79
|
self.raw_payload
|
73
80
|
end
|
74
|
-
|
81
|
+
|
75
82
|
def self.path_version_prefix
|
76
83
|
PATH_VERSION_PREFIX
|
77
84
|
end
|
78
85
|
|
79
86
|
PATH_VERSION_MATCHER = %r{^#{self.path_version_prefix}(?<version>[^\/]+)\/}.freeze
|
80
|
-
|
87
|
+
|
81
88
|
def path_version_matcher
|
82
89
|
PATH_VERSION_MATCHER
|
83
90
|
end
|
84
|
-
|
85
|
-
def version(using: VERSION_USING_DEFAULTS
|
91
|
+
|
92
|
+
def version(using: VERSION_USING_DEFAULTS)
|
86
93
|
result = nil
|
87
94
|
Array(using).find do |mode|
|
88
95
|
case mode
|
89
|
-
when :header
|
96
|
+
when :header;
|
90
97
|
result = env[API_VERSION_HEADER_NAME]
|
91
|
-
when :params
|
98
|
+
when :params;
|
92
99
|
result = @query[API_VERSION_PARAM_NAME]
|
93
|
-
when :path
|
94
|
-
m = self.path.match(@path_version_matcher)
|
100
|
+
when :path;
|
101
|
+
m = self.path.match(@path_version_matcher)
|
95
102
|
result = m[:version] unless m.nil?
|
96
103
|
else
|
97
104
|
raise "Unknown method for retrieving the API version: #{mode}"
|
@@ -102,7 +109,7 @@ module Praxis
|
|
102
109
|
|
103
110
|
def load_headers(context)
|
104
111
|
return unless action.headers
|
105
|
-
defined_headers = action.precomputed_header_keys_for_rack.each_with_object(Hash.new) do |(upper,original), hash|
|
112
|
+
defined_headers = action.precomputed_header_keys_for_rack.each_with_object(Hash.new) do |(upper, original), hash|
|
106
113
|
hash[original] = self.env[upper] if self.env.has_key? upper
|
107
114
|
end
|
108
115
|
self.headers = action.headers.load(defined_headers, context)
|
@@ -115,16 +122,18 @@ module Praxis
|
|
115
122
|
|
116
123
|
def load_payload(context)
|
117
124
|
return unless action.payload
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
125
|
+
return if content_type.nil?
|
126
|
+
|
127
|
+
handler = Praxis::Application.instance.handlers[content_type.handler_name]
|
128
|
+
|
129
|
+
if handler
|
130
|
+
raw = handler.parse(self.raw_payload)
|
131
|
+
else
|
132
|
+
# TODO is this a good default?
|
133
|
+
raw = self.raw_payload
|
134
|
+
end
|
126
135
|
|
127
|
-
self.payload = action.payload.load(raw, context, content_type: content_type)
|
136
|
+
self.payload = action.payload.load(raw, context, content_type: content_type.to_s)
|
128
137
|
end
|
129
138
|
|
130
139
|
def validate_headers(context)
|
data/lib/praxis/response.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
module Praxis
|
2
2
|
class Response
|
3
|
-
|
4
|
-
|
5
3
|
attr_reader :name
|
6
4
|
attr_reader :parts
|
7
5
|
|
@@ -29,6 +27,22 @@ module Praxis
|
|
29
27
|
@parts = Hash.new
|
30
28
|
end
|
31
29
|
|
30
|
+
# Determine the content type of this response.
|
31
|
+
#
|
32
|
+
# @return [MediaTypeIdentifier]
|
33
|
+
def content_type
|
34
|
+
MediaTypeIdentifier.load(headers['Content-Type']).freeze
|
35
|
+
end
|
36
|
+
|
37
|
+
# Set the content type for this response.
|
38
|
+
# @todo DRY this out (also used in Multipart::Part)
|
39
|
+
#
|
40
|
+
# @return [String]
|
41
|
+
# @param [String,MediaTypeIdentifier] identifier
|
42
|
+
def content_type=(identifier)
|
43
|
+
headers['Content-Type'] = MediaTypeIdentifier.load(identifier).to_s
|
44
|
+
end
|
45
|
+
|
32
46
|
def handle
|
33
47
|
end
|
34
48
|
|
@@ -54,7 +68,12 @@ module Praxis
|
|
54
68
|
def encode!
|
55
69
|
case @body
|
56
70
|
when Hash, Array
|
57
|
-
|
71
|
+
# response payload is structured data; transform it into an entity using the handler
|
72
|
+
# implied by the response's media type. If no handler is registered for this
|
73
|
+
# name, assume JSON as a default handler.
|
74
|
+
handlers = Praxis::Application.instance.handlers
|
75
|
+
handler = (content_type && handlers[content_type.handler_name]) || handlers['json']
|
76
|
+
@body = handler.generate(@body)
|
58
77
|
end
|
59
78
|
end
|
60
79
|
|
@@ -210,19 +210,15 @@ module Praxis
|
|
210
210
|
case value
|
211
211
|
when String
|
212
212
|
if response.headers[name] != value
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
)
|
217
|
-
end
|
213
|
+
raise Exceptions::Validation.new(
|
214
|
+
"Header #{name.inspect}, with value #{value.inspect} does not match #{response.headers[name]}."
|
215
|
+
)
|
218
216
|
end
|
219
217
|
when Regexp
|
220
218
|
if response.headers[name] !~ value
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
)
|
225
|
-
end
|
219
|
+
raise Exceptions::Validation.new(
|
220
|
+
"Header #{name.inspect}, with value #{value.inspect} does not match #{response.headers[name].inspect}."
|
221
|
+
)
|
226
222
|
end
|
227
223
|
end
|
228
224
|
end
|
@@ -238,36 +234,15 @@ module Praxis
|
|
238
234
|
def validate_content_type!(response)
|
239
235
|
return unless media_type
|
240
236
|
|
241
|
-
response_content_type =
|
242
|
-
|
243
|
-
response_content_type = Praxis::ContentTypeParser.parse(response.headers['Content-Type'])
|
244
|
-
end
|
245
|
-
|
246
|
-
expected_content_type = Praxis::ContentTypeParser.parse(media_type.identifier)
|
237
|
+
response_content_type = response.content_type
|
238
|
+
expected_content_type = Praxis::MediaTypeIdentifier.load(media_type.identifier)
|
247
239
|
|
248
|
-
unless response_content_type
|
240
|
+
unless expected_content_type.match(response_content_type)
|
249
241
|
raise Exceptions::Validation.new(
|
250
|
-
"Bad Content-Type header. #{
|
251
|
-
"
|
242
|
+
"Bad Content-Type header. #{response_content_type}" +
|
243
|
+
" is incompatible with #{expected_content_type} as described in response: #{self.name}"
|
252
244
|
)
|
253
245
|
end
|
254
|
-
|
255
|
-
if (expected_params = expected_content_type[:params])
|
256
|
-
expected_params.each do |param_name,expected_param|
|
257
|
-
response_param = response_content_type[:params].fetch(param_name) do
|
258
|
-
raise Exceptions::Validation.new(
|
259
|
-
"Bad Content-Type header: #{response.headers['Content-Type']}" +
|
260
|
-
" does not contain expected param '#{param_name}' as described in response: #{self.name}"
|
261
|
-
)
|
262
|
-
end
|
263
|
-
unless response_param == expected_param
|
264
|
-
raise Exceptions::Validation.new(
|
265
|
-
"Bad Content-Type header: #{response.headers['Content-Type']}" +
|
266
|
-
" param: #{param_name} does not match expected value #{expected_param} as described in response: #{self.name}"
|
267
|
-
)
|
268
|
-
end
|
269
|
-
end
|
270
|
-
end
|
271
246
|
end
|
272
247
|
|
273
248
|
def validate_parts!(response)
|
@@ -1,5 +1,10 @@
|
|
1
1
|
module Praxis
|
2
|
-
|
2
|
+
|
3
|
+
# Stripped-down representation of an Internet Media Type where the structure and content of the
|
4
|
+
# type are unknown, or are defined externally to the Praxis application.
|
5
|
+
#
|
6
|
+
# @see Praxis::MediaType
|
7
|
+
# @see Praxis::Types::MediaTypeCommon
|
3
8
|
SimpleMediaType = Struct.new(:identifier) do
|
4
9
|
def name
|
5
10
|
self.class.name
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Praxis
|
2
2
|
module Types
|
3
3
|
|
4
|
+
# Traits that are shared by MediaType and SimpleMediaType.
|
4
5
|
module MediaTypeCommon
|
5
6
|
extend ::ActiveSupport::Concern
|
6
7
|
|
@@ -18,10 +19,14 @@ module Praxis
|
|
18
19
|
@description
|
19
20
|
end
|
20
21
|
|
22
|
+
# Get or set the identifier of this media type.
|
23
|
+
#
|
24
|
+
# @deprecated this method is not deprecated, but its return type will change to MediaTypeIdentifier in Praxis 1.0
|
25
|
+
#
|
26
|
+
# @return [String] the string-representation of this type's identifier
|
21
27
|
def identifier(identifier=nil)
|
22
|
-
return @identifier unless identifier
|
23
|
-
|
24
|
-
@identifier = identifier
|
28
|
+
return @identifier.to_s unless identifier
|
29
|
+
(@identifier = MediaTypeIdentifier.load(identifier)).to_s
|
25
30
|
end
|
26
31
|
end
|
27
32
|
|
@@ -4,11 +4,14 @@ module Praxis
|
|
4
4
|
|
5
5
|
@key_type = Attributor::String
|
6
6
|
|
7
|
-
def self.load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, content_type:)
|
8
|
-
|
7
|
+
def self.load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, content_type:nil)
|
8
|
+
return value if value.kind_of?(self) || value.nil?
|
9
9
|
|
10
|
+
unless (value.kind_of?(::String) && ! content_type.nil?)
|
11
|
+
raise Attributor::CoercionError, context: context, from: value.class, to: self.name, value: value
|
12
|
+
end
|
13
|
+
|
10
14
|
headers = {'Content-Type' => content_type}
|
11
|
-
|
12
15
|
parser = MultipartParser.new(headers, value)
|
13
16
|
preamble, parts = parser.parse
|
14
17
|
|
@@ -60,9 +63,6 @@ module Praxis
|
|
60
63
|
super
|
61
64
|
end
|
62
65
|
|
63
|
-
#def []=(k, v)
|
64
|
-
#end
|
65
|
-
|
66
66
|
end
|
67
67
|
|
68
68
|
|
data/lib/praxis/version.rb
CHANGED
data/lib/praxis.rb
CHANGED
@@ -50,7 +50,6 @@ module Praxis
|
|
50
50
|
autoload :Router, 'praxis/router'
|
51
51
|
autoload :SimpleMediaType, 'praxis/simple_media_type'
|
52
52
|
autoload :Stage, 'praxis/stage'
|
53
|
-
autoload :ContentTypeParser, 'praxis/content_type_parser'
|
54
53
|
|
55
54
|
autoload :Stats, 'praxis/stats'
|
56
55
|
autoload :Notifications, 'praxis/notifications'
|
@@ -65,6 +64,7 @@ module Praxis
|
|
65
64
|
autoload :Links, 'praxis/links'
|
66
65
|
autoload :MediaType, 'praxis/media_type'
|
67
66
|
autoload :MediaTypeCollection, 'praxis/media_type_collection'
|
67
|
+
autoload :MediaTypeIdentifier, 'praxis/media_type_identifier'
|
68
68
|
autoload :Multipart, 'praxis/types/multipart'
|
69
69
|
autoload :Collection, 'praxis/collection'
|
70
70
|
|
@@ -91,6 +91,12 @@ module Praxis
|
|
91
91
|
require 'praxis/responses/internal_server_error'
|
92
92
|
require 'praxis/responses/validation_error'
|
93
93
|
|
94
|
+
module Handlers
|
95
|
+
autoload :JSON, 'praxis/handlers/json'
|
96
|
+
autoload :WWWForm, 'praxis/handlers/www_form'
|
97
|
+
autoload :XML, 'praxis/handlers/xml'
|
98
|
+
end
|
99
|
+
|
94
100
|
module BootloaderStages
|
95
101
|
autoload :FileLoader, 'praxis/bootloader_stages/file_loader'
|
96
102
|
autoload :Environment, 'praxis/bootloader_stages/environment'
|
data/praxis.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_dependency 'mime', '~> 0'
|
27
27
|
spec.add_dependency 'praxis-mapper', '~> 3.3'
|
28
28
|
spec.add_dependency 'praxis-blueprints', '~> 1.3'
|
29
|
-
spec.add_dependency 'attributor', '~> 2.6
|
29
|
+
spec.add_dependency 'attributor', '~> 2.6'
|
30
30
|
spec.add_dependency 'thor', '~> 0.18'
|
31
31
|
spec.add_dependency 'terminal-table', '~> 1.4'
|
32
32
|
spec.add_dependency 'harness', '~> 2'
|
data/spec/functional_spec.rb
CHANGED
@@ -58,7 +58,8 @@ describe 'Functional specs' do
|
|
58
58
|
end
|
59
59
|
|
60
60
|
it 'works' do
|
61
|
-
|
61
|
+
the_body = StringIO.new("{}") # This is a funny, GET request expecting a body
|
62
|
+
get '/clouds/1/instances/2?junk=foo&api_version=1.0', nil,'rack.input' => the_body,'CONTENT_TYPE' => "application/json", 'global_session' => session
|
62
63
|
expect(last_response.status).to eq(200)
|
63
64
|
expected = {
|
64
65
|
"cloud_id" => 1,
|
@@ -101,6 +102,7 @@ describe 'Functional specs' do
|
|
101
102
|
let(:body) { form.body.to_s }
|
102
103
|
|
103
104
|
it 'works' do
|
105
|
+
|
104
106
|
post '/clouds/1/instances?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
|
105
107
|
|
106
108
|
_reponse_preamble, response = Praxis::MultipartParser.parse(last_response.headers, last_response.body)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Praxis::Application do
|
4
|
-
|
4
|
+
context 'configuration' do
|
5
5
|
subject(:app) do
|
6
6
|
app = Class.new(Praxis::Application).instance
|
7
7
|
|
@@ -43,4 +43,61 @@ describe Praxis::Application do
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
46
|
+
|
47
|
+
context 'media type handlers' do
|
48
|
+
subject { Class.new(Praxis::Application).instance }
|
49
|
+
|
50
|
+
before do
|
51
|
+
bootloader = double('bootloader')
|
52
|
+
allow(bootloader).to receive(:setup!).and_return(true)
|
53
|
+
|
54
|
+
app = double('built Rack app')
|
55
|
+
|
56
|
+
builder = double('Rack builder')
|
57
|
+
allow(builder).to receive(:run)
|
58
|
+
allow(builder).to receive(:to_app).and_return(app)
|
59
|
+
|
60
|
+
subject.instance_variable_set(:@bootloader, bootloader)
|
61
|
+
subject.instance_variable_set(:@builder, builder)
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#handler' do
|
65
|
+
let(:new_handler_name) { 'awesomesauce' }
|
66
|
+
let(:new_handler_instance) { double('awesomesauce instance', generate: '', parse: {}) }
|
67
|
+
let(:new_handler_class) { double('awesomesauce', new: new_handler_instance) }
|
68
|
+
let(:bad_handler_instance) { double('bad handler instance', wokka: true, meep: false) }
|
69
|
+
let(:bad_handler_class) { double('bad handler', new: bad_handler_instance) }
|
70
|
+
|
71
|
+
context 'given a Class' do
|
72
|
+
it 'instantiates and registers an instance' do
|
73
|
+
expect(new_handler_class).to receive(:new)
|
74
|
+
subject.handler new_handler_name, new_handler_class
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'given a non-Class' do
|
79
|
+
it 'raises' do
|
80
|
+
expect {
|
81
|
+
subject.handler('awesomesauce', 'hi') # no instances allowed
|
82
|
+
}.to raise_error(NoMethodError)
|
83
|
+
|
84
|
+
expect {
|
85
|
+
subject.handler('awesomesauce', ::Kernel) # no modules allowed
|
86
|
+
}.to raise_error(NoMethodError)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'overrides default handlers' do
|
91
|
+
subject.handler 'json', new_handler_class
|
92
|
+
subject.setup
|
93
|
+
expect(subject.handlers['json']).to eq(new_handler_instance)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'ensures that handlers will work' do
|
97
|
+
expect {
|
98
|
+
subject.handler new_handler_name, bad_handler_class
|
99
|
+
}.to raise_error(ArgumentError)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
46
103
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Praxis::Handlers::JSON do
|
4
|
+
let(:dictionary) { {"foo" => 1} }
|
5
|
+
let(:dictionary_json) { '{"foo":1}' }
|
6
|
+
|
7
|
+
let(:array) { [1,2,3] }
|
8
|
+
let(:array_json) { '[1,2,3]' }
|
9
|
+
|
10
|
+
describe '#parse' do
|
11
|
+
it 'handles dictionaries' do
|
12
|
+
expect(subject.parse(dictionary_json)).to eq(dictionary)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'handles arrays' do
|
16
|
+
expect(subject.parse(array_json)).to eq(array)
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# slightly cheesy: use #parse to test #generate by round-tripping everything
|
22
|
+
describe '#generate' do
|
23
|
+
it 'pretty-prints' do
|
24
|
+
result = subject.generate({"foo" => 1})
|
25
|
+
expect(result).to include("\n")
|
26
|
+
expect(result).to match(/^ /m)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'handles dictionaries' do
|
30
|
+
expect(subject.parse(subject.generate(dictionary))).to eq(dictionary)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'handles arrays' do
|
34
|
+
expect(subject.parse(subject.generate(array))).to eq(array)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Praxis::Handlers::XML do
|
4
|
+
|
5
|
+
describe '#parse' do
|
6
|
+
after do
|
7
|
+
expect( subject.parse(xml) ).to eq(parsed)
|
8
|
+
end
|
9
|
+
|
10
|
+
# XML_TYPE_NAMES = {
|
11
|
+
# "Symbol" => "symbol",
|
12
|
+
# "Fixnum" => "integer",
|
13
|
+
# "Bignum" => "integer",
|
14
|
+
# "BigDecimal" => "decimal",
|
15
|
+
# "Float" => "float",
|
16
|
+
# "TrueClass" => "boolean",
|
17
|
+
# "FalseClass" => "boolean",
|
18
|
+
# "Date" => "date",
|
19
|
+
# "DateTime" => "dateTime",
|
20
|
+
# "Time" => "dateTime"
|
21
|
+
# }
|
22
|
+
|
23
|
+
context "Parsing symbols" do
|
24
|
+
let(:xml){ '<objects type="array"><object type="symbol">a_symbol</object></objects>' }
|
25
|
+
let(:parsed){ [:a_symbol] }
|
26
|
+
it 'works' do end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "Parsing integers" do
|
30
|
+
let(:xml){ '<objects type="array"><object type="integer">1234</object></objects>' }
|
31
|
+
let(:parsed){ [1234] }
|
32
|
+
it 'works' do end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "Parsing decimals" do
|
36
|
+
let(:xml){ '<objects type="array"><object type="decimal">0.1</object></objects>' }
|
37
|
+
let(:parsed){ [0.1] }
|
38
|
+
it 'works' do end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "Parsing floats" do
|
42
|
+
let(:xml){ '<objects type="array"><object type="float">0.1</object></objects>' }
|
43
|
+
let(:parsed){ [0.1] }
|
44
|
+
it 'works' do end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "Parsing booleans" do
|
48
|
+
context "that are true" do
|
49
|
+
let(:xml){ '<objects type="array"><object type="boolean">true</object></objects>' }
|
50
|
+
let(:parsed){ [true] }
|
51
|
+
it 'works' do end
|
52
|
+
end
|
53
|
+
context "that are false" do
|
54
|
+
let(:xml){ '<objects type="array"><object type="boolean">false</object></objects>' }
|
55
|
+
let(:parsed){ [false] }
|
56
|
+
it 'works' do end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "Parsing dates" do
|
61
|
+
let(:xml){ '<objects type="array"><object type="date">2001-01-01</object></objects>' }
|
62
|
+
let(:parsed){ [Date.parse("2001-01-01")] }
|
63
|
+
it 'works' do end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "Parsing dateTimes" do
|
67
|
+
let(:xml){ '<objects type="array"><object type="dateTime">2015-03-13T19:34:40-07:00</object></objects>' }
|
68
|
+
let(:parsed){ [DateTime.parse("2015-03-13T19:34:40-07:00")] }
|
69
|
+
it 'works' do end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "Parsing hashes" do
|
73
|
+
context "that are empty" do
|
74
|
+
let(:xml){ '<hash></hash>' }
|
75
|
+
let(:parsed){ {} }
|
76
|
+
it 'works' do end
|
77
|
+
end
|
78
|
+
context "with a couple of elements" do
|
79
|
+
let(:xml){ '<hash><one type="integer">1</one><two type="integer">2</two></hash>' }
|
80
|
+
let(:parsed){ {"one"=>1, "two"=>2} }
|
81
|
+
it 'works' do end
|
82
|
+
end
|
83
|
+
context "with a nested hash" do
|
84
|
+
let(:xml){ '<hash><one type="integer">1</one><sub_hash><first>hello</first></sub_hash></hash>' }
|
85
|
+
let(:parsed){ {"one"=>1, "sub_hash"=>{"first"=>"hello"} } }
|
86
|
+
it 'works' do end
|
87
|
+
end
|
88
|
+
context "with a nested array" do
|
89
|
+
let(:xml){ '<hash><one type="integer">1</one><two type="array"><object>just text</object></two></hash>' }
|
90
|
+
let(:parsed){ {"one"=>1, "two" => ["just text"] } }
|
91
|
+
it 'works' do end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
context "Parsing an Array" do
|
97
|
+
context 'with a couple of simple elements in it' do
|
98
|
+
let(:xml){ '<objects type="array"><object>just text</object><object type="integer">1</object></objects>' }
|
99
|
+
let(:parsed){ ["just text", 1] }
|
100
|
+
it 'works' do end
|
101
|
+
end
|
102
|
+
context "with a nested hash" do
|
103
|
+
let(:xml){ '<objects type="array"><object>just text</object><object><one type="integer">1</one></object></objects>' }
|
104
|
+
let(:parsed){ ["just text", { "one" => 1}] }
|
105
|
+
it 'works' do end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "Parsing XML strings created with .to_xml" do
|
110
|
+
let(:xml){ parsed.to_xml }
|
111
|
+
context 'array with a couple of simple elements in it' do
|
112
|
+
let(:parsed){ ["just text", 1] }
|
113
|
+
it 'works' do end
|
114
|
+
end
|
115
|
+
context 'a hash with a couple of simple elements in it' do
|
116
|
+
let(:parsed){ { "one"=>"just text", "two"=> 1 } }
|
117
|
+
it 'works' do end
|
118
|
+
end
|
119
|
+
context 'a array with elements of all types' do
|
120
|
+
let(:parsed){ ["just text",:a,1,BigDecimal.new(100),0.1,true,Date.new] }
|
121
|
+
it 'works' do end
|
122
|
+
end
|
123
|
+
context 'a hash with a complex substructure' do
|
124
|
+
let(:parsed) do
|
125
|
+
Hash(
|
126
|
+
"text" => "just text",
|
127
|
+
"symbol" => :a,
|
128
|
+
"num" => 1,
|
129
|
+
"BD" => BigDecimal.new(100),
|
130
|
+
"float" => 0.1,
|
131
|
+
"truthyness" => true,
|
132
|
+
"day" => Date.new )
|
133
|
+
end
|
134
|
+
it 'works' do end
|
135
|
+
end
|
136
|
+
|
137
|
+
context "transformed characters when using .to_xml" do
|
138
|
+
context 'underscores become dashes' do
|
139
|
+
let(:xml){ {"one_thing"=>1, "two_things"=>2}.to_xml }
|
140
|
+
let(:parsed){ {"one-thing"=>1, "two-things"=>2} }
|
141
|
+
it 'works' do end
|
142
|
+
end
|
143
|
+
context 'spaces become dashes' do
|
144
|
+
let(:xml){ {"one thing"=>1, "two things"=>2}.to_xml }
|
145
|
+
let(:parsed){ {"one-thing"=>1, "two-things"=>2} }
|
146
|
+
it 'works' do end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe '#generate' do
|
152
|
+
it 'has tests'
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|