oven 0.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ # frozen-string-literal: true
2
+ module Oven
3
+ module Patches
4
+ module StringExt
5
+ refine String do
6
+ def underscore
7
+ return self unless self =~ /[A-Z-]|::/
8
+ word = gsub('::', '/')
9
+ word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{/(?=a)b/})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" }
10
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
11
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
12
+ word.tr!("-", "_")
13
+ word.downcase!
14
+ word
15
+ end
16
+
17
+ def deconstantize
18
+ self[0, rindex('::') || 0]
19
+ end
20
+
21
+ def demodulize
22
+ (i = rindex('::')) ? self[(i+2)..-1] : self
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,82 @@
1
+ # frozen-string-literal: true
2
+ require 'net/http'
3
+ <%- requires.each do |_require| -%>
4
+ require '<%= File.join(name_declaration.select(&:namespace?).map(&:underscore) << _require) %>'
5
+ <%- end -%>
6
+
7
+ <%- name_declaration.each do |name| -%>
8
+ <%= name.type %> <%= name %>
9
+ <%- end -%>
10
+ attr_reader :domain, :proxy_addr, :proxy_port, :proxy_user, :proxy_password
11
+
12
+ def initialize(domain, proxy_addr: nil, proxy_port: nil, proxy_user: nil, proxy_password: nil, **options)
13
+ @domain, @proxy_addr, @proxy_port, @proxy_user, @proxy_password, @interceptors, @observers, @options =
14
+ domain, proxy_addr, proxy_port, proxy_user, proxy_password, [], [], DEFAULT_OPTIONS.merge(options)
15
+
16
+ <%- interceptors.each do |interceptor| -%>
17
+ register_interceptor(<%= interceptor %>.new)
18
+ <%- end -%>
19
+ <%- observers.each do |observer| -%>
20
+ register_observer(<%= observer %>.new)
21
+ <%- end -%>
22
+ end
23
+
24
+ def register_interceptor(interceptor)
25
+ @interceptors << interceptor
26
+ self
27
+ end
28
+
29
+ def register_observer(observer)
30
+ @observers << observer
31
+ self
32
+ end
33
+ <%- method_definitions.each do |definition| %>
34
+ def <%= definition.method_name %>(<%= definition.parameter_signature %><%= ', ' if !definition.parameters.empty? %>query: {}, headers: {}, **options)
35
+ request(Net::HTTP::<%= definition.verb.to_s.capitalize %>, uri("<%= definition.path %>", query), <%= definition.variable_name_for_body %>, headers, method: :<%= definition.method_name %>, **options)
36
+ end
37
+ <% definition.aliases.each {|name| %>alias <%= name %> <%= definition.method_name %><% } %>
38
+ <%- end %>
39
+ private
40
+
41
+ DEFAULT_OPTIONS = {
42
+ ca_file: nil,
43
+ ca_path: nil,
44
+ cert: nil,
45
+ cert_store: nil,
46
+ ciphers: nil,
47
+ close_on_empty_response: nil,
48
+ key: nil,
49
+ open_timeout: nil,
50
+ read_timeout: nil,
51
+ ssl_timeout: nil,
52
+ ssl_version: nil,
53
+ use_ssl: nil,
54
+ verify_callback: nil,
55
+ verify_depth: nil,
56
+ verify_mode: nil
57
+ }.freeze
58
+
59
+ HTTPS = 'https'.freeze
60
+
61
+ def request(request_class, uri, body, headers, options = {})
62
+ uri, body, headers, options = @interceptors.reduce([uri, body, headers, @options.merge(options)]) {|r, i| i.before_request(*r) }
63
+
64
+ begin
65
+ response = Net::HTTP.start(uri.host, uri.port, proxy_addr, proxy_port, proxy_user, proxy_password, options, use_ssl: (uri.scheme == HTTPS)) do |http|
66
+ http.request request_class.new(uri, headers), body
67
+ end
68
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
69
+ raise NetworkError, "A network error occurred: #{e.class} (#{e.message})"
70
+ end
71
+
72
+ @observers.reduce(response) {|r, o| o.received_response(r, options) }
73
+ end
74
+
75
+ def uri(path, query = {})
76
+ uri = URI.join(domain, path)
77
+ uri.query = URI.encode_www_form(query) if !query.empty?
78
+ uri
79
+ end
80
+ <%- name_declaration.each do -%>
81
+ end
82
+ <%- end -%>
@@ -0,0 +1,45 @@
1
+ <%- name_declaration.namespace_or_client_name.each do |name| -%>
2
+ <%= name.type %> <%= name %>
3
+ <%- end -%>
4
+ class APIError < StandardError; end
5
+ class NetworkError < APIError; end
6
+
7
+ class HttpError < APIError
8
+ attr_reader :response
9
+
10
+ def initialize(message, response)
11
+ super(message)
12
+ @response = response
13
+ end
14
+ end
15
+
16
+ class ClientError < HttpError; end
17
+
18
+ <%- CLIENT_ERROR_STATUSES.each do |status, error_name| -%>
19
+ class <%= error_name.tr(" ", "").ljust(27) %> < ClientError; end # status: <%= status %>
20
+ <%- end -%>
21
+
22
+ class ServerError < HttpError; end
23
+
24
+ <%- SERVER_ERROR_STATUSES.each do |status, error_name| -%>
25
+ class <%= error_name.tr(" ", "").ljust(29) %> < ServerError; end # status: <%= status %>
26
+ <%- end -%>
27
+
28
+ STATUS_TO_EXCEPTION_MAPPING = {
29
+ <%- CLIENT_ERROR_STATUSES.merge(SERVER_ERROR_STATUSES).each do |status, error| -%>
30
+ '<%= status %>' => <%= error.tr(" ", "") %>,
31
+ <%- end -%>
32
+ }.freeze
33
+
34
+ class ResponseHandler
35
+ def received_response(response, options)
36
+ error = STATUS_TO_EXCEPTION_MAPPING[response.code]
37
+ raise error.new("Receieved an error response #{response.code} #{error.to_s.split("::").last}: #{response.body}", response) if error
38
+ response
39
+ end
40
+ end
41
+
42
+ private_constant :ResponseHandler
43
+ <%- name_declaration.namespace_or_client_name.each do -%>
44
+ end
45
+ <%- end -%>
@@ -0,0 +1,47 @@
1
+ require 'json'
2
+ require 'delegate'
3
+
4
+ <%- name_declaration.namespace_or_client_name.each do |name| -%>
5
+ <%= name.type %> <%= name %>
6
+ <%- end -%>
7
+ class JsonResponse < DelegateClass(Net::HTTPResponse)
8
+ alias response __getobj__
9
+ alias headers to_hash
10
+ HAS_SYMBOL_GC = RUBY_VERSION > '2.2.0'
11
+
12
+ def json
13
+ parsable? ? JSON.parse(body, symbolize_names: HAS_SYMBOL_GC) : nil
14
+ end
15
+
16
+ def inspect
17
+ "#<JsonResponse response: #{response.inspect}, json: #{json}>"
18
+ end
19
+ alias to_s inspect
20
+
21
+ def parsable?
22
+ !!body && !body.empty?
23
+ end
24
+ end
25
+
26
+ class JsonSerializer
27
+ APPLICATION_JSON = 'application/json'.freeze
28
+ JSON_REQUEST_HEADERS = {
29
+ 'Content-Type' => APPLICATION_JSON,
30
+ 'Accept' => APPLICATION_JSON
31
+ }.freeze
32
+
33
+ def before_request(uri, body, headers, options)
34
+ [uri, (body.nil? ? body : JSON.dump(body)), headers.merge(JSON_REQUEST_HEADERS), options]
35
+ end
36
+ end
37
+
38
+ class JsonDeserializer
39
+ def received_response(response, options)
40
+ JsonResponse.new(response)
41
+ end
42
+ end
43
+
44
+ private_constant :JsonSerializer, :JsonDeserializer
45
+ <%- name_declaration.namespace_or_client_name.each do -%>
46
+ end
47
+ <%- end -%>
@@ -0,0 +1,21 @@
1
+ <%- extension.class_names.each do |class_name| -%>
2
+ require '<%= File.join(name_declaration.select(&:namespace?).map(&:underscore) << 'models' << class_name.underscore) %>'
3
+ <%- end -%>
4
+
5
+ <%- name_declaration.namespace_or_client_name.each do |name| -%>
6
+ <%= name.type %> <%= name %>
7
+ <%- end -%>
8
+ OBJECT_MAP = {
9
+ <%- extension.object_mapping.each do |class_name, attrs| -%>
10
+ <%= class_name %> => {
11
+ <%- attrs.each do |attr_name, attr_class| -%>
12
+ <%- if !extension.primitive_classes.include?(attr_class) -%>
13
+ <%= attr_name %>: <%= attr_class %>,
14
+ <%- end -%>
15
+ <%- end -%>
16
+ },
17
+ <%- end -%>
18
+ }
19
+ <%- name_declaration.namespace_or_client_name.each do -%>
20
+ end
21
+ <%- end -%>
@@ -0,0 +1,39 @@
1
+ require 'object_mapper'
2
+
3
+ <%- name_declaration.namespace_or_client_name.each do |name| -%>
4
+ <%= name.type %> <%= name %>
5
+ <%- end -%>
6
+ class ObjectConverter
7
+ METHOD_TO_CLASS_MAP = {
8
+ <%- extension.method_definitions.select {|d| d.options.key?(:class) }.each do |definition| -%>
9
+ <%= definition.method_name %>: <%= definition.options.fetch(:class, Hash) %>,
10
+ <%- end -%>
11
+ }
12
+
13
+ def received_response(response, method: nil, **)
14
+ if method && response.respond_to?(:parsable?) && response.parsable? && METHOD_TO_CLASS_MAP[method]
15
+ ObjectResponse.new(response, METHOD_TO_CLASS_MAP[method])
16
+ else
17
+ response
18
+ end
19
+ end
20
+ end
21
+
22
+ class ObjectResponse < DelegateClass(JsonResponse)
23
+ OBJECT_MAPPER = ::ObjectMapper.new(OBJECT_MAP)
24
+
25
+ def initialize(response, target_class)
26
+ super(response)
27
+ @target_class = target_class
28
+ end
29
+
30
+ def object
31
+ OBJECT_MAPPER.convert(json, to: @target_class)
32
+ end
33
+ alias body_as_object object
34
+ end
35
+
36
+ private_constant :ObjectConverter, :ObjectResponse
37
+ <%- name_declaration.namespace_or_client_name.each do -%>
38
+ end
39
+ <%- end -%>
@@ -0,0 +1,13 @@
1
+ <%- name_declaration.namespace_or_client_name.each do |name| -%>
2
+ <%= name.type %> <%= name %>
3
+ <%- end -%>
4
+ class <%= class_name %>
5
+ attr <%= attributes.map{|attr| ":#{attr}" }.join(', ') %>
6
+
7
+ def initialize(<%= attributes.map{|attr| "#{attr}: nil" }.join(', ') %>)
8
+ <%= attributes.map{|attr| "@#{attr}" }.join(", ") %> = <%= attributes.map{|attr| reserved_keyword_map.fetch(attr.to_s, attr) }.join(", ") %>
9
+ end
10
+ end
11
+ <%- name_declaration.namespace_or_client_name.each do -%>
12
+ end
13
+ <%- end -%>
@@ -0,0 +1,3 @@
1
+ module Oven
2
+ VERSION = "0.1.0.rc1"
3
+ end
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'oven/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "oven"
8
+ spec.version = Oven::VERSION
9
+ spec.authors = ["Yuki Nishijima"]
10
+ spec.email = ["mail@yukinishijima.net"]
11
+ spec.summary = %q{Static API client generator}
12
+ spec.description = %q{Oven generates a dependency-free HTTP client.}
13
+ spec.homepage = "https://github.com/yuki24/oven"
14
+ spec.license = "MIT"
15
+ spec.files = `git ls-files -z`.split("\x0").reject {|f| f.match(%r{^(test)/}) }
16
+ spec.require_paths = ["lib"]
17
+
18
+ spec.add_development_dependency "bundler"
19
+ spec.add_development_dependency "rake"
20
+ spec.add_development_dependency "minitest"
21
+ spec.add_development_dependency "webmock"
22
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oven
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.rc1
5
+ platform: ruby
6
+ authors:
7
+ - Yuki Nishijima
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
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: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Oven generates a dependency-free HTTP client.
70
+ email:
71
+ - mail@yukinishijima.net
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".travis.yml"
78
+ - CODE_OF_CONDUCT.md
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - bin/console
84
+ - bin/setup
85
+ - lib/oven.rb
86
+ - lib/oven/builders.rb
87
+ - lib/oven/dsl_context.rb
88
+ - lib/oven/extension_configurers.rb
89
+ - lib/oven/http_statuses.rb
90
+ - lib/oven/path_parser.rb
91
+ - lib/oven/path_parser.y
92
+ - lib/oven/path_parser/nodes.rb
93
+ - lib/oven/path_parser/parser_extension.rb
94
+ - lib/oven/path_parser/path_ast.rb
95
+ - lib/oven/path_parser/scanner.rb
96
+ - lib/oven/refinements.rb
97
+ - lib/oven/templates/client.rb.erb
98
+ - lib/oven/templates/exceptions.rb.erb
99
+ - lib/oven/templates/json.rb.erb
100
+ - lib/oven/templates/models.rb.erb
101
+ - lib/oven/templates/object_mapping.rb.erb
102
+ - lib/oven/templates/poro.rb.erb
103
+ - lib/oven/version.rb
104
+ - oven.gemspec
105
+ - tmp/.gitkeep
106
+ homepage: https://github.com/yuki24/oven
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">"
122
+ - !ruby/object:Gem::Version
123
+ version: 1.3.1
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.6.8
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Static API client generator
130
+ test_files: []