taimour_openapi_sdk_generator 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: edb42df3a6a1c4802d39ed957bbb54daa2d3d2352524a06486412477336db50d
4
+ data.tar.gz: d98bce34a6c069bd33bc0d1e1e127122df935077e8c278bf3fefa783bb6c921f
5
+ SHA512:
6
+ metadata.gz: 4f49be3da129f05378d4946a921b0fa7e164473c225b2418b3dfb04e696bdf9288f50c1534b71dd4c172b2ea650e963edebfa9c98c1a8136c4c7a167162e0634
7
+ data.tar.gz: 488c788f5e003b49247311b16a809f828ea7da34b261ea9cc0b0932b839fd55680d6a7cccbe5bf819c963761e72afa065c0e738ac5ed49cbac98f3db15983a42
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require_relative '../lib/openapi_sdk_generator'
5
+
6
+ options = {}
7
+
8
+ parser = OptionParser.new do |opts|
9
+ opts.banner = "Usage: openapi-sdk-generator [options]"
10
+ opts.separator ""
11
+ opts.separator "Generate SDK from OpenAPI specification"
12
+ opts.separator ""
13
+
14
+ opts.on("-i", "--input FILE", "OpenAPI specification file (JSON or YAML)") do |v|
15
+ options[:input] = v
16
+ end
17
+
18
+ opts.on("-o", "--output DIR", "Output directory for generated SDK") do |v|
19
+ options[:output] = v
20
+ end
21
+
22
+ opts.on("-l", "--language LANG", "Target language (ruby, javascript)") do |v|
23
+ options[:language] = v
24
+ end
25
+
26
+ opts.on("-h", "--help", "Show this help message") do
27
+ puts opts
28
+ exit
29
+ end
30
+
31
+ opts.on("-v", "--version", "Show version") do
32
+ puts "OpenAPI SDK Generator v0.1.0"
33
+ exit
34
+ end
35
+ end
36
+
37
+ begin
38
+ parser.parse!
39
+
40
+ if options.empty?
41
+ puts parser
42
+ exit
43
+ end
44
+
45
+ OpenapiSdkGenerator::CLI.new(options).run
46
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
47
+ puts "Error: #{e.message}"
48
+ puts parser
49
+ exit 1
50
+ rescue OpenapiSdkGenerator::Error => e
51
+ puts "Error: #{e.message}"
52
+ exit 1
53
+ rescue => e
54
+ puts "Unexpected error: #{e.message}"
55
+ puts e.backtrace.first(5)
56
+ exit 1
57
+ end
@@ -0,0 +1,36 @@
1
+ module OpenapiSdkGenerator
2
+ class Generator
3
+ attr_reader :parser
4
+
5
+ def initialize(parser)
6
+ @parser = parser
7
+ end
8
+
9
+ def generate
10
+ raise NotImplementedError, "Subclasses must implement #generate"
11
+ end
12
+
13
+ def write_to_directory(output_dir)
14
+ raise NotImplementedError, "Subclasses must implement #write_to_directory"
15
+ end
16
+
17
+ protected
18
+
19
+ def render_template(template_name, binding_context)
20
+ template_path = File.join(__dir__, 'templates', template_name)
21
+ template = File.read(template_path)
22
+ ERB.new(template, trim_mode: '-').result(binding_context)
23
+ end
24
+
25
+ def sanitize_name(name)
26
+ puts "DEBUG param: #{p.inspect} (#{p.class})"
27
+
28
+ # Convert to snake_case and remove special characters
29
+ name.gsub(/[^a-zA-Z0-9_]/, '_').gsub(/_{2,}/, '_').downcase
30
+ end
31
+
32
+ def camelize(string)
33
+ string.split('_').map(&:capitalize).join
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,107 @@
1
+ module OpenapiSdkGenerator
2
+ module Generators
3
+ class JavascriptGenerator < Generator
4
+ def generate
5
+ {
6
+ client: generate_client
7
+ }
8
+ end
9
+
10
+ def write_to_directory(output_dir)
11
+ FileUtils.mkdir_p(output_dir)
12
+
13
+ # Write client file
14
+ client_content = generate_client
15
+ File.write(File.join(output_dir, 'client.js'), client_content)
16
+
17
+ # Write package.json
18
+ package_json = generate_package_json
19
+ File.write(File.join(output_dir, 'package.json'), package_json)
20
+
21
+ # Write README
22
+ readme_content = generate_readme
23
+ File.write(File.join(output_dir, 'README.md'), readme_content)
24
+ end
25
+
26
+ private
27
+
28
+
29
+ def generate_client
30
+ render_template('javascript_client.erb', binding)
31
+ end
32
+
33
+ def format_js_params(endpoint)
34
+ return "" unless endpoint[:parameters]
35
+
36
+ endpoint[:parameters].map { |p| sanitize_name(p[:name]) }.join(", ")
37
+ end
38
+
39
+ def generate_client
40
+ render_template('javascript_client.erb', binding)
41
+ end
42
+
43
+ def generate_package_json
44
+ {
45
+ name: sanitize_name(parser.api_title).tr('_', '-'),
46
+ version: parser.api_version,
47
+ description: parser.api_description,
48
+ main: "client.js",
49
+ scripts: {
50
+ test: "echo \"Error: no test specified\" && exit 1"
51
+ },
52
+ keywords: ["api", "client", "sdk"],
53
+ author: "",
54
+ license: "MIT"
55
+ }.to_json
56
+ end
57
+
58
+ def generate_readme
59
+ <<~README
60
+ # #{parser.api_title}
61
+
62
+ #{parser.api_description}
63
+
64
+ Version: #{parser.api_version}
65
+
66
+ ## Installation
67
+ ```bash
68
+
69
+ npm install
70
+
71
+ ## Usage
72
+ ```javascript
73
+
74
+ const APIClient = require('./client');
75
+
76
+
77
+ // Example usage
78
+ #{parser.endpoints.first ? "// #{parser.endpoints.first[:summary]}" : '// Make API calls'}
79
+ #{parser.endpoints.first ? "// const response = await client.#{js_method_name(parser.endpoints.first)}(...);" : ''}
80
+
81
+
82
+ ## Available Methods
83
+
84
+ #{parser.endpoints.map { |e| "- `#{js_method_name(e)}()` - #{e[:summary]}" }.join("\n")}
85
+ README
86
+ end
87
+
88
+ def js_method_name(endpoint)
89
+ op = endpoint[:operation_id] || "#{endpoint[:method]}_#{endpoint[:path]}"
90
+ parts = sanitize_name(op).split('_')
91
+ parts.first + parts[1..].map(&:capitalize).join
92
+ end
93
+
94
+
95
+ def js_type(openapi_type)
96
+ case openapi_type
97
+ when 'integer', 'number' then 'number'
98
+ when 'string' then 'string'
99
+ when 'boolean' then 'boolean'
100
+ when 'array' then 'array'
101
+ when 'object' then 'object'
102
+ else 'any'
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,148 @@
1
+ module OpenapiSdkGenerator
2
+ module Generators
3
+ class RubyGenerator < Generator
4
+ def generate
5
+ {
6
+ client: generate_client,
7
+ models: generate_models
8
+ }
9
+ end
10
+
11
+ def write_to_directory(output_dir)
12
+ FileUtils.mkdir_p(output_dir)
13
+ FileUtils.mkdir_p(File.join(output_dir, 'models'))
14
+
15
+ # Write client file
16
+ client_content = generate_client
17
+ File.write(File.join(output_dir, 'client.rb'), client_content)
18
+
19
+ # Write model files
20
+ parser.models.each do |name, model|
21
+ model_content = generate_model(model)
22
+ filename = "#{sanitize_name(name)}.rb"
23
+ File.write(File.join(output_dir, 'models', filename), model_content)
24
+ end
25
+
26
+ # Write README
27
+ readme_content = generate_readme
28
+ File.write(File.join(output_dir, 'README.md'), readme_content)
29
+ end
30
+
31
+ private
32
+
33
+ def generate_client
34
+ render_template('ruby_client.erb', binding)
35
+ end
36
+
37
+ def generate_models
38
+ parser.models.map do |name, model|
39
+ generate_model(model)
40
+ end
41
+ end
42
+
43
+ def generate_model(model)
44
+ @current_model = model
45
+ render_template('ruby_model.erb', binding)
46
+ end
47
+
48
+ def generate_readme
49
+ <<~README
50
+ # #{parser.api_title}
51
+
52
+ #{parser.api_description}
53
+
54
+ Version: #{parser.api_version}
55
+
56
+ ## Installation
57
+
58
+ Add this to your application's Gemfile:
59
+ ```ruby
60
+ gem 'your_gem_name'
61
+ ```
62
+
63
+ ## Usage
64
+ ```ruby
65
+ require_relative 'client'
66
+
67
+ client = APIClient.new('#{parser.base_url}')
68
+
69
+ # Example usage
70
+ #{parser.endpoints.first ? "# #{parser.endpoints.first[:summary]}" : '# Make API calls'}
71
+ #{parser.endpoints.first ? "# response = client.#{method_name(parser.endpoints.first)}(...)" : ''}
72
+ ```
73
+
74
+ ## Available Methods
75
+
76
+ #{parser.endpoints.map { |e| "- `#{method_name(e)}` - #{e[:summary]}" }.join("\n")}
77
+ README
78
+ end
79
+
80
+ def method_name(endpoint)
81
+ sanitize_name(endpoint[:operation_id])
82
+ end
83
+
84
+ def ruby_type(openapi_type)
85
+ case openapi_type
86
+ when 'integer' then 'Integer'
87
+ when 'number' then 'Float'
88
+ when 'string' then 'String'
89
+ when 'boolean' then 'Boolean'
90
+ when 'array' then 'Array'
91
+ when 'object' then 'Hash'
92
+ else 'Object'
93
+ end
94
+ end
95
+
96
+
97
+
98
+
99
+ # Converts parameters into Ruby method signature
100
+ def format_method_params(params)
101
+ return "" unless params.is_a?(Array) && !params.empty?
102
+
103
+ params.map do |p|
104
+ next unless p.is_a?(Hash)
105
+
106
+ name = sanitize_name(p[:name].to_s)
107
+ required = p[:required] ? "" : " = nil"
108
+ "#{name}#{required}"
109
+ end.compact.join(", ")
110
+ end
111
+
112
+ # Converts "/pets/{petId}" → "/pets/#{pet_id}"
113
+ def format_url_path(path, params = [])
114
+ return path unless params.is_a?(Array) && !params.empty?
115
+
116
+ params
117
+ .select { |p| p[:location] == "path" } # FIXED HERE
118
+ .reduce(path) do |memo, p|
119
+ ruby_name = sanitize_name(p[:name].to_s)
120
+ memo.gsub("{#{p[:name]}}", "\#{#{ruby_name}}")
121
+ end
122
+ end
123
+
124
+ # Converts OpenAPI type to Ruby class (used by templates)
125
+ def format_ruby_type(schema)
126
+ return "Object" if schema.nil?
127
+
128
+ case schema[:type]
129
+ when "integer" then "Integer"
130
+ when "number" then "Float"
131
+ when "string" then "String"
132
+ when "boolean" then "Boolean"
133
+ when "array" then "Array"
134
+ when "object" then "Hash"
135
+ else
136
+ if schema.is_a?(Hash) && schema["$ref"]
137
+ sanitize_name(schema["$ref"].split('/').last)
138
+ else
139
+ "Object"
140
+ end
141
+ end
142
+ end
143
+
144
+
145
+
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,158 @@
1
+ module OpenapiSdkGenerator
2
+ class Parser
3
+ attr_reader :spec, :api_info, :base_url, :endpoints, :models
4
+
5
+ def initialize(file_path)
6
+ @file_path = file_path
7
+ @spec = load_spec
8
+ @endpoints = []
9
+ @models = {}
10
+ @api_info = {}
11
+ parse_spec
12
+ end
13
+
14
+ def api_title
15
+ @api_info[:title] || 'Generated API Client'
16
+ end
17
+
18
+ def api_version
19
+ @api_info[:version] || '1.0.0'
20
+ end
21
+
22
+ def api_description
23
+ @api_info[:description] || 'API client generated from OpenAPI specification'
24
+ end
25
+
26
+ private
27
+
28
+ def load_spec
29
+ content = File.read(@file_path)
30
+ if @file_path.end_with?('.json')
31
+ JSON.parse(content)
32
+ elsif @file_path.end_with?('.yaml', '.yml')
33
+ YAML.load(content)
34
+ else
35
+ raise Error, "Unsupported file format. Use .json, .yaml, or .yml"
36
+ end
37
+ rescue => e
38
+ raise Error, "Failed to parse OpenAPI spec: #{e.message}"
39
+ end
40
+
41
+ def parse_spec
42
+ parse_info
43
+ parse_servers
44
+ parse_paths
45
+ parse_schemas
46
+ end
47
+
48
+ def parse_info
49
+ info = @spec['info'] || {}
50
+ @api_info = {
51
+ title: info['title'],
52
+ version: info['version'],
53
+ description: info['description']
54
+ }
55
+ end
56
+
57
+ def parse_servers
58
+ servers = @spec['servers'] || []
59
+ @base_url = servers.first&.dig('url') || 'https://api.example.com'
60
+ end
61
+
62
+ def parse_paths
63
+ paths = @spec['paths'] || {}
64
+
65
+ paths.each do |path, methods|
66
+ methods.each do |method, details|
67
+ next if method.start_with?('$') # Skip special keys like $ref
68
+
69
+ @endpoints << {
70
+ path: path,
71
+ method: method.upcase,
72
+ operation_id: details['operationId'] || generate_operation_id(method, path),
73
+ summary: details['summary'],
74
+ description: details['description'],
75
+ parameters: parse_parameters(details['parameters']),
76
+ request_body: parse_request_body(details['requestBody']),
77
+ responses: parse_responses(details['responses'])
78
+ }
79
+ end
80
+ end
81
+ end
82
+
83
+ def parse_parameters(parameters)
84
+ return [] unless parameters
85
+
86
+ parameters.map do |param|
87
+ {
88
+ name: param['name'],
89
+ location: param['in'],
90
+ required: param['required'] || false,
91
+ type: param['schema']&.dig('type') || 'string',
92
+ description: param['description']
93
+ }
94
+ end
95
+ end
96
+
97
+ def parse_request_body(request_body)
98
+ return nil unless request_body
99
+
100
+ content = request_body['content'] || {}
101
+ json_content = content['application/json'] || {}
102
+
103
+ {
104
+ required: request_body['required'] || false,
105
+ schema: json_content['schema']
106
+ }
107
+ end
108
+
109
+ def parse_responses(responses)
110
+ return {} unless responses
111
+
112
+ responses.transform_values do |response|
113
+ {
114
+ description: response['description'],
115
+ content: response['content']
116
+ }
117
+ end
118
+ end
119
+
120
+ def parse_schemas
121
+ components = @spec['components'] || {}
122
+ schemas = components['schemas'] || {}
123
+
124
+ schemas.each do |name, schema|
125
+ @models[name] = {
126
+ name: name,
127
+ type: schema['type'],
128
+ properties: parse_properties(schema['properties']),
129
+ required: schema['required'] || []
130
+ }
131
+ end
132
+ end
133
+
134
+ def parse_properties(properties)
135
+ return {} unless properties
136
+
137
+ properties.transform_values do |prop|
138
+ {
139
+ type: prop['type'],
140
+ format: prop['format'],
141
+ description: prop['description'],
142
+ items: prop['items']
143
+ }
144
+ end
145
+ end
146
+
147
+ def generate_operation_id(method, path)
148
+ # Generate operation ID from method and path
149
+ # e.g., GET /users/{id} -> getUserById
150
+ path_parts = path.split('/').reject(&:empty?)
151
+ path_name = path_parts.map do |part|
152
+ part.start_with?('{') ? "by_#{part.tr('{}', '')}" : part
153
+ end.join('_')
154
+
155
+ "#{method}_#{path_name}".downcase
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,102 @@
1
+ /**
2
+ * <%= parser.api_title %>
3
+ * <%= parser.api_description %>
4
+ * Version: <%= parser.api_version %>
5
+ */
6
+
7
+ class APIClient {
8
+ constructor(baseUrl = '<%= parser.base_url %>', apiKey = null) {
9
+ this.baseUrl = baseUrl;
10
+ this.headers = {
11
+ 'Content-Type': 'application/json',
12
+ 'Accept': 'application/json'
13
+ };
14
+
15
+ if (apiKey) {
16
+ this.headers['Authorization'] = `Bearer ${apiKey}`;
17
+ }
18
+ }
19
+
20
+ <% parser.endpoints.each do |endpoint| %>
21
+ /**
22
+ * <%= endpoint[:summary] %>
23
+ * <%= endpoint[:description] %>
24
+ <%- params = endpoint[:parameters] || [] -%>
25
+ <%- params.each do |param| -%>
26
+ * @param {<%= js_type(param[:type]) %>} <%= sanitize_name(param[:name]) %> - <%= param[:description] %>
27
+ <%- end -%>
28
+ <%- if endpoint[:request_body] -%>
29
+ * @param {object} body - Request body
30
+ <%- end -%>
31
+ * @returns {Promise<object>}
32
+ */
33
+ async <%= js_method_name(endpoint) %>(<%= format_js_params(endpoint) %>) {
34
+ <%- path_params = params.select { |p| p[:location] == 'path' } -%>
35
+ <%- query_params = params.select { |p| p[:location] == 'query' } -%>
36
+ <%- if path_params.any? -%>
37
+ let path = '<%= endpoint[:path] %>';
38
+ <%- path_params.each do |param| -%>
39
+ path = path.replace('{<%= param[:name] %>}', <%= sanitize_name(param[:name]) %>);
40
+ <%- end -%>
41
+ <%- else -%>
42
+ let path = '<%= endpoint[:path] %>';
43
+ <%- end -%>
44
+
45
+ <%- if query_params.any? -%>
46
+ const queryParams = new URLSearchParams();
47
+ <%- query_params.each do |param| -%>
48
+ if (<%= sanitize_name(param[:name]) %> !== undefined && <%= sanitize_name(param[:name]) %> !== null) {
49
+ queryParams.append('<%= param[:name] %>', <%= sanitize_name(param[:name]) %>);
50
+ }
51
+ <%- end -%>
52
+
53
+ const queryString = queryParams.toString();
54
+ if (queryString) {
55
+ path += `?${queryString}`;
56
+ }
57
+ <%- end -%>
58
+
59
+ return this.makeRequest('<%= endpoint[:method].upcase %>', path<%= endpoint[:request_body] ? ', body' : '' %>);
60
+ }
61
+ <% end %>
62
+
63
+ async makeRequest(method, path, body = null) {
64
+ const url = `${this.baseUrl}${path}`;
65
+
66
+ const options = {
67
+ method: method,
68
+ headers: this.headers
69
+ };
70
+
71
+ if (body) {
72
+ options.body = JSON.stringify(body);
73
+ }
74
+
75
+ try {
76
+ const response = await fetch(url, options);
77
+
78
+ if (!response.ok) {
79
+ const errorText = await response.text();
80
+ throw new Error(`HTTP Error ${response.status}: ${errorText}`);
81
+ }
82
+
83
+ const contentType = response.headers.get('content-type');
84
+ if (contentType && contentType.includes('application/json')) {
85
+ return await response.json();
86
+ }
87
+
88
+ return await response.text();
89
+ } catch (error) {
90
+ throw new Error(`Request failed: ${error.message}`);
91
+ }
92
+ }
93
+ }
94
+
95
+ module.exports = APIClient;
96
+
97
+ <%- def format_js_params(endpoint)
98
+ params = endpoint[:parameters] || []
99
+ param_list = params.map { |p| sanitize_name(p[:name]) }
100
+ param_list << 'body = null' if endpoint[:request_body]
101
+ param_list.join(', ')
102
+ end -%>
@@ -0,0 +1,87 @@
1
+ require 'net/http'
2
+ require 'json'
3
+ require 'uri'
4
+
5
+ class APIClient
6
+ attr_reader :base_url, :headers
7
+
8
+ def initialize(base_url = '<%= parser.base_url %>', api_key: nil)
9
+ @base_url = base_url
10
+ @headers = {
11
+ 'Content-Type' => 'application/json',
12
+ 'Accept' => 'application/json'
13
+ }
14
+ @headers['Authorization'] = "Bearer #{api_key}" if api_key
15
+ end
16
+
17
+ <% parser.endpoints.each do |endpoint| %>
18
+ # <%= endpoint[:summary] %>
19
+ # <%= endpoint[:description] %>
20
+ <%- params = endpoint[:parameters] || [] -%>
21
+ <%- query_params = params.select { |p| p[:location] == 'query' } -%>
22
+ <%- path_params = params.select { |p| p[:location] == 'path' } -%>
23
+ def <%= method_name(endpoint) %>(<%= format_method_params(endpoint) %>)
24
+ <%- if path_params.any? -%>
25
+ path = "<%= endpoint[:path] %>"
26
+ <%- path_params.each do |param| -%>
27
+ path = path.gsub('{<%= param[:name] %>}', <%= sanitize_name(param[:name]) %>.to_s)
28
+ <%- end -%>
29
+ <%- else -%>
30
+ path = "<%= endpoint[:path] %>"
31
+ <%- end -%>
32
+
33
+ <%- if query_params.any? -%>
34
+ query_params = {}
35
+ <%- query_params.each do |param| -%>
36
+ query_params['<%= param[:name] %>'] = <%= sanitize_name(param[:name]) %> if <%= sanitize_name(param[:name]) %>
37
+ <%- end -%>
38
+ path += "?#{URI.encode_www_form(query_params)}" unless query_params.empty?
39
+ <%- end -%>
40
+
41
+ make_request('<%= endpoint[:method] %>', path<%= endpoint[:request_body] ? ', body: body' : '' %>)
42
+ end
43
+ <% end %>
44
+
45
+ private
46
+
47
+ def make_request(method, path, body: nil)
48
+ uri = URI.join(@base_url, path)
49
+
50
+ http = Net::HTTP.new(uri.host, uri.port)
51
+ http.use_ssl = uri.scheme == 'https'
52
+
53
+ request = case method.upcase
54
+ when 'GET'
55
+ Net::HTTP::Get.new(uri)
56
+ when 'POST'
57
+ Net::HTTP::Post.new(uri)
58
+ when 'PUT'
59
+ Net::HTTP::Put.new(uri)
60
+ when 'DELETE'
61
+ Net::HTTP::Delete.new(uri)
62
+ when 'PATCH'
63
+ Net::HTTP::Patch.new(uri)
64
+ else
65
+ raise "Unsupported HTTP method: #{method}"
66
+ end
67
+
68
+ @headers.each { |key, value| request[key] = value }
69
+ request.body = body.to_json if body
70
+
71
+ response = http.request(request)
72
+
73
+ case response
74
+ when Net::HTTPSuccess
75
+ response.body.empty? ? {} : JSON.parse(response.body)
76
+ else
77
+ raise "HTTP Error #{response.code}: #{response.body}"
78
+ end
79
+ end
80
+ end
81
+
82
+ <%- def format_method_params(endpoint)
83
+ params = endpoint[:parameters] || []
84
+ param_list = params.map { |p| sanitize_name(p[:name]) }
85
+ param_list << 'body: nil' if endpoint[:request_body]
86
+ param_list.join(', ')
87
+ end -%>
@@ -0,0 +1,29 @@
1
+ # <%= @current_model[:name] %>
2
+ class <%= camelize(@current_model[:name]) %>
3
+ <%- @current_model[:properties].each do |name, prop| -%>
4
+ attr_accessor :<%= sanitize_name(name) %>
5
+ <%- end -%>
6
+
7
+ def initialize(attributes = {})
8
+ <%- @current_model[:properties].each do |name, prop| -%>
9
+ @<%= sanitize_name(name) %> = attributes['<%= name %>'] || attributes[:<%= sanitize_name(name) %>]
10
+ <%- end -%>
11
+ end
12
+
13
+ def to_h
14
+ {
15
+ <%- @current_model[:properties].keys.each_with_index do |name, index| -%>
16
+ '<%= name %>' => @<%= sanitize_name(name) %><%= index < @current_model[:properties].keys.length - 1 ? ',' : '' %>
17
+ <%- end -%>
18
+ }
19
+ end
20
+
21
+ def to_json(*args)
22
+ to_h.to_json(*args)
23
+ end
24
+
25
+ def self.from_json(json)
26
+ data = JSON.parse(json)
27
+ new(data)
28
+ end
29
+ end
@@ -0,0 +1,58 @@
1
+ require 'json'
2
+ require 'yaml'
3
+ require 'erb'
4
+ require 'fileutils'
5
+
6
+ require_relative 'openapi_sdk_generator/parser'
7
+ require_relative 'openapi_sdk_generator/generator'
8
+ require_relative 'openapi_sdk_generator/generators/ruby_generator'
9
+ require_relative 'openapi_sdk_generator/generators/javascript_generator'
10
+
11
+ module OpenapiSdkGenerator
12
+ class Error < StandardError; end
13
+
14
+ class CLI
15
+ def initialize(options)
16
+ @options = options
17
+ end
18
+
19
+ def run
20
+ validate_options!
21
+
22
+ puts " Parsing OpenAPI specification..."
23
+ parser = Parser.new(@options[:input])
24
+
25
+ puts "🔨 Generating #{@options[:language]} SDK..."
26
+ generator = create_generator(@options[:language], parser)
27
+
28
+ puts " Writing files to #{@options[:output]}..."
29
+ generator.write_to_directory(@options[:output])
30
+
31
+ puts " SDK generated successfully!"
32
+ puts "📁 Output directory: #{@options[:output]}"
33
+ rescue => e
34
+ puts " Error: #{e.message}"
35
+ exit 1
36
+ end
37
+
38
+ private
39
+
40
+ def validate_options!
41
+ raise Error, "Input file is required" unless @options[:input]
42
+ raise Error, "Output directory is required" unless @options[:output]
43
+ raise Error, "Language is required (ruby or javascript)" unless @options[:language]
44
+ raise Error, "Input file not found" unless File.exist?(@options[:input])
45
+ end
46
+
47
+ def create_generator(language, parser)
48
+ case language.downcase
49
+ when 'ruby'
50
+ Generators::RubyGenerator.new(parser)
51
+ when 'javascript', 'js'
52
+ Generators::JavascriptGenerator.new(parser)
53
+ else
54
+ raise Error, "Unsupported language: #{language}"
55
+ end
56
+ end
57
+ end
58
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: taimour_openapi_sdk_generator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Taimour
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: json
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.12'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.12'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ description: A lightweight tool to generate client SDKs in multiple languages from
55
+ OpenAPI/Swagger specs
56
+ email: taimour.ffcb@gmail.com
57
+ executables:
58
+ - openapi-sdk-generator
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - bin/openapi-sdk-generator
63
+ - lib/openapi_sdk_generator.rb
64
+ - lib/openapi_sdk_generator/generator.rb
65
+ - lib/openapi_sdk_generator/generators/javascript_generator.rb
66
+ - lib/openapi_sdk_generator/generators/ruby_generator.rb
67
+ - lib/openapi_sdk_generator/parser.rb
68
+ - lib/openapi_sdk_generator/templates/javascript_client.erb
69
+ - lib/openapi_sdk_generator/templates/ruby_client.erb
70
+ - lib/openapi_sdk_generator/templates/ruby_model.erb
71
+ homepage: https://github.com/yourusername/openapi_sdk_generator
72
+ licenses:
73
+ - MIT
74
+ metadata: {}
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 2.7.0
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubygems_version: 3.6.9
90
+ specification_version: 4
91
+ summary: Generate SDKs from OpenAPI specifications
92
+ test_files: []