haveapi-go-client 0.13.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.
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