Class: Joshua
- Inherits:
-
Object
- Object
- Joshua
- Defined in:
- lib/doc/special.rb,
lib/doc/doc.rb,
lib/joshua/response.rb,
lib/joshua/base_class.rb,
lib/joshua/render_proxy.rb,
lib/joshua/base_instance.rb
Overview
Proxy class for simplified more user friendly render
UserApi.render.login(123, foo: ‘bar’) -> UserApi.render :login, id: 133, params: { foo: ‘bar’ }
spec/tests/proxy_spec.rb UserApi.render.login(user: ‘foo’, pass: ‘bar’) CompanyApi.render.show(1)
Defined Under Namespace
Modules: Doc Classes: DocSpecial, Error, RenderProxy, Response
Constant Summary collapse
- @@after_auto_mount =
nil
- @@opts =
{}
Instance Attribute Summary collapse
-
#api ⇒ Object
readonly
Returns the value of attribute api.
Class Method Summary collapse
-
.after(&block) ⇒ Object
block execute after any public method or just some member or collection methods used to add meta tags to response.
- .after_auto_mount(&blok) ⇒ Object
- .allow(type) ⇒ Object
-
.annotation(name, &block) ⇒ Object
define method annotations annotation :unsecure! do @is_unsecure = true end unsecure! def login …
- .api_path ⇒ Object
-
.auto_mount(api_host:, mount_on: nil, bearer: nil, development: false) ⇒ Object
ApplicationApi.auto_mount request: request, response: response, mount_on: ‘/api’, development: true auto mount to a root * display doc in a root * call methods if possible /api/v1.comapny/1/show.
-
.before(&block) ⇒ Object
block execute before any public method or just some member or collection methods.
-
.call(env) ⇒ Object
perform auto_mount from a rake call.
-
.collection(&block) ⇒ Object
(also: collections)
/api/companies/list?countrty_id=1.
-
.define(name, &block) ⇒ Object
aleternative way to define a api function members do define :foo do params {} proc {} end end.
-
.desc(data) ⇒ Object
api method description.
-
.detail(data) ⇒ Object
api method detailed description.
-
.documented ⇒ Object
if you want to make API DOC public use “documented”.
-
.error(desc) ⇒ Object
class errors, raised by params validation.
- .error_print(error) ⇒ Object
- .get(*args) ⇒ Object
-
.gettable ⇒ Object
method in available for GET requests as well.
-
.icon(data) ⇒ Object
api method icon you can find great icons at boxicons.com/ - export to svg.
-
.member(&block) ⇒ Object
(also: members)
/api/companies/1/show.
-
.method_added(name) ⇒ Object
here we capture member & collection metods.
-
.model(name, &block) ⇒ Object
propagate to typero.
-
.mount_on(what) ⇒ Object
sets api mount point mount_on ‘/api’.
-
.opts ⇒ Object
dig all options for a current class.
-
.params(&block) ⇒ Object
params do name? String email :email end.
-
.plugin(name, &block) ⇒ Object
simplified module include, masked as plugin Joshua.plugin :foo do …
-
.render(action = nil, opts = {}) ⇒ Object
renders api doc or calls api class + action.
-
.rescue_from(klass, desc = nil, &block) ⇒ Object
rescue_from CustomError do …
-
.response_error(text) ⇒ Object
show and render single error in class error format usually when API class not found.
-
.unsafe ⇒ Object
allow methods without @api.bearer token set.
Instance Method Summary collapse
- #execute_call ⇒ Object
-
#initialize(action, params: {}, opts: {}, development: false, id: nil, bearer: nil, api_host: nil) ⇒ Joshua
constructor
A new instance of Joshua.
- #to_h ⇒ Object
- #to_json ⇒ Object
Constructor Details
#initialize(action, params: {}, opts: {}, development: false, id: nil, bearer: nil, api_host: nil) ⇒ Joshua
Returns a new instance of Joshua.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/joshua/base_instance.rb', line 27 def initialize action, params: {}, opts: {}, development: false, id: nil, bearer: nil, api_host: nil @api = INSTANCE.new if action.is_a?(Array) # unpack id and action is action is given in path form # [123, :show] @api.id, @api.action = action[1] ? action : [nil, action[0]] else @api.action = action end @api.bearer = bearer @api.id ||= id @api.action = @api.action.to_sym @api.request = api_host ? api_host.request : nil @api.method_opts = self.class.opts.dig(@api.id ? :member : :collection, @api.action) || {} @api.development = !!development @api.params = HashWia.new params @api.opts = HashWia.new opts @api.api_host = api_host @api.response = ::Joshua::Response.new @api end |
Instance Attribute Details
#api ⇒ Object (readonly)
Returns the value of attribute api.
25 26 27 |
# File 'lib/joshua/base_instance.rb', line 25 def api @api end |
Class Method Details
.after(&block) ⇒ Object
block execute after any public method or just some member or collection methods used to add meta tags to response
331 332 333 |
# File 'lib/joshua/base_class.rb', line 331 def after &block set_callback :after, block end |
.after_auto_mount(&blok) ⇒ Object
159 160 161 |
# File 'lib/joshua/base_class.rb', line 159 def after_auto_mount &blok @@after_auto_mount = blok end |
.allow(type) ⇒ Object
298 299 300 301 302 303 304 |
# File 'lib/joshua/base_class.rb', line 298 def allow type if @method_type @@opts[:allow] = type else raise ArgumentError.new('allow can only be set on methods') end end |
.annotation(name, &block) ⇒ Object
define method annotations annotation :unsecure! do
@is_unsecure = true
end unsecure! def login
...
212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/joshua/base_class.rb', line 212 def annotation name, &block ANNOTATIONS[name] = block self.define_singleton_method name do |*args| unless @method_type error 'Annotation "%s" defined outside the API method blocks (member & collections)' % name end @@opts[:annotations] ||= {} @@opts[:annotations][name] = args end end |
.api_path ⇒ Object
201 202 203 |
# File 'lib/joshua/base_class.rb', line 201 def api_path to_s.underscore.sub(/_api$/, '') end |
.auto_mount(api_host:, mount_on: nil, bearer: nil, development: false) ⇒ Object
ApplicationApi.auto_mount request: request, response: response, mount_on: ‘/api’, development: true auto mount to a root
-
display doc in a root
-
call methods if possible /api/v1.comapny/1/show
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/joshua/base_class.rb', line 40 def auto_mount api_host:, mount_on: nil, bearer: nil, development: false request = api_host.request response = api_host.response mount_on ||= OPTS[:api][:mount_on] || '/' mount_on = [request.base_url, mount_on].join('') unless mount_on.to_s.include?('//') if request.url == mount_on && request.request_method == 'GET' response.header['Content-Type'] = 'text/html' if response Doc.render request: request, bearer: bearer else response.header['Content-Type'] = 'application/json' if response body = request.body.read.to_s body = body[0] == '{' ? JSON.parse(body) : nil # class: klass, params: params, bearer: bearer, request: request, response: response, development: development opts = {} opts[:api_host] = api_host opts[:development] = development opts[:bearer] = bearer action = if body # { # "id": 'foo', # unique ID that will be returned, as required by JSON RPC spec # "class": 'v1/users', # v1/users => V1::UsersApi # "action": 'index', # "index' or "6/info" or [6, "info"] # "token": 'ab12ef', # api_token (bearer) # "params": {} # methos params # } opts[:params] = body['params'] || {} opts[:bearer] = body['token'] if body['token'] opts[:class] = body['class'] body['action'] else opts[:params] = request.params || {} opts[:bearer] = opts[:params][:api_token] if opts[:params][:api_token] mount_on = mount_on+'/' unless mount_on.end_with?('/') path = request.url.split(mount_on, 2).last.split('?').first.to_s parts = path.split('/') @@after_auto_mount.call parts, opts if @@after_auto_mount opts[:class] = parts.shift parts end opts[:bearer] ||= request.env['HTTP_AUTHORIZATION'].to_s.split('Bearer ')[1] api_response = render action, **opts if api_response.is_a?(Hash) response.status = api_response[:status] if response api_response.to_h else api_response end end end |
.before(&block) ⇒ Object
block execute before any public method or just some member or collection methods
325 326 327 |
# File 'lib/joshua/base_class.rb', line 325 def before &block set_callback :before, block end |
.call(env) ⇒ Object
perform auto_mount from a rake call
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/joshua/base_class.rb', line 7 def call env request = Rack::Request.new env if request.path == '/favicon.ico' [ 200, { 'Cache-Control'=>'public; max-age=1000000' }, [Doc.misc_file('favicon.png')] ] else data = auto_mount request: request, development: ENV['RACK_ENV'] == 'development' if data.is_a?(Hash) [ data[:status] || 200, { 'Content-Type' => 'application/json', 'Cache-Control'=>'private; max-age=0' }, [data.to_json] ] else data = data.to_s [ 200, { 'Content-Type' => 'text/html', 'Cache-Control'=>'public; max-age=3600' }, [data] ] end end end |
.collection(&block) ⇒ Object Also known as: collections
/api/companies/list?countrty_id=1
250 251 252 253 254 |
# File 'lib/joshua/base_class.rb', line 250 def collection &block @method_type = :collection class_exec &block @method_type = nil end |
.define(name, &block) ⇒ Object
aleternative way to define a api function members do
define :foo do
params {}
proc {}
end
end
231 232 233 234 235 236 237 238 239 |
# File 'lib/joshua/base_class.rb', line 231 def define name, &block func = class_exec &block if func.is_a?(Proc) self.define_method(name, func) else raise 'Member block has to return a Func object' end end |
.desc(data) ⇒ Object
api method description
279 280 281 282 283 284 285 |
# File 'lib/joshua/base_class.rb', line 279 def desc data if @method_type @@opts[:desc] = data else set :opts, :desc, data end end |
.detail(data) ⇒ Object
api method detailed description
288 289 290 291 292 293 294 295 296 |
# File 'lib/joshua/base_class.rb', line 288 def detail data return if data.to_s == '' if @method_type @@opts[:detail] = data else set :opts, :detail, data end end |
.documented ⇒ Object
if you want to make API DOC public use “documented”
193 194 195 196 197 198 199 |
# File 'lib/joshua/base_class.rb', line 193 def documented if self == Joshua DOCUMENTED.map(&:to_s).sort.map(&:constantize) else DOCUMENTED.push self unless DOCUMENTED.include?(self) end end |
.error(desc) ⇒ Object
class errors, raised by params validation
172 173 174 |
# File 'lib/joshua/base_class.rb', line 172 def error desc raise Joshua::Error, desc end |
.error_print(error) ⇒ Object
176 177 178 179 180 181 182 183 184 |
# File 'lib/joshua/base_class.rb', line 176 def error_print error puts puts 'Joshua error dump'.red puts '---' puts '%s: %s' % [error.class, error.] puts '---' puts error.backtrace puts '---' end |
.get(*args) ⇒ Object
350 351 352 |
# File 'lib/joshua/base_class.rb', line 350 def get *args opts.dig *args end |
.gettable ⇒ Object
method in available for GET requests as well
307 308 309 310 311 312 313 |
# File 'lib/joshua/base_class.rb', line 307 def gettable if @method_type @@opts[:gettable] = true else raise ArgumentError.new('gettable can only be set on methods') end end |
.icon(data) ⇒ Object
api method icon you can find great icons at boxicons.com/ - export to svg
270 271 272 273 274 275 276 |
# File 'lib/joshua/base_class.rb', line 270 def icon data if @method_type raise ArgumentError.new('Icons cant be added on methods') else set :opts, :icon, data end end |
.member(&block) ⇒ Object Also known as: members
/api/companies/1/show
242 243 244 245 246 |
# File 'lib/joshua/base_class.rb', line 242 def member &block @method_type = :member func = class_exec &block @method_type = nil end |
.method_added(name) ⇒ Object
here we capture member & collection metods
381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/joshua/base_class.rb', line 381 def method_added name return if name.to_s.start_with?('_api_') return unless @method_type set @method_type, name, @@opts @@opts = {} alias_method "_api_#{@method_type}_#{name}", name remove_method name end |
.model(name, &block) ⇒ Object
propagate to typero
376 377 378 |
# File 'lib/joshua/base_class.rb', line 376 def model name, &block Typero.schema name, &block end |
.mount_on(what) ⇒ Object
sets api mount point mount_on ‘/api’
188 189 190 |
# File 'lib/joshua/base_class.rb', line 188 def mount_on what OPTS[:api][:mount_on] = what end |
.opts ⇒ Object
dig all options for a current class
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
# File 'lib/joshua/base_class.rb', line 355 def opts out = {} # dig down the ancestors tree till Object class ancestors.each do |klass| break if klass == Object # copy all member and collection method options keys = (OPTS[klass.to_s] || {}).keys keys.each do |type| for k, v in (OPTS.dig(klass.to_s, type) || {}) out[type] ||= {} out[type][k] ||= v end end end out end |
.params(&block) ⇒ Object
261 262 263 264 265 266 |
# File 'lib/joshua/base_class.rb', line 261 def params &block raise ArgumentError.new('Block not given for Joshua API method params') unless block_given? @@opts[:_typero] = Typero.schema &block @@opts[:params] = @@opts[:_typero].to_h end |
.plugin(name, &block) ⇒ Object
simplified module include, masked as plugin Joshua.plugin :foo do … Joshua.plugin :foo
338 339 340 341 342 343 344 345 346 347 348 |
# File 'lib/joshua/base_class.rb', line 338 def plugin name, &block if block_given? # if block given, define a plugin PLUGINS[name] = block else # without a block execute it blk = PLUGINS[name] raise ArgumentError.new('Plugin :%s not defined' % name) unless blk instance_exec &blk end end |
.render(action = nil, opts = {}) ⇒ Object
renders api doc or calls api class + action
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/joshua/base_class.rb', line 105 def render action=nil, opts={} if action return error 'Action not defined' unless action[0] else return RenderProxy.new self end api_class = if klass = opts.delete(:class) # /api/_/foo if klass == '_' klass = Joshua::DocSpecial.new(opts) if klass.respond_to?(action.first) return klass.send action.first.to_sym else return error 'Action %s not defined' % action.first end end klass = klass.split('/') if klass.is_a?(String) klass[klass.length-1] += '_api' begin klass.join('/').classify.constantize rescue NameError => e return error 'API class "%s" not found' % klass end else self end api = api_class.new action, **opts api.execute_call rescue => error error_print error if opts[:development] Response.auto_format error end |
.rescue_from(klass, desc = nil, &block) ⇒ Object
rescue_from CustomError do … for unhandled rescue_from :all do
api.error 500, 'Error happens'
end define handled error code and description error :not_found, ‘Document not found’ error 404, ‘Document not found’ in api methods error 404 error :not_found
155 156 157 |
# File 'lib/joshua/base_class.rb', line 155 def rescue_from klass, desc=nil, &block RESCUE_FROM[klass] = desc || block end |
.response_error(text) ⇒ Object
show and render single error in class error format usually when API class not found
165 166 167 168 169 |
# File 'lib/joshua/base_class.rb', line 165 def response_error text out = Response.new nil out.error text out.render end |
.unsafe ⇒ Object
allow methods without @api.bearer token set
316 317 318 319 320 321 322 |
# File 'lib/joshua/base_class.rb', line 316 def unsafe if @method_type @@opts[:unsafe] = true else raise ArgumentError.new('Only api methods can be unsafe') end end |
Instance Method Details
#execute_call ⇒ Object
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/joshua/base_instance.rb', line 49 def execute_call if !@api.development && @api.request && @api.request.request_method == 'GET' && !@api.method_opts[:gettable] response.error 'GET request is not allowed' else begin parse_api_params parse_annotations unless response.error? resolve_api_body unless response.error? rescue Joshua::Error => error # controlled error raised via error "message", ignore response.error error. rescue => error # uncontrolled error, should be logged Joshua.error_print error if @api.development block = RESCUE_FROM[error.class] || RESCUE_FROM[:all] if block instance_exec error, &block else response.error error., status: 500 end end # we execute generic after block in case of error or no execute_callback :after_all end @api.raw || response.render end |
#to_h ⇒ Object
84 85 86 |
# File 'lib/joshua/base_instance.rb', line 84 def to_h execute_call end |
#to_json ⇒ Object
80 81 82 |
# File 'lib/joshua/base_instance.rb', line 80 def to_json execute_call.to_json end |