From 00e9778bd41268dc2f49f2dbf88e9f4a4b4f6584 Mon Sep 17 00:00:00 2001 From: Masato Nakamura Date: Thu, 22 Feb 2024 17:15:11 +0900 Subject: [PATCH 01/12] Bump GHA actions/checkout to be on node20 by default (#911) --- .github/workflows/build.yml | 2 +- .github/workflows/rubocop-challenger.yml | 2 +- .github/workflows/rubocop.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 152ace5b..8252680a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: RAILS_VERSION: ${{ matrix.rails }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} diff --git a/.github/workflows/rubocop-challenger.yml b/.github/workflows/rubocop-challenger.yml index b8997764..eca78732 100644 --- a/.github/workflows/rubocop-challenger.yml +++ b/.github/workflows/rubocop-challenger.yml @@ -11,7 +11,7 @@ jobs: env: RUBY_VERSION: 3.2.3 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Ruby 3.2 uses: ruby/setup-ruby@v1 with: diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 2a9caeba..49e35d4d 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -8,7 +8,7 @@ jobs: env: RUBY_VERSION: 3.2.3 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Ruby 3.2 uses: ruby/setup-ruby@v1 From 174c4906f1bb0cb0b2a0c588dac2efbb3b9b5602 Mon Sep 17 00:00:00 2001 From: Panos Dalitsouris Date: Fri, 1 Mar 2024 21:11:45 +0200 Subject: [PATCH 02/12] Change README extension to `.md` --- README.rst => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README.rst => README.md (100%) diff --git a/README.rst b/README.md similarity index 100% rename from README.rst rename to README.md From c5c08f518d6c57320def48ec951f070985d9768f Mon Sep 17 00:00:00 2001 From: Panos Dalitsouris Date: Fri, 1 Mar 2024 21:12:30 +0200 Subject: [PATCH 03/12] Convert README from RTS to MD --- README.md | 3062 +++++++++++++++++++++++++++-------------------------- 1 file changed, 1583 insertions(+), 1479 deletions(-) diff --git a/README.md b/README.md index 4a97754f..107d9a24 100644 --- a/README.md +++ b/README.md @@ -1,1968 +1,2072 @@ -======================== - API Documentation Tool -======================== - -.. image:: https://github.com/Apipie/apipie-rails/actions/workflows/build.yml/badge.svg - :target: https://github.com/Apipie/apipie-rails/actions/workflows/build.yml -.. image:: https://codeclimate.com/github/Apipie/apipie-rails.svg - :target: https://codeclimate.com/github/Apipie/apipie-rails -.. image:: https://badges.gitter.im/Apipie/apipie-rails.svg - :alt: Join the chat at https://gitter.im/Apipie/apipie-rails - :target: https://gitter.im/Apipie/apipie-rails?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge -.. image:: https://img.shields.io/gem/v/apipie-rails.svg - :alt: Latest release - :target: https://rubygems.org/gems/apipie-rails - -Apipie-rails is a DSL and Rails engine for documenting your RESTful -API. Instead of traditional use of ``#comments``, Apipie lets you -describe the code, through the code. This brings advantages like: - -* No need to learn yet another syntax, you already know Ruby, right? -* Possibility of reusing the docs for other purposes (such as validation) -* Easier to extend and maintain (no string parsing involved) -* Possibility of reusing other sources for documentation purposes (such as - routes etc.) - -The documentation is available from within your app (by default under the -``/apipie`` path.) In development mode, you can see the changes as you -go. It's markup language agnostic, and even provides an API for reusing -the documentation data in JSON. - -Getting started ---------------- - -The easiest way to get Apipie up and running with your app is: +# API Documentation Tool -.. code:: sh +[![image](https://github.com/Apipie/apipie-rails/actions/workflows/build.yml/badge.svg)](https://github.com/Apipie/apipie-rails/actions/workflows/build.yml) - echo "gem 'apipie-rails'" >> Gemfile - bundle install - rails g apipie:install +[![image](https://codeclimate.com/github/Apipie/apipie-rails.svg)](https://codeclimate.com/github/Apipie/apipie-rails) -Now you can start documenting your resources and actions (see -`DSL Reference`_ for more info): +[![Join the chat at https://gitter.im/Apipie/apipie-rails](https://badges.gitter.im/Apipie/apipie-rails.svg)](https://gitter.im/Apipie/apipie-rails?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -.. code:: ruby +[![Latest release](https://img.shields.io/gem/v/apipie-rails.svg)](https://rubygems.org/gems/apipie-rails) - api :GET, '/users/:id' - param :id, :number, desc: 'id of the requested user' - def show - # ... - end +Apipie-rails is a DSL and Rails engine for documenting your RESTful API. +Instead of traditional use of `#comments`, Apipie lets you describe the +code, through the code. This brings advantages like: +- No need to learn yet another syntax, you already know Ruby, right? +- Possibility of reusing the docs for other purposes (such as + validation) +- Easier to extend and maintain (no string parsing involved) +- Possibility of reusing other sources for documentation purposes + (such as routes etc.) -Run your application and see the result at -``http://localhost:3000/apipie``. For further processing, you can -use ``http://localhost:3000/apipie.json``. - -For a more comprehensive getting started guide, see -`this demo `_, which includes -features such as generating documentation from tests, recording examples etc. +The documentation is available from within your app (by default under +the `/apipie` path.) In development mode, you can see the changes as you +go. It\'s markup language agnostic, and even provides an API for reusing +the documentation data in JSON. -Screenshots ------------ +## Getting started -.. image:: https://github.com/Apipie/apipie-rails/blob/master/images/screenshot-1.png -.. image:: https://github.com/Apipie/apipie-rails/blob/master/images/screenshot-2.png +The easiest way to get Apipie up and running with your app is: -Authors -------- +``` sh +echo "gem 'apipie-rails'" >> Gemfile +bundle install +rails g apipie:install +``` -`Pajk `_ and `iNecas `_ +Now you can start documenting your resources and actions (see [DSL +Reference](#dsl-reference) for more info): -Contributors ------------- +``` ruby +api :GET, '/users/:id' +param :id, :number, desc: 'id of the requested user' +def show + # ... +end +``` -See `Contributors page `_. Special thanks to all of them! +Run your application and see the result at +`http://localhost:3000/apipie`. For further processing, you can use +`http://localhost:3000/apipie.json`. + +For a more comprehensive getting started guide, see [this +demo](https://github.com/Apipie/apipie-demo), which includes features +such as generating documentation from tests, recording examples etc. + +## Screenshots + +![image](https://github.com/Apipie/apipie-rails/blob/master/images/screenshot-1.png) + +![image](https://github.com/Apipie/apipie-rails/blob/master/images/screenshot-2.png) + +## Authors + +[Pajk](https://github.com/Pajk) and [iNecas](https://github.com/iNecas) + +## Contributors + +See [Contributors +page](https://github.com/Apipie/apipie-rails/graphs/contributors). +Special thanks to all of them! + +## License + +Apipie-rails is released under the [MIT +License](https://opensource.org/licenses/MIT) + +## Table Of Contents + +- [API Documentation Tool](#api-documentation-tool) +- [Getting started](#getting-started) +- [Screenshots](#screenshots) +- [Authors](#authors) +- [Contributors](#contributors) +- [License](#license) +- [Documentation](#documentation) + - [DSL Reference](#dsl-reference) + - [Resource Description](#resource-description) + - [Method Description](#method-description) + - [Parameter Description](#parameter-description) + - [DRY with param_group](#dry-with-param_group) + - [Action Aware params](#action-aware-params) + - [Response Description](#response-description) + - [Concerns](#concerns) + - [Response validation](#response-validation) + - [Configuration Reference](#configuration-reference) + - [Rails Routes Integration](#rails-routes-integration) + - [Processing](#processing) + - [Validators](#validators) + - [TypeValidator](#typevalidator) + - [RegexpValidator](#regexpvalidator) + - [EnumValidator](#enumvalidator) + - [ProcValidator](#procvalidator) + - [HashValidator](#hashvalidator) + - [NilValidator](#nilvalidator) + - [NumberValidator](#numbervalidator) + - [DecimalValidator](#decimalvalidator) + - [ArrayValidator](#arrayvalidator) + - [NestedValidator](#nestedvalidator) + - [Adding custom validator](#adding-custom-validator) + - [Versioning](#versioning) + - [Markup](#markup) + - [Localization](#localization) + - [Modifying Views](#modifying-views) + - [Static files](#static-files) + - [Static Swagger (OpenAPI 2.0) files](#static-swagger-openapi-20-files) + - [Specifying default values for parameters](#specifying-default-values-for-parameters) + - [Generated Warnings](#generated-warnings) + - [Swagger-Specific Configuration Parameters](#swagger-specific-configuration-parameters) + - [Known limitations of the current implementation](#known-limitations-of-the-current-implementation) + - [Dynamic Swagger generation](#dynamic-swagger-generation) + - [JSON checksums](#json-checksums) + - [Tests Integration](#tests-integration) + - [Documentation Bootstrapping](#documentation-bootstrapping) + - [Examples Recording](#examples-recording) + - [Caveats](#caveats) + - [Bindings Generator](#bindings-generator) + - [Contributing](#contributing) + - [Disqus Integration](#disqus-integration) + - [External References](#external-references) + +# DSL Reference + +## Resource Description + +You can describe a resource on the controller level. The description is +introduced by calling `resource_description do ... end`. + +Inheritance is supported, so you can specify common params for group of +controllers in their parent class. -License -------- +The following keywords are available (all are optional): -Apipie-rails is released under the `MIT License `_ +`resource_id` -=============== - Documentation -=============== +How the resource will be referenced in Apipie (paths, `see` command etc.); by default `controller_name.downcase` is used. -.. contents:: `Table Of Contents` - :depth: 2 +`name` -=============== - DSL Reference -=============== +Human readable name of resource. By default `class.name.humanize` is +used. -Resource Description --------------------- + - Can be specified as a proc, which will receive the controller + class as an argument. + - Can be a symbol, which will be sent to the controller class to + get the name. + - Can be a string, which will be used as is. -You can describe a resource on the controller level. The description is introduced by calling -``resource_description do ... end``. +`short` (also `short_description`) -Inheritance is supported, so you can specify common params for group of controllers in their parent -class. +Short description of the resource (it\'s shown on both the list of resources, and resource details) -The following keywords are available (all are optional): +`desc` (also description and full_description) -resource_id - How the resource will be referenced in Apipie (paths, ``see`` command etc.); by default `controller_name.downcase` is used. +Full description of the resource (shown only in resource details) -name - Human readable name of resource. By default ``class.name.humanize`` is used. +`param` - - Can be specified as a proc, which will receive the controller class as an argument. - - Can be a symbol, which will be sent to the controller class to get the name. - - Can be a string, which will be used as is. +Common params for all methods defined in controller/child controllers. -short (also short_description) - Short description of the resource (it's shown on both the list of resources, and resource details) +`returns` -desc (also description and full_description) - Full description of the resource (shown only in resource details) +Common responses for all methods defined in controller/child controllers. -param - Common params for all methods defined in controller/child controllers. +`api_base_url` -returns - Common responses for all methods defined in controller/child controllers. +What URL is the resource available under. -api_base_url - What URL is the resource available under. +`api_versions` (also api_version) -api_versions (also api_version) - What versions does the controller define the resource. (See `Versioning`_ for details.) +What versions does the controller define the resource. (See [Versioning](#versioning) for details.) -formats - Request / response formats. +`formats` -error - Describe every possible error that can happen when calling all - methods defined in controller. HTTP response code and description can be provided. +Request / response formats. -app_info - In case of versioning, this sets app info description on a per_version basis. +`error` -meta - Hash or array with custom metadata. +Describe every possible error that can happen when calling all +methods defined in controller. HTTP response code and description +can be provided. -deprecated - Boolean value indicating if the resource is marked as deprecated. (Default false) +`app_info` -Example: -~~~~~~~~ - -.. code:: ruby - - resource_description do - short 'Site members' - formats ['json'] - param :id, Integer, :desc => "User ID", :required => false - param :resource_param, Hash, :desc => 'Param description for all methods' do - param :ausername, String, :desc => "Username for login", :required => true - param :apassword, String, :desc => "Password for login", :required => true - end - api_version "development" - error 404, "Missing" - error 500, "Server crashed for some <%= reason %>", :meta => {:anything => "you can think of"} - error :unprocessable_entity, "Could not save the entity." - returns :code => 403 do - property :reason, String, :desc => "Why this was forbidden" - end - meta :author => {:name => 'John', :surname => 'Doe'} - deprecated false - description <<-EOS - == Long description - Example resource for rest api documentation - These can now be accessed in shared/header with: - Headline: <%= headline %> - First name: <%= person.first_name %> - - If you need to find out whether a certain local variable has been - assigned a value in a particular render call, you need to use the - following pattern: - - <% if local_assigns.has_key? :headline %> - Headline: <%= headline %> - <% end %> - - Testing using defined? headline will not work. This is an - implementation restriction. - - === Template caching - - By default, Rails will compile each template to a method in order - to render it. When you alter a template, Rails will check the - file's modification time and recompile it in development mode. - EOS - end +In case of versioning, this sets app info description on a per_version basis. +`meta` -Method Description ------------------- +Hash or array with custom metadata. -Then describe methods available to your API. +`deprecated` -api - Describe how this method is exposed, and provide a short description. - The first parameter is HTTP method (one of :GET/:POST/:PUT/:DELETE). - The second parameter is the relative URL path which is mapped to this - method. The last parameter is the methods short description. - You can use this +api+ method more than once per method. It could - be useful when there are more routes mapped to it. +Boolean value indicating if the resource is marked as deprecated. (Default `false`) - When providing just one argument (description), or no argument at all, - the paths will be loaded from the routes.rb file. +### Example: -api! - Provide a short description and additional option. - The last parameter is the methods short description. - The paths will be loaded from routes.rb file. See - `Rails Routes Integration`_ for more details. +``` ruby +resource_description do + short 'Site members' + formats ['json'] + param :id, Integer, :desc => "User ID", :required => false + param :resource_param, Hash, :desc => 'Param description for all methods' do + param :ausername, String, :desc => "Username for login", :required => true + param :apassword, String, :desc => "Password for login", :required => true + end + api_version "development" + error 404, "Missing" + error 500, "Server crashed for some <%= reason %>", :meta => {:anything => "you can think of"} + error :unprocessable_entity, "Could not save the entity." + returns :code => 403 do + property :reason, String, :desc => "Why this was forbidden" + end + meta :author => {:name => 'John', :surname => 'Doe'} + deprecated false + description <<-EOS + == Long description + Example resource for rest api documentation + These can now be accessed in shared/header with: + Headline: <%= headline %> + First name: <%= person.first_name %> -api_versions (also api_version) - What version(s) does the action belong to. (See `Versioning`_ for details.) + If you need to find out whether a certain local variable has been + assigned a value in a particular render call, you need to use the + following pattern: -param - Look at `Parameter description`_ section for details. + <% if local_assigns.has_key? :headline %> + Headline: <%= headline %> + <% end %> -returns - Look at `Response description`_ section for details. + Testing using defined? headline will not work. This is an + implementation restriction. -tags - Adds tags for grouping operations together in Swagger outputs. See `swagger`_ - for more details. You can also provide tags in the `Resource Description`_ - block so that they are automatically prepended to all action tags in the - controller. + === Template caching -formats - Method level request / response formats. + By default, Rails will compile each template to a method in order + to render it. When you alter a template, Rails will check the + file's modification time and recompile it in development mode. + EOS +end +``` -error - Describe each possible error that can happen while calling this - method. HTTP response code and description can be provided. +## Method Description -description - Full method description, which will be converted into HTML by the - chosen markup language processor. +Then describe methods available to your API. -example - Provide an example of the server response; whole communication or response type. - It will be formatted as code. +`api` -see - Provide reference to another method, this has to be a string with - controller_name#method_name. +Describe how this method is exposed, and provide a short +description. The first parameter is HTTP method (one of +:GET/:POST/:PUT/:DELETE). The second parameter is the relative URL +path which is mapped to this method. The last parameter is the +methods short description. You can use this +api+ method more than +once per method. It could be useful when there are more routes +mapped to it. -meta - Hash or array with custom metadata. +When providing just one argument (description), or no argument at +all, the paths will be loaded from the routes.rb file. -show - Resource is hidden from documentation when set to false (true by default) +`api!` -Example: -~~~~~~~~ +Provide a short description and additional option. The last +parameter is the methods short description. The paths will be loaded +from routes.rb file. See [Rails Routes +Integration](#rails-routes-integration) for more details. -.. code:: ruby +`api_versions` (also api_version) - # The simplest case: just load the paths from routes.rb - api! - def index - end +What version(s) does the action belong to. (See +[Versioning](#versioning) for details.) - # More complex example - api :GET, "/users/:id", "Show user profile" - show false - error :code => 401, :desc => "Unauthorized" - error :code => 404, :desc => "Not Found", :meta => {:anything => "you can think of"} - param :session, String, :desc => "user is logged in", :required => true - param :regexp_param, /^[0-9]* years/, :desc => "regexp param" - param :array_param, [100, "one", "two", 1, 2], :desc => "array validator" - param :boolean_param, [true, false], :desc => "array validator with boolean" - param :proc_param, lambda { |val| - val == "param value" ? true : "The only good value is 'param value'." - }, :desc => "proc validator" - param :param_with_metadata, String, :desc => "", :meta => [:your, :custom, :metadata] - returns :code => 200, :desc => "a successful response" do - property :value1, String, :desc => "A string value" - property :value2, Integer, :desc => "An integer value" - property :value3, Hash, :desc => "An object" do - property :enum1, ['v1', 'v2'], :desc => "One of 2 possible string values" - end - end - tags %w[profiles logins] - tags 'more', 'related', 'resources' - description "method description" - formats ['json', 'jsonp', 'xml'] - meta :message => "Some very important info" - example " 'user': {...} " - see "users#showme", "link description" - see :link => "users#update", :desc => "another link description" - def show - #... - end +`param` -Parameter Description ---------------------- +Look at [Parameter description](#parameter-description) section for +details. -Use ``param`` to describe every possible parameter. You can use the Hash validator -in conjunction with a block given to the param method to describe nested parameters. +`returns` -name - The first argument is the parameter name as a symbol. +Look at [Response description](#response-description) section for +details. -validator - Second parameter is the parameter validator, choose one from section `Validators`_ +`tags` -desc - Parameter description. +Adds tags for grouping operations together in Swagger outputs. See +[swagger](#Swagger) for more details. You can also provide tags in +the [Resource Description](#resource-description) block so that they +are automatically prepended to all action tags in the controller. -required - Set this true/false to make it required/optional. Default is optional +`formats` -example - Provide the example for this parameter. +Method level request / response formats. -allow_nil - Setting this to true means that ``nil`` can be passed. +`error` -allow_blank - Like ``allow_nil``, but for blank values. ``false``, ``""``, ``' '``, ``nil``, ``[]``, and ``{}`` are all blank. +Describe each possible error that can happen while calling this + method. HTTP response code and description can be provided. -as - Used by the processing functionality to change the name of a key params. +`description` -meta - Hash or array with custom metadata. +Full method description, which will be converted into HTML by the + chosen markup language processor. -show - Parameter is hidden from documentation when set to false (true by default) +`example` -missing_message - Specify the message to be returned if the parameter is missing as a string or Proc. - Defaults to ``Missing parameter #{name}`` if not specified. +Provide an example of the server response; whole communication or + response type. It will be formatted as code. -only_in - This can be set to ``:request`` or ``:response``. - Setting to ``:response`` causes the param to be ignored when used as part of a request description. - Setting to ``:request`` causes this param to be ignored when used as part of a response description. - If ``only_in`` is not specified, the param definition is used for both requests and responses. - (Note that the keyword ``property`` is similar to ``param``, but it has a ``:only_in => :response`` default). +`see` -Example: -~~~~~~~~ +Provide reference to another method, this has to be a string with + controller_name#method_name. -.. code:: ruby +`meta` - param :user, Hash, :desc => "User info" do - param :username, String, :desc => "Username for login", :required => true, :example => 'John' - param :password, String, :desc => "Password for login", :required => true, :example => '1234567' - param :membership, ["standard","premium"], :desc => "User membership" - param :admin_override, String, :desc => "Not shown in documentation", :show => false - param :ip_address, String, :desc => "IP address", :required => true, :missing_message => lambda { I18n.t("ip_address.required") } - end - def create - #... - end +Hash or array with custom metadata. -deprecated - Indicates if the parameter is marked as deprecated. +`show` -Example -~~~~~~~~ +Resource is hidden from documentation when set to false (true by + default) -.. code:: ruby +### Example: - param :pet_name, String, desc: "Name of pet", deprecated: true - param :pet_name, String, desc: "Name of pet", deprecated: 'Some deprecation info' - param :pet_name, String, desc: "Name of pet", deprecated: { in: "2.3", info: "Something", sunset: "3.0" } - def create - #... +``` ruby +# The simplest case: just load the paths from routes.rb +api! +def index +end + +# More complex example +api :GET, "/users/:id", "Show user profile" +show false +error :code => 401, :desc => "Unauthorized" +error :code => 404, :desc => "Not Found", :meta => {:anything => "you can think of"} +param :session, String, :desc => "user is logged in", :required => true +param :regexp_param, /^[0-9]* years/, :desc => "regexp param" +param :array_param, [100, "one", "two", 1, 2], :desc => "array validator" +param :boolean_param, [true, false], :desc => "array validator with boolean" +param :proc_param, lambda { |val| + val == "param value" ? true : "The only good value is 'param value'." +}, :desc => "proc validator" +param :param_with_metadata, String, :desc => "", :meta => [:your, :custom, :metadata] +returns :code => 200, :desc => "a successful response" do + property :value1, String, :desc => "A string value" + property :value2, Integer, :desc => "An integer value" + property :value3, Hash, :desc => "An object" do + property :enum1, ['v1', 'v2'], :desc => "One of 2 possible string values" end +end +tags %w[profiles logins] +tags 'more', 'related', 'resources' +description "method description" +formats ['json', 'jsonp', 'xml'] +meta :message => "Some very important info" +example " 'user': {...} " +see "users#showme", "link description" +see :link => "users#update", :desc => "another link description" +def show + #... +end +``` +## Parameter Description -DRY with param_group --------------------- - -Often, params occur together in more actions. Typically, most of the -params for ``create`` and ``update`` actions are shared between them. - -These params can be extracted with ``def_param_group`` and -``param_group`` keywords. - -The definition is looked up in the scope of the controller. If the -group is defined in a different controller, it might be referenced by -specifying the second argument. +Use `param` to describe every possible parameter. You can use the Hash +validator in conjunction with a block given to the param method to +describe nested parameters. -Example: -~~~~~~~~ +`name` -.. code:: ruby +The first argument is the parameter name as a symbol. - # v1/users_controller.rb - def_param_group :address do - param :street, String - param :number, Integer - param :zip, String - end +`validator` - def_param_group :user do - param :user, Hash do - param :name, String, "Name of the user" - param_group :address - end - end +Second parameter is the parameter validator, choose one from section + [Validators](#validators) - api :POST, "/users", "Create a user" - param_group :user - def create - # ... - end +`desc` - api :PUT, "/users/:id", "Update a user" - param_group :user - def update - # ... - end +Parameter description. - # v2/users_controller.rb - api :POST, "/users", "Create a user" - param_group :user, V1::UsersController - def create - # ... - end +`required` -Action Aware params -------------------- +Set this true/false to make it required/optional. Default is + optional -In CRUD operations, this pattern occurs quite often - params that need -to be set are: +`example` -* for create action: ``required => true`` and ``allow_nil => false`` -* for update action: ``required => false`` and ``allow_nil => false`` +Provide the example for this parameter. -This makes it hard to share the param definitions across theses -actions. Therefore, you can make the description a bit smarter by -setting ``:action_aware => true``. +`allow_nil` -You can specify explicitly how the param group should be evaluated -with ``:as`` option (either :create or :update) +Setting this to true means that `nil` can be passed. -Example -~~~~~~~ +`allow_blank` -.. code:: ruby +Like `allow_nil`, but for blank values. `false`, `""`, `' '`, `nil`, + `[]`, and `{}` are all blank. - def_param_group :user do - param :user, Hash, :action_aware => true do - param :name, String, :required => true - param :description, String - end - end +`as` - api :POST, "/users", "Create a user" - param_group :user - def create - # ... - end +Used by the processing functionality to change the name of a key params. - api :PUT, "/users/admin", "Create an admin" - param_group :user, :as => :create - def create_admin - # ... - end +`meta` - api :PUT, "/users/:id", "Update a user" - param_group :user - def update - # ... - end +Hash or array with custom metadata. -In this case, ``user[name]`` will be not be allowed nil for all -actions and required only for ``create`` and ``create_admin``. Params -with ``allow_nil`` set explicitly don't have this value changed. +`show` -Action awareness is inherited from ancestors (in terms of -nested params). +Parameter is hidden from documentation when set to false (true by default) +`missing_message` -Response Description --------------------- +Specify the message to be returned if the parameter is missing as a +string or Proc. Defaults to `Missing parameter #{name}` if not +specified. -The response from an API call can be documented by adding a ``returns`` statement to the method -description. This is especially useful when using Apipie to auto-generate a machine-readable Swagger -definition of your API (see the `swagger`_ section for more details). +`only_in` -A ``returns`` statement has several possible formats: +This can be set to `:request` or `:response`. Setting to `:response` +causes the param to be ignored when used as part of a request +description. Setting to `:request` causes this param to be ignored +when used as part of a response description. If `only_in` is not +specified, the param definition is used for both requests and +responses. (Note that the keyword `property` is similar to `param`, +but it has a `:only_in => :response` default). -.. code:: ruby +### Example: - # format #1: reference to a param-group - returns [, :code => |] [, :desc => ] +``` ruby +param :user, Hash, :desc => "User info" do + param :username, String, :desc => "Username for login", :required => true, :example => 'John' + param :password, String, :desc => "Password for login", :required => true, :example => '1234567' + param :membership, ["standard","premium"], :desc => "User membership" + param :admin_override, String, :desc => "Not shown in documentation", :show => false + param :ip_address, String, :desc => "IP address", :required => true, :missing_message => lambda { I18n.t("ip_address.required") } +end +def create + #... +end +``` - # format #2: inline response definition - returns :code => | [, :desc => ] do - # property ... - # property ... - # param_group ... - end +`deprecated` - # format #3: describing an array-of-objects response - returns :array_of => [, :code => |] [, :desc => ] +Indicates if the parameter is marked as deprecated. +### Example -If the ``:code`` argument is ommitted, ``200`` is used. +``` ruby +param :pet_name, String, desc: "Name of pet", deprecated: true +param :pet_name, String, desc: "Name of pet", deprecated: 'Some deprecation info' +param :pet_name, String, desc: "Name of pet", deprecated: { in: "2.3", info: "Something", sunset: "3.0" } +def create + #... +end +``` +## DRY with param_group -Example -~~~~~~~ +Often, params occur together in more actions. Typically, most of the +params for `create` and `update` actions are shared between them. -.. code:: ruby +These params can be extracted with `def_param_group` and `param_group` +keywords. - # ------------------------------------------------ - # Example of format #1 (reference to param-group): - # ------------------------------------------------ - # the param_group :pet is defined here to describe the output returned by the method below. - def_param_group :pet do - property :pet_name, String, :desc => "Name of pet" - property :animal_type, ['dog','cat','iguana','kangaroo'], :desc => "Type of pet" - end +The definition is looked up in the scope of the controller. If the group +is defined in a different controller, it might be referenced by +specifying the second argument. - api :GET, "/pets/:id", "Get a pet record" - returns :pet, :desc => "The pet" - def show_detailed - render JSON({:pet_name => "Skippie", :animal_type => "kangaroo"}) - end +### Example: - # ------------------------------------------------ - # Example of format #2 (inline): - # ------------------------------------------------ - api :GET, "/pets/:id/with-extra-details", "Get a detailed pet record" - returns :code => 200, :desc => "Detailed info about the pet" do - param_group :pet - property :num_legs, Integer, :desc => "How many legs the pet has" - end - def show - render JSON({:pet_name => "Barkie", :animal_type => "iguana", :legs => 4}) - end +``` ruby +# v1/users_controller.rb +def_param_group :address do + param :street, String + param :number, Integer + param :zip, String +end - # ------------------------------------------------ - # Example of format #3 (array response): - # ------------------------------------------------ - api :GET, "/pets", "Get all pet records" - returns :array_of => :pet, :code => 200, :desc => "All pets" - def index - render JSON([ {:pet_name => "Skippie", :animal_type => "kangaroo"}, - {:pet_name => "Woofie", :animal_type => "cat"} ]) +def_param_group :user do + param :user, Hash do + param :name, String, "Name of the user" + param_group :address end +end + +api :POST, "/users", "Create a user" +param_group :user +def create + # ... +end + +api :PUT, "/users/:id", "Update a user" +param_group :user +def update + # ... +end + +# v2/users_controller.rb +api :POST, "/users", "Create a user" +param_group :user, V1::UsersController +def create + # ... +end +``` + +## Action Aware params +In CRUD operations, this pattern occurs quite often - params that need +to be set are: -Note the use of the ``property`` keyword rather than ``param``. This is the -preferred mechanism for documenting response-only fields. +- for create action: `required => true` and `allow_nil => false` +- for update action: `required => false` and `allow_nil => false` +This makes it hard to share the param definitions across theses actions. +Therefore, you can make the description a bit smarter by setting +`:action_aware => true`. -The Property keyword -:::::::::::::::::::::::::::::::::::::::::::::::: +You can specify explicitly how the param group should be evaluated with +`:as` option (either :create or :update) -``property`` is very similar to ``param`` with the following differences: +### Example -* a ``property`` is ``:only_in => :response`` by default +``` ruby +def_param_group :user do + param :user, Hash, :action_aware => true do + param :name, String, :required => true + param :description, String + end +end + +api :POST, "/users", "Create a user" +param_group :user +def create + # ... +end + +api :PUT, "/users/admin", "Create an admin" +param_group :user, :as => :create +def create_admin + # ... +end + +api :PUT, "/users/:id", "Update a user" +param_group :user +def update + # ... +end +``` + +In this case, `user[name]` will be not be allowed nil for all actions +and required only for `create` and `create_admin`. Params with +`allow_nil` set explicitly don\'t have this value changed. + +Action awareness is inherited from ancestors (in terms of nested +params). + +## Response Description + +The response from an API call can be documented by adding a `returns` +statement to the method description. This is especially useful when +using Apipie to auto-generate a machine-readable Swagger definition of +your API (see the [swagger](#Swagger) section for more details). + +A `returns` statement has several possible formats: + +``` ruby +# format #1: reference to a param-group +returns [, :code => |] [, :desc => ] + +# format #2: inline response definition +returns :code => | [, :desc => ] do + # property ... + # property ... + # param_group ... +end + +# format #3: describing an array-of-objects response +returns :array_of => [, :code => |] [, :desc => ] +``` + +If the `:code` argument is ommitted, `200` is used. + +### Example + +``` ruby +# ------------------------------------------------ +# Example of format #1 (reference to param-group): +# ------------------------------------------------ +# the param_group :pet is defined here to describe the output returned by the method below. +def_param_group :pet do + property :pet_name, String, :desc => "Name of pet" + property :animal_type, ['dog','cat','iguana','kangaroo'], :desc => "Type of pet" +end + +api :GET, "/pets/:id", "Get a pet record" +returns :pet, :desc => "The pet" +def show_detailed + render JSON({:pet_name => "Skippie", :animal_type => "kangaroo"}) +end + +# ------------------------------------------------ +# Example of format #2 (inline): +# ------------------------------------------------ +api :GET, "/pets/:id/with-extra-details", "Get a detailed pet record" +returns :code => 200, :desc => "Detailed info about the pet" do + param_group :pet + property :num_legs, Integer, :desc => "How many legs the pet has" +end +def show + render JSON({:pet_name => "Barkie", :animal_type => "iguana", :legs => 4}) +end + +# ------------------------------------------------ +# Example of format #3 (array response): +# ------------------------------------------------ +api :GET, "/pets", "Get all pet records" +returns :array_of => :pet, :code => 200, :desc => "All pets" +def index + render JSON([ {:pet_name => "Skippie", :animal_type => "kangaroo"}, + {:pet_name => "Woofie", :animal_type => "cat"} ]) +end +``` + +Note the use of the `property` keyword rather than `param`. This is the +preferred mechanism for documenting response-only fields. -* a ``property`` is ``:required => :true`` by default +#### The Property keyword -* a ``property`` can be an ``:array_of`` objects +`property` is very similar to `param` with the following differences: -Example -_______ -.. code:: ruby +- a `property` is `:only_in => :response` by default +- a `property` is `:required => :true` by default +- a `property` can be an `:array_of` objects - property :example, :array_of => Hash do - property :number1, Integer - property :number2, Integer - end +##### Example +``` ruby +property :example, :array_of => Hash do + property :number1, Integer + property :number2, Integer +end +``` -Describing multiple return codes -:::::::::::::::::::::::::::::::::::::::::::::::: +#### Describing multiple return codes -To describe multiple possible return codes, the ``:returns`` keyword can be repeated as many times as necessary -(once for each return code). Each one of the ``:returns`` entries can specify a different response format. +To describe multiple possible return codes, the `:returns` keyword can +be repeated as many times as necessary (once for each return code). Each +one of the `:returns` entries can specify a different response format. -Example -_______ +##### Example -.. code:: ruby +``` ruby +api :GET, "/pets/:id/extra_info", "Get extra information about a pet" + returns :desc => "Found a pet" do + param_group :pet + property 'pet_history', Hash do + param_group :pet_history + end + end + returns :code => :unprocessable_entity, :desc => "Fleas were discovered on the pet" do + param_group :pet + property :num_fleas, Integer, :desc => "Number of fleas on this pet" + end + def show_extra_info + # ... implementation here + end +``` + +#### Reusing a param_group to describe inputs and outputs + +In many cases (such as CRUD implementations), the output from certain +API calls is very similar - but not identical - to the inputs of the +same or other API calls. + +If you already have a `:param_group` that defines the input to a +`create` or `update routine, it would be quite +frustrating to have to define a completely separate `:param_group` to +describe the output of the `show` routine. + +To address such situations, it is possible to define a single +`:param_group` which combines `param` and `property` statements (as well +as `:only_in => :request` / `:only_in => :response`) to differentiate +between fields that are only expected in the request, only included in +the response, or common to both. + +This is somewhat analogous to the way [Action Aware +params](#action-aware-params) work. + +##### Example + +``` ruby +def_param_group :user_record + param :name, String # this is commong to both the request and the response + param :force_update, [true, false], :only_in => :request # this does not show up in responses + property :last_login, String # this shows up only in the response +end + +api :POST, "/users", "Create a user" +param_group :user_record # the :last_login field is not expected here, but :force_update is +def create + # ... +end + +api :GET, "/users", "Create a user" +returns :array_of => :user_record # the :last_login field will be included in the response, but :force_update will not +def index + # ... +end +``` + +#### Embedded response descriptions + +If the code creating JSON responses is encapsulated within dedicated +classes, it can be more convenient to place the response descriptions +outside of the controller and embed them within the response generator. + +To support such use cases, Apipie allows any class to provide a +`describe_own_properties` class method which returns a +description of the properties such a class would expose. It is then +possible to specify that class in the `returns` statement +instead of a `param_group`. + +The `describe_own_properties` method is expected to return +an array of `Apipie::prop` objects, each one describing a +single property. + +##### Example + +``` ruby +class Pet + # this method is automatically called by Apipie when Pet is specified as the returned object type + def self.describe_own_properties + [ + Apipie::prop(:pet_name, 'string', {:description => 'Name of pet', :required => false}), + Apipie::prop(:animal_type, 'string', {:description => 'Type of pet', :values => ["dog", "cat", "iguana", "kangaroo"]}), + Apipie::additional_properties(false) # this indicates that :pet_name and :animal_type are the only properties in the response + ] + end - api :GET, "/pets/:id/extra_info", "Get extra information about a pet" - returns :desc => "Found a pet" do - param_group :pet - property 'pet_history', Hash do - param_group :pet_history - end - end - returns :code => :unprocessable_entity, :desc => "Fleas were discovered on the pet" do - param_group :pet - property :num_fleas, Integer, :desc => "Number of fleas on this pet" - end - def show_extra_info - # ... implementation here - end + # this method w + def json + JSON({:pet_name => @name, :animal_type => @type }) + end +end +class PetsController + api :GET, "/index", "Get all pets" + returns :array_of => Pet # Pet is a 'self-describing-class' + def index + # ... + end +end +``` -Reusing a param_group to describe inputs and outputs -:::::::::::::::::::::::::::::::::::::::::::::::::::: +A use case where this is very useful is when JSON generation is done +using a reflection mechanism or some other sort of declarative +mechanism. -In many cases (such as CRUD implementations), the output from certain API calls is very similar - but not -identical - to the inputs of the same or other API calls. +The `Apipie::prop` function expects the following inputs: -If you already have a ``:param_group`` that defines the input to a `create` or `update` routine, it would be quite -frustrating to have to define a completely separate ``:param_group`` to describe the output of the `show` routine. +``` ruby +Apipie::prop(, , [, ]) + +# property-name should be a symbol +# +# property-type can be any of the following strings: +# "integer": maps to a swagger "integer" with an "int32" format +# "long": maps to a swagger "integer" with an "int64" format +# "number": maps to a swagger "number"(no format specifier) +# "float": maps to a swagger "number" with a "float" format +# "double": maps to a swagger "number" with a "double" format +# "string": maps to a swagger "string" (no format specifier) +# "byte": maps to a swagger "string" with a "byte" format +# "binary": maps to a swagger "string" with a "binary" format +# "boolean": maps to a swagger "boolean" (no format specifier) +# "date": maps to a swagger "string" with a "date" format +# "dateTime": maps to a swagger "string" with a "date-time" format +# "password": maps to a swagger "string" with a "password" format +# "object": the property has sub-properties. include in the call. +# (see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more information +# about the mapped swagger types) +# +# options-hash can include any of the options fields allowed in a :returns statement. +# additionally, it can include the ':is_array => true', in which case the property is understood to be +# an array of the described type. +``` -To address such situations, it is possible to define a single ``:param_group`` which combines ``param`` and ``property`` -statements (as well as ``:only_in => :request`` / ``:only_in => :response``) to differentiate between fields that are -only expected in the request, only included in the response, or common to both. +To describe an embedded object: -This is somewhat analogous to the way `Action Aware params`_ work. +``` ruby +# +# PetWithMeasurements is a self-describing class with an embedded object +# +class PetWithMeasurements + def self.describe_own_properties + [ + Apipie::prop(:pet_name, 'string', {:description => 'Name of pet', :required => false}), + Apipie::prop('animal_type', 'string', {:description => 'Type of pet', :values => ["dog", "cat", "iguana", "kangaroo"]}), + Apipie::prop(:pet_measurements, 'object', {}, [ + Apipie::prop(:weight, 'number', {:description => "Weight in pounds" }), + Apipie::prop(:height, 'number', {:description => "Height in inches" }), + Apipie::prop(:num_legs, 'number', {:description => "Number of legs", :required => false }), + Apipie::additional_properties(false) + ]) + ] + end +end + +# +# PetWithManyMeasurements is a self-describing class with an embedded array of objects +# +class PetWithManyMeasurements + def self.describe_own_properties + [ + Apipie::prop(:pet_name, 'string', {:description => 'Name of pet', :required => false}), + Apipie::prop(:many_pet_measurements, 'object', {is_array: true}, [ + Apipie::prop(:weight, 'number', {:description => "Weight in pounds" }), + Apipie::prop(:height, 'number', {:description => "Height in inches" }), + ]) + ] + end +end +``` -Example -_______ +## Concerns -.. code:: ruby +Sometimes, the actions are not defined in the controller class directly +but included from a module instead. You can load the Apipie DSL into the +module by extending it with `Apipie::DSL::Concern`. - def_param_group :user_record - param :name, String # this is commong to both the request and the response - param :force_update, [true, false], :only_in => :request # this does not show up in responses - property :last_login, String # this shows up only in the response - end +The module can be used in more controllers. Therefore there is a way to +substitute parts of the documentation in the module with controller +specific values. These substitutions can be stated explicitly with +`apipie_concern_subst(:key => "value")` (needs to be called before the +module is included to take effect). The substitutions are performed in +the paths and descriptions of APIs and names and descriptions of params. - api :POST, "/users", "Create a user" - param_group :user_record # the :last_login field is not expected here, but :force_update is - def create - # ... - end +There are some default substitutions available: - api :GET, "/users", "Create a user" - returns :array_of => :user_record # the :last_login field will be included in the response, but :force_update will not - def index - # ... - end +`:controller_path` +value of `controller.controller_path`, e.g. `api/users` for + `Api::UsersController`. Only if not using the `api!` keyword. -Embedded response descriptions -:::::::::::::::::::::::::::::: +`:resource_id` -If the code creating JSON responses is encapsulated within dedicated classes, it can be more convenient to -place the response descriptions outside of the controller and embed them within the response generator. +Apipie identifier of the resource, e.g. `users` for + `Api::UsersController` or set by `resource_id` -To support such use cases, Apipie allows any class to provide a `describe_own_properties` class method which -returns a description of the properties such a class would expose. It is then possible to specify that -class in the `returns` statement instead of a `param_group`. +### Example -The `describe_own_properties` method is expected to return an array of `Apipie::prop` objects, each one -describing a single property. +``` ruby +# users_module.rb +module UsersModule + extend Apipie::DSL::Concern -Example -_______ + api :GET, '/:controller_path', 'List :resource_id' + def index + # ... + end -.. code:: ruby + api! 'Show a :resource' + def show + # ... + end - class Pet - # this method is automatically called by Apipie when Pet is specified as the returned object type - def self.describe_own_properties - [ - Apipie::prop(:pet_name, 'string', {:description => 'Name of pet', :required => false}), - Apipie::prop(:animal_type, 'string', {:description => 'Type of pet', :values => ["dog", "cat", "iguana", "kangaroo"]}), - Apipie::additional_properties(false) # this indicates that :pet_name and :animal_type are the only properties in the response - ] - end + api :POST, '/:resource_id', "Create a :resource" + param :concern, Hash, :required => true + param :name, String, 'Name of a :resource' + param :resource_type, ['standard','vip'] + end + def create + # ... + end - # this method w - def json - JSON({:pet_name => @name, :animal_type => @type }) + api :GET, '/:resource_id/:custom_subst' + def custom + # ... + end +end + +# users_controller.rb +class UsersController < ApplicationController + + resource_description { resource_id 'customers' } + + apipie_concern_subst(:custom_subst => 'custom', :resource => 'customer') + include UsersModule + + # the following paths are documented + # api :GET, '/users' + # api :GET, '/customers/:id', 'Show a customer' + # api :POST, '/customers', 'Create a customer' + # param :customer, :required => true do + # param :name, String, 'Name of a customer' + # param :customer_type, ['standard', 'vip'] + # end + # api :GET, '/customers/:custom' +end +``` + +Sometimes, it\'s needed to extend an existing controller method with +additional parameters (usually when extending exiting API from +plugins/rails engines). The concern can be also used for this purposed, +using `update_api` method. The params defined in this block +are merged with the params of the original method in the controller this +concern is included to. + +### Example + +``` ruby +module Concerns + module OauthConcern + extend Apipie::DSL::Concern + + update_api(:create, :update) do + param :user, Hash do + param :oauth, String, :desc => 'oauth param' end end + end +end +``` +The concern needs to be included to the controller after the methods are +defined (either at the end of the class, or by using +`Controller.send(:include, Concerns::OauthConcern)`. - class PetsController - api :GET, "/index", "Get all pets" - returns :array_of => Pet # Pet is a 'self-describing-class' - def index - # ... - end - end - - -A use case where this is very useful is when JSON generation is done using a reflection mechanism or some -other sort of declarative mechanism. - - +## Response validation +The swagger definitions created by Apipie can be used to auto-generate +clients that access the described APIs. Those clients will break if the +responses returned from the API do not match the declarations. As such, +it is very important to include unit tests that validate the actual +responses against the swagger definitions. -The `Apipie::prop` function expects the following inputs: +The implemented mechanism provides two ways to include such validations +in RSpec unit tests: manual (using an RSpec matcher) and automated (by +injecting a test into the http operations \'get\', \'post\', raising an +error if there is no match). -.. code:: ruby - - Apipie::prop(, , [, ]) - - # property-name should be a symbol - # - # property-type can be any of the following strings: - # "integer": maps to a swagger "integer" with an "int32" format - # "long": maps to a swagger "integer" with an "int64" format - # "number": maps to a swagger "number"(no format specifier) - # "float": maps to a swagger "number" with a "float" format - # "double": maps to a swagger "number" with a "double" format - # "string": maps to a swagger "string" (no format specifier) - # "byte": maps to a swagger "string" with a "byte" format - # "binary": maps to a swagger "string" with a "binary" format - # "boolean": maps to a swagger "boolean" (no format specifier) - # "date": maps to a swagger "string" with a "date" format - # "dateTime": maps to a swagger "string" with a "date-time" format - # "password": maps to a swagger "string" with a "password" format - # "object": the property has sub-properties. include in the call. - # (see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more information - # about the mapped swagger types) - # - # options-hash can include any of the options fields allowed in a :returns statement. - # additionally, it can include the ':is_array => true', in which case the property is understood to be - # an array of the described type. +### Example of the manual mechanism: +``` ruby +require 'apipie/rspec/response_validation_helper' +RSpec.describe MyController, :type => :controller, :show_in_doc => true do -To describe an embedded object: + describe "GET stuff with response validation" do + render_views # this makes sure the 'get' operation will actually + # return the rendered view even though this is a Controller spec -.. code:: ruby - - - # - # PetWithMeasurements is a self-describing class with an embedded object - # - class PetWithMeasurements - def self.describe_own_properties - [ - Apipie::prop(:pet_name, 'string', {:description => 'Name of pet', :required => false}), - Apipie::prop('animal_type', 'string', {:description => 'Type of pet', :values => ["dog", "cat", "iguana", "kangaroo"]}), - Apipie::prop(:pet_measurements, 'object', {}, [ - Apipie::prop(:weight, 'number', {:description => "Weight in pounds" }), - Apipie::prop(:height, 'number', {:description => "Height in inches" }), - Apipie::prop(:num_legs, 'number', {:description => "Number of legs", :required => false }), - Apipie::additional_properties(false) - ]) - ] - end - end + it "does something" do + response = get :index, {format: :json} - # - # PetWithManyMeasurements is a self-describing class with an embedded array of objects - # - class PetWithManyMeasurements - def self.describe_own_properties - [ - Apipie::prop(:pet_name, 'string', {:description => 'Name of pet', :required => false}), - Apipie::prop(:many_pet_measurements, 'object', {is_array: true}, [ - Apipie::prop(:weight, 'number', {:description => "Weight in pounds" }), - Apipie::prop(:height, 'number', {:description => "Height in inches" }), - ]) - ] - end + # the following expectation will fail if the returned object + # does not match the 'returns' declaration in the Controller, + # or if there is no 'returns' declaration for the returned + # HTTP status code + expect(response).to match_declared_responses end + end +end +``` +### Example of the automated mechanism: +``` ruby +require 'apipie/rspec/response_validation_helper' -Concerns --------- - -Sometimes, the actions are not defined in the controller class -directly but included from a module instead. You can load the Apipie -DSL into the module by extending it with ``Apipie::DSL::Concern``. - -The module can be used in more controllers. Therefore there is a way to -substitute parts of the documentation in the module with controller -specific values. These substitutions can be stated explicitly with -``apipie_concern_subst(:key => "value")`` (needs to be called before -the module is included to take effect). The substitutions are -performed in the paths and descriptions of APIs and names and descriptions -of params. +RSpec.describe MyController, :type => :controller, :show_in_doc => true do -There are some default substitutions available: + describe "GET stuff with response validation" do + render_views + auto_validate_rendered_views -:controller_path - value of ``controller.controller_path``, e.g. ``api/users`` for - ``Api::UsersController``. Only if not using the ``api!`` keyword. - -:resource_id - Apipie identifier of the resource, e.g. ``users`` for - ``Api::UsersController`` or set by ``resource_id`` - -Example -~~~~~~~ - -.. code:: ruby - - # users_module.rb - module UsersModule - extend Apipie::DSL::Concern - - api :GET, '/:controller_path', 'List :resource_id' - def index - # ... - end - - api! 'Show a :resource' - def show - # ... - end - - api :POST, '/:resource_id', "Create a :resource" - param :concern, Hash, :required => true - param :name, String, 'Name of a :resource' - param :resource_type, ['standard','vip'] - end - def create - # ... - end - - api :GET, '/:resource_id/:custom_subst' - def custom - # ... - end - end + it "does something" do + get :index, {format: :json} + end + it "does something else" do + get :another_index, {format: :json} + end + end - # users_controller.rb - class UsersController < ApplicationController + describe "GET stuff without response validation" do + it "does something" do + get :index, {format: :json} + end + it "does something else" do + get :another_index, {format: :json} + end + end +end +``` - resource_description { resource_id 'customers' } +# Configuration Reference - apipie_concern_subst(:custom_subst => 'custom', :resource => 'customer') - include UsersModule +Create a configuration file in e.g. `/config/initializers/apipie.rb`. +You can set the application name, footer text, API and documentation +base URL and turn off validations. You can also choose your favorite +markup language for full descriptions. - # the following paths are documented - # api :GET, '/users' - # api :GET, '/customers/:id', 'Show a customer' - # api :POST, '/customers', 'Create a customer' - # param :customer, :required => true do - # param :name, String, 'Name of a customer' - # param :customer_type, ['standard', 'vip'] - # end - # api :GET, '/customers/:custom' - end +`app_name` +Name of your application; used in breadcrumbs navigation. -Sometimes, it's needed to extend an existing controller method with additional -parameters (usually when extending exiting API from plugins/rails engines). -The concern can be also used for this purposed, using `update_api` method. -The params defined in this block are merged with the params of the original method -in the controller this concern is included to. +`copyright` -Example -~~~~~~~ +Copyright information (shown in page footer). -.. code:: ruby +`compress_examples` - module Concerns - module OauthConcern - extend Apipie::DSL::Concern +If `true` recorded examples are compressed using `Zlib`. Useful for + big test-suits. - update_api(:create, :update) do - param :user, Hash do - param :oauth, String, :desc => 'oauth param' - end - end - end - end +`doc_base_url` -The concern needs to be included to the controller after the methods are defined -(either at the end of the class, or by using -``Controller.send(:include, Concerns::OauthConcern)``. +Documentation frontend base url. +`api_base_url` -Response validation -------------------- +Base url for default version of your API. To set it for specific + version use `config.api_base_url[version] = url`. -The swagger definitions created by Apipie can be used to auto-generate clients that access the -described APIs. Those clients will break if the responses returned from the API do not match -the declarations. As such, it is very important to include unit tests that validate the actual -responses against the swagger definitions. +`default_version` -The implemented mechanism provides two ways to include such validations in RSpec unit tests: -manual (using an RSpec matcher) and automated (by injecting a test into the http operations 'get', 'post', -raising an error if there is no match). +Default API version to be used (1.0 by default) -Example of the manual mechanism: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`validate` -.. code:: ruby +Parameters validation is turned off when set to false. When set to + `:explicitly`, you must invoke parameter validation yourself by + calling controller method `apipie_validations` (typically in a + before_action). When set to `:implicitly` (or just true), your + controller\'s action methods are wrapped with generated methods + which call `apipie_validations`, and then call the action method. + (`:implicitly` by default) - require 'apipie/rspec/response_validation_helper' +`validate_value` - RSpec.describe MyController, :type => :controller, :show_in_doc => true do +Check the value of params against specified validators (true by default) - describe "GET stuff with response validation" do - render_views # this makes sure the 'get' operation will actually - # return the rendered view even though this is a Controller spec +`validate_presence` - it "does something" do - response = get :index, {format: :json} +Check the params presence against the documentation. - # the following expectation will fail if the returned object - # does not match the 'returns' declaration in the Controller, - # or if there is no 'returns' declaration for the returned - # HTTP status code - expect(response).to match_declared_responses - end - end - end +`validate_key` +Check the received params to ensure they are defined in the API. + (false by default) -Example of the automated mechanism: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`action_on_non_validated_keys` -.. code:: ruby +Either `:raise` or `:skip`. If `validate_key` fails, raise error or +delete the non-validated key from the params and log the key (`:raise` by default) - require 'apipie/rspec/response_validation_helper' +`process_params` - RSpec.describe MyController, :type => :controller, :show_in_doc => true do +Process and extract the parameter defined from the params of the + request to the api_params variable - describe "GET stuff with response validation" do - render_views - auto_validate_rendered_views +`app_info` - it "does something" do - get :index, {format: :json} - end - it "does something else" do - get :another_index, {format: :json} - end - end +Application long description. - describe "GET stuff without response validation" do - it "does something" do - get :index, {format: :json} - end - it "does something else" do - get :another_index, {format: :json} - end - end - end +`reload_controllers` +Set to enable/disable reloading controllers (and the documentation + with it). Enabled by default in development. -========================= - Configuration Reference -========================= +`api_controllers_matcher` -Create a configuration file in e.g. ``/config/initializers/apipie.rb``. -You can set the application name, footer text, API and documentation base URL -and turn off validations. You can also choose your favorite markup language -for full descriptions. +For reloading to work properly you need to specify where your API + controllers are. Can be an array if multiple paths are needed -app_name - Name of your application; used in breadcrumbs navigation. +`api_action_matcher` -copyright - Copyright information (shown in page footer). +Determines the strategy to identity the correct controller action. + Needs to be a class that implements a `.call(controller)` method -compress_examples - If ``true`` recorded examples are compressed using ``Zlib``. Useful for big test-suits. +`api_routes` -doc_base_url - Documentation frontend base url. +Set if your application uses a custom API router, different from the + Rails default -api_base_url - Base url for default version of your API. To set it for specific version use ``config.api_base_url[version] = url``. +`routes_formatter` -default_version - Default API version to be used (1.0 by default) +An object providing the translation from the Rails routes to the + format usable in the documentation when using the `api!` + keyword. By default, the `Apipie::RoutesFormatter` is used. -validate - Parameters validation is turned off when set to false. When set to - ``:explicitly``, you must invoke parameter validation yourself by calling - controller method ``apipie_validations`` (typically in a before_action). - When set to ``:implicitly`` (or just true), your controller's action - methods are wrapped with generated methods which call ``apipie_validations``, - and then call the action method. (``:implicitly`` by default) +`markup` -validate_value - Check the value of params against specified validators (true by - default) +You can choose markup language for descriptions of your application, + resources and methods. RDoc is the default but you can choose from + Apipie::Markup::Markdown.new or Apipie::Markup::Textile.new. In + order to use Markdown you need Maruku gem and for Textile you need + RedCloth. Add those to your gemfile and run bundle if you want to + use them. You can also add any other markup language processor. -validate_presence - Check the params presence against the documentation. +`layout` -validate_key - Check the received params to ensure they are defined in the API. (false by default) +Name of a layout template to use instead of Apipie\'s layout. You + can use Apipie.include_stylesheets and Apipie.include_javascripts + helpers to include Apipie\'s stylesheets and javascripts. -action_on_non_validated_keys - Either `:raise` or `:skip`. If `validate_key` fails, raise error or delete the non-validated key from the params and log the key (`:raise` by default) +`ignored` -process_params - Process and extract the parameter defined from the params of the request - to the api_params variable +An array of controller names (strings) (might include actions as + well) to be ignored when generating the documentation e.g. + `%w[Api::CommentsController Api::PostsController#post]` -app_info - Application long description. +`namespaced_resources` -reload_controllers - Set to enable/disable reloading controllers (and the documentation with it). Enabled by default in development. +Use controller paths instead of controller names as resource id. + This prevents same named controllers overwriting each other. -api_controllers_matcher - For reloading to work properly you need to specify where your API controllers are. Can be an array if multiple paths are needed +`authenticate` -api_action_matcher - Determines the strategy to identity the correct controller action. Needs to be a class that implements a `.call(controller)` method +Pass a proc in order to authenticate user. Pass nil for no + authentication (by default). -api_routes - Set if your application uses a custom API router, different from the Rails - default +`authorize` -routes_formatter - An object providing the translation from the Rails routes to the - format usable in the documentation when using the `api!` keyword. By - default, the ``Apipie::RoutesFormatter`` is used. +Pass a proc in order to authorize controllers and methods. The Proc + is evaluated in the controller context. -markup - You can choose markup language for descriptions of your application, - resources and methods. RDoc is the default but you can choose from - Apipie::Markup::Markdown.new or Apipie::Markup::Textile.new. - In order to use Markdown you need Maruku gem and for Textile you - need RedCloth. Add those to your gemfile and run bundle if you - want to use them. You can also add any other markup language - processor. +`show_all_examples` -layout - Name of a layout template to use instead of Apipie's layout. You can use - Apipie.include_stylesheets and Apipie.include_javascripts helpers to include - Apipie's stylesheets and javascripts. +Set this to true to set show_in_doc=1 in all recorded examples -ignored - An array of controller names (strings) (might include actions as well) - to be ignored when generating the documentation - e.g. ``%w[Api::CommentsController Api::PostsController#post]`` +`ignore_allow_blank_false` -namespaced_resources - Use controller paths instead of controller names as resource id. - This prevents same named controllers overwriting each other. +`allow_blank: false` was incorrectly ignored up until + version 0.6.0, this bug was fixed in 0.7.0 if you need the old + behavior, set this to true -authenticate - Pass a proc in order to authenticate user. Pass nil for - no authentication (by default). +`link_extension` -authorize - Pass a proc in order to authorize controllers and methods. The Proc is evaluated in the controller context. +The extension to use for API pages (\'.html\' by default). Link + extensions in static API docs cannot be changed from \'.html\'. -show_all_examples - Set this to true to set show_in_doc=1 in all recorded examples +`languages` -ignore_allow_blank_false - `allow_blank: false` was incorrectly ignored up until version 0.6.0, this bug was fixed in 0.7.0 - if you need the old behavior, set this to true +List of languages the API documentation should be translated into. + Empty by default. -link_extension - The extension to use for API pages ('.html' by default). Link extensions - in static API docs cannot be changed from '.html'. +`default_locale` -languages - List of languages the API documentation should be translated into. Empty by default. +Locale used for generating documentation when no specific locale is + set. Set to \'en\' by default. -default_locale - Locale used for generating documentation when no specific locale is set. - Set to 'en' by default. +`locale` -locale - Pass locale setter/getter +Pass locale setter/getter -.. code:: ruby +``` ruby +config.locale = lambda { |loc| loc ? FastGettext.set_locale(loc) : FastGettext.locale } +``` - config.locale = lambda { |loc| loc ? FastGettext.set_locale(loc) : FastGettext.locale } +`translate` -translate - Pass proc to translate strings using the localization library your project uses. - For example see `Localization`_ +Pass proc to translate strings using the localization library your + project uses. For example see [Localization](#localization) Example: -.. code:: ruby - - Apipie.configure do |config| - config.app_name = "Test app" - config.copyright = "© 2012 Pavel Pokorny" - config.doc_base_url = "/apidoc" - config.api_base_url = "/api" - config.validate = false - config.markup = Apipie::Markup::Markdown.new - config.reload_controllers = Rails.env.development? - config.api_controllers_matcher = File.join(Rails.root, "app", "controllers", "**","*.rb") - config.api_action_matcher = proc { |controller| controller.params[:action] } - config.api_routes = Rails.application.routes - config.app_info["1.0"] = " - This is where you can inform user about your application and API - in general. - " - config.authenticate = Proc.new do - authenticate_or_request_with_http_basic do |username, password| - username == "test" && password == "supersecretpassword" - end - end - config.authorize = Proc.new do |controller, method, doc| - !method # show all controller doc, but no method docs. - end - end - -checksum_path - Used in ChecksumInHeaders middleware (see `JSON checksums`_ for more info). It contains path prefix(es) where the header with checksum is added. If set to nil, checksum is added in headers in every response. e.g. ``%w[/api /apipie]`` +``` ruby +Apipie.configure do |config| + config.app_name = "Test app" + config.copyright = "© 2012 Pavel Pokorny" + config.doc_base_url = "/apidoc" + config.api_base_url = "/api" + config.validate = false + config.markup = Apipie::Markup::Markdown.new + config.reload_controllers = Rails.env.development? + config.api_controllers_matcher = File.join(Rails.root, "app", "controllers", "**","*.rb") + config.api_action_matcher = proc { |controller| controller.params[:action] } + config.api_routes = Rails.application.routes + config.app_info["1.0"] = " + This is where you can inform user about your application and API + in general. + " + config.authenticate = Proc.new do + authenticate_or_request_with_http_basic do |username, password| + username == "test" && password == "supersecretpassword" + end + end + config.authorize = Proc.new do |controller, method, doc| + !method # show all controller doc, but no method docs. + end +end +``` -update_checksum - If set to true, the checksum is recalculated with every documentation_reload call +`checksum_path` -======================== -Rails Routes Integration -======================== +Used in ChecksumInHeaders middleware (see [JSON + checksums](#json-checksums) for more info). It contains path + prefix(es) where the header with checksum is added. If set to nil, + checksum is added in headers in every response. e.g. + `%w[/api /apipie]` -Apipie is able to load the information about the paths based on the -routes defined in the Rails application, by using the `api!` keyword -in the DSL. +`update_checksum` -It should be usable out of box, however, one might want -to do some customization (such as omitting some implicit parameters in -the path etc.). For this kind of customizations one can create a new -formatter and pass as the ``Apipie.configuration.routes_formatter`` -option, like this: +If set to `true`, the checksum is recalculated with every + documentation_reload call -.. code:: ruby +# Rails Routes Integration - class MyFormatter < Apipie::RoutesFormatter - def format_path(route) - super.gsub(/\(.*?\)/, '').gsub('//','') # hide all implicit parameters - end - end +Apipie is able to load the information about the paths based on the +routes defined in the Rails application, by using the `api!` +keyword in the DSL. + +It should be usable out of box, however, one might want to do some +customization (such as omitting some implicit parameters in the path +etc.). For this kind of customizations one can create a new formatter +and pass as the `Apipie.configuration.routes_formatter` option, like +this: + +``` ruby +class MyFormatter < Apipie::RoutesFormatter + def format_path(route) + super.gsub(/\(.*?\)/, '').gsub('//','') # hide all implicit parameters + end +end - Apipie.configure do |config| - ... - config.routes_formatter = MyFormatter.new - ... - end +Apipie.configure do |config| + ... + config.routes_formatter = MyFormatter.new + ... +end +``` -A similar way can be used to influence things like order, or a description -of the loaded APIs, even omitting some paths if needed. +A similar way can be used to influence things like order, or a +description of the loaded APIs, even omitting some paths if needed. -============ - Processing -============ +# Processing The goal is to extract and pre-process parameters of the request. -For example Rails, by default, transforms an empty array to nil value. Perhaps -you want to transform it again into an empty array. Or you -want to support an enumeration type (comma separated values) and -you want to automatically transform this string into an array. +For example Rails, by default, transforms an empty array to nil value. +Perhaps you want to transform it again into an empty array. Or you want +to support an enumeration type (comma separated values) and you want to +automatically transform this string into an array. -To use it, set the ``process_params`` configuration variable to true. +To use it, set the `process_params` configuration variable to true. -Also by using ``as`` you can separate your API parameter -names from the names you are using inside your code. +Also by using `as` you can separate your API parameter names from the +names you are using inside your code. -To implement it, you just have to write a process_value -function in your validator: +To implement it, you just have to write a process_value function in your +validator: For an enumeration type: -.. code:: ruby - - def process_value(value) - value ? value.split(',') : [] - end +``` ruby +def process_value(value) + value ? value.split(',') : [] +end +``` -============ - Validators -============ +# Validators -Every parameter needs to have an associated validator. For now there are some -basic validators. You can always provide your own to achieve complex -results. +Every parameter needs to have an associated validator. For now there are +some basic validators. You can always provide your own to achieve +complex results. If validations are enabled (default state) the parameters of every -request are validated. If the value is wrong an +ArgumentError+ exception -is raised and can be rescued and processed. It contains a description -of the parameter value expectations. Validations can be turned off -in the configuration file. +request are validated. If the value is wrong an +ArgumentError+ +exception is raised and can be rescued and processed. It contains a +description of the parameter value expectations. Validations can be +turned off in the configuration file. Here is an example of how to rescue and process a +ParamMissing+ or +ParamInvalid+ error from within the ApplicationController. -.. code:: ruby - - class ApplicationController < ActionController::Base - - # ParamError is superclass of ParamMissing, ParamInvalid - rescue_from Apipie::ParamError do |e| - render text: e.message, status: :unprocessable_entity - end +``` ruby +class ApplicationController < ActionController::Base - # ... + # ParamError is superclass of ParamMissing, ParamInvalid + rescue_from Apipie::ParamError do |e| + render text: e.message, status: :unprocessable_entity end + # ... +end +``` + Parameter validation normally happens after before_actions, just before -your controller method is invoked. If you prefer to control when parameter -validation occurs, set the configuration parameter ``validate`` to ``:explicitly``. -You must then call the ``apipie_validations`` method yourself, e.g.: +your controller method is invoked. If you prefer to control when +parameter validation occurs, set the configuration parameter `validate` +to `:explicitly`. You must then call the `apipie_validations` method +yourself, e.g.: -.. code:: ruby +``` ruby +before_action :apipie_validations +``` - before_action :apipie_validations +This is useful if you have before_actions which use parameter values: +just add them after the `apipie_validations` before_action. -This is useful if you have before_actions which use parameter values: just add them -after the ``apipie_validations`` before_action. +## TypeValidator -TypeValidator -------------- -Check the parameter type. Only String, Integer, Hash and Array are supported -for the sake of simplicity. Read more to find out how to add +Check the parameter type. Only String, Integer, Hash and Array are +supported for the sake of simplicity. Read more to find out how to add your own validator. -.. code:: ruby +``` ruby +param :session, String, :desc => "user is logged in", :required => true +param :facts, Hash, :desc => "Additional optional facts about the user" +``` - param :session, String, :desc => "user is logged in", :required => true - param :facts, Hash, :desc => "Additional optional facts about the user" +## RegexpValidator - -RegexpValidator ---------------- Check parameter value against given regular expression. -.. code:: ruby - - param :regexp_param, /^[0-9]* years/, :desc => "regexp param" +``` ruby +param :regexp_param, /^[0-9]* years/, :desc => "regexp param" +``` - -EnumValidator --------------- +## EnumValidator Check if parameter value is included in the given array. -.. code:: ruby - - param :enum_param, [100, "one", "two", 1, 2], :desc => "enum validator" - - -ProcValidator -------------- +``` ruby +param :enum_param, [100, "one", "two", 1, 2], :desc => "enum validator" +``` -If you need more complex validation and you know you won't reuse it, you -can use the Proc/lambda validator. Provide your own Proc, taking the value -of the parameter as the only argument. Return true if value passes validation -or return some text about what is wrong otherwise. _Don't use the keyword *return* -if you provide an instance of Proc (with lambda it is ok), just use the last -statement return property of ruby. +## ProcValidator -.. code:: ruby +If you need more complex validation and you know you won\'t reuse it, +you can use the Proc/lambda validator. Provide your own Proc, taking the +value of the parameter as the only argument. Return true if value passes +validation or return some text about what is wrong otherwise. Don't +use the keyword *return* if you provide an instance of Proc (with lambda +it is ok), just use the last statement return property of ruby. - param :proc_param, lambda { |val| - val == "param value" ? true : "The only good value is 'param value'." - }, :desc => "proc validator" +``` ruby +param :proc_param, lambda { |val| + val == "param value" ? true : "The only good value is 'param value'." +}, :desc => "proc validator" +``` - -HashValidator -------------- +## HashValidator You can describe hash parameters in depth if you provide a block with a description of nested values. -.. code:: ruby - - param :user, Hash, :desc => "User info" do - param :username, String, :desc => "Username for login", :required => true - param :password, String, :desc => "Password for login", :required => true - param :membership, ["standard","premium"], :desc => "User membership" - end - +``` ruby +param :user, Hash, :desc => "User info" do + param :username, String, :desc => "Username for login", :required => true + param :password, String, :desc => "Password for login", :required => true + param :membership, ["standard","premium"], :desc => "User membership" +end +``` -NilValidator ------------- +## NilValidator -In fact there isn't any NilValidator, but setting it to nil can be used to -override parameters described on the resource level. +In fact there isn\'t any NilValidator, but setting it to nil can be used +to override parameters described on the resource level. -.. code:: ruby +``` ruby +param :user, nil +def destroy + #... +end +``` - param :user, nil - def destroy - #... - end - -NumberValidator ---------------- +## NumberValidator Check if the parameter is a positive integer number or zero -.. code:: ruby +``` ruby +param :product_id, :number, :desc => "Identifier of the product", :required => true +param :quantity, :number, :desc => "Number of products to order", :required => true +``` - param :product_id, :number, :desc => "Identifier of the product", :required => true - param :quantity, :number, :desc => "Number of products to order", :required => true - -DecimalValidator --------------- +DecimalValidator \-\-\-\-\-\-\-\-\-\-\-\-\-- Check if the parameter is a decimal number -.. code:: ruby - - param :latitude, :decimal, :desc => "Geographic latitude", :required => true - param :longitude, :decimal, :desc => "Geographic longitude", :required => true +``` ruby +param :latitude, :decimal, :desc => "Geographic latitude", :required => true +param :longitude, :decimal, :desc => "Geographic longitude", :required => true +``` -ArrayValidator --------------- +## ArrayValidator Check if the parameter is an array Additional options -~~~~~~~~~~~~~~~~~ -of - Specify the type of items. If not given it accepts an array of any item type +`of` -in - Specify an array of valid item values. +Specify the type of items. If not given it accepts an array of any item type -Examples -~~~~~~~~ +`in` -Assert `things` is an array of any items +Specify an array of valid item values. + +### Examples -.. code:: ruby +Assert `things` is an array of any items - param :things, Array +``` ruby +param :things, Array +``` Assert `hits` must be an array of integer values -.. code:: ruby - - param :hits, Array, of: Integer +``` ruby +param :hits, Array, of: Integer +``` Assert `colors` must be an array of valid string values -.. code:: ruby - - param :colors, Array, in: ["red", "green", "blue"] - - -The retrieving of valid items can be deferred until needed using a lambda. It is evaluated only once - -.. code:: ruby +``` ruby +param :colors, Array, in: ["red", "green", "blue"] +``` - param :colors, Array, in: -> { Color.all.pluck(:name) } +The retrieving of valid items can be deferred until needed using a +lambda. It is evaluated only once +``` ruby +param :colors, Array, in: -> { Color.all.pluck(:name) } +``` -NestedValidator -------------- +### NestedValidator -You can describe nested parameters in depth if you provide a block with a -description of nested values. - -.. code:: ruby - - param :comments, Array, :desc => "User comments" do - param :name, String, :desc => "Name of the comment", :required => true - param :comment, String, :desc => "Full comment", :required => true - end +You can describe nested parameters in depth if you provide a block with +a description of nested values. +``` ruby +param :comments, Array, :desc => "User comments" do + param :name, String, :desc => "Name of the comment", :required => true + param :comment, String, :desc => "Full comment", :required => true +end +``` +## Adding custom validator -Adding custom validator ------------------------ - -Only basic validators are included but it is really easy to add your own. -Create a new initializer with a subclass of Apipie::Validator::BaseValidator. -Two methods are required to implement this - instance method -:code:`validate(value)` and class method -:code:`build(param_description, argument, options, block)`. +Only basic validators are included but it is really easy to add your +own. Create a new initializer with a subclass of +Apipie::Validator::BaseValidator. Two methods are required to implement +this - instance method `validate(value)` and class method +`build(param_description, argument, options, block)`. When searching for the validator +build+ method, every subclass of -Apipie::Validator::BaseValidator is called. The first one that returns the -constructed validator object is used. +Apipie::Validator::BaseValidator is called. The first one that returns +the constructed validator object is used. Example: Adding IntegerValidator We want to check if the parameter value is an integer like this: -.. code:: ruby - - param :id, Integer, :desc => "Company ID" +``` ruby +param :id, Integer, :desc => "Company ID" +``` So we create apipie_validators.rb initializer with this content: -.. code:: ruby +``` ruby +class IntegerValidator < Apipie::Validator::BaseValidator - class IntegerValidator < Apipie::Validator::BaseValidator - - def initialize(param_description, argument) - super(param_description) - @type = argument - end + def initialize(param_description, argument) + super(param_description) + @type = argument + end - def validate(value) - return false if value.nil? - !!(value.to_s =~ /^[-+]?[0-9]+$/) - end + def validate(value) + return false if value.nil? + !!(value.to_s =~ /^[-+]?[0-9]+$/) + end - def self.build(param_description, argument, options, block) - if argument == Integer - self.new(param_description, argument) - end - end + def self.build(param_description, argument, options, block) + if argument == Integer + self.new(param_description, argument) + end + end - def description - "Must be #{@type}." - end + def description + "Must be #{@type}." + end - def expected_type - 'numeric' - end - end + def expected_type + 'numeric' + end +end +``` Parameters of the build method: -param_description - Instance of Apipie::ParamDescription contains all - given information about the validated parameter. - -argument - Specified validator; in our example it is +Integer+ - -options - Hash with specified options, for us just ``{:desc => "Company ID"}`` +`param_description` -block - Block converted into Proc, use it as you desire. In this example nil. +Instance of Apipie::ParamDescription contains all given information about the validated parameter. -If your validator includes valid values that respond true to `.blank?`, you -should also define: +`argument` -.. code:: ruby +Specified validator; in our example it is +Integer+ - def ignore_allow_blank? - true - end +`options` -so that the validation does not fail for valid values. +Hash with specified options, for us just `{:desc => "Company ID"}` -============ - Versioning -============ +`block` -Every resource/method can belong to one or more versions. The version is -specified with the `api_version` DSL keyword. When not specified, -the resource belongs to `config.default_version` ("1.0" by default) +Block converted into Proc, use it as you desire. In this example nil. -.. code:: ruby +If your validator includes valid values that respond true to +`.blank?`, you should also define: - resource_description do - api_versions "1", "2" - end +``` ruby +def ignore_allow_blank? + true +end +``` - api :GET, "/api/users/", "List: users" - api_version "1" - def index - # ... - end +so that the validation does not fail for valid values. - api :GET, "/api/users/", "List: users", :deprecated => true +# Versioning -In the example above we say the whole controller/resource is defined -for versions "1" and "2", but we override this by explicitly saying -`index` belongs only to version "1". Also, inheritance works (therefore -we can specify the api_version for the parent controller, and all -children will know about that). Routes can be flagged as deprecated, -and an annotation will be added to them when viewing in the API -documentation. +Every resource/method can belong to one or more versions. The version is +specified with the `api_version` DSL keyword. When not +specified, the resource belongs to `config.default_version` +(\"1.0\" by default) + +``` ruby +resource_description do + api_versions "1", "2" +end + +api :GET, "/api/users/", "List: users" +api_version "1" +def index + # ... +end + +api :GET, "/api/users/", "List: users", :deprecated => true +``` + +In the example above we say the whole controller/resource is defined for +versions \"1\" and \"2\", but we override this by explicitly saying +`index` belongs only to version \"1\". Also, inheritance +works (therefore we can specify the api_version for the parent +controller, and all children will know about that). Routes can be +flagged as deprecated, and an annotation will be added to them when +viewing in the API documentation. From the Apipie API perspective, the resources belong to the version. With versioning, there are paths like this provided by apipie: -.. code:: - - /apipie/1/users/index - /apipie/2/users/index +``` +/apipie/1/users/index +/apipie/2/users/index +``` When not specifying the version explicitly in the path (or in DSL), -default version (`Apipie.configuration.default_version`) is used -instead ("1.0" by default). Therefore, an application that doesn't -need versioning should work as before. +default version (`Apipie.configuration.default_version`) is +used instead (\"1.0\" by default). Therefore, an application that +doesn\'t need versioning should work as before. The static page generator takes a version parameter (or uses default). -You can specify the versions for the examples, with the `versions` -keyword. It specifies the versions the example is used for. When not -specified, it's shown in all versions with the given method. - -When referencing or quering the resource/method descripion, this -format should be used: "version#resource#method". When not specified, -the default version is used instead. +You can specify the versions for the examples, with the +`versions` keyword. It specifies the versions the example is +used for. When not specified, it\'s shown in all versions with the given +method. +When referencing or quering the resource/method descripion, this format +should be used: \"version#resource#method\". When not specified, the +default version is used instead. -======== - Markup -======== +# Markup -The default markup language is `RDoc -`_. It can be changed in -the config file (``config.markup=``) to one of these: +The default markup language is +[RDoc](https://rdoc.github.io/rdoc/RDoc/Markup.html). It can be changed +in the config file (`config.markup=`) to one of these: Markdown - Use Apipie::Markup::Markdown.new. You need Maruku gem. + +Use Apipie::Markup::Markdown.new. You need Maruku gem. Textile - Use Apipie::Markup::Textile.new. You need RedCloth gem. -Or provide you own object with a ``to_html(text)`` method. -For inspiration, this is how Textile markup usage is implemented: +Use Apipie::Markup::Textile.new. You need RedCloth gem. -.. code:: ruby +Or provide you own object with a `to_html(text)` method. For +inspiration, this is how Textile markup usage is implemented: - class Textile - def initialize - require 'RedCloth' - end - def to_html(text) - RedCloth.new(text).to_html - end - end +``` ruby +class Textile + def initialize + require 'RedCloth' + end + def to_html(text) + RedCloth.new(text).to_html + end +end +``` -============ -Localization -============ +# Localization -Apipie has support for localized API documentation in both formats (JSON and HTML). -Apipie uses the library I18n for localization of itself. -Check ``config/locales`` directory for available translations. +Apipie has support for localized API documentation in both formats (JSON +and HTML). Apipie uses the library I18n for localization of itself. +Check `config/locales` directory for available translations. -A major part of strings in the documentation comes from the API. -As preferences regarding localization libraries differ amongst project, Apipie needs to know how to set the locale for your project, -and how to translate a string using the library your project uses. That can be done using lambdas in configuration. +A major part of strings in the documentation comes from the API. As +preferences regarding localization libraries differ amongst project, +Apipie needs to know how to set the locale for your project, and how to +translate a string using the library your project uses. That can be done +using lambdas in configuration. Sample configuration when your project uses FastGettext - -.. code:: ruby - - Apipie.configure do |config| - ... - config.languages = ['en', 'cs'] - config.default_locale = 'en' - config.locale = lambda { |loc| loc ? FastGettext.set_locale(loc) : FastGettext.locale } - config.translate = lambda do |str, loc| - old_loc = FastGettext.locale - FastGettext.set_locale(loc) - trans = _(str) - FastGettext.set_locale(old_loc) - trans - end - end - -And the strings in the API documentation need to be marked with the ``N_()`` function - -.. code:: ruby - - api :GET, "/users/:id", N_("Show user profile") - param :session, String, :desc => N_("user is logged in"), :required => true - - - -When your project use I18n, localization related configuration could appear as follows - -.. code:: ruby - - Apipie.configure do |config| - ... - config.languages = ['en', 'cs'] - config.default_locale = 'en' - config.locale = lambda { |loc| loc ? I18n.locale = loc : I18n.locale } - config.translate = lambda do |str, loc| - return '' if str.blank? - I18n.t str, locale: loc, scope: 'doc' - end - end - -And the strings in the API documentation needs to be in the form of translation keys - -.. code:: ruby - - api :GET, "/users/:id", "show_user_profile" - param :session, String, :desc => "user_is_logged_in", :required => true - - -The localized versions of the documentation are distinguished by language in the filename. -E.g. ``doc/apidoc/apidoc.cs.html`` is static documentation in the Czech language. -If the language is missing, e.g. ``doc/apidoc/apidoc.html``, -the documentation is localized with the ``default_locale``. - -The dynamic documentation follows the same schema. The ``http://localhost:3000/apidoc/v1.cs.html`` is documentation for version '1' of the API in the Czech language. For JSON descriptions, the API applies the same format: ``http://localhost:3000/apidoc/v1.cs.json`` - - -================ -Modifying Views -================ - -To modify the views of your documentation, run ``rails g apipie:views``. -This will copy the Apipie views to ``app/views/apipie/apipies`` and -``app/views/layouts/apipie``. - - -============== - Static files -============== - -To generate a static version of documentation (perhaps to put it on -your project site or something), run the ``rake apipie:static`` task. It will +``` ruby +Apipie.configure do |config| + ... + config.languages = ['en', 'cs'] + config.default_locale = 'en' + config.locale = lambda { |loc| loc ? FastGettext.set_locale(loc) : FastGettext.locale } + config.translate = lambda do |str, loc| + old_loc = FastGettext.locale + FastGettext.set_locale(loc) + trans = _(str) + FastGettext.set_locale(old_loc) + trans + end +end +``` + +And the strings in the API documentation need to be marked with the +`N_()` function + +``` ruby +api :GET, "/users/:id", N_("Show user profile") +param :session, String, :desc => N_("user is logged in"), :required => true +``` + +When your project use I18n, localization related configuration could +appear as follows + +``` ruby +Apipie.configure do |config| + ... + config.languages = ['en', 'cs'] + config.default_locale = 'en' + config.locale = lambda { |loc| loc ? I18n.locale = loc : I18n.locale } + config.translate = lambda do |str, loc| + return '' if str.blank? + I18n.t str, locale: loc, scope: 'doc' + end +end +``` + +And the strings in the API documentation needs to be in the form of +translation keys + +``` ruby +api :GET, "/users/:id", "show_user_profile" +param :session, String, :desc => "user_is_logged_in", :required => true +``` + +The localized versions of the documentation are distinguished by +language in the filename. E.g. `doc/apidoc/apidoc.cs.html` is static +documentation in the Czech language. If the language is missing, e.g. +`doc/apidoc/apidoc.html`, the documentation is localized with the +`default_locale`. + +The dynamic documentation follows the same schema. The +`http://localhost:3000/apidoc/v1.cs.html` is documentation for version +\'1\' of the API in the Czech language. For JSON descriptions, the API +applies the same format: `http://localhost:3000/apidoc/v1.cs.json` + +# Modifying Views + +To modify the views of your documentation, run `rails g apipie:views`. +This will copy the Apipie views to `app/views/apipie/apipies` and +`app/views/layouts/apipie`. + +# Static files + +To generate a static version of documentation (perhaps to put it on your +project site or something), run the `rake apipie:static` task. It will create a set of HTML files (multi-pages, single-page, plain) in your doc -directory. If you prefer a JSON version run ``rake apipie:static_json``. -By default the documentation for the default API version is -used. You can specify the version with ``rake apipie:static[2.0]`` +directory. If you prefer a JSON version run `rake apipie:static_json`. +By default the documentation for the default API version is used. You +can specify the version with `rake apipie:static[2.0]` When you want to avoid any unnecessary computation in production mode, -you can generate a cache with ``rake apipie:cache`` and configure the -app to use it in production with ``config.use_cache = Rails.env.production?`` +you can generate a cache with `rake apipie:cache` and configure the app +to use it in production with `config.use_cache = Rails.env.production?` -Default cache dir is ``File.join(Rails.root, "public", "apipie-cache")``, -you can change it to where you want, example: ``config.cache_dir = File.join(Rails.root, "doc", "apidoc")``. +Default cache dir is `File.join(Rails.root, "public", "apipie-cache")`, +you can change it to where you want, example: +`config.cache_dir = File.join(Rails.root, "doc", "apidoc")`. -If, for some complex cases, you need to generate/re-generate just part of the cache -use ``rake apipie:cache cache_part=index`` resp. ``rake apipie:cache cache_part=resources`` -To generate it for different locations for further processing use ``rake apipie:cache OUT=/tmp/apipie_cache``. +If, for some complex cases, you need to generate/re-generate just part +of the cache use `rake apipie:cache cache_part=index` resp. +`rake apipie:cache cache_part=resources` To generate it for different +locations for further processing use +`rake apipie:cache OUT=/tmp/apipie_cache`. -.. _Swagger: +# Static Swagger (OpenAPI 2.0) files -==================================== - Static Swagger (OpenAPI 2.0) files -==================================== +To generate a static Swagger definition file from the api, run +`rake apipie:static_swagger_json`. By default the documentation for the +default API version is used. You can specify the version with +`rake apipie:static_swagger_json[2.0]`. A swagger file will be generated +for each locale. The files will be generated in the same location as the +static_json files, but instead of being named +`schema_apipie[.locale].json`, they will be called +`schema_swagger[.locale].json`. -To generate a static Swagger definition file from the api, run ``rake apipie:static_swagger_json``. -By default the documentation for the default API version is -used. You can specify the version with ``rake apipie:static_swagger_json[2.0]``. A swagger file will be -generated for each locale. The files will be generated in the same location as the static_json files, but -instead of being named ``schema_apipie[.locale].json``, they will be called ``schema_swagger[.locale].json``. +## Specifying default values for parameters -Specifying default values for parameters ------------------------------------------ -Swagger allows method definitions to include an indication of the the default value for each parameter. To include such -indications, use ``:default_value => `` in the parameter definition DSL. For example: +Swagger allows method definitions to include an indication of the the +default value for each parameter. To include such indications, use +`:default_value => ` in the parameter definition DSL. For +example: -.. code:: ruby +``` ruby +param :do_something, Boolean, :desc => "take an action", :required => false, :default_value => false +``` - param :do_something, Boolean, :desc => "take an action", :required => false, :default_value => false +## Generated Warnings +The help identify potential improvements to your documentation, the +swagger generation process issues warnings if it identifies various +shortcomings of the DSL documentation. Each warning has a code to allow +selective suppression (see swagger-specific configuration below) -Generated Warnings -------------------- -The help identify potential improvements to your documentation, the swagger generation process issues warnings if -it identifies various shortcomings of the DSL documentation. Each warning has a code to allow selective suppression -(see swagger-specific configuration below) +| Error Code | Description | +|------------|-------------------------------------------------------------------------------------| +| 100 | Missing short description for method | +| 101 | Added missing / at beginning of path | +| 102 | No return codes specified for method | +| 103 | A parameter is a generic Hash without an internal type specification | +| 104 | A parameter is an 'in-path' parameter, but specified as 'not required' in the DSL | +| 105 | A parameter is optional but does not have a default value specified | +| 106 | A parameter was omitted from the swagger output because it is a Hash without fields in a formData specification | +| 107 | A path parameter is not described | +| 108 | Inferring that a parameter type is boolean because described as an enum with `[false true]` values | -:100: missing short description for method -:101: added missing / at beginning of path -:102: no return codes specified for method -:103: a parameter is a generic Hash without an internal type specification -:104: a parameter is an 'in-path' parameter, but specified as 'not required' in the DSL -:105: a parameter is optional but does not have a default value specified -:106: a parameter was ommitted from the swagger output because it is a Hash without fields in a formData specification -:107: a path parameter is not described -:108: inferring that a parameter type is boolean because described as an enum with [false,true] values +## Swagger-Specific Configuration Parameters +There are several configuration parameters that determine the structure +of the generated swagger file: -Swagger-Specific Configuration Parameters -------------------------------------------------- +`config.generator.swagger.content_type_input` -There are several configuration parameters that determine the structure of the generated swagger file: +- If the value is `:form_data` - the swagger file will indicate that +the server consumes the content types +`application/x-www-form-urlencoded` and `multipart/form-data`. +Non-path parameters will have the value `"in": "formData"`. Note +that parameters of type Hash that do not have any fields in them +will *be ommitted* from the resulting files, as there is no way to +describe them in swagger. -``config.generator.swagger.content_type_input`` - If the value is ``:form_data`` - the swagger file will indicate that the server consumes the content types - ``application/x-www-form-urlencoded`` and ``multipart/form-data``. Non-path parameters will have the - value ``"in": "formData"``. Note that parameters of type Hash that do not have any fields in them will *be ommitted* - from the resulting files, as there is no way to describe them in swagger. +- If the value is `:json` - the swagger file will indicate that the +server consumes the content type `application/json`. All non-path +parameters will be included in the schema of a single `"in": "body"` +parameter of type `object`. - If the value is ``:json`` - the swagger file will indicate that the server consumes the content type - ``application/json``. All non-path parameters will be included in the schema of a single ``"in": "body"`` parameter - of type ``object``. +- You can specify the value of this configuration parameter as an +additional input to the rake command (e.g., +`rake apipie:static_swagger_json[2.0,form_data]`). - You can specify the value of this configuration parameter as an additional input to the rake command (e.g., - ``rake apipie:static_swagger_json[2.0,form_data]``). +`config.generator.swagger.json_input_uses_refs` -``config.generator.swagger.json_input_uses_refs`` - This parameter is only relevant if ``swagger.content_type_input`` is ``:json``. +- This parameter is only relevant if `swagger.content_type_input` is + `:json`. - If ``true``: the schema of the ``"in": "body"`` parameter of each method is given its own entry in the ``definitions`` - section, and is referenced using ``$ref`` from the method definition. +- If `true`: the schema of the `"in": "body"` parameter of each method +is given its own entry in the `definitions` section, and is +referenced using `$ref` from the method definition. - If ``false``: the body parameter definitions are inlined within the method definitions. +- If `false`: the body parameter definitions are inlined within the +method definitions. -``config.generator.swagger.include_warning_tags`` - If ``true``: in addition to tagging methods with the name of the resource they belong to, methods for which warnings - have been issued will be tagged with. +`config.generator.swagger.include_warning_tags` -``config.generator.swagger.suppress_warnings`` - If ``false``: no warnings will be suppressed +- If `true`: in addition to tagging methods with the name of the +resource they belong to, methods for which warnings have been issued +will be tagged with. - If ``true``: all warnings will be suppressed +`config.generator.swagger.suppress_warnings` - If an array of values (e.g., ``[100,102,107]``), only the warnings identified by the numbers in the array will be suppressed. +- If `false`: no warnings will be suppressed -``config.generator.swagger.api_host`` - The value to place in the swagger host field. +- If `true`: all warnings will be suppressed - Default is ``localhost:3000`` +- If an array of values (e.g., `[100,102,107]`), only the warnings +identified by the numbers in the array will be suppressed. - If ``nil`` then then host field will not be included. +`config.generator.swagger.api_host` -``config.generator.swagger.allow_additional_properties_in_response`` - If ``false`` (default): response descriptions in the generated swagger will include an ``additional-properties: false`` - field +- The value to place in the swagger host field. Default is `localhost:3000` +- If `nil` then then host field will not be included. - If ``true``: the ``additional-properties: false`` field will not be included in response object descriptions +`config.generator.swagger.allow_additional_properties_in_response` -``config.generator.swagger.schemes`` - An array of transport schemes that the API supports. - This can include any combination of ``http``, ``https``, ``ws`` and ``wss``. - By default to encourage good security practices, ``['https']`` is specified. +- If `false` (default): response descriptions in the generated swagger +will include an `additional-properties: false` field +- If `true`: the `additional-properties: false` field will not be +included in response object descriptions -``config:swagger.security_definitions`` - If the API requires authentication, you can specify details of the authentication mechanisms supported as a (Hash) value here. - See [https://swagger.io/docs/specification/2-0/authentication/] for details of what values can be specified - By default, no security is defined. +`config.generator.swagger.schemes` -``config.generator.swagger.global_security`` - If the API requires authentication, you can specify which of the authentication mechanisms are supported by all API operations as an Array of hashes here. - This should be used in conjunction with the mechanisms defined by ``swagger.security_definitions``. - See [https://swagger.io/docs/specification/2-0/authentication/] for details of what values can be specified - By default, no security is defined. +- An array of transport schemes that the API supports. This can +include any combination of `http`, `https`, `ws` and `wss`. By +default to encourage good security practices, `['https']` is +specified. -``config.generator.swagger.skip_default_tags`` - By setting ``false`` (default): The resource name for e.g. ``/pets/{petId}`` will automatically be added as a tag ``pets``. - By setting ``true``: The tags needs to be explicitly added to the resource using the DSL. +`config:swagger.security_definitions` -Known limitations of the current implementation -------------------------------------------------- -* There is currently no way to document the structure and content-type of the data returned from a method -* Recorded examples are currently not included in the generated swagger file -* The apipie ``formats`` value is ignored. -* It is not possible to specify the "consumed" content type on a per-method basis -* It is not possible to leverage all of the parameter type/format capabilities of swagger -* Only OpenAPI 2.0 is supported -* Responses are defined inline and not as a $ref -* It is not possible to specify per-operation security requirements (only global) +- If the API requires authentication, you can specify details of the +authentication mechanisms supported as a (Hash) value here. See +\[\] for +details of what values can be specified By default, no security is +defined. -==================================== - Dynamic Swagger generation -==================================== +`config.generator.swagger.global_security` -To generate swagger dynamically, use ``http://localhost:3000/apipie.json?type=swagger``. +- If the API requires authentication, you can specify which of the +authentication mechanisms are supported by all API operations as an +Array of hashes here. This should be used in conjunction with the +mechanisms defined by `swagger.security_definitions`. See +\[\] for +details of what values can be specified By default, no security is +defined. -Note that authorization is not supported for dynamic swagger generation, so if ``config.authorize`` is defined, -dynamic swagger generation will be disabled. +`config.generator.swagger.skip_default_tags` -Dynamically generated swagger is not cached, and is always generated on the fly. +- By setting `false` (default): The resource name for e.g. +`/pets/{petId}` will automatically be added as a tag `pets`. By +setting `true`: The tags needs to be explicitly added to the +resource using the DSL. +## Known limitations of the current implementation -=================== - JSON checksums -=================== +- There is currently no way to document the structure and content-type + of the data returned from a method +- Recorded examples are currently not included in the generated + swagger file +- The apipie `formats` value is ignored. +- It is not possible to specify the \"consumed\" content type on a + per-method basis +- It is not possible to leverage all of the parameter type/format + capabilities of swagger +- Only OpenAPI 2.0 is supported +- Responses are defined inline and not as a \$ref +- It is not possible to specify per-operation security requirements + (only global) -If the API client needs to be sure that the JSON didn't changed, add -the ``ApipieChecksumInHeaders`` middleware in your rails app. -It can add a checksum of the entire JSON document in the response headers. +# Dynamic Swagger generation -.. code:: +To generate swagger dynamically, use +`http://localhost:3000/apipie.json?type=swagger`. - "Apipie-Checksum"=>"fb81460e7f4e78d059f826624bdf9504" +Note that authorization is not supported for dynamic swagger generation, +so if `config.authorize` is defined, dynamic swagger generation will be +disabled. -`Apipie bindings `_ uses this feature to refresh its JSON cache. +Dynamically generated swagger is not cached, and is always generated on +the fly. -To set it up add the following to your ``application.rb`` +# JSON checksums -.. code:: +If the API client needs to be sure that the JSON didn\'t changed, add +the `ApipieChecksumInHeaders` middleware in your rails app. It can add a +checksum of the entire JSON document in the response headers. - require 'apipie/middleware/checksum_in_headers' - # Add JSON checksum in headers for smarter caching - config.middleware.use "Apipie::Middleware::ChecksumInHeaders" +``` +"Apipie-Checksum"=>"fb81460e7f4e78d059f826624bdf9504" +``` -And in your apipie initializer allow checksum calculation +[Apipie bindings](https://github.com/Apipie/apipie-bindings) uses this +feature to refresh its JSON cache. -.. code:: +To set it up add the following to your `application.rb` - Apipie.configuration.update_checksum = true +``` +require 'apipie/middleware/checksum_in_headers' +# Add JSON checksum in headers for smarter caching +config.middleware.use "Apipie::Middleware::ChecksumInHeaders" +``` +And in your apipie initializer allow checksum calculation -By default the header is added to responses for ``config.doc_base_url`` and ``/api``. -It can be changed in configuration (see `Configuration Reference`_ for details). +``` +Apipie.configuration.update_checksum = true +``` -The checksum calculation is lazy, and done with the first request. If you run with ``use_cache = true``, -do not forget to run the rake task ``apipie:cache``. +By default the header is added to responses for `config.doc_base_url` +and `/api`. It can be changed in configuration (see [Configuration +Reference](#configuration-reference) for details). +The checksum calculation is lazy, and done with the first request. If +you run with `use_cache = true`, do not forget to run the rake task +`apipie:cache`. -=================== - Tests Integration -=================== +# Tests Integration Apipie integrates with automated testing in two ways. *Documentation bootstrapping* and *examples recording*. -Documentation Bootstrapping ---------------------------- +## Documentation Bootstrapping -Let's say you have an application without REST API documentation. -However you have a set of tests that are run against this API. A lot -of information is already included in these tests, it just needs to be +Let\'s say you have an application without REST API documentation. +However you have a set of tests that are run against this API. A lot of +information is already included in these tests, it just needs to be extracted somehow. Luckily, Apipie provides such a feature. -When running the tests, set the ``APIPIE_RECORD=params`` environment -variable or call ``Apipie.record('params')`` from specs starter. You can either use it with functional tests: +When running the tests, set the `APIPIE_RECORD=params` environment +variable or call `Apipie.record('params')` from specs starter. You can +either use it with functional tests: -.. code:: - - APIPIE_RECORD=params rake test:functionals +``` +APIPIE_RECORD=params rake test:functionals +``` or you can run your server with this param, in case you run the tests against running server: -.. code:: - - APIPIE_RECORD=params rails server +``` +APIPIE_RECORD=params rails server +``` When the process quits, the data from requests/responses are used to -determine the documentation. It's quite raw, but it makes the initial +determine the documentation. It\'s quite raw, but it makes the initial phase much easier. -Examples Recording ------------------- +## Examples Recording You can also use the tests to generate up-to-date examples for your -code. Similar to the bootstrapping process, you can use it with functional -tests or a running server, setting ``APIPIE_RECORD=examples`` or calling ``Apipie.record('examples')`` in your specs starter. - -.. code:: - - APIPIE_RECORD=examples rake test:functionals - APIPIE_RECORD=examples rails server - -The data is written into ``doc/apipie_examples.yml``. By default, -only the first example is shown for each action. You can customize -this by setting the ``show_in_doc`` attribute at each example. - -You can add a title to the examples (useful when showing more than -one example per method) by adding a 'title' attribute. - -.. code:: - - --- !omap - - announcements#index: - - !omap - - title: This is a custom title for this example - - verb: :GET - - path: /api/blabla/1 - - versions: - - '1.0' - - query: - - request_data: - - response_data: - ... - - code: 200 - - show_in_doc: 1 # If 1, show. If 0, do not show. - - recorded: true - -In RSpec you can add metadata to examples. We can use that feature -to mark selected examples - the ones that perform the requests that we want to -show as examples in the documentation. - -For example, we can add ``show_in_doc`` to examples, like this: - -.. code:: ruby - - describe "This is the correct path" do - it "some test", :show_in_doc do - .... - end - end +code. Similar to the bootstrapping process, you can use it with +functional tests or a running server, setting `APIPIE_RECORD=examples` +or calling `Apipie.record('examples')` in your specs starter. + +``` +APIPIE_RECORD=examples rake test:functionals +APIPIE_RECORD=examples rails server +``` + +The data is written into `doc/apipie_examples.yml`. By default, only the +first example is shown for each action. You can customize this by +setting the `show_in_doc` attribute at each example. + +You can add a title to the examples (useful when showing more than one +example per method) by adding a \'title\' attribute. + +``` +--- !omap + - announcements#index: + - !omap + - title: This is a custom title for this example + - verb: :GET + - path: /api/blabla/1 + - versions: + - '1.0' + - query: + - request_data: + - response_data: + ... + - code: 200 + - show_in_doc: 1 # If 1, show. If 0, do not show. + - recorded: true +``` + +In RSpec you can add metadata to examples. We can use that feature to +mark selected examples - the ones that perform the requests that we want +to show as examples in the documentation. + +For example, we can add `show_in_doc` to examples, like this: + +``` ruby +describe "This is the correct path" do + it "some test", :show_in_doc do + .... + end +end - context "These are edge cases" do - it "Can't authenticate" do - .... - end +context "These are edge cases" do + it "Can't authenticate" do + .... + end - it "record not found" do - .... - end + it "record not found" do + .... end +end +``` And then configure RSpec in this way: -.. code:: ruby +``` ruby +RSpec.configure do |config| + config.treat_symbols_as_metadata_keys_with_true_values = true + config.filter_run :show_in_doc => true if ENV['APIPIE_RECORD'] +end +``` - RSpec.configure do |config| - config.treat_symbols_as_metadata_keys_with_true_values = true - config.filter_run :show_in_doc => true if ENV['APIPIE_RECORD'] - end - -This way, when running in recording mode, only the tests that have been marked with the -``:show_in_doc`` metadata will be run, and hence only those will be used as examples. +This way, when running in recording mode, only the tests that have been +marked with the `:show_in_doc` metadata will be run, and hence only +those will be used as examples. -Caveats -------- +## Caveats -Make sure to enable ``config.render_views`` in your ``config/rails_helper.rb`` or -``config/spec_helper.rb`` if you're using jbuilder, or you will get back empty results +Make sure to enable `config.render_views` in your +`config/rails_helper.rb` or `config/spec_helper.rb` if you\'re using +jbuilder, or you will get back empty results -==================== - Bindings Generator -==================== +# Bindings Generator -In earlier versions (<= 0.0.13), there was a simple client generator -as a part of Apipie gem. As more features and users came to Apipie, -there was a greater need for changes on a per project basis. It's -hard (or even impossible) to provide a generic solution for the client -code. We also don't want to tell you what's the right way to do it -(what gems to use, how the API should look like etc.). +In earlier versions (\<= 0.0.13), there was a simple client generator as +a part of Apipie gem. As more features and users came to Apipie, there +was a greater need for changes on a per project basis. It\'s hard (or +even impossible) to provide a generic solution for the client code. We +also don\'t want to tell you what\'s the right way to do it (what gems +to use, how the API should look like etc.). -Therefore you can't generate client code directly by a rake task in +Therefore you can\'t generate client code directly by a rake task in further versions. -There is, however, an even better and more flexible way to reuse your API -documentation for this purpose: using the API the Apipie -provides in the generator code. Check out our sister project -`apipie-bindings `_, as they -use exactly this approach. You also don't need to run the service, +There is, however, an even better and more flexible way to reuse your +API documentation for this purpose: using the API the Apipie provides in +the generator code. Check out our sister project +[apipie-bindings](https://github.com/Apipie/apipie-bindings), as they +use exactly this approach. You also don\'t need to run the service, provided it uses Apipie as a backend. -And if you write one on your own, don't hesitate to share it with us! +And if you write one on your own, don\'t hesitate to share it with us! -==================== - Contributing -==================== +# Contributing Then, you can install dependencies and run the test suite: -.. code:: shell - - > bundle install - > bundle exec rspec - -==================== - Disqus Integration -==================== - -You can setup `Disqus `_ discussion within -your documentation. Just set the credentials in the Apipie -configuration: - -.. code:: ruby - - config.disqus_shortname = "MyProjectDoc" +``` shell +> bundle install +> bundle exec rspec +``` -===================== - External References -===================== +# Disqus Integration -* `Getting started tutorial `_ - - including examples of using the tests integration and versioning. +You can setup [Disqus](https://disqus.com/) discussion within your +documentation. Just set the credentials in the Apipie configuration: -* `Real-world application usage `_ +``` ruby +config.disqus_shortname = "MyProjectDoc" +``` -* `Read-world application usage with versioning `_ +# External References -* `Using Apipie API to generate bindings `_ +- [Getting started tutorial](https://github.com/iNecas/apipie-demo) + -including examples of using the tests integration and versioning. +- [Real-world application usage](https://github.com/Katello/katello) +- [Read-world application usage with + versioning](https://github.com/theforeman/foreman) +- [Using Apipie API to generate + bindings](https://github.com/Apipie/apipie-bindings) From 4f1c3fbba0c720ef81ca042005bbeb24b59db1b9 Mon Sep 17 00:00:00 2001 From: Panos Dalitsouris Date: Fri, 1 Mar 2024 21:20:52 +0200 Subject: [PATCH 04/12] Inline README badges --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 107d9a24..83db21df 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,8 @@ # API Documentation Tool [![image](https://github.com/Apipie/apipie-rails/actions/workflows/build.yml/badge.svg)](https://github.com/Apipie/apipie-rails/actions/workflows/build.yml) - [![image](https://codeclimate.com/github/Apipie/apipie-rails.svg)](https://codeclimate.com/github/Apipie/apipie-rails) - [![Join the chat at https://gitter.im/Apipie/apipie-rails](https://badges.gitter.im/Apipie/apipie-rails.svg)](https://gitter.im/Apipie/apipie-rails?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - [![Latest release](https://img.shields.io/gem/v/apipie-rails.svg)](https://rubygems.org/gems/apipie-rails) Apipie-rails is a DSL and Rails engine for documenting your RESTful API. From f300fcc27c7805bcce54b6e02d147bd15931ab12 Mon Sep 17 00:00:00 2001 From: Kian-Meng Ang Date: Thu, 21 Mar 2024 08:49:45 +0800 Subject: [PATCH 05/12] Fix typos (#921) Found via `codespell -S config,CHANGELOG.md -L isnt,splitted,childs,xdescribe` --- README.md | 8 ++++---- lib/apipie/application.rb | 2 +- lib/apipie/dsl_definition.rb | 6 +++--- lib/apipie/extractor/writer.rb | 2 +- lib/apipie/param_description.rb | 2 +- lib/apipie/response_description_adapter.rb | 2 +- lib/apipie/routes_formatter.rb | 2 +- rel-eng/gem_release.ipynb | 10 +++++----- spec/controllers/users_controller_spec.rb | 2 +- .../app/controllers/api/v2/empty_middle_controller.rb | 2 +- spec/dummy/app/controllers/pets_controller.rb | 2 +- .../app/controllers/twitter_example_controller.rb | 6 +++--- spec/lib/apipie/apipies_controller_spec.rb | 2 +- spec/lib/apipie/file_handler_spec.rb | 2 +- spec/lib/apipie/param_description_spec.rb | 2 +- 15 files changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 83db21df..80be5af0 100644 --- a/README.md +++ b/README.md @@ -517,7 +517,7 @@ to be set are: - for create action: `required => true` and `allow_nil => false` - for update action: `required => false` and `allow_nil => false` -This makes it hard to share the param definitions across theses actions. +This makes it hard to share the param definitions across these actions. Therefore, you can make the description a bit smarter by setting `:action_aware => true`. @@ -584,7 +584,7 @@ end returns :array_of => [, :code => |] [, :desc => ] ``` -If the `:code` argument is ommitted, `200` is used. +If the `:code` argument is omitted, `200` is used. ### Example @@ -1573,7 +1573,7 @@ You can specify the versions for the examples, with the used for. When not specified, it\'s shown in all versions with the given method. -When referencing or quering the resource/method descripion, this format +When referencing or querying the resource/method descripion, this format should be used: \"version#resource#method\". When not specified, the default version is used instead. @@ -1761,7 +1761,7 @@ the server consumes the content types `application/x-www-form-urlencoded` and `multipart/form-data`. Non-path parameters will have the value `"in": "formData"`. Note that parameters of type Hash that do not have any fields in them -will *be ommitted* from the resulting files, as there is no way to +will *be omitted* from the resulting files, as there is no way to describe them in swagger. - If the value is `:json` - the swagger file will indicate that the diff --git a/lib/apipie/application.rb b/lib/apipie/application.rb index c7867947..898f534f 100644 --- a/lib/apipie/application.rb +++ b/lib/apipie/application.rb @@ -89,7 +89,7 @@ def define_method_description(controller, method_name, dsl_data) # we create separate method description for each version in # case the method belongs to more versions. We return just one - # becuase the version doesn't matter for the purpose it's used + # because the version doesn't matter for the purpose it's used # (to wrap the original version with validators) ret_method_description ||= method_description resource_description.add_method_description(method_description) diff --git a/lib/apipie/dsl_definition.rb b/lib/apipie/dsl_definition.rb index 03cfe496..c41ee02c 100644 --- a/lib/apipie/dsl_definition.rb +++ b/lib/apipie/dsl_definition.rb @@ -178,7 +178,7 @@ def desc(description) #:doc: alias full_description desc # describe next method with document in given path - # in convension, these doc located under "#{Rails.root}/doc" + # in convention, these doc located under "#{Rails.root}/doc" # Example: # document "hello_world.md" # def hello_world @@ -452,7 +452,7 @@ module Controller include Apipie::DSL::Action include Apipie::DSL::Param - # defines the substitutions to be made in the API paths deifned + # defines the substitutions to be made in the API paths defined # in concerns included. For example: # # There is this method defined in concern: @@ -472,7 +472,7 @@ module Controller # # It has to be specified before the concern is included. # - # If not specified, the default predefined substitions are + # If not specified, the default predefined substitutions are # # {:conroller_path => controller.controller_path, # :resource_id => `resource_id_from_apipie` } diff --git a/lib/apipie/extractor/writer.rb b/lib/apipie/extractor/writer.rb index 4ede1ca8..0583c6c0 100644 --- a/lib/apipie/extractor/writer.rb +++ b/lib/apipie/extractor/writer.rb @@ -419,7 +419,7 @@ def lines_above_method end # this method would be totally useless unless some clever guy - # desided that it would be great idea to change a behavior of + # decided that it would be great idea to change a behavior of # "".lines method to not include end of lines. # # For more details: diff --git a/lib/apipie/param_description.rb b/lib/apipie/param_description.rb index ea19ad90..7b917d68 100644 --- a/lib/apipie/param_description.rb +++ b/lib/apipie/param_description.rb @@ -245,7 +245,7 @@ def as_action # makes modification that are based on the action that the param # is defined for. Typical for required and allow_nil variations in - # crate/update actions. + # create/update actions. def action_awareness if action_aware? if !@options.key?(:allow_nil) diff --git a/lib/apipie/response_description_adapter.rb b/lib/apipie/response_description_adapter.rb index 519b0903..f8caaf14 100644 --- a/lib/apipie/response_description_adapter.rb +++ b/lib/apipie/response_description_adapter.rb @@ -11,7 +11,7 @@ def self.additional_properties(yesno) class ResponseDescriptionAdapter class Modifier def apply(adapter) - raise "Modifer subclass must implement 'apply' method" + raise "Modifier subclass must implement 'apply' method" end end diff --git a/lib/apipie/routes_formatter.rb b/lib/apipie/routes_formatter.rb index 63985786..c62bc53b 100644 --- a/lib/apipie/routes_formatter.rb +++ b/lib/apipie/routes_formatter.rb @@ -24,7 +24,7 @@ def format_verb(rails_route) if verb.count != 1 verb = API_METHODS.select{|defined_verb| defined_verb == rails_route.constraints[:method]} if verb.blank? - raise "Unknow verb #{rails_route.path.spec.to_s}" + raise "Unknown verb #{rails_route.path.spec.to_s}" end end verb.first diff --git a/rel-eng/gem_release.ipynb b/rel-eng/gem_release.ipynb index 1934c5bc..b3cdf191 100644 --- a/rel-eng/gem_release.ipynb +++ b/rel-eng/gem_release.ipynb @@ -10,7 +10,7 @@ "- push access to https://github.com/Apipie/apipie-rails\n", "- push access to rubygems.org for apipie-rails\n", "- sudo yum install python-slugify asciidoc\n", - "- ensure neither the `git push` or `gem push` don't require interractive auth. If you can't use api key or ssh key to auth skip these steps and run them form the shell manually\n", + "- ensure neither the `git push` or `gem push` don't require interactive auth. If you can't use api key or ssh key to auth skip these steps and run them form the shell manually\n", "- ensure all checks have passed on the branch you're about to release\n", "\n", "### Release process\n", @@ -117,7 +117,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Run tests localy if your setup allows, otherwise ensure the HEAD is green" + "### Run tests locally if your setup allows, otherwise ensure the HEAD is green" ] }, { @@ -217,14 +217,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Manual step: Update deps in the gemspec if neccessary" + "#### Manual step: Update deps in the gemspec if necessary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Check what is going to be commited" + "### Check what is going to be committed" ] }, { @@ -347,7 +347,7 @@ "source": [ "### Some manual steps follow to improve the UX\n", "\n", - "#### New relase on GitHub\n", + "#### New release on GitHub\n", "\n", "Copy the following changelog lines to the description in form on link below\n", "The release title is the new version." diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index f4fff6f4..3ac743d5 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -430,7 +430,7 @@ def reload_controllers expect(b.full_description.length).to be > 400 end - context "Usign routes.rb" do + context "Using routes.rb" do it "should contain basic info about method" do a = Apipie[UsersController, :create_route] expect(a.apis.count).to eq(1) diff --git a/spec/dummy/app/controllers/api/v2/empty_middle_controller.rb b/spec/dummy/app/controllers/api/v2/empty_middle_controller.rb index 53ded523..d005f829 100644 --- a/spec/dummy/app/controllers/api/v2/empty_middle_controller.rb +++ b/spec/dummy/app/controllers/api/v2/empty_middle_controller.rb @@ -2,7 +2,7 @@ module Api module V2 class EmptyMiddleController < V2::BaseController # This is an empty controller, used to test cases where controllers - # may inherit from a middle controler that does not define a resource_description, + # may inherit from a middle controller that does not define a resource_description, # but the middle controller's parent does. def inconsequential_method diff --git a/spec/dummy/app/controllers/pets_controller.rb b/spec/dummy/app/controllers/pets_controller.rb index 7935215c..fbf5516c 100644 --- a/spec/dummy/app/controllers/pets_controller.rb +++ b/spec/dummy/app/controllers/pets_controller.rb @@ -63,7 +63,7 @@ def index # mixing request/response and response-only parameters # # the param_group :pet_with_id has several parameters which are - # not expectd in the request, but would show up in the response + # not expected in the request, but would show up in the response # and vice versa #----------------------------------------------------------- def_param_group :pet_with_id do diff --git a/spec/dummy/app/controllers/twitter_example_controller.rb b/spec/dummy/app/controllers/twitter_example_controller.rb index afc2f595..462d81a2 100644 --- a/spec/dummy/app/controllers/twitter_example_controller.rb +++ b/spec/dummy/app/controllers/twitter_example_controller.rb @@ -10,7 +10,7 @@ class TwitterExampleController < ApplicationController api :GET, '/twitter_example/lookup', 'Return up to 100 users worth of extended information, specified by either ID, screen name, or combination of the two.' param :screen_name, String, :desc => 'A comma separated list of screen names, up to 100 are allowed in a single request. You are strongly encouraged to use a POST for larger (up to 100 screen names) requests.' param :user_id, Integer, :desc => 'A comma separated list of user IDs, up to 100 are allowed in a single request. You are strongly encouraged to use a POST for larger requests.' - param :include_entities, String, :desc => 'When set to either true, t or 1, each tweet will include a node called "entities,". This node offers a variety of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags. While entities are opt-in on timelines at present, they will be made a default component of output in the future. See Tweet Entities for more detail on entities.' + param :include_entities, String, :desc => 'When set to either true, t or 1, each tweet will include a node called "entities,". This node offers a variety of metadata about the tweet in a discrete structure, including: user_mentions, urls, and hashtags. While entities are opt-in on timelines at present, they will be made a default component of output in the future. See Tweet Entities for more detail on entities.' description <<-EOS Return up to 100 users worth of extended information, specified by either ID, screen name, or combination of the two. The author's most recent status (if the authenticating user has permission) will be returned inline. @@ -53,8 +53,8 @@ def profile_image api :GET, '/twitter_example/search', 'Runs a search for users similar to Find People button on Twitter.com.' param :q, String, :desc => 'The search query to run against people search.', :required => true param :page, Integer, :desc => 'Specifies the page of results to retrieve.' - param :per_page, Integer, :desc => 'The number of people to retrieve. Maxiumum of 20 allowed per page.' - param :include_entities, String, :desc => 'When set to either true, t or 1, each tweet will include a node called "entities,". This node offers a variety of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags. While entities are opt-in on timelines at present, they will be made a default component of output in the future. See Tweet Entities for more detail on entities.' + param :per_page, Integer, :desc => 'The number of people to retrieve. Maximum of 20 allowed per page.' + param :include_entities, String, :desc => 'When set to either true, t or 1, each tweet will include a node called "entities,". This node offers a variety of metadata about the tweet in a discrete structure, including: user_mentions, urls, and hashtags. While entities are opt-in on timelines at present, they will be made a default component of output in the future. See Tweet Entities for more detail on entities.' description <<-EOS Runs a search for users similar to Find People button on Twitter.com. The results returned by people search on Twitter.com are the same as those returned by this API request. Note that unlike GET search, this method does not support any operators. diff --git a/spec/lib/apipie/apipies_controller_spec.rb b/spec/lib/apipie/apipies_controller_spec.rb index 90a497c9..47434c91 100644 --- a/spec/lib/apipie/apipies_controller_spec.rb +++ b/spec/lib/apipie/apipies_controller_spec.rb @@ -188,7 +188,7 @@ describe "authenticate user" do - it "authenticate user if an authentication method is setted" do + it "authenticate user if an authentication method is set" do test = false Apipie.configuration.authenticate = Proc.new do test = true diff --git a/spec/lib/apipie/file_handler_spec.rb b/spec/lib/apipie/file_handler_spec.rb index bd9e649f..274c1cc8 100644 --- a/spec/lib/apipie/file_handler_spec.rb +++ b/spec/lib/apipie/file_handler_spec.rb @@ -15,7 +15,7 @@ it { expect { file_handler.match? path }.to_not raise_error } end - context 'when the path contans an invalid byte sequence in UTF-8' do + context 'when the path contains an invalid byte sequence in UTF-8' do let(:path) { "%B6" } it { expect(file_handler.match? path).to be_falsy } diff --git a/spec/lib/apipie/param_description_spec.rb b/spec/lib/apipie/param_description_spec.rb index f9aef925..096caf63 100644 --- a/spec/lib/apipie/param_description_spec.rb +++ b/spec/lib/apipie/param_description_spec.rb @@ -21,7 +21,7 @@ it "should return the metadata" do meta = { - :lenght => 32, + :length => 32, :weight => '830g' } param = Apipie::ParamDescription.new(method_desc, :some_param, String, :meta => meta) From eb9f0f7528356f6ded094fa251b5e1fcd127a9a7 Mon Sep 17 00:00:00 2001 From: m-nakamura145 Date: Fri, 10 May 2024 08:36:27 +0900 Subject: [PATCH 06/12] Bump ruby versions on test jobs --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8252680a..a5b5f3d6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false matrix: rails: ["7.1", "7.0", "6.1", "6.0"] - ruby: ["3.3.0", "3.2.2", "3.1.4", "3.0.6", "2.7.8"] + ruby: ["3.3.1", "3.2.4", "3.1.5", "3.0.7", "2.7.8"] include: - rails: "5.2" ruby: "2.7.8" From 363a90675bed01100c3aba9e99a0662fc6d086c9 Mon Sep 17 00:00:00 2001 From: Masato Nakamura Date: Fri, 10 May 2024 19:55:52 +0900 Subject: [PATCH 07/12] Update .github/workflows/build.yml Co-authored-by: Ewoud Kohl van Wijngaarden --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5b5f3d6..19829b22 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false matrix: rails: ["7.1", "7.0", "6.1", "6.0"] - ruby: ["3.3.1", "3.2.4", "3.1.5", "3.0.7", "2.7.8"] + ruby: ["3.3", "3.2", "3.1", "3.0", "2.7"] include: - rails: "5.2" ruby: "2.7.8" From 733193d324cbf2a2a90de4b3880a7fd40866e01b Mon Sep 17 00:00:00 2001 From: m-nakamura145 Date: Sun, 12 May 2024 10:51:44 +0900 Subject: [PATCH 08/12] Remove RUBY_VERSION env --- .github/workflows/build.yml | 1 - .github/workflows/rubocop-challenger.yml | 2 -- .github/workflows/rubocop.yml | 2 -- Gemfile | 1 - 4 files changed, 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 19829b22..0f1d59b8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,6 @@ jobs: ruby: "2.6.10" env: - RUBY_VERSION: ${{ matrix.ruby }} RAILS_VERSION: ${{ matrix.rails }} steps: diff --git a/.github/workflows/rubocop-challenger.yml b/.github/workflows/rubocop-challenger.yml index eca78732..b5345c8f 100644 --- a/.github/workflows/rubocop-challenger.yml +++ b/.github/workflows/rubocop-challenger.yml @@ -8,8 +8,6 @@ jobs: create-pr: name: Create Pull Request runs-on: ubuntu-latest - env: - RUBY_VERSION: 3.2.3 steps: - uses: actions/checkout@v4 - name: Set up Ruby 3.2 diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 49e35d4d..56368ef1 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -5,8 +5,6 @@ on: [push, pull_request] jobs: build: runs-on: ubuntu-latest - env: - RUBY_VERSION: 3.2.3 steps: - uses: actions/checkout@v4 diff --git a/Gemfile b/Gemfile index 358f8f65..813bfcd2 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,6 @@ source 'https://rubygems.org' gemspec path: '.' # use ENV vars, with default value as fallback for local setup -ruby(ENV['RUBY_VERSION'] || '3.2.2') gem 'actionpack', "~> #{ENV['RAILS_VERSION'] || '7.0'}.0" gem 'activesupport', "~> #{ENV['RAILS_VERSION'] || '7.0'}.0" From 4b464be51e80d84a89dd5aa9157f03ebdc9144c4 Mon Sep 17 00:00:00 2001 From: Masato Nakamura Date: Tue, 28 May 2024 13:52:39 +0900 Subject: [PATCH 09/12] Support for Ruby 3.4.0-preview1 (#929) * default to 7.1 when unset --------- Co-authored-by: Mathieu Jobin <99191+mathieujobin@users.noreply.github.com> --- .github/workflows/build.yml | 2 ++ Gemfile | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0f1d59b8..aa36be4b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,8 @@ jobs: rails: ["7.1", "7.0", "6.1", "6.0"] ruby: ["3.3", "3.2", "3.1", "3.0", "2.7"] include: + - rails: "7.1" + ruby: "3.4.0-preview1" - rails: "5.2" ruby: "2.7.8" - rails: "5.1" diff --git a/Gemfile b/Gemfile index 813bfcd2..75b40981 100644 --- a/Gemfile +++ b/Gemfile @@ -5,8 +5,8 @@ source 'https://rubygems.org' gemspec path: '.' # use ENV vars, with default value as fallback for local setup -gem 'actionpack', "~> #{ENV['RAILS_VERSION'] || '7.0'}.0" -gem 'activesupport', "~> #{ENV['RAILS_VERSION'] || '7.0'}.0" +gem 'actionpack', "~> #{ENV['RAILS_VERSION'] || '7.1'}.0" +gem 'activesupport', "~> #{ENV['RAILS_VERSION'] || '7.1'}.0" gem 'mime-types' # , '~> 3.0' gem 'rails-controller-testing' From 1ea8d7a06f1bb8c6154cbdfcebea75e42935a05a Mon Sep 17 00:00:00 2001 From: Peko <91131029+pekopekopekopayo@users.noreply.github.com> Date: Tue, 28 May 2024 13:54:03 +0900 Subject: [PATCH 10/12] add "blank allowed" message to api doc (#926) --- app/views/apipie/apipies/_params.html.erb | 1 + app/views/apipie/apipies/_params_plain.html.erb | 1 + config/locales/en.yml | 1 + config/locales/ko.yml | 1 + 4 files changed, 4 insertions(+) diff --git a/app/views/apipie/apipies/_params.html.erb b/app/views/apipie/apipies/_params.html.erb index 71a20bab..91ea0792 100644 --- a/app/views/apipie/apipies/_params.html.erb +++ b/app/views/apipie/apipies/_params.html.erb @@ -15,6 +15,7 @@ <%= param[:required] ? t('apipie.required') : t('apipie.optional') %> <%= param[:allow_nil] ? ', '+t('apipie.nil_allowed') : '' %> + <%= param[:allow_blank] ? ', '+t('apipie.blank_allowed') : '' %> diff --git a/app/views/apipie/apipies/_params_plain.html.erb b/app/views/apipie/apipies/_params_plain.html.erb index 00d9aac3..2704ff1b 100644 --- a/app/views/apipie/apipies/_params_plain.html.erb +++ b/app/views/apipie/apipies/_params_plain.html.erb @@ -9,6 +9,7 @@ <%= param[:required] ? t('apipie.required') : t('apipie.optional') %> <%= param[:allow_nil] ? ', '+t('apipie.nil_allowed') : '' %> + <%= param[:allow_blank] ? ', '+t('apipie.blank_allowed') : '' %> <% if param[:validator] %> [ <%= Apipie.markup_to_html(param[:validator]).html_safe %> ] <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index f751cffe..91dcc653 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -15,6 +15,7 @@ en: required: required optional: optional nil_allowed: nil allowed + blank_allowed: blank allowed param_name: Param name params: Params examples: Examples diff --git a/config/locales/ko.yml b/config/locales/ko.yml index f15a70cb..01433c1d 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -15,6 +15,7 @@ ko: required: 필수 optional: 옵션 nil_allowed: nil 허용 + blank_allowed: blank 허용 param_name: Param 이름 params: Params examples: 예시 From 41a6c5abfa404fe58596d0c40bd9b9f42471fc13 Mon Sep 17 00:00:00 2001 From: Panos Dalitsouris <22100396+PanosCodes@users.noreply.github.com> Date: Tue, 28 May 2024 08:03:11 +0300 Subject: [PATCH 11/12] Custom headers responses (#924) * Support headers in responses * Reorder methods --------- --- README.md | 19 ++++++ app/views/apipie/apipies/_method_detail.erb | 2 + .../method_description/response_service.rb | 15 ++++- lib/apipie/response_description.rb | 43 ++++++++++--- .../response_service_spec.rb | 62 +++++++++++++++++++ .../response_object_spec.rb | 22 +++++++ spec/lib/apipie/response_description_spec.rb | 56 +++++++++++++++++ 7 files changed, 209 insertions(+), 10 deletions(-) create mode 100644 spec/lib/apipie/generator/swagger/method_description/response_service_spec.rb create mode 100644 spec/lib/apipie/response_description/response_object_spec.rb create mode 100644 spec/lib/apipie/response_description_spec.rb diff --git a/README.md b/README.md index 80be5af0..825569db 100644 --- a/README.md +++ b/README.md @@ -630,6 +630,25 @@ end Note the use of the `property` keyword rather than `param`. This is the preferred mechanism for documenting response-only fields. +#### Specify response headers + +We can specify the response headers using the `header` keyword within the `returns` block. + +##### Example +```ruby +api :GET, "/pets/:id/with-extra-details", "Get a detailed pet record" +returns code: 200, desc: "Detailed info about the pet" do + param_group :pet + property :num_legs, Integer, :desc => "How many legs the pet has" + header 'Link', String, 'Relative links' + header 'Current-Page', Integer, 'The current page', required: true +end + +def show + render JSON({ :pet_name => "Barkie", :animal_type => "iguana", :legs => 4 }) +end +``` + #### The Property keyword `property` is very similar to `param` with the following differences: diff --git a/app/views/apipie/apipies/_method_detail.erb b/app/views/apipie/apipies/_method_detail.erb index 15767ecc..390a9b65 100644 --- a/app/views/apipie/apipies/_method_detail.erb +++ b/app/views/apipie/apipies/_method_detail.erb @@ -55,6 +55,8 @@ <%= render(:partial => "params", :locals => {:params => item[:returns_object]}) %> + + <%= render(:partial => "headers", :locals => {:headers => item[:headers], :h_level => h_level+2 }) %> <% end %> <% end %> diff --git a/lib/apipie/generator/swagger/method_description/response_service.rb b/lib/apipie/generator/swagger/method_description/response_service.rb index 0b7fc7ec..c1d5449f 100644 --- a/lib/apipie/generator/swagger/method_description/response_service.rb +++ b/lib/apipie/generator/swagger/method_description/response_service.rb @@ -39,7 +39,8 @@ def responses allow_null: false, http_method: @http_method, controller_method: @method_description - ).to_swagger + ).to_swagger, + headers: response_headers(response.headers) }.compact end end @@ -55,4 +56,16 @@ def empty_returns { 200 => { description: 'ok' } } end + + # @param [Array] headers + # + # https://swagger.io/specification/v2/#header-object + def response_headers(headers) + headers.each_with_object({}) do |header, result| + result[header[:name].to_s] = { + description: header[:description], + type: header[:validator] + } + end + end end diff --git a/lib/apipie/response_description.rb b/lib/apipie/response_description.rb index 7c11eea1..fd226bfd 100644 --- a/lib/apipie/response_description.rb +++ b/lib/apipie/response_description.rb @@ -6,6 +6,7 @@ class ResponseObject include Apipie::DSL::Param attr_accessor :additional_properties, :typename + attr_reader :headers def initialize(method_description, scope, block, typename) @method_description = method_description @@ -13,6 +14,7 @@ def initialize(method_description, scope, block, typename) @param_group = {scope: scope} @additional_properties = false @typename = typename + @headers = [] self.instance_exec(&block) if block @@ -43,6 +45,18 @@ def prepare_hash_params end end + # @param [String] header_name + # @param [String, symbol, Class] validator + # @param [String] description + # @param [Hash] options + def header(header_name, validator, description, options = {}) + @headers << { + name: header_name, + validator: validator.to_s.downcase, + description: description, + options: options + } + end end end @@ -64,15 +78,6 @@ def self.from_dsl_data(method_description, code, args) adapter) end - def is_array? - @is_array_of != false - end - - def typename - @response_object.typename - end - - def initialize(method_description, code, options, scope, block, adapter) @type_ref = options[:param_group] @@ -105,6 +110,14 @@ def initialize(method_description, code, options, scope, block, adapter) @response_object.additional_properties ||= options[:additional_properties] end + def is_array? + @is_array_of != false + end + + def typename + @response_object.typename + end + def param_description nil end @@ -118,6 +131,17 @@ def additional_properties end alias allow_additional_properties additional_properties + # @return [Array] + def headers + # TODO: Support headers for Apipie::ResponseDescriptionAdapter + if @response_object.is_a?(Apipie::ResponseDescriptionAdapter) + return [] + end + + @response_object.headers + end + + # @return [Hash{Symbol->TrueClass | FalseClass}] def to_json(lang = nil) { :code => code, @@ -125,6 +149,7 @@ def to_json(lang = nil) :is_array => is_array?, :returns_object => params_ordered.map{ |param| param.to_json(lang).tap{|h| h.delete(:validations) }}.flatten, :additional_properties => additional_properties, + :headers => headers } end end diff --git a/spec/lib/apipie/generator/swagger/method_description/response_service_spec.rb b/spec/lib/apipie/generator/swagger/method_description/response_service_spec.rb new file mode 100644 index 00000000..71adc015 --- /dev/null +++ b/spec/lib/apipie/generator/swagger/method_description/response_service_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' + +describe Apipie::Generator::Swagger::MethodDescription::ResponseService do + let(:http_method) { nil } + let(:language) { :en } + let(:dsl_data) { ActionController::Base.send(:_apipie_dsl_data_init) } + + let(:method_description) do + Apipie::Generator::Swagger::MethodDescription::Decorator.new( + Apipie::MethodDescription.new( + 'create', + Apipie::ResourceDescription.new(ApplicationController, 'pets'), + dsl_data + ) + ) + end + + let(:returns) { [] } + + let(:service) do + described_class.new( + method_description, + http_method: http_method, + language: language + ) + end + + describe '#call' do + describe 'headers' do + subject(:headers) { service.call[status_code][:headers] } + + let(:status_code) { 200 } + + it { is_expected.to be_blank } + + context 'when headers exists' do + let(:dsl_data) { super().merge({ returns: returns }) } + let(:returns) { { status_code => [{}, nil, returns_dsl, nil] } } + + let(:returns_dsl) do + proc do + header 'link', String, 'Relative links' + header 'Current-Page', Integer, 'The current page' + end + end + + it 'returns the correct format headers' do + expect(headers).to eq({ + 'link' => { + description: 'Relative links', + type: 'string' + }, + 'Current-Page' => { + description: 'The current page', + type: 'integer' + } + }) + end + end + end + end +end diff --git a/spec/lib/apipie/response_description/response_object_spec.rb b/spec/lib/apipie/response_description/response_object_spec.rb new file mode 100644 index 00000000..47adf3be --- /dev/null +++ b/spec/lib/apipie/response_description/response_object_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe Apipie::ResponseDescription::ResponseObject do + describe '#header' do + let(:response_object) { described_class.new(nil, nil, nil, nil) } + let(:name) { 'Current-Page' } + let(:description) { 'The current page in the pagination' } + + before { response_object.header(name, String, description) } + + it 'adds it to the headers' do + expect(response_object.headers).to( + contain_exactly({ + name: name, + description: description, + validator: 'string', + options: {} + }) + ) + end + end +end diff --git a/spec/lib/apipie/response_description_spec.rb b/spec/lib/apipie/response_description_spec.rb new file mode 100644 index 00000000..f9f0fb2e --- /dev/null +++ b/spec/lib/apipie/response_description_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe Apipie::ResponseDescription do + let(:resource_description) do + Apipie::ResourceDescription.new(PetsController, 'pets') + end + + let(:method_description) do + Apipie::MethodDescription.new( + 'create', + resource_description, + ActionController::Base.send(:_apipie_dsl_data_init) + ) + end + + let(:response_description_dsl) { proc {} } + let(:options) { {} } + + let(:response_description) do + described_class.new( + method_description, + 204, + options, + PetsController, + response_description_dsl, + nil + ) + end + + describe '#to_json' do + let(:to_json) { response_description.to_json } + + describe 'headers' do + subject(:headers) { to_json[:headers] } + + it { is_expected.to be_blank } + + context 'when response has headers' do + let(:response_description_dsl) do + proc do + header 'Current-Page', Integer, 'The current page in the pagination', required: true + end + end + + it 'returns the header' do + expect(headers).to contain_exactly({ + name: 'Current-Page', + description: 'The current page in the pagination', + validator: 'integer', + options: { required: true } + }) + end + end + end + end +end From 942313b85ed8e060b89c2637e1971cf0c149ea71 Mon Sep 17 00:00:00 2001 From: Mathieu Jobin <99191+mathieujobin@users.noreply.github.com> Date: Thu, 30 May 2024 07:43:33 +0900 Subject: [PATCH 12/12] Release v1.4.0 --- CHANGELOG.md | 13 +++++++++++++ lib/apipie/version.rb | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a2e80ed..c615dea9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ Changelog =========== +## [v1.4.0](https://github.com/Apipie/apipie-rails/tree/v1.4.0) (2024-05-30) +[Full Changelog](https://github.com/Apipie/apipie-rails/compare/v1.3.0...v1.4.0) +* Add Ruby 3.3.0 to CI build matrix (#906) Masato Nakamura +* Fix rubocop CI (#910) (Masato Nakamura) +* Add metadata for rubygems.org and use https URLs (#909) (Masato Nakamura) +* Bump GHA actions/checkout to be on node20 by default (#911) (Masato Nakamura) +* Convert readme to markdown (#920) (Panos Dalitsouris) +* Fix typos, Found via `codespell` (#921) (Kian-Meng Ang) +* Bump ruby versions on test jobs (#927) (Masato Nakamura) +* Support for Ruby 3.4.0-preview1 (#929) (Masato Nakamura) +* Add "blank allowed" message to api doc (#926) (Peko) +* Custom headers responses (#924) (Panos Dalitsouris) + ## [v1.3.0](https://github.com/Apipie/apipie-rails/tree/v1.3.0) (2023-12-19) [Full Changelog](https://github.com/Apipie/apipie-rails/compare/v1.2.3...v1.3.0) * rubocop fixes ([#897](https://github.com/Apipie/apipie-rails/pull/897)) (Mathieu Jobin) diff --git a/lib/apipie/version.rb b/lib/apipie/version.rb index 70f25910..beaf9f6f 100644 --- a/lib/apipie/version.rb +++ b/lib/apipie/version.rb @@ -1,3 +1,3 @@ module Apipie - VERSION = "1.3.0" + VERSION = "1.4.0" end