ruby-api 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|