swagger-to-rbs 0.2.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 651f699800da6263854e5f030b5b2e9084570686b17b92b164319c8c18dbe28f
4
- data.tar.gz: 23e3253fd8e999fa5042045c9548227e5addd97ed26fe230c0303850665a6d85
3
+ metadata.gz: 4009216d856b770d4226cdc662c17250a113cf2a06366f7a5731d39a72d417ed
4
+ data.tar.gz: 456756e34e5ce92d602ac0c23aa0d6e243a78b95e4f20a39210976a477a41ad7
5
5
  SHA512:
6
- metadata.gz: 6ceb2196aaf7cb48385a7c3d200f45a1c28880385ce00da876a6dcb9d19c08ca1a991fbc461e5d026e4276f78266e8f5d3ccc444f26d6fb61c309b9e5edae73b
7
- data.tar.gz: 27fc3959ff9f6a56846322e03b14e83fd60a96659e006abfe23c6ccb069bd404e4a4a89b0472edc92c4b87415bedf6b1bbad47ef96721a47473adfc1fd3b59c9
6
+ metadata.gz: ba0e9c1101cfa113600e891feef046f2aff46b4dfa56d8c9f16dc38f08327ecd5bc16f49633bbb5420a7835401ceb0c3cd036137eccd7e7636da3537fb9fd866
7
+ data.tar.gz: 1b374d837cf5050238dac0096866624da70c192e7d03dca63f425cca3f44619f2020cedd441584dd8cc56582829753358c00fc723ee6d8e9f826762611415843
data/README.md CHANGED
@@ -1,4 +1,10 @@
1
- ### Usage
1
+ ## Install
2
+
3
+ ```
4
+ gem install swagger-to-rbs
5
+ ```
6
+
7
+ ## Usage
2
8
 
3
9
  Dowload swagger spec
4
10
 
@@ -11,7 +17,7 @@ curl https://petstore3.swagger.io/api/v3/openapi.json > swagger.json
11
17
  Generate http client based on [Httparty gem](https://github.com/jnunemaker/httparty)
12
18
 
13
19
  ```
14
- swagger-to-rbs generate --name MyApi --spec swagger.json
20
+ swagger-to-rbs generate --name PetApi --spec swagger.json
15
21
  ```
16
22
 
17
23
  Result:
@@ -24,16 +30,20 @@ class PetApi
24
30
  base_uri "/api/v3"
25
31
  #...
26
32
 
27
- def getPetById(petId, params, options = {})
28
- self.class.get("/pet/#{petId}", params: params.to_json, headers: { 'Content-Type' => 'application/json' }.merge(options))
33
+ def initialize
34
+ self.class.headers({ 'Content-Type' => 'application/json' })
35
+ end
36
+
37
+ def getPetById(petId, options = {})
38
+ self.class.get("/pet/#{petId}", options)
29
39
  end
30
40
 
31
41
  def updatePet(body, options = {})
32
- self.class.put("/pet", body: body.to_json, headers: { 'Content-Type' => 'application/json' }.merge(options))
42
+ self.class.put("/pet", { body: body.to_json }.merge(options))
33
43
  end
34
44
 
35
45
  def addPet(body, options = {})
36
- self.class.post("/pet", body: body.to_json, headers: { 'Content-Type' => 'application/json' }.merge(options))
46
+ self.class.post("/pet", { body: body.to_json }.merge(options))
37
47
  end
38
48
  #...
39
49
  end
@@ -44,7 +54,7 @@ end
44
54
  Authorization example:
45
55
 
46
56
  ```
47
- api = MyApi.new
57
+ api = PetApi.new
48
58
 
49
59
  MyApi.headers(Authorization: "Bearer #{access_token}")
50
60
  MyApi.base_uri("http://otherurl.test/api/v1)
@@ -19,7 +19,21 @@ module Swagger2Rbs
19
19
 
20
20
  file_name = to_underscore(name)
21
21
  File.write("#{file_name}.rb", ::Swagger2Rbs.generate(name, data.dup))
22
- File.write("#{file_name}.rbs", ::Swagger2Rbs.generate_rbs(name, data.dup))
22
+ Dir.mkdir('sig') unless File.exist?("sig")
23
+ File.write("sig/#{file_name}.rbs", ::Swagger2Rbs.generate_rbs(name, data.dup))
24
+ end
25
+
26
+ desc 'genearte-yaml', 'generate yaml file'
27
+ option :name, desc: 'Name'
28
+ option :spec, desc: 'Swagger file path'
29
+ def generate_yaml
30
+ @name = options[:name]
31
+ @swagger_path = options[:spec]
32
+ @debug = options[:debug]
33
+ swagger_spec = JSON.parse(File.read(@swagger_path))
34
+
35
+ file_name = to_underscore(name)
36
+ File.write("#{file_name}.yaml", ::Swagger2Rbs.swagger_to_rest_api_yaml(swagger_spec))
23
37
  end
24
38
 
25
39
  private
@@ -1,5 +1,40 @@
1
1
  module Swagger2Rbs
2
2
  class HashHelper
3
+
4
+ def self.resolve_special_key(data, special_key)
5
+ return data unless data
6
+
7
+ new_data = data.dup
8
+ walk(data) do |key, value|
9
+ if (key.split(".").last == special_key)
10
+ update_key = key.split(".").reject{|k| k == special_key}.join(".")
11
+ new_value = yield(key, value)
12
+ set_value(new_data, update_key, new_value)
13
+ end
14
+ end
15
+ new_data
16
+ end
17
+
18
+ def self.present?(hash)
19
+ hash && !hash.empty?
20
+ end
21
+
22
+ # https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/keys.rb#L129
23
+ def self.deep_transform_keys_in_object!(object, &block)
24
+ case object
25
+ when Hash
26
+ object.keys.each do |key|
27
+ value = object.delete(key)
28
+ object[yield(key)] = deep_transform_keys_in_object!(value, &block)
29
+ end
30
+ object
31
+ when Array
32
+ object.map! { |e| deep_transform_keys_in_object!(e, &block) }
33
+ else
34
+ object
35
+ end
36
+ end
37
+
3
38
  def self.set_value(hash, key, value)
4
39
  arr = key.split(".")
5
40
  last_key = arr.pop()
@@ -0,0 +1,46 @@
1
+ module Swagger2Rbs
2
+ class RbsType
3
+ attr_reader :data, :symbolize_keys
4
+ def initialize(data, symbolize_keys: true)
5
+ @data = data
6
+ @symbolize_keys = symbolize_keys
7
+ end
8
+
9
+ def write_types
10
+ return nil unless HashHelper.present? data
11
+ typed = (data.is_a?(Array) ? data[0] : data)&.map{ |k, v| to_typed(k, v) }.join(', ')
12
+ if data.is_a?(Array)
13
+ "Array[{ #{typed} }]"
14
+ else
15
+ "{ #{typed} }"
16
+ end
17
+ end
18
+
19
+ def type_case(str)
20
+ case str
21
+ when "boolean"
22
+ "bool"
23
+ when nil
24
+ "untyped"
25
+ else
26
+ str&.capitalize
27
+ end
28
+ end
29
+
30
+ def key_to_typed(k)
31
+ return "#{k}:" if @symbolize_keys
32
+ "\"#{k}\" =>"
33
+ end
34
+
35
+ def to_typed(k, v)
36
+ return "#{key_to_typed(k)} #{type_case(v)}" unless v.is_a?(Array) || v.is_a?(Hash)
37
+ return "#{key_to_typed(k)} {#{v.map{ |k2, v2| to_typed(k2, v2) }.join(", ")}}" if v.is_a?(Hash)
38
+
39
+ if v[0]&.is_a?(Hash)
40
+ "#{key_to_typed(k)} Array[{" + v[0].map{ |k, v| to_typed(k, v) }.join(", ") + "}]"
41
+ else
42
+ "#{key_to_typed(k)} Array[#{type_case(v[0])}]"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,8 +1,10 @@
1
1
  require 'slugify'
2
+ require_relative 'rest_endpoint_typed'
2
3
 
3
4
  module Swagger2Rbs
4
5
  class RestEndpoint
5
6
  attr_reader :path, :method, :props
7
+ include RestEndpointTyped
6
8
 
7
9
  def initialize(path, method, props)
8
10
  @path = path
@@ -13,14 +15,31 @@ module Swagger2Rbs
13
15
  def to_h
14
16
  {
15
17
  path: path_with_parameters,
16
- method: method,
17
- parameters: parameters,
18
+ http_method: method,
18
19
  parameters_for_method: parameters_for_method,
19
- parameters_typed: parameters_typed,
20
+ typed_parameters_for_method: typed_parameters_for_method,
21
+ has_body: body?,
22
+ method_name: method_name,
23
+ all_responses_typed: all_responses_typed,
24
+ all_responses_for_return_method: all_responses_for_return_method,
25
+ }
26
+ rescue => e
27
+ raise e, "Context: #{path} #{method} Message: #{e.message}"
28
+ end
29
+
30
+ def body?
31
+ HashHelper.present? body
32
+ end
33
+
34
+ def to_yaml
35
+ {
36
+ path: path,
37
+ method: method,
38
+ path_parameters: parameters,
20
39
  method_name: method_name,
21
40
  body: body,
22
- body_typed: body_typed,
23
- response_typed: response_typed,
41
+ response: response("200"),
42
+ all_responses: all_responses
24
43
  }
25
44
  rescue => e
26
45
  raise e, "Context: #{path} #{method} Message: #{e.message}"
@@ -37,63 +56,55 @@ module Swagger2Rbs
37
56
  def parameters
38
57
  return [] unless props["parameters"]
39
58
 
40
- props["parameters"].select{|it| it["in"] == "path"}.map{|it| it["name"]}
59
+ props["parameters"]&.select{|it| it["in"] == "path"}&.map{|it| it["name"]}
41
60
  end
42
61
 
43
- def response_typed
44
- schema = resolve_all_of(@props.dig("responses", "200", "content", "application/json", "schema"))
45
- schema_to_typed(schema, {})
46
- end
62
+ def body
63
+ body_schema = resolve_of(props.dig("requestBody", "content", "application/json", "schema"))
64
+ return {} unless body_schema
47
65
 
48
- def parameters_typed
49
- return nil if method != "get"
50
- parameters.map{|it| "String #{it}" }
51
- .push("?Hash[untyped, untyped] options")
52
- .join(", ")
66
+ schema_to_hash(body_schema)
53
67
  end
54
68
 
55
- def body_typed
56
- return nil if method != "post"
57
-
58
- return "Hash[String, untyped]" unless body
59
- return "Hash[String, untyped]" if body.empty?
60
-
61
- "{" + body.map{ |k, v| to_typed(k, v) }.join(", ") + "}" + " body, ?Hash[untyped, untyped] options"
69
+ def response(http_code)
70
+ schema = resolve_all_of(@props.dig("responses", http_code, "content", "application/json", "schema"))
71
+ schema_to_hash(schema, {})
62
72
  end
63
73
 
64
- def to_typed(k, v)
65
- return "#{k}: #{v&.capitalize}" unless v.is_a?(Array) || v.is_a?(Hash)
66
- return "#{k}: {#{v.map{ |k2, v2| to_typed(k2, v2) }.join(", ")}}" if v.is_a?(Hash)
67
-
68
- if v[0]&.is_a?(Hash)
69
- "#{k}: Array[{" + v[0].map{ |k, v| to_typed(k, v) }.join(", ") + "}]"
70
- else
71
- "#{k}: Array[#{v[0]&.capitalize}]"
74
+ def all_responses
75
+ result = @props.dig("responses").keys.reduce({}) do |memo, key|
76
+ memo.merge({ key => response(key) })
72
77
  end
73
78
  end
74
79
 
75
- def body
76
- body_schema = resolve_of(props.dig("requestBody", "content", "application/json", "schema"))
77
- return {} unless body_schema
80
+ def all_responses_for_return_method
81
+ return '{ "200" => response }' if all_responses.empty?
78
82
 
79
- schema_to_typed(body_schema)
83
+ result = all_responses.keys.map{|key| "\"#{key}\" => response" }.join(", ")
84
+ "{ #{result} }"
80
85
  end
81
86
 
82
87
  def parameters_for_method
83
- return parameters.push("options = {}").join(", ") if (method == "get")
88
+ return parameters.push("options = {}").join(", ") if method == "get"
84
89
 
85
- parameters.push("body").push("options = {}").join(", ")
90
+ if body&.empty?
91
+ parameters.push("options = {}").join(", ")
92
+ else
93
+ parameters.push("body").push("options = {}").join(", ")
94
+ end
86
95
  end
87
96
 
88
- def schema_to_typed(schema, memo = {})
97
+ def schema_to_hash(schema, memo = {})
89
98
  return nil unless schema
90
99
 
91
- schema["properties"]&.reduce(memo)do |memo, (k,v)|
100
+ properties = schema["type"] == "array" ? schema["items"]["properties"] : schema["properties"]
101
+
102
+ result = properties&.reduce(memo)do |memo, (k,v)|
92
103
  if v["type"] == "object"
93
- memo.merge({k => schema_to_typed(v, {})})
104
+ memo.merge({k => schema_to_hash(v, {})})
94
105
  elsif v["type"] == "array"
95
106
  if v.dig("items", "type") == "object"
96
- memo.merge({k => [schema_to_typed(v["items"], {})] })
107
+ memo.merge({k => [schema_to_hash(v["items"], {})] })
97
108
  else
98
109
  memo.merge({k => [v.dig("items", "type")] })
99
110
  end
@@ -101,6 +112,9 @@ module Swagger2Rbs
101
112
  memo.merge({k => v["type"] })
102
113
  end
103
114
  end
115
+ return [result] if schema["type"] == "array"
116
+
117
+ result
104
118
  end
105
119
 
106
120
  def resolve_of(data)
@@ -115,10 +129,12 @@ module Swagger2Rbs
115
129
  end
116
130
 
117
131
  def resolve_all_of(data)
118
- return data unless data
119
- return data unless data["allOf"]
120
-
121
- data["allOf"].reduce(&:merge)
132
+ HashHelper.resolve_special_key(data, "allOf") do |key, value|
133
+ value.reduce(value[0]) do |memo, it|
134
+ memo["properties"] = memo["properties"].merge(it["properties"])
135
+ memo
136
+ end
137
+ end
122
138
  end
123
139
  end
124
140
  end
@@ -0,0 +1,30 @@
1
+ require_relative './rbs_type'
2
+ module Swagger2Rbs
3
+ module RestEndpointTyped
4
+ def typed_parameters_for_method
5
+ options_typed = "?Hash[untyped, untyped] options"
6
+ return "(#{options_typed})" unless body && parameters
7
+ return "(#{options_typed})" if body.empty? && parameters.empty?
8
+
9
+ typed_parameters = parameters&.map{|it| "String #{it}" } || []
10
+
11
+ typed_parameters.push("#{body_typed} body") if body?
12
+
13
+ "(#{typed_parameters.push(options_typed).join(', ')})"
14
+ end
15
+
16
+ def body_typed
17
+ RbsType.new(body).write_types
18
+ end
19
+
20
+ def response_typed(http_code)
21
+ RbsType.new(response(http_code), symbolize_keys: false).write_types
22
+ end
23
+
24
+ def all_responses_typed
25
+ return '{ "200" => untyped }' unless @props.dig("responses")
26
+
27
+ RbsType.new(all_responses, symbolize_keys: false).write_types
28
+ end
29
+ end
30
+ end
data/lib/swagger2_rbs.rb CHANGED
@@ -5,37 +5,32 @@ require_relative 'swagger2_rbs/hash_helper'
5
5
 
6
6
  module Swagger2Rbs
7
7
 
8
- def self.resolve_ref(hash, key, value)
9
- ref_key = value.gsub("#/", "").split("/")
10
- data = hash.dig(*ref_key)
11
- update_key = key.split(".").reject{|k| k == "$ref"}.join(".")
12
-
13
- [update_key, data]
14
- end
15
-
16
8
  def self.resolve_all_ref(swagger_spec)
17
- new_swagger_spec = swagger_spec.dup
18
- HashHelper.walk(swagger_spec) do |key, value|
19
- if key.split(".").last == "$ref"
20
- update_key, data = resolve_ref(swagger_spec, key, value)
21
- HashHelper.set_value(new_swagger_spec, update_key, data)
22
- end
9
+ HashHelper.resolve_special_key(swagger_spec, "$ref") do |key, value|
10
+ ref_key = value.gsub("#/", "").split("/")
11
+ swagger_spec.dig(*ref_key)
23
12
  end
24
- new_swagger_spec
25
13
  end
26
14
 
27
- def self.swagger_to_rest_api(swagger_spec)
15
+ def self.swagger_to_rest_api(swagger_spec, parse_method = :to_h)
28
16
  result = []
29
17
  resolve_all_ref(swagger_spec)["paths"].each do |path, data|
30
18
  data.each do |method, props|
31
19
  rest_data = RestEndpoint.new(path, method, props)
32
- result << rest_data.to_h
20
+ result << rest_data.send(parse_method)
33
21
  end
34
22
  end
35
23
 
36
24
  { base_uri: swagger_spec["servers"].first["url"], endpoints: result }
37
25
  end
38
26
 
27
+ def self.swagger_to_rest_api_yaml(swagger_spec)
28
+ response = swagger_to_rest_api(swagger_spec, :to_yaml)
29
+ YAML.dump(
30
+ HashHelper.deep_transform_keys_in_object!(response, &:to_s)
31
+ ).gsub("---\n", "")
32
+ end
33
+
39
34
  def self.rest_api_all(spec)
40
35
  result = []
41
36
  spec[:endpoints].each do |endpoint|
@@ -1,5 +1,5 @@
1
-
2
- require 'httparty'
1
+ # require 'httparty'
2
+ require 'json'
3
3
 
4
4
  class <%= @module_name %>
5
5
  include HTTParty
@@ -11,13 +11,15 @@ class <%= @module_name %>
11
11
  end
12
12
  <%- @data[:endpoints].each do |endpoint| -%>
13
13
 
14
- <%- if endpoint[:method] == 'get' -%>
14
+ <%- unless endpoint[:has_body] -%>
15
15
  def <%= endpoint[:method_name] %>(<%= endpoint[:parameters_for_method] %>)
16
- self.class.<%= endpoint[:method] %>("<%= endpoint[:path] %>", options)
16
+ response = self.class.<%= endpoint[:http_method] %>("<%= endpoint[:path] %>", options)
17
+ [response, <%= endpoint[:all_responses_for_return_method] %>]
17
18
  end
18
19
  <%- else -%>
19
20
  def <%= endpoint[:method_name] %>(<%= endpoint[:parameters_for_method] %>)
20
- self.class.<%= endpoint[:method] %>("<%= endpoint[:path] %>", { body: body.to_json }.merge(options))
21
+ response = self.class.<%= endpoint[:http_method] %>("<%= endpoint[:path] %>", { body: body.to_json }.merge(options))
22
+ [response, <%= endpoint[:all_responses_for_return_method] %>]
21
23
  end
22
24
  <%- end -%>
23
25
  <%- end -%>
@@ -1,16 +1,26 @@
1
-
2
1
  # Classes
3
2
 
3
+ module HTTParty
4
+ class Response
5
+ def body: -> untyped
6
+ def success?: -> bool
7
+ def code: -> Integer
8
+ end
9
+ end
10
+
4
11
  class <%= @module_name %>
5
12
  include HTTParty
6
13
 
14
+ def self.headers: (untyped headers) -> void
15
+ def self.base_uri: (String uri) -> void
16
+ def self.get: (String path, untyped options) -> HTTParty::Response
17
+ def self.post: (String path, untyped options) -> HTTParty::Response
18
+ def self.put: (String path, untyped options) -> HTTParty::Response
19
+ def self.delete: (String path, untyped options) -> HTTParty::Response
20
+
7
21
  def initialize: -> void
8
22
  <%- @data[:endpoints].each do |endpoint| -%>
9
23
 
10
- <%- if endpoint[:method] == 'get' -%>
11
- def <%= endpoint[:method_name] %>(<%= endpoint[:parameters_typed] %>) -> HTTParty::Response
12
- <%- else -%>
13
- def <%= endpoint[:method_name] %>(<%= endpoint[:body_typed] %>) -> HTTParty::Response
14
- <%- end -%>
24
+ def <%= endpoint[:method_name] %>: <%= endpoint[:typed_parameters_for_method] %> -> [HTTParty::Response, <%= endpoint[:all_responses_typed] %>]
15
25
  <%- end -%>
16
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swagger-to-rbs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miguel Savignano
@@ -14,14 +14,14 @@ dependencies:
14
14
  name: thor
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
@@ -51,7 +51,9 @@ files:
51
51
  - lib/swagger2_rbs.rb
52
52
  - lib/swagger2_rbs/cli.rb
53
53
  - lib/swagger2_rbs/hash_helper.rb
54
+ - lib/swagger2_rbs/rbs_type.rb
54
55
  - lib/swagger2_rbs/rest_endpoint.rb
56
+ - lib/swagger2_rbs/rest_endpoint_typed.rb
55
57
  - lib/templates/http_client.rb.erb
56
58
  - lib/templates/http_client.rbs.erb
57
59
  homepage: https://github.com/MiguelSavignano/swagger-to-rbs