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 +4 -4
- data/README.md +39 -16
- data/lib/apicraft/concerns/middleware_util.rb +18 -0
- data/lib/apicraft/concerns.rb +1 -0
- data/lib/apicraft/config.rb +28 -8
- data/lib/apicraft/middlewares/introspector.rb +2 -4
- data/lib/apicraft/middlewares/mocker.rb +3 -18
- data/lib/apicraft/middlewares/request_validator.rb +52 -0
- data/lib/apicraft/middlewares.rb +1 -0
- data/lib/apicraft/openapi/contract.rb +4 -4
- data/lib/apicraft/openapi/operation.rb +12 -4
- data/lib/apicraft/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7692b6e9a2cd444c2f5db48a932c78d64e4a23172575cafb76ed307c527c1ed3
|
4
|
+
data.tar.gz: 120c7cf23160c70761a168a815704bbcafc2e1c87901d5b2c58c037c840c4151
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
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
|
-
- [
|
23
|
-
- [
|
24
|
-
- [
|
25
|
-
- [
|
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.
|
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
|
-
|
93
|
-
|
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
|
-
|
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
|
-
### 🎮
|
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
|
-
### 🧐
|
164
|
-
All APIs are can be introspected. You can do so by passing the `Apicraft-
|
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-
|
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
|
-
### 📖
|
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
|
data/lib/apicraft/concerns.rb
CHANGED
data/lib/apicraft/config.rb
CHANGED
@@ -19,14 +19,16 @@ module Apicraft
|
|
19
19
|
mocks: true,
|
20
20
|
introspection: true,
|
21
21
|
strict_reference_validation: true,
|
22
|
-
|
23
|
-
|
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
|
27
|
-
@opts = DEFAULTS
|
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
|
73
|
-
@opts[:
|
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
|
-
|
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
|
data/lib/apicraft/middlewares.rb
CHANGED
@@ -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
|
-
|
20
|
-
|
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
|
11
|
+
@operation = operation
|
12
|
+
@operation_object = operation.operation_object
|
12
13
|
end
|
13
14
|
|
14
15
|
def responses
|
15
|
-
@
|
16
|
+
@operation_object.responses
|
16
17
|
end
|
17
18
|
|
18
19
|
def summary
|
19
|
-
@
|
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
|
-
|
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
|
data/lib/apicraft/version.rb
CHANGED
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.
|
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-
|
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.
|
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
|