Comments (16)
Thanks for the response. Sorry, I should have added a bit more context with what I've tried:
require "openapi_first"
class OpenapiValidation
API_PATH_PREFIX = "/api/".freeze
def initialize(app)
@app = app
# NOTE: This produces warnings for each endpoint that come from an internal
# router that's part of OpenapiFirst. It's not relevant for us because
# we're not using the router.
Warning.ignore(/operationId is missing in '.*'\. I am ignoring this operation\./)
spec = OpenapiFirst.load("app/schemas/openapi.yml")
@openapi_validator = OpenapiFirst::ResponseValidator.new(spec)
end
def call(env)
request = Rack::Request.new(env)
response = @app.call(env)
if request.path.starts_with?(API_PATH_PREFIX)
begin
@openapi_validator.validate(request, response)
rescue OpenapiFirst::ResponseBodyInvalidError => e
raise e unless Rails.env.production?
MyLogger.log(
"openapi.request_validation.error",
path: request.path,
method: request.env["REQUEST_METHOD"],
error_message: e.message
)
rescue OpenapiFirst::NotFoundError
# Until we've got more of the API documented we can simply ignore
# unspecified routes.
end
end
response
end
end
This is the middleware I wrote the other day. The main issue is that OpenapiFirst::ResponseValidator
requires an operation ID to be set so the router can set env[OPERATION]
. We could look at adding operation IDs, but AFAICT that shouldn't be required by OpenAPI to validate our request/responses.
As far as removing ResponseValidator
, I think ideally some API similar to that would remain, as it's lower level it should add more flexibility for people wanting to write their own very specific middleware where they might want to modify responses or simply log issues.
How about adding the RequestValidation and ResponseValidation middlewares to just one controller at a time and not add it globally to your Rails app? Do you think that would work for you?
This might work, if the middleware was more customizble, such as allowing specifying lambdas to run in error cases. As it is I think I'll still need to write my own. I'd also still be facing the "operation ID" issue.
Or are you thinking about tracking exceptions silently and don't want to have RequestValidation return a 400 if the request is not valid?
Yeah, for now we just want to be aware of any issues.
Thanks again!
from openapi_first.
We could look at adding operation IDs, but AFAICT that shouldn't be required by OpenAPI to validate our request/responses.
Good point! I will remove the check for the operationId. This is only relevant in the Responder part, which you are not using currently.
from openapi_first.
Hi, I've changed a few small things that I wanted to change for some time and that might be helpful for you:
- The warning about missing operationId is gone ✨. (operationId is used only in the (Rack)Responder parts).
- You no longer have to call
OpenapiFirst.load
, but can also pass the path to the OpenAPI file instead - Adding the "Router" middleware is optional now as it is added automatically (not relevant for your use case, though)
About your actual question: I think using ResponseValidator here make sense the way you use it in the middleware and I agree that we should keep/add a low level API to validate requests/responses or parts of it.
Questions about your use case:
- Do you want to add request validation as well and log issues about that as well?
from openapi_first.
Ah yes, that example will work for now, thanks!
As far as API design, I'd probably do something along these lines (naming just for illustration purposes):
class RequestValidator
def call(request:)
# This would raise exceptions on invalid requests
end
end
class ResponseValidator
def call(request:, response:)
# This would raise exceptions on invalid responses
end
end
class ResponseValidationMiddleware
def call(env)
# uses ResponseValidator and optionally catches errors
end
end
class RequestValidationMiddleware
def call(env)
# uses RequestValidator and optionally catches errors
end
end
That way folks that want to write their own middleware or use the library in different ways have that flexibility and it should keep the middleware quite simple.
from openapi_first.
This is what I've ended up with for now:
require "openapi_first"
class OpenapiValidation
API_PATH_PREFIX = "/api/".freeze
SPEC_PATH = "app/schemas/openapi.yml".freeze
def initialize(app)
@app = app
spec = OpenapiFirst.load(SPEC_PATH)
@response_validator = OpenapiFirst::ResponseValidator.new(spec)
@request_validator = OpenapiFirst::RequestValidation.new(->(_env) {}, spec: SPEC_PATH, raise_error: true)
end
def call(env)
request = Rack::Request.new(env)
response = @app.call(env)
if request.path.starts_with?(API_PATH_PREFIX)
begin
@request_validator.call(env)
@response_validator.validate(request, response)
rescue OpenapiFirst::NotFoundError
# Until we've got more of the API documented we can simply ignore
# unspecified routes.
rescue => e
raise e unless Rails.env.production?
MyLogger.log(
"openapi.request_validation.error",
path: request.path,
method: request.env["REQUEST_METHOD"],
error_message: e.message
)
end
end
response
end
end
Thanks again for your help!
from openapi_first.
@twe4ked Thanks for sharing that snippet. <3
from openapi_first.
Hi,
thanks so much for the feedback. There is ResponseValidator, which can be used for response validation, which we used at work for response validation, but we switched to using the normal middlewares instead. But I would really like to remove ResponseValidator.
How about adding the RequestValidation and ResponseValidation middlewares to just one controller at a time and not add it globally to your Rails app? Do you think that would work for you?
Or are you thinking about tracking exceptions silently and don't want to have RequestValidation return a 400 if the request is not valid? Thanks again for the interest in this gem.
from openapi_first.
I was also thinking about removing the necessity to use the Router middleware, which might make implementing your use case a bit easier.
from openapi_first.
That'd be great, thanks!
from openapi_first.
Amazing, thanks for the changes 🤩 – I'll update and let you know how I go!
Do you want to add request validation as well and log issues about that as well?
Yup, we'll definitely want both request and response validation.
from openapi_first.
Would it be possible to introduce an API similar to OpenapiFirst::ResponseValidator#validate
for requests?
from openapi_first.
@twe4ked yes, very much! Do you have any preference how that API should look like?
from openapi_first.
@twe4ked Another option would be to call an instance of the RequestValidator Middleware something like this:
request_validator = OpenapiFirst::RequestValidation.new(->(_env){}, spec: 'openapi/openapi.yaml', raise_error: true)
request_validator.call(env)
This got a bit simpler, because you no longer have to setup "Router".
from openapi_first.
@twe4ked Cool!
from openapi_first.
I‘ll close this. Thanks for the insights.
from openapi_first.
Related Issues (20)
- Add Allow header to OPTIONS response HOT 1
- Improve error message if value is not defined in enum HOT 1
- Add support for multiple content types HOT 2
- Feedback wanted 👋🏼 HOT 1
- 415 Unsupported Media Type when posting form data from Postman HOT 4
- Capture names affects finding definitions for nested routes HOT 4
- Is anybody using the standalong mode (OpenapiFirst.app) HOT 1
- Access operation level or top-level `security` object HOT 1
- Request header validation
- Cookie request parameter validation HOT 1
- Response header validation
- The purpose of hanami in this gem? HOT 10
- Manual request/response validation HOT 6
- v1.0.0.beta5 release not available via rubygems.org HOT 7
- Support Rack array parameters like`name: 'fields[]'`? HOT 2
- Support "explode" and "style" parameter arguments HOT 2
- Idea: Return customizable Sunset header for deprecated endpoints
- Support application/problem+json for error responses HOT 2
- Convert `format: 'date'`, `format: 'date-time'` to Ruby objects HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from openapi_first.