swagger-to-rbs 0.1.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 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: []