apicraft-rails 0.5.2.beta1 → 1.0.0

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: 5411b6d7fec8f1e73a0827f8809c53212ff39f0c97d94bd5ed54825e84a81882
4
- data.tar.gz: b4d2b5001f2aca8c32b5ac510e9b67a749e2eb5fc199ed89eaaaa42135b598df
3
+ metadata.gz: 7692b6e9a2cd444c2f5db48a932c78d64e4a23172575cafb76ed307c527c1ed3
4
+ data.tar.gz: 120c7cf23160c70761a168a815704bbcafc2e1c87901d5b2c58c037c840c4151
5
5
  SHA512:
6
- metadata.gz: 46cf0e60bd1d0f7551c80f118af6d8c421c8d6485252dc92a2b58d75c02f052c6c3216ac976ecb46f636fee72946c8f9662ed3af7b243575a5d716bcf5c6d40a
7
- data.tar.gz: 5e77011472cb349e45f84131e9ef15e17b22a13576b2cb3b5f40b2311d191bede0c36bcec654932527e4e7e1c7006b3a12cd7967b8a42c9cd47865efedbbfb18
6
+ metadata.gz: dfe9dc9efc333d44b43825b5f0b5a3377ffdb1e3016a725c131b923ac15be131b5e8bc5e1ad6dc06cfd58ccc7ae493cf18b0c54846507491828550c884657e0b
7
+ data.tar.gz: a9bb65b6c85b07e5a17c52b99bcf59435cff0810191358b214fdd45bbea8cb5e181f5d7e1332431b74c9473f6a6a531e063e9391500eef78fad67b2b2d197231
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # APICraft Rails
2
2
  [![Build](https://github.com/apicraft-dev/apicraft-rails/actions/workflows/build.yml/badge.svg)](https://github.com/apicraft-dev/apicraft-rails/actions/workflows/build.yml)
3
- [![Gem Version](https://badge.fury.io/rb/apicraft-rails.svg?v=0.5.2.beta1)](https://badge.fury.io/rb/apicraft-rails)
3
+ [![Gem Version](https://badge.fury.io/rb/apicraft-rails.svg?v=1.0.0)](https://badge.fury.io/rb/apicraft-rails)
4
4
 
5
5
  🚀 Accelerates your development by 2-3x with an API Design First approach. Seamlessly integrates with your Rails application server — no fancy tooling or expenses required.
6
6
 
@@ -12,23 +12,26 @@ It avoids the pitfalls of the code-first methodology, where contracts are auto-g
12
12
 
13
13
  ![APICraft Rails Logo](assets/apicraft_rails.png)
14
14
 
15
- - [APICraft Rails (Beta)](#apicraft-rails-beta)
15
+ - [APICraft Rails](#apicraft-rails)
16
16
  - [✨ Features](#-features)
17
17
  - [🔜 Upcoming Features](#-upcoming-features)
18
18
  - [🪄 Works Like Magic](#-works-like-magic)
19
19
  - [🕊 API Design First Philosophy](#-api-design-first-philosophy)
20
20
  - [🏗 Installation](#-installation)
21
21
  - [⚙️ Usage](#️-usage)
22
- - [🎭 API Mocking](#-api-mocking)
23
- - [🎮 API Mocking (Behaviours)](#-api-mocking-behaviours)
24
- - [🧐 API Introspection](#-api-introspection)
25
- - [📖 API Documentation (Swagger docs and RapiDoc)](#-api-documentation-swagger-docs-and-rapidoc)
22
+ - [🛡️ Request Validations](#️-request-validations)
23
+ - [🎭 Mocking](#-mocking)
24
+ - [🎮 Behaviour Mocking](#-behaviour-mocking)
25
+ - [🧐 Introspection](#-introspection)
26
+ - [📖 Documentation (Swagger docs and RapiDoc)](#-documentation-swagger-docs-and-rapidoc)
26
27
  - [🔧 Configuration](#-configuration)
27
28
  - [🤝 Contributing](#-contributing)
28
29
  - [📝 License](#-license)
29
30
  - [📘 Code of Conduct](#-code-of-conduct)
30
31
 
31
32
  ## ✨ Features
33
+ - 🛡️ **Automatic Request Validations** - Validates the request based on the openapi specs so that you don't need to add params validations everywhere in your controllers.
34
+
32
35
  - 🧑‍💻️ **Dynamic Mock Data Generation** - Detects the specifications and instantly mounts working routes with mock responses. No extra configuration required.
33
36
 
34
37
  - ⚙️ **Customizable Mock Responses** - Tailor mock responses to simulate different scenarios and edge cases, helping your team prepare for real-world conditions right from the start.
@@ -40,7 +43,6 @@ It avoids the pitfalls of the code-first methodology, where contracts are auto-g
40
43
  - 🗂 **Easy Contracts Management** - Management of `openapi` specifications from within `app/contracts` directory. No new syntax, just plain old `openapi` standard with `.json` or `.yaml` formats
41
44
 
42
45
  ## 🔜 Upcoming Features
43
- - 💢 **Request Validations** - Automatic request validations.
44
46
  - 💎 **Clean & Custom Ruby DSL** - Support for a Ruby DSL alongwith the current `.json` and `.yaml` formats.
45
47
 
46
48
 
@@ -72,7 +74,7 @@ By adopting an API Design First approach with APICraft Rails, you can accelerate
72
74
  Add this line to your application's Gemfile:
73
75
 
74
76
  ```ruby
75
- gem 'apicraft-rails', '~> 0.5.2.beta1'
77
+ gem 'apicraft-rails', '~> 1.0.0'
76
78
  ```
77
79
 
78
80
  And then execute:
@@ -89,8 +91,11 @@ module App
89
91
  class Application < Rails::Application
90
92
  # Rest of the configuration...
91
93
 
92
- config.middleware.use Apicraft::Middlewares::Mocker
93
- config.middleware.use Apicraft::Middlewares::Introspector
94
+ [
95
+ Apicraft::Middlewares::Mocker,
96
+ Apicraft::Middlewares::Introspector,
97
+ Apicraft::Middlewares::RequestValidator
98
+ ].each { |mw| config.middleware.use mw }
94
99
 
95
100
  Apicraft.configure do |config|
96
101
  config.contracts_path = Rails.root.join("app/contracts")
@@ -117,7 +122,11 @@ my_rails_app/
117
122
  │ │ ├── user.rb
118
123
  │ │ └── order.rb
119
124
  ```
120
- ### 🎭 API Mocking
125
+
126
+ ### 🛡️ Request Validations
127
+ All incoming requests will be validated against the defined schema. This ensures that by the time the params reach the controller they are adhering to all the schema requirements. It's enabled by default. You can customize the response of a failed validation. Check the [configuration section](#-configuration) section for a full list of options for this.
128
+
129
+ ### 🎭 Mocking
121
130
  **APICraft** dynamically generates mock APIs by interpreting contract specifications on the fly. You can request the mock response by passing `Apicraft-Mock: true` in the headers.
122
131
 
123
132
  `https://yoursite.com/api/orders`
@@ -141,7 +150,7 @@ headers: {
141
150
  ]
142
151
  ```
143
152
 
144
- ### 🎮 API Mocking (Behaviours)
153
+ ### 🎮 Behaviour Mocking
145
154
  The above is an example of a 200 response. If you have more responses documented you can force that behaviour using `Apicraft-Response-Code` header in the mock request.
146
155
  You can find a list of all the supported headers in the [configuration section](#-configuration) that would allow you to manipulate the API Behaviour.
147
156
 
@@ -160,12 +169,12 @@ headers: {
160
169
  }
161
170
  ```
162
171
 
163
- ### 🧐 API Introspection
164
- All APIs are can be introspected. You can do so by passing the `Apicraft-Introspection` header.
172
+ ### 🧐 Introspection
173
+ All APIs are can be introspected. You can do so by passing the `Apicraft-Introspect` header.
165
174
 
166
175
  ```
167
176
  headers: {
168
- Apicraft-Introspection: true
177
+ Apicraft-Introspect: true
169
178
  }
170
179
  ```
171
180
 
@@ -195,7 +204,7 @@ Example: `https://yoursite.com/api/orders`
195
204
  }
196
205
  }
197
206
  ```
198
- ### 📖 API Documentation (Swagger docs and RapiDoc)
207
+ ### 📖 Documentation (Swagger docs and RapiDoc)
199
208
 
200
209
  Mount the documentation views in your route file.
201
210
 
@@ -275,6 +284,20 @@ Apicraft.configure do |config|
275
284
  # Delay simulation header name
276
285
  delay: "Apicraft-Delay"
277
286
  }
287
+
288
+ config.request_validation = {
289
+ enabled: true,
290
+
291
+ # Return the http code for validation errors, defaults to 400
292
+ http_code: 400,
293
+
294
+ # Return a custom response body, defaults to `{ message: "..." }`
295
+ response_body: proc do |ex|
296
+ {
297
+ message: ex.message
298
+ }
299
+ end
300
+ }
278
301
  end
279
302
 
280
303
  Apicraft::Web::App.use do |user, password|
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apicraft
4
+ module Concerns
5
+ # Helper class with shared methods
6
+ module MiddlewareUtil
7
+ def config
8
+ @config ||= Apicraft.config
9
+ end
10
+
11
+ def convertor(format)
12
+ return if format.blank?
13
+
14
+ Apicraft::Constants::MIME_TYPE_CONVERTORS[format]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "concerns/cacheable"
4
+ require_relative "concerns/middleware_util"
4
5
 
5
6
  module Apicraft
6
7
  # Namespace module for Concerns
@@ -19,14 +19,16 @@ module Apicraft
19
19
  mocks: true,
20
20
  introspection: true,
21
21
  strict_reference_validation: true,
22
- request_validations: true,
23
- max_allowed_delay: 30
22
+ max_allowed_delay: 30,
23
+ request_validation: {
24
+ enabled: true,
25
+ http_code: 400,
26
+ response_body: proc { |ex| { message: ex.message } }
27
+ }
24
28
  }.with_indifferent_access
25
29
 
26
- def initialize(opts = {})
27
- @opts = DEFAULTS.merge(
28
- opts
29
- ).with_indifferent_access
30
+ def initialize
31
+ @opts = DEFAULTS
30
32
  end
31
33
 
32
34
  def headers
@@ -53,6 +55,22 @@ module Apicraft
53
55
  @opts[:max_allowed_delay]
54
56
  end
55
57
 
58
+ def request_validation
59
+ @opts[:request_validation]
60
+ end
61
+
62
+ def request_validation_enabled?
63
+ @opts[:request_validation][:enabled]
64
+ end
65
+
66
+ def request_validation_http_code
67
+ @opts[:request_validation][:http_code] || DEFAULTS[:request_validation][:http_code]
68
+ end
69
+
70
+ def request_validation_response_body
71
+ @opts[:request_validation][:response_body]
72
+ end
73
+
56
74
  def contracts_path=(contracts_path)
57
75
  @opts[:contracts_path] = contracts_path
58
76
  end
@@ -69,8 +87,10 @@ module Apicraft
69
87
  @opts[:strict_reference_validation] = enabled
70
88
  end
71
89
 
72
- def request_validations=(enabled)
73
- @opts[:request_validations] = enabled
90
+ def request_validation=(request_validation_opts)
91
+ @opts[:request_validation] = @opts[:request_validation].merge(
92
+ request_validation_opts.with_indifferent_access
93
+ )
74
94
  end
75
95
 
76
96
  def max_allowed_delay=(enabled)
@@ -4,6 +4,8 @@ module Apicraft
4
4
  module Middlewares
5
5
  # Apicraft Middleware to handle API Introspection.
6
6
  class Introspector
7
+ include Concerns::MiddlewareUtil
8
+
7
9
  def initialize(app)
8
10
  @app = app
9
11
  end
@@ -30,10 +32,6 @@ module Apicraft
30
32
 
31
33
  private
32
34
 
33
- def config
34
- @config ||= Apicraft.config
35
- end
36
-
37
35
  def introspect?(request)
38
36
  request.headers[config.headers[:introspect]].present?
39
37
  end
@@ -5,6 +5,8 @@ module Apicraft
5
5
  # Apicraft Middleware to handle routing
6
6
  # and make mock calls available.
7
7
  class Mocker
8
+ include Concerns::MiddlewareUtil
9
+
8
10
  def initialize(app)
9
11
  @app = app
10
12
  end
@@ -23,14 +25,7 @@ module Apicraft
23
25
  response = operation.response_for(code.to_s)
24
26
  raise Errors::InvalidResponse if response.blank?
25
27
 
26
- # Determine the format passed in the request.
27
- # If passed we use it and the response format.
28
- # If not we use the first format from the specs.
29
- request.format.to_s
30
- # indicates that not format was specified.
31
- format = nil
32
-
33
- content, content_type = response.mock(format)
28
+ content, content_type = response.mock(request.content_type)
34
29
 
35
30
  [
36
31
  code.to_i,
@@ -45,16 +40,6 @@ module Apicraft
45
40
 
46
41
  private
47
42
 
48
- def config
49
- @config ||= Apicraft.config
50
- end
51
-
52
- def convertor(format)
53
- return if format.blank?
54
-
55
- Apicraft::Constants::MIME_TYPE_CONVERTORS[format]
56
- end
57
-
58
43
  def mock?(request)
59
44
  request.headers[config.headers[:mock]].present?
60
45
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apicraft
4
+ module Middlewares
5
+ # Middleware to handle Request Validation.
6
+ class RequestValidator
7
+ include Concerns::MiddlewareUtil
8
+
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ return @app.call(env) unless validate?
15
+
16
+ request = ActionDispatch::Request.new(env)
17
+ content_type = request.content_type
18
+
19
+ operation = Apicraft::Openapi::Contract.find_by_operation(
20
+ request.method, request.path_info
21
+ )&.operation(
22
+ request.method, request.path_info
23
+ )
24
+ return @app.call(env) if operation.blank?
25
+
26
+ operation.validate_request_body(
27
+ content_type,
28
+ request.params
29
+ )
30
+ @app.call(env)
31
+ rescue OpenAPIParser::OpenAPIError => e
32
+ [
33
+ config.request_validation_http_code,
34
+ { 'Content-Type': content_type },
35
+ [
36
+ response_body(e)&.send(convertor(content_type))
37
+ ].compact
38
+ ]
39
+ end
40
+
41
+ private
42
+
43
+ def validate?
44
+ config.request_validation_enabled?
45
+ end
46
+
47
+ def response_body(ex)
48
+ config.request_validation_response_body.call(ex)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "middlewares/mocker"
4
4
  require_relative "middlewares/introspector"
5
+ require_relative "middlewares/request_validator"
5
6
 
6
7
  module Apicraft
7
8
  # Namespace module for Concerns
@@ -15,10 +15,10 @@ module Apicraft
15
15
  end
16
16
 
17
17
  def operation(method, path)
18
- with_cache("operation-#{method.downcase}-#{path}") do
19
- op = document.request_operation(method.downcase, path)
20
- Operation.new(op) if op.present?
21
- end
18
+ # with_cache("operation-#{method.downcase}-#{path}") do
19
+ op = document.request_operation(method.downcase, path)
20
+ Operation.new(op) if op.present?
21
+ # end
22
22
  end
23
23
 
24
24
  class << self
@@ -8,15 +8,16 @@ module Apicraft
8
8
  attr_accessor :operation
9
9
 
10
10
  def initialize(operation)
11
- @operation = operation.operation_object
11
+ @operation = operation
12
+ @operation_object = operation.operation_object
12
13
  end
13
14
 
14
15
  def responses
15
- @operation.responses
16
+ @operation_object.responses
16
17
  end
17
18
 
18
19
  def summary
19
- @operation.summary
20
+ @operation_object.summary
20
21
  end
21
22
 
22
23
  def response_for(code)
@@ -29,7 +30,14 @@ module Apicraft
29
30
  end
30
31
 
31
32
  def raw_schema
32
- operation.raw_schema
33
+ @operation_object.raw_schema
34
+ end
35
+
36
+ def validate_request_body(content_type, body)
37
+ operation.validate_request_body(
38
+ content_type,
39
+ body
40
+ )
33
41
  end
34
42
  end
35
43
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Current version of Apicraft.
4
4
  module Apicraft
5
- VERSION = "0.5.2.beta1"
5
+ VERSION = "1.0.0"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apicraft-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2.beta1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abhishek Sarkar
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-12 00:00:00.000000000 Z
11
+ date: 2024-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -126,6 +126,7 @@ files:
126
126
  - lib/apicraft-rails.rb
127
127
  - lib/apicraft/concerns.rb
128
128
  - lib/apicraft/concerns/cacheable.rb
129
+ - lib/apicraft/concerns/middleware_util.rb
129
130
  - lib/apicraft/config.rb
130
131
  - lib/apicraft/constants.rb
131
132
  - lib/apicraft/errors.rb
@@ -133,6 +134,7 @@ files:
133
134
  - lib/apicraft/middlewares.rb
134
135
  - lib/apicraft/middlewares/introspector.rb
135
136
  - lib/apicraft/middlewares/mocker.rb
137
+ - lib/apicraft/middlewares/request_validator.rb
136
138
  - lib/apicraft/mocker.rb
137
139
  - lib/apicraft/mocker/all_of.rb
138
140
  - lib/apicraft/mocker/any_of.rb
@@ -183,7 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
183
185
  - !ruby/object:Gem::Version
184
186
  version: '0'
185
187
  requirements: []
186
- rubygems_version: 3.5.11
188
+ rubygems_version: 3.2.33
187
189
  signing_key:
188
190
  specification_version: 4
189
191
  summary: APICraft Rails - Simplified API Design First Development