Skip to content

Commit 2e9c06d

Browse files
committed
Changes:
1. Support designated http method in `open_api`. 2. Support multi-httpverbs. 3. Support both type and type:.
1 parent 1bd6a1c commit 2e9c06d

File tree

8 files changed

+60
-61
lines changed

8 files changed

+60
-61
lines changed

LICENSE.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2017 will.huang
3+
Copyright (c) 2017 zhandao
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

+17-8
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
- [Global DRYing](#trick2---global-drying)
2929
- [Auto generate description](#trick3---auto-generate-description)
3030
- [Skip or Use parameters define in api_dry](#trick4---skip-or-use-parameters-define-in-api_dry)
31-
- [Atuo Generate index/show Actions's Responses Based on DB Schema](#trick5---auto-generate-indexshow-actionss-responses-based-on-db-schema)
31+
- [Atuo Generate index/show Actions's Responses Based on DB Schema](#trick5---auto-generate-indexshow-actionss-response-types-based-on-db-schema)
3232
- [Troubleshooting](#troubleshooting)
3333

3434
## About OAS
@@ -232,7 +232,7 @@
232232

233233
### DSL methods inside [open_api]() and [api_dry]()'s block
234234

235-
[source code](lib/open_api/dsl_inside_block.rb), ApiInfoObj
235+
[source code](lib/open_api/dsl/api_info_obj.rb)
236236

237237
These following methods in the block describe the specified API action: description, valid?,
238238
parameters, request body, responses, securities, servers.
@@ -305,14 +305,23 @@
305305

306306

307307
# method signature
308-
header(param_name, schema_type, schema_hash = { })
309-
header!(param_name, schema_type, schema_hash = { })
310-
query!(param_name, schema_type, schema_hash = { })
308+
header(param_name, schema_type = nil, schema_hash = { })
309+
header!(param_name, schema_type = nil, schema_hash = { })
310+
query!(param_name, schema_type = nil, schema_hash = { })
311311
# usage
312312
header! 'Token', String
313313
query! :readed, Boolean, must_be: true, default: false
314314
# The same effect as above, but not simple
315-
param :query, :readed, Boolean, :req, must_be: true, default: false
315+
param :query, :readed, Boolean, :req, must_be: true, default: false
316+
#
317+
# When schema_type is a Object
318+
# (describe by hash, key means prop's name, value means prop's schema_type)
319+
query :good, { name: String, price: Float, spec: { size: String, weight: Integer } }, desc: 'good info'
320+
# Or you can use `type:` to sign the schema_type, maybe this is clearer for describing object
321+
query :good, type: { name: String, price: Float, spec: { size: String, weight: Integer } }, desc: 'good info'
322+
#
323+
query :good_name, type: String # It's also OK, but some superfluous
324+
query :good_name, String # recommended
316325

317326

318327
# method signature
@@ -457,7 +466,7 @@
457466

458467
#### (7) `server`: TODO
459468

460-
### DSL methods inside [components]()'block ([code source](lib/open_api/dsl_inside_block.rb):: CtrlInfoObj )
469+
### DSL methods inside [components]()'block ([code source](lib/open_api/dsl/ctrl_info_obj.rb):: CtrlInfoObj )
461470

462471
(Here corresponds to OAS [Components Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#componentsObject))
463472

@@ -492,7 +501,7 @@
492501
# or (unrecommended)
493502
schema :Dog, { id!: Integer, name: String }, dft: { id: 1, name: 'pet' }, desc: 'dogee'
494503
```
495-
[1] see: [Type](documentation/parameter.md#type)
504+
[1] see: [Type](documentation/parameter.md#type-schema_type)
496505

497506
## Usage - Generate JSON Documentation File
498507

lib/oas_objs/schema_obj.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ def processed_is_and_format(name)
185185
def recognize_is_options_in(name)
186186
# identify whether `is` patterns matched the name, if so, generate `is`.
187187
Config.is_options.each do |pattern|
188-
self._is = pattern or break if name.match? /#{pattern}/
188+
self._is = pattern or break if name.match?(/#{pattern}/)
189189
end if _is.nil?
190190
end
191191

lib/open_api/dsl.rb

+17-22
Original file line numberDiff line numberDiff line change
@@ -30,40 +30,35 @@ def components &block
3030
current_ctrl._process_objs
3131
end
3232

33-
def open_api action, summary = '', builder: nil, skip: [ ], use: [ ], &block
33+
def open_api action, summary = '', http: nil, builder: nil, skip: [ ], use: [ ], &block
3434
apis_tag if @_ctrl_infos.nil?
35-
3635
# select the routing info (corresponding to the current method) from routing list.
3736
action_path = "#{@_ctrl_path ||= controller_path}##{action}"
38-
routes_info = ctrl_routes_list&.select { |api| api[:action_path].match? /^#{action_path}$/ }&.first
37+
routes_info = ctrl_routes_list&.select { |api| api[:action_path].match?(/^#{action_path}$/) }&.first
3938
pp "[ZRO Warning] Routing mapping failed: #{@_ctrl_path}##{action}" and return if routes_info.nil?
40-
Generator.generate_builder_file(action_path, builder) if builder.present?
39+
Generator.generate_builder_file(action_path, builder)
4140

42-
# structural { #path: { #http_method:{ } } }, for pushing into Paths Object.
43-
path = (@_api_infos ||= { })[routes_info[:path]] ||= { }
44-
current_api = path[routes_info[:http_verb]] =
45-
ApiInfoObj.new(action_path, skip: Array(skip), use: Array(use))
46-
.merge! description: '', summary: summary, operationId: action, tags: [@_apis_tag],
47-
parameters: [ ], requestBody: '', responses: { }, security: [ ], servers: [ ]
41+
api = ApiInfoObj.new(action_path, skip: Array(skip), use: Array(use))
42+
.merge! description: '', summary: summary, operationId: action, tags: [@_apis_tag],
43+
parameters: [ ], requestBody: '', responses: { }, security: [ ], servers: [ ]
44+
[action, :all].each { |blk_key| @_api_dry_blocks&.[](blk_key)&.each { |blk| api.instance_eval(&blk) } }
45+
api.param_use = [ ] # `skip` and `use` only affect `api_dry`'s blocks
46+
api.instance_eval(&block) if block_given?
47+
api._process_objs
48+
api.delete_if { |_, v| v.blank? }
4849

49-
current_api.tap do |api|
50-
[action, :all].each do |key| # blocks_store_key
51-
@_apis_blocks&.[](key)&.each { |blk| api.instance_eval(&blk) }
52-
end
53-
api.param_use = [ ] # skip 和 use 是对 dry 块而言的
54-
api.instance_eval(&block) if block_given?
55-
api._process_objs
56-
api.delete_if { |_, v| v.blank? }
57-
end
50+
path = (@_api_infos ||= { })[routes_info[:path]] ||= { }
51+
http_verbs = (http || routes_info[:http_verb]).split('|')
52+
http_verbs.each { |verb| path[verb] = api }
5853
end
5954

6055
# method could be symbol array, like: %i[ .. ]
6156
def api_dry action = :all, desc = '', &block
62-
@_apis_blocks ||= { }
57+
@_api_dry_blocks ||= { }
6358
if action.is_a? Array
64-
action.each { |m| (@_apis_blocks[m.to_sym] ||= [ ]) << block }
59+
action.each { |m| (@_api_dry_blocks[m.to_sym] ||= [ ]) << block }
6560
else
66-
(@_apis_blocks[action.to_sym] ||= [ ]) << block
61+
(@_api_dry_blocks[action.to_sym] ||= [ ]) << block
6762
end
6863
end
6964

lib/open_api/dsl/api_info_obj.rb

+7-14
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ def param param_type, name, type, required, schema_hash = { }
4242
index.present? ? self[:parameters][index] = param_obj : self[:parameters] << param_obj
4343
end
4444

45-
# Support this writing: (just like `form '', data: { }`)
45+
# For supporting this: (just like `form '', data: { }` usage)
4646
# do_query by: {
4747
# :search_type => { type: String },
4848
# :export! => { type: Boolean }
4949
# }
50-
%i[header header! path path! query query! cookie cookie!].each do |param_type|
50+
%i[ header header! path path! query query! cookie cookie! ].each do |param_type|
5151
define_method "do_#{param_type}" do |by:|
5252
by.each do |key, value|
5353
args = [ key.dup.to_s.delete('!').to_sym, value.delete(:type), value ]
@@ -56,7 +56,8 @@ def param param_type, name, type, required, schema_hash = { }
5656
end unless param_type.to_s['!']
5757
end
5858

59-
def _param_agent name, type, schema_hash = { }
59+
def _param_agent name, type = nil, schema_hash = { }
60+
(schema_hash = type) and (type = type.delete(:type)) if type.is_a?(Hash) && type.key?(:type)
6061
param "#{@param_type}".delete('!'), name, type, (@param_type['!'] ? :req : :opt), schema_hash
6162
end
6263

@@ -122,18 +123,10 @@ def order *param_names
122123
def param_examples exp_by = :all, examples_hash
123124
_process_objs
124125
exp_by = self[:parameters].map { |p| p[:name] } if exp_by == :all
125-
# TODO: ref obj
126-
# exp_in_params = self[:parameters].map { |p| p[:schema][:examples] }.compact
127-
# examples_hash.map! do |key, value|
128-
# if value == []
129-
# if key.in?(exp_in_params.map { |e| e.keys }.flatten.uniq)
130-
# # TODO
131-
# end
132-
# end
133-
# end
134126
self[:examples] = ExampleObj.new(examples_hash, exp_by).process
135127
end
136-
alias_method :examples, :param_examples
128+
129+
alias examples param_examples
137130

138131

139132
def _process_objs
@@ -143,7 +136,7 @@ def _process_objs
143136

144137
# Parameters sorting
145138
self[:parameters].clone.each do |p|
146-
self[:parameters][param_order.index(p[:name])] = p
139+
self[:parameters][param_order.index(p[:name]) || -1] = p
147140
end if param_order.present?
148141

149142
self[:responses]&.each do |code, obj|

lib/open_api/dsl/ctrl_info_obj.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ def param component_key, param_type, name, type, required, schema_hash = { }
2020
ParamObj.new(name, param_type, type, required, schema_hash).process
2121
end
2222

23-
def _param_agent component_key, name, type, schema_hash = { }
23+
def _param_agent component_key, name, type = nil, schema_hash = { }
24+
(schema_hash = type) and (type = type.delete(:type)) if type.is_a?(Hash) && type.key?(:type)
2425
param component_key,
2526
"#{@param_type}".delete('!'), name, type, (@param_type['!'] ? :req : :opt), schema_hash
2627
end

lib/open_api/dsl/helpers.rb

+7-8
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,26 @@ def load_schema(model)
1010
# (1) BuilderSupport module: https://github.com/zhandao/zero-rails/blob/master/app/models/concerns/builder_support.rb
1111
# (2) config in model: https://github.com/zhandao/zero-rails/tree/master/app/models/good.rb
1212
# (3) jbuilder file: https://github.com/zhandao/zero-rails/blob/mster/app/views/api/v1/goods/index.json.jbuilder
13-
# in a word, BuilderSupport let you control the `output fields and nested association infos` very easily.
13+
# In a word, BuilderSupport let you control the `output fields and nested association infos` very easily.
1414
if model.respond_to? :show_attrs
1515
columns = model.column_names.map(&:to_sym)
1616
model.show_attrs.map do |attr|
17-
if columns.include? attr
18-
index = columns.index attr
17+
if columns.include?(attr)
18+
index = columns.index(attr)
1919
type = model.columns[index].sql_type_metadata.type.to_s.camelize
2020
type = 'DateTime' if type == 'Datetime'
2121
{ attr => Object.const_get(type) }
22-
elsif attr.match? /_info/
22+
elsif attr.match?(/_info/)
2323
# TODO: 如何获知关系是 many?因为不能只判断结尾是否 ‘s’
2424
assoc_model = Object.const_get(attr.to_s.split('_').first.singularize.camelize)
2525
{ attr => load_schema(assoc_model) }
2626
end rescue next
2727
end
2828
else
2929
model.columns.map do |column|
30-
name = column.name.to_sym
3130
type = column.sql_type_metadata.type.to_s.camelize
3231
type = 'DateTime' if type == 'Datetime'
33-
{ name => Object.const_get(type) }
32+
{ column.name.to_sym => Object.const_get(type) }
3433
end
3534
end.compact.reduce({ }, :merge) rescue ''
3635
end
@@ -43,8 +42,8 @@ def load_schema(model)
4342
# the key-value (arrow) writing is easy to understand.
4443
def arrow_writing_support
4544
proc do |args, executor|
46-
_args = args.size == 1 && args.first.is_a?(Hash) ? args[0].to_a.flatten : args
47-
send executor, *_args
45+
_args = (args.size == 1 && args.first.is_a?(Hash)) ? args[0].to_a.flatten : args
46+
send(executor, *_args)
4847
end
4948
end
5049

lib/open_api/generator.rb

+8-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def self.included(base)
88

99
module ClassMethods
1010
def generate_docs(api_name = nil)
11-
Dir['./app/controllers/**/*s_controller.rb'].each do |file|
11+
Dir['./app/controllers/**/*_controller.rb'].each do |file|
1212
# Do Not `require`!
1313
# It causes problems, such as making `skip_before_action` not working.
1414
file.sub('./app/controllers/', '').sub('.rb', '').camelize.constantize
@@ -56,7 +56,7 @@ def write_docs(generate_files: true)
5656
max_length = docs.keys.map(&:size).sort.last
5757
puts '[ZRO] * * * * * *'
5858
docs.each do |doc_name, doc|
59-
puts "[ZRO] `%#{max_length}s.json` has been generated." % "#{doc_name}"
59+
puts "[ZRO] `#{doc_name.to_s.rjust(max_length)}.json` has been generated."
6060
File.open("#{output_path}/#{doc_name}.json", 'w') { |file| file.write JSON.pretty_generate doc }
6161
end
6262
# pp OpenApi.docs
@@ -92,15 +92,17 @@ def self.routes_list
9292
end
9393

9494
@routes_list ||= routes.split("\n").drop(1).map do |line|
95-
infos = line.match(/[A-Z].*/).to_s.split(' ') # => [GET, /api/v1/examples/:id, api/v1/examples#index]
95+
next unless line.match?('#')
96+
infos = line.match(/[A-Z|].*/).to_s.split(' ') # => [GET, /api/v1/examples/:id, api/v1/examples#index]
97+
9698
{
97-
http_verb: infos[0].downcase, # => "get"
99+
http_verb: infos[0].downcase, # => "get" / "get|post"
98100
path: infos[1][0..-11].split('/').map do |item|
99101
item[':'] ? "{#{item[1..-1]}}" : item
100102
end.join('/'), # => "/api/v1/examples/{id}"
101103
action_path: infos[2] # => "api/v1/examples#index"
102104
} rescue next
103-
end.compact.group_by {|api| api[:action_path].split('#').first } # => { "api/v1/examples" => [..] }, group by paths
105+
end.compact.group_by { |api| api[:action_path].split('#').first } # => { "api/v1/examples" => [..] }, group by paths
104106
end
105107

106108
def self.get_actions_by_ctrl_path(ctrl_path)
@@ -112,7 +114,7 @@ def self.get_actions_by_ctrl_path(ctrl_path)
112114
def self.find_path_httpverb_by(ctrl_path, action)
113115
routes_list[ctrl_path]&.map do |action_info|
114116
if action_info[:action_path].split('#').last == action.to_s
115-
return [ action_info[:path], action_info[:http_verb] ]
117+
return [ action_info[:path], action_info[:http_verb].split('|').first ]
116118
end
117119
end
118120
nil

0 commit comments

Comments
 (0)