Git Product home page Git Product logo

swagger_yard's Introduction

SwaggerYard Build Status

SwaggerYard is a gem to convert custom YARD tags in comments into Swagger 2.0 or OpenAPI 3.0.0 specs.

Installation

Put SwaggerYard in your Gemfile:

gem 'swagger_yard'

Install the gem with Bunder:

bundle install

Getting Started

Place configuration in a Rails initializer or suitable configuration file:

# config/initializers/swagger_yard.rb
SwaggerYard.configure do |config|
  config.api_version = "1.0"

  config.title = 'Your API'
  config.description = 'Your API does this'

  # where your actual api is hosted from
  config.api_base_path = "http://localhost:3000/api"

  # Where to find controllers (can be an array of paths/globs)
  config.controller_path = ::Rails.root + 'app/controllers/**/*'

  # Where to find models (can be an array)
  config.model_path = ::Rails.root + 'app/decorators/**/*'

  # Whether to include controller methods marked as private
  # (either with ruby `private` or YARD `# @visibility private`
  # Default: true
  config.include_private = true
end

Then start to annotate controllers and models as described below.

Generate Specification

To generate a Swagger or OpenAPI specification, use one of the SwaggerYard::Swagger or SwaggerYard::OpenAPI classes as follows in a script or Rake task (or use swagger_yard-rails):

# Register the yard tags
SwaggerYard.register_custom_yard_tags!

spec = SwaggerYard::OpenAPI.new
# Generate YAML
File.open("openapi.yml", "w") { |f| f << YAML.dump(spec.to_h) }
# Generate JSON
File.open("openapi.json, "w") { |f| f << JSON.pretty_generate(spec.to_h) }

Documenting APIs

Documenting controllers and models is best illustrated by example.

Controller

Each Swagger-ized controller must have a @resource tag naming the API to be documented. Without this tag, no endpoints will be generated.

Then, document each controller action method that is an endpoint of the API. Each endpoint needs to have a @path tag at a minimum. @parameter tags and @response_type/@response tags specify the inputs and outputs to the endpoint. A request body is specified with the use of a single @parameter tag with a (body) option. (@response_type is shorthand for the type of the default response, while @response allows you to specify the HTTP status.)

# @resource Account ownership
#
# This document describes the API for creating, reading, and deleting account ownerships.
#
class Accounts::OwnershipsController < ActionController::Base
  ##
  # Returns a list of ownerships associated with the account.
  #
  # Status can be -1(Deleted), 0(Inactive), 1(Active), 2(Expired) and 3(Cancelled).
  #
  # @path [GET] /accounts/ownerships
  #
  # @parameter offset   [integer]               Used for pagination of response data (default: 25 items per response). Specifies the offset of the next block of data to receive.
  # @parameter status   [array<string>]         Filter by status. (e.g. status[]=1&status[]=2&status[]=3).
  # @parameter sort_order [enum<id,begin_at,end_at,created_at>]  Orders response by fields. (e.g. sort_order=created_at).
  # @parameter sort_descending    [boolean]     Reverse order of sort_order sorting, make it descending.
  # @parameter begin_at_greater   [date]        Filters response to include only items with begin_at >= specified timestamp (e.g. begin_at_greater=2012-02-15T02:06:56Z).
  # @parameter begin_at_less      [date]        Filters response to include only items with begin_at <= specified timestamp (e.g. begin_at_less=2012-02-15T02:06:56Z).
  # @parameter end_at_greater     [date]        Filters response to include only items with end_at >= specified timestamp (e.g. end_at_greater=2012-02-15T02:06:56Z).
  # @parameter end_at_less        [date]        Filters response to include only items with end_at <= specified timestamp (e.g. end_at_less=2012-02-15T02:06:56Z).
  #
  def index
  end

  ##
  # Returns an ownership for an account by id
  #
  # @path [GET] /accounts/ownerships/{id}
  # @response_type [Ownership]
  # @response [EmptyOwnership] 404 Ownership not found
  # @response 400 Invalid ID supplied
  #
  def show
  end

  ##
  # Creates an ownership for an account
  #
  # @path [POST] /accounts/ownerships
  # @parameter ownership(body) [Ownership] The ownership to be created
  # @response_type [Ownership]
  def create
  end
end

Private controllers/actions

When you set include_private = false in the SwaggerYard configuration, you can mark action methods as private, so that they won't be documented, using @visibility private in comments.

  ##
  # @visibility private
  def show
  end

Model

Each model to be exposed in the specification must have a @model tag. Model properties are specified with @property tags. JSON Schema additionalProperties can be specified with @additional_properties <type> where <type> is any type defined elsewhere, or simply false to denote a closed model (additionalProperties: false).

#
# @model
#
# @property id(required)    [integer]   the identifier for the pet
# @property name  [Array<string>]    the names for the pet
# @property age   [integer]   the age of the pet
# @property relatives(required) [Array<Pet>] other Pets in its family
# @additional_properties string
#
class Pet
end

To then use your Model in your Controller documentation, add @parameters:

# @parameter pet(body) [Pet] The pet object

To support Swagger Polymorphism, use @discriminator and @inherits:

#
# @model
#
# @property id(required)    [integer]   the identifier for the pet
# @property name  [Array<string>]    the names for the pet
# @property age   [integer]   the age of the pet
# @property relatives(required) [Array<Pet>] other Pets in its family
# @discriminator petType(required) [string] the type of pet
#
class Pet
end

#
# @model
#
# @inherits Pet
#
# @property packSize(required) [integer] the size of the pack the dog is from
#
class Dog < Pet
end

If you wish to name your model differently from the underlying ruby class, add the name as text to the @model tag. In the example here, if we did not specify Dog as the model name, it would have been named Models_Dog.

# @model Dog
module Models
  class Dog
  end
end

Types

Types of things (parameters or responses of an operation, properties of a model) are indicated inside square-brackets (e.g., [string]) as part of a YARD tag.

  • Model references should be Capitalized or CamelCased by convention.
  • Basic types (integer, boolean, string, object, number, date, time, date-time, uuid, etc.) should be lowercased.
  • An array of models or basic types is specified with [array<...>].
  • An enum of allowed string values is specified with [enum<one,two,three>].
  • An enum of allowed values that are defined in the application [enum<{CURRENCIES}>].
  • An object definition can include the property definitions of its fields, and / or of an additional property for any remaining allowed fields. E.g., [object<name: string, age: integer, string >]
  • Structured data like objects, arrays, pairs, etc., definitions can also be nested; E.g., [object<pairs:array<object<right:integer,left:integer>>>]
  • JSON-Schema format attributes can be specified for basic types using <...>. For example, [integer<int64>] produces JSON { "type": "integer", "format": "int64" }.
  • Regex pattern constraints can be specified for strings using [regex<PATTERN>]. For example, [regex<^.{3}$>] produces JSON { "type": "string", "pattern": "^.{3}$" }.
  • A union of two or more sub-types is expressed as (A | B) (parentheses required). This translates to oneOf: in JSON Schema.
  • An intersection of two or more sub-types is expressed as (A & B) (parentheses required). This translates to allOf: in JSON Schema.

Options

Parameter or property options are expressed inside parenthesis immediately following the parameter or property name.

Examples:

# @parameter name(required) [string]  Name of the package
# @parameter age(nullable)  [integer] Age of package
# @parameter package(body)  [Package] Package object

Possible parameters include:

  • required: indicates a required parameter or property.
  • nullable: indicates that JSON null is an allowed value for the property.
  • multiple: indicates a parameter may appear multiple times (usually in a query string, e.g., param=a&param=b&param=c)
  • body/query/path/formData: Indicates where the parameter is located.

Examples

The Swagger and OpenAPI specs both allow for specifying example data at multiple levels. SwaggerYard allows you to use an @example tag to specify example JSON data at the response, model, and individual property levels.

The basic format of an @example is:

# @example [name]
#    body content
#    should be indented
#    and can span
#    multiple lines

Response examples

Response examples should appear in a method documentation block inside a controller, alongside the parameters and response tags. Use a named @example to associate the example with a specific response, or use an unnammed example to associate the data to the default response (when using a @response_type tag).

  # return a Pet
  # @path [GET] /pets/{id}
  # @parameter id [integer] The ID for the Pet
  # @response_type [Pet]
  # @response [ErrorPet] 404 Pet not found
  # @example
  #    {"id": 1, "names": ["Fido"], "age": 12}
  # @example 404
  #    {"error": 404, "message": "Pet not found"}
  def show
  end

Model examples

Use a model example to specify an example for the entire model at once. The example tag should omit any name to associate the data with the model itself and not a single property.

# @model
# @property id(required)  [integer]
# @property name          [string]
# @example
#   {"id": 42, "name": "Fred Flintstone"}
class Person
end

Property examples

Use property examples to specify data for individual properties. To associate the example data, the @example tag must use the same name as the property and appear after the property.

# @model
# @property id(required)  [integer]
# @example id
#    42
# @property name          [string]
# @example name
#   "Fred Flintstone"
class Person
end

Standalone Model

Types can be specified without being associated to an existing model with the @!model directive. It is useful when documenting a create and an update of the same class:

# @!model CreatePet
# @property id(required)    [integer]
# @property name(required)  [string]
#
# @!model UpdatePet
# @property id(required)    [integer]
# @property name            [string]

It can also be needed when the body parameter of a path is not totally matching a model.

Note that a model name must be given to the directive.

External Schema

Types can be specified that refer to external JSON schema documents for their definition. External schema documents are expected to also define their models under a definitions top-level key like so:

{
  "definitions": {
    "MyStandardModel": {
    }
  }
}

To register an external schema so that it can be referenced in places where you specify a type, configure SwaggerYard as follows:

SwaggerYard.configure do |config|
  config.external_schema mymodels: 'https://example.com/mymodels/v1.0'
end

Then refer to models in the schema using the syntax [mymodels#MyStandardModel] where types are specified. This causes SwaggerYard to emit the following schema for the type:

{ "$ref": "https://example.com/mymodels/v1.0#/definitions/MyStandardModel" }

Authorization

API Key auth

SwaggerYard supports several authorization styles. Start by adding @authorization to your ApplicationController.

#
# @authorization [api_key] header X-APPLICATION-API-KEY
#
class ApplicationController < ActionController::Base
end

Then you can use these authorizations from your controller or actions in a controller.

#
# @authorize_with header_x_application_api_key
#
class PetController < ApplicationController
end

Supported formats for the @authorization tag are as follows:

# @authorization [apiKey] (query|header|cookie) key-name The rest is a description
# @authorization [bearer] mybearerauth bearer-format The rest is a description
# @authorization [basic] mybasicauth The rest is a description
# @authorization [digest] digestauth The rest is a description
# @authorization [<any-rfc7235-auth>] myrfcauth The rest is a description
  • For apiKey the name of the authorization is formed as "#{location}_#{key_name}".downcase.gsub('-','_'). Example: @authorization [apiKey] header X-API-Key is named header_x_api_key. (This naming scheme is kept for backwards compatibility.)

  • All others are named by the tag name following the [type]. Example: @authorization [bearer] myBearerAuth Format Description is named myBearerAuth.

Custom security schemes

SwaggerYard also supports custom security schemes. You can define these in your configuration like:

SwaggerYard.configure do |config|
  config.security_schemes['petstore_oauth'] = {
    type: "oauth2",
    flows: {
      implicit: {
        authorizationUrl: "http://swagger.io/api/oauth/dialog",
        scopes: {
          "write:pets": "modify pets in your account",
          "read:pets": "read your pets"		
        }
      }
    }
  }
end

Then you can also use these authorizations from your controller or actions in a controller.

# @authorize_with petstore_oauth
class PetController < ApplicationController
end

Better Rails integration with swagger_yard-rails

To generate specifications from your Rails app on request, check out the swagger_yard-rails project. This provides an engine that has a mountable endpoint that will parse the source code and render the specification as a json document.

Path Discovery Function

SwaggerYard configuration allows setting of a "path discovery function" which will be called for controller action method documentation that have no @path tag. The function should return an array containing ["<method>", "<path>"] if any can be determined.

SwaggerYard.configure do |config|
  config.path_discovery_function = ->(yard_obj) do
    # code here to inspect the yard doc object
    # and return a [method, path] for the api
  end
end

In swagger_yard-rails, this hook is used to set a function that inspects the Rails routing tables to reverse look up and compute paths.

More Information

swagger_yard's People

Contributors

bfad avatar chtrinh avatar daareiza avatar erictuvesson avatar nicksieger avatar olemchls avatar quiwin avatar stefschenkelaars avatar therealadam avatar timbogit avatar tjschuck avatar tpitale avatar twrodriguez avatar xhagrg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

swagger_yard's Issues

non-array @properties of a custom type do not get interpreted

If I declare a property like this:

@property [Array<FoobarClass>] foobars

The parsed response is correct, and includes an array of foobars. However...

@property [FoobarClass] just_one_foobar

is rendered as a string with the sample text "FoobarClass" and any attributes of FoobarClass, even those marked with @property attributes themselves are ignored.

Yaml generation fails to generate a correct tags block.

The write to yaml is broken.
generates:
....
tags:

  • name: Phenotypes foobar
    description: &9 !ruby/string:YARD::Docstring
    str: Some descr
    tags:
    • !ruby/object:YARD::Tags::Tag
      tag_name: resource
      text: Phenotypes foobar
      name:
      types:
      object: &2 !ruby/object:YARD::CodeObjects::ClassObject
      children: !ruby/array:YARD::CodeObjects::CodeObjectList
      ... 1000+ lines of stuff :( ...
      components:
      schemas:
      ...

the json writer generates:
...
"tags": [
{
"name": "Phenotypes foobar",
"description": "Some descr"
}
],
"components": {
...

The tags seems to be the only block that is broken, the other blocks are as expected.

Parameter serialization options

The OpenAPI 3.0 specification provides the ability to specify a parameter serialization style for objects, but I can't find a way to implement the "style": "deepObject" option when defining a parameter that's an object.

For example, the JSON-API specification indicates that sparse fieldset and pagination parameters should be sent in the following format:

/api/v1/examples?page[number]=1&page[size]=25

In order to accomplish this, I would define a parameter like this:

# @parameter page  [object<number:integer,size:integer>] Sets the current page and the page size

When I run that through SwaggerYard, the parameter structure that shows up in the JSON file looks like this:

{
  "name": "page",
  "description": "Sets the current page and the page size",
  "required": false,
  "in": "query",
  "schema": {
    "type": "object",
    "properties": {
      "number": {
        "type": "integer"
      },
      "size": {
        "type": "integer"
      }
    }
  }
}

In order for Swagger UI to serialize the object in the format that's necessary, I need to be able to specify the "style": "deepObject" option for the parameter above, but I don't see anything in the SwaggerYard documentation that would allow me to do it.

Is there a way for me to specify this option? Thanks for your help.

Improve maintainability of library

Currently it takes quite long for pull requests to get reviewed and merged. As discussed #75 (comment), it would be nice if we could improve the community around this gem.

Steps we can take:

Add more contributors to GitHub

@nicksieger said he will try to track someone with admin rights.

Simplify releasing to rubygems

Maybe we can automate the releasing to rubygems in some way. I know there are tools to automatically bump versions once PRs are merged.
At our company we use this GitHub actions workflow to build and publish the gem to rubygems based on a GitHub release / tag.

Remove configuration layer

We have an application with multiple API definitions in the same codebase. Therefore we would like to be able to generate multiple specifications with different settings. This is doable but I think this flow could be improved by removing the configuration or specification layer. Let me explain.

Currently the library uses global settings which are defined in the Configuration class. Then some local configuration can be passed to the Specification class but not everything can be overwritten. Next to this, the parsing is depending on the configuration settings which makes the dependency tree quite complex.

My proposal is therefore to make merge the Configuration and Specification class. This object then defines all the settings needed throughout the modules. The Swagger and OpenAPI classes should then receive an instance of this class as an argument which completely defines all parsing and processing settings.

@nicksieger Do you think it's worth it to spend the time on this?

!ruby/string:YARD::Docstring lines in OpenAPI.yml

The OpenAPI.yml from swagger_yard has thousands of lines such as !ruby/string:YARD::Docstring and &730 !ruby/object:YARD::CodeObjects::MethodObject. What can I do to prevent these lines from showing up in my OpenAPI file?

@authorize_with not working on specific action

Ruby 3.0
Rails 6.1.4

When trying to apply @authroize_with on specific controller action rather than whole controller, security not created in schema.

Base controller:

# @authorization [bearer] access_token jwt
class ApplicationController < ActionController::Base
end

Token controller:

# @resource Token
class TokensController < ApplicationController
##
# Sign in user
#
# @path [GET] /sign_in
 def sign_in
  render(json: {status: 'ok'})
 end

##
# Sign out user
#
# @path [GET] /sign_out
# @authorize_with access_token
 def sign_out
  render(json: {status: 'ok'})
 end 
end

But @authorize_with works if you will place it on controller thus security will be created and for all actions

OAuth2 support

Hey there,

I was wondering if you folks have a opinion on how swagger_yard should define OAuth2 based auth. Because with the current pattern of @authorization it seems to be very complicated. The OAuth2 SECURITY SCHEME OBJECT has a few more parameters to configure.

Excerpt from the 2.0 specs http://swagger.io/specification/#securitySchemeObject

petstore_auth:
  type: oauth2
  authorizationUrl: http://swagger.io/api/oauth/dialog
  flow: implicit
  scopes:
    write:pets: modify pets in your account
    read:pets: read your pets

I wanted to add support for it (first w/o support for scopes), but wanted to check your ideas first. Before I head in the wrong direction. Also having more complex yard tags seems not very common in this gem.

Two ideas I was thinking of:

1. Add it to @authorization tag

In order to implement this, there must be a way to integrate more parameters into the tag. Like flow and the flow dependent urls like authorizationUrl.

2. Add a securityDefinitions option to SwaggerYard::Configuration

This would be the easier solution. Adding a attribute to the config so it can be used with @authorize_with I image the config block look like this:

SwaggerYard.configure do |config|
  config.security_definitions['petstore_oauth'] = {
    type: "oauth2",
    authorizationUrl: "http://swagger.io/api/oauth/dialog",
    flow: :implicit
  }
  ....
end

If you have any more ideas, I'd love to hear your feedback! I'd also love to hear some thoughts on scopes.

cheers!

Appears not to like nested resources

defining for a nested controller:

@resource test
@resource_path /api/user/{user_id}/tests
@resource_parameter user_id [String]
class API::UserTestsController < APIController

# ....
end

Results in JS cratering and printing the message:
Unable to read api '' from path http://server/swagger/api/users/{user_id}/tests/ (server returned undefined)

and the JS appears to be trying to fetch the resource URL directly, escaping the braces in the URL.

Controller documentation tags

Hey,

I was trying to use this gem for our projects which are documented with yard docs.

Our docs are like this, as that is the only way we have found it to work with yard.

##
# 
# @path [POST] /path/action
#
# @overload set(params)
# @option abc1 [Hash] args
# @option abc2 [Integer] order_id
# @option abc3 [Hash] options

I tried the example here.
https://github.com/livingsocial/swagger_yard#controller

However I am getting a lot of Unknown tag @path errors and Unknown tag @parameter.

What is the correct format to get this working?

How do I use this gem?

Sorry, if this is an obvious question. I followed the instructions in the readme, including adding the gem and the initializer. However, if I run yard I don't see any Swagger files generated. Am I missing something?

Not Registering Custom Tags

It looks like SwaggerYard.register_custom_yard_tags! is never fired when initializing SwaggerYard. Before adding this line to my own config I got errors like Unknown tag @resource in file and the documentation is never generated. Is this an oversight or am I doing something wrong? I've followed the docs pretty much exactly

Group tags by name

In my case I have the same resource as both controllers work around the same object, just makes it easier to split it out a bit.

# @resource MyResource
class MyController1 < ActionController::Base
end

# @resource MyResource
class MyController2 < ActionController::Base
end

This will generate 2 tags in the swagger output.

"tags": [
  {
    "name": "MyResource",
    "description": ""
  },
  {
    "name": "MyResource",
    "description": ""
  }
],

When parsing the swagger file I get this warning.

Semantic error at tags.2
Tag Objects must have unique `name` field values

This is not major as it still works with most parsers.

Issue on the README documentation

Hi,
Thanks for your wonderful gem. I have faced some problems while following your README guideline . When I try accessing page http://<- root_url ->/swagger/docs, it shows me
No route matches [GET] "/swagger/docs"
But when I do http://<- root_url ->/swagger/doc, it works fine.
Is it just me?

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.