thecompaniesapi 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.
- checksums.yaml +7 -0
- data/.env.example +4 -0
- data/.github/workflows/release.yml +106 -0
- data/.github/workflows/tests.yml +93 -0
- data/.gitignore +146 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +36 -0
- data/LICENSE.txt +21 -0
- data/README.md +551 -0
- data/Rakefile +25 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/thecompaniesapi/client.rb +643 -0
- data/lib/thecompaniesapi/error.rb +28 -0
- data/lib/thecompaniesapi/generated/models/action.rb +91 -0
- data/lib/thecompaniesapi/generated/models/company_v2.rb +136 -0
- data/lib/thecompaniesapi/generated/models/email_pattern.rb +51 -0
- data/lib/thecompaniesapi/generated/models/job_title.rb +81 -0
- data/lib/thecompaniesapi/generated/models/list.rb +141 -0
- data/lib/thecompaniesapi/generated/models/list_analytics.rb +46 -0
- data/lib/thecompaniesapi/generated/models/llmanswer.rb +66 -0
- data/lib/thecompaniesapi/generated/models/nominatim_city.rb +101 -0
- data/lib/thecompaniesapi/generated/models/nominatim_continent.rb +111 -0
- data/lib/thecompaniesapi/generated/models/nominatim_country.rb +121 -0
- data/lib/thecompaniesapi/generated/models/nominatim_county.rb +96 -0
- data/lib/thecompaniesapi/generated/models/nominatim_state.rb +96 -0
- data/lib/thecompaniesapi/generated/models/page_contents_ideated.rb +171 -0
- data/lib/thecompaniesapi/generated/models/page_contents_link.rb +41 -0
- data/lib/thecompaniesapi/generated/models/page_contents_page.rb +71 -0
- data/lib/thecompaniesapi/generated/models/pagination_meta.rb +76 -0
- data/lib/thecompaniesapi/generated/models/prompt.rb +91 -0
- data/lib/thecompaniesapi/generated/models/segmentation_condition.rb +56 -0
- data/lib/thecompaniesapi/generated/models/team.rb +101 -0
- data/lib/thecompaniesapi/generated/models/technology.rb +96 -0
- data/lib/thecompaniesapi/generated/models/user.rb +101 -0
- data/lib/thecompaniesapi/generated/operations_map.rb +11 -0
- data/lib/thecompaniesapi/generated/requests/ask_company_request.rb +60 -0
- data/lib/thecompaniesapi/generated/requests/count_companies_post_request.rb +50 -0
- data/lib/thecompaniesapi/generated/requests/create_list_request.rb +70 -0
- data/lib/thecompaniesapi/generated/requests/export_companies_analytics_request.rb +70 -0
- data/lib/thecompaniesapi/generated/requests/fetch_companies_in_list_post_request.rb +70 -0
- data/lib/thecompaniesapi/generated/requests/product_prompt_request.rb +65 -0
- data/lib/thecompaniesapi/generated/requests/prompt_to_segmentation_request.rb +55 -0
- data/lib/thecompaniesapi/generated/requests/request_action_request.rb +80 -0
- data/lib/thecompaniesapi/generated/requests/retry_action_request.rb +30 -0
- data/lib/thecompaniesapi/generated/requests/search_companies_post_request.rb +90 -0
- data/lib/thecompaniesapi/generated/requests/toggle_companies_in_list_request.rb +50 -0
- data/lib/thecompaniesapi/generated/requests/update_list_request.rb +65 -0
- data/lib/thecompaniesapi/generated/requests/update_team_request.rb +45 -0
- data/lib/thecompaniesapi/generated/responses/ask_company_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/count_companies_post_response.rb +34 -0
- data/lib/thecompaniesapi/generated/responses/count_companies_response.rb +34 -0
- data/lib/thecompaniesapi/generated/responses/create_list_response.rb +25 -0
- data/lib/thecompaniesapi/generated/responses/delete_list_response.rb +25 -0
- data/lib/thecompaniesapi/generated/responses/delete_prompt_response.rb +25 -0
- data/lib/thecompaniesapi/generated/responses/enrich_job_titles_response.rb +25 -0
- data/lib/thecompaniesapi/generated/responses/export_companies_analytics_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/fetch_actions_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/fetch_api_health_response.rb +42 -0
- data/lib/thecompaniesapi/generated/responses/fetch_companies_analytics_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/fetch_companies_in_list_post_response.rb +42 -0
- data/lib/thecompaniesapi/generated/responses/fetch_companies_in_list_response.rb +42 -0
- data/lib/thecompaniesapi/generated/responses/fetch_company_by_email_response.rb +42 -0
- data/lib/thecompaniesapi/generated/responses/fetch_company_by_social_response.rb +25 -0
- data/lib/thecompaniesapi/generated/responses/fetch_company_context_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/fetch_company_email_patterns_response.rb +25 -0
- data/lib/thecompaniesapi/generated/responses/fetch_company_in_list_response.rb +25 -0
- data/lib/thecompaniesapi/generated/responses/fetch_company_response.rb +25 -0
- data/lib/thecompaniesapi/generated/responses/fetch_lists_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/fetch_open_api_response.rb +30 -0
- data/lib/thecompaniesapi/generated/responses/fetch_prompts_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/fetch_team_response.rb +25 -0
- data/lib/thecompaniesapi/generated/responses/fetch_user_response.rb +25 -0
- data/lib/thecompaniesapi/generated/responses/product_prompt_response.rb +42 -0
- data/lib/thecompaniesapi/generated/responses/prompt_to_segmentation_response.rb +42 -0
- data/lib/thecompaniesapi/generated/responses/request_action_response.rb +34 -0
- data/lib/thecompaniesapi/generated/responses/retry_action_response.rb +34 -0
- data/lib/thecompaniesapi/generated/responses/search_cities_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/search_companies_by_name_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/search_companies_by_prompt_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/search_companies_post_response.rb +42 -0
- data/lib/thecompaniesapi/generated/responses/search_companies_response.rb +42 -0
- data/lib/thecompaniesapi/generated/responses/search_continents_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/search_counties_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/search_countries_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/search_industries_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/search_industries_similar_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/search_similar_companies_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/search_states_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/search_technologies_response.rb +38 -0
- data/lib/thecompaniesapi/generated/responses/toggle_companies_in_list_response.rb +25 -0
- data/lib/thecompaniesapi/generated/responses/update_list_response.rb +25 -0
- data/lib/thecompaniesapi/generated/responses/update_team_response.rb +25 -0
- data/lib/thecompaniesapi/http_client.rb +146 -0
- data/lib/thecompaniesapi/version.rb +3 -0
- data/lib/thecompaniesapi.rb +7 -0
- data/script/generate_client.rb +653 -0
- data/thecompaniesapi.gemspec +34 -0
- metadata +188 -0
@@ -0,0 +1,653 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'net/http'
|
5
|
+
require 'json'
|
6
|
+
require 'uri'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
##
|
10
|
+
# OpenAPI Client Generator for The Companies API Ruby SDK
|
11
|
+
#
|
12
|
+
# This script fetches the OpenAPI schema and generates operation methods
|
13
|
+
# that integrate with the existing Client class architecture.
|
14
|
+
class OpenAPIClientGenerator
|
15
|
+
DEFAULT_OPENAPI_URL = 'https://api.thecompaniesapi.com/v2/openapi'
|
16
|
+
|
17
|
+
attr_reader :openapi_url, :output_path, :schema
|
18
|
+
|
19
|
+
def initialize(openapi_url = DEFAULT_OPENAPI_URL, output_path = 'lib/thecompaniesapi/generated')
|
20
|
+
@openapi_url = openapi_url
|
21
|
+
@output_path = output_path
|
22
|
+
@schema = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate
|
26
|
+
puts "🚀 Generating Ruby SDK from OpenAPI schema...\n\n"
|
27
|
+
|
28
|
+
# Step 1: Fetch OpenAPI schema
|
29
|
+
fetch_schema
|
30
|
+
|
31
|
+
# Step 2: Generate operations map
|
32
|
+
operations = generate_operations_map
|
33
|
+
|
34
|
+
# Step 3: Generate model classes
|
35
|
+
generate_models
|
36
|
+
|
37
|
+
# Step 4: Generate API request classes
|
38
|
+
generate_api_requests(operations)
|
39
|
+
|
40
|
+
# Step 5: Generate API response classes
|
41
|
+
generate_api_responses(operations)
|
42
|
+
|
43
|
+
# Step 6: Generate client methods
|
44
|
+
client_methods = generate_client_methods(operations)
|
45
|
+
|
46
|
+
# Step 7: Create the generated client class
|
47
|
+
create_generated_client(client_methods, operations)
|
48
|
+
|
49
|
+
puts "✅ Generation complete!"
|
50
|
+
puts "📁 Generated files in: #{@output_path}"
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def fetch_schema
|
56
|
+
puts "📥 Fetching OpenAPI schema from #{@openapi_url}..."
|
57
|
+
|
58
|
+
uri = URI(@openapi_url)
|
59
|
+
|
60
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
61
|
+
request = Net::HTTP::Get.new(uri)
|
62
|
+
request['User-Agent'] = 'TheCompaniesApi-Ruby-SDK-Generator/1.0'
|
63
|
+
|
64
|
+
response = http.request(request)
|
65
|
+
|
66
|
+
unless response.code == '200'
|
67
|
+
raise "Failed to fetch OpenAPI schema: HTTP #{response.code}"
|
68
|
+
end
|
69
|
+
|
70
|
+
@schema = JSON.parse(response.body)
|
71
|
+
end
|
72
|
+
|
73
|
+
puts "✅ Schema fetched successfully"
|
74
|
+
rescue => e
|
75
|
+
raise "Error fetching schema: #{e.message}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def generate_operations_map
|
79
|
+
puts "🔧 Generating operations map..."
|
80
|
+
|
81
|
+
operations = {}
|
82
|
+
paths = @schema['paths'] || {}
|
83
|
+
|
84
|
+
paths.each do |path, path_data|
|
85
|
+
%w[get post put patch delete].each do |method|
|
86
|
+
next unless path_data[method]
|
87
|
+
|
88
|
+
operation = path_data[method]
|
89
|
+
operation_id = operation['operationId']
|
90
|
+
|
91
|
+
next unless operation_id
|
92
|
+
|
93
|
+
# Extract path parameters
|
94
|
+
path_params = path.scan(/\{([^}]+)\}/).flatten
|
95
|
+
|
96
|
+
operations[operation_id] = {
|
97
|
+
'path' => path,
|
98
|
+
'method' => method.upcase,
|
99
|
+
'path_params' => path_params,
|
100
|
+
'summary' => operation['summary'] || '',
|
101
|
+
'description' => operation['description'] || '',
|
102
|
+
'parameters' => operation['parameters'] || [],
|
103
|
+
'request_body' => operation['requestBody'],
|
104
|
+
'responses' => operation['responses'] || {}
|
105
|
+
}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
puts "✅ Generated #{operations.size} operations"
|
110
|
+
operations
|
111
|
+
end
|
112
|
+
|
113
|
+
def generate_models
|
114
|
+
puts "📋 Generating model classes..."
|
115
|
+
|
116
|
+
schemas = @schema.dig('components', 'schemas') || {}
|
117
|
+
model_count = 0
|
118
|
+
|
119
|
+
ensure_directory("#{@output_path}/models")
|
120
|
+
|
121
|
+
schemas.each do |schema_name, schema_data|
|
122
|
+
generate_model_class(schema_name, schema_data)
|
123
|
+
model_count += 1
|
124
|
+
end
|
125
|
+
|
126
|
+
puts "✅ Generated #{model_count} model classes"
|
127
|
+
end
|
128
|
+
|
129
|
+
def generate_model_class(name, schema)
|
130
|
+
class_name = to_pascal_case(name)
|
131
|
+
properties = schema['properties'] || {}
|
132
|
+
required = schema['required'] || []
|
133
|
+
|
134
|
+
class_content = <<~RUBY
|
135
|
+
# frozen_string_literal: true
|
136
|
+
|
137
|
+
module TheCompaniesAPI
|
138
|
+
module Generated
|
139
|
+
module Models
|
140
|
+
##
|
141
|
+
# #{class_name} model
|
142
|
+
#
|
143
|
+
RUBY
|
144
|
+
|
145
|
+
if schema['description']
|
146
|
+
class_content += " # #{schema['description']}\n"
|
147
|
+
end
|
148
|
+
|
149
|
+
class_content += <<~RUBY
|
150
|
+
class #{class_name}
|
151
|
+
RUBY
|
152
|
+
|
153
|
+
# Generate attr_accessors
|
154
|
+
properties.each do |prop_name, prop_data|
|
155
|
+
class_content += " # @return [#{get_ruby_type(prop_data)}] #{prop_data['description'] || prop_name}\n"
|
156
|
+
class_content += " attr_accessor :#{to_snake_case(prop_name)}\n\n"
|
157
|
+
end
|
158
|
+
|
159
|
+
# Generate constructor
|
160
|
+
class_content += <<~RUBY
|
161
|
+
##
|
162
|
+
# Initialize a new #{class_name}
|
163
|
+
#
|
164
|
+
# @param data [Hash] Initial data
|
165
|
+
def initialize(data = {})
|
166
|
+
RUBY
|
167
|
+
|
168
|
+
properties.each do |prop_name, prop_data|
|
169
|
+
snake_prop = to_snake_case(prop_name)
|
170
|
+
ruby_type = get_ruby_type(prop_data)
|
171
|
+
|
172
|
+
if ruby_type == 'Time'
|
173
|
+
class_content += " @#{snake_prop} = data['#{prop_name}'] ? Time.parse(data['#{prop_name}'].to_s) : nil\n"
|
174
|
+
else
|
175
|
+
class_content += " @#{snake_prop} = data['#{prop_name}']\n"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
class_content += <<~RUBY
|
180
|
+
end
|
181
|
+
|
182
|
+
##
|
183
|
+
# Convert to hash
|
184
|
+
#
|
185
|
+
# @return [Hash] Hash representation
|
186
|
+
def to_hash
|
187
|
+
{
|
188
|
+
RUBY
|
189
|
+
|
190
|
+
properties.each do |prop_name, prop_data|
|
191
|
+
snake_prop = to_snake_case(prop_name)
|
192
|
+
ruby_type = get_ruby_type(prop_data)
|
193
|
+
|
194
|
+
if ruby_type == 'Time'
|
195
|
+
class_content += " '#{prop_name}' => @#{snake_prop}&.iso8601,\n"
|
196
|
+
else
|
197
|
+
class_content += " '#{prop_name}' => @#{snake_prop},\n"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
class_content += <<~RUBY
|
202
|
+
}
|
203
|
+
end
|
204
|
+
|
205
|
+
alias_method :to_h, :to_hash
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
RUBY
|
211
|
+
|
212
|
+
File.write("#{@output_path}/models/#{to_snake_case(class_name)}.rb", class_content)
|
213
|
+
end
|
214
|
+
|
215
|
+
def generate_api_requests(operations)
|
216
|
+
puts "📤 Generating API request classes..."
|
217
|
+
|
218
|
+
request_count = 0
|
219
|
+
ensure_directory("#{@output_path}/requests")
|
220
|
+
|
221
|
+
operations.each do |operation_id, operation|
|
222
|
+
request_body = operation['request_body']
|
223
|
+
|
224
|
+
next unless request_body&.dig('content', 'application/json', 'schema')
|
225
|
+
|
226
|
+
schema = request_body['content']['application/json']['schema']
|
227
|
+
class_name = "#{to_pascal_case(operation_id)}Request"
|
228
|
+
|
229
|
+
generate_request_class(class_name, schema, operation_id)
|
230
|
+
request_count += 1
|
231
|
+
end
|
232
|
+
|
233
|
+
puts "✅ Generated #{request_count} API request classes"
|
234
|
+
end
|
235
|
+
|
236
|
+
def generate_request_class(class_name, schema, operation_id)
|
237
|
+
properties = schema['properties'] || {}
|
238
|
+
required = schema['required'] || []
|
239
|
+
|
240
|
+
class_content = <<~RUBY
|
241
|
+
# frozen_string_literal: true
|
242
|
+
|
243
|
+
module TheCompaniesAPI
|
244
|
+
module Generated
|
245
|
+
module Requests
|
246
|
+
##
|
247
|
+
# #{class_name} - API request class
|
248
|
+
#
|
249
|
+
RUBY
|
250
|
+
|
251
|
+
if schema['description']
|
252
|
+
class_content += " # #{schema['description']}\n #\n"
|
253
|
+
end
|
254
|
+
|
255
|
+
class_content += <<~RUBY
|
256
|
+
class #{class_name}
|
257
|
+
RUBY
|
258
|
+
|
259
|
+
# Generate attr_accessors
|
260
|
+
properties.each do |prop_name, prop_data|
|
261
|
+
snake_prop = to_snake_case(prop_name)
|
262
|
+
class_content += " # @return [#{get_ruby_type(prop_data)}] #{prop_data['description'] || prop_name}\n"
|
263
|
+
class_content += " attr_accessor :#{snake_prop}\n\n"
|
264
|
+
end
|
265
|
+
|
266
|
+
# Generate constructor
|
267
|
+
class_content += <<~RUBY
|
268
|
+
##
|
269
|
+
# Initialize a new #{class_name}
|
270
|
+
#
|
271
|
+
# @param data [Hash] Request data
|
272
|
+
def initialize(data = {})
|
273
|
+
RUBY
|
274
|
+
|
275
|
+
properties.each do |prop_name, prop_data|
|
276
|
+
snake_prop = to_snake_case(prop_name)
|
277
|
+
ruby_type = get_ruby_type(prop_data)
|
278
|
+
|
279
|
+
if ruby_type == 'Time'
|
280
|
+
class_content += " @#{snake_prop} = data['#{prop_name}'] || data[:#{snake_prop}]\n"
|
281
|
+
class_content += " @#{snake_prop} = Time.parse(@#{snake_prop}.to_s) if @#{snake_prop}.is_a?(String)\n"
|
282
|
+
else
|
283
|
+
class_content += " @#{snake_prop} = data['#{prop_name}'] || data[:#{snake_prop}]\n"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
class_content += <<~RUBY
|
288
|
+
end
|
289
|
+
|
290
|
+
##
|
291
|
+
# Convert to hash for API request
|
292
|
+
#
|
293
|
+
# @return [Hash] Hash representation
|
294
|
+
def to_hash
|
295
|
+
{
|
296
|
+
RUBY
|
297
|
+
|
298
|
+
properties.each do |prop_name, prop_data|
|
299
|
+
snake_prop = to_snake_case(prop_name)
|
300
|
+
ruby_type = get_ruby_type(prop_data)
|
301
|
+
|
302
|
+
if ruby_type == 'Time'
|
303
|
+
class_content += " '#{prop_name}' => @#{snake_prop}&.iso8601,\n"
|
304
|
+
else
|
305
|
+
class_content += " '#{prop_name}' => @#{snake_prop},\n"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
class_content += <<~RUBY
|
310
|
+
}.compact
|
311
|
+
end
|
312
|
+
|
313
|
+
alias_method :to_h, :to_hash
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
RUBY
|
319
|
+
|
320
|
+
File.write("#{@output_path}/requests/#{to_snake_case(class_name)}.rb", class_content)
|
321
|
+
end
|
322
|
+
|
323
|
+
def generate_api_responses(operations)
|
324
|
+
puts "📥 Generating API response classes..."
|
325
|
+
|
326
|
+
response_count = 0
|
327
|
+
ensure_directory("#{@output_path}/responses")
|
328
|
+
|
329
|
+
operations.each do |operation_id, operation|
|
330
|
+
responses = operation['responses']
|
331
|
+
|
332
|
+
# Look for successful responses (200, 201, etc.)
|
333
|
+
[200, 201, 202].each do |status_code|
|
334
|
+
response = responses[status_code.to_s]
|
335
|
+
next unless response&.dig('content', 'application/json', 'schema')
|
336
|
+
|
337
|
+
schema = response['content']['application/json']['schema']
|
338
|
+
class_name = "#{to_pascal_case(operation_id)}Response"
|
339
|
+
|
340
|
+
generate_response_class(class_name, schema, operation)
|
341
|
+
response_count += 1
|
342
|
+
break # Only generate for the first successful response
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
puts "✅ Generated #{response_count} API response classes"
|
347
|
+
end
|
348
|
+
|
349
|
+
def generate_response_class(class_name, schema, operation)
|
350
|
+
class_content = <<~RUBY
|
351
|
+
# frozen_string_literal: true
|
352
|
+
|
353
|
+
module TheCompaniesAPI
|
354
|
+
module Generated
|
355
|
+
module Responses
|
356
|
+
##
|
357
|
+
# #{class_name} - API response class
|
358
|
+
#
|
359
|
+
RUBY
|
360
|
+
|
361
|
+
if operation['summary']
|
362
|
+
class_content += " # #{operation['summary']}\n #\n"
|
363
|
+
end
|
364
|
+
|
365
|
+
class_content += " class #{class_name}\n"
|
366
|
+
|
367
|
+
if schema['type'] == 'object' && schema['properties']
|
368
|
+
# Object response with properties
|
369
|
+
properties = schema['properties']
|
370
|
+
|
371
|
+
properties.each do |prop_name, prop_data|
|
372
|
+
snake_prop = to_snake_case(prop_name)
|
373
|
+
class_content += " # @return [#{get_ruby_type(prop_data)}] #{prop_data['description'] || prop_name}\n"
|
374
|
+
class_content += " attr_reader :#{snake_prop}\n\n"
|
375
|
+
end
|
376
|
+
|
377
|
+
class_content += <<~RUBY
|
378
|
+
##
|
379
|
+
# Initialize a new #{class_name}
|
380
|
+
#
|
381
|
+
# @param data [Hash] Response data
|
382
|
+
def initialize(data = {})
|
383
|
+
RUBY
|
384
|
+
|
385
|
+
properties.each do |prop_name, prop_data|
|
386
|
+
snake_prop = to_snake_case(prop_name)
|
387
|
+
class_content += " @#{snake_prop} = convert_property(data['#{prop_name}'], '#{prop_name}')\n"
|
388
|
+
end
|
389
|
+
|
390
|
+
class_content += <<~RUBY
|
391
|
+
end
|
392
|
+
|
393
|
+
private
|
394
|
+
|
395
|
+
def convert_property(value, property)
|
396
|
+
case property
|
397
|
+
RUBY
|
398
|
+
|
399
|
+
# Add specific conversions for typed properties
|
400
|
+
properties.each do |prop_name, prop_data|
|
401
|
+
ruby_type = get_ruby_type(prop_data)
|
402
|
+
if ruby_type.start_with?('TheCompaniesAPI::Generated::Models::')
|
403
|
+
class_content += " when '#{prop_name}'\n"
|
404
|
+
class_content += " value.is_a?(Hash) ? #{ruby_type}.new(value) : value\n"
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
class_content += <<~RUBY
|
409
|
+
else
|
410
|
+
value
|
411
|
+
end
|
412
|
+
end
|
413
|
+
RUBY
|
414
|
+
|
415
|
+
elsif schema['type'] == 'array'
|
416
|
+
class_content += <<~RUBY
|
417
|
+
# @return [Array] Response data
|
418
|
+
attr_reader :data
|
419
|
+
|
420
|
+
##
|
421
|
+
# Initialize a new #{class_name}
|
422
|
+
#
|
423
|
+
# @param data [Array] Response data
|
424
|
+
def initialize(data = [])
|
425
|
+
@data = data
|
426
|
+
end
|
427
|
+
RUBY
|
428
|
+
else
|
429
|
+
class_content += <<~RUBY
|
430
|
+
# @return [Object] Response data
|
431
|
+
attr_reader :data
|
432
|
+
|
433
|
+
##
|
434
|
+
# Initialize a new #{class_name}
|
435
|
+
#
|
436
|
+
# @param data [Object] Response data
|
437
|
+
def initialize(data = nil)
|
438
|
+
@data = data
|
439
|
+
end
|
440
|
+
RUBY
|
441
|
+
end
|
442
|
+
|
443
|
+
class_content += " end\n end\n end\n end\n"
|
444
|
+
|
445
|
+
File.write("#{@output_path}/responses/#{to_snake_case(class_name)}.rb", class_content)
|
446
|
+
end
|
447
|
+
|
448
|
+
def generate_client_methods(operations)
|
449
|
+
puts "🏗️ Generating client methods..."
|
450
|
+
|
451
|
+
methods = []
|
452
|
+
|
453
|
+
operations.each do |operation_id, operation|
|
454
|
+
method_name = to_snake_case(operation_id)
|
455
|
+
path = operation['path']
|
456
|
+
http_method = operation['method']
|
457
|
+
path_params = operation['path_params']
|
458
|
+
summary = operation['summary']
|
459
|
+
description = operation['description']
|
460
|
+
request_body = operation['request_body']
|
461
|
+
responses = operation['responses']
|
462
|
+
|
463
|
+
# Generate method
|
464
|
+
method_content = generate_method(
|
465
|
+
method_name, path, http_method, path_params,
|
466
|
+
summary, description, request_body, responses, operation_id
|
467
|
+
)
|
468
|
+
|
469
|
+
methods << method_content
|
470
|
+
end
|
471
|
+
|
472
|
+
puts "✅ Generated #{methods.size} client methods"
|
473
|
+
methods.join("\n\n")
|
474
|
+
end
|
475
|
+
|
476
|
+
def generate_method(method_name, path, http_method, path_params, summary, description, request_body, responses, operation_id)
|
477
|
+
# Generate YARD documentation
|
478
|
+
doc_lines = [" ##", " # #{summary}"]
|
479
|
+
doc_lines << " #" if summary != description && !description.empty?
|
480
|
+
doc_lines << " # #{description}" if !description.empty? && description != summary
|
481
|
+
doc_lines << " #"
|
482
|
+
|
483
|
+
# Add path parameters to documentation
|
484
|
+
path_params.each do |param|
|
485
|
+
doc_lines << " # @param #{to_snake_case(param)} [String] Path parameter"
|
486
|
+
end
|
487
|
+
|
488
|
+
if %w[GET DELETE].include?(http_method)
|
489
|
+
doc_lines << " # @param params [Hash] Query parameters"
|
490
|
+
else
|
491
|
+
doc_lines << " # @param data [Hash] Request body data"
|
492
|
+
end
|
493
|
+
|
494
|
+
doc_lines << " # @param headers [Hash] Additional headers"
|
495
|
+
doc_lines << " # @return [Hash] API response"
|
496
|
+
|
497
|
+
# Generate method signature
|
498
|
+
signature_params = []
|
499
|
+
path_params.each { |param| signature_params << "#{to_snake_case(param)}" }
|
500
|
+
|
501
|
+
if %w[GET DELETE].include?(http_method)
|
502
|
+
signature_params << "params: {}"
|
503
|
+
else
|
504
|
+
signature_params << "data: {}"
|
505
|
+
end
|
506
|
+
|
507
|
+
signature_params << "headers: {}"
|
508
|
+
signature = "def #{method_name}(#{signature_params.join(', ')})"
|
509
|
+
|
510
|
+
# Generate method body
|
511
|
+
body_lines = []
|
512
|
+
|
513
|
+
# Build endpoint path with parameter substitution
|
514
|
+
if path_params.any?
|
515
|
+
path_expression = path.dup
|
516
|
+
path_params.each do |param|
|
517
|
+
snake_param = to_snake_case(param)
|
518
|
+
path_expression = path_expression.gsub("{#{param}}", "\#{#{snake_param}}")
|
519
|
+
end
|
520
|
+
body_lines << " endpoint = \"#{path_expression}\""
|
521
|
+
else
|
522
|
+
body_lines << " endpoint = '#{path}'"
|
523
|
+
end
|
524
|
+
|
525
|
+
body_lines << ""
|
526
|
+
|
527
|
+
# Make the HTTP request
|
528
|
+
if %w[GET DELETE].include?(http_method)
|
529
|
+
body_lines << " #{http_method.downcase}(endpoint, params: params)"
|
530
|
+
else
|
531
|
+
body_lines << " #{http_method.downcase}(endpoint, body: data)"
|
532
|
+
end
|
533
|
+
|
534
|
+
# Combine everything
|
535
|
+
[
|
536
|
+
doc_lines.join("\n"),
|
537
|
+
" #{signature}",
|
538
|
+
body_lines.map { |line| " #{line}" }.join("\n"),
|
539
|
+
" end"
|
540
|
+
].join("\n")
|
541
|
+
end
|
542
|
+
|
543
|
+
def create_generated_client(methods, operations)
|
544
|
+
puts "🏗️ Creating generated client class..."
|
545
|
+
|
546
|
+
# Generate the main Client class with all API methods
|
547
|
+
class_content = <<~RUBY
|
548
|
+
# frozen_string_literal: true
|
549
|
+
|
550
|
+
require_relative 'http_client'
|
551
|
+
|
552
|
+
module TheCompaniesAPI
|
553
|
+
##
|
554
|
+
# The Companies API client with auto-generated methods
|
555
|
+
#
|
556
|
+
# This class extends HttpClient with auto-generated methods
|
557
|
+
# from the OpenAPI specification.
|
558
|
+
class Client < HttpClient
|
559
|
+
#{methods}
|
560
|
+
end
|
561
|
+
end
|
562
|
+
RUBY
|
563
|
+
|
564
|
+
# Write to the main client file, not the generated subdirectory
|
565
|
+
File.write("#{File.dirname(@output_path)}/client.rb", class_content)
|
566
|
+
|
567
|
+
# Create operations map in the generated directory for reference
|
568
|
+
operations_content = <<~RUBY
|
569
|
+
# frozen_string_literal: true
|
570
|
+
|
571
|
+
module TheCompaniesAPI
|
572
|
+
module Generated
|
573
|
+
##
|
574
|
+
# Operations map auto-generated from OpenAPI schema
|
575
|
+
module OperationsMap
|
576
|
+
OPERATIONS = #{operations.inspect}
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|
580
|
+
RUBY
|
581
|
+
|
582
|
+
File.write("#{@output_path}/operations_map.rb", operations_content)
|
583
|
+
end
|
584
|
+
|
585
|
+
# Helper methods
|
586
|
+
def to_snake_case(string)
|
587
|
+
string
|
588
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
589
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
590
|
+
.downcase
|
591
|
+
end
|
592
|
+
|
593
|
+
def to_pascal_case(string)
|
594
|
+
# Handle camelCase, snake_case, and kebab-case
|
595
|
+
string
|
596
|
+
.gsub(/([a-z])([A-Z])/, '\1_\2') # Insert underscore between camelCase
|
597
|
+
.split(/[-_]/) # Split on hyphens and underscores
|
598
|
+
.map(&:capitalize) # Capitalize each word
|
599
|
+
.join # Join without separators
|
600
|
+
end
|
601
|
+
|
602
|
+
def get_ruby_type(property)
|
603
|
+
type = property['type']
|
604
|
+
format = property['format']
|
605
|
+
|
606
|
+
return 'Object' unless type
|
607
|
+
|
608
|
+
case type
|
609
|
+
when 'string'
|
610
|
+
case format
|
611
|
+
when 'date', 'date-time'
|
612
|
+
'Time'
|
613
|
+
else
|
614
|
+
'String'
|
615
|
+
end
|
616
|
+
when 'integer'
|
617
|
+
'Integer'
|
618
|
+
when 'number'
|
619
|
+
'Float'
|
620
|
+
when 'boolean'
|
621
|
+
'Boolean'
|
622
|
+
when 'array'
|
623
|
+
'Array'
|
624
|
+
when 'object'
|
625
|
+
'Hash'
|
626
|
+
else
|
627
|
+
'Object'
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
def ensure_directory(path)
|
632
|
+
FileUtils.mkdir_p(path) unless Dir.exist?(path)
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
# CLI Usage
|
637
|
+
if ARGV.empty?
|
638
|
+
puts "Usage: ruby generate_client.rb [openapi-url] [output-dir]"
|
639
|
+
puts "Example: ruby generate_client.rb https://api.thecompaniesapi.com/v2/openapi lib/thecompaniesapi/generated"
|
640
|
+
exit 1
|
641
|
+
end
|
642
|
+
|
643
|
+
openapi_url = ARGV[0] || OpenAPIClientGenerator::DEFAULT_OPENAPI_URL
|
644
|
+
output_dir = ARGV[1] || 'lib/thecompaniesapi/generated'
|
645
|
+
|
646
|
+
begin
|
647
|
+
generator = OpenAPIClientGenerator.new(openapi_url, output_dir)
|
648
|
+
generator.generate
|
649
|
+
rescue => e
|
650
|
+
puts "❌ Error: #{e.message}"
|
651
|
+
puts e.backtrace.join("\n") if ENV['DEBUG']
|
652
|
+
exit 1
|
653
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'lib/thecompaniesapi/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "thecompaniesapi"
|
5
|
+
spec.version = TheCompaniesAPI::VERSION
|
6
|
+
spec.authors = ["The Companies API SARL"]
|
7
|
+
spec.email = ["yael@thecompaniesapi.com"]
|
8
|
+
|
9
|
+
spec.summary = 'Ruby SDK for The Companies API'
|
10
|
+
spec.description = 'A fully-featured Ruby SDK for The Companies API, providing type-safe access to company data, locations, industries, technologies, job titles, lists, and more.'
|
11
|
+
spec.homepage = "https://github.com/thecompaniesapi/sdk-ruby"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
14
|
+
|
15
|
+
spec.metadata["homepage_uri"] = "https://www.thecompaniesapi.com"
|
16
|
+
spec.metadata["source_code_uri"] = "https://github.com/thecompaniesapi/sdk-ruby"
|
17
|
+
spec.metadata["changelog_uri"] = "https://github.com/thecompaniesapi/sdk-ruby/releases"
|
18
|
+
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
20
|
+
# The `git ls-files -z` command works on MacOS to list files tracked by git.
|
21
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
22
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
|
+
end
|
24
|
+
spec.bindir = "exe"
|
25
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
# Runtime dependencies
|
29
|
+
spec.add_runtime_dependency "faraday", "~> 2.0"
|
30
|
+
spec.add_runtime_dependency "faraday-net_http", "~> 3.0"
|
31
|
+
|
32
|
+
# Development dependencies
|
33
|
+
spec.add_development_dependency "dotenv", "~> 2.8"
|
34
|
+
end
|