swagger-to-rbs 0.1.2 → 0.5.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: 4c6bed3da0a9afc4136a9ed5fd605bc9331d1cabecd60dffc687061de701e985
4
- data.tar.gz: b820d03e4f501e6ec75f5756144b1ab67f35b5fce82914704e1c04122e30b71e
3
+ metadata.gz: ddb76510d5a4e87aee8bb582481a21c80616fa83e58a6fc49e8c1b02e449a4a5
4
+ data.tar.gz: 3460975741e8aad8d93e42939ef77cd6b40ba63ac3febf946e8a3e0af388618d
5
5
  SHA512:
6
- metadata.gz: 0b405ea2f717d6a2a7a5473dc092b3163c0a7305849d4730d9c0d69e8aa7ebecef3915b0d517f730f141900004ba8dd5124223d606ddac609f807d305fdd9ade
7
- data.tar.gz: 0c38554cbb91292be48311a8ee0e7de88663ef1c0de7e432e08525b9ce2fb308ca302482025ec68fe117231b7cc0aad0859a43d2da17f8b3ac9f2a27ad335703
6
+ metadata.gz: 1c788888a133f558bfae8348f7d22012e401ce42f6cdac5ed5248b7a669e5dbde5b797145316a626a39a2fff667f283ec6d0b5f75e2d663f6e004540f804c7b6
7
+ data.tar.gz: ba69ff9e2377795ed266c9319254d94695d758d5795ff64d26fe81996f01ef595567b75787d413dcb8bf6177bb699fc09c1f408c4dd8d5bc2cc85c9a6142af45
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
- ruby lib/index.rb MyApi
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
@@ -0,0 +1,41 @@
1
+ module Swagger2Rbs
2
+ class HashHelper
3
+
4
+ # https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/keys.rb#L129
5
+ def self.deep_transform_keys_in_object!(object, &block)
6
+ case object
7
+ when Hash
8
+ object.keys.each do |key|
9
+ value = object.delete(key)
10
+ object[yield(key)] = deep_transform_keys_in_object!(value, &block)
11
+ end
12
+ object
13
+ when Array
14
+ object.map! { |e| deep_transform_keys_in_object!(e, &block) }
15
+ else
16
+ object
17
+ end
18
+ end
19
+
20
+ def self.set_value(hash, key, value)
21
+ arr = key.split(".")
22
+ last_key = arr.pop()
23
+ hash.dig(*arr)[last_key] = value
24
+ hash
25
+ rescue => e
26
+ hash
27
+ end
28
+
29
+ def self.walk(hash, &block)
30
+ hash.each do |k, v|
31
+ if v.is_a?(Hash)
32
+ walk(v) do |k2, v2|
33
+ yield "#{k}.#{k2}", v2
34
+ end
35
+ else
36
+ yield k, v
37
+ end
38
+ end
39
+ end
40
+ end
41
+ 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,19 +15,34 @@ 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?,
20
22
  method_name: method_name,
21
- body: body,
22
- body_typed: body_typed,
23
23
  response_typed: response_typed,
24
24
  }
25
25
  rescue => e
26
26
  raise e, "Context: #{path} #{method} Message: #{e.message}"
27
27
  end
28
28
 
29
+ def body?
30
+ body && !body.empty?
31
+ end
32
+
33
+ def to_yaml
34
+ {
35
+ path: path,
36
+ method: method,
37
+ path_parameters: parameters,
38
+ method_name: method_name,
39
+ body: body,
40
+ response: response_typed,
41
+ }
42
+ rescue => e
43
+ raise e, "Context: #{path} #{method} Message: #{e.message}"
44
+ end
45
+
29
46
  def method_name
30
47
  props["operationId"] || path.slugify.gsub("-", "_")
31
48
  end
@@ -37,39 +54,7 @@ module Swagger2Rbs
37
54
  def parameters
38
55
  return [] unless props["parameters"]
39
56
 
40
- props["parameters"].select{|it| it["in"] == "path"}.map{|it| it["name"]}
41
- end
42
-
43
- def response_typed
44
- schema = resolve_all_of(@props.dig("responses", "200", "content", "application/json", "schema"))
45
- schema_to_typed(schema, {})
46
- end
47
-
48
- def parameters_typed
49
- return nil if method != "get"
50
- parameters.map{|it| "String #{it}" }
51
- .push("?Hash[untyped, untyped] options")
52
- .join(", ")
53
- end
54
-
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"
62
- end
63
-
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}]"
72
- end
57
+ props["parameters"]&.select{|it| it["in"] == "path"}&.map{|it| it["name"]}
73
58
  end
74
59
 
75
60
  def body
@@ -80,26 +65,12 @@ module Swagger2Rbs
80
65
  end
81
66
 
82
67
  def parameters_for_method
83
- return parameters.push("options = {}").join(", ") if (method == "get")
68
+ return parameters.push("options = {}").join(", ") if method == "get"
84
69
 
85
- parameters.push("body").push("options = {}").join(", ")
86
- end
87
-
88
- def schema_to_typed(schema, memo = {})
89
- return nil unless schema
90
-
91
- schema["properties"]&.reduce(memo)do |memo, (k,v)|
92
- if v["type"] == "object"
93
- memo.merge({k => schema_to_typed(v, {})})
94
- elsif v["type"] == "array"
95
- if v.dig("items", "type") == "object"
96
- memo.merge({k => [schema_to_typed(v["items"], {})] })
97
- else
98
- memo.merge({k => [v.dig("items", "type")] })
99
- end
100
- else
101
- memo.merge({k => v["type"] })
102
- end
70
+ if body&.empty?
71
+ parameters.push("options = {}").join(", ")
72
+ else
73
+ parameters.push("body").push("options = {}").join(", ")
103
74
  end
104
75
  end
105
76
 
@@ -0,0 +1,74 @@
1
+ module Swagger2Rbs
2
+
3
+ module RestEndpointTyped
4
+ def response_typed
5
+ schema = resolve_all_of(@props.dig("responses", "200", "content", "application/json", "schema"))
6
+ schema_to_typed(schema, {})
7
+ end
8
+
9
+ def typed_parameters_for_method
10
+ options_typed = "?Hash[untyped, untyped] options"
11
+ return "(#{options_typed})" unless body && parameters
12
+ return "(#{options_typed})" if body.empty? && parameters.empty?
13
+
14
+ typed_parameters = parameters&.map{|it| "String #{it}" } || []
15
+
16
+ typed_parameters.push("#{body_typed} body") if body?
17
+
18
+ "(#{typed_parameters.push(options_typed).join(', ')})"
19
+ end
20
+
21
+ def body_typed
22
+ return nil unless body?
23
+ typed = (body.is_a?(Array) ? body[0] : body)&.map{ |k, v| to_typed(k, v) }.join(', ')
24
+ if body.is_a?(Array)
25
+ "Array[{ #{typed} }]"
26
+ else
27
+ "{ #{typed} }"
28
+ end
29
+ end
30
+
31
+ def type_case(str)
32
+ case str
33
+ when "boolean"
34
+ "bool"
35
+ else
36
+ str&.capitalize
37
+ end
38
+ end
39
+
40
+ def to_typed(k, v)
41
+ return "#{k}: #{type_case(v)}" unless v.is_a?(Array) || v.is_a?(Hash)
42
+ return "#{k}: {#{v.map{ |k2, v2| to_typed(k2, v2) }.join(", ")}}" if v.is_a?(Hash)
43
+
44
+ if v[0]&.is_a?(Hash)
45
+ "#{k}: Array[{" + v[0].map{ |k, v| to_typed(k, v) }.join(", ") + "}]"
46
+ else
47
+ "#{k}: Array[#{type_case(v[0])}]"
48
+ end
49
+ end
50
+
51
+ def schema_to_typed(schema, memo = {})
52
+ return nil unless schema
53
+
54
+ properties = schema["type"]["array"] ? schema["items"]["properties"] : schema["properties"]
55
+
56
+ result = properties&.reduce(memo)do |memo, (k,v)|
57
+ if v["type"] == "object"
58
+ memo.merge({k => schema_to_typed(v, {})})
59
+ elsif v["type"] == "array"
60
+ if v.dig("items", "type") == "object"
61
+ memo.merge({k => [schema_to_typed(v["items"], {})] })
62
+ else
63
+ memo.merge({k => [v.dig("items", "type")] })
64
+ end
65
+ else
66
+ memo.merge({k => v["type"] })
67
+ end
68
+ end
69
+ return [result] if schema["type"]["array"]
70
+
71
+ result
72
+ end
73
+ end
74
+ end
data/lib/swagger2_rbs.rb CHANGED
@@ -1,21 +1,48 @@
1
1
  require 'json'
2
2
  require 'erb'
3
3
  require_relative 'swagger2_rbs/rest_endpoint'
4
+ require_relative 'swagger2_rbs/hash_helper'
4
5
 
5
6
  module Swagger2Rbs
6
7
 
7
- def self.swagger_to_rest_api(swagger_spec)
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
+ 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
23
+ end
24
+ new_swagger_spec
25
+ end
26
+
27
+ def self.swagger_to_rest_api(swagger_spec, parse_method = :to_h)
8
28
  result = []
9
- swagger_spec["paths"].each do |path, data|
29
+ resolve_all_ref(swagger_spec)["paths"].each do |path, data|
10
30
  data.each do |method, props|
11
31
  rest_data = RestEndpoint.new(path, method, props)
12
- result << rest_data.to_h
32
+ result << rest_data.send(parse_method)
13
33
  end
14
34
  end
15
35
 
16
36
  { base_uri: swagger_spec["servers"].first["url"], endpoints: result }
17
37
  end
18
38
 
39
+ def self.swagger_to_rest_api_yaml(swagger_spec)
40
+ response = swagger_to_rest_api(swagger_spec, :to_yaml)
41
+ YAML.dump(
42
+ HashHelper.deep_transform_keys_in_object!(response, &:to_s)
43
+ ).gsub("---\n", "")
44
+ end
45
+
19
46
  def self.rest_api_all(spec)
20
47
  result = []
21
48
  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,13 @@ 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
+ self.class.<%= endpoint[:http_method] %>("<%= endpoint[:path] %>", options)
17
17
  end
18
18
  <%- else -%>
19
19
  def <%= endpoint[:method_name] %>(<%= endpoint[:parameters_for_method] %>)
20
- self.class.<%= endpoint[:method] %>("<%= endpoint[:path] %>", { body: body.to_json }.merge(options))
20
+ self.class.<%= endpoint[:http_method] %>("<%= endpoint[:path] %>", { body: body.to_json }.merge(options))
21
21
  end
22
22
  <%- end -%>
23
23
  <%- end -%>
@@ -1,16 +1,25 @@
1
-
2
1
  # Classes
3
2
 
3
+ module HTTParty
4
+ class Response
5
+ def body: -> untyped
6
+ def success?: -> bool
7
+ end
8
+ end
9
+
4
10
  class <%= @module_name %>
5
11
  include HTTParty
6
12
 
13
+ def self.headers: (untyped headers) -> void
14
+ def self.base_uri: (String uri) -> void
15
+ def self.get: (String path, untyped options) -> void
16
+ def self.post: (String path, untyped options) -> void
17
+ def self.put: (String path, untyped options) -> void
18
+ def self.delete: (String path, untyped options) -> void
19
+
7
20
  def initialize: -> void
8
21
  <%- @data[:endpoints].each do |endpoint| -%>
9
22
 
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 -%>
23
+ def <%= endpoint[:method_name] %>: <%= endpoint[:typed_parameters_for_method] %> -> HTTParty::Response
15
24
  <%- end -%>
16
25
  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.1.2
4
+ version: 0.5.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
@@ -50,7 +50,9 @@ files:
50
50
  - lib/index.rb
51
51
  - lib/swagger2_rbs.rb
52
52
  - lib/swagger2_rbs/cli.rb
53
+ - lib/swagger2_rbs/hash_helper.rb
53
54
  - lib/swagger2_rbs/rest_endpoint.rb
55
+ - lib/swagger2_rbs/rest_endpoint_typed.rb
54
56
  - lib/templates/http_client.rb.erb
55
57
  - lib/templates/http_client.rbs.erb
56
58
  homepage: https://github.com/MiguelSavignano/swagger-to-rbs