1313WRAP_LINE_LENGTH = 80
1414TOP_LEVEL_RESPONSE_KEYS = %w[ status message data meta ] . freeze
1515
16- def api_methods_by_api_module_name
17- @api_methods_by_api_module_name ||=
18- document . paths . path . each_with_object ( { } ) do |( path , path_item ) , paths_by_api_module |
19- %w[ get post put patch delete ] . each do |http_method |
20- operation = path_item . operation ( http_method )
21- next if !operation
22-
23- api_module_name = operation . tags . first . remove ( ' ' )
24- next if !api_module_name
25-
26- paths_by_api_module [ api_module_name ] ||= [ ]
27- paths_by_api_module [ api_module_name ] << { path :, http_method :, operation : }
28- end
29- end
30- end
31-
32- def update_required_files ( module_names )
33- file_lines = File . readlines ( LIB_FILE )
34-
35- comment_start_index = file_lines . find_index { |line | line . include? ( '# API Modules' ) }
36- if !comment_start_index
37- puts "Could not find '# API Modules' comment in #{ LIB_FILE } "
38- return
39- end
40-
41- insertion_start = comment_start_index + 1
42- insertion_start += 1 while insertion_start < file_lines . size && file_lines [ insertion_start ] . start_with? ( '#' )
43-
44- insertion_end = insertion_start
45- insertion_end += 1 while insertion_end < file_lines . size && !file_lines [ insertion_end ] . strip . empty?
46-
47- file_lines [ insertion_start ...insertion_end ] =
48- module_names . map { |module_name | "require 'paystack_gateway/#{ module_name . underscore } '\n " } . sort . join
49-
50- File . write ( LIB_FILE , file_lines . join )
51-
52- puts 'Updated paystack_gateway.rb with new API Modules requires.'
53- end
54-
55- def api_module_content ( api_module_name , api_methods )
16+ def api_module_content ( api_module_name , api_methods ) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
17+ module_info = tags_by_name [ api_module_name ]
5618 <<~RUBY
5719 # frozen_string_literal: true
5820
5921 module PaystackGateway
60- #{ api_module_docstring ( api_module_name ) }
22+ # https://paystack.com/docs/api/#{ api_module_name . parameterize }
23+ #
24+ # #{ module_info [ 'x-product-name' ] . squish || api_module_name . humanize }
25+ # #{ module_info [ 'description' ] . squish }
6126 module #{ api_module_name }
6227 include PaystackGateway::RequestModule
6328
64- #{ api_methods . map { |info | api_method_composition ( api_module_name , info ) } . join ( "\n " ) . chomp }
29+ #{
30+ api_methods . map do |api_method_info | # rubocop:disable Metrics/BlockLength
31+ api_method_info => { path :, http_method :, operation : }
32+
33+ api_method_name = api_method_name ( api_module_name , operation )
34+ <<~RUBY . chomp
35+ #{INDENT * 2}# Successful response from calling ##{api_method_name}.
36+ #{INDENT * 2}class #{"#{api_method_name}_response".camelize} < PaystackGateway::Response#{
37+ if ( data_keys = api_response_data_keys ( operation ) ) . any?
38+ if data_keys . length > 3
39+ "\n #{ INDENT * 3 } delegate #{ data_keys . map { |key | ":#{ key } ," } . join ( "\n #{ INDENT * 3 } #{ ' ' * 'delegate' . length } " ) } to: :data\n #{ INDENT * 2 } end"
40+ else
41+ "\n #{ INDENT * 3 } delegate #{ data_keys . map { |key | ":#{ key } " } . join ( ', ' ) } , to: :data\n #{ INDENT * 2 } end"
42+ end
43+ else
44+ '; end'
45+ end
46+ }
47+
48+ #{ INDENT * 2 } # Error response from ##{ api_method_name } .
49+ #{ INDENT * 2 } class #{ "#{ api_method_name } _error" . camelize } < ApiError; end
50+
51+ #{ INDENT * 2 } # https://paystack.com/docs/api/#{ operation . tags . first . parameterize } /##{ api_method_name }
52+ #{ INDENT * 2 } # #{ operation . summary } : #{ http_method . upcase } #{ path } #{
53+ operation . description ? "\n #{ INDENT * 2 } # #{ operation . description } " : ''
54+ }
55+ #{ INDENT * 2 } ##{
56+ docstring = ''
57+ api_method_parameters(operation).each do |param|
58+ docstring += "\n #{ INDENT * 2 } # @param #{ param [ :name ] } [#{ param [ :type ] } ]"
59+ docstring += ' (required)' if param[:required]
60+
61+ if (description = param[:description]&.squish)
62+ docstring += wrapped_text(description, "\n #{ INDENT * 2 } ##{INDENT * 3} ")
63+ end
64+
65+ next if !(object_properties = param[:object_properties])
66+
67+ object_properties.each do |props|
68+ docstring += "\n #{ INDENT * 2 } ##{INDENT * 1} @option #{param[:name]} [#{props[:type]}] :#{props[:name]}"
69+ docstring += wrapped_text(props[:description], "\n #{ INDENT * 2 } ##{INDENT * 5}").to_s
70+ end
71+ end
72+
73+ docstring
74+ }
75+ #{ INDENT * 2 } #
76+ #{ INDENT * 2 } # @return [#{ "#{ api_method_name } _response" . camelize } ] successful response
77+ #{ INDENT * 2 } # @raise [#{ "#{ api_method_name } _error" . camelize } ] if the request fails
78+ #{ INDENT * 2 } api_method def self.#{ api_method_name } #{
79+ if ( method_args = api_method_args ( operation ) ) . any?
80+ if method_args . length > 5
81+ "(#{ method_args . map { |arg | "\n #{ INDENT * 3 } #{ arg } " } . join ( ',' ) } \n #{ INDENT * 2 } )"
82+ else
83+ "(#{ method_args . join ( ', ' ) } )"
84+ end
85+ end
86+ }
87+ #{ INDENT * 2 } use_connection do |connection|
88+ #{ INDENT * 2 } connection.#{ http_method } (
89+ #{ INDENT * 2 } #{
90+ if path == ( interpolated = path . gsub ( /{([^}]+)}/ , "\# {\\ 1}" ) )
91+ "'#{ path } '"
92+ else
93+ "\" #{ interpolated } \" "
94+ end
95+ } ,#{
96+ if ( request_args = api_request_args ( operation ) ) . any?
97+ if request_args . length > 5
98+ "\n #{ INDENT * 5 } {#{ request_args . map { |arg | "\n #{ INDENT * 6 } #{ arg } ," } . join } \n #{ INDENT * 5 } }.compact,"
99+ else
100+ "\n #{ INDENT * 5 } { #{ request_args . join ( ', ' ) } }.compact,"
101+ end
102+ end
103+ }
104+ #{ INDENT * 2 } )
105+ #{ INDENT * 2 } end
106+ #{ INDENT * 2 } end
107+
108+ RUBY
109+ end.join("\n ").chomp
110+ }
65111 end
66112 end
67113 RUBY
68114end
69115
70- def api_module_docstring ( api_module_name )
71- module_info = tags_by_name [ api_module_name ]
72- return if !module_info
73-
74- docstring = "# https://paystack.com/docs/api/#{ api_module_name . parameterize } "
75- docstring += "\n #{ INDENT * 1 } #"
76- docstring += "\n #{ INDENT * 1 } # #{ module_info [ 'x-product-name' ] . squish || api_module_name . humanize } "
77-
78- docstring += "\n #{ INDENT * 1 } # #{ module_info [ 'description' ] . squish } " if module_info [ 'description' ]
79- docstring
80- end
81-
82- def api_method_composition ( api_module_name , api_method_info )
83- api_method_info => { path :, http_method :, operation : }
84-
85- api_method_name = api_method_name ( api_module_name , operation )
86- <<~RUBY . chomp
87- #{ api_method_response_class_content ( api_method_name , operation ) }
88-
89- #{ api_method_error_class_content ( api_method_name ) }
90-
91- #{ api_method_content ( api_method_name , operation , http_method , path ) }
92- RUBY
93- end
94-
95- def api_method_response_class_content ( api_method_name , operation )
96- definition = "#{ INDENT * 2 } # Successful response from calling ##{ api_method_name } .\n " \
97- "#{ INDENT * 2 } class #{ "#{ api_method_name } _response" . camelize } < PaystackGateway::Response"
98-
99- if ( delegate_content = api_method_response_class_delegate_content ( operation ) )
100- "#{ definition } #{ delegate_content } \n #{ INDENT * 2 } end"
101- else
102- "#{ definition } ; end"
103- end
104- end
105-
106- def api_method_response_class_delegate_content ( operation )
116+ def api_response_data_keys ( operation )
107117 responses = operation . responses . response
108118 success_response = responses [ responses . keys . find { _1 . match? ( /\A 2..\z / ) } ]
109- return if !success_response
119+ return [ ] if !success_response
110120
111121 required_data_keys = success_response . content [ 'application/json' ] . schema . properties [ 'data' ] &.required || [ ]
112-
113- required_data_keys -= TOP_LEVEL_RESPONSE_KEYS
114- return if required_data_keys . none?
115-
116- if required_data_keys . length > 3
117- definition = "\n #{ INDENT * 3 } delegate :#{ required_data_keys . shift } ,"
118-
119- while ( line_key = required_data_keys . shift ) . present?
120- definition += "\n #{ INDENT * 3 } #{ ' ' * 'delegate' . length } :#{ line_key } ,"
121- end
122-
123- "#{ definition } to: :data"
124- else
125- "\n #{ INDENT * 3 } delegate #{ required_data_keys . map { |key | ":#{ key } " } . join ( ', ' ) } , to: :data"
126- end
127- end
128-
129- def api_method_error_class_content ( api_method_name )
130- "#{ INDENT * 2 } # Error response from ##{ api_method_name } .\n " \
131- "#{ INDENT * 2 } class #{ "#{ api_method_name } _error" . camelize } < ApiError; end"
122+ required_data_keys - TOP_LEVEL_RESPONSE_KEYS
132123end
133124
134- def api_method_content ( api_method_name , operation , http_method , path )
135- <<-RUBY
136- #{ api_method_definition_header_docstring ( api_method_name , operation , http_method , path ) }
137- #{ api_method_definition_params_docstring ( operation ) }
138- #{ api_method_definition_response_docstring ( api_method_name ) }
139- #{ api_method_definition_name_and_parameters ( api_method_name , operation ) }
140- use_connection do |connection|
141- connection.#{ http_method } (
142- #{ api_method_definition_path ( path ) } ,#{ api_method_definition_request_params ( operation ) }
143- )
144- end
145- end
146- RUBY
147- end
148-
149- def api_method_definition_header_docstring ( api_method_name , operation , http_method , path )
150- docstring = "# https://paystack.com/docs/api/#{ operation . tags . first . parameterize } /##{ api_method_name } "
151- docstring += "\n #{ INDENT * 2 } # #{ operation . summary } : #{ http_method . upcase } #{ path } "
152- docstring += "\n #{ INDENT * 2 } # #{ operation . description } " if operation . description . present?
153- docstring
154- end
155-
156- def api_method_definition_params_docstring ( operation )
157- docstring = '#'
158-
159- api_method_parameters ( operation ) . each do |param |
160- docstring += "\n #{ INDENT * 2 } # @param #{ param [ :name ] } [#{ param [ :type ] } ]"
161- docstring += ' (required)' if param [ :required ]
162-
163- if ( description = param [ :description ] &.squish )
164- docstring += wrapped_text ( description , "\n #{ INDENT * 2 } ##{ INDENT * 3 } " )
165- end
166-
167- next if !( object_properties = param [ :object_properties ] )
168-
169- object_properties . each do |props |
170- docstring += "\n #{ INDENT * 2 } ##{ INDENT * 1 } @option #{ param [ :name ] } [#{ props [ :type ] } ] :#{ props [ :name ] } "
171- docstring += wrapped_text ( props [ :description ] , "\n #{ INDENT * 2 } ##{ INDENT * 5 } " )
172- end
173- end
174-
175- docstring
176- end
177-
178- def api_method_definition_response_docstring ( api_method_name )
179- docstring = '#'
180- docstring += "\n #{ INDENT * 2 } # @return [#{ "#{ api_method_name } _response" . camelize } ] successful response"
181- docstring + "\n #{ INDENT * 2 } # @raise [#{ "#{ api_method_name } _error" . camelize } ] if the request fails"
182- end
183-
184- def api_method_definition_name_and_parameters ( api_method_name , operation )
185- method_args = api_method_parameters ( operation ) . map do |param |
125+ def api_method_args ( operation )
126+ api_method_parameters ( operation ) . map do |param |
186127 name = param [ :name ] . underscore
187128 param [ :required ] ? "#{ name } :" : "#{ name } : nil"
188129 end
189-
190- definition = "api_method def self.#{ api_method_name } "
191- return definition if method_args . none?
192-
193- definition += '('
194-
195- if method_args . length > 5
196- while ( line_arg = method_args . shift ) . present?
197- definition += "\n #{ INDENT * 3 } #{ line_arg } "
198- definition += ',' if method_args . any?
199- end
200- definition + "\n #{ INDENT * 2 } )"
201- else
202- definition + "#{ method_args . join ( ', ' ) } )"
203- end
204- end
205-
206- def api_method_definition_path ( path )
207- # "/transaction/verify/{reference}" -> "/transaction/verify/#{reference}"
208- interpolated_path = path . gsub ( /{([^}]+)}/ , "\# {\\ 1}" )
209-
210- interpolated_path == path ? "'#{ interpolated_path } '" : "\" #{ interpolated_path } \" "
211130end
212131
213- def api_method_definition_request_params ( operation )
214- params = api_method_parameters ( operation )
215- . reject { |param | param [ :in ] == 'path' }
216- . map { |param | param [ :name ] }
217- return if params . none?
218-
219- definition = "\n #{ INDENT * 5 } {"
132+ def api_request_args ( operation )
133+ api_method_parameters ( operation )
134+ . filter_map do |param |
135+ next if param [ :in ] == 'path'
220136
221- if params . length > 5
222- while ( line_param = params . shift ) . present?
223- definition += "\n #{ INDENT * 6 } #{ line_param } :" \
224- "#{ line_param == line_param . underscore ? nil : " #{ line_param . underscore } " } ,"
137+ if param [ :name ] == param [ :name ] . underscore
138+ "#{ param [ :name ] } :"
139+ else
140+ "#{ param [ :name ] } : #{ param [ :name ] . underscore } "
141+ end
225142 end
226-
227- definition + "\n #{ INDENT * 5 } }.compact,"
228- else
229- "#{ definition } #{ params . map { |param | "#{ param } :" } . join ( ', ' ) } }.compact,"
230- end
231143end
232144
233145def api_method_parameters ( operation )
@@ -365,6 +277,22 @@ def document
365277 @document ||= OpenAPIParser . parse ( YAML . load_file ( OPENAPI_SPEC ) , strict_reference_validation : true )
366278end
367279
280+ def api_methods_by_api_module_name
281+ @api_methods_by_api_module_name ||=
282+ document . paths . path . each_with_object ( { } ) do |( path , path_item ) , paths_by_api_module |
283+ %w[ get post put patch delete ] . each do |http_method |
284+ operation = path_item . operation ( http_method )
285+ next if !operation
286+
287+ api_module_name = operation . tags . first . remove ( ' ' )
288+ next if !api_module_name
289+
290+ paths_by_api_module [ api_module_name ] ||= [ ]
291+ paths_by_api_module [ api_module_name ] << { path :, http_method :, operation : }
292+ end
293+ end
294+ end
295+
368296module_names = api_methods_by_api_module_name . map do |api_module_name , api_methods |
369297 if existing_api_modules . include? ( api_module_name )
370298 puts "Updating existing module: #{ api_module_name } "
@@ -381,4 +309,23 @@ def document
381309 api_module_name
382310end
383311
384- update_required_files ( module_names )
312+ lib_file_lines = File . readlines ( LIB_FILE )
313+
314+ comment_start_index = lib_file_lines . find_index { |line | line . include? ( '# API Modules' ) }
315+ if !comment_start_index
316+ puts "Could not find '# API Modules' comment in #{ LIB_FILE } "
317+ exit 1
318+ end
319+
320+ insertion_start = comment_start_index + 1
321+ insertion_start += 1 while insertion_start < lib_file_lines . size && lib_file_lines [ insertion_start ] . start_with? ( '#' )
322+
323+ insertion_end = insertion_start
324+ insertion_end += 1 while insertion_end < lib_file_lines . size && !lib_file_lines [ insertion_end ] . strip . empty?
325+
326+ lib_file_lines [ insertion_start ...insertion_end ] =
327+ module_names . map { |module_name | "require 'paystack_gateway/#{ module_name . underscore } '\n " } . sort . join
328+
329+ File . write ( LIB_FILE , lib_file_lines . join )
330+
331+ puts 'Updated paystack_gateway.rb with new API Modules requires.'
0 commit comments