wrappix 0.1.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 +7 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +216 -0
- data/Rakefile +12 -0
- data/bin/wrappix +7 -0
- data/lib/wrappix/builder.rb +207 -0
- data/lib/wrappix/cli.rb +24 -0
- data/lib/wrappix/client.rb +39 -0
- data/lib/wrappix/configuration.rb +25 -0
- data/lib/wrappix/request.rb +74 -0
- data/lib/wrappix/templates/cache.rb +62 -0
- data/lib/wrappix/templates/client.rb +35 -0
- data/lib/wrappix/templates/collection.rb +32 -0
- data/lib/wrappix/templates/configuration.rb +67 -0
- data/lib/wrappix/templates/documentation.rb +241 -0
- data/lib/wrappix/templates/error.rb +25 -0
- data/lib/wrappix/templates/main.rb +52 -0
- data/lib/wrappix/templates/object.rb +33 -0
- data/lib/wrappix/templates/readme.rb +258 -0
- data/lib/wrappix/templates/request.rb +129 -0
- data/lib/wrappix/templates/resource.rb +115 -0
- data/lib/wrappix/templates/tests.rb +368 -0
- data/lib/wrappix/version.rb +5 -0
- data/lib/wrappix.rb +29 -0
- data/sig/wrappix.rbs +4 -0
- metadata +150 -0
@@ -0,0 +1,258 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wrappix
|
4
|
+
module Templates
|
5
|
+
class Readme
|
6
|
+
def self.render(api_name, module_name, config)
|
7
|
+
base_url = config["base_url"] || "https://api.example.com"
|
8
|
+
auth_instructions = auth_setup_instructions(config)
|
9
|
+
resource_docs = generate_resource_docs(module_name, config)
|
10
|
+
|
11
|
+
<<~MARKDOWN
|
12
|
+
# #{module_name} API Client
|
13
|
+
|
14
|
+
A Ruby API wrapper for the #{api_name} API.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem '#{api_name}'
|
22
|
+
```
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
|
26
|
+
```bash
|
27
|
+
$ bundle install
|
28
|
+
```
|
29
|
+
|
30
|
+
Or install it yourself as:
|
31
|
+
|
32
|
+
```bash
|
33
|
+
$ gem install #{api_name}
|
34
|
+
```
|
35
|
+
|
36
|
+
## Configuration
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
#{module_name}.configure do |config|
|
40
|
+
config.base_url = "#{base_url}"
|
41
|
+
#{auth_instructions}
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
## Usage
|
46
|
+
|
47
|
+
### Initializing the client
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# Initialize the client
|
51
|
+
client = #{module_name}.client
|
52
|
+
```
|
53
|
+
|
54
|
+
### Examples
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
#{usage_examples(module_name, config)}
|
58
|
+
```
|
59
|
+
|
60
|
+
### Working with responses
|
61
|
+
|
62
|
+
Objects returned by the API can be accessed using dot notation:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
user = client.users.get(123)
|
66
|
+
puts user.id # => 123
|
67
|
+
puts user.name # => "John Doe"
|
68
|
+
puts user.email # => "john@example.com"
|
69
|
+
```
|
70
|
+
|
71
|
+
Collections include pagination support:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
users = client.users.list
|
75
|
+
|
76
|
+
# Iterate through items
|
77
|
+
users.data.each do |user|
|
78
|
+
puts user.name
|
79
|
+
end
|
80
|
+
|
81
|
+
# Check pagination info
|
82
|
+
if users.next_href
|
83
|
+
# More results available
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
## Resources and Endpoints
|
88
|
+
|
89
|
+
#{resource_docs}
|
90
|
+
|
91
|
+
## Error Handling
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
begin
|
95
|
+
response = client.users.get(123)
|
96
|
+
rescue #{module_name}::Error => e
|
97
|
+
puts "Error: \#{e.message}"
|
98
|
+
puts "Status: \#{e.status}"
|
99
|
+
puts "Details: \#{e.body}"
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
## API Documentation
|
104
|
+
|
105
|
+
Detailed API documentation is available in the `docs/api.md` file, which includes:
|
106
|
+
|
107
|
+
- All available endpoints
|
108
|
+
- Required parameters
|
109
|
+
- Example requests and responses
|
110
|
+
- Authentication details
|
111
|
+
|
112
|
+
## Advanced Usage
|
113
|
+
|
114
|
+
### Caching
|
115
|
+
|
116
|
+
#{module_name} uses a caching solution to improve efficiency (e.g., for caching tokens). By default, it uses a simple memory cache,
|
117
|
+
but you can change the cache method by setting the `#{module_name}.cache` attribute.
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
# Use Redis cache
|
121
|
+
#{module_name}.cache = Redis.new
|
122
|
+
|
123
|
+
# Or use Rails cache
|
124
|
+
#{module_name}.cache = Rails.cache
|
125
|
+
|
126
|
+
# Or use file-based cache
|
127
|
+
#{module_name}.cache = #{module_name}::FileCache.new
|
128
|
+
|
129
|
+
# Or any object that responds to read/write/delete/clear
|
130
|
+
#{module_name}.cache = YourCustomCache.new
|
131
|
+
```
|
132
|
+
|
133
|
+
### Custom Headers
|
134
|
+
|
135
|
+
You can set custom headers for all requests:
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
#{module_name}.configure do |config|
|
139
|
+
config.headers["User-Agent"] = "MyApp/1.0"
|
140
|
+
config.headers["X-Custom-Header"] = "Value"
|
141
|
+
end
|
142
|
+
```
|
143
|
+
MARKDOWN
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.auth_setup_instructions(config)
|
147
|
+
case config["auth_type"]
|
148
|
+
when "oauth"
|
149
|
+
<<~RUBY.strip
|
150
|
+
config.client_id = "your_client_id"
|
151
|
+
config.client_secret = "your_client_secret"
|
152
|
+
config.access_token = "your_access_token"
|
153
|
+
RUBY
|
154
|
+
when "basic"
|
155
|
+
<<~RUBY.strip
|
156
|
+
config.username = "your_username"
|
157
|
+
config.password = "your_password"
|
158
|
+
RUBY
|
159
|
+
when "api_key"
|
160
|
+
<<~RUBY.strip
|
161
|
+
config.api_key = "your_api_key"
|
162
|
+
config.api_key_header = "#{config["api_key_header"] || "X-Api-Key"}"
|
163
|
+
RUBY
|
164
|
+
else
|
165
|
+
"# No authentication required"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.usage_examples(_module_name, config)
|
170
|
+
resources = config["resources"] || {}
|
171
|
+
examples = []
|
172
|
+
|
173
|
+
resources.each do |resource_name, resource_config|
|
174
|
+
endpoints = resource_config["endpoints"] || []
|
175
|
+
next if endpoints.empty?
|
176
|
+
|
177
|
+
# Take the first endpoint as an example
|
178
|
+
endpoint = endpoints.first
|
179
|
+
method = endpoint["method"] || "get"
|
180
|
+
has_params = endpoint["path"].to_s.include?("{")
|
181
|
+
|
182
|
+
if has_params
|
183
|
+
# Extract parameters
|
184
|
+
params = endpoint["path"].scan(/\{([^}]+)\}/).flatten
|
185
|
+
param_values = params.map { |_p| "123" } # Example values
|
186
|
+
args = param_values.join(", ")
|
187
|
+
|
188
|
+
examples << "# #{resource_name.capitalize} - #{endpoint["name"]} example"
|
189
|
+
examples << "response = client.#{resource_name}.#{endpoint["name"]}(#{args})"
|
190
|
+
else
|
191
|
+
examples << "# #{resource_name.capitalize} - #{endpoint["name"]} example"
|
192
|
+
examples << if %w[post put patch].include?(method)
|
193
|
+
"response = client.#{resource_name}.#{endpoint["name"]}({name: 'value', other_field: 'value'})"
|
194
|
+
else
|
195
|
+
"response = client.#{resource_name}.#{endpoint["name"]}"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
examples << ""
|
200
|
+
end
|
201
|
+
|
202
|
+
examples.join("\n")
|
203
|
+
end
|
204
|
+
|
205
|
+
def self.generate_resource_docs(_module_name, config)
|
206
|
+
resources = config["resources"] || {}
|
207
|
+
docs = []
|
208
|
+
|
209
|
+
resources.each do |resource_name, resource_config|
|
210
|
+
docs << "### #{resource_name.capitalize}"
|
211
|
+
endpoints = resource_config["endpoints"] || []
|
212
|
+
|
213
|
+
endpoints.each do |endpoint|
|
214
|
+
name = endpoint["name"]
|
215
|
+
method = endpoint["method"] || "get"
|
216
|
+
path = endpoint["path"] || name
|
217
|
+
has_params = path.include?("{")
|
218
|
+
|
219
|
+
docs << "#### `#{name}`"
|
220
|
+
docs << "- HTTP Method: `#{method.upcase}`"
|
221
|
+
docs << "- Path: `#{path}`"
|
222
|
+
|
223
|
+
if has_params
|
224
|
+
params = path.scan(/\{([^}]+)\}/).flatten
|
225
|
+
docs << "- Path Parameters: #{params.map { |p| "`#{p}`" }.join(", ")}"
|
226
|
+
end
|
227
|
+
|
228
|
+
docs << "- Accepts additional query parameters" if endpoint["params"]
|
229
|
+
|
230
|
+
# Usage example
|
231
|
+
docs << "\n```ruby"
|
232
|
+
if has_params
|
233
|
+
params = path.scan(/\{([^}]+)\}/).flatten
|
234
|
+
param_args = params.map { |p| "#{p}: 123" }.join(", ")
|
235
|
+
|
236
|
+
docs << if endpoint["params"]
|
237
|
+
"client.#{resource_name}.#{name}(#{param_args}, {param1: 'value', param2: 'value'})"
|
238
|
+
else
|
239
|
+
"client.#{resource_name}.#{name}(#{param_args})"
|
240
|
+
end
|
241
|
+
else
|
242
|
+
docs << if %w[post put patch].include?(method)
|
243
|
+
"client.#{resource_name}.#{name}({field1: 'value', field2: 'value'})"
|
244
|
+
elsif endpoint["params"]
|
245
|
+
"client.#{resource_name}.#{name}({param1: 'value', param2: 'value'})"
|
246
|
+
else
|
247
|
+
"client.#{resource_name}.#{name}"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
docs << "```\n"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
docs.join("\n")
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wrappix
|
4
|
+
module Templates
|
5
|
+
class Request
|
6
|
+
def self.render(module_name, config)
|
7
|
+
oauth_token_logic = if config["auth_type"] == "oauth"
|
8
|
+
<<~RUBY
|
9
|
+
def get_access_token
|
10
|
+
# Try to get token from cache first
|
11
|
+
token = #{module_name}.cache.read("access_token")
|
12
|
+
return token if token
|
13
|
+
|
14
|
+
# If not in cache, fetch new token
|
15
|
+
response = Faraday.post(@config.token_url, {
|
16
|
+
client_id: @config.client_id,
|
17
|
+
client_secret: @config.client_secret,
|
18
|
+
grant_type: "client_credentials"
|
19
|
+
})
|
20
|
+
|
21
|
+
if response.status == 200
|
22
|
+
data = JSON.parse(response.body)
|
23
|
+
token = data["access_token"]
|
24
|
+
expires_in = data["expires_in"] || 3600
|
25
|
+
|
26
|
+
# Cache the token
|
27
|
+
#{module_name}.cache.write("access_token", token)
|
28
|
+
|
29
|
+
# Cache expiration handling could be improved
|
30
|
+
token
|
31
|
+
else
|
32
|
+
raise #{module_name}::Error.new("Failed to obtain access token", response.body, response.status)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
RUBY
|
36
|
+
else
|
37
|
+
""
|
38
|
+
end
|
39
|
+
|
40
|
+
<<~RUBY
|
41
|
+
# frozen_string_literal: true
|
42
|
+
|
43
|
+
require "faraday"
|
44
|
+
require "json"
|
45
|
+
|
46
|
+
module #{module_name}
|
47
|
+
class Request
|
48
|
+
def initialize(path, config = #{module_name}.configuration)
|
49
|
+
@path = path
|
50
|
+
@config = config
|
51
|
+
@base_url = config.base_url
|
52
|
+
end
|
53
|
+
|
54
|
+
def get(params: {}, headers: {})
|
55
|
+
make_request(:get, params: params, headers: headers)
|
56
|
+
end
|
57
|
+
|
58
|
+
def post(body: {}, headers: {})
|
59
|
+
make_request(:post, body: body, headers: headers)
|
60
|
+
end
|
61
|
+
|
62
|
+
def put(body: {}, headers: {})
|
63
|
+
make_request(:put, body: body, headers: headers)
|
64
|
+
end
|
65
|
+
|
66
|
+
def patch(body: {}, headers: {})
|
67
|
+
make_request(:patch, body: body, headers: headers)
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete(params: {}, headers: {})
|
71
|
+
make_request(:delete, params: params, headers: headers)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def make_request(method, params: {}, body: nil, headers: {})
|
77
|
+
response = connection.public_send(method) do |req|
|
78
|
+
req.url @path
|
79
|
+
req.params = params if params && !params.empty?
|
80
|
+
req.body = body.to_json if body && !body.empty?
|
81
|
+
req.headers.merge!(headers) if headers && !headers.empty?
|
82
|
+
req.options.timeout = @config.timeout
|
83
|
+
end
|
84
|
+
|
85
|
+
handle_response(response)
|
86
|
+
end
|
87
|
+
|
88
|
+
def connection
|
89
|
+
@connection ||= Faraday.new(url: @base_url) do |conn|
|
90
|
+
#{connection_auth_config(config)}
|
91
|
+
conn.headers = @config.headers
|
92
|
+
conn.response :json, content_type: /\\bjson$/
|
93
|
+
conn.adapter Faraday.default_adapter
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def handle_response(response)
|
98
|
+
return response.body if response.status.between?(200, 299)
|
99
|
+
|
100
|
+
error_message = if response.body.is_a?(Hash)
|
101
|
+
response.body["message"] || response.body["error"] || "Error \#{response.status}"
|
102
|
+
else
|
103
|
+
"Error \#{response.status}"
|
104
|
+
end
|
105
|
+
|
106
|
+
raise #{module_name}::Error.new(error_message, response.body, response.status)
|
107
|
+
end
|
108
|
+
|
109
|
+
#{oauth_token_logic}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
RUBY
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.connection_auth_config(config)
|
116
|
+
case config["auth_type"]
|
117
|
+
when "oauth"
|
118
|
+
"conn.request :authorization, 'Bearer', get_access_token if @config.client_id && @config.client_secret"
|
119
|
+
when "basic"
|
120
|
+
"conn.basic_auth(@config.username, @config.password) if @config.username && @config.password"
|
121
|
+
when "api_key"
|
122
|
+
"conn.headers[@config.api_key_header] = @config.api_key if @config.api_key"
|
123
|
+
else
|
124
|
+
""
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wrappix
|
4
|
+
module Templates
|
5
|
+
class Resource
|
6
|
+
def self.render(module_name, resource_name, resource_config)
|
7
|
+
endpoints = resource_config["endpoints"] || []
|
8
|
+
methods = endpoints.map do |endpoint|
|
9
|
+
endpoint_method(module_name, resource_name, endpoint, resource_config)
|
10
|
+
end.join("\n\n")
|
11
|
+
|
12
|
+
# Para normalizar el nombre del recurso
|
13
|
+
class_name = resource_name.capitalize
|
14
|
+
|
15
|
+
<<~RUBY
|
16
|
+
# frozen_string_literal: true
|
17
|
+
|
18
|
+
module #{module_name}
|
19
|
+
module Resources
|
20
|
+
class #{class_name}
|
21
|
+
def initialize(client)
|
22
|
+
@client = client
|
23
|
+
@config = #{module_name}.configuration
|
24
|
+
end
|
25
|
+
|
26
|
+
#{methods}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
RUBY
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.endpoint_method(module_name, _resource_name, endpoint, resource_config)
|
34
|
+
name = endpoint["name"]
|
35
|
+
method = endpoint["method"] || "get"
|
36
|
+
path = endpoint["path"] || name
|
37
|
+
|
38
|
+
# Obtener configuración de formato de respuesta
|
39
|
+
response_format = resource_config["response_format"] || {}
|
40
|
+
|
41
|
+
# Determinar si es una colección
|
42
|
+
is_collection = endpoint["collection"] || %w[all list index search].include?(name)
|
43
|
+
|
44
|
+
# Procesar parámetros del path
|
45
|
+
has_params = path.include?("{")
|
46
|
+
param_list = has_params ? path.scan(/\{([^}]+)\}/).flatten : []
|
47
|
+
|
48
|
+
# Preparar los parámetros del método
|
49
|
+
endpoint_params = []
|
50
|
+
endpoint_params.concat(param_list)
|
51
|
+
endpoint_params << "params = {}" if endpoint["params"]
|
52
|
+
endpoint_params << "body = {}" if %w[post put patch].include?(method)
|
53
|
+
|
54
|
+
# Preparar argumentos para el método request
|
55
|
+
request_args = []
|
56
|
+
request_args << "params: params" if endpoint["params"]
|
57
|
+
request_args << "body: body" if %w[post put patch].include?(method)
|
58
|
+
request_options = request_args.empty? ? "" : "(#{request_args.join(", ")})"
|
59
|
+
|
60
|
+
# Generar path con reemplazos de variables
|
61
|
+
path_with_params = path
|
62
|
+
param_list.each do |param|
|
63
|
+
path_with_params = path_with_params.gsub(/\{#{param}\}/, "\#{#{param}}")
|
64
|
+
end
|
65
|
+
|
66
|
+
# Código para transformar la respuesta basada en la configuración
|
67
|
+
response_transform = if is_collection
|
68
|
+
"#{module_name}::Collection.from_response(response, type: #{module_name}::Object)"
|
69
|
+
else
|
70
|
+
item_root = response_format["item_root"]
|
71
|
+
if item_root
|
72
|
+
"#{module_name}::Object.new(response[:#{item_root}] || response[\"#{item_root}\"] || response)"
|
73
|
+
else
|
74
|
+
"#{module_name}::Object.new(response)"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
<<~RUBY.strip
|
79
|
+
def #{name}(#{endpoint_params.join(", ")})
|
80
|
+
request = #{module_name}::Request.new("#{path_with_params}")
|
81
|
+
response = request.#{method}#{request_options}
|
82
|
+
|
83
|
+
#{response_transform}
|
84
|
+
end
|
85
|
+
RUBY
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.generate_response_transform(module_name, is_collection, response_format)
|
89
|
+
if is_collection
|
90
|
+
collection_root = response_format["collection_root"] || "data"
|
91
|
+
pagination_config = response_format["pagination"] || {}
|
92
|
+
next_page_key = pagination_config["next_page_key"] || "next_href"
|
93
|
+
|
94
|
+
# Código para extraer datos considerando la estructura de paginación
|
95
|
+
<<~RUBY
|
96
|
+
data = response[:#{collection_root}] || response["#{collection_root}"] || []
|
97
|
+
next_href = response[:#{next_page_key}] || response["#{next_page_key}"]
|
98
|
+
|
99
|
+
#{module_name}::Collection.from_response({
|
100
|
+
data: data,
|
101
|
+
next_href: next_href
|
102
|
+
}, type: #{module_name}::Object)
|
103
|
+
RUBY
|
104
|
+
else
|
105
|
+
item_root = response_format["item_root"]
|
106
|
+
if item_root
|
107
|
+
"#{module_name}::Object.new(response[:#{item_root}] || response[\"#{item_root}\"] || response)"
|
108
|
+
else
|
109
|
+
"#{module_name}::Object.new(response)"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|