ruby-api 1.0.1
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 +7 -0
- data/bin/rapi +5 -0
- data/lib/ruby_api/application.rb +115 -0
- data/lib/ruby_api/cli/main.rb +13 -0
- data/lib/ruby_api/cli.rb +10 -0
- data/lib/ruby_api/config.rb +37 -0
- data/lib/ruby_api/operation.rb +21 -0
- data/lib/ruby_api/request.rb +89 -0
- data/lib/ruby_api/response.rb +61 -0
- data/lib/ruby_api/version.rb +5 -0
- data/lib/ruby_api.rb +36 -0
- metadata +138 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e92e373ca61cca2ea400c3f3f1c36926498e8ae2
|
4
|
+
data.tar.gz: a6f8698555978a338d7820602042461c5c393d58
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3378c144ee95281ab186d290ccb01d2cf96259df32719f7fcee6e7d2edec913079007955178bfc8399b86136e9f2ed11c4f9373f91de1308d15e92cae0da1ca1
|
7
|
+
data.tar.gz: 6b3eabf0941b0a526e96b10b5b8e2935e9d11c4e986b5bf47f531136371839c34d471be818bc1e761a37a6770d177e7e65e4a7a488e9e22ad0252af070b6731e
|
data/bin/rapi
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyAPI
|
4
|
+
# Application
|
5
|
+
class Application
|
6
|
+
APP_MODULE_NAME = 'APP'
|
7
|
+
APP_SOURCE_PATH = 'app/app'
|
8
|
+
|
9
|
+
attr_reader :config, :logger, :routes
|
10
|
+
|
11
|
+
def initialize(cfg = {})
|
12
|
+
@config = load_config(cfg)
|
13
|
+
@routes = load_routes
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_config(cfg)
|
17
|
+
require_hash_or_string(cfg, 'Invalid configuration: %s')
|
18
|
+
cfg = YAML.load_file cfg if cfg.is_a?(String)
|
19
|
+
Config.new(cfg)
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_routes
|
23
|
+
return {} unless config.key?(:routes)
|
24
|
+
|
25
|
+
routes = config.routes
|
26
|
+
require_hash_or_string(routes, 'Invalid routes config: %s')
|
27
|
+
routes = YAML.load_file routes if routes.is_a?(String)
|
28
|
+
@routes = routes.deep_symbolize_keys!
|
29
|
+
end
|
30
|
+
|
31
|
+
def boot
|
32
|
+
@logger = Logger.new log_path[:info]
|
33
|
+
@logger_err = Logger.new log_path[:error]
|
34
|
+
require boot_script if boot_script?
|
35
|
+
require app_path
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def call(env)
|
40
|
+
@env = env
|
41
|
+
response = process Request.new(self, env, routes)
|
42
|
+
return handle_error(response) if response.failure?
|
43
|
+
handle_success(response)
|
44
|
+
rescue StandardError => e
|
45
|
+
handle_exception(e)
|
46
|
+
end
|
47
|
+
|
48
|
+
def app_module
|
49
|
+
Kernel.const_get(app_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def log_path
|
55
|
+
if config.key?(:log)
|
56
|
+
{ info: config.log[:info], error: config.log[:error] }
|
57
|
+
else
|
58
|
+
{ info: $stdout, error: $stderr }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def app_name
|
63
|
+
name = config.app[:name] if config.key?(:app) && config.app[:name]
|
64
|
+
name || APP_MODULE_NAME
|
65
|
+
end
|
66
|
+
|
67
|
+
def app_path
|
68
|
+
return APP_SOURCE_PATH unless config.key?(:app)
|
69
|
+
return APP_SOURCE_PATH unless config.app.key?(:source)
|
70
|
+
config.app[:source]
|
71
|
+
end
|
72
|
+
|
73
|
+
def boot_script
|
74
|
+
config.boot_script
|
75
|
+
end
|
76
|
+
|
77
|
+
def boot_script?
|
78
|
+
return false unless config.key?(:boot_script)
|
79
|
+
path = config.boot_script
|
80
|
+
logger.warn "Boot script not found at: #{path}" unless File.exist?(path)
|
81
|
+
path
|
82
|
+
end
|
83
|
+
|
84
|
+
def process(request)
|
85
|
+
operation = request.operation
|
86
|
+
result = operation.process(self, request)
|
87
|
+
Response.operation(result)
|
88
|
+
end
|
89
|
+
|
90
|
+
def handle_success(response)
|
91
|
+
rack_response response.status, response.headers, response.data
|
92
|
+
end
|
93
|
+
|
94
|
+
def handle_error(response)
|
95
|
+
@logger_err.error response.data
|
96
|
+
rack_response Response::OK, response.headers, response.data
|
97
|
+
end
|
98
|
+
|
99
|
+
def handle_exception(error)
|
100
|
+
@logger_err.error error
|
101
|
+
rack_response Response::FAIL, Response::DEFAULT_HEADERS,
|
102
|
+
Response.exception(error).data
|
103
|
+
end
|
104
|
+
|
105
|
+
def rack_response(status, headers, data)
|
106
|
+
data = [data] unless data.is_a?(Array)
|
107
|
+
[status, headers || {}, data]
|
108
|
+
end
|
109
|
+
|
110
|
+
def require_hash_or_string(object, message = 'Invalid Hash/String: %s')
|
111
|
+
return if object.is_a?(Hash) || object.is_a?(String)
|
112
|
+
raise format(message, object.inspect)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/ruby_api/cli.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyAPI
|
4
|
+
# Config data
|
5
|
+
class Config
|
6
|
+
def self.from_file(path)
|
7
|
+
new(YAML.load_file(path))
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(data)
|
11
|
+
@data = prep_data(data)
|
12
|
+
end
|
13
|
+
|
14
|
+
def merge!(data)
|
15
|
+
@data.merge! prep_data(data)
|
16
|
+
end
|
17
|
+
|
18
|
+
def key?(key)
|
19
|
+
@data.key?(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def respond_to_missing?(name, *)
|
23
|
+
@data.key?(name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(name, *args)
|
27
|
+
return @data[name.to_sym] if respond_to_missing?(name.to_sym)
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def prep_data(hash)
|
34
|
+
hash.deep_symbolize_keys!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyAPI
|
4
|
+
# Operation Error
|
5
|
+
class Operation < Trailblazer::Operation
|
6
|
+
def self.process(app, request)
|
7
|
+
call(request.arguments.merge(logger: app.logger))
|
8
|
+
end
|
9
|
+
|
10
|
+
def result(opts, data)
|
11
|
+
data = { class_key => data } unless data.is_a?(Hash)
|
12
|
+
opts['result'] = data
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def class_key
|
18
|
+
self.class.name.split('::').last.underscore
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
require 'rack/multipart'
|
5
|
+
require 'rack/request'
|
6
|
+
|
7
|
+
module RubyAPI
|
8
|
+
# Request
|
9
|
+
class Request
|
10
|
+
def initialize(app, env, routes = {})
|
11
|
+
@app = app
|
12
|
+
@env = env
|
13
|
+
@routes = routes
|
14
|
+
@rack_req = Rack::Request.new(env)
|
15
|
+
end
|
16
|
+
|
17
|
+
def feature
|
18
|
+
class_name = feature_name.camelize
|
19
|
+
unless app_module.const_defined?(class_name)
|
20
|
+
raise "Feature `#{feature_name}` not found"
|
21
|
+
end
|
22
|
+
app_module.const_get(class_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def operation
|
26
|
+
class_name = operation_name.camelize
|
27
|
+
unless feature.const_defined?(class_name)
|
28
|
+
fname = feature.name.split('::').last.underscore
|
29
|
+
raise "Operation `#{fname}/#{operation_name}` not found"
|
30
|
+
end
|
31
|
+
feature.const_get class_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def arguments
|
35
|
+
@rack_req.params.merge json_arguments
|
36
|
+
end
|
37
|
+
|
38
|
+
def json_arguments
|
39
|
+
JSON.parse @rack_req.body.read
|
40
|
+
rescue JSON::ParserError
|
41
|
+
{}
|
42
|
+
end
|
43
|
+
|
44
|
+
def app_module
|
45
|
+
@app.app_module
|
46
|
+
end
|
47
|
+
|
48
|
+
def root_route
|
49
|
+
@routes[:root]
|
50
|
+
end
|
51
|
+
|
52
|
+
def root_feature
|
53
|
+
root_route.split('/')[0].to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
def root_operation
|
57
|
+
root_route.split('/')[1].to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
def feature_name
|
61
|
+
slices[0].to_s.empty? ? root_feature : slices[0].to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
def operation_name
|
65
|
+
slices[1].to_s.empty? ? root_operation : slices[1].to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
def slices
|
69
|
+
return route_slices(path) if routes?(path)
|
70
|
+
path.split('/')
|
71
|
+
end
|
72
|
+
|
73
|
+
def path
|
74
|
+
@env['REQUEST_PATH'].to_s.gsub(%r{^\/}, '')
|
75
|
+
end
|
76
|
+
|
77
|
+
def routes?(path)
|
78
|
+
@routes.key?(path.to_sym)
|
79
|
+
end
|
80
|
+
|
81
|
+
def route_slices(path)
|
82
|
+
@routes[path.to_sym].split('/')
|
83
|
+
end
|
84
|
+
|
85
|
+
def method
|
86
|
+
@env['REQUEST_METHOD'].to_s.downcase
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyAPI
|
4
|
+
# Response
|
5
|
+
class Response
|
6
|
+
FAIL = 500
|
7
|
+
OK = 200
|
8
|
+
|
9
|
+
DEFAULT_HEADERS = {
|
10
|
+
'Content-Type' => 'application/json; charset=utf-8'
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def error(data)
|
15
|
+
new(data, error: true)
|
16
|
+
end
|
17
|
+
|
18
|
+
def exception(err)
|
19
|
+
data = { message: err.message }
|
20
|
+
data[:backtrace] = err.backtrace if RubyAPI.development?
|
21
|
+
new(data, error: true)
|
22
|
+
end
|
23
|
+
|
24
|
+
def ok(data)
|
25
|
+
new(data)
|
26
|
+
end
|
27
|
+
|
28
|
+
def operation(result)
|
29
|
+
return new(result['errors'], error: true) if result.failure?
|
30
|
+
new(result['result'])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def status
|
35
|
+
success? ? OK : (@status || FAIL)
|
36
|
+
end
|
37
|
+
|
38
|
+
def headers
|
39
|
+
@headers || DEFAULT_HEADERS
|
40
|
+
end
|
41
|
+
|
42
|
+
def failure?
|
43
|
+
@error
|
44
|
+
end
|
45
|
+
|
46
|
+
def success?
|
47
|
+
!failure?
|
48
|
+
end
|
49
|
+
|
50
|
+
def data
|
51
|
+
@data.is_a?(String) ? @data : @data.to_json
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def initialize(data, error: false)
|
57
|
+
@data = data
|
58
|
+
@error = error ? true : false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/ruby_api.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'bundler'
|
6
|
+
require 'trailblazer/operation'
|
7
|
+
require 'active_support/core_ext/string'
|
8
|
+
require 'dry-validation'
|
9
|
+
require 'dry-struct'
|
10
|
+
|
11
|
+
# RubyAPI
|
12
|
+
module RubyAPI
|
13
|
+
require_relative 'ruby_api/version'
|
14
|
+
require_relative 'ruby_api/config'
|
15
|
+
require_relative 'ruby_api/operation'
|
16
|
+
require_relative 'ruby_api/response'
|
17
|
+
require_relative 'ruby_api/request'
|
18
|
+
require_relative 'ruby_api/application'
|
19
|
+
|
20
|
+
CONFIG_DIR = 'config'
|
21
|
+
BOOT_SCRIPT = 'boot'
|
22
|
+
|
23
|
+
def self.application(config = nil)
|
24
|
+
return @app if @app
|
25
|
+
@app = Application.new(config)
|
26
|
+
.boot
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.environment
|
30
|
+
ENV['RACK_ENV'] || 'development'
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.development?
|
34
|
+
%w[development test].include? environment
|
35
|
+
end
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Voislav Jovanovic
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-07-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dry-struct
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: dry-validation
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.12'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.12'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: json
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.1'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: thor
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.20'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.20'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: trailblazer-operation
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.4'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.4'
|
97
|
+
description: ruby api framework
|
98
|
+
email: voislavj@gmail.com
|
99
|
+
executables:
|
100
|
+
- rapi
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- bin/rapi
|
105
|
+
- lib/ruby_api.rb
|
106
|
+
- lib/ruby_api/application.rb
|
107
|
+
- lib/ruby_api/cli.rb
|
108
|
+
- lib/ruby_api/cli/main.rb
|
109
|
+
- lib/ruby_api/config.rb
|
110
|
+
- lib/ruby_api/operation.rb
|
111
|
+
- lib/ruby_api/request.rb
|
112
|
+
- lib/ruby_api/response.rb
|
113
|
+
- lib/ruby_api/version.rb
|
114
|
+
homepage: http://bitbucket.org/voislavj/ruby-api
|
115
|
+
licenses:
|
116
|
+
- MIT
|
117
|
+
metadata: {}
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
requirements: []
|
133
|
+
rubyforge_project:
|
134
|
+
rubygems_version: 2.6.14.1
|
135
|
+
signing_key:
|
136
|
+
specification_version: 4
|
137
|
+
summary: summary
|
138
|
+
test_files: []
|