resteze 0.3.0 → 0.4.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.
data/docs/API.md ADDED
@@ -0,0 +1,410 @@
1
+ # Resteze API Documentation
2
+
3
+ ## Table of Contents
4
+ - [Core Concepts](#core-concepts)
5
+ - [Basic Setup](#basic-setup)
6
+ - [Resource Definition](#resource-definition)
7
+ - [Making Requests](#making-requests)
8
+ - [Response Handling](#response-handling)
9
+ - [Advanced Features](#advanced-features)
10
+
11
+ ## Core Concepts
12
+
13
+ Resteze provides a framework for building REST API client gems with the following core components:
14
+
15
+ ### ApiModule
16
+ The foundation that gets included in your API namespace module. It sets up the infrastructure for your API client.
17
+
18
+ ### ApiResource
19
+ Base class for all your API resources. Inherits from `Resteze::Object` (which extends `Hashie::Trash`) providing property management and data transformation capabilities.
20
+
21
+ ### Client
22
+ Manages HTTP connections and request execution. Thread-safe and supports connection customization.
23
+
24
+ ### Request
25
+ Module that provides request functionality to resources, delegating to the active client.
26
+
27
+ ## Basic Setup
28
+
29
+ ### Creating Your API Module
30
+
31
+ ```ruby
32
+ require 'resteze'
33
+
34
+ module MyApi
35
+ include Resteze
36
+
37
+ # Basic configuration
38
+ configure do |config|
39
+ config.api_base = 'https://api.example.com/'
40
+ config.open_timeout = 30 # seconds
41
+ config.read_timeout = 60 # seconds
42
+ config.logger = Logger.new($stdout)
43
+ config.proxy = 'http://proxy.example.com:8080' # optional
44
+ end
45
+ end
46
+ ```
47
+
48
+ ### Custom Configuration Properties
49
+
50
+ You can add custom configuration properties to your API module:
51
+
52
+ ```ruby
53
+ module MyApi
54
+ include Resteze
55
+
56
+ class << self
57
+ attr_accessor :api_key, :api_secret, :environment
58
+ end
59
+
60
+ configure do |config|
61
+ config.api_base = 'https://api.example.com/'
62
+ config.api_key = ENV['MY_API_KEY']
63
+ config.api_secret = ENV['MY_API_SECRET']
64
+ config.environment = :production
65
+ end
66
+ end
67
+ ```
68
+
69
+ ## Resource Definition
70
+
71
+ ### Basic Resource
72
+
73
+ ```ruby
74
+ module MyApi
75
+ class User < ApiResource
76
+ # Define properties that map to API response fields
77
+ property :id
78
+ property :email
79
+ property :name
80
+ property :created_at
81
+ property :updated_at
82
+ end
83
+ end
84
+ ```
85
+
86
+ ### Resource with Custom Paths
87
+
88
+ ```ruby
89
+ module MyApi
90
+ class User < ApiResource
91
+ property :id
92
+ property :email
93
+
94
+ # Override the resource slug (defaults to pluralized class name)
95
+ def self.resource_slug
96
+ 'accounts' # Use /accounts instead of /users
97
+ end
98
+
99
+ # Override the entire resource path
100
+ def self.resource_path(id = nil)
101
+ if id
102
+ "/v2/accounts/#{CGI.escape(id.to_s)}"
103
+ else
104
+ "/v2/accounts"
105
+ end
106
+ end
107
+
108
+ # Custom service path
109
+ def self.service_path
110
+ '/api'
111
+ end
112
+
113
+ # API version
114
+ def self.api_version
115
+ 'v2'
116
+ end
117
+ end
118
+ end
119
+ ```
120
+
121
+ ### Nested Resources
122
+
123
+ ```ruby
124
+ module MyApi
125
+ class Comment < ApiResource
126
+ property :id
127
+ property :post_id
128
+ property :content
129
+ property :author
130
+
131
+ def self.resource_path(id = nil, post_id: nil)
132
+ if post_id
133
+ "/posts/#{post_id}/comments#{id ? "/#{id}" : ""}"
134
+ else
135
+ super(id)
136
+ end
137
+ end
138
+
139
+ # Retrieve comments for a specific post
140
+ def self.list_by_post(post_id)
141
+ request(:get, resource_path(nil, post_id: post_id))
142
+ end
143
+ end
144
+ end
145
+ ```
146
+
147
+ ## Making Requests
148
+
149
+ ### Retrieving Resources
150
+
151
+ ```ruby
152
+ # Get a single resource by ID
153
+ user = MyApi::User.retrieve('123')
154
+ puts user.email
155
+
156
+ # With additional parameters
157
+ user = MyApi::User.new('123', values: { include: 'profile' })
158
+ user.refresh # Makes the API call
159
+
160
+ # Refresh existing resource
161
+ user.refresh # Re-fetches from API
162
+ ```
163
+
164
+ ### Custom Request Methods
165
+
166
+ ```ruby
167
+ module MyApi
168
+ class User < ApiResource
169
+ property :id
170
+ property :email
171
+ property :status
172
+
173
+ # Instance method for custom action
174
+ def activate!
175
+ response = request(
176
+ :post,
177
+ "#{resource_path}/activate",
178
+ params: { send_email: true }
179
+ )
180
+ initialize_from(response.data)
181
+ end
182
+
183
+ # Class method for custom endpoint
184
+ def self.search(query)
185
+ response = request(
186
+ :get,
187
+ "#{resource_path}/search",
188
+ params: { q: query }
189
+ )
190
+ response.data.map { |attrs| construct_from(attrs) }
191
+ end
192
+
193
+ # Custom headers
194
+ def update_with_version(version, attributes)
195
+ request(
196
+ :patch,
197
+ resource_path,
198
+ params: attributes,
199
+ headers: { 'If-Match' => version }
200
+ )
201
+ end
202
+ end
203
+ end
204
+ ```
205
+
206
+ ### Direct Request Access
207
+
208
+ ```ruby
209
+ # Using the request module directly
210
+ MyApi::User.request(:get, '/custom/endpoint', params: { foo: 'bar' })
211
+
212
+ # Using the client directly
213
+ client = MyApi::Client.active_client
214
+ response = client.execute_request(
215
+ :post,
216
+ '/api/v1/users',
217
+ headers: { 'X-Custom-Header' => 'value' },
218
+ params: { email: 'user@example.com' }
219
+ )
220
+ ```
221
+
222
+ ## Response Handling
223
+
224
+ ### Response Object
225
+
226
+ ```ruby
227
+ response = MyApi::User.request(:get, '/users')
228
+
229
+ # Access response data
230
+ response.data # Parsed response body
231
+ response.status # HTTP status code
232
+ response.headers # Response headers
233
+ response.body # Raw response body
234
+ response.request_id # Request ID from headers (if present)
235
+ ```
236
+
237
+ ### Working with Objects
238
+
239
+ ```ruby
240
+ user = MyApi::User.retrieve('123')
241
+
242
+ # Access properties
243
+ user.id
244
+ user.email
245
+ user[:email] # Hash-style access
246
+
247
+ # Check if persisted
248
+ user.persisted? # true if has an ID
249
+
250
+ # Access metadata (fields not defined as properties)
251
+ user.resteze_metadata # Hash of additional fields
252
+ user.property_bag # Hash of undefined properties
253
+
254
+ # Deep merge data
255
+ user.merge_from({ email: 'new@example.com', profile: { name: 'John' } })
256
+ ```
257
+
258
+ ### Property Transformation
259
+
260
+ ```ruby
261
+ module MyApi
262
+ class User < ApiResource
263
+ # Use Hashie transformations
264
+ property :email, from: :email_address
265
+ property :name, from: :full_name
266
+ property :active, from: :is_active, with: ->(v) { v == 'true' }
267
+
268
+ # Custom transformation method
269
+ property :created_at, transform_with: ->(v) { Time.parse(v) if v }
270
+ end
271
+ end
272
+ ```
273
+
274
+ ## Advanced Features
275
+
276
+ ### Custom Object Keys
277
+
278
+ When API responses wrap data in a specific key:
279
+
280
+ ```ruby
281
+ module MyApi
282
+ # Response format: { "user": { "id": 1, "email": "..." } }
283
+
284
+ def self.default_object_key(klass)
285
+ klass.name.demodulize.underscore
286
+ end
287
+
288
+ class User < ApiResource
289
+ property :id
290
+ property :email
291
+
292
+ # Or override per-class
293
+ def self.object_key
294
+ :account # Look for data in { "account": {...} }
295
+ end
296
+ end
297
+ end
298
+ ```
299
+
300
+ ### Custom API Keys
301
+
302
+ Transform property names between Ruby and API formats:
303
+
304
+ ```ruby
305
+ module MyApi
306
+ # Convert snake_case to camelCase
307
+ def self.default_api_key(attribute)
308
+ attribute.to_s.camelcase(:lower)
309
+ end
310
+
311
+ class User < ApiResource
312
+ property :first_name # Maps to "firstName" in API
313
+ property :last_name # Maps to "lastName" in API
314
+ end
315
+ end
316
+ ```
317
+
318
+ ### Thread Safety
319
+
320
+ Resteze uses thread-local storage for client management:
321
+
322
+ ```ruby
323
+ # Each thread gets its own client instance
324
+ Thread.new do
325
+ MyApi::Client.new(custom_connection).request do
326
+ # All requests in this block use the custom client
327
+ user = MyApi::User.retrieve('123')
328
+ end
329
+ end
330
+
331
+ # Default client is shared within a thread
332
+ MyApi::Client.active_client # Returns thread's active client
333
+ MyApi::Client.default_client # Returns thread's default client
334
+ ```
335
+
336
+ ### Custom Middleware
337
+
338
+ ```ruby
339
+ module MyApi
340
+ class Client < Resteze::Client
341
+ def self.default_connection
342
+ @default_connection ||= Faraday.new do |conn|
343
+ # Add custom middleware
344
+ conn.use MyCustomMiddleware
345
+ conn.request :json
346
+ conn.response :json
347
+ conn.use Faraday::Request::UrlEncoded
348
+ conn.use MyApi::Middleware::RaiseError
349
+ conn.adapter Faraday.default_adapter
350
+ end
351
+ end
352
+ end
353
+ end
354
+ ```
355
+
356
+ ### Connection Customization
357
+
358
+ ```ruby
359
+ # Create a custom connection
360
+ custom_conn = Faraday.new do |conn|
361
+ conn.request :retry, max: 3, interval: 0.5
362
+ conn.request :authorization, 'Bearer', -> { MyApi.api_key }
363
+ conn.adapter :typhoeus # Use Typhoeus adapter
364
+ end
365
+
366
+ # Use custom connection
367
+ client = MyApi::Client.new(custom_conn)
368
+ client.request do
369
+ user = MyApi::User.retrieve('123')
370
+ end
371
+ ```
372
+
373
+ ### Logging
374
+
375
+ ```ruby
376
+ module MyApi
377
+ configure do |config|
378
+ # Use a custom logger
379
+ config.logger = Logger.new('api.log')
380
+ config.logger.level = Logger::DEBUG # Show detailed request/response
381
+ end
382
+
383
+ class Client < Resteze::Client
384
+ # Override logging behavior
385
+ def log_request(context)
386
+ super
387
+ # Add custom logging
388
+ logger.info "Custom: #{context.inspect}"
389
+ end
390
+ end
391
+ end
392
+ ```
393
+
394
+ ### Proxy Support
395
+
396
+ ```ruby
397
+ module MyApi
398
+ configure do |config|
399
+ # Simple proxy
400
+ config.proxy = 'http://proxy.example.com:8080'
401
+
402
+ # Proxy with authentication
403
+ config.proxy = {
404
+ uri: 'http://proxy.example.com:8080',
405
+ user: 'username',
406
+ password: 'password'
407
+ }
408
+ end
409
+ end
410
+ ```