model_driven_api 3.1.8 → 3.1.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dcceec80f10a8a7bc91a9f8f32982f6d632f8af71936e8333ffe2f47308da2f7
4
- data.tar.gz: e6b1f3833363f78ff5884a25ac986fb47e44e322193b65e3fc90494de2ab08dd
3
+ metadata.gz: 0afb3dc1dca5b7277c7ea2bfd601669765d5227fe68ede17fa6fbfc3de19d36f
4
+ data.tar.gz: 02c3ece69e6237b42f8f04de6660469d528c81a84cf392a34a743360b765d832
5
5
  SHA512:
6
- metadata.gz: 8675f05d7145638a141e358fac87bb481318f2ea868817ea578d1400e6600291bdbc1d21691277f9c11f2c498be9e2659d724d0e3038f23c706b4a5ef8a81528
7
- data.tar.gz: c1bd46b4cd97f04f50ddef90b3394f4ffce98348f8f73f8e05ac30259c0c16319b785ceeb5f77fb17aa0bbb55ae374009d65eaae7dc1e9e860f06ee7e0ec3565
6
+ metadata.gz: 92c3d537101cb23123ce34564eaac4fe1d767770628dddcd1cc94487253bb82c880e07721a78c2be3dc571c370020b12e13ab30e9981d035c7766634dccdeb7b
7
+ data.tar.gz: 69385bf1c20fe26e8168be63da529edb847002984084e133e431aef6b07815bb88edf1f42ed43d65dc0ef5194a28ede011c29f05aca3de0176dd11a119da3a8a
@@ -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
@@ -521,7 +521,7 @@ class Api::V2::InfoController < Api::V2::ApplicationController
521
521
  custom_actions.each do |action|
522
522
  custom_action_name = action.to_s.gsub("custom_action_", "")
523
523
  pivot["/#{model}/custom_action/#{custom_action_name}"] = {
524
- "post": {
524
+ "get": {
525
525
  "summary": "Custom Action #{custom_action_name.titleize}",
526
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
527
  "tags": [model.classify],
@@ -0,0 +1,56 @@
1
+ class Endpoints::TestApi < NonCrudEndpoints
2
+ def 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", "POST"],
6
+ body: {
7
+ messages: {
8
+ type: :array,
9
+ optional: true,
10
+ items: {
11
+ type: :string,
12
+ optional: false
13
+ }
14
+ },
15
+ is_connected: {
16
+ type: :boolean,
17
+ optional: false
18
+ },
19
+ user: {
20
+ type: :object,
21
+ optional: true,
22
+ properties: {
23
+ name: {
24
+ type: :string,
25
+ optional: false
26
+ },
27
+ age: {
28
+ type: :integer,
29
+ optional: true
30
+ }
31
+ }
32
+ }
33
+ },
34
+ query: {
35
+ explain: {
36
+ type: :boolean,
37
+ optional: true
38
+ }
39
+ },
40
+ responses: {
41
+ 200 => {
42
+ message: :string,
43
+ params: {},
44
+ },
45
+ 501 => {
46
+ error: :string,
47
+ },
48
+ },
49
+ }
50
+ return explain, 200 if params[:explain].to_s == "true" && !explain.blank?
51
+
52
+
53
+ return { message: "Hello World From Test API Custom Action called test", params: params }, 200
54
+ end
55
+ end
56
+ # 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.8".freeze
2
+ VERSION = "3.1.10".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,54 @@
1
+ class NonCrudEndpoints
2
+ attr_accessor :result
3
+ # Add a validation method which will be inherited by all the instances, and automatically run before any method call
4
+ def initialize(m, params)
5
+ # Check if self hase the m method, if not, raise a NoMethodError
6
+ raise NoMethodError, "The method #{m} does not exist in #{self.class}" unless self.respond_to? m
7
+ definition = self.send(m, { explain: true }) rescue []
8
+ validate_request(definition.first.presence || {}, params)
9
+ @result = self.send(m, params)
10
+ end
11
+
12
+ def validate_request(definition, params)
13
+ # If there is no definition, return
14
+ return if definition.blank?
15
+ # puts "Called Class is: #{self.class}"
16
+ # puts "Which is son of: #{self.class.superclass}"
17
+ body_mandatory_keys = definition[:body].select { |k, v| v[:optional] == false }.keys
18
+ query_mandatory_keys = definition[:query].select { |k, v| v[:optional] == false }.keys
19
+ # Raise a ValidationError if the request does not match the definition
20
+ raise EndpointValidationError, "The verb \"#{params[:request_verb].presence || "No Verb Provided"}\" is not present in #{definition[:verbs].join(", ")}." if definition[:verbs].exclude? params[:request_verb]
21
+ # 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
22
+ 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
23
+ # 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
24
+ 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
25
+ # Rais if the type of the param is not the same as the definition
26
+ definition[:body].each do |k, v|
27
+ next if params[k].nil?
28
+ computed_type = get_type(params[k])
29
+ 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
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def get_type(type)
36
+ case type
37
+ when String
38
+ :string
39
+ when Integer
40
+ :integer
41
+ when Float
42
+ :number
43
+ when TrueClass, FalseClass
44
+ :boolean
45
+ when Array
46
+ :array
47
+ when Hash
48
+ :object
49
+ else
50
+ :undefined
51
+ end
52
+ end
53
+ end
54
+
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.8
4
+ version: 3.1.10
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-18 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,5 +0,0 @@
1
- module Endpoints::TestApi
2
- def self.test params
3
- return { message: "Hello World From Test API Custom Action called test", params: params }, 200
4
- end
5
- end