berkeley_library-util 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57ff7fc89570ad61496f1587934e40dc18752df9cd99d766f591c4fe03fcde51
4
- data.tar.gz: e0bc0403c067c04801683e12de79a569721332ac23c770a05a0a0fde41842214
3
+ metadata.gz: 9eac060e7b0956b965455bddb8cc6c9721d5af784ac421766c2d791d99632927
4
+ data.tar.gz: a68fecb0b685ddffa3cb0fff6a46180e377efeb71b1cc5d23a389117c27be72d
5
5
  SHA512:
6
- metadata.gz: 6f7786f78fd64266dab9e424f61c03a95dc5a7eb294990bfef1d124699d5e0f6db7b53eaa2d3ca84c77eea1e096b11a5d227c679f5994699973b6274e3d6afe0
7
- data.tar.gz: a8a61db572a00d9d0ca6d41f23d2c76490196c14a6fdd8202b88d1f924fd5f9a32ebbea0e7811a4fba2a49a6fcec4ac40242cd79dfbaec8f37deaff046650436
6
+ metadata.gz: 052c93d1c09d569479d4bec70ffa9413e3ab0404f19482955b89552891c7058ab76ec11191456aa79016e1e2c98403eb355c89982ffe31f31d37f650517970b2
7
+ data.tar.gz: 530542deae99ffbd089cfc294b614d9560e2298ed4f7a20161970185935155a620b578bd99066a219e7d0410305d525e354c9b811162adbefdf3dda35c904427
data/.idea/util.iml CHANGED
@@ -8,9 +8,9 @@
8
8
  <content url="file://$MODULE_DIR$" />
9
9
  <orderEntry type="jdk" jdkName="RVM: ruby-2.7.4" jdkType="RUBY_SDK" />
10
10
  <orderEntry type="sourceFolder" forTests="false" />
11
- <orderEntry type="library" scope="PROVIDED" name="actionpack (v7.0.2.4, RVM: ruby-2.7.4) [gem]" level="application" />
12
- <orderEntry type="library" scope="PROVIDED" name="actionview (v7.0.2.4, RVM: ruby-2.7.4) [gem]" level="application" />
13
- <orderEntry type="library" scope="PROVIDED" name="activesupport (v7.0.2.4, RVM: ruby-2.7.4) [gem]" level="application" />
11
+ <orderEntry type="library" scope="PROVIDED" name="actionpack (v7.0.3, RVM: ruby-2.7.4) [gem]" level="application" />
12
+ <orderEntry type="library" scope="PROVIDED" name="actionview (v7.0.3, RVM: ruby-2.7.4) [gem]" level="application" />
13
+ <orderEntry type="library" scope="PROVIDED" name="activesupport (v7.0.3, RVM: ruby-2.7.4) [gem]" level="application" />
14
14
  <orderEntry type="library" scope="PROVIDED" name="addressable (v2.8.0, RVM: ruby-2.7.4) [gem]" level="application" />
15
15
  <orderEntry type="library" scope="PROVIDED" name="amazing_print (v1.4.0, RVM: ruby-2.7.4) [gem]" level="application" />
16
16
  <orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, RVM: ruby-2.7.4) [gem]" level="application" />
@@ -35,13 +35,13 @@
35
35
  <orderEntry type="library" scope="PROVIDED" name="http-cookie (v1.0.4, RVM: ruby-2.7.4) [gem]" level="application" />
36
36
  <orderEntry type="library" scope="PROVIDED" name="i18n (v1.10.0, RVM: ruby-2.7.4) [gem]" level="application" />
37
37
  <orderEntry type="library" scope="PROVIDED" name="lograge (v0.12.0, RVM: ruby-2.7.4) [gem]" level="application" />
38
- <orderEntry type="library" scope="PROVIDED" name="loofah (v2.17.0, RVM: ruby-2.7.4) [gem]" level="application" />
38
+ <orderEntry type="library" scope="PROVIDED" name="loofah (v2.18.0, RVM: ruby-2.7.4) [gem]" level="application" />
39
39
  <orderEntry type="library" scope="PROVIDED" name="method_source (v1.0.0, RVM: ruby-2.7.4) [gem]" level="application" />
40
40
  <orderEntry type="library" scope="PROVIDED" name="mime-types (v3.4.1, RVM: ruby-2.7.4) [gem]" level="application" />
41
41
  <orderEntry type="library" scope="PROVIDED" name="mime-types-data (v3.2022.0105, RVM: ruby-2.7.4) [gem]" level="application" />
42
42
  <orderEntry type="library" scope="PROVIDED" name="minitest (v5.15.0, RVM: ruby-2.7.4) [gem]" level="application" />
43
43
  <orderEntry type="library" scope="PROVIDED" name="netrc (v0.11.0, RVM: ruby-2.7.4) [gem]" level="application" />
44
- <orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.13.5, RVM: ruby-2.7.4) [gem]" level="application" />
44
+ <orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.13.6, RVM: ruby-2.7.4) [gem]" level="application" />
45
45
  <orderEntry type="library" scope="PROVIDED" name="oj (v3.13.11, RVM: ruby-2.7.4) [gem]" level="application" />
46
46
  <orderEntry type="library" scope="PROVIDED" name="ougai (v1.9.1, RVM: ruby-2.7.4) [gem]" level="application" />
47
47
  <orderEntry type="library" scope="PROVIDED" name="parallel (v1.22.1, RVM: ruby-2.7.4) [gem]" level="application" />
@@ -52,7 +52,7 @@
52
52
  <orderEntry type="library" scope="PROVIDED" name="rack-test (v1.1.0, RVM: ruby-2.7.4) [gem]" level="application" />
53
53
  <orderEntry type="library" scope="PROVIDED" name="rails-dom-testing (v2.0.3, RVM: ruby-2.7.4) [gem]" level="application" />
54
54
  <orderEntry type="library" scope="PROVIDED" name="rails-html-sanitizer (v1.4.2, RVM: ruby-2.7.4) [gem]" level="application" />
55
- <orderEntry type="library" scope="PROVIDED" name="railties (v7.0.2.4, RVM: ruby-2.7.4) [gem]" level="application" />
55
+ <orderEntry type="library" scope="PROVIDED" name="railties (v7.0.3, RVM: ruby-2.7.4) [gem]" level="application" />
56
56
  <orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, RVM: ruby-2.7.4) [gem]" level="application" />
57
57
  <orderEntry type="library" scope="PROVIDED" name="rake (v13.0.6, RVM: ruby-2.7.4) [gem]" level="application" />
58
58
  <orderEntry type="library" scope="PROVIDED" name="regexp_parser (v2.3.1, RVM: ruby-2.7.4) [gem]" level="application" />
@@ -98,7 +98,7 @@
98
98
  </RakeTaskImpl>
99
99
  <RakeTaskImpl description="Run all specs in spec directory, with coverage" fullCommand="coverage" id="coverage" />
100
100
  <RakeTaskImpl description="Run tests, check test coverage, check code style, check for vulnerabilities, build gem" fullCommand="default" id="default" />
101
- <RakeTaskImpl description="Build berkeley_library-util.gemspec as berkeley_library-util-0.1.1.gem" fullCommand="gem" id="gem" />
101
+ <RakeTaskImpl description="Build berkeley_library-util.gemspec as berkeley_library-util-0.1.4.gem" fullCommand="gem" id="gem" />
102
102
  <RakeTaskImpl description="Run RuboCop with auto-correct, and output results to console" fullCommand="ra" id="ra" />
103
103
  <RakeTaskImpl description="Run rubocop with HTML output" fullCommand="rubocop" id="rubocop" />
104
104
  <RakeTaskImpl id="rubocop">
data/.rubocop.yml CHANGED
@@ -55,6 +55,10 @@ Style/CommentedKeyword:
55
55
  Style/Documentation:
56
56
  Enabled: false
57
57
 
58
+ # You say "cryptic", I say "compact"
59
+ Style/FormatString:
60
+ EnforcedStyle: percent
61
+
58
62
  # Adding more line noise to format strings will not improve them
59
63
  Style/FormatStringToken:
60
64
  Enabled: false
data/CHANGES.md CHANGED
@@ -1,3 +1,10 @@
1
+ # 0.1.5 (2022-09-16)
2
+
3
+ - Adds `URIs#path_escape` to escape URL path segments
4
+ - Adds `URIs#head` and `URIs#head_response`, to make a HEAD request and return the HTTP status code
5
+ and raw response object, respectively
6
+ - Clarifies documentation for `URIs#get` and `URIs#get_response` regarding unsuccessful requests
7
+
1
8
  # 0.1.4 (2022-07-20)
2
9
 
3
10
  - Adds `URIs#safe_parse_uri`, which returns `nil` for invalid URLs (unlike `URIs#uri_or_nil`, which
@@ -7,7 +7,7 @@ module BerkeleyLibrary
7
7
  SUMMARY = 'Miscellaneous Ruby utilities for the UC Berkeley Library'.freeze
8
8
  DESCRIPTION = 'A collection of miscellaneous Ruby routines for the UC Berkeley Library.'.freeze
9
9
  LICENSE = 'MIT'.freeze
10
- VERSION = '0.1.4'.freeze
10
+ VERSION = '0.1.5'.freeze
11
11
  HOMEPAGE = 'https://github.com/BerkeleyLibrary/util'.freeze
12
12
  end
13
13
  end
@@ -16,29 +16,56 @@ module BerkeleyLibrary
16
16
  # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
17
17
  # @param headers [Hash] the request headers.
18
18
  # @return [String] the body as a string.
19
- # @raise [RestClient::Exception] in the event of an error.
19
+ # @raise [RestClient::Exception] in the event of an unsuccessful request.
20
20
  def get(uri, params: {}, headers: {})
21
- resp = make_get_request(uri, params, headers)
21
+ resp = make_request(:get, uri, params, headers)
22
22
  resp.body
23
23
  end
24
24
 
25
- # Performs a GET request and returns the response.
25
+ # Performs a HEAD request and returns the response status as an integer.
26
+ # Note that unlike {Requester#get}, this does not raise an error in the
27
+ # event of an unsuccessful request.
28
+ #
29
+ # @param uri [URI, String] the URI to HEAD
30
+ # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
31
+ # @param headers [Hash] the request headers.
32
+ # @return [Integer] the response code as an integer.
33
+ def head(uri, params: {}, headers: {})
34
+ head_response(uri, params: params, headers: headers).code
35
+ end
36
+
37
+ # Performs a GET request and returns the response, even in the event of
38
+ # a failed request.
26
39
  #
27
40
  # @param uri [URI, String] the URI to GET
28
41
  # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
29
42
  # @param headers [Hash] the request headers.
30
43
  # @return [RestClient::Response] the body as a string.
31
44
  def get_response(uri, params: {}, headers: {})
32
- make_get_request(uri, params, headers)
45
+ make_request(:get, uri, params, headers)
46
+ rescue RestClient::Exception => e
47
+ e.response
48
+ end
49
+
50
+ # Performs a HEAD request and returns the response, even in the event of
51
+ # a failed request.
52
+ #
53
+ # @param uri [URI, String] the URI to HEAD
54
+ # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
55
+ # @param headers [Hash] the request headers.
56
+ # @return [RestClient::Response] the body as a string.
57
+ def head_response(uri, params: {}, headers: {})
58
+ make_request(:head, uri, params, headers)
33
59
  rescue RestClient::Exception => e
34
60
  e.response
35
61
  end
36
62
 
37
63
  private
38
64
 
39
- def make_get_request(uri, params, headers)
65
+ # @return [RestClient::Response]
66
+ def make_request(method, uri, params, headers)
40
67
  url_str = url_str_with_params(uri, params)
41
- get_or_raise(url_str, headers)
68
+ req_resp_or_raise(method, url_str, headers)
42
69
  end
43
70
 
44
71
  def url_str_with_params(uri, params)
@@ -57,15 +84,15 @@ module BerkeleyLibrary
57
84
  end
58
85
 
59
86
  # @return [RestClient::Response]
60
- def get_or_raise(url_str, headers)
61
- resp = RestClient.get(url_str, headers)
87
+ def req_resp_or_raise(method, url_str, headers)
88
+ resp = RestClient::Request.execute(method: method, url: url_str, headers: headers)
62
89
  begin
63
90
  return resp if (status = resp.code) == 200
64
91
 
65
92
  raise(exception_for(resp, status))
66
93
  ensure
67
94
  # noinspection RubyMismatchedReturnType
68
- logger.info("GET #{url_str} returned #{status}")
95
+ logger.info("#{method.to_s.upcase} #{url_str} returned #{status}")
69
96
  end
70
97
  end
71
98
 
@@ -8,6 +8,8 @@ module BerkeleyLibrary
8
8
  module URIs
9
9
  include BerkeleyLibrary::Logging
10
10
 
11
+ UTF_8 = Encoding::UTF_8
12
+
11
13
  class << self
12
14
  include URIs
13
15
  end
@@ -24,18 +26,31 @@ module BerkeleyLibrary
24
26
  Appender.new(uri, *elements).to_uri
25
27
  end
26
28
 
27
- # Performs a GET request.
29
+ # Performs a GET request and returns the response body as a string.
28
30
  #
29
31
  # @param uri [URI, String] the URI to GET
30
32
  # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
31
33
  # @param headers [Hash] the request headers.
32
34
  # @return [String] the body as a string.
33
- # @raise [RestClient::Exception] in the event of an error.
35
+ # @raise [RestClient::Exception] in the event of an unsuccessful request.
34
36
  def get(uri, params: {}, headers: {})
35
37
  Requester.get(uri, params: params, headers: headers)
36
38
  end
37
39
 
38
- # Performs a GET request and returns the response.
40
+ # Performs a HEAD request and returns the response status as an integer.
41
+ # Note that unlike {Requester#get}, this does not raise an error in the
42
+ # event of an unsuccessful request.
43
+ #
44
+ # @param uri [URI, String] the URI to HEAD
45
+ # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
46
+ # @param headers [Hash] the request headers.
47
+ # @return [Integer] the response code as an integer.
48
+ def head(uri, params: {}, headers: {})
49
+ Requester.head(uri, params: params, headers: headers)
50
+ end
51
+
52
+ # Performs a GET request and returns the response, even in the event of
53
+ # a failed request.
39
54
  #
40
55
  # @param uri [URI, String] the URI to GET
41
56
  # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
@@ -45,6 +60,17 @@ module BerkeleyLibrary
45
60
  Requester.get_response(uri, params: params, headers: headers)
46
61
  end
47
62
 
63
+ # Performs a HEAD request and returns the response, even in the event of
64
+ # a failed request.
65
+ #
66
+ # @param uri [URI, String] the URI to HEAD
67
+ # @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
68
+ # @param headers [Hash] the request headers.
69
+ # @return [RestClient::Response] the body as a string.
70
+ def head_response(uri, params: {}, headers: {})
71
+ Requester.head_response(uri, params: params, headers: headers)
72
+ end
73
+
48
74
  # Returns the specified URL as a URI, or `nil` if the URL is `nil`.
49
75
  # @param url [String, URI, nil] the URL.
50
76
  # @return [URI] the URI, or `nil`.
@@ -54,6 +80,19 @@ module BerkeleyLibrary
54
80
  Validator.uri_or_nil(url)
55
81
  end
56
82
 
83
+ # Escapes the specified string so that it can be used as a URL path segment,
84
+ # replacing disallowed characters (including /) with percent-encodings as needed.
85
+ def path_escape(s)
86
+ raise ArgumentError, "Can't escape #{s.inspect}: not a string" unless s.respond_to?(:encoding)
87
+ raise ArgumentError, "Can't escape #{s.inspect}: expected #{UTF_8}, was #{s.encoding}" unless s.encoding == UTF_8
88
+
89
+ ''.tap do |escaped|
90
+ s.bytes.each do |b|
91
+ escaped << (should_escape?(b, :path_segment) ? '%%%02X' % b : b.chr)
92
+ end
93
+ end
94
+ end
95
+
57
96
  # Returns the specified URL as a URI, or `nil` if the URL cannot
58
97
  # be parsed.
59
98
  # @param url [Object, nil] the URL.
@@ -65,6 +104,32 @@ module BerkeleyLibrary
65
104
  logger.warn("Error parsing URL #{url.inspect}", e)
66
105
  nil
67
106
  end
107
+
108
+ private
109
+
110
+ # TODO: extend to cover other modes - host, zone, path, password, query, fragment
111
+ # cf. https://github.com/golang/go/blob/master/src/net/url/url.go
112
+ ALLOWED_BYTES_BY_MODE = {
113
+ path_segment: [0x24, 0x26, 0x2b, 0x3a, 0x3d, 0x40] # @ & = + $
114
+ }.freeze
115
+
116
+ def should_escape?(b, mode)
117
+ return false if unreserved?(b)
118
+ return false if ALLOWED_BYTES_BY_MODE[mode]&.include?(b)
119
+
120
+ true
121
+ end
122
+
123
+ # rubocop:disable Metrics/CyclomaticComplexity
124
+ def unreserved?(byte)
125
+ return true if byte >= 0x41 && byte <= 0x5a # A-Z
126
+ return true if byte >= 0x61 && byte <= 0x7a # a-z
127
+ return true if byte >= 0x30 && byte <= 0x39 # 0-9
128
+ return true if [0x2d, 0x2e, 0x5f, 0x7e].include?(byte) # - . _ ~
129
+
130
+ false
131
+ end
132
+ # rubocop:enable Metrics/CyclomaticComplexity
68
133
  end
69
134
  end
70
135
  end
@@ -66,6 +66,70 @@ module BerkeleyLibrary
66
66
  expect(result).to eq(expected_body)
67
67
  end
68
68
  end
69
+
70
+ describe :head do
71
+ it 'returns an HTTP status code for a URL string' do
72
+ url = 'https://example.org/'
73
+ expected_status = 203
74
+ stub_request(:head, url).to_return(status: expected_status)
75
+
76
+ result = Requester.head(url)
77
+ expect(result).to eq(expected_status)
78
+ end
79
+
80
+ it 'returns an HTTP response body for a URI' do
81
+ uri = URI.parse('https://example.org/')
82
+ expected_status = 203
83
+ stub_request(:head, uri).to_return(status: expected_status)
84
+
85
+ result = Requester.head(uri)
86
+ expect(result).to eq(expected_status)
87
+ end
88
+
89
+ it 'appends query parameters' do
90
+ url = 'https://example.org/'
91
+ params = { p1: 1, p2: 2 }
92
+ url_with_query = "#{url}?#{URI.encode_www_form(params)}"
93
+ expected_status = 203
94
+ stub_request(:head, url_with_query).to_return(status: expected_status)
95
+
96
+ result = Requester.head(url, params: params)
97
+ expect(result).to eq(expected_status)
98
+ end
99
+
100
+ it 'sends request headers' do
101
+ url = 'https://example.org/'
102
+ headers = { 'X-help' => 'I am trapped in a unit test' }
103
+ expected_status = 203
104
+ stub_request(:head, url).with(headers: headers).to_return(status: expected_status)
105
+
106
+ result = Requester.head(url, headers: headers)
107
+ expect(result).to eq(expected_status)
108
+ end
109
+
110
+ it 'returns the status even for unsuccessful requests' do
111
+ aggregate_failures 'responses' do
112
+ [207, 400, 401, 403, 404, 405, 418, 451, 500, 503].each do |expected_status|
113
+ url = "http://example.edu/#{expected_status}"
114
+ stub_request(:head, url).to_return(status: expected_status)
115
+
116
+ result = Requester.head(url)
117
+ expect(result).to eq(expected_status)
118
+ end
119
+ end
120
+ end
121
+
122
+ it 'handles redirects' do
123
+ url1 = 'https://example.org/'
124
+ url2 = 'https://example.edu/'
125
+ stub_request(:head, url1).to_return(status: 302, headers: { 'Location' => url2 })
126
+ expected_status = 203
127
+ stub_request(:head, url2).to_return(status: expected_status)
128
+
129
+ result = Requester.head(url1)
130
+ expect(result).to eq(expected_status)
131
+ end
132
+ end
69
133
  end
70
134
  end
71
135
  end
@@ -16,16 +16,14 @@ module BerkeleyLibrary::Util
16
16
  expect(new_uri).to eq(expected_uri)
17
17
  end
18
18
 
19
- # TODO: make this work
20
- xit "doesn't append to a bare URI when there's nothing to append" do
19
+ it 'returns a bare URI when there\'s nothing to append' do
21
20
  original_url = 'https://example.org'
22
21
  new_uri = URIs.append(original_url)
23
22
  expected_uri = URI(original_url)
24
23
  expect(new_uri).to eq(expected_uri)
25
24
  end
26
25
 
27
- # TODO: make this work
28
- xit "doesn't append to a bare URI when there's only a query string" do
26
+ it 'appends to a bare URI even when there\'s only a query string' do
29
27
  original_url = 'https://example.org'
30
28
  new_uri = URIs.append(original_url, '?foo=bar')
31
29
  expected_uri = URI("#{original_url}?foo=bar")
@@ -136,6 +134,19 @@ module BerkeleyLibrary::Util
136
134
  new_uri = URIs.append(original_uri, '?qux=corge', '&grault=plugh#xyzzy')
137
135
  expect(new_uri).to eq(URI('https://example.org/foo/bar?qux=corge&grault=plugh#xyzzy'))
138
136
  end
137
+
138
+ it 'rejects invalid characters' do
139
+ original_uri = URI('https://example.org/')
140
+ expect { URIs.append(original_uri, '精力善用') }.to raise_error(URI::InvalidComponentError)
141
+ end
142
+
143
+ it 'accepts percent-encoded path segments' do
144
+ original_uri = URI('https://example.org/')
145
+ encoded_segment = URIs.path_escape('精力善用')
146
+ new_uri = URIs.append(original_uri, encoded_segment, 'foo.html')
147
+ expected_url = "https://example.org/#{encoded_segment}/foo.html"
148
+ expect(new_uri).to eq(URI(expected_url))
149
+ end
139
150
  end
140
151
 
141
152
  describe 'requests' do
@@ -177,6 +188,42 @@ module BerkeleyLibrary::Util
177
188
  expect(response.code).to eq(404)
178
189
  end
179
190
  end
191
+
192
+ describe :head do
193
+ it 'makes a HEAD request' do
194
+ expected_status = 200
195
+ stub_request(:head, url_with_query).with(headers: headers).to_return(status: expected_status)
196
+
197
+ result = URIs.head(url, params: params, headers: headers)
198
+ expect(result).to eq(expected_status)
199
+ end
200
+
201
+ it 'returns the status even for unsuccessful requests' do
202
+ expected_status = 404
203
+ stub_request(:head, url_with_query).with(headers: headers).to_return(status: expected_status)
204
+
205
+ result = URIs.head(url, params: params, headers: headers)
206
+ expect(result).to eq(expected_status)
207
+ end
208
+ end
209
+
210
+ describe :head_response do
211
+ it 'makes a HEAD request' do
212
+ stub_request(:head, url_with_query).with(headers: headers).to_return(body: expected_body)
213
+
214
+ response = URIs.head_response(url, params: params, headers: headers)
215
+ expect(response.body).to eq(expected_body)
216
+ expect(response.code).to eq(200)
217
+ end
218
+
219
+ it 'returns the response even for errors' do
220
+ stub_request(:head, url_with_query).with(headers: headers).to_return(status: 404, body: expected_body)
221
+
222
+ response = URIs.head_response(url, params: params, headers: headers)
223
+ expect(response.body).to eq(expected_body)
224
+ expect(response.code).to eq(404)
225
+ end
226
+ end
180
227
  end
181
228
 
182
229
  describe :safe_parse_uri do
@@ -210,5 +257,49 @@ module BerkeleyLibrary::Util
210
257
  end
211
258
  end
212
259
  end
260
+
261
+ describe :path_escape do
262
+ let(:in_out) do
263
+ {
264
+ '' => '',
265
+ 'corge' => 'corge',
266
+ 'foo+bar' => 'foo+bar',
267
+ 'qux/quux' => 'qux%2Fquux',
268
+ 'foo bar baz' => 'foo%20bar%20baz',
269
+ '25%' => '25%25',
270
+ "\t !\"#$%&'()*+,/:;<=>?@[\\]^`{|}☺" => '%09%20%21%22%23$%25&%27%28%29%2A+%2C%2F:%3B%3C=%3E%3F@%5B%5C%5D%5E%60%7B%7C%7D%E2%98%BA',
271
+ '精力善用' => '%E7%B2%BE%E5%8A%9B%E5%96%84%E7%94%A8'
272
+ }
273
+ end
274
+
275
+ it 'escapes a path segment' do
276
+ aggregate_failures do
277
+ in_out.each do |in_str, out_str|
278
+ expect(URIs.path_escape(in_str)).to eq(out_str)
279
+ end
280
+ end
281
+ end
282
+
283
+ it 'rejects non-strings' do
284
+ str = in_out.keys.last
285
+ expect { URIs.path_escape(str.bytes) }.to raise_error(ArgumentError)
286
+ end
287
+
288
+ it 'rejects non-UTF-8 strings' do
289
+ str = in_out.keys.last
290
+ expect { URIs.path_escape(str.encode(Encoding::Shift_JIS)) }.to raise_error(ArgumentError)
291
+ end
292
+
293
+ it 'accepts non-UTF-8 strings converted to UTF-8' do
294
+ in_str = in_out.keys.last
295
+ out_str = in_out[in_str]
296
+
297
+ # OK, we're really just testing String#encode here, but
298
+ # it's useful for documentation
299
+ in_str_sjis = in_str.encode(Encoding::Shift_JIS)
300
+ in_str_utf8 = in_str_sjis.encode(Encoding::UTF_8)
301
+ expect(URIs.path_escape(in_str_utf8)).to eq(out_str)
302
+ end
303
+ end
213
304
  end
214
305
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: berkeley_library-util
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Moles
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-20 00:00:00.000000000 Z
11
+ date: 2022-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: berkeley_library-logging