Merge pull request #371 from rswag/jamie

PR Grooming
This commit is contained in:
Blake Erickson 2021-01-27 07:59:59 -07:00 committed by GitHub
commit b86d3063a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 721 additions and 76 deletions

View File

@ -1,15 +1,17 @@
# rswag
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- RequestBody now supports the `required` flag [#342](https://github.com/rswag/rswag/pull/342)
### Changed
### Deprecated
### Removed
### Fixed
- Fix response example rendering [#330](https://github.com/rswag/rswag/pull/330)
- Fix empty content block [#347](https://github.com/rswag/rswag/pull/347)
### Security
## [2.3.1] - 2020-04-08

View File

@ -363,8 +363,8 @@ you should use the folowing syntax, making sure there are no whitespaces at the
### Specifying/Testing API Security ###
Swagger allows for the specification of different security schemes and their applicability to operations in an API.
To leverage this in rswag, you define the schemes globally in _swagger_helper.rb_ and then use the "security" attribute at the operation level to specify which schemes, if any, are applicable to that operation.
Swagger allows for the specification of different security schemes and their applicability to operations in an API.
To leverage this in rswag, you define the schemes globally in _swagger_helper.rb_ and then use the "security" attribute at the operation level to specify which schemes, if any, are applicable to that operation.
Swagger supports :basic, :bearer, :apiKey and :oauth2 and :openIdConnect scheme types. See [the spec](https://swagger.io/docs/specification/authentication/) for more info, as this underwent major changes between Swagger 2.0 and Open API 3.0
```ruby
@ -416,7 +416,7 @@ describe 'Blogs API' do
end
# example of documenting an endpoint that handles basic auth and api key based security
describe 'Auth examples API' do
describe 'Auth examples API' do
path '/auth-tests/basic-and-api-key' do
post 'Authenticates with basic auth and api key' do
tags 'Auth Tests'
@ -437,11 +437,11 @@ describe 'Auth examples API' do
end
end
end
```
__NOTE:__ Depending on the scheme types, you'll be required to assign a corresponding parameter value with each example.
__NOTE:__ Depending on the scheme types, you'll be required to assign a corresponding parameter value with each example.
For example, :basic auth is required above and so the :Authorization (header) parameter must be set accordingly
## Configuration & Customization ##
@ -479,9 +479,9 @@ rake rswag:specs:swaggerize PATTERN="spec/swagger/**/*_spec.rb"
### Referenced Parameters and Schema Definitions ###
Swagger allows you to describe JSON structures inline with your operation descriptions OR as referenced globals.
Swagger allows you to describe JSON structures inline with your operation descriptions OR as referenced globals.
For example, you might have a standard response structure for all failed operations.
Again, this is a structure that changed since swagger 2.0. Notice the new "schemas" section for these.
Again, this is a structure that changed since swagger 2.0. Notice the new "schemas" section for these.
Rather than repeating the schema in every operation spec, you can define it globally and provide a reference to it in each spec:
```ruby
@ -560,7 +560,7 @@ end
### Response headers ###
In Rswag, you could use `header` method inside the response block to specify header objects for this response.
In Rswag, you could use `header` method inside the response block to specify header objects for this response.
Rswag will validate your response headers with those header objects and inject them into the generated swagger file:
```ruby
@ -608,16 +608,20 @@ To enable examples generation from responses add callback above run_test! like:
```
after do |example|
example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) }
example.metadata[:response][:content] = {
'application/json' => {
example: JSON.parse(response.body, symbolize_names: true)
}
}
end
```
You need to disable --dry-run option for Rspec > 3
<!-- This is now enabled by default in rswag.
<!-- This is now enabled by default in rswag.
You need to set the ``` config.swagger_dry_run = false``` value in the spec/spec_helper.rb file.
This is one of the more powerful features of rswag. When rswag runs your integration test suite via ```bundle exec rspec```, it will capture the request and response bodies and output those values in the examples section.
These integration tests are usually written with ```let``` variables for post body parameters, and since its an integration test the service is returning actual values.
These integration tests are usually written with ```let``` variables for post body parameters, and since its an integration test the service is returning actual values.
We might as well re-use these values and embed them into the generated swagger to provide a more real world example for request/response examples. -->
Add to config/environments/test.rb:
@ -656,8 +660,8 @@ describe 'Blogs API', document: false do
```
##### rswag helper methods #####
<!--
There are some helper methods to help with documenting request bodies.
<!--
There are some helper methods to help with documenting request bodies.
```ruby
describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
let(:api_key) { 'fake_key' }
@ -693,7 +697,7 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
end
end
end
end
end
```
In the above example, we see methods ```request_body_json``` ```request_body_plain``` ```request_body_xml```.
@ -703,7 +707,7 @@ and the examples: :blog which will create a named example "blog" under the "requ
Again, documenting request response examples changed in Open API 3.0. The example above would generate a swagger.json snippet that looks like this:
```json
...
...
{"requestBody": {
"required": true,
"content": {
@ -737,9 +741,9 @@ Again, documenting request response examples changed in Open API 3.0. The exampl
}
```
*NOTE:* for this example request body to work in the tests properly, you need to ``let`` a variable named *blog*.
*NOTE:* for this example request body to work in the tests properly, you need to ``let`` a variable named *blog*.
The variable with the matching name (blog in this case) is eval-ed and captured to be placed in the examples section.
This ```let``` value is used in the integration test to run the test AND captured and injected into the requestBody section.
This ```let``` value is used in the integration test to run the test AND captured and injected into the requestBody section.
##### rswag response examples #####
@ -837,7 +841,7 @@ You can specify custom headers for serving your generated Swagger JSON. For exam
```ruby
Rswag::Api.configure do |c|
...
c.swagger_headers = { 'Content-Type' => 'application/json; charset=UTF-8' }
end
```
@ -909,5 +913,5 @@ docker pull swaggerapi/swagger-editor
```
docker run -d -p 80:8080 swaggerapi/swagger-editor
```
This will run the swagger editor in the docker daemon and can be accessed
This will run the swagger editor in the docker daemon and can be accessed
at ```http://localhost```. From here, you can use the UI to load the generated swagger.json to validate the output.

View File

@ -19,7 +19,11 @@ RSpec.describe '<%= controller_path %>', type: :request do
<% end -%>
after do |example|
example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) }
example.metadata[:response][:content] = {
'application/json' => {
example: JSON.parse(response.body, symbolize_names: true)
}
}
end
run_test!
end

View File

@ -72,7 +72,10 @@ module Rswag
def examples(example = nil)
return super() if example.nil?
metadata[:response][:examples] = example
metadata[:response][:content] =
example.each_with_object({}) do |(mime, example_object), memo|
memo[mime] = { example: example_object }
end
end
def run_test!(&block)

View File

@ -118,7 +118,8 @@ module Rswag
def build_query_string_part(param, value)
name = param[:name]
return "#{name}=#{value}" unless param[:type]&.to_sym == :array
type = param[:type] || param.dig(:schema, :type)
return "#{name}=#{value}" unless type&.to_sym == :array
case param[:collectionFormat]
when :ssv

View File

@ -59,6 +59,7 @@ module Rswag
mime_list = value.dig(:consumes) || doc[:consumes]
if value && schema_param && mime_list
value[:requestBody] = { content: {} } unless value.dig(:requestBody, :content)
value[:requestBody][:required] = true if schema_param[:required]
mime_list.each do |mime|
value[:requestBody][:content][mime] = { schema: schema_param[:schema] }
end
@ -129,14 +130,14 @@ module Rswag
end
def upgrade_content!(mime_list, target_node)
target_node.merge!(content: {}) if target_node[:content].nil?
schema = target_node[:schema]
return if mime_list.empty? || schema.nil?
target_node[:content] ||= {}
target_node.merge!(content: {})
mime_list.each do |mime_type|
# TODO upgrade to have content-type specific schema
target_node[:content][mime_type] = {} if target_node[:content][mime_type].nil?
target_node[:content][mime_type][:schema] = schema
(target_node[:content][mime_type] ||= {}).merge!(schema: schema)
end
end

View File

@ -137,9 +137,10 @@ module Rswag
end
describe '#examples(example)' do
let(:mime) { 'application/json' }
let(:json_example) do
{
'application/json' => {
mime => {
foo: 'bar'
}
}
@ -151,7 +152,11 @@ module Rswag
end
it "adds to the 'response examples' metadata" do
expect(api_metadata[:response][:examples]).to eq(json_example)
expect(api_metadata[:response][:content]).to match(
mime => {
example: json_example[mime]
}
)
end
end
end

View File

@ -129,6 +129,56 @@ module Rswag
)
end
context 'with empty content' do
let(:swagger_doc) do
{
openapi: '3.0.1',
basePath: '/foo',
schemes: ['http', 'https'],
host: 'api.example.com',
components: {
securitySchemes: {
myClientCredentials: {
type: :oauth2,
flow: :application,
token_url: :somewhere
},
myAuthorizationCode: {
type: :oauth2,
flow: :accessCode,
token_url: :somewhere
},
myImplicit: {
type: :oauth2,
flow: :implicit,
token_url: :somewhere
}
}
}
}
end
it 'converts query and path params, type: to schema: { type: }' do
expect(swagger_doc.slice(:paths)).to match(
paths: {
'/blogs' => {
parameters: [{ schema: { type: :string } }],
post: {
parameters: [{ schema: { type: :string } }],
summary: 'Creates a blog',
responses: {
'201' => {
description: 'blog created',
headers: { schema: { type: :string } }
}
}
}
}
}
)
end
end
it 'converts basePath, schemas and host to urls' do
expect(swagger_doc.slice(:servers)).to match(
servers: {

View File

@ -0,0 +1,2 @@
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.

View File

@ -0,0 +1,4 @@
/*
Place all the styles related to the matching controller here.
They will automatically be included in application.css.
*/

View File

@ -0,0 +1,13 @@
class StubsController < ApplicationController
def index
render plain: 'OK'
end
def create
render plain: 'OK'
end
def show
render plain: 'OK'
end
end

View File

@ -0,0 +1,2 @@
module StubsHelper
end

View File

@ -9,6 +9,8 @@ TestApp::Application.routes.draw do
post 'auth-tests/api-key', to: 'auth_tests#api_key'
post 'auth-tests/basic-and-api-key', to: 'auth_tests#basic_and_api_key'
resources :stubs
mount Rswag::Api::Engine => 'api-docs'
mount Rswag::Ui::Engine => 'api-docs'
end

View File

@ -0,0 +1,349 @@
require 'swagger_helper'
require 'rswag/specs/swagger_formatter'
# This spec file validates OpenAPI output generated by spec metadata.
# Specifically here, we look at OpenApi 3 as documented at
# https://swagger.io/docs/specification/about/
RSpec.describe 'Generated OpenApi', type: :request, swagger_doc: 'v3/openapi.json' do
before do |example|
output = double('output').as_null_object
swagger_root = File.expand_path('tmp/swagger', __dir__)
config = double('config', swagger_root: swagger_root, get_swagger_doc: swagger_doc )
formatter = Rswag::Specs::SwaggerFormatter.new(output, config)
example_group = OpenStruct.new(group: OpenStruct.new(metadata: example.metadata))
formatter.example_group_finished(example_group)
end
# Framework definition, to be overridden for contexts
let(:swagger_doc) do
{ # That which would be defined in swagger_helper.rb
openapi: api_openapi,
info: {},
servers: api_servers,
produces: api_produces,
components: api_components
}
end
let(:api_openapi) { '3.0.3' }
let(:api_servers) {[{ url: "https://api.example.com/foo" }]}
let(:api_produces) { ['application/json'] }
let(:api_components) { {} }
describe 'Basic Structure'
describe 'API Server and Base Path' do
path '/stubs' do
get 'a summary' do
tags 'Server and Path'
response '200', 'OK' do
run_test!
it 'lists server' do
tree = swagger_doc.dig(:servers)
expect(tree).to eq([
{ url: "https://api.example.com/foo" }
])
end
context "multiple" do
let(:api_servers) {[
{ url: "https://api.example.com/foo" },
{ url: "http://api.example.com/foo" },
]}
it 'lists servers' do
tree = swagger_doc.dig(:servers)
expect(tree).to eq([
{ url: "https://api.example.com/foo" },
{ url: "http://api.example.com/foo" }
])
end
end
context "with variables" do
let(:api_servers) {[{
url: "https://{defaultHost}/foo",
variables: {
defaultHost: {
default: "api.example.com"
}
}
}]}
it 'lists server and variables' do
tree = swagger_doc.dig(:servers)
expect(tree).to eq([{
url: "https://{defaultHost}/foo",
variables: {
defaultHost: {
default: "api.example.com"
}
}
}])
end
end
# TODO: Enum variables, defaults, override at path/operation
end
end
end
end
describe 'Media Types' do
path '/stubs' do
get 'a summary' do
tags 'Media Types'
response '200', 'OK' do
run_test!
it 'declares output as application/json' do
pending "Not yet implemented?"
tree = swagger_doc.dig(:paths, "/stubs", :get, :responses, '200', :content)
expect(tree).to have_key('application/json')
end
end
end
end
end
describe 'Paths and Operations'
describe 'Parameter Serialization' do
describe 'Path Parameters' do
path '/stubs/{a_param}' do
get 'a summary' do
tags 'Parameter Serialization: Query String'
produces 'application/json'
parameter(
name: 'a_param',
in: :path,
)
let(:a_param) { "42" }
response '200', 'OK' do
run_test!
it 'declares parameter in path' do
tree = swagger_doc.dig(:paths, "/stubs/{a_param}", :get, :parameters)
expect(tree.first[:name]).to eq('a_param')
expect(tree.first[:in]).to eq(:path)
end
it 'declares path parameters as required' do
tree = swagger_doc.dig(:paths, "/stubs/{a_param}", :get, :parameters)
expect(tree.first[:required]).to eq(true)
end
end
end
end
end
describe 'Query Parameters' do
path '/stubs' do
get 'a summary' do
tags 'Parameter Serialization: Query String'
produces 'application/json'
parameter(
name: 'a_param',
in: :query,
)
let(:a_param) { "a foo" }
response '200', 'OK' do
run_test!
it 'declares parameter in query string' do
tree = swagger_doc.dig(:paths, "/stubs", :get, :parameters)
expect(tree.first[:name]).to eq('a_param')
expect(tree.first[:in]).to eq(:query)
end
end
# TODO: Serialization (form/spaceDelimited/pipeDelimited/deepObject)
end
end
end
# TODO: Header
# TODO: Cookie
# TODO: Default values
# TODO: Enum
# TODO: Constant
# TODO: Empty/Nullable
# TODO: Examples
# TODO: Deprecated
# TODO: Common Parameters
end
describe 'Request Body' do
path '/stubs' do
post 'body is required' do
tags 'Media Types'
consumes 'multipart/form-data'
parameter name: :file, :in => :formData, :type => :file, required: true
let(:file) { Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) }
response '200', 'OK' do
run_test!
it 'declares requestBody is required' do
pending "This output is massaged in SwaggerFormatter#stop, and isn't quite ready here to assert"
tree = swagger_doc.dig(:paths, "/stubs", :post, :requestBody)
expect(tree[:required]).to eq(true)
end
end
end
end
end
describe 'Responses'
describe 'Data Models (Schemas)'
describe 'Examples'
describe 'Authentication'
describe 'Links'
describe 'Callbacks'
describe 'Components Section'
describe 'Using $ref'
describe 'Grouping Operations with Tags'
# path '/blogs' do
# post 'Creates a blog' do
# tags 'Blogs'
# description 'Creates a new blog from provided data'
# operationId 'createBlog'
# consumes 'application/json'
# produces 'application/json'
# parameter name: :blog, in: :body, schema: { '$ref' => '#/definitions/blog' }
# let(:blog) { { title: 'foo', content: 'bar' } }
# response '201', 'blog created' do
# # schema '$ref' => '#/definitions/blog'
# run_test!
# end
# response '422', 'invalid request' do
# schema '$ref' => '#/definitions/errors_object'
# let(:blog) { { title: 'foo' } }
# run_test! do |response|
# expect(response.body).to include("can't be blank")
# end
# it 'outputs parameters' do
# pp swagger_doc
# params = swagger_doc.dig(:paths, "/blogs", :post, :parameters)
# expect(params[0][:name]).to eq(:blog)
# end
# end
# end
# get 'Searches blogs' do
# tags 'Blogs'
# description 'Searches blogs by keywords'
# operationId 'searchBlogs'
# produces 'application/json'
# parameter name: :keywords, in: :query, type: 'string'
# let(:keywords) { 'foo bar' }
# response '200', 'success' do
# schema type: 'array', items: { '$ref' => '#/definitions/blog' }
# end
# response '406', 'unsupported accept header' do
# let(:'Accept') { 'application/foo' }
# run_test!
# end
# end
# end
# path '/blogs/flexible' do
# post 'Creates a blog flexible body' do
# tags 'Blogs'
# description 'Creates a flexible blog from provided data'
# operationId 'createFlexibleBlog'
# consumes 'application/json'
# produces 'application/json'
# parameter name: :flexible_blog, in: :body, schema: {
# oneOf: [
# { '$ref' => '#/definitions/blog' },
# { '$ref' => '#/definitions/flexible_blog' }
# ]
# }
# let(:flexible_blog) { { blog: { headline: 'my headline', text: 'my text' } } }
# response '201', 'flexible blog created' do
# schema oneOf: [{ '$ref' => '#/definitions/blog' }, { '$ref' => '#/definitions/flexible_blog' }]
# run_test!
# end
# end
# end
# path '/blogs/{id}' do
# parameter name: :id, in: :path, type: :string
# let(:id) { blog.id }
# let(:blog) { Blog.create(title: 'foo', content: 'bar', thumbnail: 'thumbnail.png') }
# get 'Retrieves a blog' do
# tags 'Blogs'
# description 'Retrieves a specific blog by id'
# operationId 'getBlog'
# produces 'application/json'
# response '200', 'blog found' do
# header 'ETag', type: :string
# header 'Last-Modified', type: :string
# header 'Cache-Control', type: :string
# schema '$ref' => '#/definitions/blog'
# examples 'application/json' => {
# id: 1,
# title: 'Hello world!',
# content: 'Hello world and hello universe. Thank you all very much!!!',
# thumbnail: 'thumbnail.png'
# }
# let(:id) { blog.id }
# run_test!
# end
# response '404', 'blog not found' do
# let(:id) { 'invalid' }
# run_test!
# end
# end
# end
# path '/blogs/{id}/upload' do
# parameter name: :id, in: :path, type: :string
# let(:id) { blog.id }
# let(:blog) { Blog.create(title: 'foo', content: 'bar') }
# put 'Uploads a blog thumbnail' do
# tags 'Blogs'
# description 'Upload a thumbnail for specific blog by id'
# operationId 'uploadThumbnailBlog'
# consumes 'multipart/form-data'
# parameter name: :file, :in => :formData, :type => :file, required: true
# response '200', 'blog updated' do
# let(:file) { Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) }
# run_test!
# end
# end
# end
end

View File

@ -67,14 +67,83 @@ RSpec.configure do |config|
required: ['id', 'headline']
}
},
securityDefinitions: {
basic_auth: {
type: :basic
components: {
securitySchemes: {
basic_auth: {
type: :http,
scheme: :basic
},
api_key: {
type: :apiKey,
name: 'api_key',
in: :query
}
}
}
},
'v3/openapi.json' => {
openapi: '3.0.0',
info: {
title: 'API V1',
version: 'v1'
},
paths: {},
servers: [
{
url: 'https://{defaultHost}',
variables: {
defaultHost: {
default: 'www.example.com'
}
}
}
],
definitions: {
errors_object: {
type: 'object',
properties: {
errors: { '$ref' => '#/definitions/errors_map' }
}
},
api_key: {
type: :apiKey,
name: 'api_key',
in: :query
errors_map: {
type: 'object',
additionalProperties: {
type: 'array',
items: { type: 'string' }
}
},
blog: {
type: 'object',
properties: {
id: { type: 'integer' },
title: { type: 'string' },
content: { type: 'string', 'x-nullable': true },
thumbnail: { type: 'string', 'x-nullable': true}
},
required: [ 'id', 'title' ]
},
flexible_blog: {
type: 'object',
properties: {
id: { type: 'integer' },
headline: { type: 'string' },
text: { type: 'string', nullable: true },
thumbnail: { type: 'string', nullable: true }
},
required: ['id', 'headline']
}
},
components: {
securitySchemes: {
basic_auth: {
type: :http,
scheme: :basic
},
api_key: {
type: :apiKey,
name: 'api_key',
in: :query
}
}
}
}

View File

@ -21,14 +21,10 @@
],
"responses": {
"204": {
"description": "Valid credentials",
"content": {
}
"description": "Valid credentials"
},
"401": {
"description": "Invalid credentials",
"content": {
}
"description": "Invalid credentials"
}
}
}
@ -49,14 +45,10 @@
],
"responses": {
"204": {
"description": "Valid credentials",
"content": {
}
"description": "Valid credentials"
},
"401": {
"description": "Invalid credentials",
"content": {
}
"description": "Invalid credentials"
}
}
}
@ -80,14 +72,10 @@
],
"responses": {
"204": {
"description": "Valid credentials",
"content": {
}
"description": "Valid credentials"
},
"401": {
"description": "Invalid credentials",
"content": {
}
"description": "Invalid credentials"
}
}
}
@ -105,9 +93,7 @@
],
"responses": {
"201": {
"description": "blog created",
"content": {
}
"description": "blog created"
},
"422": {
"description": "invalid request",
@ -148,9 +134,7 @@
],
"responses": {
"406": {
"description": "unsupported accept header",
"content": {
}
"description": "unsupported accept header"
}
}
}
@ -235,14 +219,6 @@
"type": "string"
}
},
"examples": {
"application/json": {
"id": 1,
"title": "Hello world!",
"content": "Hello world and hello universe. Thank you all very much!!!",
"thumbnail": "thumbnail.png"
}
},
"content": {
"application/json": {
"schema": {
@ -252,9 +228,7 @@
}
},
"404": {
"description": "blog not found",
"content": {
}
"description": "blog not found"
}
}
}
@ -282,9 +256,7 @@
],
"responses": {
"200": {
"description": "blog updated",
"content": {
}
"description": "blog updated"
}
},
"requestBody": {
@ -294,7 +266,8 @@
"type": "file"
}
}
}
},
"required": true
}
}
}
@ -377,7 +350,8 @@
"components": {
"securitySchemes": {
"basic_auth": {
"type": "basic"
"type": "http",
"scheme": "basic"
},
"api_key": {
"type": "apiKey",

View File

@ -0,0 +1,160 @@
{
"openapi": "3.0.0",
"info": {
"title": "API V1",
"version": "v1"
},
"paths": {
"/stubs": {
"get": {
"summary": "a summary",
"tags": [
"Parameter Serialization: Query String"
],
"responses": {
"200": {
"description": "OK"
}
},
"parameters": [
{
"name": "a_param",
"in": "query"
}
]
},
"post": {
"summary": "body is required",
"tags": [
"Media Types"
],
"parameters": [
],
"responses": {
"200": {
"description": "OK"
}
},
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "file"
}
}
},
"required": true
}
}
},
"/stubs/{a_param}": {
"get": {
"summary": "a summary",
"tags": [
"Parameter Serialization: Query String"
],
"parameters": [
{
"name": "a_param",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
}
},
"servers": [
{
"url": "https://{defaultHost}",
"variables": {
"defaultHost": {
"default": "www.example.com"
}
}
}
],
"definitions": {
"errors_object": {
"type": "object",
"properties": {
"errors": {
"$ref": "#/definitions/errors_map"
}
}
},
"errors_map": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"blog": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"title": {
"type": "string"
},
"content": {
"type": "string",
"x-nullable": true
},
"thumbnail": {
"type": "string",
"x-nullable": true
}
},
"required": [
"id",
"title"
]
},
"flexible_blog": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"headline": {
"type": "string"
},
"text": {
"type": "string",
"nullable": true
},
"thumbnail": {
"type": "string",
"nullable": true
}
},
"required": [
"id",
"headline"
]
}
},
"components": {
"securitySchemes": {
"basic_auth": {
"type": "http",
"scheme": "basic"
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "query"
}
}
}
}