praxis 0.14.0 → 0.15.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.
- 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
|