api_adaptor 0.1.0 → 1.0.1

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.
@@ -4,65 +4,103 @@ require "json"
4
4
  require "forwardable"
5
5
 
6
6
  module ApiAdaptor
7
- # This wraps an HTTP response with a JSON body.
7
+ # Wraps an HTTP response with a JSON body and provides convenient access methods.
8
8
  #
9
- # Responses can be configured to use relative URLs for `web_url` properties.
10
- # API endpoints should return absolute URLs so that they make sense outside of the
11
- # domain context. However on systems within an API we want to present relative URLs.
12
- # By specifying a base URI, this will convert all matching web_urls into relative URLs
9
+ # Response objects parse JSON and provide hash-like access to the response body.
10
+ # They also handle cache control headers and can convert absolute URLs to relative URLs.
13
11
  #
14
- # Example:
12
+ # @example Basic usage
13
+ # response = client.get_json("https://api.example.com/users")
14
+ # users = response["results"]
15
+ # cache_duration = response.cache_control.max_age
15
16
  #
16
- # r = Response.new(response, web_urls_relative_to: "https://www.example.com")
17
- # r['results'][0]['web_url']
18
- # => "/foo"
17
+ # @example With relative URLs
18
+ # response = Response.new(http_response, web_urls_relative_to: "https://www.example.com")
19
+ # response['results'][0]['web_url'] # => "/foo" instead of "https://www.example.com/foo"
20
+ #
21
+ # @example Checking cache headers
22
+ # if response.cache_control.public? && response.cache_control.max_age > 300
23
+ # # Cache this response
24
+ # end
19
25
  class Response
20
26
  extend Forwardable
21
27
  include Enumerable
22
28
 
29
+ # Parses and provides access to HTTP Cache-Control header directives.
30
+ #
31
+ # @example
32
+ # cc = CacheControl.new("public, max-age=3600, must-revalidate")
33
+ # cc.public? # => true
34
+ # cc.max_age # => 3600
35
+ # cc.must_revalidate? # => true
23
36
  class CacheControl < Hash
37
+ # Regex pattern for parsing Cache-Control directives
24
38
  PATTERN = /([-a-z]+)(?:\s*=\s*([^,\s]+))?,?+/i
25
39
 
40
+ # Initializes a new CacheControl object by parsing a header value
41
+ #
42
+ # @param value [String, nil] Cache-Control header value
26
43
  def initialize(value = nil)
27
44
  super()
28
45
  parse(value)
29
46
  end
30
47
 
48
+ # @return [Boolean] true if cache is public
31
49
  def public?
32
50
  self["public"]
33
51
  end
34
52
 
53
+ # @return [Boolean] true if cache is private
35
54
  def private?
36
55
  self["private"]
37
56
  end
38
57
 
58
+ # @return [Boolean] true if no-cache directive is present
39
59
  def no_cache?
40
60
  self["no-cache"]
41
61
  end
42
62
 
63
+ # @return [Boolean] true if no-store directive is present
43
64
  def no_store?
44
65
  self["no-store"]
45
66
  end
46
67
 
68
+ # @return [Boolean] true if must-revalidate directive is present
47
69
  def must_revalidate?
48
70
  self["must-revalidate"]
49
71
  end
50
72
 
73
+ # @return [Boolean] true if proxy-revalidate directive is present
51
74
  def proxy_revalidate?
52
75
  self["proxy-revalidate"]
53
76
  end
54
77
 
78
+ # Returns the max-age directive value
79
+ #
80
+ # @return [Integer, nil] Maximum age in seconds, or nil if not present
55
81
  def max_age
56
82
  self["max-age"].to_i if key?("max-age")
57
83
  end
58
84
 
85
+ # Returns the r-maxage directive value
86
+ #
87
+ # Note: r-maxage is a non-standard Cache-Control directive and is not part
88
+ # of RFC 7234. This method exists for compatibility with custom cache implementations.
89
+ #
90
+ # @return [Integer, nil] Reverse maximum age in seconds, or nil if not present
59
91
  def reverse_max_age
60
92
  self["r-maxage"].to_i if key?("r-maxage")
61
93
  end
62
94
  alias r_maxage reverse_max_age
63
95
 
96
+ # Returns the s-maxage (shared max age) directive value
97
+ #
98
+ # The s-maxage directive is like max-age but only applies to shared caches
99
+ # (e.g., CDNs, proxies). It overrides max-age for shared caches.
100
+ #
101
+ # @return [Integer, nil] Shared maximum age in seconds, or nil if not present
64
102
  def shared_max_age
65
- self["s-maxage"].to_i if key?("r-maxage")
103
+ self["s-maxage"].to_i if key?("s-maxage")
66
104
  end
67
105
  alias s_maxage shared_max_age
68
106
 
@@ -92,26 +130,67 @@ module ApiAdaptor
92
130
  end
93
131
  end
94
132
 
133
+ # @!method [](key)
134
+ # Access parsed JSON response by key
135
+ # @param key [String, Symbol] The key to access
136
+ # @return [Object] The value at the key
137
+ #
138
+ # @!method <=>(other)
139
+ # Compare responses
140
+ # @param other [Response] Another response
141
+ # @return [Integer] Comparison result
142
+ #
143
+ # @!method each(&block)
144
+ # Iterate over parsed response hash
145
+ # @yield [key, value] Each key-value pair
146
+ #
147
+ # @!method dig(*keys)
148
+ # Dig into nested hash structure
149
+ # @param keys [Array] Keys to traverse
150
+ # @return [Object, nil] The value at the path or nil
95
151
  def_delegators :to_hash, :[], :"<=>", :each, :dig
96
152
 
153
+ # Initializes a new Response object
154
+ #
155
+ # @param http_response [RestClient::Response] The raw HTTP response
156
+ # @param options [Hash] Configuration options
157
+ # @option options [String] :web_urls_relative_to Base URL for converting absolute URLs to relative
158
+ #
159
+ # @example
160
+ # response = Response.new(http_response)
161
+ #
162
+ # @example With relative URLs
163
+ # response = Response.new(http_response, web_urls_relative_to: "https://www.example.com")
97
164
  def initialize(http_response, options = {})
98
165
  @http_response = http_response
99
166
  @web_urls_relative_to = options[:web_urls_relative_to] ? URI.parse(options[:web_urls_relative_to]) : nil
100
167
  end
101
168
 
169
+ # Returns the raw response body string
170
+ #
171
+ # @return [String] Raw response body
102
172
  def raw_response_body
103
173
  @http_response.body
104
174
  end
105
175
 
176
+ # Returns the HTTP status code
177
+ #
178
+ # @return [Integer] HTTP status code
106
179
  def code
107
180
  # Return an integer code for consistency with HTTPErrorResponse
108
181
  @http_response.code
109
182
  end
110
183
 
184
+ # Returns the response headers
185
+ #
186
+ # @return [Hash] HTTP headers
111
187
  def headers
112
188
  @http_response.headers
113
189
  end
114
190
 
191
+ # Calculates when the response expires based on cache headers
192
+ #
193
+ # @return [Time, nil] Expiration time or nil if not cacheable
115
194
  def expires_at
116
195
  if headers[:date] && cache_control.max_age
117
196
  response_date = Time.parse(headers[:date])
@@ -121,6 +200,9 @@ module ApiAdaptor
121
200
  end
122
201
  end
123
202
 
203
+ # Calculates how many seconds until the response expires
204
+ #
205
+ # @return [Integer, nil] Seconds until expiration or nil if not cacheable
124
206
  def expires_in
125
207
  return unless headers[:date]
126
208
 
@@ -133,22 +215,37 @@ module ApiAdaptor
133
215
  end
134
216
  end
135
217
 
218
+ # Returns parsed Cache-Control header
219
+ #
220
+ # @return [CacheControl] Parsed cache control directives
136
221
  def cache_control
137
222
  @cache_control ||= CacheControl.new(headers[:cache_control])
138
223
  end
139
224
 
225
+ # Returns the parsed JSON response as a hash
226
+ #
227
+ # @return [Hash] Parsed JSON response
140
228
  def to_hash
141
229
  parsed_content
142
230
  end
143
231
 
232
+ # Returns the parsed and transformed JSON content
233
+ #
234
+ # @return [Hash, Array] Parsed JSON with transformed web_urls
144
235
  def parsed_content
145
236
  @parsed_content ||= transform_parsed(JSON.parse(@http_response.body))
146
237
  end
147
238
 
239
+ # Always returns true (response is present)
240
+ #
241
+ # @return [Boolean] true
148
242
  def present?
149
243
  true
150
244
  end
151
245
 
246
+ # Always returns false (response is not blank)
247
+ #
248
+ # @return [Boolean] false
152
249
  def blank?
153
250
  false
154
251
  end
@@ -1,15 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ApiAdaptor
4
+ # Environment variable configuration for User-Agent metadata
5
+ #
6
+ # These variables are used to construct the User-Agent header for HTTP requests,
7
+ # allowing API providers to identify and contact clients if needed.
8
+ #
9
+ # @example Setting environment variables
10
+ # ENV["APP_NAME"] = "MyApiClient"
11
+ # ENV["APP_VERSION"] = "2.1.0"
12
+ # ENV["APP_CONTACT"] = "dev@example.com"
13
+ #
14
+ # @example User-Agent header format
15
+ # # "MyApiClient/2.1.0 (dev@example.com)"
16
+ #
17
+ # @see JSONClient.default_request_headers
4
18
  module Variables
19
+ # Returns the application name from environment variable
20
+ #
21
+ # @return [String] Application name (default: "Ruby ApiAdaptor App")
22
+ #
23
+ # @example
24
+ # ENV["APP_NAME"] = "WikidataClient"
25
+ # Variables.app_name # => "WikidataClient"
5
26
  def self.app_name
6
27
  ENV["APP_NAME"] || "Ruby ApiAdaptor App"
7
28
  end
8
29
 
30
+ # Returns the application version from environment variable
31
+ #
32
+ # @return [String] Application version (default: "Version not stated")
33
+ #
34
+ # @example
35
+ # ENV["APP_VERSION"] = "3.2.1"
36
+ # Variables.app_version # => "3.2.1"
9
37
  def self.app_version
10
38
  ENV["APP_VERSION"] || "Version not stated"
11
39
  end
12
40
 
41
+ # Returns the application contact from environment variable
42
+ #
43
+ # Should be an email address or URL where API providers can reach you.
44
+ #
45
+ # @return [String] Contact information (default: "Contact not stated")
46
+ #
47
+ # @example
48
+ # ENV["APP_CONTACT"] = "api@example.com"
49
+ # Variables.app_contact # => "api@example.com"
13
50
  def self.app_contact
14
51
  ENV["APP_CONTACT"] || "Contact not stated"
15
52
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ApiAdaptor
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.1"
5
5
  end
data/lib/api_adaptor.rb CHANGED
@@ -3,7 +3,37 @@
3
3
  require_relative "api_adaptor/version"
4
4
  require_relative "api_adaptor/base"
5
5
 
6
+ # ApiAdaptor provides a framework for building JSON API clients with minimal boilerplate.
7
+ #
8
+ # It handles common patterns like request/response parsing, authentication, redirect handling,
9
+ # pagination, and error management, allowing you to focus on your API's specific logic.
10
+ #
11
+ # @example Building a simple API client
12
+ # class MyApiClient < ApiAdaptor::Base
13
+ # def initialize
14
+ # super("https://api.example.com")
15
+ # end
16
+ #
17
+ # def get_user(id)
18
+ # get_json("/users/#{id}")
19
+ # end
20
+ #
21
+ # def create_user(data)
22
+ # post_json("/users", data)
23
+ # end
24
+ # end
25
+ #
26
+ # client = MyApiClient.new
27
+ # user = client.get_user(123)
28
+ #
29
+ # @example Using JSONClient directly
30
+ # client = ApiAdaptor::JSONClient.new(bearer_token: "abc123")
31
+ # response = client.get_json("https://api.example.com/data")
32
+ # puts response["items"]
33
+ #
34
+ # @see Base Base class for building API clients
35
+ # @see JSONClient Low-level HTTP client with JSON support
6
36
  module ApiAdaptor
37
+ # Base error class for all ApiAdaptor exceptions
7
38
  class Error < StandardError; end
8
- # Your code goes here...
9
39
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api_adaptor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Huw Diprose
@@ -119,6 +119,20 @@ dependencies:
119
119
  - - "~>"
120
120
  - !ruby/object:Gem::Version
121
121
  version: '13.0'
122
+ - !ruby/object:Gem::Dependency
123
+ name: redcarpet
124
+ requirement: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - "~>"
127
+ - !ruby/object:Gem::Version
128
+ version: '3.6'
129
+ type: :development
130
+ prerelease: false
131
+ version_requirements: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - "~>"
134
+ - !ruby/object:Gem::Version
135
+ version: '3.6'
122
136
  - !ruby/object:Gem::Dependency
123
137
  name: rspec
124
138
  requirement: !ruby/object:Gem::Requirement
@@ -147,6 +161,20 @@ dependencies:
147
161
  - - "~>"
148
162
  - !ruby/object:Gem::Version
149
163
  version: '1.21'
164
+ - !ruby/object:Gem::Dependency
165
+ name: rubocop-yard
166
+ requirement: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ type: :development
172
+ prerelease: false
173
+ version_requirements: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ version: '0'
150
178
  - !ruby/object:Gem::Dependency
151
179
  name: simplecov
152
180
  requirement: !ruby/object:Gem::Requirement
@@ -189,6 +217,20 @@ dependencies:
189
217
  - - "~>"
190
218
  - !ruby/object:Gem::Version
191
219
  version: '3.18'
220
+ - !ruby/object:Gem::Dependency
221
+ name: yard
222
+ requirement: !ruby/object:Gem::Requirement
223
+ requirements:
224
+ - - "~>"
225
+ - !ruby/object:Gem::Version
226
+ version: '0.9'
227
+ type: :development
228
+ prerelease: false
229
+ version_requirements: !ruby/object:Gem::Requirement
230
+ requirements:
231
+ - - "~>"
232
+ - !ruby/object:Gem::Version
233
+ version: '0.9'
192
234
  description: A basic adaptor to send HTTP requests and parse the responses. Intended
193
235
  to bootstrap the quick writing of Adaptors for specific APIs, without having to
194
236
  write the same old JSON request and processing time and time again.
@@ -201,7 +243,9 @@ files:
201
243
  - ".env.example"
202
244
  - ".rspec"
203
245
  - ".rubocop.yml"
246
+ - ".yardopts"
204
247
  - CHANGELOG.md
248
+ - CLAUDE.md
205
249
  - CODE_OF_CONDUCT.md
206
250
  - Gemfile
207
251
  - Gemfile.lock
@@ -242,7 +286,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
242
286
  - !ruby/object:Gem::Version
243
287
  version: '0'
244
288
  requirements: []
245
- rubygems_version: 4.0.3
289
+ rubygems_version: 4.0.6
246
290
  specification_version: 4
247
291
  summary: A basic adaptor to send HTTP requests and parse the responses.
248
292
  test_files: []