Git Product home page Git Product logo

Comments (16)

twe4ked avatar twe4ked commented on June 19, 2024 1

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.

ahx avatar ahx commented on June 19, 2024 1

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.

ahx avatar ahx commented on June 19, 2024 1

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.

twe4ked avatar twe4ked commented on June 19, 2024 1

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.

twe4ked avatar twe4ked commented on June 19, 2024 1

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.

gjtorikian avatar gjtorikian commented on June 19, 2024 1

@twe4ked Thanks for sharing that snippet. <3

from openapi_first.

ahx avatar ahx commented on June 19, 2024

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.

ahx avatar ahx commented on June 19, 2024

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.

twe4ked avatar twe4ked commented on June 19, 2024

That'd be great, thanks!

from openapi_first.

twe4ked avatar twe4ked commented on June 19, 2024

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.

twe4ked avatar twe4ked commented on June 19, 2024

Would it be possible to introduce an API similar to OpenapiFirst::ResponseValidator#validate for requests?

from openapi_first.

ahx avatar ahx commented on June 19, 2024

@twe4ked yes, very much! Do you have any preference how that API should look like?

from openapi_first.

ahx avatar ahx commented on June 19, 2024

@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.

ahx avatar ahx commented on June 19, 2024

@twe4ked Cool!

from openapi_first.

ahx avatar ahx commented on June 19, 2024

I‘ll close this. Thanks for the insights.

from openapi_first.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.