haveapi-go-client 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/Gemfile +4 -0
  4. data/README.md +64 -0
  5. data/bin/haveapi-go-client +3 -0
  6. data/haveapi-go-client.gemspec +25 -0
  7. data/lib/haveapi/go_client/action.rb +128 -0
  8. data/lib/haveapi/go_client/api_version.rb +21 -0
  9. data/lib/haveapi/go_client/authentication/base.rb +13 -0
  10. data/lib/haveapi/go_client/authentication/basic.rb +21 -0
  11. data/lib/haveapi/go_client/authentication/token.rb +56 -0
  12. data/lib/haveapi/go_client/authentication/unsupported.rb +13 -0
  13. data/lib/haveapi/go_client/authentication_methods.rb +24 -0
  14. data/lib/haveapi/go_client/cli.rb +39 -0
  15. data/lib/haveapi/go_client/erb_template.rb +46 -0
  16. data/lib/haveapi/go_client/generator.rb +65 -0
  17. data/lib/haveapi/go_client/input_output.rb +48 -0
  18. data/lib/haveapi/go_client/metadata.rb +63 -0
  19. data/lib/haveapi/go_client/parameter.rb +27 -0
  20. data/lib/haveapi/go_client/parameters/association.rb +54 -0
  21. data/lib/haveapi/go_client/parameters/base.rb +66 -0
  22. data/lib/haveapi/go_client/parameters/global_meta_includes.rb +17 -0
  23. data/lib/haveapi/go_client/parameters/resource.rb +20 -0
  24. data/lib/haveapi/go_client/parameters/typed.rb +30 -0
  25. data/lib/haveapi/go_client/resource.rb +133 -0
  26. data/lib/haveapi/go_client/utils.rb +10 -0
  27. data/lib/haveapi/go_client/version.rb +5 -0
  28. data/lib/haveapi/go_client.rb +21 -0
  29. data/shell.nix +23 -0
  30. data/template/action.go.erb +475 -0
  31. data/template/authentication/basic.go.erb +22 -0
  32. data/template/authentication/token.go.erb +164 -0
  33. data/template/authentication.go.erb +10 -0
  34. data/template/client.go.erb +26 -0
  35. data/template/go.mod.erb +1 -0
  36. data/template/request.go.erb +96 -0
  37. data/template/resource.go.erb +36 -0
  38. data/template/response.go.erb +13 -0
  39. data/template/types.go.erb +41 -0
  40. metadata +125 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d9a94df4ba98576591129b131154972fd9a42960b0fb85269f20dbb178d5d57c
4
+ data.tar.gz: 285063beca2e245611104a43a283893bf3bf1182e7b0275a93b876e7783717d9
5
+ SHA512:
6
+ metadata.gz: 93767bf1ec0dc97ea788080d327899a6883f620f65e123c4f548c8e9d2e20494fc58011da1688128e2a34ea9299b7a63a4d7189240d6ed7e675d00e9c51daf3c
7
+ data.tar.gz: d41fb0d6f3b0786c1e3d58c8edcb66ad1ac24a0f7771586fb3474a8e62801e565e6d41d9685093657dab5a6c511632104b7f52b6ac5f75d01ab18f77d53ce9dd
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .gems
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'haveapi-client', path: '../ruby'
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ HaveAPI Client Generator For Go
2
+ -------------------------------
3
+ HaveAPI-Go-Client generates a client library for accessing selected APIs built
4
+ with the [HaveAPI framework](https://github.com/vpsfreecz/haveapi).
5
+
6
+ ## Installation
7
+
8
+ $ gem install 'haveapi-go-client'
9
+
10
+ ## CLI
11
+ The generator is run against an API server that the resulting client should be
12
+ able to work with. The generated client uses one selected API version. When
13
+ the API server changes, the client has to be regenerated.
14
+
15
+ ```
16
+ $ haveapi-go-client -h
17
+ Usage: haveapi-go-client [options] <api url> <destination>
18
+ --version VERSION Use specified API version
19
+ --module MODULE Name of the generated Go module
20
+ --package PKG Name of the generated Go package
21
+ ```
22
+
23
+ For example:
24
+
25
+ ```
26
+ $ haveapi-go-client https://api.vpsfree.cz ~/go/src/foo/client
27
+ ```
28
+
29
+ ## Usage
30
+ The generated library can then be imported in your projects. For example,
31
+ in a project with the following `go.mod`:
32
+
33
+ ```
34
+ module foo
35
+ ```
36
+
37
+ The client could be imported and used as:
38
+
39
+ ```go
40
+ package main
41
+
42
+ import (
43
+ "fmt"
44
+ "foo/client"
45
+ )
46
+
47
+ func main() {
48
+ api := client.New("https://api.vpsfree.cz")
49
+ api.SetBasicAuthentication("admin", "secret")
50
+
51
+ action := api.Cluster.PublicStats.Prepare()
52
+ resp, err := action.Call()
53
+
54
+ if err != nil {
55
+ fmt.Println(err)
56
+ return
57
+ }
58
+
59
+ fmt.Printf("%+v\n", resp)
60
+ fmt.Printf("%+v\n", resp.Response)
61
+ fmt.Printf("%+v\n", resp.Response.Cluster)
62
+ fmt.Printf("%+v\n", resp.Output)
63
+ }
64
+ ```
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'haveapi/go_client/cli'
3
+ HaveAPI::GoClient::Cli.run
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'haveapi/go_client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'haveapi-go-client'
8
+ spec.version = HaveAPI::GoClient::VERSION
9
+ spec.authors = ['Jakub Skokan']
10
+ spec.email = ['jakub.skokan@vpsfree.cz']
11
+ spec.summary =
12
+ spec.description = 'Go client generator'
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler'
22
+ spec.add_development_dependency 'rake'
23
+
24
+ spec.add_runtime_dependency 'haveapi-client', '~> 0.13.0'
25
+ end
@@ -0,0 +1,128 @@
1
+ require 'haveapi/go_client/utils'
2
+
3
+ module HaveAPI::GoClient
4
+ class Action
5
+ include Utils
6
+
7
+ # @return [Resource]
8
+ attr_reader :resource
9
+
10
+ # Name as returned by the API
11
+ # @return [String]
12
+ attr_reader :name
13
+
14
+ # Name aliases as returned by the API
15
+ # @return [Array<String>]
16
+ attr_reader :aliases
17
+
18
+ # Full action name, including resource
19
+ # @return [String]
20
+ attr_reader :full_dot_name
21
+
22
+ # Name for usage in Go
23
+ # @return [String]
24
+ attr_reader :go_name
25
+
26
+ # Data type for Go
27
+ # @return [String]
28
+ attr_reader :go_type
29
+
30
+ # @return [Metadata]
31
+ attr_reader :metadata
32
+
33
+ # @return [InputOutput]
34
+ attr_reader :input
35
+
36
+ # @return [InputOutput]
37
+ attr_reader :output
38
+
39
+ # @return [String]
40
+ attr_reader :http_method
41
+
42
+ # @return [String]
43
+ attr_reader :path
44
+
45
+ # Go type for invocation struct
46
+ # @return [String]
47
+ attr_reader :go_invocation_type
48
+
49
+ # Go type for request struct
50
+ # @return [String]
51
+ attr_reader :go_request_type
52
+
53
+ # Go type for response struct
54
+ # @return [String]
55
+ attr_reader :go_response_type
56
+
57
+ def initialize(resource, name, desc, prefix: nil)
58
+ @resource = resource
59
+ @name = name.to_s
60
+ @prefix = prefix
61
+ @aliases = desc[:aliases]
62
+ @full_dot_name = resource.full_dot_name + '#' + @name.capitalize
63
+ @go_name = camelize(name)
64
+ @go_type = full_go_type
65
+ @go_invocation_type = go_type + 'Invocation'
66
+ @go_request_type = go_type + 'Request'
67
+ @go_response_type = go_type + 'Response'
68
+ @input = desc[:input] && InputOutput.new(self, :io, :input, desc[:input])
69
+ @output = desc[:output] && InputOutput.new(self, :io, :output, desc[:output])
70
+ @http_method = desc[:method]
71
+ @path = desc[:path]
72
+ @metadata = desc[:meta] && Metadata.new(self, desc[:meta])
73
+ @blocking = desc[:blocking]
74
+ end
75
+
76
+ # Return action name with all aliases, camelized
77
+ # @return [Array<String>]
78
+ def all_names
79
+ yield(go_name)
80
+ aliases.each { |v| yield(camelize(v)) }
81
+ end
82
+
83
+ # @return [Boolean]
84
+ def has_path_params?
85
+ path =~ /\{[a-zA-Z\-_]+\}/
86
+ end
87
+
88
+ # @return [Boolean]
89
+ def has_input?
90
+ input && input.parameters.any?
91
+ end
92
+
93
+ # @return [Boolean]
94
+ def has_output?
95
+ output && output.parameters.any?
96
+ end
97
+
98
+ def input_output
99
+ %i(input output).select do |v|
100
+ send(v) && send(v).parameters.any?
101
+ end.map { |v| send(v) }
102
+ end
103
+
104
+ def blocking?
105
+ @blocking
106
+ end
107
+
108
+ def resolve_associations
109
+ input_output.each do |io|
110
+ io.resolve_associations
111
+ end
112
+
113
+ metadata && metadata.resolve_associations
114
+ end
115
+
116
+ protected
117
+ attr_reader :prefix
118
+
119
+ def full_go_type
120
+ names = []
121
+ names << camelize(prefix) if prefix
122
+ names << 'Action'
123
+ names.concat(resource.resource_path.map(&:go_name))
124
+ names << go_name
125
+ names.join('')
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,21 @@
1
+ module HaveAPI::GoClient
2
+ class ApiVersion
3
+ # @return [Array<Authentication::Base>]
4
+ attr_reader :auth_methods
5
+
6
+ # @return [String]
7
+ attr_reader :metadata_namespace
8
+
9
+ # @return [Array<Resource>]
10
+ attr_reader :resources
11
+
12
+ def initialize(desc)
13
+ @resources = desc[:resources].map { |k, v| Resource.new(self, k, v) }
14
+ @resources.each { |r| r.resolve_associations }
15
+ @auth_methods = desc[:authentication].map do |k, v|
16
+ AuthenticationMethods.new(self, k, v)
17
+ end
18
+ @metadata_namespace = desc[:meta][:namespace]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ module HaveAPI::GoClient
2
+ class Authentication::Base
3
+ # @param name [Symbol]
4
+ def self.register(name)
5
+ AuthenticationMethods.register(name, self)
6
+ end
7
+
8
+ # @param dst [String]
9
+ def generate(dst)
10
+ raise NotImplementedError
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ require 'haveapi/go_client/authentication/base'
2
+
3
+ module HaveAPI::GoClient
4
+ class Authentication::Basic < Authentication::Base
5
+ register :basic
6
+
7
+ def initialize(api_version, name, desc)
8
+
9
+ end
10
+
11
+ def generate(gen)
12
+ ErbTemplate.render_to_if_changed(
13
+ 'authentication/basic.go',
14
+ {
15
+ package: gen.package,
16
+ },
17
+ File.join(gen.dst, 'auth_basic.go')
18
+ )
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,56 @@
1
+ require 'haveapi/go_client/authentication/base'
2
+
3
+ module HaveAPI::GoClient
4
+ class Authentication::Token < Authentication::Base
5
+ register :token
6
+
7
+ # HTTP header the token is sent in
8
+ # @return [String]
9
+ attr_reader :http_header
10
+
11
+ # Query parameter the token is sent in
12
+ # @return [String]
13
+ attr_reader :query_parameter
14
+
15
+ # Resource for token manipulation
16
+ # @return [Resource]
17
+ attr_reader :resource
18
+
19
+ def initialize(api_version, name, desc)
20
+ @http_header = desc[:http_header]
21
+ @query_parameter = desc[:query_parameter]
22
+ @resource = Resource.new(
23
+ api_version,
24
+ :token,
25
+ desc[:resources][:token],
26
+ prefix: 'auth_token',
27
+ )
28
+ resource.resolve_associations
29
+ end
30
+
31
+ def generate(gen)
32
+ ErbTemplate.render_to_if_changed(
33
+ 'authentication/token.go',
34
+ {
35
+ package: gen.package,
36
+ auth: self,
37
+ },
38
+ File.join(gen.dst, 'auth_token.go')
39
+ )
40
+
41
+ resource.generate(gen)
42
+ end
43
+
44
+ # @return [Action]
45
+ def request_action
46
+ @request_action ||= resource.actions.detect { |a| a.name == 'request' }
47
+ end
48
+
49
+ # @return [Array<Action>]
50
+ def custom_actions
51
+ @custom_actions ||= resource.actions.reject do |a|
52
+ %w(request renew revoke).include?(a.name)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,13 @@
1
+ require 'haveapi/go_client/authentication/base'
2
+
3
+ module HaveAPI::GoClient
4
+ class Authentication::Unsupported < Authentication::Base
5
+ def initialize(api_version, name, desc)
6
+ warn "Ignoring unsupported authentication method #{name}"
7
+ end
8
+
9
+ def generate(gen)
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ module HaveAPI::GoClient
2
+ module Authentication ; end
3
+
4
+ module AuthenticationMethods
5
+ # @param name [Symbol]
6
+ # @param klass [Class]
7
+ def self.register(name, klass)
8
+ @methods ||= {}
9
+ @methods[name] = klass
10
+ end
11
+
12
+ # @param name [String]
13
+ def self.get(name)
14
+ @methods[name.to_sym]
15
+ end
16
+
17
+ # @param api_version [ApiVersion]
18
+ # @param name [String]
19
+ def self.new(api_version, name, *args)
20
+ klass = get(name) || Authentication::Unsupported
21
+ klass.new(api_version, name, *args)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,39 @@
1
+ require 'haveapi/go_client'
2
+ require 'optparse'
3
+
4
+ module HaveAPI::GoClient
5
+ class Cli
6
+ def self.run
7
+ options = {
8
+ package: 'client',
9
+ }
10
+
11
+ parser = OptionParser.new do |opts|
12
+ opts.banner = "Usage: #{$0} [options] <api url> <destination>"
13
+
14
+ opts.on('--version VERSION', 'Use specified API version') do |v|
15
+ options[:version] = v
16
+ end
17
+
18
+ opts.on('--module MODULE', 'Name of the generated Go module') do |v|
19
+ options[:module] = v
20
+ end
21
+
22
+ opts.on('--package PKG', 'Name of the generated Go package') do |v|
23
+ options[:package] = v
24
+ end
25
+ end
26
+
27
+ parser.parse!
28
+
29
+ if ARGV.length != 2
30
+ warn 'Invalid arguments'
31
+ puts @global_opt.help
32
+ exit(false)
33
+ end
34
+
35
+ g = Generator.new(ARGV[0], ARGV[1], options)
36
+ g.generate
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,46 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+
4
+ module HaveAPI::GoClient
5
+ class ErbTemplate
6
+ def self.render(name, vars)
7
+ t = new(name, vars)
8
+ t.render
9
+ end
10
+
11
+ def self.render_to(name, vars, path)
12
+ File.write("#{path}.new", render(name, vars))
13
+ File.rename("#{path}.new", path)
14
+ end
15
+
16
+ def self.render_to_if_changed(name, vars, path)
17
+ tmp_path = "#{path}.new"
18
+ File.write(tmp_path, render(name, vars))
19
+
20
+ if !File.exist?(path) || !FileUtils.identical?(path, tmp_path)
21
+ File.rename(tmp_path, path)
22
+
23
+ else
24
+ File.unlink(tmp_path)
25
+ end
26
+ end
27
+
28
+ def initialize(name, vars)
29
+ @_tpl = ERB.new(File.new(HaveAPI::GoClient.tpl(name)).read, 0, '-')
30
+
31
+ vars.each do |k, v|
32
+ if v.is_a?(Proc)
33
+ define_singleton_method(k, &v)
34
+ elsif v.is_a?(Method)
35
+ define_singleton_method(k) { |*args| v.call(*args) }
36
+ else
37
+ define_singleton_method(k) { v }
38
+ end
39
+ end
40
+ end
41
+
42
+ def render
43
+ @_tpl.result(binding)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,65 @@
1
+ require 'fileutils'
2
+ require 'haveapi/client'
3
+
4
+ module HaveAPI::GoClient
5
+ class Generator
6
+ # Destination directory
7
+ # @return [String]
8
+ attr_reader :dst
9
+
10
+ # Go module name
11
+ # @return [String]
12
+ attr_reader :module
13
+
14
+ # Go package name
15
+ # @return [String]
16
+ attr_reader :package
17
+
18
+ # @param url [String] API URL
19
+ # @param dst [String] destination directory
20
+ # @param opts [Hash]
21
+ # @option opts [String] :version
22
+ # @option opts [String] :module
23
+ # @option opts [String] :package
24
+ def initialize(url, dst, opts)
25
+ @dst = dst
26
+ @module = opts[:module]
27
+ @package = opts[:package]
28
+
29
+ conn = HaveAPI::Client::Communicator.new(url)
30
+ @api = ApiVersion.new(conn.describe_api(opts[:version]))
31
+ end
32
+
33
+ def generate
34
+ FileUtils.mkpath(dst)
35
+
36
+ if self.module
37
+ ErbTemplate.render_to_if_changed(
38
+ 'go.mod',
39
+ {mod: self.module},
40
+ File.join(dst, 'go.mod')
41
+ )
42
+
43
+ @dst = File.join(dst, package)
44
+ FileUtils.mkpath(dst)
45
+ end
46
+
47
+ %w(client authentication request response types).each do |v|
48
+ ErbTemplate.render_to_if_changed(
49
+ "#{v}.go",
50
+ {
51
+ package: package,
52
+ api: api,
53
+ },
54
+ File.join(dst, "#{v}.go")
55
+ )
56
+ end
57
+
58
+ api.resources.each { |r| r.generate(self) }
59
+ api.auth_methods.each { |v| v.generate(self) }
60
+ end
61
+
62
+ protected
63
+ attr_reader :api
64
+ end
65
+ end
@@ -0,0 +1,48 @@
1
+ require 'haveapi/go_client/utils'
2
+
3
+ module HaveAPI::GoClient
4
+ class InputOutput
5
+ include Utils
6
+
7
+ # @return [Action]
8
+ attr_reader :action
9
+
10
+ # @return [Symbol]
11
+ attr_reader :role
12
+
13
+ # @return [Symbol]
14
+ attr_reader :direction
15
+
16
+ # @return [String]
17
+ attr_reader :layout
18
+
19
+ # @return [String]
20
+ attr_reader :namespace
21
+
22
+ # @return [Array<Parameter>]
23
+ attr_reader :parameters
24
+
25
+ # @return [String]
26
+ attr_reader :go_type
27
+
28
+ # @return [String]
29
+ attr_reader :go_namespace
30
+
31
+ def initialize(action, role, direction, desc, prefix: nil)
32
+ @action = action
33
+ @role = role
34
+ @direction = direction
35
+ @layout = desc[:layout]
36
+ @namespace = desc[:namespace]
37
+ @parameters = desc[:parameters].map do |k, v|
38
+ Parameter.new(role, direction, self, k.to_s, v)
39
+ end.compact
40
+ @go_type = action.go_type + (prefix ? prefix : '') + direction.to_s.capitalize
41
+ @go_namespace = camelize(desc[:namespace])
42
+ end
43
+
44
+ def resolve_associations
45
+ parameters.each { |p| p.resolve }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,63 @@
1
+ module HaveAPI::GoClient
2
+ class Metadata
3
+ class Type
4
+ # @return [InputOutput, nil]
5
+ attr_reader :input
6
+
7
+ # @return [InputOutput, nil]
8
+ attr_reader :output
9
+
10
+ def initialize(action, type, desc)
11
+ @input = desc[:input] && InputOutput.new(
12
+ action,
13
+ :"#{type}_meta",
14
+ :input,
15
+ desc[:input],
16
+ prefix: "Meta#{type.to_s.capitalize}"
17
+ )
18
+ @output = desc[:output] && InputOutput.new(
19
+ action,
20
+ :"#{type}_meta",
21
+ :output,
22
+ desc[:output],
23
+ prefix: "Meta#{type.to_s.capitalize}"
24
+ )
25
+ end
26
+
27
+ def resolve_associations
28
+ input && input.resolve_associations
29
+ output && output.resolve_associations
30
+ end
31
+ end
32
+
33
+ # @return [Type, nil]
34
+ attr_reader :global
35
+
36
+ # @return [Type, nil]
37
+ attr_reader :object
38
+
39
+ def initialize(action, desc)
40
+ @global = desc[:global] && Type.new(action, :global, desc[:global])
41
+ @object = desc[:object] && Type.new(action, :object, desc[:object])
42
+ end
43
+
44
+ def resolve_associations
45
+ global && global.resolve_associations
46
+ object && object.resolve_associations
47
+ end
48
+
49
+ %i(global object).each do |type|
50
+ %i(input output).each do |dir|
51
+ define_method(:"has_#{type}_#{dir}?") do
52
+ t = send(type)
53
+ next(false) unless t
54
+
55
+ io = t.send(dir)
56
+ next(false) unless io
57
+
58
+ io.parameters.any?
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,27 @@
1
+ module HaveAPI::GoClient
2
+ module Parameters ; end
3
+
4
+ module Parameter
5
+ # @param klass [Class]
6
+ # @param block [Proc]
7
+ def self.register(klass, block)
8
+ @handlers ||= []
9
+ @handlers << [klass, block]
10
+ end
11
+
12
+ # @param role [Symbol]
13
+ # @param direction [Symbol]
14
+ # @param io [InputOutput]
15
+ # @param name [String]
16
+ # @param desc [Hash]
17
+ # @return [Parameters::Base, nil]
18
+ def self.new(role, direction, io, name, desc)
19
+ klass, _ =
20
+ @handlers.select do |klass, block|
21
+ block.call(role, direction, name, desc)
22
+ end.first
23
+
24
+ klass ? klass.new(io, name, desc) : nil
25
+ end
26
+ end
27
+ end