model_driven_api 3.1.9 → 3.1.11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94b18a1b177632591c520596c5b8443da17b84937c934b450550512355d4690d
4
- data.tar.gz: 8992f753df4957e584a303989130f48ef63346f9adff290f72140bc2f7df14f7
3
+ metadata.gz: bbaf42c9a62f9ebdc9cdf60633aeeb47ad39b11e9ebb9d20a94af73fea6c7213
4
+ data.tar.gz: c56851d1ae81777a20cc1d7ea26c0a31fb2f349933df72e41f303b8194cc8da6
5
5
  SHA512:
6
- metadata.gz: be09d8af6479897b60e760e1d8a1096804acca2a881a9dba302af39a701eeeba3e801ca09bd25caadcdc3a4ae20d8ea0280e9e70458331ea2a53d6a7bbba55b0
7
- data.tar.gz: a49c63942e572826abe3e6176bca618589993da59957d012a34db8b49acd4550102b0fb8987a8a602faf20be56363ace53c53fe1496feb97316cd7796edf736d
6
+ metadata.gz: 67ff210a1024a027db62fdaad0091b52229b36d61753ee95c128930a3e4ea8bd127bc1bf8e61209a7aac9938b0c32fd768cdd8b72548adf7f8cc1336b41c95e6
7
+ data.tar.gz: fc5a82329178979b349055efb02840708ee1d2df4a3ea37d19f9b317403a5d583a584c25f712e3a72c83d438d344d46fbcfb8762bfa573d40909f991e8716fc6
@@ -66,7 +66,6 @@ class Api::V2::ApplicationController < ActionController::API
66
66
 
67
67
  def create
68
68
  # Normal Create Action
69
- @record = @model.new(@body)
70
69
  authorize! :create, @record
71
70
  # Custom Action
72
71
  status, result, status_number = check_for_custom_action
@@ -74,6 +73,7 @@ class Api::V2::ApplicationController < ActionController::API
74
73
  # Keeping this automation can be too dangerous and lead to unpredicted results
75
74
  # TODO: Remove it
76
75
  # @record.user_id = current_user.id if @model.column_names.include? "user_id"
76
+ @record = @model.new(@body)
77
77
  @record.save!
78
78
  render json: @record.to_json(json_attrs), status: 201
79
79
  end
@@ -158,11 +158,13 @@ class Api::V2::ApplicationController < ActionController::API
158
158
  # The endpoint can be expressed in two ways:
159
159
  # 1. As a method in the model, with suffix custom_action_<custom_action>
160
160
  # 2. As a module instance method in the model, like Track::Endpoints.inventory
161
- if defined?("Endpoints::#{@model}.#{custom_action}")
162
- # Custom endpoint exists and can be called in the sub-modules form
163
- body, status = "Endpoints::#{@model}".constantize.send(custom_action, params)
164
- elsif @model.respond_to?("custom_action_#{custom_action}")
161
+ # Example:
162
+ # Endpoints::TestApi.new(:test, {request_verb: "POST", is_connected: "Uhhhh"}).result
163
+ if @model.respond_to?("custom_action_#{custom_action}")
165
164
  body, status = @model.send("custom_action_#{custom_action}", params)
165
+ elsif ("Endpoints::#{@model}".constantize rescue false) && "Endpoints::#{@model}".constantize.instance_methods.include?(custom_action.to_sym)
166
+ # Custom endpoint exists and can be called in the sub-modules form
167
+ body, status = "Endpoints::#{@model}".constantize.new(custom_action, params).result
166
168
  else
167
169
  # Custom endpoint does not exist or cannot be called
168
170
  raise NoMethodError
@@ -515,49 +515,17 @@ class Api::V2::InfoController < Api::V2::ApplicationController
515
515
  }
516
516
  }
517
517
  # Non CRUD or Search, but custom, usually bulk operations endpoints
518
- custom_actions = d.methods(false).select do |m| m.to_s.starts_with?("custom_action_") end
519
- # Add also custom actions created using th enew Endpoints Interface
520
- custom_actions += "Endpoints::#{d.model_name.name}".constantize.methods(false) rescue []
521
- custom_actions.each do |action|
522
- custom_action_name = action.to_s.gsub("custom_action_", "")
523
- pivot["/#{model}/custom_action/#{custom_action_name}"] = {
524
- "get": {
525
- "summary": "Custom Action #{custom_action_name.titleize}",
526
- "description": "This is just an example of a custom action, they can accept a wide range of payloads and response with a wide range of responses, also all verbs are valid. Please refer to the documentation for more information.",
527
- "tags": [model.classify],
528
- "security": [
529
- "bearerAuth": []
530
- ],
531
- "responses": {
532
- "200": {
533
- "description": "Custom Action",
534
- "content": {
535
- "application/json": {
536
- "schema": {
537
- "type": "object",
538
- "properties": {
539
- "id": {
540
- "type": "integer"
541
- },
542
- "created_at": {
543
- "type": "string",
544
- "format": "date-time"
545
- },
546
- "updated_at": {
547
- "type": "string",
548
- "format": "date-time"
549
- }
550
- }
551
- }
552
- }
553
- }
554
- },
555
- "404": {
556
- "description": "No #{model} found"
557
- }
558
- }
559
- }
560
- }
518
+ new_custom_actions = ("Endpoints::#{d.model_name.name}".constantize.instance_methods(false) rescue [])
519
+ # Rails.logger.debug "New Custom Actions (#{d.model_name.name}): #{new_custom_actions}"
520
+ new_custom_actions.each do |action|
521
+ openapi_definition = "Endpoints::#{d.model_name.name}".constantize.definitions[action.to_sym] rescue false
522
+
523
+ # Add the tag to the openapi definition
524
+ openapi_definition.each do |k, v|
525
+ v[:tags] = [ d.model_name.name ]
526
+ end
527
+
528
+ pivot["/#{model}/custom_action/#{action}"] = openapi_definition if openapi_definition
561
529
  end
562
530
  pivot["/#{model}/search"] = {
563
531
  # Complex queries are made using ranskac search via a post endpoint
@@ -0,0 +1,78 @@
1
+ class Endpoints::TestApi < NonCrudEndpoints
2
+ self.desc :test, {
3
+ # Define the action name using openapi swagger format
4
+ get: {
5
+ summary: "Test API Custom Action",
6
+ description: "This is a test API custom action",
7
+ operationId: "test",
8
+ tags: ["Test"],
9
+ parameters: [
10
+ {
11
+ name: "explain",
12
+ in: "query",
13
+ description: "Explain the action by returning this openapi schema",
14
+ required: true,
15
+ schema: {
16
+ type: "boolean"
17
+ }
18
+ }
19
+ ],
20
+ responses: {
21
+ 200 => {
22
+ description: "The openAPI json schema for this action",
23
+ content: {
24
+ "application/json": {
25
+ schema: {
26
+ type: "object",
27
+ additionalProperties: true
28
+ }
29
+ }
30
+ }
31
+ },
32
+ 501 => {
33
+ error: :string,
34
+ }
35
+ }
36
+ },
37
+ post: {
38
+ summary: "Test API Custom Action",
39
+ description: "This is a test API custom action",
40
+ operationId: "test",
41
+ tags: ["Test"],
42
+ requestBody: {
43
+ required: true,
44
+ content: {
45
+ "application/json": {}
46
+ }
47
+ },
48
+ responses: {
49
+ 200 => {
50
+ description: "The openAPI json schema for this action",
51
+ # This will return the object with a message string and a params object
52
+ content: {
53
+ "application/json": {
54
+ schema: {
55
+ type: "object",
56
+ properties: {
57
+ message: {
58
+ type: "string"
59
+ },
60
+ params: {
61
+ type: "object",
62
+ additionalProperties: true
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ },
69
+ 501 => {
70
+ error: :string,
71
+ }
72
+ }
73
+ }
74
+ }
75
+ def test(params)
76
+ return { message: "Hello World From Test API Custom Action called test", params: params }, 200
77
+ end
78
+ end
@@ -1,6 +1,2 @@
1
1
  class TestApi
2
- # Initialize the class with the request params
3
- def initialize params
4
- @params = params
5
- end
6
2
  end
data/config/routes.rb CHANGED
@@ -30,17 +30,17 @@ Rails.application.routes.draw do
30
30
  patch ":ctrl/custom_action/:action_name/:id", to: 'application#update'
31
31
  delete ":ctrl/custom_action/:action_name/:id", to: 'application#destroy'
32
32
  # Catchall routes
33
- # # # CRUD Show
34
- # get '*path/:id', to: 'application#show'
35
- # # # CRUD Index
36
- # get '*path', to: 'application#index'
37
- # # # CRUD Create
38
- # post '*path', to: 'application#create'
39
- # # CRUD Update
33
+ # # CRUD Show
34
+ get '*path/:id', to: 'application#show'
35
+ # # CRUD Index
36
+ get '*path', to: 'application#index'
37
+ # # CRUD Create
38
+ post '*path', to: 'application#create'
39
+ # CRUD Update
40
40
  put '*path/:id/multi', to: 'application#update_multi'
41
41
  patch '*path/:id/multi', to: 'application#update_multi'
42
- # put '*path/:id', to: 'application#update'
43
- # patch '*path/:id', to: 'application#patch'
42
+ put '*path/:id', to: 'application#update'
43
+ patch '*path/:id', to: 'application#patch'
44
44
 
45
45
  # # CRUD Delete
46
46
  delete '*path/:id/multi', to: 'application#destroy_multi'
@@ -11,6 +11,7 @@ module ApiExceptionManagement
11
11
  rescue_from ActiveRecord::RecordInvalid, with: :invalid!
12
12
  rescue_from ActiveRecord::RecordNotFound, with: :not_found!
13
13
  rescue_from ActiveRecord::RecordNotUnique, with: :invalid!
14
+ rescue_from EndpointValidationError, with: :api_error
14
15
  end
15
16
 
16
17
  def unauthenticated! exception = AuthenticateUser::AccessDenied.new
@@ -34,7 +35,7 @@ module ApiExceptionManagement
34
35
  return api_error status: 500, errors: exception.message
35
36
  end
36
37
 
37
- def api_error(status: 500, errors: [])
38
+ def api_error(status: 501, errors: [])
38
39
  # puts errors.full_messages if !Rails.env.production? && errors.respond_to?(:full_messages)
39
40
  head status && return if errors.blank?
40
41
 
@@ -0,0 +1,2 @@
1
+ class EndpointValidationError < StandardError
2
+ end
@@ -1,3 +1,3 @@
1
1
  module ModelDrivenApi
2
- VERSION = "3.1.9".freeze
2
+ VERSION = "3.1.11".freeze
3
3
  end
@@ -1,3 +1,5 @@
1
+ require 'endpoint_validation_error'
2
+ require 'non_crud_endpoints'
1
3
  require 'thecore_backend_commons'
2
4
  require 'rack/cors'
3
5
  require 'ransack'
@@ -0,0 +1,62 @@
1
+ class NonCrudEndpoints
2
+ attr_accessor :result
3
+ cattr_accessor :definitions
4
+ self.definitions = {}
5
+ # Add a validation method which will be inherited by all the instances, and automatically run before any method call
6
+ def initialize(m, params)
7
+ # Check if self hase the m method, if not, raise a NoMethodError
8
+ raise NoMethodError, "The method #{m} does not exist in #{self.class.name}" unless self.respond_to? m
9
+ @definition = self.definitions[m.to_sym].with_indifferent_access
10
+
11
+ # self.send(m, { explain: true }) rescue []
12
+ validate_request(params)
13
+ @result = self.send(m, params)
14
+ end
15
+
16
+ def validate_request(params)
17
+ # If there is no definition, return
18
+ return if @definition.blank?
19
+ # puts "Called Class is: #{self.class}"
20
+ # puts "Which is son of: #{self.class.superclass}"
21
+ # body_mandatory_keys = definition[:body].select { |k, v| v[:optional] == false }.keys
22
+ # query_mandatory_keys = definition[:query].select { |k, v| v[:optional] == false }.keys
23
+ # # Raise a ValidationError if the request does not match the definition
24
+ # raise EndpointValidationError, "The verb \"#{params[:request_verb].presence || "No Verb Provided"}\" is not present in #{definition.keys.join(", ")}." if definition.keys.exclude? params[:request_verb]
25
+ # # Raise an exception if the verb is put or post and the body keys in definition are not all present as params keys, both params and definition[:body] can have nested objects
26
+ # raise EndpointValidationError, "The request body does not match the definition: in #{params[:request_verb]} requests all of the params must be present in definition. The body definition is #{definition[:body]}." if params[:request_verb] != "GET" && (body_mandatory_keys & params.keys) != body_mandatory_keys
27
+ # # Raise an exception if the verb is put or post and the body keys in definition are not all present as params keys, both params and definition[:body] can have nested objects
28
+ # raise EndpointValidationError, "The request query does not match the definition. The query definition is: #{definition[:query]}." if (query_mandatory_keys & params.keys) != query_mandatory_keys
29
+ # # Rais if the type of the param is not the same as the definition
30
+ # definition[:body].each do |k, v|
31
+ # next if params[k].nil?
32
+ # computed_type = get_type(params[k])
33
+ # raise EndpointValidationError, "The type of the param #{k} is not the same as the definition. The definition is #{v[:type]} and the param is #{computed_type}." if v[:type] != computed_type
34
+ # end
35
+ end
36
+
37
+ private
38
+
39
+ def self.desc(key, definition)
40
+ self.definitions[key] = definition
41
+ end
42
+
43
+ def get_type(type)
44
+ case type
45
+ when String
46
+ :string
47
+ when Integer
48
+ :integer
49
+ when Float
50
+ :number
51
+ when TrueClass, FalseClass
52
+ :boolean
53
+ when Array
54
+ :array
55
+ when Hash
56
+ :object
57
+ else
58
+ :undefined
59
+ end
60
+ end
61
+ end
62
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: model_driven_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.9
4
+ version: 3.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-06 00:00:00.000000000 Z
11
+ date: 2024-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thecore_backend_commons
@@ -125,7 +125,7 @@ files:
125
125
  - app/controllers/api/v2/authentication_controller.rb
126
126
  - app/controllers/api/v2/info_controller.rb
127
127
  - app/controllers/api/v2/users_controller.rb
128
- - app/models/concerns/endpoints/test_api.rb
128
+ - app/models/endpoints/test_api.rb
129
129
  - app/models/test_api.rb
130
130
  - app/models/used_token.rb
131
131
  - config/initializers/after_initialize_for_model_driven_api.rb
@@ -140,10 +140,12 @@ files:
140
140
  - lib/concerns/model_driven_api_application_record.rb
141
141
  - lib/concerns/model_driven_api_role.rb
142
142
  - lib/concerns/model_driven_api_user.rb
143
+ - lib/endpoint_validation_error.rb
143
144
  - lib/json_web_token.rb
144
145
  - lib/model_driven_api.rb
145
146
  - lib/model_driven_api/engine.rb
146
147
  - lib/model_driven_api/version.rb
148
+ - lib/non_crud_endpoints.rb
147
149
  - lib/tasks/model_driven_api_tasks.rake
148
150
  homepage: https://github.com/gabrieletassoni/model_driven_api
149
151
  licenses:
@@ -1,23 +0,0 @@
1
- module Endpoints::TestApi
2
- def self.test params
3
- # Define an explain var to be used to validate and document the action behavior when using ?explain=true in query string
4
- explain = {
5
- verbs: ["GET"],
6
- body: {},
7
- query: {},
8
- responses: {
9
- 200 => {
10
- message: :string,
11
- params: {}
12
- },
13
- 501 => {
14
- error: :string
15
- }
16
- }
17
- }
18
-
19
- return explain, 200 if params[:explain] == "true"
20
- return { error: "This method responds only to #{explain[:verbs].join(", ")} requests" }, 501 if explain[:verbs].exclude? params[:request_verb]
21
- return { message: "Hello World From Test API Custom Action called test", params: params }, 200
22
- end
23
- end