ruby-api 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/ruby_api/cli'
5
+ RubyAPI::Cli::Main.start(ARGV)
@@ -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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyAPI
4
+ module Cli
5
+ # RubyAPI Main CLI command
6
+ class Main < Thor
7
+ desc 'hello [MESSAGE]', 'say: Hello MESSAGE'
8
+ def hello(message = 'World')
9
+ puts "Hello #{message}"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module RubAPI
6
+ # RubyAPI CLI Commands
7
+ module Cli
8
+ require_relative 'cli/main'
9
+ end
10
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyAPI
4
+ VERSION = '1.0.1'
5
+ 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: []