├── .rspec ├── spec ├── resources │ ├── nested │ │ ├── stats.yaml │ │ ├── person.yaml │ │ └── mixed_person.yaml │ ├── file_reference_example.yaml │ ├── mixed_reference_example.yaml │ ├── pointer_example.yaml │ ├── pointer_example.json │ ├── invalid_spec.yaml │ ├── valid_with_cycle_spec.yaml │ └── valid_spec.yaml ├── spec_helper.rb ├── open_api_parser_spec.rb └── open_api_parser │ ├── document_spec.rb │ ├── pointer_spec.rb │ ├── specification │ ├── root_spec.rb │ └── endpoint_spec.rb │ └── specification_spec.rb ├── Gemfile ├── lib ├── open_api_parser │ ├── version.rb │ ├── file_cache.rb │ ├── specification.rb │ ├── pointer.rb │ ├── specification │ │ ├── root.rb │ │ └── endpoint.rb │ └── document.rb └── open_api_parser.rb ├── bin ├── setup └── console ├── .travis.yml ├── Rakefile ├── .gitignore ├── CHANGELOG.md ├── open_api_parser.gemspec ├── LICENSE.txt ├── README.md └── resources └── swagger_meta_schema.json /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /spec/resources/nested/stats.yaml: -------------------------------------------------------------------------------- 1 | age: 34 2 | -------------------------------------------------------------------------------- /spec/resources/nested/person.yaml: -------------------------------------------------------------------------------- 1 | name: "Drew" 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /spec/resources/file_reference_example.yaml: -------------------------------------------------------------------------------- 1 | person: 2 | $ref: "file:nested/person.yaml" 3 | -------------------------------------------------------------------------------- /lib/open_api_parser/version.rb: -------------------------------------------------------------------------------- 1 | module OpenApiParser 2 | VERSION = "1.4.0-dev".freeze 3 | end 4 | -------------------------------------------------------------------------------- /spec/resources/mixed_reference_example.yaml: -------------------------------------------------------------------------------- 1 | person: 2 | $ref: "file:nested/mixed_person.yaml" 3 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.2.9 5 | - 2.3.6 6 | - 2.4.3 7 | - 2.5.0 8 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) 2 | require "open_api_parser" 3 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "open_api_parser" 5 | 6 | require "irb" 7 | IRB.start 8 | -------------------------------------------------------------------------------- /spec/resources/pointer_example.yaml: -------------------------------------------------------------------------------- 1 | definitions: 2 | person: 3 | name: "Drew" 4 | 5 | person: 6 | $ref: "#/definitions/person" 7 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /spec/resources/nested/mixed_person.yaml: -------------------------------------------------------------------------------- 1 | definitions: 2 | greeting: 3 | hi: "Drew" 4 | 5 | greeting: 6 | $ref: "#/definitions/greeting" 7 | stats: 8 | $ref: "file:stats.yaml" 9 | -------------------------------------------------------------------------------- /spec/open_api_parser_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe OpenApiParser do 4 | it "has a version number" do 5 | expect(OpenApiParser::VERSION).not_to be nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/resources/pointer_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": { 3 | "person": { 4 | "name": "Drew" 5 | } 6 | }, 7 | "person": { 8 | "$ref": "#/definitions/person" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | /tags 11 | /.ruby-version 12 | /vendor/bundle 13 | /open_api_parser*.gem 14 | /bin 15 | -------------------------------------------------------------------------------- /lib/open_api_parser/file_cache.rb: -------------------------------------------------------------------------------- 1 | module OpenApiParser 2 | class FileCache 3 | def initialize 4 | @cache = {} 5 | end 6 | 7 | def get(key, &block) 8 | return @cache[key] if @cache.has_key?(key) 9 | 10 | block.call.tap do |result| 11 | @cache[key] = result 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/open_api_parser.rb: -------------------------------------------------------------------------------- 1 | require "json" 2 | require "uri" 3 | require "yaml" 4 | 5 | require "addressable/uri" 6 | require "json-schema" 7 | 8 | require "open_api_parser/document" 9 | require "open_api_parser/file_cache" 10 | require "open_api_parser/pointer" 11 | require "open_api_parser/specification" 12 | require "open_api_parser/specification/endpoint" 13 | require "open_api_parser/specification/root" 14 | require "open_api_parser/version" 15 | -------------------------------------------------------------------------------- /lib/open_api_parser/specification.rb: -------------------------------------------------------------------------------- 1 | module OpenApiParser 2 | module Specification 3 | META_SCHEMA_PATH = File.expand_path("../../../resources/swagger_meta_schema.json", __FILE__) 4 | 5 | def self.resolve(path, validate_meta_schema: true) 6 | raw_specification = Document.resolve(path) 7 | 8 | if validate_meta_schema 9 | meta_schema = JSON.parse(File.read(META_SCHEMA_PATH)) 10 | JSON::Validator.validate!(meta_schema, raw_specification) 11 | end 12 | 13 | OpenApiParser::Specification::Root.new(raw_specification) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.4.0-dev 4 | 5 | ## 1.3.0 6 | 7 | * Expose `path` and `method` on endpoints 8 | * Fix issue where references that matched a substring of the path were not being resolved 9 | (e.g. "/definitions/PersonInfo" in "/definitions/PersonInfoResponse/schema") 10 | 11 | ## 1.2.3 12 | 13 | * Handle circular references during resolution 14 | 15 | ## 1.2.2 16 | 17 | * Use `json-schema` to validate meta schema 18 | 19 | ## 1.2.1 20 | 21 | * Use `Addressable::URI.unencode` instead of obsoleted `URI.decode` 22 | 23 | ## 1.2.0 24 | 25 | * Make all response headers optional 26 | 27 | ## 1.1.2 28 | 29 | * Match path to correct endpoint when many similar paths exist 30 | 31 | ## 1.1.1 32 | 33 | * Handle invalid URLs in `endpoint` 34 | 35 | ## 1.1.0 36 | 37 | * Bump JsonSchema version 38 | 39 | ## 1.0.1 40 | 41 | * Correctly handle known responses with empty schemas 42 | 43 | ## 1.0.0 44 | 45 | * Initial release 46 | -------------------------------------------------------------------------------- /spec/resources/invalid_spec.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | 3 | info: 4 | title: Simple API overview 5 | version: "1" 6 | 7 | produces: 8 | - application/json 9 | consumes: 10 | - application/json 11 | 12 | definitions: 13 | validResponse: 14 | type: object 15 | required: 16 | - status 17 | properties: 18 | status: 19 | type: string 20 | enum: 21 | - ok 22 | 23 | errorResponse: 24 | type: object 25 | required: 26 | - status 27 | properties: 28 | status: 29 | type: string 30 | enum: 31 | - bad 32 | 33 | paths: 34 | /animals/{id}: 35 | fake-http-method: 36 | operationId: fakeHttpMethod 37 | parameters: 38 | - name: id 39 | in: path 40 | required: true 41 | type: integer 42 | responses: 43 | 200: 44 | description: Valid create 45 | schema: 46 | $ref: "#/definitions/validResponse" 47 | default: 48 | description: Error response 49 | schema: 50 | $ref: "#/definitions/errorResponse" 51 | -------------------------------------------------------------------------------- /lib/open_api_parser/pointer.rb: -------------------------------------------------------------------------------- 1 | module OpenApiParser 2 | class Pointer 3 | def initialize(raw_pointer) 4 | @raw_pointer = raw_pointer 5 | end 6 | 7 | def resolve(document) 8 | return document if escaped_pointer == "" 9 | 10 | tokens.reduce(document) do |nested_doc, token| 11 | nested_doc.fetch(token) 12 | end 13 | end 14 | 15 | def exists_in_path?(path) 16 | path.include?("#{escaped_pointer}/") 17 | end 18 | 19 | def escaped_pointer 20 | if @raw_pointer.start_with?("#") 21 | Addressable::URI.unencode(@raw_pointer[1..-1]) 22 | else 23 | @raw_pointer 24 | end 25 | end 26 | 27 | private 28 | 29 | def parse_token(token) 30 | if token =~ /\A\d+\z/ 31 | token.to_i 32 | else 33 | token.gsub("~0", "~").gsub("~1", "/") 34 | end 35 | end 36 | 37 | def tokens 38 | tokens = escaped_pointer[1..-1].split("/") 39 | tokens << "" if @raw_pointer.end_with?("/") 40 | tokens.map do |token| 41 | parse_token(token) 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /open_api_parser.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'open_api_parser/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "open_api_parser" 8 | spec.version = OpenApiParser::VERSION 9 | spec.authors = ["Braintree"] 10 | spec.email = ["code@getbraintree.com"] 11 | 12 | spec.summary = %q{A parser for Open API specifications} 13 | spec.description = %q{A parser for Open API specifications} 14 | spec.homepage = "https://github.com/braintree/open_api_parser" 15 | spec.license = "MIT" 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 18 | f.match(%r{^(test|spec|features)/}) 19 | end 20 | 21 | spec.require_paths = ["lib"] 22 | 23 | spec.required_ruby_version = "~> 2.0" 24 | 25 | spec.add_dependency "addressable", "~> 2.3" 26 | spec.add_dependency "json-schema", "~> 2.8" 27 | 28 | spec.add_development_dependency "bundler", "~> 1.13" 29 | spec.add_development_dependency "rake", "~> 10.0" 30 | spec.add_development_dependency "rspec", "~> 3.0" 31 | end 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Braintree, a division of PayPal, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/open_api_parser/specification/root.rb: -------------------------------------------------------------------------------- 1 | module OpenApiParser 2 | module Specification 3 | class Root 4 | attr_reader :raw 5 | 6 | def initialize(raw) 7 | @raw = raw 8 | end 9 | 10 | def endpoint(path, request_method) 11 | uri = URI.parse(path) 12 | requested_path = uri.path.gsub(/\..+\z/, "") 13 | 14 | matching_path_details = @raw["paths"].detect do |path_name, path| 15 | requested_path =~ to_pattern(path_name) && 16 | path.keys.any? { |method| matching_method?(method, request_method) } 17 | end 18 | return nil if matching_path_details.nil? 19 | 20 | matching_name, matching_path = matching_path_details 21 | 22 | method_details = matching_path.detect do |method, schema| 23 | matching_method?(method, request_method) 24 | end 25 | 26 | Endpoint.new(matching_name, method_details.first, method_details.last) 27 | rescue URI::InvalidURIError 28 | nil 29 | end 30 | 31 | def to_json 32 | JSON.generate(@raw) 33 | end 34 | 35 | private 36 | 37 | def matching_method?(method, request_method) 38 | method.to_s == request_method.downcase 39 | end 40 | 41 | def to_pattern(path_name) 42 | Regexp.new("\\A" + path_name.gsub(/\{[^}]+\}/, "[^/]+") + "\\z") 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/open_api_parser/document_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe OpenApiParser::Document do 4 | describe "self.resolve" do 5 | context "yaml" do 6 | it "resolves JSON pointers" do 7 | path = File.expand_path("../../resources/pointer_example.yaml", __FILE__) 8 | json = OpenApiParser::Document.resolve(path) 9 | 10 | expect(json["person"]).to eq({ 11 | "name" => "Drew" 12 | }) 13 | end 14 | 15 | it "resolves JSON file references" do 16 | path = File.expand_path("../../resources/file_reference_example.yaml", __FILE__) 17 | json = OpenApiParser::Document.resolve(path) 18 | 19 | expect(json["person"]).to eq({ 20 | "name" => "Drew" 21 | }) 22 | end 23 | 24 | it "resolves a mix of pointers and file references" do 25 | path = File.expand_path("../../resources/mixed_reference_example.yaml", __FILE__) 26 | json = OpenApiParser::Document.resolve(path) 27 | 28 | expect(json["person"]["greeting"]).to eq({ 29 | "hi" => "Drew" 30 | }) 31 | 32 | expect(json["person"]["stats"]).to eq({ 33 | "age" => 34 34 | }) 35 | end 36 | end 37 | 38 | context "json" do 39 | it "resolves JSON pointers" do 40 | path = File.expand_path("../../resources/pointer_example.json", __FILE__) 41 | json = OpenApiParser::Document.resolve(path) 42 | 43 | expect(json["person"]).to eq({ 44 | "name" => "Drew" 45 | }) 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/open_api_parser/pointer_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe OpenApiParser::Pointer do 4 | DOCUMENT = { 5 | "foo" => ["bar", "baz"], 6 | "" => 0, 7 | "a/b" => 1, 8 | "c%d" => 2, 9 | "e^f" => 3, 10 | "g|h" => 4, 11 | "i\\j" => 5, 12 | "k\"l" => 6, 13 | " " => 7, 14 | "m~n" => 8 15 | } 16 | 17 | describe "resolve" do 18 | it "works with RFC examples" do 19 | resolutions = { 20 | "" => DOCUMENT, 21 | "/foo" => ["bar", "baz"], 22 | "/foo/0" => "bar", 23 | "/" => 0, 24 | "/a~1b" => 1, 25 | "/c%d" => 2, 26 | "/e^f" => 3, 27 | "/g|h" => 4, 28 | "/i\\j" => 5, 29 | "/k\"l" => 6, 30 | "/ " => 7, 31 | "/m~0n" => 8, 32 | } 33 | 34 | resolutions.each do |pointer, expected| 35 | expect(OpenApiParser::Pointer.new(pointer).resolve(DOCUMENT)).to eq(expected) 36 | end 37 | end 38 | 39 | it "works with escaped RFC examples" do 40 | resolutions = { 41 | "#" => DOCUMENT, 42 | "#/foo" => ["bar", "baz"], 43 | "#/foo/0" => "bar", 44 | "#/" => 0, 45 | "#/a~1b" => 1, 46 | "#/c%25d" => 2, 47 | "#/e%5Ef" => 3, 48 | "#/g%7Ch" => 4, 49 | "#/i%5Cj" => 5, 50 | "#/k%22l" => 6, 51 | "#/%20" => 7, 52 | "#/m~0n" => 8, 53 | } 54 | 55 | resolutions.each do |pointer, expected| 56 | expect(OpenApiParser::Pointer.new(pointer).resolve(DOCUMENT)).to eq(expected) 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/open_api_parser/document.rb: -------------------------------------------------------------------------------- 1 | module OpenApiParser 2 | class Document 3 | def self.resolve(path, file_cache = OpenApiParser::FileCache.new) 4 | file_cache.get(path) do 5 | content = YAML.load_file(path) 6 | Document.new(path, content, file_cache).resolve 7 | end 8 | end 9 | 10 | def initialize(path, content, file_cache) 11 | @path = path 12 | @content = content 13 | @file_cache = file_cache 14 | end 15 | 16 | def resolve 17 | deeply_expand_refs(@content, nil) 18 | end 19 | 20 | private 21 | 22 | def deeply_expand_refs(fragment, cur_path) 23 | fragment, cur_path = expand_refs(fragment, cur_path) 24 | 25 | if fragment.is_a?(Hash) 26 | fragment.reduce({}) do |hash, (k, v)| 27 | hash.merge(k => deeply_expand_refs(v, "#{cur_path}/#{k}")) 28 | end 29 | elsif fragment.is_a?(Array) 30 | fragment.map { |e| deeply_expand_refs(e, cur_path) } 31 | else 32 | fragment 33 | end 34 | end 35 | 36 | def expand_refs(fragment, cur_path) 37 | if fragment.is_a?(Hash) && fragment.key?("$ref") 38 | ref = fragment["$ref"] 39 | 40 | if ref.start_with?("file:") 41 | expand_file(ref) 42 | else 43 | expand_pointer(ref, cur_path) 44 | end 45 | else 46 | [fragment, cur_path] 47 | end 48 | end 49 | 50 | def expand_file(ref) 51 | relative_path = ref.split(":").last 52 | absolute_path = File.expand_path(File.join("..", relative_path), @path) 53 | 54 | Document.resolve(absolute_path, @file_cache) 55 | end 56 | 57 | def expand_pointer(ref, cur_path) 58 | pointer = OpenApiParser::Pointer.new(ref) 59 | 60 | if pointer.exists_in_path?(cur_path) 61 | { "$ref" => ref } 62 | else 63 | fragment = pointer.resolve(@content) 64 | expand_refs(fragment, cur_path + pointer.escaped_pointer) 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/open_api_parser/specification/root_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe OpenApiParser::Specification::Root do 4 | def root 5 | @root ||= begin 6 | path = File.expand_path("../../../resources/valid_spec.yaml", __FILE__) 7 | OpenApiParser::Specification.resolve(path) 8 | end 9 | end 10 | 11 | describe "endpoint" do 12 | it "returns an endpoint for a given path and method" do 13 | endpoint = root.endpoint("/animals", "post") 14 | 15 | expect(endpoint.raw.fetch("operationId")).to eq("createAnimal") 16 | end 17 | 18 | it "normalized the http method" do 19 | endpoint = root.endpoint("/animals", "POST") 20 | 21 | expect(endpoint.raw.fetch("operationId")).to eq("createAnimal") 22 | end 23 | 24 | it "handles path parameters" do 25 | endpoint = root.endpoint("/animals/123", "get") 26 | 27 | expect(endpoint.raw.fetch("operationId")).to eq("getAnimal") 28 | end 29 | 30 | it "handles path parameters and query parameters" do 31 | endpoint = root.endpoint("/animals/123?foo=bar", "get") 32 | 33 | expect(endpoint.raw.fetch("operationId")).to eq("getAnimal") 34 | end 35 | 36 | it "handles invalid URLs" do 37 | endpoint = root.endpoint("&&^^..%", "get") 38 | 39 | expect(endpoint).to be_nil 40 | end 41 | 42 | it "matches the expected path/operation when paths have significant components in common" do 43 | endpoint = root.endpoint("/animals/search", "post") 44 | 45 | expect(endpoint.raw.fetch("operationId")).to eq("searchAnimals") 46 | end 47 | 48 | it "handles a missing method on a valid URL" do 49 | endpoint = root.endpoint("/headers", "head") 50 | expect(endpoint).to be_nil 51 | end 52 | end 53 | 54 | describe "raw" do 55 | it "exposes the raw schema" do 56 | expect(root.raw.fetch("swagger")).to eq("2.0") 57 | end 58 | end 59 | 60 | describe "to_json" do 61 | it "returns a json representation of the raw schema" do 62 | json = root.to_json 63 | decoded = JSON.parse(json) 64 | 65 | expect(decoded.fetch("swagger")).to eq("2.0") 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/resources/valid_with_cycle_spec.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | 3 | info: 4 | title: Simple API overview 5 | version: "1" 6 | 7 | produces: 8 | - application/json 9 | consumes: 10 | - application/json 11 | 12 | definitions: 13 | createAnimal: 14 | type: object 15 | required: 16 | - name 17 | - legs 18 | properties: 19 | name: 20 | type: string 21 | legs: 22 | type: integer 23 | 24 | validResponse: 25 | type: object 26 | required: 27 | - status 28 | properties: 29 | status: 30 | type: string 31 | enum: 32 | - ok 33 | 34 | errorResponse: 35 | type: object 36 | required: 37 | - status 38 | properties: 39 | status: 40 | type: string 41 | enum: 42 | - bad 43 | 44 | animalHierarchy: 45 | type: object 46 | properties: 47 | descendants: 48 | type: array 49 | items: 50 | $ref: '#/definitions/animalHierarchyDescendants' 51 | name: 52 | type: string 53 | 54 | animalHierarchyDescendants: 55 | type: object 56 | properties: 57 | descendants: 58 | type: array 59 | items: 60 | $ref: '#/definitions/animalHierarchyDescendants' 61 | name: 62 | type: string 63 | 64 | paths: 65 | /animals: 66 | post: 67 | operationId: createAnimal 68 | parameters: 69 | - name: body 70 | in: body 71 | required: true 72 | schema: 73 | $ref: "#/definitions/createAnimal" 74 | responses: 75 | 201: 76 | description: Valid create 77 | schema: 78 | $ref: "#/definitions/validResponse" 79 | default: 80 | description: Error response 81 | schema: 82 | $ref: "#/definitions/errorResponse" 83 | 84 | /animals/{id}: 85 | get: 86 | operationId: getAnimal 87 | parameters: 88 | - name: id 89 | in: path 90 | required: true 91 | type: integer 92 | responses: 93 | 200: 94 | description: Valid get animal 95 | schema: 96 | $ref: "#/definitions/validResponse" 97 | default: 98 | description: Error response 99 | schema: 100 | $ref: "#/definitions/errorResponse" 101 | 102 | /animals/{id}/hierarchy: 103 | get: 104 | operationId: getAnimalHierarchy 105 | description: View the ancestors and descendants hierarchy for a given animal 106 | parameters: 107 | - name: id 108 | in: path 109 | required: true 110 | type: integer 111 | responses: 112 | 200: 113 | description: Valid get animal hierarchy 114 | schema: 115 | type: array 116 | items: 117 | $ref: '#/definitions/animalHierarchy' 118 | default: 119 | description: Error response 120 | schema: 121 | $ref: "#/definitions/errorResponse" 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenApiParser 2 | 3 | [![Build Status](https://travis-ci.org/braintree/open_api_parser.svg?branch=master)](https://travis-ci.org/braintree/open_api_parser) 4 | 5 | A gem for parsing [Open API](https://openapis.org/) specifications. 6 | 7 | ## Usage 8 | 9 | First, resolve a specification given an absolute file path. 10 | 11 | ```ruby 12 | specification = OpenApiParser::Specification.resolve("/path/to/my/specification.yaml") 13 | ``` 14 | 15 | When you `resolve` your file, all references will be expanded and inlined. The fully resolved specification will be validated against the Open API V2 meta schema. If you'd like to prevent meta schema validation, you can provide an option to do so. 16 | 17 | ```ruby 18 | specification = OpenApiParser::Specification.resolve("/path/to/my/specification.yaml", validate_meta_schema: false) 19 | ``` 20 | 21 | If you'd rather instantiate a specification with a _fully resolved_ ruby hash, you can do so by directly instantiating an `OpenApiParser::Specification::Root`. 22 | 23 | ``` 24 | OpenApiParser::Specification::Root.new({...}) 25 | ``` 26 | 27 | ### Reference resolution 28 | 29 | `OpenApiParser::Specification.resolve` will fully resolve the provided Open API specification by inlining all [JSON References](https://tools.ietf.org/id/draft-pbryan-zyp-json-ref-03.html). Two types of references are allowed: 30 | 31 | * [JSON Pointers](https://tools.ietf.org/html/rfc6901) 32 | * JSON References to files 33 | 34 | Pointers are resolved from the root of the document in which they appear. File references are resolved relative to the file in which they appear. Here's an example of each: 35 | 36 | ```yaml 37 | definitions: 38 | person: 39 | type: object 40 | properties: 41 | name: 42 | type: string 43 | age: 44 | type: integer 45 | 46 | info: 47 | person: 48 | $ref: "/definitions/person" 49 | other: 50 | $ref: "file:another/file.yaml" 51 | ``` 52 | 53 | For more information, see the specs. 54 | 55 | ### Endpoints 56 | 57 | With a resolved schema, you can access the information for an endpoint given a path and an HTTP verb. 58 | 59 | ```ruby 60 | endpoint = specification.endpoint("/animals", "post") 61 | ``` 62 | 63 | With an endpoint, you can get access to JSON schema representations of the body, headers, path, query params and response body and headers defined in your Open API specification. You can use these to validate input using any existing JSON Schema library. We recommend [JsonSchema](https://github.com/brandur/json_schema). 64 | 65 | ```ruby 66 | endpoint.body_schema 67 | # => {...} 68 | 69 | endpoint.header_schema 70 | # => {...} 71 | 72 | endpoint.path_schema 73 | # => {...} 74 | 75 | endpoint.query_schema 76 | # => {...} 77 | 78 | endpoint.response_body_schema(201) 79 | # => {...} 80 | 81 | endpoint.response_header_schema(201) 82 | # => {...} 83 | ``` 84 | 85 | You can also use the endpoint to transform user input into json that can be validated against the schemas generated in the previous examples. 86 | 87 | ```ruby 88 | endpoint.header_json(request_headers_as_hash) 89 | # => {...} 90 | 91 | endpoint.path_json("/animals/123?query=param") 92 | # => {...} 93 | 94 | endpoint.query_json(request_query_params_as_hash) 95 | # => {...} 96 | ``` 97 | 98 | For more information, see the specs. 99 | 100 | ## Installation 101 | 102 | Add this line to your application's Gemfile: 103 | 104 | ```ruby 105 | gem 'open_api_parser' 106 | ``` 107 | 108 | And then execute: 109 | 110 | ```bash 111 | $ bundle 112 | ``` 113 | 114 | Or install it yourself as: 115 | 116 | ```bash 117 | $ gem install open_api_parser 118 | ``` 119 | -------------------------------------------------------------------------------- /spec/open_api_parser/specification_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe OpenApiParser::Specification do 4 | describe "self.resolve" do 5 | context "valid specification" do 6 | let(:path) { File.expand_path("../../resources/valid_spec.yaml", __FILE__) } 7 | let(:specification) { OpenApiParser::Specification.resolve(path) } 8 | 9 | it "resolves successfully" do 10 | expect(specification.raw.fetch("swagger")).to eq("2.0") 11 | end 12 | 13 | it "properly resolves matching substring references" do 14 | expanded_info_response = { 15 | "type" => "object", 16 | "properties" => { 17 | "info" => { 18 | "type" => "object", 19 | "properties" => { 20 | "name" => { 21 | "type" => "string" 22 | } 23 | } 24 | } 25 | } 26 | } 27 | 28 | expect(specification.raw.fetch("definitions").fetch("personInfoResponse")).to eq(expanded_info_response) 29 | end 30 | end 31 | 32 | context "valid specification containing a circular reference" do 33 | it "resolves successfully and stops expanding references if they are circular" do 34 | expanded_descendents = { 35 | "type" => "object", 36 | "properties" => { 37 | "name" => { 38 | "type" => "string" 39 | }, 40 | "descendants" => { 41 | "type" => "array", 42 | "items" => { 43 | "$ref" => "#/definitions/animalHierarchyDescendants" 44 | } 45 | } 46 | } 47 | } 48 | 49 | expanded_hierarchy = { 50 | "type" => "object", 51 | "properties" => { 52 | "name" => { 53 | "type" => "string" 54 | }, 55 | "descendants" => { 56 | "type" => "array", 57 | "items" => expanded_descendents 58 | } 59 | } 60 | } 61 | 62 | path = File.expand_path("../../resources/valid_with_cycle_spec.yaml", __FILE__) 63 | specification = OpenApiParser::Specification.resolve(path) 64 | 65 | expect(specification.raw.fetch("swagger")).to eq("2.0") 66 | expect(specification.raw.fetch("definitions").fetch("animalHierarchyDescendants")).to eq(expanded_descendents) 67 | expect(specification.raw.fetch("definitions").fetch("animalHierarchy")).to eq(expanded_hierarchy) 68 | end 69 | end 70 | 71 | context "invalid specification" do 72 | it "fails to resolve if required properties are not set" do 73 | expect do 74 | path = File.expand_path("../../resources/pointer_example.yaml", __FILE__) 75 | OpenApiParser::Specification.resolve(path) 76 | end.to raise_error( 77 | JSON::Schema::ValidationError, 78 | /did not contain a required property of \'swagger\'/ 79 | ) 80 | end 81 | 82 | it "fails to resolve if nested validation rules are not met" do 83 | expect do 84 | path = File.expand_path("../../resources/invalid_spec.yaml", __FILE__) 85 | OpenApiParser::Specification.resolve(path) 86 | end.to raise_error( 87 | JSON::Schema::ValidationError, 88 | /contains additional properties \[\"fake-http-method\"\] outside of the schema/ 89 | ) 90 | end 91 | 92 | it "allows skipping meta schema validation" do 93 | expect do 94 | path = File.expand_path("../../resources/invalid_spec.yaml", __FILE__) 95 | OpenApiParser::Specification.resolve(path, validate_meta_schema: false) 96 | end.to_not raise_error 97 | end 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /spec/resources/valid_spec.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | 3 | info: 4 | title: Simple API overview 5 | version: "1" 6 | 7 | produces: 8 | - application/json 9 | consumes: 10 | - application/json 11 | 12 | definitions: 13 | createAnimal: 14 | type: object 15 | required: 16 | - name 17 | - legs 18 | properties: 19 | name: 20 | type: string 21 | legs: 22 | type: integer 23 | 24 | validResponse: 25 | type: object 26 | required: 27 | - status 28 | properties: 29 | status: 30 | type: string 31 | enum: 32 | - ok 33 | 34 | createSearch: 35 | type: object 36 | required: 37 | - name 38 | properties: 39 | name: 40 | type: string 41 | 42 | personInfo: 43 | type: object 44 | properties: 45 | name: 46 | type: string 47 | 48 | personInfoResponse: 49 | type: object 50 | properties: 51 | info: 52 | $ref: '#/definitions/personInfo' 53 | 54 | searchResponse: 55 | type: object 56 | required: 57 | - name 58 | - legs 59 | properties: 60 | name: 61 | type: string 62 | legs: 63 | type: integer 64 | 65 | errorResponse: 66 | type: object 67 | required: 68 | - status 69 | properties: 70 | status: 71 | type: string 72 | enum: 73 | - bad 74 | 75 | paths: 76 | /animals: 77 | post: 78 | operationId: createAnimal 79 | parameters: 80 | - name: body 81 | in: body 82 | required: true 83 | schema: 84 | $ref: "#/definitions/createAnimal" 85 | responses: 86 | 201: 87 | description: Valid create 88 | schema: 89 | $ref: "#/definitions/validResponse" 90 | default: 91 | description: Error response 92 | schema: 93 | $ref: "#/definitions/errorResponse" 94 | 95 | /animals/{id}: 96 | get: 97 | operationId: getAnimal 98 | parameters: 99 | - name: id 100 | in: path 101 | required: true 102 | type: integer 103 | - name: size 104 | in: query 105 | required: false 106 | type: string 107 | enum: 108 | - small 109 | - large 110 | - name: age 111 | in: query 112 | required: false 113 | type: integer 114 | - name: tag 115 | in: query 116 | required: false 117 | type: string 118 | - name: User-Id 119 | in: header 120 | required: false 121 | type: integer 122 | responses: 123 | 200: 124 | description: Valid get animal 125 | schema: 126 | $ref: "#/definitions/validResponse" 127 | default: 128 | description: Error response 129 | schema: 130 | $ref: "#/definitions/errorResponse" 131 | delete: 132 | operationId: deleteAnimal 133 | parameters: 134 | - name: id 135 | in: path 136 | required: true 137 | type: integer 138 | responses: 139 | 204: 140 | description: Valid delete 141 | default: 142 | description: Error response 143 | schema: 144 | $ref: "#/definitions/errorResponse" 145 | 146 | /animals/search: 147 | post: 148 | operationId: searchAnimals 149 | parameters: 150 | - name: body 151 | in: body 152 | required: true 153 | schema: 154 | $ref: "#/definitions/createSearch" 155 | responses: 156 | 200: 157 | description: Search results 158 | schema: 159 | $ref: "#/definitions/searchResponse" 160 | default: 161 | description: Error response 162 | schema: 163 | $ref: "#/definitions/errorResponse" 164 | 165 | /people/{id}: 166 | get: 167 | operationId: getPerson 168 | parameters: 169 | - name: id 170 | in: path 171 | required: true 172 | type: string 173 | responses: 174 | 200: 175 | description: Valid get person 176 | schema: 177 | $ref: "#/definitions/personInfoResponse" 178 | default: 179 | description: Error response 180 | schema: 181 | $ref: "#/definitions/errorResponse" 182 | 183 | /headers: 184 | get: 185 | operationId: getHeaders 186 | responses: 187 | 200: 188 | description: Valid get headers 189 | schema: 190 | $ref: "#/definitions/validResponse" 191 | headers: 192 | X-My-Header: 193 | type: string 194 | enum: 195 | - "my value" 196 | -------------------------------------------------------------------------------- /lib/open_api_parser/specification/endpoint.rb: -------------------------------------------------------------------------------- 1 | module OpenApiParser 2 | module Specification 3 | class Endpoint 4 | RESERVED_PARAMETER_KEYS = [ 5 | "name", 6 | "in", 7 | "description", 8 | "required" 9 | ] 10 | 11 | attr_reader :path 12 | attr_reader :method 13 | attr_reader :raw 14 | 15 | def initialize(path, method, raw) 16 | @path = path 17 | @method = method 18 | @raw = raw 19 | end 20 | 21 | def body_schema 22 | body_param = parameters.detect { |param| param["in"] == "body" } 23 | return restrictive_schema if body_param.nil? 24 | 25 | body_param.fetch("schema", restrictive_schema) 26 | end 27 | 28 | def header_schema 29 | @header_schema ||= begin 30 | schema = parameter_schema(permissive_schema, "header") 31 | 32 | schema.tap do |schema| 33 | schema["properties"] = schema["properties"].reduce({}) do |props, (k, v)| 34 | props.merge(headerize(k) => v) 35 | end 36 | end 37 | end 38 | end 39 | 40 | def path_schema 41 | @path_schema ||= parameter_schema(restrictive_schema, "path") 42 | end 43 | 44 | def query_schema 45 | @query_schema ||= parameter_schema(restrictive_schema, "query") 46 | end 47 | 48 | def response_body_schema(status) 49 | response = response_from_status(status) 50 | return restrictive_schema if response.nil? 51 | 52 | response.fetch("schema", restrictive_schema) 53 | end 54 | 55 | def response_header_schema(status) 56 | response = response_from_status(status) 57 | return permissive_schema if response.nil? 58 | 59 | header_properties = response.fetch("headers", {}).reduce({}) do |props, (k, v)| 60 | props.merge(headerize(k) => v) 61 | end 62 | 63 | permissive_schema.tap do |schema| 64 | schema["properties"] = header_properties 65 | end 66 | end 67 | 68 | def header_json(headers) 69 | schema = header_schema 70 | 71 | headers.reduce({}) do |json, (k, v)| 72 | json.merge(json_entry(schema, headerize(k), v)) 73 | end 74 | end 75 | 76 | def path_json(request_path) 77 | pattern = Regexp.new(@path.gsub(/\{([^}]+)\}/, '(?<\1>[^/]+)')) 78 | match = pattern.match(request_path.gsub(/\..+\z/, "")) 79 | schema = path_schema 80 | 81 | match.names.reduce({}) do |json, name| 82 | json.merge(json_entry(schema, name, match[name])) 83 | end 84 | end 85 | 86 | def query_json(query_params) 87 | schema = query_schema 88 | 89 | query_params.reduce({}) do |json, (k, v)| 90 | json.merge(json_entry(schema, k, v)) 91 | end 92 | end 93 | 94 | private 95 | 96 | def permissive_schema 97 | { 98 | "additionalProperties" => true, 99 | "properties" => {} 100 | } 101 | end 102 | 103 | def restrictive_schema 104 | { 105 | "additionalProperties" => false, 106 | "properties" => {} 107 | } 108 | end 109 | 110 | def headerize(name) 111 | name.gsub("-", "_").upcase 112 | end 113 | 114 | def json_entry(schema, name, value) 115 | properties = schema["properties"] 116 | 117 | if properties.has_key?(name) && properties[name]["type"] == "integer" && value =~ /\A[0-9]+\z/ 118 | {name => value.to_i} 119 | else 120 | {name => value} 121 | end 122 | end 123 | 124 | def parameters 125 | @raw.fetch("parameters", []) 126 | end 127 | 128 | def parameter_schema(empty_schema, type) 129 | type_params = parameters.select { |param| param["in"] == type } 130 | return empty_schema if type_params.empty? 131 | 132 | properties = type_params.reduce({}) do |schema, param| 133 | schema_value = param.clone.delete_if do |k, v| 134 | RESERVED_PARAMETER_KEYS.include?(k) 135 | end 136 | 137 | schema.merge(param["name"] => schema_value) 138 | end 139 | 140 | type_schema = empty_schema.merge("properties" => properties) 141 | 142 | required_params = type_params.select { |param| param["required"] == true }.map { |param| param["name"] } 143 | 144 | if required_params.any? 145 | type_schema["required"] = required_params 146 | end 147 | 148 | type_schema 149 | end 150 | 151 | def response_from_status(status) 152 | entry = @raw.fetch("responses", {}).detect do |k, v| 153 | k.to_s == status.to_s 154 | end 155 | 156 | return @raw.fetch("responses", {})["default"] if entry.nil? 157 | 158 | entry.last 159 | end 160 | end 161 | end 162 | end 163 | -------------------------------------------------------------------------------- /spec/open_api_parser/specification/endpoint_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe OpenApiParser::Specification::Endpoint do 4 | def root 5 | @root ||= begin 6 | path = File.expand_path("../../../resources/valid_spec.yaml", __FILE__) 7 | OpenApiParser::Specification.resolve(path) 8 | end 9 | end 10 | 11 | describe "path" do 12 | it "returns the path of the matched operation item" do 13 | endpoint = root.endpoint("/animals/1", "get") 14 | expect(endpoint.path).to eq "/animals/{id}" 15 | end 16 | end 17 | 18 | describe "method" do 19 | it "returns the method of the matched operation item" do 20 | endpoint = root.endpoint("/animals", "POST") 21 | expect(endpoint.method).to eq "post" 22 | end 23 | end 24 | 25 | describe "body_schema" do 26 | it "returns the schema for the body" do 27 | endpoint = root.endpoint("/animals", "post") 28 | 29 | expect(endpoint.body_schema).to eq({ 30 | "type" => "object", 31 | "required" => ["name", "legs"], 32 | "properties" => { 33 | "name" => { 34 | "type" => "string" 35 | }, 36 | "legs" => { 37 | "type" => "integer" 38 | } 39 | } 40 | }) 41 | end 42 | 43 | it "returns a restrictive schema if no body is specified" do 44 | endpoint = root.endpoint("/animals/1", "get") 45 | 46 | expect(endpoint.body_schema).to eq({ 47 | "additionalProperties" => false, 48 | "properties" => {} 49 | }) 50 | end 51 | end 52 | 53 | describe "header_schema" do 54 | it "returns the schema for the headers with normalized names" do 55 | endpoint = root.endpoint("/animals/1", "get") 56 | 57 | expect(endpoint.header_schema).to eq({ 58 | "additionalProperties" => true, 59 | "properties" => { 60 | "USER_ID" => { 61 | "type" => "integer" 62 | } 63 | } 64 | }) 65 | end 66 | 67 | it "returns a permissive schema if there are no headers" do 68 | endpoint = root.endpoint("/animals", "post") 69 | 70 | expect(endpoint.header_schema).to eq({ 71 | "additionalProperties" => true, 72 | "properties" => {} 73 | }) 74 | end 75 | end 76 | 77 | describe "path_schema" do 78 | it "returns the schema for the path" do 79 | endpoint = root.endpoint("/animals/1", "get") 80 | 81 | expect(endpoint.path_schema).to eq({ 82 | "additionalProperties" => false, 83 | "required" => ["id"], 84 | "properties" => { 85 | "id" => { 86 | "type" => "integer" 87 | } 88 | } 89 | }) 90 | end 91 | 92 | it "returns a restrictive schema with no path params" do 93 | endpoint = root.endpoint("/animals", "post") 94 | 95 | expect(endpoint.path_schema).to eq({ 96 | "additionalProperties" => false, 97 | "properties" => {} 98 | }) 99 | end 100 | end 101 | 102 | describe "query_schema" do 103 | it "returns the schema for the query params" do 104 | endpoint = root.endpoint("/animals/1", "get") 105 | 106 | expect(endpoint.query_schema).to eq({ 107 | "additionalProperties" => false, 108 | "properties" => { 109 | "size" => { 110 | "type" => "string", 111 | "enum" => [ 112 | "small", 113 | "large" 114 | ] 115 | }, 116 | "age" => { 117 | "type" => "integer" 118 | }, 119 | "tag" => { 120 | "type" => "string" 121 | } 122 | } 123 | }) 124 | end 125 | 126 | it "returns a restrictive schema with no query params" do 127 | endpoint = root.endpoint("/animals", "post") 128 | 129 | expect(endpoint.query_schema).to eq({ 130 | "additionalProperties" => false, 131 | "properties" => {} 132 | }) 133 | end 134 | end 135 | 136 | describe "response_body_schema" do 137 | it "returns the body associated with the response code" do 138 | endpoint = root.endpoint("/animals", "post") 139 | 140 | expect(endpoint.response_body_schema(201)).to eq({ 141 | "type" => "object", 142 | "properties" => { 143 | "status" => { 144 | "type" => "string", 145 | "enum" => ["ok"] 146 | } 147 | }, 148 | "required" => ["status"], 149 | }) 150 | end 151 | 152 | it "handles string or integer response codes" do 153 | endpoint = root.endpoint("/animals", "post") 154 | 155 | expect(endpoint.response_body_schema("201")).to eq({ 156 | "type" => "object", 157 | "properties" => { 158 | "status" => { 159 | "type" => "string", 160 | "enum" => ["ok"] 161 | } 162 | }, 163 | "required" => ["status"], 164 | }) 165 | end 166 | 167 | it "returns the default response if present" do 168 | endpoint = root.endpoint("/animals", "post") 169 | 170 | expect(endpoint.response_body_schema(400)).to eq({ 171 | "type" => "object", 172 | "properties" => { 173 | "status" => { 174 | "type" => "string", 175 | "enum" => ["bad"] 176 | } 177 | }, 178 | "required" => ["status"], 179 | }) 180 | end 181 | 182 | it "returns a restrictive schema for an unknown code without a default" do 183 | endpoint = root.endpoint("/headers", "get") 184 | 185 | expect(endpoint.response_body_schema(400)).to eq({ 186 | "additionalProperties" => false, 187 | "properties" => {} 188 | }) 189 | end 190 | 191 | it "returns a restrictive schema if no schema is specified for a known code" do 192 | endpoint = root.endpoint("/animals/1", "delete") 193 | 194 | expect(endpoint.response_body_schema(204)).to eq({ 195 | "additionalProperties" => false, 196 | "properties" => {} 197 | }) 198 | end 199 | end 200 | 201 | describe "response_body_header" do 202 | it "returns the headers associated with the response code" do 203 | endpoint = root.endpoint("/headers", "get") 204 | 205 | expect(endpoint.response_header_schema(200)).to eq({ 206 | "additionalProperties" => true, 207 | "properties" => { 208 | "X_MY_HEADER" => { 209 | "type" => "string", 210 | "enum" => ["my value"] 211 | } 212 | } 213 | }) 214 | end 215 | 216 | it "handles string or integer response codes" do 217 | endpoint = root.endpoint("/headers", "get") 218 | 219 | expect(endpoint.response_header_schema("200")).to eq({ 220 | "additionalProperties" => true, 221 | "properties" => { 222 | "X_MY_HEADER" => { 223 | "type" => "string", 224 | "enum" => ["my value"] 225 | } 226 | } 227 | }) 228 | end 229 | 230 | it "returns a premissive schema for an unknown code without a default" do 231 | endpoint = root.endpoint("/headers", "get") 232 | 233 | expect(endpoint.response_header_schema(400)).to eq({ 234 | "additionalProperties" => true, 235 | "properties" => {} 236 | }) 237 | end 238 | end 239 | 240 | describe "header_json" do 241 | it "returns a json representation of the headers, given a hash" do 242 | endpoint = root.endpoint("/animals/1", "get") 243 | header_hash = { 244 | "User-Id" => "foo" 245 | } 246 | 247 | expect(endpoint.header_json(header_hash)).to eq({ 248 | "USER_ID" => "foo" 249 | }) 250 | end 251 | 252 | it "parses integers if the value looks like an integer and the schema is an integer" do 253 | endpoint = root.endpoint("/animals/1", "get") 254 | header_hash = { 255 | "User-Id" => "123" 256 | } 257 | 258 | expect(endpoint.header_json(header_hash)).to eq({ 259 | "USER_ID" => 123 260 | }) 261 | end 262 | end 263 | 264 | describe "path_json" do 265 | it "returns a json representation of the path, given a string" do 266 | endpoint = root.endpoint("/animals/1", "get") 267 | 268 | expect(endpoint.path_json("/animals/1")).to eq({ 269 | "id" => 1 270 | }) 271 | end 272 | 273 | it "parses integers if the value looks like an integer and the schema is an integer" do 274 | endpoint = root.endpoint("/animals/1", "get") 275 | 276 | expect(endpoint.path_json("/animals/foo")).to eq({ 277 | "id" => "foo" 278 | }) 279 | 280 | expect(endpoint.path_json("/animals/1")).to eq({ 281 | "id" => 1 282 | }) 283 | end 284 | end 285 | 286 | describe "query_json" do 287 | it "returns a json representation of the query params, given a hash" do 288 | endpoint = root.endpoint("/animals/1", "get") 289 | query_hash = { 290 | "size" => "small" 291 | } 292 | 293 | expect(endpoint.query_json(query_hash)).to eq({ 294 | "size" => "small" 295 | }) 296 | end 297 | 298 | it "parses integers if the value looks like an integer and the schema is an integer" do 299 | endpoint = root.endpoint("/animals/1", "get") 300 | query_hash = { 301 | "age" => "25" 302 | } 303 | 304 | expect(endpoint.query_json(query_hash)).to eq({ 305 | "age" => 25 306 | }) 307 | 308 | query_hash = { 309 | "age" => "twenty" 310 | } 311 | 312 | expect(endpoint.query_json(query_hash)).to eq({ 313 | "age" => "twenty" 314 | }) 315 | end 316 | end 317 | end 318 | -------------------------------------------------------------------------------- /resources/swagger_meta_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "A JSON Schema for Swagger 2.0 API.", 3 | "id": "http://swagger.io/v2/schema.json#", 4 | "$schema": "http://json-schema.org/draft-04/schema#", 5 | "type": "object", 6 | "required": [ 7 | "swagger", 8 | "info", 9 | "paths" 10 | ], 11 | "additionalProperties": false, 12 | "patternProperties": { 13 | "^x-": { 14 | "$ref": "#/definitions/vendorExtension" 15 | } 16 | }, 17 | "properties": { 18 | "swagger": { 19 | "type": "string", 20 | "enum": [ 21 | "2.0" 22 | ], 23 | "description": "The Swagger version of this document." 24 | }, 25 | "info": { 26 | "$ref": "#/definitions/info" 27 | }, 28 | "host": { 29 | "type": "string", 30 | "pattern": "^[^{}/ :\\\\]+(?::\\d+)?$", 31 | "description": "The host (name or ip) of the API. Example: 'swagger.io'" 32 | }, 33 | "basePath": { 34 | "type": "string", 35 | "pattern": "^/", 36 | "description": "The base path to the API. Example: '/api'." 37 | }, 38 | "schemes": { 39 | "$ref": "#/definitions/schemesList" 40 | }, 41 | "consumes": { 42 | "description": "A list of MIME types accepted by the API.", 43 | "allOf": [ 44 | { 45 | "$ref": "#/definitions/mediaTypeList" 46 | } 47 | ] 48 | }, 49 | "produces": { 50 | "description": "A list of MIME types the API can produce.", 51 | "allOf": [ 52 | { 53 | "$ref": "#/definitions/mediaTypeList" 54 | } 55 | ] 56 | }, 57 | "paths": { 58 | "$ref": "#/definitions/paths" 59 | }, 60 | "definitions": { 61 | "$ref": "#/definitions/definitions" 62 | }, 63 | "parameters": { 64 | "$ref": "#/definitions/parameterDefinitions" 65 | }, 66 | "responses": { 67 | "$ref": "#/definitions/responseDefinitions" 68 | }, 69 | "security": { 70 | "$ref": "#/definitions/security" 71 | }, 72 | "securityDefinitions": { 73 | "$ref": "#/definitions/securityDefinitions" 74 | }, 75 | "tags": { 76 | "type": "array", 77 | "items": { 78 | "$ref": "#/definitions/tag" 79 | }, 80 | "uniqueItems": true 81 | }, 82 | "externalDocs": { 83 | "$ref": "#/definitions/externalDocs" 84 | } 85 | }, 86 | "definitions": { 87 | "info": { 88 | "type": "object", 89 | "description": "General information about the API.", 90 | "required": [ 91 | "version", 92 | "title" 93 | ], 94 | "additionalProperties": false, 95 | "patternProperties": { 96 | "^x-": { 97 | "$ref": "#/definitions/vendorExtension" 98 | } 99 | }, 100 | "properties": { 101 | "title": { 102 | "type": "string", 103 | "description": "A unique and precise title of the API." 104 | }, 105 | "version": { 106 | "type": "string", 107 | "description": "A semantic version number of the API." 108 | }, 109 | "description": { 110 | "type": "string", 111 | "description": "A longer description of the API. Should be different from the title. GitHub Flavored Markdown is allowed." 112 | }, 113 | "termsOfService": { 114 | "type": "string", 115 | "description": "The terms of service for the API." 116 | }, 117 | "contact": { 118 | "$ref": "#/definitions/contact" 119 | }, 120 | "license": { 121 | "$ref": "#/definitions/license" 122 | } 123 | } 124 | }, 125 | "contact": { 126 | "type": "object", 127 | "description": "Contact information for the owners of the API.", 128 | "additionalProperties": false, 129 | "properties": { 130 | "name": { 131 | "type": "string", 132 | "description": "The identifying name of the contact person/organization." 133 | }, 134 | "url": { 135 | "type": "string", 136 | "description": "The URL pointing to the contact information.", 137 | "format": "uri" 138 | }, 139 | "email": { 140 | "type": "string", 141 | "description": "The email address of the contact person/organization.", 142 | "format": "email" 143 | } 144 | }, 145 | "patternProperties": { 146 | "^x-": { 147 | "$ref": "#/definitions/vendorExtension" 148 | } 149 | } 150 | }, 151 | "license": { 152 | "type": "object", 153 | "required": [ 154 | "name" 155 | ], 156 | "additionalProperties": false, 157 | "properties": { 158 | "name": { 159 | "type": "string", 160 | "description": "The name of the license type. It's encouraged to use an OSI compatible license." 161 | }, 162 | "url": { 163 | "type": "string", 164 | "description": "The URL pointing to the license.", 165 | "format": "uri" 166 | } 167 | }, 168 | "patternProperties": { 169 | "^x-": { 170 | "$ref": "#/definitions/vendorExtension" 171 | } 172 | } 173 | }, 174 | "paths": { 175 | "type": "object", 176 | "description": "Relative paths to the individual endpoints. They must be relative to the 'basePath'.", 177 | "patternProperties": { 178 | "^x-": { 179 | "$ref": "#/definitions/vendorExtension" 180 | }, 181 | "^/": { 182 | "$ref": "#/definitions/pathItem" 183 | } 184 | }, 185 | "additionalProperties": false 186 | }, 187 | "definitions": { 188 | "type": "object", 189 | "additionalProperties": { 190 | "$ref": "#/definitions/schema" 191 | }, 192 | "description": "One or more JSON objects describing the schemas being consumed and produced by the API." 193 | }, 194 | "parameterDefinitions": { 195 | "type": "object", 196 | "additionalProperties": { 197 | "$ref": "#/definitions/parameter" 198 | }, 199 | "description": "One or more JSON representations for parameters" 200 | }, 201 | "responseDefinitions": { 202 | "type": "object", 203 | "additionalProperties": { 204 | "$ref": "#/definitions/response" 205 | }, 206 | "description": "One or more JSON representations for parameters" 207 | }, 208 | "externalDocs": { 209 | "type": "object", 210 | "additionalProperties": false, 211 | "description": "information about external documentation", 212 | "required": [ 213 | "url" 214 | ], 215 | "properties": { 216 | "description": { 217 | "type": "string" 218 | }, 219 | "url": { 220 | "type": "string", 221 | "format": "uri" 222 | } 223 | }, 224 | "patternProperties": { 225 | "^x-": { 226 | "$ref": "#/definitions/vendorExtension" 227 | } 228 | } 229 | }, 230 | "examples": { 231 | "type": "object", 232 | "additionalProperties": true 233 | }, 234 | "mimeType": { 235 | "type": "string", 236 | "description": "The MIME type of the HTTP message." 237 | }, 238 | "operation": { 239 | "type": "object", 240 | "required": [ 241 | "responses" 242 | ], 243 | "additionalProperties": false, 244 | "patternProperties": { 245 | "^x-": { 246 | "$ref": "#/definitions/vendorExtension" 247 | } 248 | }, 249 | "properties": { 250 | "tags": { 251 | "type": "array", 252 | "items": { 253 | "type": "string" 254 | }, 255 | "uniqueItems": true 256 | }, 257 | "summary": { 258 | "type": "string", 259 | "description": "A brief summary of the operation." 260 | }, 261 | "description": { 262 | "type": "string", 263 | "description": "A longer description of the operation, GitHub Flavored Markdown is allowed." 264 | }, 265 | "externalDocs": { 266 | "$ref": "#/definitions/externalDocs" 267 | }, 268 | "operationId": { 269 | "type": "string", 270 | "description": "A unique identifier of the operation." 271 | }, 272 | "produces": { 273 | "description": "A list of MIME types the API can produce.", 274 | "allOf": [ 275 | { 276 | "$ref": "#/definitions/mediaTypeList" 277 | } 278 | ] 279 | }, 280 | "consumes": { 281 | "description": "A list of MIME types the API can consume.", 282 | "allOf": [ 283 | { 284 | "$ref": "#/definitions/mediaTypeList" 285 | } 286 | ] 287 | }, 288 | "parameters": { 289 | "$ref": "#/definitions/parametersList" 290 | }, 291 | "responses": { 292 | "$ref": "#/definitions/responses" 293 | }, 294 | "schemes": { 295 | "$ref": "#/definitions/schemesList" 296 | }, 297 | "deprecated": { 298 | "type": "boolean", 299 | "default": false 300 | }, 301 | "security": { 302 | "$ref": "#/definitions/security" 303 | } 304 | } 305 | }, 306 | "pathItem": { 307 | "type": "object", 308 | "additionalProperties": false, 309 | "patternProperties": { 310 | "^x-": { 311 | "$ref": "#/definitions/vendorExtension" 312 | } 313 | }, 314 | "properties": { 315 | "$ref": { 316 | "type": "string" 317 | }, 318 | "get": { 319 | "$ref": "#/definitions/operation" 320 | }, 321 | "put": { 322 | "$ref": "#/definitions/operation" 323 | }, 324 | "post": { 325 | "$ref": "#/definitions/operation" 326 | }, 327 | "delete": { 328 | "$ref": "#/definitions/operation" 329 | }, 330 | "options": { 331 | "$ref": "#/definitions/operation" 332 | }, 333 | "head": { 334 | "$ref": "#/definitions/operation" 335 | }, 336 | "patch": { 337 | "$ref": "#/definitions/operation" 338 | }, 339 | "parameters": { 340 | "$ref": "#/definitions/parametersList" 341 | } 342 | } 343 | }, 344 | "responses": { 345 | "type": "object", 346 | "description": "Response objects names can either be any valid HTTP status code or 'default'.", 347 | "minProperties": 1, 348 | "additionalProperties": false, 349 | "patternProperties": { 350 | "^([0-9]{3})$|^(default)$": { 351 | "$ref": "#/definitions/responseValue" 352 | }, 353 | "^x-": { 354 | "$ref": "#/definitions/vendorExtension" 355 | } 356 | }, 357 | "not": { 358 | "type": "object", 359 | "additionalProperties": false, 360 | "patternProperties": { 361 | "^x-": { 362 | "$ref": "#/definitions/vendorExtension" 363 | } 364 | } 365 | } 366 | }, 367 | "responseValue": { 368 | "oneOf": [ 369 | { 370 | "$ref": "#/definitions/response" 371 | }, 372 | { 373 | "$ref": "#/definitions/jsonReference" 374 | } 375 | ] 376 | }, 377 | "response": { 378 | "type": "object", 379 | "required": [ 380 | "description" 381 | ], 382 | "properties": { 383 | "description": { 384 | "type": "string" 385 | }, 386 | "schema": { 387 | "oneOf": [ 388 | { 389 | "$ref": "#/definitions/schema" 390 | }, 391 | { 392 | "$ref": "#/definitions/fileSchema" 393 | } 394 | ] 395 | }, 396 | "headers": { 397 | "$ref": "#/definitions/headers" 398 | }, 399 | "examples": { 400 | "$ref": "#/definitions/examples" 401 | } 402 | }, 403 | "additionalProperties": false, 404 | "patternProperties": { 405 | "^x-": { 406 | "$ref": "#/definitions/vendorExtension" 407 | } 408 | } 409 | }, 410 | "headers": { 411 | "type": "object", 412 | "additionalProperties": { 413 | "$ref": "#/definitions/header" 414 | } 415 | }, 416 | "header": { 417 | "type": "object", 418 | "additionalProperties": false, 419 | "required": [ 420 | "type" 421 | ], 422 | "properties": { 423 | "type": { 424 | "type": "string", 425 | "enum": [ 426 | "string", 427 | "number", 428 | "integer", 429 | "boolean", 430 | "array" 431 | ] 432 | }, 433 | "format": { 434 | "type": "string" 435 | }, 436 | "items": { 437 | "$ref": "#/definitions/primitivesItems" 438 | }, 439 | "collectionFormat": { 440 | "$ref": "#/definitions/collectionFormat" 441 | }, 442 | "default": { 443 | "$ref": "#/definitions/default" 444 | }, 445 | "maximum": { 446 | "$ref": "#/definitions/maximum" 447 | }, 448 | "exclusiveMaximum": { 449 | "$ref": "#/definitions/exclusiveMaximum" 450 | }, 451 | "minimum": { 452 | "$ref": "#/definitions/minimum" 453 | }, 454 | "exclusiveMinimum": { 455 | "$ref": "#/definitions/exclusiveMinimum" 456 | }, 457 | "maxLength": { 458 | "$ref": "#/definitions/maxLength" 459 | }, 460 | "minLength": { 461 | "$ref": "#/definitions/minLength" 462 | }, 463 | "pattern": { 464 | "$ref": "#/definitions/pattern" 465 | }, 466 | "maxItems": { 467 | "$ref": "#/definitions/maxItems" 468 | }, 469 | "minItems": { 470 | "$ref": "#/definitions/minItems" 471 | }, 472 | "uniqueItems": { 473 | "$ref": "#/definitions/uniqueItems" 474 | }, 475 | "enum": { 476 | "$ref": "#/definitions/enum" 477 | }, 478 | "multipleOf": { 479 | "$ref": "#/definitions/multipleOf" 480 | }, 481 | "description": { 482 | "type": "string" 483 | } 484 | }, 485 | "patternProperties": { 486 | "^x-": { 487 | "$ref": "#/definitions/vendorExtension" 488 | } 489 | } 490 | }, 491 | "vendorExtension": { 492 | "description": "Any property starting with x- is valid.", 493 | "additionalProperties": true, 494 | "additionalItems": true 495 | }, 496 | "bodyParameter": { 497 | "type": "object", 498 | "required": [ 499 | "name", 500 | "in", 501 | "schema" 502 | ], 503 | "patternProperties": { 504 | "^x-": { 505 | "$ref": "#/definitions/vendorExtension" 506 | } 507 | }, 508 | "properties": { 509 | "description": { 510 | "type": "string", 511 | "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." 512 | }, 513 | "name": { 514 | "type": "string", 515 | "description": "The name of the parameter." 516 | }, 517 | "in": { 518 | "type": "string", 519 | "description": "Determines the location of the parameter.", 520 | "enum": [ 521 | "body" 522 | ] 523 | }, 524 | "required": { 525 | "type": "boolean", 526 | "description": "Determines whether or not this parameter is required or optional.", 527 | "default": false 528 | }, 529 | "schema": { 530 | "$ref": "#/definitions/schema" 531 | } 532 | }, 533 | "additionalProperties": false 534 | }, 535 | "headerParameterSubSchema": { 536 | "additionalProperties": false, 537 | "patternProperties": { 538 | "^x-": { 539 | "$ref": "#/definitions/vendorExtension" 540 | } 541 | }, 542 | "properties": { 543 | "required": { 544 | "type": "boolean", 545 | "description": "Determines whether or not this parameter is required or optional.", 546 | "default": false 547 | }, 548 | "in": { 549 | "type": "string", 550 | "description": "Determines the location of the parameter.", 551 | "enum": [ 552 | "header" 553 | ] 554 | }, 555 | "description": { 556 | "type": "string", 557 | "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." 558 | }, 559 | "name": { 560 | "type": "string", 561 | "description": "The name of the parameter." 562 | }, 563 | "type": { 564 | "type": "string", 565 | "enum": [ 566 | "string", 567 | "number", 568 | "boolean", 569 | "integer", 570 | "array" 571 | ] 572 | }, 573 | "format": { 574 | "type": "string" 575 | }, 576 | "items": { 577 | "$ref": "#/definitions/primitivesItems" 578 | }, 579 | "collectionFormat": { 580 | "$ref": "#/definitions/collectionFormat" 581 | }, 582 | "default": { 583 | "$ref": "#/definitions/default" 584 | }, 585 | "maximum": { 586 | "$ref": "#/definitions/maximum" 587 | }, 588 | "exclusiveMaximum": { 589 | "$ref": "#/definitions/exclusiveMaximum" 590 | }, 591 | "minimum": { 592 | "$ref": "#/definitions/minimum" 593 | }, 594 | "exclusiveMinimum": { 595 | "$ref": "#/definitions/exclusiveMinimum" 596 | }, 597 | "maxLength": { 598 | "$ref": "#/definitions/maxLength" 599 | }, 600 | "minLength": { 601 | "$ref": "#/definitions/minLength" 602 | }, 603 | "pattern": { 604 | "$ref": "#/definitions/pattern" 605 | }, 606 | "maxItems": { 607 | "$ref": "#/definitions/maxItems" 608 | }, 609 | "minItems": { 610 | "$ref": "#/definitions/minItems" 611 | }, 612 | "uniqueItems": { 613 | "$ref": "#/definitions/uniqueItems" 614 | }, 615 | "enum": { 616 | "$ref": "#/definitions/enum" 617 | }, 618 | "multipleOf": { 619 | "$ref": "#/definitions/multipleOf" 620 | } 621 | } 622 | }, 623 | "queryParameterSubSchema": { 624 | "additionalProperties": false, 625 | "patternProperties": { 626 | "^x-": { 627 | "$ref": "#/definitions/vendorExtension" 628 | } 629 | }, 630 | "properties": { 631 | "required": { 632 | "type": "boolean", 633 | "description": "Determines whether or not this parameter is required or optional.", 634 | "default": false 635 | }, 636 | "in": { 637 | "type": "string", 638 | "description": "Determines the location of the parameter.", 639 | "enum": [ 640 | "query" 641 | ] 642 | }, 643 | "description": { 644 | "type": "string", 645 | "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." 646 | }, 647 | "name": { 648 | "type": "string", 649 | "description": "The name of the parameter." 650 | }, 651 | "allowEmptyValue": { 652 | "type": "boolean", 653 | "default": false, 654 | "description": "allows sending a parameter by name only or with an empty value." 655 | }, 656 | "type": { 657 | "type": "string", 658 | "enum": [ 659 | "string", 660 | "number", 661 | "boolean", 662 | "integer", 663 | "array" 664 | ] 665 | }, 666 | "format": { 667 | "type": "string" 668 | }, 669 | "items": { 670 | "$ref": "#/definitions/primitivesItems" 671 | }, 672 | "collectionFormat": { 673 | "$ref": "#/definitions/collectionFormatWithMulti" 674 | }, 675 | "default": { 676 | "$ref": "#/definitions/default" 677 | }, 678 | "maximum": { 679 | "$ref": "#/definitions/maximum" 680 | }, 681 | "exclusiveMaximum": { 682 | "$ref": "#/definitions/exclusiveMaximum" 683 | }, 684 | "minimum": { 685 | "$ref": "#/definitions/minimum" 686 | }, 687 | "exclusiveMinimum": { 688 | "$ref": "#/definitions/exclusiveMinimum" 689 | }, 690 | "maxLength": { 691 | "$ref": "#/definitions/maxLength" 692 | }, 693 | "minLength": { 694 | "$ref": "#/definitions/minLength" 695 | }, 696 | "pattern": { 697 | "$ref": "#/definitions/pattern" 698 | }, 699 | "maxItems": { 700 | "$ref": "#/definitions/maxItems" 701 | }, 702 | "minItems": { 703 | "$ref": "#/definitions/minItems" 704 | }, 705 | "uniqueItems": { 706 | "$ref": "#/definitions/uniqueItems" 707 | }, 708 | "enum": { 709 | "$ref": "#/definitions/enum" 710 | }, 711 | "multipleOf": { 712 | "$ref": "#/definitions/multipleOf" 713 | } 714 | } 715 | }, 716 | "formDataParameterSubSchema": { 717 | "additionalProperties": false, 718 | "patternProperties": { 719 | "^x-": { 720 | "$ref": "#/definitions/vendorExtension" 721 | } 722 | }, 723 | "properties": { 724 | "required": { 725 | "type": "boolean", 726 | "description": "Determines whether or not this parameter is required or optional.", 727 | "default": false 728 | }, 729 | "in": { 730 | "type": "string", 731 | "description": "Determines the location of the parameter.", 732 | "enum": [ 733 | "formData" 734 | ] 735 | }, 736 | "description": { 737 | "type": "string", 738 | "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." 739 | }, 740 | "name": { 741 | "type": "string", 742 | "description": "The name of the parameter." 743 | }, 744 | "allowEmptyValue": { 745 | "type": "boolean", 746 | "default": false, 747 | "description": "allows sending a parameter by name only or with an empty value." 748 | }, 749 | "type": { 750 | "type": "string", 751 | "enum": [ 752 | "string", 753 | "number", 754 | "boolean", 755 | "integer", 756 | "array", 757 | "file" 758 | ] 759 | }, 760 | "format": { 761 | "type": "string" 762 | }, 763 | "items": { 764 | "$ref": "#/definitions/primitivesItems" 765 | }, 766 | "collectionFormat": { 767 | "$ref": "#/definitions/collectionFormatWithMulti" 768 | }, 769 | "default": { 770 | "$ref": "#/definitions/default" 771 | }, 772 | "maximum": { 773 | "$ref": "#/definitions/maximum" 774 | }, 775 | "exclusiveMaximum": { 776 | "$ref": "#/definitions/exclusiveMaximum" 777 | }, 778 | "minimum": { 779 | "$ref": "#/definitions/minimum" 780 | }, 781 | "exclusiveMinimum": { 782 | "$ref": "#/definitions/exclusiveMinimum" 783 | }, 784 | "maxLength": { 785 | "$ref": "#/definitions/maxLength" 786 | }, 787 | "minLength": { 788 | "$ref": "#/definitions/minLength" 789 | }, 790 | "pattern": { 791 | "$ref": "#/definitions/pattern" 792 | }, 793 | "maxItems": { 794 | "$ref": "#/definitions/maxItems" 795 | }, 796 | "minItems": { 797 | "$ref": "#/definitions/minItems" 798 | }, 799 | "uniqueItems": { 800 | "$ref": "#/definitions/uniqueItems" 801 | }, 802 | "enum": { 803 | "$ref": "#/definitions/enum" 804 | }, 805 | "multipleOf": { 806 | "$ref": "#/definitions/multipleOf" 807 | } 808 | } 809 | }, 810 | "pathParameterSubSchema": { 811 | "additionalProperties": false, 812 | "patternProperties": { 813 | "^x-": { 814 | "$ref": "#/definitions/vendorExtension" 815 | } 816 | }, 817 | "required": [ 818 | "required" 819 | ], 820 | "properties": { 821 | "required": { 822 | "type": "boolean", 823 | "enum": [ 824 | true 825 | ], 826 | "description": "Determines whether or not this parameter is required or optional." 827 | }, 828 | "in": { 829 | "type": "string", 830 | "description": "Determines the location of the parameter.", 831 | "enum": [ 832 | "path" 833 | ] 834 | }, 835 | "description": { 836 | "type": "string", 837 | "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." 838 | }, 839 | "name": { 840 | "type": "string", 841 | "description": "The name of the parameter." 842 | }, 843 | "type": { 844 | "type": "string", 845 | "enum": [ 846 | "string", 847 | "number", 848 | "boolean", 849 | "integer", 850 | "array" 851 | ] 852 | }, 853 | "format": { 854 | "type": "string" 855 | }, 856 | "items": { 857 | "$ref": "#/definitions/primitivesItems" 858 | }, 859 | "collectionFormat": { 860 | "$ref": "#/definitions/collectionFormat" 861 | }, 862 | "default": { 863 | "$ref": "#/definitions/default" 864 | }, 865 | "maximum": { 866 | "$ref": "#/definitions/maximum" 867 | }, 868 | "exclusiveMaximum": { 869 | "$ref": "#/definitions/exclusiveMaximum" 870 | }, 871 | "minimum": { 872 | "$ref": "#/definitions/minimum" 873 | }, 874 | "exclusiveMinimum": { 875 | "$ref": "#/definitions/exclusiveMinimum" 876 | }, 877 | "maxLength": { 878 | "$ref": "#/definitions/maxLength" 879 | }, 880 | "minLength": { 881 | "$ref": "#/definitions/minLength" 882 | }, 883 | "pattern": { 884 | "$ref": "#/definitions/pattern" 885 | }, 886 | "maxItems": { 887 | "$ref": "#/definitions/maxItems" 888 | }, 889 | "minItems": { 890 | "$ref": "#/definitions/minItems" 891 | }, 892 | "uniqueItems": { 893 | "$ref": "#/definitions/uniqueItems" 894 | }, 895 | "enum": { 896 | "$ref": "#/definitions/enum" 897 | }, 898 | "multipleOf": { 899 | "$ref": "#/definitions/multipleOf" 900 | } 901 | } 902 | }, 903 | "nonBodyParameter": { 904 | "type": "object", 905 | "required": [ 906 | "name", 907 | "in", 908 | "type" 909 | ], 910 | "oneOf": [ 911 | { 912 | "$ref": "#/definitions/headerParameterSubSchema" 913 | }, 914 | { 915 | "$ref": "#/definitions/formDataParameterSubSchema" 916 | }, 917 | { 918 | "$ref": "#/definitions/queryParameterSubSchema" 919 | }, 920 | { 921 | "$ref": "#/definitions/pathParameterSubSchema" 922 | } 923 | ] 924 | }, 925 | "parameter": { 926 | "oneOf": [ 927 | { 928 | "$ref": "#/definitions/bodyParameter" 929 | }, 930 | { 931 | "$ref": "#/definitions/nonBodyParameter" 932 | } 933 | ] 934 | }, 935 | "schema": { 936 | "type": "object", 937 | "description": "A deterministic version of a JSON Schema object.", 938 | "patternProperties": { 939 | "^x-": { 940 | "$ref": "#/definitions/vendorExtension" 941 | } 942 | }, 943 | "properties": { 944 | "$ref": { 945 | "type": "string" 946 | }, 947 | "format": { 948 | "type": "string" 949 | }, 950 | "title": { 951 | "$ref": "http://json-schema.org/draft-04/schema#/properties/title" 952 | }, 953 | "description": { 954 | "$ref": "http://json-schema.org/draft-04/schema#/properties/description" 955 | }, 956 | "default": { 957 | "$ref": "http://json-schema.org/draft-04/schema#/properties/default" 958 | }, 959 | "multipleOf": { 960 | "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf" 961 | }, 962 | "maximum": { 963 | "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum" 964 | }, 965 | "exclusiveMaximum": { 966 | "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum" 967 | }, 968 | "minimum": { 969 | "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum" 970 | }, 971 | "exclusiveMinimum": { 972 | "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum" 973 | }, 974 | "maxLength": { 975 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" 976 | }, 977 | "minLength": { 978 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" 979 | }, 980 | "pattern": { 981 | "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern" 982 | }, 983 | "maxItems": { 984 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" 985 | }, 986 | "minItems": { 987 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" 988 | }, 989 | "uniqueItems": { 990 | "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems" 991 | }, 992 | "maxProperties": { 993 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" 994 | }, 995 | "minProperties": { 996 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" 997 | }, 998 | "required": { 999 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray" 1000 | }, 1001 | "enum": { 1002 | "$ref": "http://json-schema.org/draft-04/schema#/properties/enum" 1003 | }, 1004 | "additionalProperties": { 1005 | "anyOf": [ 1006 | { 1007 | "$ref": "#/definitions/schema" 1008 | }, 1009 | { 1010 | "type": "boolean" 1011 | } 1012 | ], 1013 | "default": {} 1014 | }, 1015 | "type": { 1016 | "$ref": "http://json-schema.org/draft-04/schema#/properties/type" 1017 | }, 1018 | "items": { 1019 | "anyOf": [ 1020 | { 1021 | "$ref": "#/definitions/schema" 1022 | }, 1023 | { 1024 | "type": "array", 1025 | "minItems": 1, 1026 | "items": { 1027 | "$ref": "#/definitions/schema" 1028 | } 1029 | } 1030 | ], 1031 | "default": {} 1032 | }, 1033 | "allOf": { 1034 | "type": "array", 1035 | "minItems": 1, 1036 | "items": { 1037 | "$ref": "#/definitions/schema" 1038 | } 1039 | }, 1040 | "properties": { 1041 | "type": "object", 1042 | "additionalProperties": { 1043 | "$ref": "#/definitions/schema" 1044 | }, 1045 | "default": {} 1046 | }, 1047 | "discriminator": { 1048 | "type": "string" 1049 | }, 1050 | "readOnly": { 1051 | "type": "boolean", 1052 | "default": false 1053 | }, 1054 | "xml": { 1055 | "$ref": "#/definitions/xml" 1056 | }, 1057 | "externalDocs": { 1058 | "$ref": "#/definitions/externalDocs" 1059 | }, 1060 | "example": {} 1061 | }, 1062 | "additionalProperties": false 1063 | }, 1064 | "fileSchema": { 1065 | "type": "object", 1066 | "description": "A deterministic version of a JSON Schema object.", 1067 | "patternProperties": { 1068 | "^x-": { 1069 | "$ref": "#/definitions/vendorExtension" 1070 | } 1071 | }, 1072 | "required": [ 1073 | "type" 1074 | ], 1075 | "properties": { 1076 | "format": { 1077 | "type": "string" 1078 | }, 1079 | "title": { 1080 | "$ref": "http://json-schema.org/draft-04/schema#/properties/title" 1081 | }, 1082 | "description": { 1083 | "$ref": "http://json-schema.org/draft-04/schema#/properties/description" 1084 | }, 1085 | "default": { 1086 | "$ref": "http://json-schema.org/draft-04/schema#/properties/default" 1087 | }, 1088 | "required": { 1089 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray" 1090 | }, 1091 | "type": { 1092 | "type": "string", 1093 | "enum": [ 1094 | "file" 1095 | ] 1096 | }, 1097 | "readOnly": { 1098 | "type": "boolean", 1099 | "default": false 1100 | }, 1101 | "externalDocs": { 1102 | "$ref": "#/definitions/externalDocs" 1103 | }, 1104 | "example": {} 1105 | }, 1106 | "additionalProperties": false 1107 | }, 1108 | "primitivesItems": { 1109 | "type": "object", 1110 | "additionalProperties": false, 1111 | "properties": { 1112 | "type": { 1113 | "type": "string", 1114 | "enum": [ 1115 | "string", 1116 | "number", 1117 | "integer", 1118 | "boolean", 1119 | "array" 1120 | ] 1121 | }, 1122 | "format": { 1123 | "type": "string" 1124 | }, 1125 | "items": { 1126 | "$ref": "#/definitions/primitivesItems" 1127 | }, 1128 | "collectionFormat": { 1129 | "$ref": "#/definitions/collectionFormat" 1130 | }, 1131 | "default": { 1132 | "$ref": "#/definitions/default" 1133 | }, 1134 | "maximum": { 1135 | "$ref": "#/definitions/maximum" 1136 | }, 1137 | "exclusiveMaximum": { 1138 | "$ref": "#/definitions/exclusiveMaximum" 1139 | }, 1140 | "minimum": { 1141 | "$ref": "#/definitions/minimum" 1142 | }, 1143 | "exclusiveMinimum": { 1144 | "$ref": "#/definitions/exclusiveMinimum" 1145 | }, 1146 | "maxLength": { 1147 | "$ref": "#/definitions/maxLength" 1148 | }, 1149 | "minLength": { 1150 | "$ref": "#/definitions/minLength" 1151 | }, 1152 | "pattern": { 1153 | "$ref": "#/definitions/pattern" 1154 | }, 1155 | "maxItems": { 1156 | "$ref": "#/definitions/maxItems" 1157 | }, 1158 | "minItems": { 1159 | "$ref": "#/definitions/minItems" 1160 | }, 1161 | "uniqueItems": { 1162 | "$ref": "#/definitions/uniqueItems" 1163 | }, 1164 | "enum": { 1165 | "$ref": "#/definitions/enum" 1166 | }, 1167 | "multipleOf": { 1168 | "$ref": "#/definitions/multipleOf" 1169 | } 1170 | }, 1171 | "patternProperties": { 1172 | "^x-": { 1173 | "$ref": "#/definitions/vendorExtension" 1174 | } 1175 | } 1176 | }, 1177 | "security": { 1178 | "type": "array", 1179 | "items": { 1180 | "$ref": "#/definitions/securityRequirement" 1181 | }, 1182 | "uniqueItems": true 1183 | }, 1184 | "securityRequirement": { 1185 | "type": "object", 1186 | "additionalProperties": { 1187 | "type": "array", 1188 | "items": { 1189 | "type": "string" 1190 | }, 1191 | "uniqueItems": true 1192 | } 1193 | }, 1194 | "xml": { 1195 | "type": "object", 1196 | "additionalProperties": false, 1197 | "properties": { 1198 | "name": { 1199 | "type": "string" 1200 | }, 1201 | "namespace": { 1202 | "type": "string" 1203 | }, 1204 | "prefix": { 1205 | "type": "string" 1206 | }, 1207 | "attribute": { 1208 | "type": "boolean", 1209 | "default": false 1210 | }, 1211 | "wrapped": { 1212 | "type": "boolean", 1213 | "default": false 1214 | } 1215 | }, 1216 | "patternProperties": { 1217 | "^x-": { 1218 | "$ref": "#/definitions/vendorExtension" 1219 | } 1220 | } 1221 | }, 1222 | "tag": { 1223 | "type": "object", 1224 | "additionalProperties": false, 1225 | "required": [ 1226 | "name" 1227 | ], 1228 | "properties": { 1229 | "name": { 1230 | "type": "string" 1231 | }, 1232 | "description": { 1233 | "type": "string" 1234 | }, 1235 | "externalDocs": { 1236 | "$ref": "#/definitions/externalDocs" 1237 | } 1238 | }, 1239 | "patternProperties": { 1240 | "^x-": { 1241 | "$ref": "#/definitions/vendorExtension" 1242 | } 1243 | } 1244 | }, 1245 | "securityDefinitions": { 1246 | "type": "object", 1247 | "additionalProperties": { 1248 | "oneOf": [ 1249 | { 1250 | "$ref": "#/definitions/basicAuthenticationSecurity" 1251 | }, 1252 | { 1253 | "$ref": "#/definitions/apiKeySecurity" 1254 | }, 1255 | { 1256 | "$ref": "#/definitions/oauth2ImplicitSecurity" 1257 | }, 1258 | { 1259 | "$ref": "#/definitions/oauth2PasswordSecurity" 1260 | }, 1261 | { 1262 | "$ref": "#/definitions/oauth2ApplicationSecurity" 1263 | }, 1264 | { 1265 | "$ref": "#/definitions/oauth2AccessCodeSecurity" 1266 | } 1267 | ] 1268 | } 1269 | }, 1270 | "basicAuthenticationSecurity": { 1271 | "type": "object", 1272 | "additionalProperties": false, 1273 | "required": [ 1274 | "type" 1275 | ], 1276 | "properties": { 1277 | "type": { 1278 | "type": "string", 1279 | "enum": [ 1280 | "basic" 1281 | ] 1282 | }, 1283 | "description": { 1284 | "type": "string" 1285 | } 1286 | }, 1287 | "patternProperties": { 1288 | "^x-": { 1289 | "$ref": "#/definitions/vendorExtension" 1290 | } 1291 | } 1292 | }, 1293 | "apiKeySecurity": { 1294 | "type": "object", 1295 | "additionalProperties": false, 1296 | "required": [ 1297 | "type", 1298 | "name", 1299 | "in" 1300 | ], 1301 | "properties": { 1302 | "type": { 1303 | "type": "string", 1304 | "enum": [ 1305 | "apiKey" 1306 | ] 1307 | }, 1308 | "name": { 1309 | "type": "string" 1310 | }, 1311 | "in": { 1312 | "type": "string", 1313 | "enum": [ 1314 | "header", 1315 | "query" 1316 | ] 1317 | }, 1318 | "description": { 1319 | "type": "string" 1320 | } 1321 | }, 1322 | "patternProperties": { 1323 | "^x-": { 1324 | "$ref": "#/definitions/vendorExtension" 1325 | } 1326 | } 1327 | }, 1328 | "oauth2ImplicitSecurity": { 1329 | "type": "object", 1330 | "additionalProperties": false, 1331 | "required": [ 1332 | "type", 1333 | "flow", 1334 | "authorizationUrl" 1335 | ], 1336 | "properties": { 1337 | "type": { 1338 | "type": "string", 1339 | "enum": [ 1340 | "oauth2" 1341 | ] 1342 | }, 1343 | "flow": { 1344 | "type": "string", 1345 | "enum": [ 1346 | "implicit" 1347 | ] 1348 | }, 1349 | "scopes": { 1350 | "$ref": "#/definitions/oauth2Scopes" 1351 | }, 1352 | "authorizationUrl": { 1353 | "type": "string", 1354 | "format": "uri" 1355 | }, 1356 | "description": { 1357 | "type": "string" 1358 | } 1359 | }, 1360 | "patternProperties": { 1361 | "^x-": { 1362 | "$ref": "#/definitions/vendorExtension" 1363 | } 1364 | } 1365 | }, 1366 | "oauth2PasswordSecurity": { 1367 | "type": "object", 1368 | "additionalProperties": false, 1369 | "required": [ 1370 | "type", 1371 | "flow", 1372 | "tokenUrl" 1373 | ], 1374 | "properties": { 1375 | "type": { 1376 | "type": "string", 1377 | "enum": [ 1378 | "oauth2" 1379 | ] 1380 | }, 1381 | "flow": { 1382 | "type": "string", 1383 | "enum": [ 1384 | "password" 1385 | ] 1386 | }, 1387 | "scopes": { 1388 | "$ref": "#/definitions/oauth2Scopes" 1389 | }, 1390 | "tokenUrl": { 1391 | "type": "string", 1392 | "format": "uri" 1393 | }, 1394 | "description": { 1395 | "type": "string" 1396 | } 1397 | }, 1398 | "patternProperties": { 1399 | "^x-": { 1400 | "$ref": "#/definitions/vendorExtension" 1401 | } 1402 | } 1403 | }, 1404 | "oauth2ApplicationSecurity": { 1405 | "type": "object", 1406 | "additionalProperties": false, 1407 | "required": [ 1408 | "type", 1409 | "flow", 1410 | "tokenUrl" 1411 | ], 1412 | "properties": { 1413 | "type": { 1414 | "type": "string", 1415 | "enum": [ 1416 | "oauth2" 1417 | ] 1418 | }, 1419 | "flow": { 1420 | "type": "string", 1421 | "enum": [ 1422 | "application" 1423 | ] 1424 | }, 1425 | "scopes": { 1426 | "$ref": "#/definitions/oauth2Scopes" 1427 | }, 1428 | "tokenUrl": { 1429 | "type": "string", 1430 | "format": "uri" 1431 | }, 1432 | "description": { 1433 | "type": "string" 1434 | } 1435 | }, 1436 | "patternProperties": { 1437 | "^x-": { 1438 | "$ref": "#/definitions/vendorExtension" 1439 | } 1440 | } 1441 | }, 1442 | "oauth2AccessCodeSecurity": { 1443 | "type": "object", 1444 | "additionalProperties": false, 1445 | "required": [ 1446 | "type", 1447 | "flow", 1448 | "authorizationUrl", 1449 | "tokenUrl" 1450 | ], 1451 | "properties": { 1452 | "type": { 1453 | "type": "string", 1454 | "enum": [ 1455 | "oauth2" 1456 | ] 1457 | }, 1458 | "flow": { 1459 | "type": "string", 1460 | "enum": [ 1461 | "accessCode" 1462 | ] 1463 | }, 1464 | "scopes": { 1465 | "$ref": "#/definitions/oauth2Scopes" 1466 | }, 1467 | "authorizationUrl": { 1468 | "type": "string", 1469 | "format": "uri" 1470 | }, 1471 | "tokenUrl": { 1472 | "type": "string", 1473 | "format": "uri" 1474 | }, 1475 | "description": { 1476 | "type": "string" 1477 | } 1478 | }, 1479 | "patternProperties": { 1480 | "^x-": { 1481 | "$ref": "#/definitions/vendorExtension" 1482 | } 1483 | } 1484 | }, 1485 | "oauth2Scopes": { 1486 | "type": "object", 1487 | "additionalProperties": { 1488 | "type": "string" 1489 | } 1490 | }, 1491 | "mediaTypeList": { 1492 | "type": "array", 1493 | "items": { 1494 | "$ref": "#/definitions/mimeType" 1495 | }, 1496 | "uniqueItems": true 1497 | }, 1498 | "parametersList": { 1499 | "type": "array", 1500 | "description": "The parameters needed to send a valid API call.", 1501 | "additionalItems": false, 1502 | "items": { 1503 | "oneOf": [ 1504 | { 1505 | "$ref": "#/definitions/parameter" 1506 | }, 1507 | { 1508 | "$ref": "#/definitions/jsonReference" 1509 | } 1510 | ] 1511 | }, 1512 | "uniqueItems": true 1513 | }, 1514 | "schemesList": { 1515 | "type": "array", 1516 | "description": "The transfer protocol of the API.", 1517 | "items": { 1518 | "type": "string", 1519 | "enum": [ 1520 | "http", 1521 | "https", 1522 | "ws", 1523 | "wss" 1524 | ] 1525 | }, 1526 | "uniqueItems": true 1527 | }, 1528 | "collectionFormat": { 1529 | "type": "string", 1530 | "enum": [ 1531 | "csv", 1532 | "ssv", 1533 | "tsv", 1534 | "pipes" 1535 | ], 1536 | "default": "csv" 1537 | }, 1538 | "collectionFormatWithMulti": { 1539 | "type": "string", 1540 | "enum": [ 1541 | "csv", 1542 | "ssv", 1543 | "tsv", 1544 | "pipes", 1545 | "multi" 1546 | ], 1547 | "default": "csv" 1548 | }, 1549 | "title": { 1550 | "$ref": "http://json-schema.org/draft-04/schema#/properties/title" 1551 | }, 1552 | "description": { 1553 | "$ref": "http://json-schema.org/draft-04/schema#/properties/description" 1554 | }, 1555 | "default": { 1556 | "$ref": "http://json-schema.org/draft-04/schema#/properties/default" 1557 | }, 1558 | "multipleOf": { 1559 | "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf" 1560 | }, 1561 | "maximum": { 1562 | "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum" 1563 | }, 1564 | "exclusiveMaximum": { 1565 | "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum" 1566 | }, 1567 | "minimum": { 1568 | "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum" 1569 | }, 1570 | "exclusiveMinimum": { 1571 | "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum" 1572 | }, 1573 | "maxLength": { 1574 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" 1575 | }, 1576 | "minLength": { 1577 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" 1578 | }, 1579 | "pattern": { 1580 | "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern" 1581 | }, 1582 | "maxItems": { 1583 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" 1584 | }, 1585 | "minItems": { 1586 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" 1587 | }, 1588 | "uniqueItems": { 1589 | "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems" 1590 | }, 1591 | "enum": { 1592 | "$ref": "http://json-schema.org/draft-04/schema#/properties/enum" 1593 | }, 1594 | "jsonReference": { 1595 | "type": "object", 1596 | "required": [ 1597 | "$ref" 1598 | ], 1599 | "additionalProperties": false, 1600 | "properties": { 1601 | "$ref": { 1602 | "type": "string" 1603 | } 1604 | } 1605 | } 1606 | } 1607 | } 1608 | --------------------------------------------------------------------------------