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 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: []