Class: Joshua
- Inherits:
-
Object
- Object
- Joshua
- Defined in:
- lib/joshua/response.rb,
lib/doc/doc.rb,
lib/joshua/base_class.rb,
lib/doc/postman_schema.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: Error, PostmanSchema, 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
allow alternative method access allow :get if defined, access will be allowed via POST + allowed method.
-
.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 = nil) ⇒ Object
perform auto_mount from a rake call.
-
.collection(&block) ⇒ Object
(also: collections)
/api/companies/list?countrty_id=1.
-
.content_type(name) ⇒ Object
define response content type (defaults to JSON).
-
.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
-
.icon(data) ⇒ Object
api method icon you can find great icons at boxicons.com/ - export to svg.
- .make_hash_html_safe(hash) ⇒ Object
-
.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.
- .render_data(action, opts = {}) ⇒ Object
-
.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, html_safe: true) ⇒ 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, html_safe: true) ⇒ 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 48 49 50 51 |
# File 'lib/joshua/base_instance.rb', line 27 def initialize action, params: {}, opts: {}, development: false, id: nil, bearer: nil, api_host: nil, html_safe: true @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 if html_safe params = Joshua.make_hash_html_safe params 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
356 357 358 |
# File 'lib/joshua/base_class.rb', line 356 def after &block set_callback :after, block end |
.after_auto_mount(&blok) ⇒ Object
168 169 170 |
# File 'lib/joshua/base_class.rb', line 168 def after_auto_mount &blok @@after_auto_mount = blok end |
.allow(type) ⇒ Object
allow alternative method access allow :get if defined, access will be allowed via POST + allowed method
310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/joshua/base_class.rb', line 310 def allow type if @method_type type = type.to_s.to_sym unless i(get head post put patch delete trace).include?(type) raise ArgumentError.new('"%s" is not allowed http method type' % type) end @@opts[:allow] = type.to_s.upcase 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
...
221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/joshua/base_class.rb', line 221 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
210 211 212 |
# File 'lib/joshua/base_class.rb', line 210 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
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 103 104 |
# File 'lib/joshua/base_class.rb', line 42 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
350 351 352 |
# File 'lib/joshua/base_class.rb', line 350 def before &block set_callback :before, block end |
.call(env = nil) ⇒ 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 35 36 |
# File 'lib/joshua/base_class.rb', line 7 def call env = nil return render unless 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
259 260 261 262 263 |
# File 'lib/joshua/base_class.rb', line 259 def collection &block @method_type = :collection class_exec &block @method_type = nil end |
.content_type(name) ⇒ Object
define response content type (defaults to JSON)
325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/joshua/base_class.rb', line 325 def content_type name if name.is_class == Symbol name = case name when :json 'application/json' when :text 'text/plain' else raise ArgumentError.new('content-type "%s" is not recognized') end end @@opts[:content_type] = name end |
.define(name, &block) ⇒ Object
aleternative way to define a api function members do
define :foo do
params {}
proc {}
end
end
240 241 242 243 244 245 246 247 248 |
# File 'lib/joshua/base_class.rb', line 240 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
288 289 290 291 292 293 294 |
# File 'lib/joshua/base_class.rb', line 288 def desc data if @method_type @@opts[:desc] = data else set :opts, :desc, data end end |
.detail(data) ⇒ Object
api method detailed description
297 298 299 300 301 302 303 304 305 |
# File 'lib/joshua/base_class.rb', line 297 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”
202 203 204 205 206 207 208 |
# File 'lib/joshua/base_class.rb', line 202 def documented if self == Joshua DOCUMENTED.sort.uniq.map(&:constantize) else DOCUMENTED.push to_s unless DOCUMENTED.include?(to_s) end end |
.error(desc) ⇒ Object
class errors, raised by params validation
181 182 183 |
# File 'lib/joshua/base_class.rb', line 181 def error desc raise Joshua::Error, desc end |
.error_print(error) ⇒ Object
185 186 187 188 189 190 191 192 193 |
# File 'lib/joshua/base_class.rb', line 185 def error_print error puts puts 'Joshua error dump' puts '---' puts '%s: %s' % [error.class, error.] puts '---' puts error.backtrace puts '---' end |
.get(*args) ⇒ Object
375 376 377 |
# File 'lib/joshua/base_class.rb', line 375 def get *args opts.dig *args end |
.icon(data) ⇒ Object
api method icon you can find great icons at boxicons.com/ - export to svg
279 280 281 282 283 284 285 |
# File 'lib/joshua/base_class.rb', line 279 def icon data if @method_type raise ArgumentError.new('Icons cant be added on methods') else set :opts, :icon, data end end |
.make_hash_html_safe(hash) ⇒ Object
418 419 420 421 422 423 424 425 426 |
# File 'lib/joshua/base_class.rb', line 418 def make_hash_html_safe hash (hash || {}).each do |k, v| if v.is_a?(Hash) make_hash_html_safe v elsif v.class == String hash[k] = v.gsub('<', '#LT;') end end end |
.member(&block) ⇒ Object Also known as: members
/api/companies/1/show
251 252 253 254 255 |
# File 'lib/joshua/base_class.rb', line 251 def member &block @method_type = :member func = class_exec &block @method_type = nil end |
.method_added(name) ⇒ Object
here we capture member & collection metods
406 407 408 409 410 411 412 413 414 415 416 |
# File 'lib/joshua/base_class.rb', line 406 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
401 402 403 |
# File 'lib/joshua/base_class.rb', line 401 def model name, &block Typero.schema name, &block end |
.mount_on(what) ⇒ Object
sets api mount point mount_on ‘/api’
197 198 199 |
# File 'lib/joshua/base_class.rb', line 197 def mount_on what OPTS[:api][:mount_on] = what end |
.opts ⇒ Object
dig all options for a current class
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 |
# File 'lib/joshua/base_class.rb', line 380 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
params do
name? String
email :email
end
270 271 272 273 274 275 |
# File 'lib/joshua/base_class.rb', line 270 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
363 364 365 366 367 368 369 370 371 372 373 |
# File 'lib/joshua/base_class.rb', line 363 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
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 143 144 145 146 |
# File 'lib/joshua/base_class.rb', line 107 def render action = nil, opts = {} if action unless action[0] return error 'Action not defined' end else return RenderProxy.new self end api_class = if klass = opts.delete(:class) # /api/_/foo if klass == '_' klass = Joshua::PostmanSchema.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' klass = klass.join('/').classify begin klass.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 |
.render_data(action, opts = {}) ⇒ Object
148 149 150 151 |
# File 'lib/joshua/base_class.rb', line 148 def render_data action, opts = {} response = render action, params: opts response && (response[:data] || []) 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
164 165 166 |
# File 'lib/joshua/base_class.rb', line 164 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
174 175 176 177 178 |
# File 'lib/joshua/base_class.rb', line 174 def response_error text out = Response.new nil out.error text out.render end |
.unsafe ⇒ Object
allow methods without @api.bearer token set
341 342 343 344 345 346 347 |
# File 'lib/joshua/base_class.rb', line 341 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
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 |
# File 'lib/joshua/base_instance.rb', line 53 def execute_call allow_type = @api.method_opts[:allow] || 'POST' request_type = @api.request&.request_method || 'POST' is_allowed = @api.development || ['POST', allow_type].include?(request_type) if is_allowed 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 else response.error '%s request is not allowed' % request_type end @api.raw || response.render end |
#to_h ⇒ Object
92 93 94 |
# File 'lib/joshua/base_instance.rb', line 92 def to_h execute_call end |
#to_json ⇒ Object
88 89 90 |
# File 'lib/joshua/base_instance.rb', line 88 def to_json execute_call.to_json end |