heroku-api 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,7 +9,7 @@ module Heroku
9
9
  with_mock_app(mock_data, app) do
10
10
  mock_data[:config_vars][app].delete(key)
11
11
  {
12
- :body => Heroku::API::OkJson.encode(mock_data[:config_vars][app]),
12
+ :body => Heroku::API::Mock.json_gzip(mock_data[:config_vars][app]),
13
13
  :status => 200
14
14
  }
15
15
  end
@@ -21,7 +21,7 @@ module Heroku
21
21
  app, _ = request_params[:captures][:path]
22
22
  with_mock_app(mock_data, app) do
23
23
  {
24
- :body => Heroku::API::OkJson.encode(mock_data[:config_vars][app]),
24
+ :body => Heroku::API::Mock.json_gzip(mock_data[:config_vars][app]),
25
25
  :status => 200
26
26
  }
27
27
  end
@@ -35,7 +35,7 @@ module Heroku
35
35
  new_config_vars = request_params[:body]
36
36
  mock_data[:config_vars][app].merge!(new_config_vars)
37
37
  {
38
- :body => Heroku::API::OkJson.encode(mock_data[:config_vars][app]),
38
+ :body => Heroku::API::Mock.json_gzip(mock_data[:config_vars][app]),
39
39
  :status => 200
40
40
  }
41
41
  end
@@ -29,7 +29,7 @@ module Heroku
29
29
  app, _ = request_params[:captures][:path]
30
30
  with_mock_app(mock_data, app) do |app_data|
31
31
  {
32
- :body => Heroku::API::OkJson.encode(mock_data[:domains][app]),
32
+ :body => Heroku::API::Mock.json_gzip(mock_data[:domains][app]),
33
33
  :status => 200
34
34
  }
35
35
  end
@@ -54,12 +54,12 @@ module Heroku
54
54
  }
55
55
  end
56
56
  {
57
- :body => Heroku::API::OkJson.encode('domain' => domain),
57
+ :body => Heroku::API::Mock.json_gzip('domain' => domain),
58
58
  :status => 201
59
59
  }
60
60
  else
61
61
  {
62
- :body => Heroku::API::OkJson.encode([["base","Please install the Custom Domains addon before adding domains to your app"]]),
62
+ :body => Heroku::API::Mock.json_gzip([["base","Please install the Custom Domains addon before adding domains to your app"]]),
63
63
  :status => 422
64
64
  }
65
65
  end
@@ -26,7 +26,7 @@ module Heroku
26
26
  Excon.stub(:expects => 200, :method => :get, :path => %r{^/user/keys}) do |params|
27
27
  request_params, mock_data = parse_stub_params(params)
28
28
  {
29
- :body => Heroku::API::OkJson.encode(mock_data[:keys]),
29
+ :body => Heroku::API::Mock.json_gzip(mock_data[:keys]),
30
30
  :status => 200
31
31
  }
32
32
  end
@@ -9,7 +9,7 @@ module Heroku
9
9
  with_mock_app(mock_data, app) do
10
10
  uuid = [SecureRandom.hex(4), SecureRandom.hex(2), SecureRandom.hex(2), SecureRandom.hex(2), SecureRandom.hex(6)].join('-')
11
11
  {
12
- :body => "https://logplex.heroku.com/sessions/#{uuid}?srv=#{Time.now.to_i}",
12
+ :body => Heroku::API::Mock.gzip("https://logplex.heroku.com/sessions/#{uuid}?srv=#{Time.now.to_i}"),
13
13
  :status => 200
14
14
  }
15
15
  end
@@ -8,7 +8,7 @@ module Heroku
8
8
  app, _ = request_params[:captures][:path]
9
9
  with_mock_app(mock_data, app) do |app_data|
10
10
  {
11
- :body => Heroku::API::OkJson.encode(get_mock_processes(mock_data, app)),
11
+ :body => Heroku::API::Mock.json_gzip(get_mock_processes(mock_data, app)),
12
12
  :status => 200
13
13
  }
14
14
  end
@@ -41,7 +41,7 @@ module Heroku
41
41
  }
42
42
  mock_data[:ps][app] << data
43
43
  {
44
- :body => Heroku::API::OkJson.encode(data),
44
+ :body => Heroku::API::Mock.json_gzip(data),
45
45
  :status => 200,
46
46
  }
47
47
  end
@@ -68,7 +68,7 @@ module Heroku
68
68
  end
69
69
  end
70
70
  {
71
- :body => 'ok',
71
+ :body => Heroku::API::Mock.gzip('ok'),
72
72
  :status => 200
73
73
  }
74
74
  end
@@ -105,18 +105,18 @@ module Heroku
105
105
  end
106
106
  end
107
107
  {
108
- :body => qty.to_s,
108
+ :body => Heroku::API::Mock.gzip(qty.to_s),
109
109
  :status => 200
110
110
  }
111
111
  else
112
112
  {
113
- :body => Heroku::API::OkJson.encode('error' => "No such type as #{type}") ,
113
+ :body => Heroku::API::Mock.json_gzip('error' => "No such type as #{type}") ,
114
114
  :status => 422
115
115
  }
116
116
  end
117
117
  else
118
118
  {
119
- :body => Heroku::API::OkJson.encode('error' => "That feature is not available on this app's stack"),
119
+ :body => Heroku::API::Mock.json_gzip('error' => "That feature is not available on this app's stack"),
120
120
  :status => 422
121
121
  }
122
122
  end
@@ -132,12 +132,12 @@ module Heroku
132
132
  type = request_params[:query].has_key?('type') && request_params[:query]['type']
133
133
  if !ps && !type
134
134
  {
135
- :body => Heroku::API::OkJson.encode({'error' => 'Missing process argument'}),
135
+ :body => Heroku::API::Mock.json_gzip({'error' => 'Missing process argument'}),
136
136
  :status => 422
137
137
  }
138
138
  else
139
139
  {
140
- :body => 'ok',
140
+ :body => Heroku::API::Mock.gzip('ok'),
141
141
  :status => 200
142
142
  }
143
143
  end
@@ -153,12 +153,12 @@ module Heroku
153
153
  unless app_data['stack'] == 'cedar'
154
154
  app_data['dynos'] = dynos
155
155
  {
156
- :body => Heroku::API::OkJson.encode({'name' => app, 'dynos' => dynos}),
156
+ :body => Heroku::API::Mock.json_gzip({'name' => app, 'dynos' => dynos}),
157
157
  :status => 200
158
158
  }
159
159
  else
160
160
  {
161
- :body => Heroku::API::OkJson.encode({'error' => "For Cedar apps, use `heroku scale web=#{dynos}`"}),
161
+ :body => Heroku::API::Mock.json_gzip({'error' => "For Cedar apps, use `heroku scale web=#{dynos}`"}),
162
162
  :status => 422
163
163
  }
164
164
  end
@@ -174,12 +174,12 @@ module Heroku
174
174
  unless app_data['stack'] == 'cedar'
175
175
  app_data['workers'] = workers
176
176
  {
177
- :body => Heroku::API::OkJson.encode({'name' => app, 'workers' => workers}),
177
+ :body => Heroku::API::Mock.json_gzip({'name' => app, 'workers' => workers}),
178
178
  :status => 200
179
179
  }
180
180
  else
181
181
  {
182
- :body => Heroku::API::OkJson.encode({'error' => "For Cedar apps, use `heroku scale worker=#{workers}`"}),
182
+ :body => Heroku::API::Mock.json_gzip({'error' => "For Cedar apps, use `heroku scale worker=#{workers}`"}),
183
183
  :status => 422
184
184
  }
185
185
  end
@@ -9,17 +9,17 @@ module Heroku
9
9
  with_mock_app(mock_data, app) do |app_data|
10
10
  if get_mock_app_addon(mock_data, app, 'releases:basic')
11
11
  {
12
- :body => mock_data[:releases][app][-2..-1],
12
+ :body => Heroku::API::Mock.json_gzip(mock_data[:releases][app][-2..-1]),
13
13
  :status => 200
14
14
  }
15
15
  elsif get_mock_app_addon(mock_data, app, 'releases:advanced')
16
16
  {
17
- :body => mock_data[:releases][app],
17
+ :body => Heroku::API::Mock.json_gzip(mock_data[:releases][app]),
18
18
  :status => 200
19
19
  }
20
20
  else
21
21
  {
22
- :body => Heroku::API::OkJson.encode({'error' => 'Please install the Release Management add-on to access release history'}),
22
+ :body => Heroku::API::Mock.json_gzip({'error' => 'Please install the Release Management add-on to access release history'}),
23
23
  :status => 422
24
24
  }
25
25
  end
@@ -39,7 +39,7 @@ module Heroku
39
39
  end
40
40
  if release_data = releases.detect {|release| release['name'] == release_name}
41
41
  {
42
- :body => Heroku::API::OkJson.encode(release_data),
42
+ :body => Heroku::API::Mock.json_gzip(release_data),
43
43
  :status => 200
44
44
  }
45
45
  else
@@ -50,7 +50,7 @@ module Heroku
50
50
  end
51
51
  else
52
52
  {
53
- :body => Heroku::API::OkJson.encode({'error' => 'Please install the Release Management add-on to access release history'}),
53
+ :body => Heroku::API::Mock.json_gzip({'error' => 'Please install the Release Management add-on to access release history'}),
54
54
  :status => 422
55
55
  }
56
56
  end
@@ -95,24 +95,24 @@ module Heroku
95
95
  }
96
96
 
97
97
  {
98
- :body => release_data['name'],
98
+ :body => Heroku::API::Mock.gzip(release_data['name']),
99
99
  :status => 200
100
100
  }
101
101
  else
102
102
  {
103
- :body => Heroku::API::OkJson.encode({'error' => 'Cannot rollback to a release that had a different set of addons installed'}),
103
+ :body => Heroku::API::Mock.json_gzip({'error' => 'Cannot rollback to a release that had a different set of addons installed'}),
104
104
  :status => 422
105
105
  }
106
106
  end
107
107
  else
108
108
  {
109
- :body => 'Record not found.',
109
+ :body => Heroku::API::Mock.gzip('Record not found.'),
110
110
  :status => 404
111
111
  }
112
112
  end
113
113
  else
114
114
  {
115
- :body => Heroku::API::OkJson.encode({'error' => 'Please install the Release Management add-on to access release history'}),
115
+ :body => Heroku::API::Mock.json_gzip({'error' => 'Please install the Release Management add-on to access release history'}),
116
116
  :status => 422
117
117
  }
118
118
  end
@@ -34,10 +34,10 @@ module Heroku
34
34
  request_params, mock_data = parse_stub_params(params)
35
35
  app, _ = request_params[:captures][:path]
36
36
  with_mock_app(mock_data, app) do |app_data|
37
- stack_data = STACKS.dup
37
+ stack_data = Marshal::load(Marshal.dump(STACKS))
38
38
  stack_data.detect {|stack| stack['name'] == app_data['stack']}['current'] = true
39
39
  {
40
- :body => Heroku::API::OkJson.encode(stack_data),
40
+ :body => Heroku::API::Mock.json_gzip(stack_data),
41
41
  :status => 200
42
42
  }
43
43
  end
@@ -52,7 +52,7 @@ module Heroku
52
52
  if app_data['stack'] != 'cedar' && stack != 'cedar'
53
53
  if STACKS.map {|stack_data| stack_data['name']}.include?(stack)
54
54
  {
55
- :body => <<-BODY,
55
+ :body => Heroku::API::Mock.gzip(<<-BODY),
56
56
  HTTP/1.1 200 OK
57
57
  -----> Preparing to migrate #{app}
58
58
  #{app_data['stack']} -> #{stack}
@@ -66,13 +66,13 @@ BODY
66
66
  }
67
67
  else
68
68
  {
69
- :body => Heroku::API::OkJson.encode('error' => 'Stack not found'),
69
+ :body => Heroku::API::Mock.json_gzip('error' => 'Stack not found'),
70
70
  :status => 404
71
71
  }
72
72
  end
73
73
  else
74
74
  {
75
- :body => Heroku::API::OkJson.encode('error' => 'Stack migration to/from Cedar is not available. Create a new app with --stack cedar instead.'),
75
+ :body => Heroku::API::Mock.json_gzip('error' => 'Stack migration to/from Cedar is not available. Create a new app with --stack cedar instead.'),
76
76
  :status => 422
77
77
  }
78
78
  end
@@ -0,0 +1,16 @@
1
+ module Heroku
2
+ class API
3
+ module Mock
4
+
5
+ # stub GET /user
6
+ Excon.stub(:expects => 200, :method => :get, :path => %r{^/user}) do |params|
7
+ request_params, mock_data = parse_stub_params(params)
8
+ {
9
+ :body => Heroku::API::Mock.gzip(File.read("#{File.dirname(__FILE__)}/cache/get_user.json")),
10
+ :status => 200
11
+ }
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ module Heroku
2
+ class API
3
+
4
+ # GET /user
5
+ def get_user
6
+ request(
7
+ :expects => 200,
8
+ :method => :get,
9
+ :path => "/user"
10
+ )
11
+ end
12
+
13
+ end
14
+ end
@@ -1,4 +1,6 @@
1
- # Copyright 2011 Keith Rarick
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright 2011, 2012 Keith Rarick
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
6
  # of this software and associated documentation files (the "Software"), to deal
@@ -30,7 +32,6 @@ module Heroku
30
32
  module OkJson
31
33
  extend self
32
34
 
33
- class ParserError < ::StandardError; end
34
35
 
35
36
  # Decodes a json document in string s and
36
37
  # returns the corresponding ruby value.
@@ -44,7 +45,7 @@ module Heroku
44
45
  ts = lex(s)
45
46
  v, ts = textparse(ts)
46
47
  if ts.length > 0
47
- raise Heroku::API::OkJson::ParserError, 'trailing garbage'
48
+ raise Error, 'trailing garbage'
48
49
  end
49
50
  v
50
51
  end
@@ -56,14 +57,15 @@ module Heroku
56
57
  # except that it does not accept atomic values.
57
58
  def textparse(ts)
58
59
  if ts.length < 0
59
- raise Heroku::API::OkJson::ParserError, 'empty'
60
+ raise Error, 'empty'
60
61
  end
61
62
 
62
63
  typ, _, val = ts[0]
63
64
  case typ
64
65
  when '{' then objparse(ts)
65
66
  when '[' then arrparse(ts)
66
- else valparse(ts)
67
+ else
68
+ raise Error, "unexpected #{val.inspect}"
67
69
  end
68
70
  end
69
71
 
@@ -72,7 +74,7 @@ module Heroku
72
74
  # Returns the parsed value and any trailing tokens.
73
75
  def valparse(ts)
74
76
  if ts.length < 0
75
- raise Heroku::API::OkJson::ParserError, 'empty'
77
+ raise Error, 'empty'
76
78
  end
77
79
 
78
80
  typ, _, val = ts[0]
@@ -81,7 +83,7 @@ module Heroku
81
83
  when '[' then arrparse(ts)
82
84
  when :val,:str then [val, ts[1..-1]]
83
85
  else
84
- raise Heroku::API::OkJson::ParserError, "unexpected #{val.inspect}"
86
+ raise Error, "unexpected #{val.inspect}"
85
87
  end
86
88
  end
87
89
 
@@ -117,11 +119,11 @@ module Heroku
117
119
 
118
120
 
119
121
  # Parses a "member" in the sense of RFC 4627.
120
- # Returns the parsed value and any trailing tokens.
122
+ # Returns the parsed values and any trailing tokens.
121
123
  def pairparse(ts)
122
124
  (typ, _, k), ts = ts[0], ts[1..-1]
123
125
  if typ != :str
124
- raise Heroku::API::OkJson::ParserError, "unexpected #{k.inspect}"
126
+ raise Error, "unexpected #{k.inspect}"
125
127
  end
126
128
  ts = eat(':', ts)
127
129
  v, ts = valparse(ts)
@@ -161,20 +163,20 @@ module Heroku
161
163
 
162
164
  def eat(typ, ts)
163
165
  if ts[0][0] != typ
164
- raise Heroku::API::OkJson::ParserError, "expected #{typ} (got #{ts[0].inspect})"
166
+ raise Error, "expected #{typ} (got #{ts[0].inspect})"
165
167
  end
166
168
  ts[1..-1]
167
169
  end
168
170
 
169
171
 
170
- # Sans s and returns a list of json tokens,
172
+ # Scans s and returns a list of json tokens,
171
173
  # excluding white space (as defined in RFC 4627).
172
174
  def lex(s)
173
175
  ts = []
174
176
  while s.length > 0
175
177
  typ, lexeme, val = tok(s)
176
178
  if typ == nil
177
- raise Heroku::API::OkJson::ParserError, "invalid character at #{s[0,10].inspect}"
179
+ raise Error, "invalid character at #{s[0,10].inspect}"
178
180
  end
179
181
  if typ != :space
180
182
  ts << [typ, lexeme, val]
@@ -187,7 +189,7 @@ module Heroku
187
189
 
188
190
  # Scans the first token in s and
189
191
  # returns a 3-element list, or nil
190
- # if no such token exists.
192
+ # if s does not begin with a valid token.
191
193
  #
192
194
  # The first list element is one of
193
195
  # '{', '}', ':', ',', '[', ']',
@@ -241,7 +243,7 @@ module Heroku
241
243
  def strtok(s)
242
244
  m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
243
245
  if ! m
244
- raise Heroku::API::OkJson::ParserError, "invalid string literal at #{abbrev(s)}"
246
+ raise Error, "invalid string literal at #{abbrev(s)}"
245
247
  end
246
248
  [:str, m[0], unquote(m[0])]
247
249
  end
@@ -258,10 +260,16 @@ module Heroku
258
260
 
259
261
  # Converts a quoted json string literal q into a UTF-8-encoded string.
260
262
  # The rules are different than for Ruby, so we cannot use eval.
261
- # Unquote will raise Heroku::API::OkJson::ParserError, an error if q contains control characters.
263
+ # Unquote will raise an error if q contains control characters.
262
264
  def unquote(q)
263
265
  q = q[1...-1]
264
266
  a = q.dup # allocate a big enough string
267
+ rubydoesenc = false
268
+ # In ruby >= 1.9, a[w] is a codepoint, not a byte.
269
+ if a.class.method_defined?(:force_encoding)
270
+ a.force_encoding('UTF-8')
271
+ rubydoesenc = true
272
+ end
265
273
  r, w = 0, 0
266
274
  while r < q.length
267
275
  c = q[r]
@@ -269,7 +277,7 @@ module Heroku
269
277
  when c == ?\\
270
278
  r += 1
271
279
  if r >= q.length
272
- raise Heroku::API::OkJson::ParserError, "string literal ends with a \"\\\": \"#{q}\""
280
+ raise Error, "string literal ends with a \"\\\": \"#{q}\""
273
281
  end
274
282
 
275
283
  case q[r]
@@ -286,7 +294,7 @@ module Heroku
286
294
  uchar = begin
287
295
  hexdec4(q[r,4])
288
296
  rescue RuntimeError => e
289
- raise Heroku::API::OkJson::ParserError, "invalid escape sequence \\u#{q[r,4]}: #{e}"
297
+ raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
290
298
  end
291
299
  r += 4
292
300
  if surrogate? uchar
@@ -299,16 +307,23 @@ module Heroku
299
307
  end
300
308
  end
301
309
  end
302
- w += ucharenc(a, w, uchar)
310
+ if rubydoesenc
311
+ a[w] = '' << uchar
312
+ w += 1
313
+ else
314
+ w += ucharenc(a, w, uchar)
315
+ end
303
316
  else
304
- raise Heroku::API::OkJson::ParserError, "invalid escape char #{q[r]} in \"#{q}\""
317
+ raise Error, "invalid escape char #{q[r]} in \"#{q}\""
305
318
  end
306
319
  when c == ?", c < Spc
307
- raise Heroku::API::OkJson::ParserError, "invalid character in string literal \"#{q}\""
320
+ raise Error, "invalid character in string literal \"#{q}\""
308
321
  else
309
322
  # Copy anything else byte-for-byte.
310
323
  # Valid UTF-8 will remain valid UTF-8.
311
324
  # Invalid UTF-8 will remain invalid UTF-8.
325
+ # In ruby >= 1.9, c is a codepoint, not a byte,
326
+ # in which case this is still what we want.
312
327
  a[w] = c
313
328
  r += 1
314
329
  w += 1
@@ -318,9 +333,36 @@ module Heroku
318
333
  end
319
334
 
320
335
 
336
+ # Encodes unicode character u as UTF-8
337
+ # bytes in string a at position i.
338
+ # Returns the number of bytes written.
339
+ def ucharenc(a, i, u)
340
+ case true
341
+ when u <= Uchar1max
342
+ a[i] = (u & 0xff).chr
343
+ 1
344
+ when u <= Uchar2max
345
+ a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
346
+ a[i+1] = (Utagx | (u&Umaskx)).chr
347
+ 2
348
+ when u <= Uchar3max
349
+ a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
350
+ a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
351
+ a[i+2] = (Utagx | (u&Umaskx)).chr
352
+ 3
353
+ else
354
+ a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
355
+ a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
356
+ a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
357
+ a[i+3] = (Utagx | (u&Umaskx)).chr
358
+ 4
359
+ end
360
+ end
361
+
362
+
321
363
  def hexdec4(s)
322
364
  if s.length != 4
323
- raise Heroku::API::OkJson::ParserError, 'short'
365
+ raise Error, 'short'
324
366
  end
325
367
  (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
326
368
  end
@@ -354,7 +396,7 @@ module Heroku
354
396
  when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
355
397
  when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
356
398
  else
357
- raise Heroku::API::OkJson::ParserError, "invalid hex code #{c}"
399
+ raise Error, "invalid hex code #{c}"
358
400
  end
359
401
  end
360
402
 
@@ -362,32 +404,53 @@ module Heroku
362
404
  # Encodes x into a json text. It may contain only
363
405
  # Array, Hash, String, Numeric, true, false, nil.
364
406
  # (Note, this list excludes Symbol.)
407
+ # X itself must be an Array or a Hash.
408
+ # No other value can be encoded, and an error will
409
+ # be raised if x contains any other value, such as
410
+ # Nan, Infinity, Symbol, and Proc, or if a Hash key
411
+ # is not a String.
365
412
  # Strings contained in x must be valid UTF-8.
366
- # Values that cannot be represented, such as
367
- # Nan, Infinity, Symbol, and Proc, are encoded
368
- # as null, in accordance with ECMA-262, 5th ed.
369
413
  def encode(x)
414
+ case x
415
+ when Hash then objenc(x)
416
+ when Array then arrenc(x)
417
+ else
418
+ raise Error, 'root value must be an Array or a Hash'
419
+ end
420
+ end
421
+
422
+
423
+ def valenc(x)
370
424
  case x
371
425
  when Hash then objenc(x)
372
426
  when Array then arrenc(x)
373
427
  when String then strenc(x)
374
428
  when Numeric then numenc(x)
375
- when Symbol then strenc(x.to_s)
376
429
  when true then "true"
377
430
  when false then "false"
378
431
  when nil then "null"
379
- else "null"
432
+ else
433
+ raise Error, "cannot encode #{x.class}: #{x.inspect}"
380
434
  end
381
435
  end
382
436
 
383
437
 
384
438
  def objenc(x)
385
- '{' + x.map{|k,v| encode(k) + ':' + encode(v)}.join(',') + '}'
439
+ '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
386
440
  end
387
441
 
388
442
 
389
443
  def arrenc(a)
390
- '[' + a.map{|x| encode(x)}.join(',') + ']'
444
+ '[' + a.map{|x| valenc(x)}.join(',') + ']'
445
+ end
446
+
447
+
448
+ def keyenc(k)
449
+ case k
450
+ when String then strenc(k)
451
+ else
452
+ raise Error, "Hash key is not a string: #{k.inspect}"
453
+ end
391
454
  end
392
455
 
393
456
 
@@ -395,6 +458,10 @@ module Heroku
395
458
  t = StringIO.new
396
459
  t.putc(?")
397
460
  r = 0
461
+
462
+ # In ruby >= 1.9, s[r] is a codepoint, not a byte.
463
+ rubydoesenc = s.class.method_defined?(:encoding)
464
+
398
465
  while r < s.length
399
466
  case s[r]
400
467
  when ?" then t.print('\\"')
@@ -409,21 +476,13 @@ module Heroku
409
476
  case true
410
477
  when Spc <= c && c <= ?~
411
478
  t.putc(c)
412
- when true
479
+ when rubydoesenc
480
+ u = c.ord
481
+ surrenc(t, u)
482
+ else
413
483
  u, size = uchardec(s, r)
414
484
  r += size - 1 # we add one more at the bottom of the loop
415
- if u < 0x10000
416
- t.print('\\u')
417
- hexenc4(t, u)
418
- else
419
- u1, u2 = unsubst(u)
420
- t.print('\\u')
421
- hexenc4(t, u1)
422
- t.print('\\u')
423
- hexenc4(t, u2)
424
- end
425
- else
426
- # invalid byte; skip it
485
+ surrenc(t, u)
427
486
  end
428
487
  end
429
488
  r += 1
@@ -433,6 +492,20 @@ module Heroku
433
492
  end
434
493
 
435
494
 
495
+ def surrenc(t, u)
496
+ if u < 0x10000
497
+ t.print('\\u')
498
+ hexenc4(t, u)
499
+ else
500
+ u1, u2 = unsubst(u)
501
+ t.print('\\u')
502
+ hexenc4(t, u1)
503
+ t.print('\\u')
504
+ hexenc4(t, u2)
505
+ end
506
+ end
507
+
508
+
436
509
  def hexenc4(t, u)
437
510
  t.putc(Hex[(u>>12)&0xf])
438
511
  t.putc(Hex[(u>>8)&0xf])
@@ -442,9 +515,9 @@ module Heroku
442
515
 
443
516
 
444
517
  def numenc(x)
445
- if x.nan? || x.infinite?
446
- return 'null'
447
- end rescue nil
518
+ if ((x.nan? || x.infinite?) rescue false)
519
+ raise Error, "Numeric cannot be represented: #{x}"
520
+ end
448
521
  "#{x}"
449
522
  end
450
523
 
@@ -506,32 +579,10 @@ module Heroku
506
579
  end
507
580
 
508
581
 
509
- # Encodes unicode character u as UTF-8
510
- # bytes in string a at position i.
511
- # Returns the number of bytes written.
512
- def ucharenc(a, i, u)
513
- case true
514
- when u <= Uchar1max
515
- a[i] = (u & 0xff).chr
516
- 1
517
- when u <= Uchar2max
518
- a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
519
- a[i+1] = (Utagx | (u&Umaskx)).chr
520
- 2
521
- when u <= Uchar3max
522
- a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
523
- a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
524
- a[i+2] = (Utagx | (u&Umaskx)).chr
525
- 3
526
- else
527
- a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
528
- a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
529
- a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
530
- a[i+3] = (Utagx | (u&Umaskx)).chr
531
- 4
532
- end
582
+ class Error < ::StandardError
533
583
  end
534
584
 
585
+
535
586
  Utagx = 0x80 # 1000 0000
536
587
  Utag2 = 0xc0 # 1100 0000
537
588
  Utag3 = 0xe0 # 1110 0000