openapi_parser 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7480b22f5a7f1e12c9c1d2b21dbcc50b711bb06fba8f31e7cdff9f340f7f6fe8
4
- data.tar.gz: d9fecd50469f7e3f8bf88cf5076c8b78df24a5d825920748f518bcbd38ca0677
3
+ metadata.gz: e6fdbfb2bf6d7101bc016fc611e144cba0c53ee1456f433f62200f6b216d15ef
4
+ data.tar.gz: 27f093ed770beca6b908f9079306afc4f74d81de136c9da8e295ab3ccf833cc6
5
5
  SHA512:
6
- metadata.gz: 18af5156bd11cd2388608f2c835cfc44a049f55e5729c25997d44a7e434f00f9278ea1b39ef5490044aa950252148585e101f021659d34fa023046af03f96401
7
- data.tar.gz: e706defe789e26b503018653b8be22671a0155a2bbc39ac62c29603e7bd9a58f1cb923c9f7b51a858dde34ae06397860c1c68345828fff5be386866a2f6ca236
6
+ metadata.gz: c179283bac82745556d85ee1c02dcf0a2135caa60ff16c6f6b1fa44ff83de531631cc4b39da00c12b0a9e3e11eb2e606d9dc2b53f7b53cee47aa4e4e7a12e709
7
+ data.tar.gz: 1638236c9fd1b22b7922ce8e2f81c942748101ef71e919fa7333b8a7e854a43c5e9ba738e47a61ffd823f682997f33c15634b18f515a88d5ddb7670bda205965
@@ -1,5 +1,9 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 0.7.0 (2020-01-15)
4
+ * Avoid potential `.send(:exit)` #58
5
+ * Improve PathItemFinder #44
6
+
3
7
  ## 0.6.1 (2019-10-12)
4
8
  * Bugfix: validate non-nullable response header #54
5
9
  * Improve grammar in error messages #55
@@ -1,10 +1,7 @@
1
1
  class OpenAPIParser::PathItemFinder
2
2
  # @param [OpenAPIParser::Schemas::Paths] paths
3
3
  def initialize(paths)
4
- @root = PathNode.new('/')
5
4
  @paths = paths
6
-
7
- @paths.path.each { |path, _path_item_object| @root.register_path_node(path.split('/'), path) }
8
5
  end
9
6
 
10
7
  # find operation object and if not exist return nil
@@ -41,71 +38,83 @@ class OpenAPIParser::PathItemFinder
41
38
  end
42
39
  end
43
40
 
44
- # path tree node
45
- class PathNode
46
- attr_accessor :full_path, :name
47
-
48
- def initialize(name)
49
- @name = name
50
- @children = Hash.new { |h, k| h[k] = PathNode.new(k) }
51
- @path_template_node = nil # we can't initialize because recursive initialize...
52
- @full_path = nil
41
+ private
42
+ # check if there is a identical path in the schema (without any param)
43
+ def matches_directly?(request_path, http_method)
44
+ @paths.path[request_path]&.operation(http_method)
53
45
  end
54
46
 
55
- # @param [Array<string>] splited_path
56
- # @param [String] full_path
57
- def register_path_node(splited_path, full_path)
58
- if splited_path.empty?
59
- @full_path = full_path
60
- return
61
- end
62
-
63
- path_name = splited_path.shift
64
-
65
- child = path_template?(path_name) ? path_template_node(path_name) : children[path_name]
66
- child.register_path_node(splited_path, full_path)
47
+ # used to filter paths with different depth or without given http method
48
+ def different_depth_or_method?(splitted_schema_path, splitted_request_path, path_item, http_method)
49
+ splitted_schema_path.size != splitted_request_path.size || !path_item.operation(http_method)
67
50
  end
68
51
 
69
- # @return [String, nil] and Hash
70
- def find_full_path(splited_path)
71
- return [@full_path, {}] if splited_path.empty?
72
-
73
- path_name = splited_path.shift
52
+ # check if the path item is a template
53
+ # EXAMPLE: path_template?('{id}') => true
54
+ def path_template?(schema_path_item)
55
+ schema_path_item.start_with?('{') && schema_path_item.end_with?('}')
56
+ end
74
57
 
75
- # when ambiguous matching like this
76
- # /{entity}/me
77
- # /books/{id}
78
- # OpenAPI3 depend on the tooling so we use concrete one (using /books/)
58
+ # get the parameter name from the schema path item
59
+ # EXAMPLE: param_name('{id}') => 'id'
60
+ def param_name(schema_path_item)
61
+ schema_path_item[1..(schema_path_item.length - 2)]
62
+ end
79
63
 
80
- path_params = {}
81
- if children.has_key?(path_name)
82
- child = children[path_name]
83
- else
84
- child = path_template_node(path_name)
85
- path_params = { child.name.to_s => path_name }
64
+ # extract params by comparing the request path and the path from schema
65
+ # EXAMPLE:
66
+ # extract_params(['org', '1', 'user', '2', 'edit'], ['org', '{org_id}', 'user', '{user_id}'])
67
+ # => { 'org_id' => 1, 'user_id' => 2 }
68
+ # return nil if the schema does not match
69
+ def extract_params(splitted_request_path, splitted_schema_path)
70
+ splitted_request_path.zip(splitted_schema_path).reduce({}) do |result, zip_item|
71
+ request_path_item, schema_path_item = zip_item
72
+
73
+ if path_template?(schema_path_item)
74
+ result[param_name(schema_path_item)] = request_path_item
75
+ else
76
+ return if schema_path_item != request_path_item
77
+ end
78
+
79
+ result
86
80
  end
87
-
88
- ret, other_path_params = child.find_full_path(splited_path)
89
- [ret, path_params.merge(other_path_params)]
90
81
  end
91
82
 
92
- private
93
-
94
- attr_reader :children
95
-
96
- def path_template_node(path_name)
97
- @path_template_node ||= PathNode.new(path_name[1..(path_name.length - 2)]) # delete {} from {name}
83
+ # find all matching patchs with parameters extracted
84
+ # EXAMPLE:
85
+ # [
86
+ # ['/user/{id}/edit', { 'id' => 1 }],
87
+ # ['/user/{id}/{action}', { 'id' => 1, 'action' => 'edit' }],
88
+ # ]
89
+ def matching_paths_with_params(request_path, http_method)
90
+ splitted_request_path = request_path.split('/')
91
+
92
+ @paths.path.reduce([]) do |result, item|
93
+ path, path_item = item
94
+ splitted_schema_path = path.split('/')
95
+
96
+ next result if different_depth_or_method?(splitted_schema_path, splitted_request_path, path_item, http_method)
97
+
98
+ extracted_params = extract_params(splitted_request_path, splitted_schema_path)
99
+ result << [path, extracted_params] if extracted_params
100
+ result
98
101
  end
102
+ end
99
103
 
100
- def path_template?(path_name)
101
- path_name.start_with?('{') && path_name.end_with?('}')
102
- end
103
- end
104
+ # find mathing path and extract params
105
+ # EXAMPLE: find_path_and_params('get', '/user/1') => ['/user/{id}', { 'id' => 1 }]
106
+ def find_path_and_params(http_method, request_path)
107
+ return [request_path, {}] if matches_directly?(request_path, http_method)
108
+
109
+ matching = matching_paths_with_params(request_path, http_method)
104
110
 
105
- private
111
+ # if there are many matching paths, return the one with the smallest number of params
112
+ # (prefer /user/{id}/action over /user/{param_1}/{param_2} )
113
+ matching.min_by { |match| match[0].size }
114
+ end
106
115
 
107
116
  def parse_request_path(http_method, request_path)
108
- original_path, path_params = @root.find_full_path(request_path.split('/'))
117
+ original_path, path_params = find_path_and_params(http_method, request_path)
109
118
  return nil unless original_path # # can't find
110
119
 
111
120
  path_item_object = @paths.path[original_path]
@@ -12,7 +12,9 @@ module OpenAPIParser::Schemas
12
12
 
13
13
  # @return [Operation]
14
14
  def operation(method)
15
- send(method)
15
+ public_send(method)
16
+ rescue NoMethodError
17
+ nil
16
18
  end
17
19
  end
18
20
  end
@@ -1,3 +1,3 @@
1
1
  module OpenAPIParser
2
- VERSION = '0.6.1'.freeze
2
+ VERSION = '0.7.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ota42y
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-10-12 00:00:00.000000000 Z
11
+ date: 2020-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -222,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
222
222
  - !ruby/object:Gem::Version
223
223
  version: '0'
224
224
  requirements: []
225
- rubygems_version: 3.0.3
225
+ rubygems_version: 3.0.6
226
226
  signing_key:
227
227
  specification_version: 4
228
228
  summary: OpenAPI3 parser