Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

For discussion: proposal for describing responses #588

Merged
merged 4 commits into from
Apr 27, 2018

Conversation

elasti-ron
Copy link
Contributor

@elasti-ron elasti-ron commented Jan 2, 2018

This pull request is for supporting a discussion, not for merging into the codebase (as per request from @iNecas).

Also relevant to #273

This was referenced Jan 2, 2018
@iNecas
Copy link
Member

iNecas commented Jan 6, 2018

First of all thanks @elasti-ron for the proposal: I like it very much. Two suggestions:

  1. introduce an alias for param, something like property, that could be used especially for response only parameters. Maybe property could have implicitly :only_in => :response attribute

  2. I would recommend thinking about possibility for the returns keyword to accept a block. This would allow to use multiple groups in the response, that would be merged to describe the response.

returns do
  param_group :user
  param_group :user_response
end

What do you think?

@iNecas
Copy link
Member

iNecas commented Jan 6, 2018

Also, let me invite @tstrachota and @mbacovsky for more eyes having on it.

@elasti-ron
Copy link
Contributor Author

Thanks @iNecas - I like both of your suggestions.

One thing that should be explicitly stated is the exact structure of a JSON response given a returns statement containing multiple param_group elements.

To maintain consistency with how input definitions are handled, I would expect that a returns statement with multiple param_group fields implies a single JSON object with the property elements of all of the param_group fields unwrapped into the same level.

For example, the following DSL:

param_group :user do
     param :name, String
end
param_group :user_response do
     property :vote, Boolean
end

returns do
    param_group :user
    param_group :user_response
end

would describe a JSON response that might look something like this:

{
    "name": "Joe",
    "vote":  true
}

@iNecas
Copy link
Member

iNecas commented Jan 10, 2018

Yes, that's how multiple param_groups for api description currently should work as well

@elasti-ron
Copy link
Contributor Author

Great. I'll share some experimental implementation code soon to get your thoughts and insights.

@elasti-ron
Copy link
Contributor Author

I pushed an update with a preliminary implementation. It includes the first 3 elements of the proposal (i.e., using a returns keyword with either a param_group or block to describe a response, as well as the :only_in=>:response feature). I'm now starting to implement returns :array_of => <param_group> and adapters.

Please take a look at the new PetsController for usage examples, and swagger_dsl_spec.rb for verification logic on PetsController.

Would love to get your feedback.

@elasti-ron
Copy link
Contributor Author

Now also with returns :array_of => <param_group name>

@elasti-ron
Copy link
Contributor Author

@iNecas - the latest commit includes the 'adapter' mechanism, but as an internal feature, allowing for clearer syntax (I think).

In the original proposal, future integration with Grape::Entity would have looked as follows:

# entity defined using Grape::Entity
class UserView < Grape::Entity
    expose :id, documentation: {type: Integer, desc: "user id", required: true}
    expose :name
end

# API defined using Apipie
# where 'from_grape_entity' is an adapter that exposes the contents of the entity
# as if it were an Apipie::ParamDescription object 
api :GET, "/users", "Get all user records"
returns :array_of => from_grape_entity(UserView), :desc => "the requested user records"

The implementation in the latest commit uses a slightly different approach: instead of indicating from_grape_entity in the returns statement, the integration of the view with Apipie would be in the UserView object itself, for example:

# entity defined using Grape::Entity
class UserView < Grape::Entity
    include "ApipieAdapter"  # this is a hypothetical module - not part of the current version
                             # such a module would include a mixin that implements
                             # a 'describe_own_properties` method, called by Apipie
                             # to get information about the generated view structure
    expose :id, documentation: {type: Integer, desc: "user id", required: true}
    expose :name
end

# API defined using Apipie
# no need to specify the 'from_grape_entity'  adapter because UserView includes the mixin
api :GET, "/users", "Get all user records"
returns :array_of => UserView, :desc => "the requested user records"

Please take a look at PetsUsingSelfDescribingClassesController and PetsUsingAutoViewsController (both in spec/dummy/app/controllers) for working examples.

Looking forward to your feedback.

@elasti-ron
Copy link
Contributor Author

elasti-ron commented Feb 8, 2018

Added ability to define response descriptions at the resource level.

BTW, I'm currently working (in a separate branch) on a feature to validate a JSON response.body against the 'returns' definition (by using the response schema in the generated swagger). Will merge into this branch after some additional cleanups. What I'm trying out there are two mechanisms, both intended to be used in a Controller test in RSpec :

  1. a matcher to validate a response object (expect(response).to match_declared_responses)
  2. an RSpec keyword auto_validate_rendered_views that turns on auto-validation for all responses within an RSpec block (similar to how RSpec's render_views keyword turns on response rendering in a block of a Controller spec)

@elasti-ron
Copy link
Contributor Author

Hi @iNecas - any thoughts on this so far?

@abenari
Copy link
Contributor

abenari commented Feb 19, 2018

@iNecas Can you review this PR?

@@ -44,9 +48,10 @@ def initialize(method, resource, dsl_data)

@metadata = dsl_data[:meta]

@params_ordered = dsl_data[:params].map do |args|
@params_ordered = dsl_data[:params].map { |args|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a multilne block, please keep do...end here.

# }
#
#
RSpec::Matchers.define :match_field_structure do |expected|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were are we using those?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used quite heavily in spec/lib/swagger/swagger_dsl_spec.rb

@@ -62,6 +64,9 @@ def initialize(method_description, name, validator, desc_or_options = nil, optio

@required = is_required?

@response_only = (@options[:only_in] == :response)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about request only case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I'll add support for this right after merging the 'response validation' mechanism that I'm cleaning up at the moment.

adapter = ResponseDescriptionAdapter.from_self_describing_class(descriptor)
else
begin
block = Apipie.get_param_group(scope, descriptor) if descriptor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indentation should be preserved on two spaces

```ruby
# entity defined using Grape::Entity
class UserView < Grape::Entity
expose :id, documentation: {type: Integer, desc: "user id", required: true}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation :)

@@ -0,0 +1,113 @@
module Apipie

class ResponseObject
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either nest it under ResponseDescription or put it into separate file, so that the file naming follows the class names

@iNecas
Copy link
Member

iNecas commented Feb 20, 2018

Some comments inline, mainly styling one. I need still to get a bit deeper on the code wise: one of the non-style questions is the lack of :only_if => :request option.

@@ -17,12 +17,12 @@ Gem::Specification.new do |s|
s.require_paths = ["lib"]

s.add_dependency "rails", ">= 4.1"
s.add_dependency "json-schema", ">= 2.8"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK we are using this only in tests, therefore it should not be a runtime dependency, unless we have a really good reason for doing so.

begin
block = Apipie.get_param_group(scope, descriptor) if descriptor
rescue
raise "No param_group or self-describing class named #{descriptor}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of exceptions do we expect here?

Copy link
Contributor Author

@elasti-ron elasti-ron Feb 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_param_group raises an exception if there is no param_group with the specified name:

    def get_param_group(controller, name)
      key = "#{controller.name}##{name}"
      if @param_groups.has_key?(key)
        return @param_groups[key]
      else
        raise "param group #{key} not defined"
      end
    end

The new code (raising "No param_group or self-describing class named...") merely changes the message of the exception because the input to returns is not limited to being a param_group.

#
#
# to use this file in a controller rspec you should
# require 'apipie/rspec/response_validation_helper' in the spec file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we could put the documentation into the README, so that people find it more easily. Also, I would rather see the people adding the json-schema depednency explicitly, rather than us adding this explicitly, as there are many folks that would not need this dependency at all.

Putting that aside, would you mind holding the whole feature out of this PR: it's already getting quite big/hard to review.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

@elasti-ron
Copy link
Contributor Author

I pushed a version with the response-validation feature redacted - hope this helps.

BTW, it occurred to me when removing this feature that its unit tests are the only ones that check the semantic correctness of the swagger, so it would be good to get it back in soon. The unit tests that we currently have are purely syntactic: they verify that the generated swagger is valid, but they don't check that it is correct.

@elasti-ron
Copy link
Contributor Author

Hi @iNecas - did you get a chance to look at the revised code?

@abenari
Copy link
Contributor

abenari commented Mar 15, 2018

@iNecas any progress on this?

@abenari
Copy link
Contributor

abenari commented Mar 26, 2018

Hi @iNecas,
This is waiting for about a month without any new comment.
We have a few more changes we want to push adding response validation and an improved swagger generation. Will you be able to review our proposal soon?

@@ -0,0 +1,244 @@
# Proposal for supporting response descriptions in Apipie
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are getting close to merging this, would you mind turning this into the documentation inside README.rst?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing.

Copy link
Contributor Author

@elasti-ron elasti-ron Mar 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Updated Apr-1] I pushed a version now with most of the README.rst work. Still need to document the response description adapter mechanism, but the straightforward use of the returns and property keywords is now there. Would you mind taking a look and letting me know if you have any comments?

@iNecas
Copy link
Member

iNecas commented Mar 28, 2018

Sry for delays, still tweaking my time-management approach to be able to respond faster, but not quite there yet, as you see :) Anyway, I like the direction now. Last one request for turning the proposal into a readme.

@elasti-ron
Copy link
Contributor Author

Hi @iNecas - just wanted to let you know that the Ruby project I'm currently working on will end on Apr 22, and my focus will turn to other things.

At that time my availability to help out with this PR (and with integration of additional enhancements that I'm waiting to upload - such as response validation, generation of swagger response objects as references and a couple of other unrelated ones) will be very limited, if at all. It would be great if we could finish this process ASAP.

@iNecas
Copy link
Member

iNecas commented Apr 27, 2018

Thanks. No other comments now. Merging this now and releasing new version.

@iNecas iNecas merged commit 2a14791 into Apipie:master Apr 27, 2018
@iNecas
Copy link
Member

iNecas commented Apr 27, 2018

apipie-rails-0.5.8 with this change has just been released

@damuz91
Copy link

damuz91 commented May 2, 2018

@iNecas Amazing, i used apipie 2 years ago and left it behind. Now i am using it again and i see the returns feature which is amazing. I am using it but can't get it to be displayed in the docs :( I will try to get this reproduced and open an issue for this

@ajaleelp
Copy link

@damuz91 returns are not being displayed for me too 😕 Where you able to figure out why? Or have you opened an issue for this? I couldn't find one.

@waissbluth
Copy link
Contributor

@damuz91 @ajaleelp, I'm facing the same issue; did you figure this out? Thanks!

@rrameshbtech
Copy link

@iNecas, I am still not getting response of the API in documents, even after adding returns block. any suggestions?

@iNecas
Copy link
Member

iNecas commented Jul 10, 2018

No clue right now: I will hopefully will be able to look at this in 2 weeks, but probably not sooner, so if anyone wants to give it a try and debug it more deeply, any help would be appreciated.

@X1ting
Copy link
Contributor

X1ting commented Oct 16, 2018

@iNecas #635 this PR will fix this issue, can you review pls?

@iNecas
Copy link
Member

iNecas commented Oct 17, 2018

@X1ting thanks, the issue is merged now and available in 0.5.12

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants