├── .gitignore ├── .gitmodules ├── .rubocop.yml ├── .travis.yml ├── Gemfile ├── Guardfile ├── Installation.md ├── LICENSE ├── README.md ├── Rakefile ├── Vagrantfile ├── lib ├── redsnow.rb └── redsnow │ ├── binding.rb │ ├── blueprint.rb │ ├── object.rb │ ├── parseresult.rb │ ├── sourcemap.rb │ └── version.rb ├── provisioning.sh ├── redsnow.gemspec └── test ├── .rubocop.yml ├── _helper.rb ├── example.rb ├── fixtures ├── sample-api-ast.json ├── sample-api-sourcemap.json └── sample-api.apib ├── redsnow_binding_test.rb ├── redsnow_options_test.rb ├── redsnow_parseresult_test.rb ├── redsnow_sourcemap_test.rb └── redsnow_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /lib/bundler/man/ 26 | 27 | # for a library or gem, you might want to ignore these files since the code is 28 | # intended to run in multiple environments; otherwise, check them in: 29 | Gemfile.lock 30 | .ruby-version 31 | .ruby-gemset 32 | 33 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 34 | .rvmrc 35 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/drafter"] 2 | path = ext/drafter 3 | url = https://github.com/apiaryio/drafter.git 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - 'ext/**/*' 4 | - 'test/fixtures/**/*' 5 | 6 | # Offense count: 283 7 | # Configuration parameters: AllowURI, URISchemes. 8 | Metrics/LineLength: 9 | Max: 300 10 | 11 | # Offense count: 17 12 | # Configuration parameters: CountComments. 13 | Metrics/MethodLength: 14 | Max: 36 15 | 16 | Metrics/AbcSize: 17 | Max: 42 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 1.9.3 5 | - 2.1.0 6 | - '2.2' 7 | script: 8 | - rake test 9 | - rake install 10 | - bundle exec rubocop lib test Rakefile redsnow.gemspec 11 | before_install: 12 | - git submodule update --init --recursive 13 | - gem update bundler 14 | - bundle 15 | env: RECOMPILE=true 16 | notifications: 17 | email: 18 | recipients: 19 | - ladislav@apiary.io 20 | on_success: change 21 | on_failure: always 22 | hipchat: 23 | rooms: 24 | secure: d1RJSY+QU7y01wmqvxpgYsAyRzNzbAK2r1Ut9X87s5UOFB+5yxUgRocs4sKpGSpm0UVtD0u/G3H1wqbmWpxnYpmQYilTw9Qa04GDwJJ2/IraHU1fpGG+gZCsOMid7NjNl6jErbY8cfoUmCQdrUiYBgLowtxh5i8HbPT//Cb7mhU= 25 | template: 26 | - ! '%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message} 27 | (Details/Change view)' 28 | format: html 29 | deploy: 30 | provider: rubygems 31 | api_key: 32 | secure: H1fybpyLM915pBlPwn6rtrZ+IBzRumTasMC/vuwcJarBTiqIvYW4XXveA4Y2FNkuo7amCjjwujLDKUImhBM0Yyk0d2WTDC6izSUyP9/+MraicnKYX/biTzW13re/sUPr/FXI4HupvZsjBcKYA6F/eidPf4QM4O2LZ89FpW23/GY= 33 | gem: redsnow 34 | on: 35 | all_branches: true 36 | tags: true 37 | rvm: 1.9.3 38 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in red_snow.gemspec 4 | gemspec 5 | 6 | # Lock dependencies for TravisCI builds using older Ruby versions 7 | gem 'activesupport', '< 5' 8 | gem 'listen', '< 3.1' 9 | gem 'rubocop', '~> 0.32.1' 10 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard :rubocop do 5 | watch(/.+\.rb$/) 6 | watch(/(?:.+\/)?\.rubocop\.yml$/) { |m| File.dirname(m[0]) } 7 | end 8 | -------------------------------------------------------------------------------- /Installation.md: -------------------------------------------------------------------------------- 1 | 2 | # RedSnow Installation 3 | 4 | For installing Ruby we recommend use [RVM](https://rvm.io/rvm/install). 5 | 6 | ## Ubuntu 14.04 ([Vagrantfile](Vagrantfile)) 7 | 8 | Install ruby and prerequisites 9 | 10 | # GCC 4.7 11 | sudo apt-get update -y 12 | sudo apt-get install -y python-software-properties 13 | sudo apt-get update -y 14 | sudo apt-get install -y build-essential git-core curl 15 | 16 | # RVM gpg key 17 | gpg --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3 18 | 19 | # RVM Install 20 | curl -sSL https://get.rvm.io | bash -s stable 21 | source ~/.rvm/scripts/rvm 22 | 23 | # Installing required packages: libreadline6-dev, zlib1g-dev, libssl-dev, libyaml-dev, libsqlite3-dev, sqlite3, autoconf, libgdbm-dev, libncurses5-dev, automake, libtool, bison, pkg-config, libffi-dev 24 | rvm install 2.1.5 25 | rvm ruby-2.1.5 26 | gem install bundler 27 | 28 | Install RedSnow 29 | 30 | gem install redsnow 31 | 32 | Run example for test 33 | 34 | $ ruby test/example.rb 35 | 36 | This should be the output 37 | 38 | My API 39 | 0 40 | 8 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Apiary 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 all 13 | 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.github.com/apiaryio/api-blueprint/master/assets/logo_apiblueprint.png) 2 | 3 | # RedSnow [![Build Status](https://travis-ci.org/apiaryio/redsnow.png?branch=master)](https://travis-ci.org/apiaryio/redsnow) 4 | ### API Blueprint Parser for Ruby 5 | 6 | **NOTE**: *This library is deprecated and unmaintained. We recommend using [drafter](https://github.com/apiaryio/drafter) directly via [libffi](https://github.com/ffi/ffi/wiki) [for example](https://github.com/apiaryio/drafter/pull/356#issuecomment-229733343).* 7 | 8 | Ruby binding for the [Snow Crash](https://github.com/apiaryio/snowcrash) library, also a thermonuclear weapon. 9 | 10 | API Blueprint is Web API documentation language. You can find API Blueprint documentation on the [API Blueprint site](http://apiblueprint.org). 11 | 12 | ## Install 13 | The best way to install RedSnow is by using its [GEM package](https://rubygems.org/gems/redsnow). 14 | 15 | gem install redsnow 16 | 17 | Installation instructions for Ubuntu 14.04 are described in detail [here](Installation.md). 18 | 19 | ## Documentation 20 | 21 | - [Documentation at rubydoc](http://rubydoc.info/gems/redsnow/) 22 | 23 | ## Getting started 24 | 25 | ```ruby 26 | require 'redsnow' 27 | 28 | result = RedSnow.parse('# My API', exportSourcemap: true) 29 | puts result.ast.name 30 | puts result.sourcemap.name 31 | ``` 32 | 33 | ## Parsing options 34 | 35 | Options can be number or hash. We support `:requireBlueprintName` and `:exportSourcemap` option. 36 | 37 | ```ruby 38 | require 'redsnow' 39 | 40 | result = RedSnow::parse('# My API', { :exportSourcemap => true }) 41 | puts result.ast.name 42 | puts result.sourcemap.name 43 | ``` 44 | 45 | ## Hacking Redsnow 46 | You are welcome to contribute. Use following steps to build & test Redsnow. 47 | 48 | ### Build 49 | 50 | 51 | 1. If needed, install bundler: 52 | 53 | ```sh 54 | $ gem install bundler 55 | ``` 56 | 57 | 2. Clone the repo + fetch the submodules: 58 | 59 | ```sh 60 | $ git clone git://github.com/apiaryio/redsnow.git 61 | $ cd redsnow 62 | $ git submodule update --init --recursive 63 | ``` 64 | 65 | 3. Build: 66 | 67 | ```sh 68 | $ rake 69 | ``` 70 | 71 | ### Test 72 | Inside the redsnow repository run: 73 | 74 | ```sh 75 | $ bundle install 76 | $ rake test 77 | ``` 78 | 79 | ### Release 80 | Use `rake install` to test locally released version. 81 | 82 | ```sh 83 | $ rake release 84 | ``` 85 | 86 | ### Contribute 87 | Fork & Pull Request. 88 | 89 | ## License 90 | MIT License. See the [LICENSE](https://github.com/apiaryio/protagonist/blob/master/LICENSE) file. 91 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'bundler/gem_tasks' 3 | rescue LoadError 4 | puts 'Cannot load bundler/gem_tasks' 5 | end 6 | 7 | begin 8 | require 'rake/testtask' 9 | rescue LoadError 10 | puts 'Cannot load rake/testtask' 11 | end 12 | 13 | begin 14 | require 'ffi' 15 | rescue LoadError 16 | puts 'Cannot load ffi' 17 | end 18 | 19 | begin 20 | require 'yard' 21 | rescue LoadError 22 | puts 'Cannot load yard' 23 | end 24 | 25 | task default: :compile 26 | 27 | desc 'Compile extension' 28 | task :compile do 29 | prefix = FFI::Platform.mac? ? '' : 'lib.target/' 30 | # Path to compiled drafter library 31 | path = File.expand_path("ext/drafter/build/out/Release/#{prefix}libdrafter.#{FFI::Platform::LIBSUFFIX}", File.dirname(__FILE__)) 32 | puts "Path to library #{path}" 33 | if !File.exist?(path) || ENV['RECOMPILE'] 34 | unless File.directory?(File.expand_path('ext/drafter/src')) 35 | puts 'Initializing submodules (if required)...' 36 | `git submodule update --init --recursive 2>/dev/null` 37 | end 38 | puts 'Compiling extension...' 39 | `cd #{File.expand_path('ext/drafter/')} && ./configure --shared && make` 40 | status = $CHILD_STATUS.to_i 41 | if status == 0 42 | puts 'Compiling done.' 43 | else 44 | puts 'Compiling error, exiting.' 45 | next # If i'm using exit, abort I have some errors in rake install but gem can be installed 46 | end 47 | else 48 | puts 'Extension already compiled. To recompile set env variable RECOMPILE=true.' 49 | end 50 | end 51 | 52 | desc 'Run tests' 53 | Rake::TestTask.new(:test) do |test| 54 | Rake::Task['compile'].invoke 55 | 56 | test.libs << 'lib' << 'test' 57 | test.test_files = FileList['test/*_test.rb'] 58 | test.verbose = true 59 | end 60 | 61 | # ----- Documentation tasks --------------------------------------------------- 62 | 63 | YARD::Rake::YardocTask.new(:doc) do |t| 64 | t.options = %w( --embed-mixins --markup=markdown ) 65 | end 66 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure('2') do |config| 2 | config.vm.box = 'ubuntu/trusty64' 3 | 4 | # VirtualBox 5 | config.vm.provider :virtualbox do |vb| 6 | vb.customize ['modifyvm', :id, '--memory', '2048'] 7 | vb.customize ['modifyvm', :id, '--cpus', '1'] 8 | end 9 | 10 | # VMWare Fusion 11 | config.vm.provider :vmware_fusion do |vb| 12 | vb.vmx['memsize'] = '4096' 13 | vb.vmx['numvcpus'] = '1' 14 | end 15 | 16 | config.vm.network :private_network, ip: '10.3.3.3' 17 | config.vm.synced_folder '.', '/vagrant', id: 'vagrant-root' 18 | config.vm.provision :shell, path: './provisioning.sh' 19 | end 20 | -------------------------------------------------------------------------------- /lib/redsnow.rb: -------------------------------------------------------------------------------- 1 | require 'redsnow/version' 2 | require 'redsnow/binding' 3 | require 'redsnow/blueprint' 4 | require 'redsnow/sourcemap' 5 | require 'redsnow/parseresult' 6 | require 'ffi' 7 | 8 | # RedSnow 9 | module RedSnow 10 | include Binding 11 | 12 | # Options 13 | EXPORT_SOURCEMAP_OPTION_KEY = :exportSourcemap 14 | REQUIRE_BLUEPRINT_NAME_OPTION_KEY = :requireBlueprintName 15 | 16 | # Parse options 17 | attr_accessor :options 18 | def self.parse_options(options) 19 | # Parse Options 20 | if options.is_a?(Numeric) 21 | return options 22 | else 23 | opt = 0 24 | opt |= (1 << 1) if options[REQUIRE_BLUEPRINT_NAME_OPTION_KEY] 25 | opt |= (1 << 2) if options[EXPORT_SOURCEMAP_OPTION_KEY] 26 | return opt 27 | end 28 | end 29 | 30 | # parse 31 | # parsing API Blueprint into Ruby objects 32 | # @param raw_blueprint [String] API Blueprint 33 | # @param options [Number] Parsing Options 34 | # 35 | # @return [ParseResult] 36 | def self.parse(raw_blueprint, options = 0) 37 | fail ArgumentError, 'Expected string value' unless raw_blueprint.is_a?(String) 38 | 39 | blueprint_options = parse_options(options) 40 | 41 | parse_result = FFI::MemoryPointer.new :pointer 42 | 43 | RedSnow::Binding.drafter_c_parse(raw_blueprint, blueprint_options, parse_result) 44 | 45 | parse_result = parse_result.get_pointer(0) 46 | 47 | ParseResult.new(parse_result.null? ? nil : parse_result.read_string) 48 | ensure 49 | 50 | RedSnow::Memory.free(parse_result) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/redsnow/binding.rb: -------------------------------------------------------------------------------- 1 | require 'ffi' 2 | 3 | module RedSnow 4 | # expose function free() to allow release memory allocated by C-interface 5 | module Memory 6 | extend FFI::Library 7 | ffi_lib FFI::Library::LIBC 8 | 9 | attach_function :free, [:pointer], :void 10 | end 11 | 12 | # C-binding with Snow Crash Library using [FFI](https://github.com/ffi/ffi) 13 | # @see https://github.com/apiaryio/drafter/blob/master/src/cdrafter.h 14 | module Binding 15 | extend FFI::Library 16 | 17 | prefix = FFI::Platform.mac? ? '' : 'lib.target/' 18 | 19 | ffi_lib File.expand_path("../../../ext/drafter/build/out/Release/#{prefix}libdrafter.#{FFI::Platform::LIBSUFFIX}", __FILE__) 20 | enum :option, [ 21 | :render_descriptions_option, 22 | :require_blueprint_name_option, 23 | :export_sourcemap_option 24 | ] 25 | 26 | attach_function('drafter_c_parse', 'drafter_c_parse', [:string, :option, :pointer], :int) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/redsnow/blueprint.rb: -------------------------------------------------------------------------------- 1 | require 'redsnow/object' 2 | 3 | # The classes in this module should be 1:1 with the Snow Crash AST 4 | # counterparts (https://github.com/apiaryio/snowcrash/blob/master/src/Blueprint.h). 5 | module RedSnow 6 | # Blueprint AST node 7 | # Base class for API Blueprint AST nodes 8 | # 9 | # @abstract 10 | class BlueprintNode 11 | end 12 | 13 | # Blueprint AST Reference node 14 | # 15 | # @attr id [String] identifier of the reference 16 | # 17 | # @abstract 18 | class ReferenceNode < BlueprintNode 19 | attr_accessor :id 20 | 21 | def initialize(id) 22 | @id = id 23 | end 24 | end 25 | 26 | # Blueprint AST node with name && description associated 27 | # 28 | # @attr name [String] name of the node 29 | # @attr description [String] description of the node 30 | # 31 | # @abstract 32 | class NamedBlueprintNode < BlueprintNode 33 | attr_accessor :name 34 | attr_accessor :description 35 | 36 | # Ensure the input string buffer ends with two newlines. 37 | # 38 | # @param buffer [String] a buffer to check 39 | # If the buffer does not ends with two newlines the newlines are added. 40 | def ensure_description_newlines(buffer) 41 | return if description.empty? 42 | 43 | if description[-1, 1] != '\n' 44 | buffer << '\n\n' 45 | elsif description.length > 1 && description[-2, 1] != '\n' 46 | buffer << '\n' 47 | end 48 | end 49 | end 50 | 51 | # Blueprint AST node for key-value collections 52 | # 53 | # @abstract 54 | # @attr collection [Array] array of key value hashes 55 | class KeyValueCollection < BlueprintNode 56 | attr_accessor :collection 57 | 58 | # Retrieves the value of the collection item by its key 59 | # 60 | # @param key [String] Name of the item key to retrieve 61 | # @return [NilClass] if the collection does not have an item with the key 62 | # @return [String] if the collection has an item with the key 63 | def [](key) 64 | return nil if @collection.nil? 65 | return_item_value key 66 | end 67 | # Filter collection keys 68 | # 69 | # @return [Array] collection without ignored keys 70 | def filter_collection(ignore_keys) 71 | return @collection if ignore_keys.blank? 72 | @collection.select { |kv_item| !ignore_keys.include?(kv_item.keys.first) } 73 | end 74 | 75 | private 76 | 77 | def return_item_value(key) 78 | item = get_item(key.to_s) 79 | item && item[:value] 80 | end 81 | 82 | def get_item(key) 83 | @collection.find { |item| item[:name].downcase == key.downcase } 84 | end 85 | end 86 | 87 | # Metadata collection Blueprint AST node 88 | # represents 'metadata section' 89 | class Metadata < KeyValueCollection 90 | # @param metadata [json] 91 | def initialize(metadata) 92 | return if metadata.nil? 93 | 94 | @collection = [] 95 | metadata.each do |item| 96 | @collection << Hash[name: item['name'], value: item.fetch('value', nil)] 97 | end 98 | end 99 | end 100 | 101 | # Headers collection Blueprint AST node 102 | # represents 'headers section' 103 | class Headers < KeyValueCollection 104 | # HTTP 'Content-Type' header 105 | CONTENT_TYPE_HEADER_KEY = :'Content-Type' 106 | 107 | # @return [String] the value of 'Content-type' header if present or nil 108 | def content_type 109 | content_type_header = @collection.find { |header| header.key?(CONTENT_TYPE_HEADER_KEY) } 110 | (content_type_header.nil?) ? nil : content_type_header[CONTENT_TYPE_HEADER_KEY] 111 | end 112 | 113 | # @param headers [json] 114 | def initialize(headers) 115 | @collection = [] 116 | 117 | return if headers.nil? 118 | 119 | headers.each do |item| 120 | @collection << Hash[name: item['name'], value: item['value']] 121 | end 122 | end 123 | end 124 | 125 | # URI parameter Blueprint AST node 126 | # represents one 'parameters section' parameter 127 | # 128 | # @attr type [String] an arbitrary type of the parameter or nil 129 | # @attr use [Symbol] parameter necessity flag, `:required`, `:optional` or :undefined 130 | # Where `:undefined` implies `:required` according to the API Blueprint Specification 131 | # @attr default_value [String] default value of the parameter or nil 132 | # This is a value used when the parameter is ommited in the request. 133 | # @attr example_value [String] example value of the parameter or nil 134 | # @attr values [Array] an enumeration of possible parameter values 135 | class Parameter < NamedBlueprintNode 136 | attr_accessor :type 137 | attr_accessor :use 138 | attr_accessor :default_value 139 | attr_accessor :example_value 140 | attr_accessor :values 141 | 142 | # @param parameter [json] 143 | def initialize(parameter) 144 | @name = parameter.fetch('name', '') 145 | @description = parameter.fetch('description', '') 146 | @type = parameter.fetch('type', '') 147 | 148 | case parameter['required'] 149 | when true 150 | @use = :required 151 | when false 152 | @use = :optional 153 | else 154 | @use = :undefined 155 | end 156 | 157 | @default_value = parameter.fetch('default', nil) 158 | @example_value = parameter.fetch('example', nil) 159 | 160 | @values = [] 161 | parameter.key?('values') && parameter['values'].each do |value| 162 | @values << value['value'] 163 | end 164 | end 165 | end 166 | 167 | # Collection of URI parameters Blueprint AST node 168 | # represents 'parameters section' 169 | # 170 | # @attr collection [Array] an array of URI parameters 171 | class Parameters < BlueprintNode 172 | attr_accessor :collection 173 | 174 | # @param parameters [json] 175 | def initialize(parameters) 176 | @collection = [] 177 | 178 | return if parameters.nil? 179 | 180 | parameters.each do |item| 181 | @collection << Parameter.new(item) 182 | end 183 | end 184 | end 185 | 186 | # HTTP message payload Blueprint AST node 187 | # base class for 'payload sections' 188 | # 189 | # @abstract 190 | # @attr parameters [Array] ignored 191 | # @attr headers [Headers] array of HTTP header fields of the message or nil 192 | # @attr body [String] HTTP-message body or nil 193 | # @attr schema [String] HTTP-message body validation schema or nil 194 | # @attr reference [Hash] Symbol Reference if the payload is a reference 195 | class Payload < NamedBlueprintNode 196 | attr_accessor :headers 197 | attr_accessor :body 198 | attr_accessor :schema 199 | attr_accessor :reference 200 | 201 | # @param payload [json] 202 | def initialize(payload) 203 | @name = payload.fetch('name', '') 204 | @description = payload.fetch('description', '') 205 | @body = payload.fetch('body', '') 206 | @schema = payload.fetch('schema', '') 207 | 208 | if payload.key?('reference') && payload['reference'].key?('id') 209 | @reference = ReferenceNode.new(payload['reference']['id']) 210 | end 211 | 212 | @headers = Headers.new(payload.fetch('headers', nil)) 213 | end 214 | end 215 | 216 | # Transaction example Blueprint AST node 217 | # 218 | # @attr requests [Array] example request payloads 219 | # @attr response [Array] example response payloads 220 | class TransactionExample < NamedBlueprintNode 221 | attr_accessor :requests 222 | attr_accessor :responses 223 | 224 | # @param example [json] 225 | def initialize(example) 226 | @name = example.fetch('name', '') 227 | @description = example.fetch('description', '') 228 | 229 | @requests = [] 230 | example.key?('requests') && example['requests'].each do |request| 231 | @requests << Payload.new(request).tap do |inst| 232 | example_instance = self 233 | inst.define_singleton_method(:example) { example_instance } 234 | end 235 | end 236 | 237 | @responses = [] 238 | example.key?('responses') && example['responses'].each do |response| 239 | @responses << Payload.new(response).tap do |inst| 240 | example_instance = self 241 | inst.define_singleton_method(:example) { example_instance } 242 | end 243 | end 244 | end 245 | end 246 | 247 | # Action Blueprint AST node 248 | # represetns 'action sction' 249 | # 250 | # @attr method [String] HTTP request method or nil 251 | # @attr parameters [Parameters] action-specific URI parameters or nil 252 | # @attr examples [Array] action transaction examples 253 | # @attr relation [String] action relation attribute 254 | # @attr uri_template [String] action uri template attribute 255 | class Action < NamedBlueprintNode 256 | attr_accessor :method 257 | attr_accessor :parameters 258 | attr_accessor :examples 259 | attr_accessor :relation 260 | attr_accessor :uri_template 261 | 262 | # @param action [json] 263 | def initialize(action) 264 | @name = action.fetch('name', '') 265 | @description = action.fetch('description', '') 266 | 267 | @method = action.fetch('method', '') 268 | 269 | @parameters = Parameters.new(action.fetch('parameters', nil)) 270 | 271 | if action.key?('attributes') 272 | @relation = action['attributes'].fetch('relation', '') 273 | @uri_template = action['attributes'].fetch('uriTemplate', '') 274 | end 275 | 276 | @examples = [] 277 | action.key?('examples') && action['examples'].each do |example| 278 | @examples << TransactionExample.new(example).tap do |inst| 279 | action_instance = self 280 | inst.define_singleton_method(:action) { action_instance } 281 | end 282 | end 283 | end 284 | end 285 | 286 | # Resource Blueprint AST node 287 | # represents 'resource section' 288 | # 289 | # @attr uri_template [String] RFC 6570 URI template 290 | # @attr model [Model] model payload for the resource or nil 291 | # @attr parameters [Parameters] action-specific URI parameters or nil 292 | # @attr actions [Array] array of resource actions or nil 293 | class Resource < NamedBlueprintNode 294 | attr_accessor :uri_template 295 | attr_accessor :model 296 | attr_accessor :parameters 297 | attr_accessor :actions 298 | 299 | # @param resource [json] 300 | def initialize(resource) 301 | @name = resource.fetch('name', '') 302 | @description = resource.fetch('description', '') 303 | @uri_template = resource.fetch('uriTemplate', '') 304 | 305 | @model = Payload.new(resource.fetch('model', nil)) 306 | 307 | @parameters = Parameters.new(resource.fetch('parameters', nil)) 308 | 309 | @actions = [] 310 | resource.key?('actions') && resource['actions'].each do |action| 311 | @actions << Action.new(action).tap do |inst| 312 | resource_instance = self 313 | inst.define_singleton_method(:resource) { resource_instance } 314 | end 315 | end 316 | end 317 | end 318 | 319 | # Resource group Blueprint AST node 320 | # represents 'resource group section' 321 | # 322 | # @attr resources [Array] array of resources in the group 323 | class ResourceGroup < NamedBlueprintNode 324 | attr_accessor :resources 325 | 326 | # @param resource_group [json] 327 | def initialize(resource_group) 328 | @name = resource_group.fetch('name', '') 329 | @description = resource_group.fetch('description', '') 330 | 331 | @resources = [] 332 | resource_group.key?('resources') && resource_group['resources'].each do |resource| 333 | @resources << Resource.new(resource).tap do |inst| 334 | resource_group_instance = self 335 | inst.define_singleton_method(:resource_group) { resource_group_instance } 336 | end 337 | end 338 | end 339 | end 340 | 341 | # Top-level Blueprint AST node 342 | # represents 'blueprint section' 343 | # 344 | # @attr metadata [Metadata] tool-specific metadata collection or nil 345 | # @attr resource_groups [Array] array of blueprint resource groups 346 | class Blueprint < NamedBlueprintNode 347 | attr_accessor :metadata 348 | attr_accessor :resource_groups 349 | 350 | # Version key 351 | VERSION_KEY = :_version 352 | 353 | # Supported version of Api Blueprint 354 | SUPPORTED_VERSIONS = ['3.0'] 355 | 356 | # @param ast [json] 357 | def initialize(ast) 358 | @name = ast.fetch('name', '') 359 | @description = ast.fetch('description', '') 360 | @metadata = Metadata.new(ast.fetch('metadata', nil)) 361 | 362 | @resource_groups = [] 363 | ast.key?('resourceGroups') && ast['resourceGroups'].each do |resource_group| 364 | @resource_groups << ResourceGroup.new(resource_group) 365 | end 366 | end 367 | end 368 | end 369 | -------------------------------------------------------------------------------- /lib/redsnow/object.rb: -------------------------------------------------------------------------------- 1 | # Essential extensions to base object class 2 | module RedSnow 3 | # Class from MatterCompiler as ascendant 4 | class Object 5 | # Symbolizes keys of a hash 6 | def deep_symbolize_keys 7 | return each_with_object({}) { |memo, (k, v)| memo[k.to_sym] = v.deep_symbolize_keys } if self.is_a?(Hash) 8 | return each_with_object([]) { |memo, v| memo << v.deep_symbolize_keys } if self.is_a?(Array) 9 | self 10 | end 11 | 12 | # Returns true if object is nil or empty, false otherwise 13 | def blank? 14 | respond_to?(:empty?) ? empty? : !self 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/redsnow/parseresult.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | module RedSnow 4 | # Parse Result 5 | # @see https://github.com/apiaryio/api-blueprint-ast/blob/master/Parse%20Result.md 6 | # @param ast [Blueprint] 7 | # @param error [Hash] Description of a parsing error as occurred during parsing. If this field is present && code different from 0 then the content of ast field should be ignored. 8 | # @param warnings [Array] Ordered array of parser warnings as occurred during the parsing. 9 | # @param sourcemap [BlueprintSourcemap] 10 | class ParseResult 11 | attr_accessor :ast 12 | attr_accessor :error 13 | attr_accessor :warnings 14 | attr_accessor :sourcemap 15 | 16 | # Version key 17 | VERSION_KEY = :_version 18 | 19 | # Supported version of Api Blueprint 20 | SUPPORTED_VERSIONS = ['2.1'] 21 | 22 | # @param parse_result [ string or nil ] 23 | def initialize(parse_result) 24 | parse_result = JSON.parse(parse_result) 25 | 26 | @ast = Blueprint.new(parse_result['ast']) 27 | @sourcemap = RedSnow::Sourcemap::Blueprint.new(parse_result['sourcemap']) 28 | 29 | @warnings = [] 30 | parse_result.key?('warnings') && parse_result['warnings'].each do |warning| 31 | @warnings << source_annotation(warning) 32 | end 33 | 34 | @error = source_annotation(parse_result['error']) 35 | end 36 | 37 | protected 38 | 39 | def source_annotation(json) 40 | annotation = {} 41 | 42 | annotation[:message] = json['message'] 43 | annotation[:code] = json['code'] 44 | annotation[:ok] = json.fetch('code', 0) 45 | 46 | annotation[:location] = [] 47 | json.key?('location') && json['location'].each do |location| 48 | annotation[:location] << Location.new(location) 49 | end 50 | 51 | annotation 52 | end 53 | end 54 | 55 | # Array of possibly non-continuous blocks of the source API Blueprint. 56 | # @param index [Number] Zero-based index of the character where warning has occurred. 57 | # @param length [Number] Number of the characters from index where warning has occurred. 58 | class Location 59 | attr_accessor :index 60 | attr_accessor :length 61 | 62 | # @param location [json] 63 | def initialize(location) 64 | @length = location['length'] 65 | @index = location['index'] 66 | end 67 | end 68 | 69 | # Warnning Codes 70 | # @see https://github.com/apiaryio/snowcrash/blob/master/src/SourceAnnotation.h#L128 71 | class WarningCodes 72 | NO_WARNING = 0 73 | API_NAME_WARNING = 1 74 | DUPLICATE_WARNING = 2 75 | FORMATTING_WARNING = 3 76 | REDEFINITION_WARNING = 4 77 | IGNORING_WARNING = 5 78 | EMPTY_DEFINITION_WARNING = 6 79 | NOT_EMPTY_DEFINITION_WARNING = 7 80 | LOGICAL_ERROR_WARNING = 8 81 | DEPRECATED_WARNING = 9 82 | INDENTATION_WARNING = 10 83 | AMBIGUITY_WARNING = 11 84 | URI_WARNING = 12 85 | end 86 | # Error Codes 87 | # @see https://github.com/apiaryio/snowcrash/blob/master/src/SourceAnnotation.h#L113 88 | class ErrorCodes 89 | NO_ERROR = 0 90 | APPLICATION_ERROR = 1 91 | BUSINESS_ERROR = 2 92 | SYMBOL_ERROR = 3 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/redsnow/sourcemap.rb: -------------------------------------------------------------------------------- 1 | require 'redsnow/object' 2 | 3 | # The classes in this module should be 1:1 with the Snow Crash Blueprint sourcemap 4 | # counterparts (https://github.com/apiaryio/snowcrash/blob/master/src/BlueprintSourcemap.h). 5 | module RedSnow 6 | module Sourcemap 7 | # Base Node - holds @collection 8 | class Node 9 | attr_accessor :collection 10 | 11 | # @param sourcemap [json array or nil] 12 | def initialize(sourcemap) 13 | @collection = [] 14 | 15 | return if sourcemap.nil? 16 | 17 | sourcemap.each do |sm| 18 | @collection << SourceMap.new(sm) 19 | end 20 | end 21 | end 22 | 23 | # SourceMap 24 | class SourceMap < Array 25 | # @param sourcemap [json array or nil] 26 | def initialize(sourcemap) 27 | return if sourcemap.nil? 28 | 29 | sourcemap.each do |position| 30 | self << position 31 | end 32 | end 33 | end 34 | 35 | # Blueprint sourcemap node with name && description associated 36 | # 37 | # @attr name [SourceMap] name of the node 38 | # @attr description [SourceMap] description of the node 39 | # 40 | # @abstract 41 | class NamedNode < Node 42 | attr_accessor :name 43 | attr_accessor :description 44 | 45 | # @param sourcemap [json array or nil] 46 | def initialize(sourcemap) 47 | return if sourcemap.nil? 48 | 49 | @name = SourceMap.new(sourcemap['name']) 50 | @description = SourceMap.new(sourcemap['description']) 51 | end 52 | end 53 | 54 | # Metadata source map collection node 55 | class Metadata < Node 56 | # @param sourcemap [json] 57 | def initialize(sourcemap) 58 | super(sourcemap) 59 | end 60 | end 61 | 62 | # Headers source map collection node 63 | class Headers < Node 64 | # @param sourcemap [json] 65 | def initialize(sourcemap) 66 | super(sourcemap) 67 | end 68 | end 69 | 70 | # Parameter source map node 71 | # 72 | # @attr type [Sourcemap] an arbitrary type of the parameter or nil 73 | # @attr use [Sourcemap] parameter necessity flag, `:required` or `:optional` 74 | # @attr default_value [Sourcemap] default value of the parameter or nil 75 | # @attr example_value [Sourcemap] example value of the parameter or nil 76 | # @attr values [Array] an enumeration of possible parameter values 77 | class Parameter < NamedNode 78 | attr_accessor :type 79 | attr_accessor :use 80 | attr_accessor :default_value 81 | attr_accessor :example_value 82 | attr_accessor :values 83 | 84 | # @param sourcemap [json] 85 | def initialize(sourcemap) 86 | super(sourcemap) 87 | 88 | @type = SourceMap.new(sourcemap['type']) 89 | @use = SourceMap.new(sourcemap['required']) 90 | @default_value = SourceMap.new(sourcemap['default']) 91 | @example_value = SourceMap.new(sourcemap['example']) 92 | @values = [] 93 | 94 | sourcemap.key?('values') && sourcemap['values'].each do |value| 95 | @values << SourceMap.new(value['value']) 96 | end 97 | end 98 | end 99 | 100 | # Parameters source map collection node 101 | # 102 | # @attr collection [Array] an array of URI parameters 103 | class Parameters < Node 104 | # @param sourcemap [json] 105 | def initialize(sourcemap) 106 | @collection = [] 107 | 108 | return if sourcemap.nil? 109 | 110 | sourcemap.each do |parameter| 111 | @collection << Parameter.new(parameter) 112 | end 113 | end 114 | end 115 | 116 | # Payload source map node 117 | # 118 | # @abstract 119 | # @attr parameters [Parameters] ignored 120 | # @attr headers [Headers] array of HTTP header fields of the message or nil 121 | # @attr body [Sourcemap] HTTP-message body or nil 122 | # @attr schema [Sourcemap] HTTP-message body validation schema or nil 123 | # @attr reference [Sourcemap] Symbol Reference sourcemap if the payload is a reference 124 | class Payload < NamedNode 125 | attr_accessor :headers 126 | attr_accessor :body 127 | attr_accessor :schema 128 | attr_accessor :reference 129 | 130 | # @param sourcemap [json] 131 | def initialize(sourcemap) 132 | return if sourcemap.nil? 133 | 134 | super(sourcemap) 135 | 136 | @body = SourceMap.new(sourcemap['body']) 137 | @schema = SourceMap.new(sourcemap['schema']) 138 | @reference = SourceMap.new(sourcemap['reference']) 139 | @headers = Headers.new(sourcemap['headers']) 140 | end 141 | end 142 | 143 | # Transaction example source map node 144 | # 145 | # @attr requests [Array] example request payloads 146 | # @attr response [Array] example response payloads 147 | class TransactionExample < NamedNode 148 | attr_accessor :requests 149 | attr_accessor :responses 150 | 151 | # @param sourcemap [json] 152 | def initialize(sourcemap) 153 | super(sourcemap) 154 | 155 | @requests = [] 156 | sourcemap.key?('requests') && sourcemap['requests'].each do |request| 157 | @requests << Payload.new(request) 158 | end 159 | 160 | @responses = [] 161 | sourcemap.key?('responses') && sourcemap['responses'].each do |response| 162 | @responses << Payload.new(response) 163 | end 164 | end 165 | end 166 | 167 | # Action source map node 168 | # 169 | # @attr method [Sourcemap] HTTP request method or nil 170 | # @attr parameters [Parameters] action-specific URI parameters or nil 171 | # @attr examples [Array] action transaction examples 172 | class Action < NamedNode 173 | attr_accessor :method 174 | attr_accessor :parameters 175 | attr_accessor :examples 176 | 177 | # @param sourcemap [json] 178 | def initialize(sourcemap) 179 | return if sourcemap.nil? 180 | 181 | super(sourcemap) 182 | 183 | @method = SourceMap.new(sourcemap['method']) 184 | @parameters = Parameters.new(sourcemap['parameters']) 185 | 186 | @examples = [] 187 | sourcemap.key?('examples') && sourcemap['examples'].each do |example| 188 | @examples << TransactionExample.new(example) 189 | end 190 | end 191 | end 192 | 193 | # Resource source map node 194 | # 195 | # @attr uri_template [Sourcemap] RFC 6570 URI template 196 | # @attr model [Payload] model payload for the resource or nil 197 | # @attr parameters [Parameters] action-specific URI parameters or nil 198 | # @attr actions [Array] array of resource actions or nil 199 | class Resource < NamedNode 200 | attr_accessor :uri_template 201 | attr_accessor :model 202 | attr_accessor :parameters 203 | attr_accessor :actions 204 | 205 | # @param sourcemap [json] 206 | def initialize(sourcemap) 207 | return if sourcemap.nil? 208 | 209 | super(sourcemap) 210 | @uri_template = SourceMap.new(sourcemap['uriTemplate']) 211 | @model = Payload.new(sourcemap['model']) 212 | @parameters = Parameters.new(sourcemap['parameters']) 213 | 214 | @actions = [] 215 | sourcemap.key?('actions') && sourcemap['actions'].each do |action| 216 | @actions << Action.new(action) 217 | end 218 | end 219 | end 220 | 221 | # Resource group source map node 222 | # 223 | # @attr resources [Array] array of resources in the group 224 | class ResourceGroup < NamedNode 225 | attr_accessor :resources 226 | 227 | # @param sourcemap [json] 228 | def initialize(sourcemap) 229 | super(sourcemap) 230 | 231 | @resources = [] 232 | sourcemap.key?('resources') && sourcemap['resources'].each do |resource| 233 | @resources << Resource.new(resource) 234 | end 235 | end 236 | end 237 | 238 | # Blueprint source map node 239 | # 240 | # @attr metadata [Metadata] tool-specific metadata collection or nil 241 | # @attr resource_groups [Array] array of resource groups 242 | class Blueprint < NamedNode 243 | attr_accessor :metadata 244 | attr_accessor :resource_groups 245 | 246 | # @param sourcemap [json] 247 | def initialize(sourcemap) 248 | return if sourcemap.nil? 249 | 250 | super(sourcemap) 251 | 252 | @metadata = Metadata.new(sourcemap['metadata']) 253 | @resource_groups = [] 254 | 255 | sourcemap.key?('resourceGroups') && sourcemap['resourceGroups'].each do |resource_group| 256 | @resource_groups << ResourceGroup.new(resource_group) 257 | end 258 | end 259 | end 260 | end 261 | end 262 | -------------------------------------------------------------------------------- /lib/redsnow/version.rb: -------------------------------------------------------------------------------- 1 | # Module RedSnow 2 | module RedSnow 3 | # Gem version 4 | VERSION = '0.4.4' 5 | end 6 | -------------------------------------------------------------------------------- /provisioning.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # GCC 4.7 4 | sudo apt-get update -y 5 | sudo apt-get install -y python-software-properties 6 | sudo apt-get update -y 7 | sudo apt-get install -y build-essential git-core curl mc vim 8 | 9 | # Ruby 10 | gem install bundler 11 | # RVM gpg key 12 | gpg --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3 13 | # RVM Install 14 | curl -sSL https://get.rvm.io | bash -s stable 15 | source /home/vagrant/.rvm/scripts/rvm 16 | # Installing required packages: libreadline6-dev, zlib1g-dev, libssl-dev, libyaml-dev, libsqlite3-dev, sqlite3, autoconf, libgdbm-dev, libncurses5-dev, automake, libtool, bison, pkg-config, libffi-dev 17 | rvm install 2.1.5 18 | rvm ruby-2.1.5 19 | gem install bundler 20 | -------------------------------------------------------------------------------- /redsnow.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'redsnow/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = 'redsnow' 8 | gem.version = RedSnow::VERSION 9 | gem.authors = ['Ladislav Prskavec'] 10 | gem.email = ['ladislav@apiary.io'] 11 | gem.description = 'Ruby bindings for Snow Crash' 12 | gem.summary = 'Ruby bindings for Snow Crash' 13 | gem.homepage = 'https://github.com/apiaryio/redsnow' 14 | gem.license = 'MIT' 15 | gem.files = Dir['lib/**/*'] 16 | gem.files << Dir['*'] 17 | gem.files << Dir['ext/drafter/**/*'].reject { |f| f =~ /cmdline|test|features|README*|LICENSE|Gemfile*|\.xcode*/ } 18 | gem.executables = gem.files.grep(/^bin/).map { |f| File.basename(f) } 19 | gem.test_files = gem.files.grep(/^(test|spec|features)/) 20 | gem.require_paths = %w(lib ext) 21 | 22 | gem.required_ruby_version = '>= 1.9.3' 23 | 24 | gem.extensions = %w(Rakefile) 25 | 26 | gem.add_dependency 'ffi', '~> 1.9.3' 27 | gem.add_dependency 'rake', '>= 10.3.2' 28 | gem.add_dependency 'bundler', '>= 1.7.0' 29 | gem.add_dependency 'yard', '~> 0.9.5' 30 | 31 | gem.add_development_dependency 'minitest' 32 | gem.add_development_dependency 'shoulda' 33 | gem.add_development_dependency 'mocha' 34 | gem.add_development_dependency 'unindent' 35 | gem.add_development_dependency 'rubocop' 36 | gem.add_development_dependency 'guard-rubocop' 37 | end 38 | -------------------------------------------------------------------------------- /test/.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: ../.rubocop.yml 2 | 3 | Lint/AmbiguousRegexpLiteral: 4 | Enabled: false 5 | 6 | # The documentation check doesn't make sense for test code 7 | Style/Documentation: 8 | Enabled: false 9 | 10 | # Configuration parameters: CountComments. 11 | Metrics/ClassLength: 12 | Max: 300 13 | -------------------------------------------------------------------------------- /test/_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | 3 | require 'minitest/autorun' 4 | require 'shoulda' 5 | require 'mocha' 6 | require File.join(File.expand_path('../../lib/redsnow.rb', __FILE__)) 7 | # Test Helper 8 | class TestHelper < Minitest::Test 9 | def fixture_file(name) 10 | File.read File.expand_path("../fixtures/#{name}", __FILE__) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/example.rb: -------------------------------------------------------------------------------- 1 | require 'redsnow' 2 | 3 | result = RedSnow.parse('# My API', exportSourcemap: true) 4 | puts result.ast.name 5 | puts result.sourcemap.name 6 | -------------------------------------------------------------------------------- /test/fixtures/sample-api-ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "_version": "3.0", 3 | "metadata": [ 4 | { 5 | "name": "FORMAT", 6 | "value": "1A" 7 | } 8 | ], 9 | "name": "", 10 | "description": "", 11 | "resourceGroups": [ 12 | { 13 | "name": "Messages", 14 | "description": "", 15 | "resources": [ 16 | { 17 | "name": "Message", 18 | "description": "", 19 | "uriTemplate": "/messages/{id}", 20 | "model": {}, 21 | "parameters": [ 22 | { 23 | "name": "id", 24 | "description": "Id of a message", 25 | "type": "", 26 | "required": true, 27 | "default": "", 28 | "example": "", 29 | "values": [ 30 | { 31 | "value": "1" 32 | }, 33 | { 34 | "value": "2" 35 | }, 36 | { 37 | "value": "3" 38 | } 39 | ] 40 | } 41 | ], 42 | "actions": [ 43 | { 44 | "name": "Retrieve Message", 45 | "description": "", 46 | "method": "GET", 47 | "parameters": [], 48 | "examples": [ 49 | { 50 | "name": "", 51 | "description": "", 52 | "requests": [], 53 | "responses": [ 54 | { 55 | "name": "200", 56 | "description": "", 57 | "headers": [ 58 | { 59 | "name": "Content-Type", 60 | "value": "text/plain" 61 | } 62 | ], 63 | "body": "Hello World!\n", 64 | "schema": "" 65 | } 66 | ] 67 | } 68 | ] 69 | }, 70 | { 71 | "name": "Delete Message", 72 | "description": "", 73 | "method": "DELETE", 74 | "parameters": [], 75 | "examples": [ 76 | { 77 | "name": "", 78 | "description": "", 79 | "requests": [], 80 | "responses": [ 81 | { 82 | "name": "204", 83 | "description": "", 84 | "headers": [], 85 | "body": "", 86 | "schema": "" 87 | } 88 | ] 89 | } 90 | ] 91 | } 92 | ] 93 | } 94 | ] 95 | } 96 | ] 97 | } 98 | -------------------------------------------------------------------------------- /test/fixtures/sample-api-sourcemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | [ 4 | [ 5 | 0, 6 | 12 7 | ] 8 | ] 9 | ], 10 | "name": [], 11 | "description": [], 12 | "resourceGroups": [ 13 | { 14 | "name": [ 15 | [ 16 | 12, 17 | 18 18 | ] 19 | ], 20 | "description": [], 21 | "resources": [ 22 | { 23 | "name": [ 24 | [ 25 | 30, 26 | 28 27 | ] 28 | ], 29 | "description": [], 30 | "uriTemplate": [ 31 | [ 32 | 30, 33 | 28 34 | ] 35 | ], 36 | "model": {}, 37 | "parameters": [ 38 | { 39 | "name": [ 40 | [ 41 | 77, 42 | 23 43 | ] 44 | ], 45 | "description": [ 46 | [ 47 | 77, 48 | 23 49 | ] 50 | ], 51 | "type": [], 52 | "required": [], 53 | "default": [], 54 | "example": [], 55 | "values": [ 56 | [ 57 | [ 58 | 129, 59 | 6 60 | ] 61 | ], 62 | [ 63 | [ 64 | 147, 65 | 6 66 | ] 67 | ], 68 | [ 69 | [ 70 | 165, 71 | 6 72 | ] 73 | ] 74 | ] 75 | } 76 | ], 77 | "actions": [ 78 | { 79 | "name": [ 80 | [ 81 | 172, 82 | 26 83 | ] 84 | ], 85 | "description": [], 86 | "method": [ 87 | [ 88 | 172, 89 | 26 90 | ] 91 | ], 92 | "parameters": [], 93 | "examples": [ 94 | { 95 | "name": [], 96 | "description": [], 97 | "requests": [], 98 | "responses": [ 99 | { 100 | "name": [ 101 | [ 102 | 200, 103 | 27 104 | ] 105 | ], 106 | "description": [], 107 | "headers": [ 108 | [ 109 | [ 110 | 200, 111 | 27 112 | ] 113 | ] 114 | ], 115 | "body": [ 116 | [ 117 | 231, 118 | 17 119 | ] 120 | ], 121 | "schema": [] 122 | } 123 | ] 124 | } 125 | ] 126 | }, 127 | { 128 | "name": [ 129 | [ 130 | 249, 131 | 27 132 | ] 133 | ], 134 | "description": [], 135 | "method": [ 136 | [ 137 | 249, 138 | 27 139 | ] 140 | ], 141 | "parameters": [], 142 | "examples": [ 143 | { 144 | "name": [], 145 | "description": [], 146 | "requests": [], 147 | "responses": [ 148 | { 149 | "name": [ 150 | [ 151 | 278, 152 | 13 153 | ] 154 | ], 155 | "description": [], 156 | "headers": [], 157 | "body": [], 158 | "schema": [] 159 | } 160 | ] 161 | } 162 | ] 163 | } 164 | ] 165 | } 166 | ] 167 | } 168 | ] 169 | } 170 | -------------------------------------------------------------------------------- /test/fixtures/sample-api.apib: -------------------------------------------------------------------------------- 1 | FORMAT: 1A 2 | 3 | # Group Messages 4 | 5 | # Message [/messages/{id}] 6 | 7 | + Parameters 8 | + id ... Id of a message 9 | + Values 10 | + `1` 11 | + `2` 12 | + `3` 13 | 14 | ## Retrieve Message [GET] 15 | + Response 200 (text/plain) 16 | 17 | Hello World! 18 | 19 | ## Delete Message [DELETE] 20 | + Response 204 21 | -------------------------------------------------------------------------------- /test/redsnow_binding_test.rb: -------------------------------------------------------------------------------- 1 | require '_helper' 2 | require 'json' 3 | # RedSnowBindingTest 4 | class RedSnowBindingTest < Minitest::Test 5 | context 'RedSnow Binding' do 6 | should 'convert API Blueprint to AST' do 7 | parse_result = FFI::MemoryPointer.new :pointer 8 | 9 | RedSnow::Binding.drafter_c_parse("meta: data\nfoo:bar\n#XXXX\ndescription for it", 4, parse_result) 10 | 11 | parse_result = parse_result.get_pointer(0) 12 | assert !parse_result.null? 13 | 14 | parse_result_as_string = parse_result.null? ? nil : parse_result.read_string 15 | refute_nil parse_result_as_string 16 | 17 | parsed = JSON.parse(parse_result_as_string) 18 | 19 | assert_equal 'XXXX', parsed['ast']['name'] 20 | assert_equal 'description for it', parsed['ast']['description'] 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/redsnow_options_test.rb: -------------------------------------------------------------------------------- 1 | require '_helper' 2 | # RedSnowOptionsTest 3 | class RedSnowOptionsTest < Minitest::Test 4 | context 'Test arguments' do 5 | context 'Arguments' do 6 | should "raise error if first parameter isn't String" do 7 | exception = assert_raises(ArgumentError) { RedSnow.parse(1) } 8 | assert_equal('Expected string value', exception.message) 9 | end 10 | 11 | should 'get option for sourcemaps' do 12 | options = RedSnow.parse_options(exportSourcemap: true) 13 | assert_equal 4, options 14 | end 15 | 16 | should 'get option for required Blueprint name' do 17 | options = RedSnow.parse_options(requireBlueprintName: true) 18 | assert_equal 2, options 19 | end 20 | 21 | should 'get option for required Blueprint name and sourcemaps' do 22 | options = RedSnow.parse_options(requireBlueprintName: true, exportSourcemap: true) 23 | assert_equal 6, options 24 | end 25 | 26 | should 'get option for required Blueprint name and not sourcemaps' do 27 | options = RedSnow.parse_options(requireBlueprintName: true, exportSourcemap: false) 28 | assert_equal 2, options 29 | end 30 | 31 | should 'no options' do 32 | options = RedSnow.parse_options(0) 33 | assert_equal 0, options 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/redsnow_parseresult_test.rb: -------------------------------------------------------------------------------- 1 | require '_helper' 2 | require 'unindent' 3 | # RedSnowParseResultTest 4 | class RedSnowParseResultTest < Minitest::Test 5 | context 'Simple API' do 6 | setup do 7 | @result = RedSnow.parse('# My API', 4) 8 | end 9 | 10 | should 'have name' do 11 | assert_equal 'My API', @result.ast.name 12 | end 13 | 14 | should "don't have error" do 15 | assert_equal 0, @result.error[:ok] 16 | end 17 | end 18 | 19 | context 'Simple API with warning' do 20 | setup do 21 | @source = <<-STR 22 | FORMAT: 1A 23 | # My API 24 | ## GET / 25 | STR 26 | @result = RedSnow.parse(@source.unindent, 4) 27 | end 28 | 29 | should 'have name' do 30 | assert_equal 'My API', @result.ast.name 31 | end 32 | 33 | should "don't have error" do 34 | assert_equal 0, @result.error[:code] 35 | end 36 | 37 | should 'have source map for api name' do 38 | assert_equal [[11, 9]], @result.sourcemap.name 39 | end 40 | 41 | should 'have some warning' do 42 | assert_equal RedSnow::WarningCodes::EMPTY_DEFINITION_WARNING, @result.warnings[0][:code] 43 | assert_equal 'action is missing a response', @result.warnings[0][:message] 44 | 45 | assert_equal 20, @result.warnings[0][:location][0].index 46 | assert_equal 9, @result.warnings[0][:location][0].length 47 | 48 | assert_equal "## GET /\n", @source.unindent[20..@source.unindent.length] 49 | # Line in blueprint 50 | assert_equal 3, @source.unindent[0..20].lines.count 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/redsnow_sourcemap_test.rb: -------------------------------------------------------------------------------- 1 | require '_helper' 2 | require 'unindent' 3 | # RedSnowParsingTest 4 | class RedSnowSourcemapTest < Minitest::Test 5 | context 'API Blueprint parser' do 6 | context 'API' do 7 | setup do 8 | @result = RedSnow.parse('# My API', 4) 9 | end 10 | 11 | should 'have name' do 12 | assert_equal [[0, 8]], @result.sourcemap.name 13 | end 14 | end 15 | 16 | context 'API' do 17 | setup do 18 | source = <<-STR 19 | **description** 20 | STR 21 | 22 | @result = RedSnow.parse(source.unindent, 4) 23 | end 24 | 25 | should 'have description' do 26 | assert_equal [], @result.sourcemap.name 27 | assert_equal [[0, 16]], @result.sourcemap.description 28 | end 29 | end 30 | 31 | context 'Group' do 32 | setup do 33 | source = <<-STR 34 | # Group Name 35 | _description_ 36 | 37 | ## My Resource [/resource] 38 | Resource description 39 | 40 | ## My Alternative Resource [/alternative_resource] 41 | Alternative resource description 42 | 43 | STR 44 | 45 | @result = RedSnow.parse(source.unindent, 4) 46 | @sourcemap = @result.sourcemap 47 | end 48 | 49 | should 'have resource group' do 50 | assert_equal 1, @sourcemap.resource_groups.count 51 | assert_equal 2, @sourcemap.resource_groups[0].resources.count 52 | end 53 | 54 | should 'have resource' do 55 | assert_equal [[28, 27]], @sourcemap.resource_groups[0].resources[0].name 56 | assert_equal [[28, 27]], @sourcemap.resource_groups[0].resources[0].uri_template 57 | end 58 | 59 | should 'have alternative resource' do 60 | assert_equal [[77, 51]], @sourcemap.resource_groups[0].resources[1].name 61 | end 62 | end 63 | 64 | context 'Resource' do 65 | setup do 66 | source = <<-STR 67 | # My Resource [/resource] 68 | Resource description 69 | 70 | + Model (text/plain) 71 | 72 | Hello World 73 | 74 | ## Retrieve Resource [GET] 75 | Method description 76 | 77 | + Response 200 (text/plain) 78 | 79 | Response description 80 | 81 | + Headers 82 | 83 | X-Response-Header: Fighter 84 | 85 | + Body 86 | 87 | Y.T. 88 | 89 | + Schema 90 | 91 | Kourier 92 | 93 | ## Delete Resource [DELETE] 94 | 95 | + Response 200 96 | 97 | [My Resource][] 98 | 99 | STR 100 | 101 | @result = RedSnow.parse(source.unindent, 4) 102 | @resource_group = @result.sourcemap.resource_groups[0] 103 | @resource = @resource_group.resources[0] 104 | end 105 | 106 | should 'have resource group' do 107 | assert_equal [], @resource_group.name 108 | assert_equal 1, @resource_group.resources.count 109 | end 110 | 111 | should 'have resource' do 112 | assert_equal [[0, 26]], @resource.uri_template 113 | assert_equal [[26, 22]], @resource.description 114 | end 115 | 116 | should 'have resource model' do 117 | assert_equal [[0, 26]], @resource.model.name 118 | assert_equal [[74, 16]], @resource.model.body 119 | assert_equal 1, @resource.model.headers.collection.count 120 | assert_equal [[50, 20]], @resource.model.headers.collection[0] 121 | end 122 | 123 | should 'have actions' do 124 | assert_equal 2, @resource.actions.count 125 | assert_equal [[91, 27]], @resource.actions[0].method 126 | assert_equal [[91, 27]], @resource.actions[0].name 127 | end 128 | 129 | should 'have reference' do 130 | assert_equal [[354, 16]], @resource.actions[1].examples[0].responses[0].reference 131 | end 132 | end 133 | 134 | context 'Action' do 135 | setup do 136 | source = <<-STR 137 | # My Resource [/resource] 138 | Resource description 139 | 140 | ## Retrieve Resource [GET] 141 | Method description 142 | 143 | + Parameters 144 | + limit = `20` (optional, number, `42`) ... This is a limit 145 | 146 | + Response 202 147 | 148 | + Response 203 149 | 150 | STR 151 | 152 | @result = RedSnow.parse(source.unindent, 4) 153 | @resource_group = @result.sourcemap.resource_groups[0] 154 | @resource = @resource_group.resources[0] 155 | @action = @resource.actions[0] 156 | @parameter = @action.parameters.collection.first 157 | end 158 | 159 | should 'have 1 example' do 160 | assert_equal 1, @action.examples.count 161 | end 162 | 163 | should 'have the right parameters' do 164 | assert_equal [[118, 58]], @parameter.name 165 | assert_equal [[118, 58]], @parameter.type 166 | assert_equal [[118, 58]], @parameter.use 167 | assert_equal [[118, 58]], @parameter.default_value 168 | assert_equal [[118, 58]], @parameter.example_value 169 | assert_equal 0, @parameter.values.count 170 | end 171 | end 172 | 173 | context 'parses resource parameters' do 174 | setup do 175 | source = <<-STR 176 | # /machine{?limit} 177 | 178 | + Parameters 179 | + limit = `20` (optional, number, `42`) ... This is a limit 180 | + Values 181 | + `20` 182 | + `42` 183 | + `53` 184 | 185 | ## GET 186 | 187 | + Response 204 188 | STR 189 | 190 | @result = RedSnow.parse(source.unindent, 4) 191 | @resource_group = @result.sourcemap.resource_groups[0] 192 | @resource = @resource_group.resources[0] 193 | @parameter = @resource.parameters.collection[0] 194 | @values = @parameter.values 195 | end 196 | 197 | should 'have parameters' do 198 | assert_equal [[39, 58]], @parameter.name 199 | assert_equal [[39, 58]], @parameter.description 200 | assert_equal [[39, 58]], @parameter.type 201 | assert_equal [[39, 58]], @parameter.use 202 | assert_equal [[39, 58]], @parameter.default_value 203 | assert_equal [[39, 58]], @parameter.example_value 204 | assert_equal 3, @parameter.values.count 205 | 206 | assert_equal [[122, 7]], @values[0] 207 | assert_equal [[139, 7]], @values[1] 208 | assert_equal [[156, 7]], @values[2] 209 | end 210 | end 211 | 212 | context 'parses multiple transactions' do 213 | setup do 214 | source = <<-STR 215 | ## Notes Collection [/notes?id={id}&testingGroup={testtingGroup}&collection={collection}] 216 | ### Create a Note [POST] 217 | + Request Create a note 218 | 219 | + Headers 220 | 221 | Content-Type: application/json 222 | Prefer: creating 223 | 224 | + Body 225 | 226 | { "title": "Buy cheese and bread for breakfast." } 227 | 228 | + Response 201 (application/json) 229 | 230 | { "id": 3, "title": "Buy cheese and bread for breakfast." } 231 | 232 | + Request Unable to create note 233 | 234 | + Headers 235 | 236 | Content-Type: application/json 237 | Prefer: testing 238 | 239 | 240 | + Body 241 | 242 | { "ti": "Buy cheese and bread for breakfast." } 243 | 244 | + Response 500 245 | 246 | { "error": "can't create record" } 247 | STR 248 | @result = RedSnow.parse(source.unindent, 4) 249 | @resource_group = @result.sourcemap.resource_groups[0] 250 | @action = @resource_group.resources[0].actions[0] 251 | @examples = @action.examples 252 | end 253 | 254 | should 'have multiple requests and responses' do 255 | assert_equal 2, @examples.count 256 | assert_equal 1, @examples[0].requests.count 257 | assert_equal 1, @examples[0].responses.count 258 | 259 | assert_equal 2, @examples[0].requests[0].headers.collection.count 260 | 261 | assert_equal 1, @examples[1].requests.count 262 | assert_equal [[549, 52]], @examples[1].requests[0].body 263 | end 264 | end 265 | end 266 | end 267 | -------------------------------------------------------------------------------- /test/redsnow_test.rb: -------------------------------------------------------------------------------- 1 | require '_helper' 2 | require 'unindent' 3 | 4 | # RedSnowParsingTest 5 | class RedSnowParsingAPITest < Minitest::Test 6 | # https://github.com/apiaryio/protagonist/blob/master/test/parser-test.coffee 7 | context 'API' do 8 | setup do 9 | @result = RedSnow.parse('# My API') 10 | end 11 | 12 | should 'have name' do 13 | assert_equal 'My API', @result.ast.name 14 | end 15 | end 16 | 17 | context 'API' do 18 | setup do 19 | source = <<-STR 20 | **description** 21 | STR 22 | 23 | @result = RedSnow.parse(source.unindent) 24 | end 25 | 26 | should 'have description' do 27 | assert_equal '', @result.ast.name 28 | assert_equal "**description**\n", @result.ast.description 29 | end 30 | end 31 | end 32 | 33 | class RedSnowParsingGroupTest < Minitest::Test 34 | context 'Group' do 35 | setup do 36 | source = <<-STR 37 | # Group Name 38 | _description_ 39 | 40 | ## My Resource [/resource] 41 | Resource description 42 | 43 | ## My Alternative Resource [/alternative_resource] 44 | Alternative resource description 45 | 46 | STR 47 | 48 | @result = RedSnow.parse(source.unindent) 49 | @resource_group = @result.ast.resource_groups[0] 50 | @resource = @resource_group.resources[0] 51 | @alternative_resource = @resource_group.resources[1] 52 | end 53 | 54 | should 'have resource group' do 55 | assert_equal 1, @result.ast.resource_groups.count 56 | assert_equal 'Name', @resource_group.name 57 | assert_equal "_description_\n\n", @resource_group.description 58 | end 59 | 60 | should 'have resource' do 61 | assert_equal '/resource', @resource.uri_template 62 | assert_equal 'My Resource', @resource.name 63 | assert_equal "Resource description\n\n", @resource.description 64 | end 65 | 66 | should 'have alternative resource' do 67 | assert_equal '/alternative_resource', @alternative_resource.uri_template 68 | assert_equal 'My Alternative Resource', @alternative_resource.name 69 | assert_equal "Alternative resource description\n\n", @alternative_resource.description 70 | end 71 | end 72 | end 73 | 74 | class RedSnowParsingResourceTest < Minitest::Test 75 | context 'Resource' do 76 | setup do 77 | source = <<-STR 78 | # My Resource [/resource] 79 | Resource description 80 | 81 | + Model (text/plain) 82 | 83 | Hello World 84 | 85 | ## Retrieve Resource [GET] 86 | Method description 87 | 88 | + Response 200 (text/plain) 89 | 90 | Response description 91 | 92 | + Headers 93 | 94 | X-Response-Header: Fighter 95 | 96 | + Body 97 | 98 | Y.T. 99 | 100 | + Schema 101 | 102 | Kourier 103 | 104 | ## Delete Resource [DELETE] 105 | 106 | + Response 200 107 | 108 | [My Resource][] 109 | 110 | STR 111 | 112 | @result = RedSnow.parse(source.unindent) 113 | @resource_group = @result.ast.resource_groups[0] 114 | @resource = @resource_group.resources[0] 115 | @action = @resource.actions[0] 116 | @response = @resource.actions[1].examples[0].responses[0] 117 | end 118 | 119 | should 'have resource group' do 120 | assert_equal 1, @result.ast.resource_groups.count 121 | assert_equal '', @resource_group.name 122 | assert_equal '', @resource_group.description 123 | assert_equal 1, @resource_group.resources.count 124 | end 125 | 126 | should 'have resource' do 127 | assert_equal '/resource', @resource.uri_template 128 | assert_equal 'My Resource', @resource.name 129 | assert_equal "Resource description\n\n", @resource.description 130 | end 131 | 132 | should 'have resource model' do 133 | assert_equal 'My Resource', @resource.model.name 134 | assert_equal '', @resource.model.description 135 | assert_equal "Hello World\n", @resource.model.body 136 | assert_equal 1, @resource.model.headers.collection.count 137 | assert_equal 'Content-Type', @resource.model.headers.collection[0][:name] 138 | assert_equal 'text/plain', @resource.model.headers['content-type'] 139 | assert_equal 'text/plain', @resource.model.headers['Content-Type'] 140 | end 141 | 142 | should 'have a getter to its resource_group' do 143 | assert_equal @resource.resource_group, @resource_group 144 | end 145 | 146 | should 'have actions' do 147 | assert_equal 2, @resource.actions.count 148 | assert_equal 'GET', @action.method 149 | assert_equal 'Retrieve Resource', @action.name 150 | assert_equal "Method description\n\n", @action.description 151 | end 152 | 153 | should 'have reference' do 154 | assert_equal 'My Resource', @response.reference.id 155 | end 156 | end 157 | end 158 | 159 | class RedSnowParsingActionTest < Minitest::Test 160 | context 'Action' do 161 | setup do 162 | source = <<-STR 163 | # My Resource [/resource] 164 | Resource description 165 | 166 | ## Retrieve Resource [GET] 167 | Method description 168 | 169 | + Parameters 170 | + limit = `20` (optional, number, `42`) ... This is a limit 171 | 172 | + Response 202 173 | 174 | + Response 203 175 | 176 | STR 177 | 178 | @result = RedSnow.parse(source.unindent) 179 | @resource_group = @result.ast.resource_groups[0] 180 | @resource = @resource_group.resources[0] 181 | @action = @resource.actions[0] 182 | @parameter = @action.parameters.collection.first 183 | end 184 | 185 | should 'have a name' do 186 | assert_equal 'Retrieve Resource', @action.name 187 | end 188 | 189 | should 'have a description' do 190 | assert_equal "Method description\n\n", @action.description 191 | end 192 | 193 | should 'have a method' do 194 | assert_equal 'GET', @action.method 195 | end 196 | 197 | should 'have 1 example' do 198 | assert_equal 1, @action.examples.count 199 | end 200 | 201 | should 'have the right parameters' do 202 | assert_equal 'limit', @parameter.name 203 | end 204 | 205 | should 'have a resource' do 206 | assert_equal @resource, @action.resource 207 | end 208 | end 209 | end 210 | 211 | class RedSnowParsingMetadataTest < Minitest::Test 212 | context 'parses blueprint metadata' do 213 | setup do 214 | source = <<-STR 215 | FORMAT: 1A 216 | A: 1 217 | B: 2 218 | C: 3 219 | 220 | # API Name 221 | STR 222 | 223 | @result = RedSnow.parse(source.unindent) 224 | @metadata = @result.ast.metadata 225 | @collection = @metadata.collection 226 | end 227 | 228 | should 'have metadata' do 229 | assert_equal 'FORMAT', @collection[0][:name] 230 | assert_equal '1A', @collection[0][:value] 231 | 232 | assert_equal 'A', @collection[1][:name] 233 | assert_equal '1', @collection[1][:value] 234 | 235 | assert_equal 'B', @collection[2][:name] 236 | assert_equal '2', @collection[2][:value] 237 | 238 | assert_equal 'C', @collection[3][:name] 239 | assert_equal '3', @collection[3][:value] 240 | end 241 | 242 | should 'return metadata values by element reference method' do 243 | assert_equal '1A', @metadata['FORMAT'] 244 | assert_equal '1', @metadata['A'] 245 | assert_equal '2', @metadata['B'] 246 | assert_equal '3', @metadata['C'] 247 | assert_equal nil, @metadata['D'] 248 | end 249 | end 250 | end 251 | 252 | class RedSnowParsingParametersTest < Minitest::Test 253 | context 'parses action attributes' do 254 | setup do 255 | source = <<-STR 256 | # /machine{?limit} 257 | 258 | + Parameters 259 | + limit = `20` (optional, number, `42`) ... This is a limit 260 | + Values 261 | + `20` 262 | + `42` 263 | + `53` 264 | 265 | ## GET 266 | 267 | + Response 204 268 | STR 269 | 270 | @result = RedSnow.parse(source.unindent) 271 | @resource_group = @result.ast.resource_groups[0] 272 | @resource = @resource_group.resources[0] 273 | @parameter = @resource.parameters.collection[0] 274 | @values = @parameter.values 275 | end 276 | 277 | should 'have parameters' do 278 | assert_equal 'limit', @parameter.name 279 | assert_equal 'This is a limit', @parameter.description 280 | assert_equal 'number', @parameter.type 281 | assert_equal :optional, @parameter.use 282 | assert_equal '20', @parameter.default_value 283 | assert_equal '42', @parameter.example_value 284 | assert_equal 3, @parameter.values.count 285 | 286 | assert_equal '20', @values[0] 287 | assert_equal '42', @values[1] 288 | assert_equal '53', @values[2] 289 | end 290 | end 291 | 292 | context 'parses action parameters and attributes' do 293 | setup do 294 | source = <<-STR 295 | # GET /coupons/{id} 296 | 297 | + Relation: coupon 298 | 299 | + Parameters 300 | + id (number, `1001`) ... Id of coupon 301 | 302 | + Response 204 303 | STR 304 | 305 | @result = RedSnow.parse(source.unindent) 306 | @resource_group = @result.ast.resource_groups[0] 307 | @resource = @resource_group.resources[0] 308 | @action = @resource.actions[0] 309 | @parameter = @action.parameters.collection[0] 310 | end 311 | 312 | should 'have parameters' do 313 | assert_equal 'id', @parameter.name 314 | assert_equal :required, @parameter.use 315 | assert_equal 'coupon', @action.relation 316 | assert_equal '', @action.uri_template 317 | end 318 | end 319 | 320 | context 'parses resource parameters' do 321 | setup do 322 | source = <<-STR 323 | # /machine{?limit} 324 | 325 | + Parameters 326 | + limit = `20` (optional, number, `42`) ... This is a limit 327 | + Values 328 | + `20` 329 | + `42` 330 | + `53` 331 | 332 | ## GET 333 | 334 | + Response 204 335 | STR 336 | 337 | @result = RedSnow.parse(source.unindent) 338 | @resource_group = @result.ast.resource_groups[0] 339 | @resource = @resource_group.resources[0] 340 | @parameter = @resource.parameters.collection[0] 341 | @values = @parameter.values 342 | end 343 | 344 | should 'have parameters' do 345 | assert_equal 'limit', @parameter.name 346 | assert_equal 'This is a limit', @parameter.description 347 | assert_equal 'number', @parameter.type 348 | assert_equal :optional, @parameter.use 349 | assert_equal '20', @parameter.default_value 350 | assert_equal '42', @parameter.example_value 351 | assert_equal 3, @parameter.values.count 352 | 353 | assert_equal '20', @values[0] 354 | assert_equal '42', @values[1] 355 | assert_equal '53', @values[2] 356 | end 357 | end 358 | 359 | context 'parses action parameters' do 360 | setup do 361 | source = <<-STR 362 | # GET /coupons/{id} 363 | 364 | + Parameters 365 | + id (number, `1001`) ... Id of coupon 366 | 367 | + Response 204 368 | STR 369 | 370 | @result = RedSnow.parse(source.unindent) 371 | @resource_group = @result.ast.resource_groups[0] 372 | @resource = @resource_group.resources[0] 373 | @action = @resource.actions[0] 374 | @parameter = @action.parameters.collection[0] 375 | end 376 | 377 | should 'have parameters' do 378 | assert_equal 'id', @parameter.name 379 | assert_equal :required, @parameter.use 380 | end 381 | end 382 | end 383 | 384 | class RedSnowParsingMultipleTransactionsTest < Minitest::Test 385 | context 'parses multiple transactions' do 386 | setup do 387 | source = <<-STR 388 | ## Notes Collection [/notes?id={id}&testingGroup={testtingGroup}&collection={collection}] 389 | ### Create a Note [POST] 390 | + Request Create a note 391 | 392 | + Headers 393 | 394 | Content-Type: application/json 395 | Prefer: creating 396 | 397 | + Body 398 | 399 | { "title": "Buy cheese and bread for breakfast." } 400 | 401 | + Response 201 (application/json) 402 | 403 | { "id": 3, "title": "Buy cheese and bread for breakfast." } 404 | 405 | + Request Unable to create note 406 | 407 | + Headers 408 | 409 | Content-Type: application/json 410 | Prefer: testing 411 | 412 | 413 | + Body 414 | 415 | { "ti": "Buy cheese and bread for breakfast." } 416 | 417 | + Response 500 418 | 419 | { "error": "can't create record" } 420 | STR 421 | @result = RedSnow.parse(source.unindent) 422 | @resource_group = @result.ast.resource_groups[0] 423 | @action = @resource_group.resources[0].actions[0] 424 | @examples = @action.examples 425 | end 426 | 427 | should 'have multiple requests and responses' do 428 | assert_equal 2, @examples.count 429 | assert_equal 1, @examples[0].requests.count 430 | assert_equal 1, @examples[0].responses.count 431 | assert_equal '', @examples[0].name 432 | assert_equal '', @examples[0].description 433 | assert_equal 'Create a note', @examples[0].requests[0].name 434 | assert_equal 'Content-Type', @examples[0].requests[0].headers.collection[0][:name] 435 | assert_equal 'application/json', @examples[0].requests[0].headers.collection[0][:value] 436 | assert_equal 'Prefer', @examples[0].requests[0].headers.collection[1][:name] 437 | assert_equal 'creating', @examples[0].requests[0].headers.collection[1][:value] 438 | assert_equal '201', @examples[0].responses[0].name 439 | assert_equal @action, @examples[0].action 440 | assert_equal @examples[0], @examples[0].responses[0].example 441 | assert_equal @examples[0], @examples[0].requests[0].example 442 | assert_equal 'Unable to create note', @examples[1].requests[0].name 443 | assert_equal 'Content-Type', @examples[1].requests[0].headers.collection[0][:name] 444 | assert_equal 'application/json', @examples[1].requests[0].headers['content-type'] 445 | assert_equal 'Prefer', @examples[1].requests[0].headers.collection[1][:name] 446 | assert_equal 'testing', @examples[1].requests[0].headers['prefer'] 447 | assert_equal '{ "ti": "Buy cheese and bread for breakfast." }' + "\n", @examples[1].requests[0].body 448 | assert_equal '500', @examples[1].responses[0].name 449 | assert_equal @action, @examples[1].action 450 | assert_equal @examples[1], @examples[1].responses[0].example 451 | assert_equal @examples[1], @examples[1].requests[0].example 452 | end 453 | end 454 | end 455 | --------------------------------------------------------------------------------