swagger-to-rbs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 74b61b73586f11db01b0d710db9d9d37ba73ff814100cee23ed8be6be6f844bb
4
+ data.tar.gz: '0649e4db09841b3681a93cb749ed43f8562271e49ac8e1e9ab4af30018404edc'
5
+ SHA512:
6
+ metadata.gz: dcb476b66268b52abbfb5b25746755618d4bae2af3a28a2a8ee5d2bf0c1a32c937eaf4418887bc9e77b981afe6a4a1231ba418ffd7c1bfe52e7a6b87e5083596
7
+ data.tar.gz: c52a43103f947a24a7e839b3f59638a81677e850b159bd85290196c59f232424b99833fb1f5c8450523f8b3a77865562b66a49c7efedadf5f42d6a605452505e
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ ### Usage
2
+
3
+ Dowload swagger spec
4
+
5
+ Example:
6
+
7
+ ```
8
+ curl https://petstore3.swagger.io/api/v3/openapi.json > swagger.json
9
+ ```
10
+
11
+ Generate http client based on [Httparty gem](https://github.com/jnunemaker/httparty)
12
+
13
+ ```
14
+ ruby lib/index.rb MyApi
15
+ ```
16
+
17
+ Result:
18
+
19
+ ```rb
20
+ require 'httparty'
21
+
22
+ class PetApi
23
+ include HTTParty
24
+ base_uri "/api/v3"
25
+ #...
26
+
27
+ def getPetById(petId, params, options = {})
28
+ self.class.get("/pet/#{petId}", params: params.to_json, headers: { 'Content-Type' => 'application/json' }.merge(options))
29
+ end
30
+
31
+ def updatePet(body, options = {})
32
+ self.class.put("/pet", body: body.to_json, headers: { 'Content-Type' => 'application/json' }.merge(options))
33
+ end
34
+
35
+ def addPet(body, options = {})
36
+ self.class.post("/pet", body: body.to_json, headers: { 'Content-Type' => 'application/json' }.merge(options))
37
+ end
38
+ #...
39
+ end
40
+ ```
41
+
42
+ ### Send default header in all request
43
+
44
+ Authorization example:
45
+
46
+ ```
47
+ api = MyApi.new
48
+
49
+ MyApi.headers(Authorization: "Bearer #{access_token}")
50
+ MyApi.base_uri("http://otherurl.test/api/v1)
51
+
52
+ api.getPetById(1)
53
+ ```
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/swagger2_rbs'
5
+ require_relative '../lib/swagger2_rbs/cli'
6
+
7
+ Swagger2Rbs::Cli.start(ARGV)
data/lib/index.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+ require 'pry'
3
+ require 'dotenv/load'
4
+ require_relative 'swagger2_rbs'
5
+ require_relative './swagger2_rbs/cli'
6
+
7
+ Swagger2Rbs::Cli.start(ARGV)
@@ -0,0 +1,42 @@
1
+ require 'thor'
2
+ require 'json'
3
+ require 'yaml'
4
+
5
+ module Swagger2Rbs
6
+ class Cli < Thor
7
+ attr_reader :name, :swagger_path, :rest_api_path, :debug
8
+
9
+ desc 'genearte', 'generate http client and rbs'
10
+ option :name, desc: 'Name'
11
+ option :spec, desc: 'Swagger file path'
12
+ option :debug, desc: 'Generate debug file'
13
+ def generate
14
+ @name = options[:name]
15
+ @swagger_path = options[:spec]
16
+ @debug = options[:debug]
17
+ data = fetch_data
18
+ File.write(".rest-api.json", JSON.pretty_generate(data)) if debug
19
+
20
+ file_name = to_underscore(name)
21
+ File.write("#{file_name}.rb", ::Swagger2Rbs.generate(name, data.dup))
22
+ File.write("#{file_name}.rbs", ::Swagger2Rbs.generate_rbs(name, data.dup))
23
+ end
24
+
25
+ private
26
+ def fetch_data
27
+ if swagger_path
28
+ swagger_spec = JSON.parse(File.read(swagger_path))
29
+ ::Swagger2Rbs.swagger_to_rest_api(swagger_spec)
30
+ elsif rest_api_path
31
+ data = YAML.load_file(rest_api_path, symbolize_names: true)
32
+ ::Swagger2Rbs.rest_api_all(data)
33
+ else
34
+ raise StandardError, "Missing swagger_path or rest_api_path"
35
+ end
36
+ end
37
+
38
+ def to_underscore(string)
39
+ string.gsub(/(.)([A-Z])/,'\1_\2').downcase
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,122 @@
1
+ require 'slugify'
2
+
3
+ module Swagger2Rbs
4
+ class RestEndpoint
5
+ attr_reader :path, :method, :props
6
+
7
+ def initialize(path, method, props)
8
+ @path = path
9
+ @method = method
10
+ @props = props || {}
11
+ end
12
+
13
+ def to_h
14
+ {
15
+ path: path_with_parameters,
16
+ method: method,
17
+ parameters: parameters,
18
+ parameters_for_method: parameters_for_method,
19
+ parameters_typed: parameters_typed,
20
+ method_name: method_name,
21
+ body: body,
22
+ body_typed: body_typed,
23
+ response_typed: response_typed,
24
+ }
25
+ end
26
+
27
+ def method_name
28
+ props["operationId"] || path.slugify.gsub("-", "_")
29
+ end
30
+
31
+ def path_with_parameters
32
+ path.gsub("{", '#{')
33
+ end
34
+
35
+ def parameters
36
+ return [] unless props["parameters"]
37
+
38
+ props["parameters"].select{|it| it["in"] == "path"}.map{|it| it["name"]}
39
+ end
40
+
41
+ def response_typed
42
+ schema = resolve_all_of(@props.dig("responses", "200", "content", "application/json", "schema"))
43
+ schema_to_typed(schema, {})
44
+ end
45
+
46
+ def parameters_typed
47
+ return nil if method != "get"
48
+ parameters.map{|it| "String #{it}" }
49
+ .push("?Hash[untyped, untyped] options")
50
+ .join(", ")
51
+ end
52
+
53
+ def body_typed
54
+ return nil if method != "post"
55
+
56
+ return "Hash[String, untyped]" unless body
57
+ return "Hash[String, untyped]" if body.empty?
58
+
59
+ "{" + body.map{ |k, v| to_typed(k, v) }.join(", ") + "}" + " body, ?Hash[untyped, untyped] options"
60
+ end
61
+
62
+ def to_typed(k, v)
63
+ return "#{k}: #{v&.capitalize}" unless v.is_a?(Array) || v.is_a?(Hash)
64
+ return "#{k}: {#{v.map{ |k2, v2| to_typed(k2, v2) }.join(", ")}}" if v.is_a?(Hash)
65
+
66
+ if v[0]&.is_a?(Hash)
67
+ "#{k}: Array[{" + v[0].map{ |k, v| "#{k}: #{v&.capitalize}" }.join(", ") + "}]"
68
+ else
69
+ "#{k}: Array[#{v[0]&.capitalize}]"
70
+ end
71
+ end
72
+
73
+ def body
74
+ body_schema = resolve_of(props.dig("requestBody", "content", "application/json", "schema"))
75
+ return {} unless body_schema
76
+
77
+ schema_to_typed(body_schema)
78
+ end
79
+
80
+ def parameters_for_method
81
+ return parameters.push("options = {}").join(", ") if (method == "get")
82
+
83
+ parameters.push("body").push("options = {}").join(", ")
84
+ end
85
+
86
+ def schema_to_typed(schema, memo = {})
87
+ return nil unless schema
88
+
89
+ schema["properties"]&.reduce(memo)do |memo, (k,v)|
90
+ if v["type"] == "object"
91
+ memo.merge({k => schema_to_typed(v, {})})
92
+ elsif v["type"] == "array"
93
+ if v.dig("items", "type") == "object"
94
+ memo.merge({k => [schema_to_typed(v["items"], {})] })
95
+ else
96
+ memo.merge({k => [v.dig("items", "type")] })
97
+ end
98
+ else
99
+ memo.merge({k => v["type"] })
100
+ end
101
+ end
102
+ end
103
+
104
+ def resolve_of(data)
105
+ resolve_all_of(resolve_one_of(data))
106
+ end
107
+
108
+ def resolve_one_of(data)
109
+ return data unless data
110
+ return data unless data["oneOf"]
111
+
112
+ data["oneOf"].reduce(&:merge)
113
+ end
114
+
115
+ def resolve_all_of(data)
116
+ return data unless data
117
+ return data unless data["allOf"]
118
+
119
+ data["allOf"].reduce(&:merge)
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,50 @@
1
+ require 'json'
2
+ require 'erb'
3
+ require_relative 'swagger2_rbs/rest_endpoint'
4
+
5
+ module Swagger2Rbs
6
+
7
+ def self.swagger_to_rest_api(swagger_spec)
8
+ result = []
9
+ swagger_spec["paths"].each do |path, data|
10
+ data.each do |method, props|
11
+ rest_data = RestEndpoint.new(path, method, props)
12
+ result << rest_data.to_h
13
+ end
14
+ end
15
+
16
+ { base_uri: swagger_spec["servers"].first["url"], endpoints: result }
17
+ end
18
+
19
+ def self.rest_api_all(spec)
20
+ result = []
21
+ spec[:endpoints].each do |endpoint|
22
+ rest_data = RestEndpoint.new(endpoint[:path], endpoint[:method], endpoint[:props])
23
+ result << rest_data.to_h
24
+ end
25
+
26
+ { base_uri: spec[:base_uri], endpoints: result }
27
+ end
28
+
29
+ def self.mock_rest_api_data
30
+ data = YAML.load_file('spec/fixtures/rest-api.yaml').transform_keys(&:to_sym)
31
+ data[:endpoints].map!{|it| it.transform_keys(&:to_sym) }
32
+ data
33
+ end
34
+
35
+ def self.generate(name, data)
36
+ @module_name = name
37
+ @data = data
38
+ template = File.read("#{File.dirname(__dir__ )}/lib/templates/http_client.rb.erb")
39
+
40
+ ERB.new(template, nil, '-').result(binding)
41
+ end
42
+
43
+ def self.generate_rbs(name, data)
44
+ @module_name = name
45
+ @data = data
46
+ template = File.read("#{File.dirname(__dir__ )}/lib/templates/http_client.rbs.erb")
47
+
48
+ ERB.new(template, nil, '-').result(binding)
49
+ end
50
+ end
@@ -0,0 +1,24 @@
1
+
2
+ require 'httparty'
3
+
4
+ class <%= @module_name %>
5
+ include HTTParty
6
+
7
+ base_uri "<%= @data[:base_uri] %>"
8
+
9
+ def initialize
10
+ self.class.headers({ 'Content-Type' => 'application/json' })
11
+ end
12
+ <%- @data[:endpoints].each do |endpoint| -%>
13
+
14
+ <%- if endpoint[:method] == 'get' -%>
15
+ def <%= endpoint[:method_name] %>(<%= endpoint[:parameters_for_method] %>)
16
+ self.class.<%= endpoint[:method] %>("<%= endpoint[:path] %>", options)
17
+ end
18
+ <%- else -%>
19
+ def <%= endpoint[:method_name] %>(<%= endpoint[:parameters_for_method] %>)
20
+ self.class.<%= endpoint[:method] %>("<%= endpoint[:path] %>", { body: body.to_json }.merge(options))
21
+ end
22
+ <%- end -%>
23
+ <%- end -%>
24
+ end
@@ -0,0 +1,17 @@
1
+
2
+ # Classes
3
+
4
+ class <%= @module_name %>
5
+ include HTTParty
6
+ attr_accessor default_headers: bot
7
+
8
+ def initialize: -> void
9
+ <%- @data[:endpoints].each do |endpoint| -%>
10
+
11
+ <%- if endpoint[:method] == 'get' -%>
12
+ def <%= endpoint[:method_name] %>(<%= endpoint[:parameters_typed] %>) -> HTTParty::Response
13
+ <%- else -%>
14
+ def <%= endpoint[:method_name] %>(<%= endpoint[:body_typed] %>) -> HTTParty::Response
15
+ <%- end -%>
16
+ <%- end -%>
17
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: swagger-to-rbs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Miguel Savignano
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: slugify
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Generate Http client and rbs files
42
+ email: migue.masx@gmail.com
43
+ executables:
44
+ - swagger-to-rbs
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - README.md
49
+ - bin/swagger-to-rbs
50
+ - lib/index.rb
51
+ - lib/swagger2_rbs.rb
52
+ - lib/swagger2_rbs/cli.rb
53
+ - lib/swagger2_rbs/rest_endpoint.rb
54
+ - lib/templates/http_client.rb.erb
55
+ - lib/templates/http_client.rbs.erb
56
+ homepage: https://github.com/MiguelSavignano/swagger-to-rbs
57
+ licenses:
58
+ - MIT
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.3.3
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: Swagger to rbs
79
+ test_files: []