committee 1.15.0 → 2.0.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/bin/committee-stub +10 -36
 - data/lib/committee/bin/committee_stub.rb +61 -0
 - data/lib/committee/drivers/hyper_schema.rb +151 -0
 - data/lib/committee/drivers/open_api_2.rb +297 -0
 - data/lib/committee/drivers.rb +57 -0
 - data/lib/committee/middleware/base.rb +52 -13
 - data/lib/committee/middleware/request_validation.rb +33 -10
 - data/lib/committee/middleware/response_validation.rb +2 -1
 - data/lib/committee/middleware/stub.rb +24 -8
 - data/lib/committee/request_validator.rb +1 -1
 - data/lib/committee/response_generator.rb +58 -13
 - data/lib/committee/response_validator.rb +32 -8
 - data/lib/committee/router.rb +5 -33
 - data/lib/committee/{query_params_coercer.rb → string_params_coercer.rb} +11 -6
 - data/lib/committee/test/methods.rb +49 -12
 - data/lib/committee.rb +15 -1
 - data/test/bin/committee_stub_test.rb +45 -0
 - data/test/bin_test.rb +20 -0
 - data/test/committee_test.rb +49 -0
 - data/test/drivers/hyper_schema_test.rb +95 -0
 - data/test/drivers/open_api_2_test.rb +255 -0
 - data/test/drivers_test.rb +60 -0
 - data/test/middleware/base_test.rb +49 -5
 - data/test/middleware/request_validation_test.rb +39 -25
 - data/test/middleware/response_validation_test.rb +32 -20
 - data/test/middleware/stub_test.rb +50 -19
 - data/test/request_unpacker_test.rb +10 -0
 - data/test/request_validator_test.rb +4 -3
 - data/test/response_generator_test.rb +50 -6
 - data/test/response_validator_test.rb +29 -4
 - data/test/router_test.rb +40 -13
 - data/test/{query_params_coercer_test.rb → string_params_coercer_test.rb} +3 -4
 - data/test/test/methods_test.rb +44 -5
 - data/test/test_helper.rb +59 -1
 - metadata +62 -10
 
| 
         @@ -6,19 +6,12 @@ module Committee::Middleware 
     | 
|
| 
       6 
6 
     | 
    
         
             
                  @error_class = options.fetch(:error_class, Committee::ValidationError)
         
     | 
| 
       7 
7 
     | 
    
         
             
                  @params_key = options[:params_key] || "committee.params"
         
     | 
| 
       8 
8 
     | 
    
         
             
                  @raise = options[:raise]
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @schema = get_schema(options[:schema] ||
         
     | 
| 
      
 10 
     | 
    
         
            +
                    raise(ArgumentError, "Committee: need option `schema`"))
         
     | 
| 
       9 
11 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
                   
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
                    schema = JSON.parse(schema)
         
     | 
| 
       14 
     | 
    
         
            -
                  end
         
     | 
| 
       15 
     | 
    
         
            -
                  if schema.is_a?(Hash)
         
     | 
| 
       16 
     | 
    
         
            -
                    schema = JsonSchema.parse!(schema)
         
     | 
| 
       17 
     | 
    
         
            -
                    schema.expand_references!
         
     | 
| 
       18 
     | 
    
         
            -
                  end
         
     | 
| 
       19 
     | 
    
         
            -
                  @schema = schema
         
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
                  @router = Committee::Router.new(@schema, options)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @router = Committee::Router.new(@schema,
         
     | 
| 
      
 13 
     | 
    
         
            +
                    prefix: options[:prefix]
         
     | 
| 
      
 14 
     | 
    
         
            +
                  )
         
     | 
| 
       22 
15 
     | 
    
         
             
                end
         
     | 
| 
       23 
16 
     | 
    
         | 
| 
       24 
17 
     | 
    
         
             
                def call(env)
         
     | 
| 
         @@ -33,8 +26,54 @@ module Committee::Middleware 
     | 
|
| 
       33 
26 
     | 
    
         | 
| 
       34 
27 
     | 
    
         
             
                private
         
     | 
| 
       35 
28 
     | 
    
         | 
| 
      
 29 
     | 
    
         
            +
                # For modern use of the library a schema should be an instance of
         
     | 
| 
      
 30 
     | 
    
         
            +
                # Committee::Drivers::Schema so that we know that all the computationally
         
     | 
| 
      
 31 
     | 
    
         
            +
                # difficult parsing is already done by the time we try to handle any
         
     | 
| 
      
 32 
     | 
    
         
            +
                # request.
         
     | 
| 
      
 33 
     | 
    
         
            +
                #
         
     | 
| 
      
 34 
     | 
    
         
            +
                # However, for reasons of backwards compatibility we also allow schema
         
     | 
| 
      
 35 
     | 
    
         
            +
                # input to be a string, a data hash, or a JsonSchema::Schema. In the former
         
     | 
| 
      
 36 
     | 
    
         
            +
                # two cases we just parse as if we were sent hyper-schema. In the latter,
         
     | 
| 
      
 37 
     | 
    
         
            +
                # we have the hyper-schema driver wrap it in a new Committee object.
         
     | 
| 
      
 38 
     | 
    
         
            +
                def get_schema(schema)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  # These are in a separately conditional ladder so that we only show the
         
     | 
| 
      
 40 
     | 
    
         
            +
                  # user one warning.
         
     | 
| 
      
 41 
     | 
    
         
            +
                  if schema.is_a?(String)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    warn_string_deprecated
         
     | 
| 
      
 43 
     | 
    
         
            +
                  elsif schema.is_a?(Hash)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    warn_hash_deprecated
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  if schema.is_a?(String)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    schema = JSON.parse(schema)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  if schema.is_a?(Hash) || schema.is_a?(JsonSchema::Schema)
         
     | 
| 
      
 52 
     | 
    
         
            +
                    driver = Committee::Drivers::HyperSchema.new
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                    # The driver itself has its own special cases to be able to parse
         
     | 
| 
      
 55 
     | 
    
         
            +
                    # either a hash or JsonSchema::Schema object.
         
     | 
| 
      
 56 
     | 
    
         
            +
                    schema = driver.parse(schema)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  # Expect the type we want by now. If we don't have it, the user passed
         
     | 
| 
      
 60 
     | 
    
         
            +
                  # something else non-standard in.
         
     | 
| 
      
 61 
     | 
    
         
            +
                  if !schema.is_a?(Committee::Drivers::Schema)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    raise ArgumentError, "Committee: schema expected to be a hash or " \
         
     | 
| 
      
 63 
     | 
    
         
            +
                      "an instance of Committee::Drivers::Schema."
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                  schema
         
     | 
| 
      
 67 
     | 
    
         
            +
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                def warn_hash_deprecated
         
     | 
| 
      
 70 
     | 
    
         
            +
                  Committee.warn_deprecated("Committee: passing a hash to schema " \
         
     | 
| 
      
 71 
     | 
    
         
            +
                    "option is deprecated; please send a driver object instead.")
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
       36 
74 
     | 
    
         
             
                def warn_string_deprecated
         
     | 
| 
       37 
     | 
    
         
            -
                  Committee.warn_deprecated("Committee: passing a string to  
     | 
| 
      
 75 
     | 
    
         
            +
                  Committee.warn_deprecated("Committee: passing a string to schema " \
         
     | 
| 
      
 76 
     | 
    
         
            +
                    "option is deprecated; please send a driver object instead.")
         
     | 
| 
       38 
77 
     | 
    
         
             
                end
         
     | 
| 
       39 
78 
     | 
    
         
             
              end
         
     | 
| 
       40 
79 
     | 
    
         
             
            end
         
     | 
| 
         @@ -2,27 +2,48 @@ module Committee::Middleware 
     | 
|
| 
       2 
2 
     | 
    
         
             
              class RequestValidation < Base
         
     | 
| 
       3 
3 
     | 
    
         
             
                def initialize(app, options={})
         
     | 
| 
       4 
4 
     | 
    
         
             
                  super
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
       5 
6 
     | 
    
         
             
                  @allow_form_params   = options.fetch(:allow_form_params, true)
         
     | 
| 
       6 
7 
     | 
    
         
             
                  @allow_query_params  = options.fetch(:allow_query_params, true)
         
     | 
| 
       7 
8 
     | 
    
         
             
                  @check_content_type  = options.fetch(:check_content_type, true)
         
     | 
| 
       8 
9 
     | 
    
         
             
                  @optimistic_json     = options.fetch(:optimistic_json, false)
         
     | 
| 
       9 
     | 
    
         
            -
                  @coerce_query_params = options.fetch(:coerce_query_params, false)
         
     | 
| 
       10 
10 
     | 
    
         
             
                  @strict              = options[:strict]
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
      
 12 
     | 
    
         
            +
                  @coerce_path_params = options.fetch(:coerce_path_params,
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @schema.driver.default_path_params)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @coerce_query_params = options.fetch(:coerce_query_params,
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @schema.driver.default_query_params)
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
       12 
17 
     | 
    
         
             
                  # deprecated
         
     | 
| 
       13 
18 
     | 
    
         
             
                  @allow_extra = options[:allow_extra]
         
     | 
| 
       14 
19 
     | 
    
         
             
                end
         
     | 
| 
       15 
20 
     | 
    
         | 
| 
       16 
21 
     | 
    
         
             
                def handle(request)
         
     | 
| 
       17 
     | 
    
         
            -
                  link = @router.find_request_link(request)
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
                       
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
      
 22 
     | 
    
         
            +
                  link, param_matches = @router.find_request_link(request)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  path_params = {}
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  if link
         
     | 
| 
      
 26 
     | 
    
         
            +
                    # Attempts to coerce parameters that appear in a link's URL to Ruby
         
     | 
| 
      
 27 
     | 
    
         
            +
                    # types that can be validated with a schema.
         
     | 
| 
      
 28 
     | 
    
         
            +
                    if @coerce_path_params
         
     | 
| 
      
 29 
     | 
    
         
            +
                      path_params = param_matches.merge(
         
     | 
| 
      
 30 
     | 
    
         
            +
                        Committee::StringParamsCoercer.new(
         
     | 
| 
      
 31 
     | 
    
         
            +
                          param_matches,
         
     | 
| 
      
 32 
     | 
    
         
            +
                          link.schema
         
     | 
| 
      
 33 
     | 
    
         
            +
                        ).call
         
     | 
| 
      
 34 
     | 
    
         
            +
                      )
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                    # Attempts to coerce parameters that appear in a query string to Ruby
         
     | 
| 
      
 38 
     | 
    
         
            +
                    # types that can be validated with a schema.
         
     | 
| 
      
 39 
     | 
    
         
            +
                    if @coerce_query_params && !request.GET.nil? && !link.schema.nil?
         
     | 
| 
      
 40 
     | 
    
         
            +
                      request.env["rack.request.query_hash"].merge!(
         
     | 
| 
      
 41 
     | 
    
         
            +
                        Committee::StringParamsCoercer.new(
         
     | 
| 
      
 42 
     | 
    
         
            +
                          request.GET,
         
     | 
| 
      
 43 
     | 
    
         
            +
                          link.schema
         
     | 
| 
      
 44 
     | 
    
         
            +
                        ).call
         
     | 
| 
      
 45 
     | 
    
         
            +
                      )
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
       26 
47 
     | 
    
         
             
                  end
         
     | 
| 
       27 
48 
     | 
    
         | 
| 
       28 
49 
     | 
    
         
             
                  request.env[@params_key] = Committee::RequestUnpacker.new(
         
     | 
| 
         @@ -32,6 +53,8 @@ module Committee::Middleware 
     | 
|
| 
       32 
53 
     | 
    
         
             
                    optimistic_json:    @optimistic_json
         
     | 
| 
       33 
54 
     | 
    
         
             
                  ).call
         
     | 
| 
       34 
55 
     | 
    
         | 
| 
      
 56 
     | 
    
         
            +
                  request.env[@params_key].merge!(path_params)
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
       35 
58 
     | 
    
         
             
                  if link
         
     | 
| 
       36 
59 
     | 
    
         
             
                    validator = Committee::RequestValidator.new(link, check_content_type: @check_content_type)
         
     | 
| 
       37 
60 
     | 
    
         
             
                    validator.call(request, request.env[@params_key])
         
     | 
| 
         @@ -10,7 +10,8 @@ module Committee::Middleware 
     | 
|
| 
       10 
10 
     | 
    
         
             
                def handle(request)
         
     | 
| 
       11 
11 
     | 
    
         
             
                  status, headers, response = @app.call(request.env)
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
                   
     | 
| 
      
 13 
     | 
    
         
            +
                  link, _ = @router.find_request_link(request)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  if validate?(status) && link
         
     | 
| 
       14 
15 
     | 
    
         
             
                    full_body = ""
         
     | 
| 
       15 
16 
     | 
    
         
             
                    response.each do |chunk|
         
     | 
| 
       16 
17 
     | 
    
         
             
                      full_body << chunk
         
     | 
| 
         @@ -2,16 +2,27 @@ module Committee::Middleware 
     | 
|
| 
       2 
2 
     | 
    
         
             
              class Stub < Base
         
     | 
| 
       3 
3 
     | 
    
         
             
                def initialize(app, options={})
         
     | 
| 
       4 
4 
     | 
    
         
             
                  super
         
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
                   
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  # A bug in Committee's cache implementation meant that it wasn't working
         
     | 
| 
      
 7 
     | 
    
         
            +
                  # for a very long time, even for people who thought they were taking
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # advantage of it. I repaired the caching feature, but have disable it by
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # default so that we don't need to introduce any class-level variables
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # that could have memory leaking implications. To enable caching, just
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # pass an empty hash to this option.
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @cache = options[:cache]
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  @call = options[:call]
         
     | 
| 
       7 
15 
     | 
    
         
             
                end
         
     | 
| 
       8 
16 
     | 
    
         | 
| 
       9 
17 
     | 
    
         
             
                def handle(request)
         
     | 
| 
       10 
     | 
    
         
            -
                   
     | 
| 
      
 18 
     | 
    
         
            +
                  link, _ = @router.find_request_link(request)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  if link
         
     | 
| 
       11 
20 
     | 
    
         
             
                    headers = { "Content-Type" => "application/json" }
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    data = cache(link) do
         
     | 
| 
       13 
23 
     | 
    
         
             
                      Committee::ResponseGenerator.new.call(link)
         
     | 
| 
       14 
24 
     | 
    
         
             
                    end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
       15 
26 
     | 
    
         
             
                    if @call
         
     | 
| 
       16 
27 
     | 
    
         
             
                      request.env["committee.response"] = data
         
     | 
| 
       17 
28 
     | 
    
         
             
                      call_status, call_headers, call_body = @app.call(request.env)
         
     | 
| 
         @@ -29,8 +40,8 @@ module Committee::Middleware 
     | 
|
| 
       29 
40 
     | 
    
         
             
                      # will be the same one that we set above)
         
     | 
| 
       30 
41 
     | 
    
         
             
                      data = request.env["committee.response"]
         
     | 
| 
       31 
42 
     | 
    
         
             
                    end
         
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
                    [ 
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                    [link.status_success, headers, [JSON.pretty_generate(data)]]
         
     | 
| 
       34 
45 
     | 
    
         
             
                  else
         
     | 
| 
       35 
46 
     | 
    
         
             
                    @app.call(request.env)
         
     | 
| 
       36 
47 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -38,8 +49,13 @@ module Committee::Middleware 
     | 
|
| 
       38 
49 
     | 
    
         | 
| 
       39 
50 
     | 
    
         
             
                private
         
     | 
| 
       40 
51 
     | 
    
         | 
| 
       41 
     | 
    
         
            -
                def cache( 
     | 
| 
       42 
     | 
    
         
            -
                   
     | 
| 
      
 52 
     | 
    
         
            +
                def cache(link)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  return yield unless @cache
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  # Just the object ID is enough to uniquely identify the link, but store
         
     | 
| 
      
 56 
     | 
    
         
            +
                  # the method and href so that we can more easily introspect the cache if
         
     | 
| 
      
 57 
     | 
    
         
            +
                  # necessary.
         
     | 
| 
      
 58 
     | 
    
         
            +
                  key = "#{link.object_id}##{link.method}+#{link.href}"
         
     | 
| 
       43 
59 
     | 
    
         
             
                  if @cache[key]
         
     | 
| 
       44 
60 
     | 
    
         
             
                    @cache[key]
         
     | 
| 
       45 
61 
     | 
    
         
             
                  else
         
     | 
| 
         @@ -24,7 +24,7 @@ module Committee 
     | 
|
| 
       24 
24 
     | 
    
         | 
| 
       25 
25 
     | 
    
         
             
                def check_content_type!(request, data)
         
     | 
| 
       26 
26 
     | 
    
         
             
                  content_type = request_media_type(request)
         
     | 
| 
       27 
     | 
    
         
            -
                  if content_type && !empty_request?(request)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  if content_type && @link.enc_type && !empty_request?(request)
         
     | 
| 
       28 
28 
     | 
    
         
             
                    unless Rack::Mime.match?(content_type, @link.enc_type)
         
     | 
| 
       29 
29 
     | 
    
         
             
                      raise Committee::InvalidRequest,
         
     | 
| 
       30 
30 
     | 
    
         
             
                        %{"Content-Type" request header must be set to "#{@link.enc_type}".}
         
     | 
| 
         @@ -1,35 +1,80 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Committee
         
     | 
| 
       2 
2 
     | 
    
         
             
              class ResponseGenerator
         
     | 
| 
       3 
3 
     | 
    
         
             
                def call(link)
         
     | 
| 
       4 
     | 
    
         
            -
                  data = generate_properties(link 
     | 
| 
      
 4 
     | 
    
         
            +
                  data = generate_properties(link, target_schema(link))
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
                  #  
     | 
| 
       7 
     | 
    
         
            -
                   
     | 
| 
      
 6 
     | 
    
         
            +
                  # List is a special case; wrap data in an array.
         
     | 
| 
      
 7 
     | 
    
         
            +
                  #
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # This is poor form that's here so as not to introduce breaking behavior.
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # The "instances" value of "rel" is a Heroku-ism and was originally
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # introduced before we understood how to use "targetSchema". It's not
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # meaningful with the context of the hyper-schema specification and
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # should be eventually be removed.
         
     | 
| 
      
 13 
     | 
    
         
            +
                  if legacy_hyper_schema_rel?(link)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    data = [data]
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
       8 
16 
     | 
    
         | 
| 
       9 
17 
     | 
    
         
             
                  data
         
     | 
| 
       10 
18 
     | 
    
         
             
                end
         
     | 
| 
       11 
19 
     | 
    
         | 
| 
       12 
20 
     | 
    
         
             
                private
         
     | 
| 
       13 
21 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
                 
     | 
| 
      
 22 
     | 
    
         
            +
                # These are basic types that are part of the JSON schema for which we'll
         
     | 
| 
      
 23 
     | 
    
         
            +
                # emit zero values when generating a response. For a schema that allows
         
     | 
| 
      
 24 
     | 
    
         
            +
                # multiple of the types in the list, types are preferred in the order in
         
     | 
| 
      
 25 
     | 
    
         
            +
                # which they're defined.
         
     | 
| 
      
 26 
     | 
    
         
            +
                SCALAR_TYPES = {
         
     | 
| 
      
 27 
     | 
    
         
            +
                  "boolean" => false,
         
     | 
| 
      
 28 
     | 
    
         
            +
                  "integer" => 0,
         
     | 
| 
      
 29 
     | 
    
         
            +
                  "number"  => 0.0,
         
     | 
| 
      
 30 
     | 
    
         
            +
                  "string"  => "",
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  # Prefer null last.
         
     | 
| 
      
 33 
     | 
    
         
            +
                  "null" => nil,
         
     | 
| 
      
 34 
     | 
    
         
            +
                }.freeze
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                def generate_properties(link, schema)
         
     | 
| 
       15 
37 
     | 
    
         
             
                  # special example attribute was included; use its value
         
     | 
| 
       16 
     | 
    
         
            -
                  if !schema.data["example"].nil?
         
     | 
| 
      
 38 
     | 
    
         
            +
                  if schema.data && !schema.data["example"].nil?
         
     | 
| 
       17 
39 
     | 
    
         
             
                    schema.data["example"]
         
     | 
| 
       18 
     | 
    
         
            -
                   
     | 
| 
       19 
     | 
    
         
            -
                  elsif schema.type.include?("null")
         
     | 
| 
       20 
     | 
    
         
            -
                    nil
         
     | 
| 
       21 
     | 
    
         
            -
                  elsif schema.type.include?("array") && !schema.items.nil?
         
     | 
| 
       22 
     | 
    
         
            -
                    [generate_properties(schema.items)]
         
     | 
| 
       23 
     | 
    
         
            -
                  elsif !schema.properties.empty?
         
     | 
| 
      
 40 
     | 
    
         
            +
                  elsif !schema.all_of.empty? || !schema.properties.empty?
         
     | 
| 
       24 
41 
     | 
    
         
             
                    data = {}
         
     | 
| 
      
 42 
     | 
    
         
            +
                    schema.all_of.each do |subschema|
         
     | 
| 
      
 43 
     | 
    
         
            +
                      data.merge!(generate_properties(link, subschema))
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
       25 
45 
     | 
    
         
             
                    schema.properties.map do |key, value|
         
     | 
| 
       26 
     | 
    
         
            -
                      data[key] = generate_properties(value)
         
     | 
| 
      
 46 
     | 
    
         
            +
                      data[key] = generate_properties(link, value)
         
     | 
| 
       27 
47 
     | 
    
         
             
                    end
         
     | 
| 
       28 
48 
     | 
    
         
             
                    data
         
     | 
| 
      
 49 
     | 
    
         
            +
                  elsif schema.type.include?("array") && !schema.items.nil?
         
     | 
| 
      
 50 
     | 
    
         
            +
                    [generate_properties(link, schema.items)]
         
     | 
| 
      
 51 
     | 
    
         
            +
                  elsif schema.type.any? { |t| SCALAR_TYPES.include?(t) }
         
     | 
| 
      
 52 
     | 
    
         
            +
                    SCALAR_TYPES.each do |k, v|
         
     | 
| 
      
 53 
     | 
    
         
            +
                      break(v) if schema.type.include?(k)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
       29 
55 
     | 
    
         
             
                  else
         
     | 
| 
       30 
     | 
    
         
            -
                    raise(%{At "#{schema.pointer}": no  
     | 
| 
      
 56 
     | 
    
         
            +
                    raise(%{At "#{link.method} #{link.href}" "#{schema.pointer}": no } +
         
     | 
| 
      
 57 
     | 
    
         
            +
                      %{"example" attribute and "null" } +
         
     | 
| 
       31 
58 
     | 
    
         
             
                      %{is not allowed; don't know how to generate property.})
         
     | 
| 
       32 
59 
     | 
    
         
             
                  end
         
     | 
| 
       33 
60 
     | 
    
         
             
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def legacy_hyper_schema_rel?(link)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  link.is_a?(Committee::Drivers::HyperSchema::Link) &&
         
     | 
| 
      
 64 
     | 
    
         
            +
                    link.rel == "instances" &&
         
     | 
| 
      
 65 
     | 
    
         
            +
                    !link.target_schema
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                # Gets the target schema of a link. This is normally just the standard
         
     | 
| 
      
 69 
     | 
    
         
            +
                # response schema, but we allow some legacy behavior for hyper-schema links
         
     | 
| 
      
 70 
     | 
    
         
            +
                # tagged with rel=instances to instead use the schema of their parent
         
     | 
| 
      
 71 
     | 
    
         
            +
                # resource.
         
     | 
| 
      
 72 
     | 
    
         
            +
                def target_schema(link)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  if link.target_schema
         
     | 
| 
      
 74 
     | 
    
         
            +
                    link.target_schema
         
     | 
| 
      
 75 
     | 
    
         
            +
                  elsif legacy_hyper_schema_rel?(link)
         
     | 
| 
      
 76 
     | 
    
         
            +
                    link.parent
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
       34 
79 
     | 
    
         
             
              end
         
     | 
| 
       35 
80 
     | 
    
         
             
            end
         
     | 
| 
         @@ -6,10 +6,7 @@ module Committee 
     | 
|
| 
       6 
6 
     | 
    
         
             
                  @link = link
         
     | 
| 
       7 
7 
     | 
    
         
             
                  @validate_errors = options[:validate_errors]
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
                   
     | 
| 
       10 
     | 
    
         
            -
                  # ... this is a Herokuism and not in the specification
         
     | 
| 
       11 
     | 
    
         
            -
                  schema = link.target_schema || link.parent
         
     | 
| 
       12 
     | 
    
         
            -
                  @validator = JsonSchema::Validator.new(schema)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @validator = JsonSchema::Validator.new(target_schema(link))
         
     | 
| 
       13 
10 
     | 
    
         
             
                end
         
     | 
| 
       14 
11 
     | 
    
         | 
| 
       15 
12 
     | 
    
         
             
                def self.validate?(status, options = {})
         
     | 
| 
         @@ -24,7 +21,14 @@ module Committee 
     | 
|
| 
       24 
21 
     | 
    
         
             
                    check_content_type!(response)
         
     | 
| 
       25 
22 
     | 
    
         
             
                  end
         
     | 
| 
       26 
23 
     | 
    
         | 
| 
       27 
     | 
    
         
            -
                   
     | 
| 
      
 24 
     | 
    
         
            +
                  # List is a special case; expect data in an array.
         
     | 
| 
      
 25 
     | 
    
         
            +
                  #
         
     | 
| 
      
 26 
     | 
    
         
            +
                  # This is poor form that's here so as not to introduce breaking behavior.
         
     | 
| 
      
 27 
     | 
    
         
            +
                  # The "instances" value of "rel" is a Heroku-ism and was originally
         
     | 
| 
      
 28 
     | 
    
         
            +
                  # introduced before we understood how to use "targetSchema". It's not
         
     | 
| 
      
 29 
     | 
    
         
            +
                  # meaningful with the context of the hyper-schema specification and
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # should be eventually be removed.
         
     | 
| 
      
 31 
     | 
    
         
            +
                  if legacy_hyper_schema_rel?(@link)
         
     | 
| 
       28 
32 
     | 
    
         
             
                    if !data.is_a?(Array)
         
     | 
| 
       29 
33 
     | 
    
         
             
                      raise InvalidResponse, "List endpoints must return an array of objects."
         
     | 
| 
       30 
34 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -50,9 +54,29 @@ module Committee 
     | 
|
| 
       50 
54 
     | 
    
         
             
                end
         
     | 
| 
       51 
55 
     | 
    
         | 
| 
       52 
56 
     | 
    
         
             
                def check_content_type!(response)
         
     | 
| 
       53 
     | 
    
         
            -
                   
     | 
| 
       54 
     | 
    
         
            -
                     
     | 
| 
       55 
     | 
    
         
            -
                       
     | 
| 
      
 57 
     | 
    
         
            +
                  if @link.media_type
         
     | 
| 
      
 58 
     | 
    
         
            +
                    unless Rack::Mime.match?(response_media_type(response), @link.media_type)
         
     | 
| 
      
 59 
     | 
    
         
            +
                      raise Committee::InvalidResponse,
         
     | 
| 
      
 60 
     | 
    
         
            +
                        %{"Content-Type" response header must be set to "#{@link.media_type}".}
         
     | 
| 
      
 61 
     | 
    
         
            +
                    end
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                def legacy_hyper_schema_rel?(link)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  link.is_a?(Committee::Drivers::HyperSchema::Link) &&
         
     | 
| 
      
 67 
     | 
    
         
            +
                    link.rel == "instances" &&
         
     | 
| 
      
 68 
     | 
    
         
            +
                    !link.target_schema
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                # Gets the target schema of a link. This is normally just the standard
         
     | 
| 
      
 72 
     | 
    
         
            +
                # response schema, but we allow some legacy behavior for hyper-schema links
         
     | 
| 
      
 73 
     | 
    
         
            +
                # tagged with rel=instances to instead use the schema of their parent
         
     | 
| 
      
 74 
     | 
    
         
            +
                # resource.
         
     | 
| 
      
 75 
     | 
    
         
            +
                def target_schema(link)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  if link.target_schema
         
     | 
| 
      
 77 
     | 
    
         
            +
                    link.target_schema
         
     | 
| 
      
 78 
     | 
    
         
            +
                  elsif legacy_hyper_schema_rel?(link)
         
     | 
| 
      
 79 
     | 
    
         
            +
                    link.parent
         
     | 
| 
       56 
80 
     | 
    
         
             
                  end
         
     | 
| 
       57 
81 
     | 
    
         
             
                end
         
     | 
| 
       58 
82 
     | 
    
         
             
              end
         
     | 
    
        data/lib/committee/router.rb
    CHANGED
    
    | 
         @@ -1,9 +1,9 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Committee
         
     | 
| 
       2 
2 
     | 
    
         
             
              class Router
         
     | 
| 
       3 
3 
     | 
    
         
             
                def initialize(schema, options = {})
         
     | 
| 
       4 
     | 
    
         
            -
                  @routes = build_routes(schema)
         
     | 
| 
       5 
4 
     | 
    
         
             
                  @prefix = options[:prefix]
         
     | 
| 
       6 
5 
     | 
    
         
             
                  @prefix_regexp = /\A#{Regexp.escape(@prefix)}/.freeze if @prefix
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @schema = schema
         
     | 
| 
       7 
7 
     | 
    
         
             
                end
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         
             
                def includes?(path)
         
     | 
| 
         @@ -16,10 +16,11 @@ module Committee 
     | 
|
| 
       16 
16 
     | 
    
         | 
| 
       17 
17 
     | 
    
         
             
                def find_link(method, path)
         
     | 
| 
       18 
18 
     | 
    
         
             
                  path = path.gsub(@prefix_regexp, "") if @prefix
         
     | 
| 
       19 
     | 
    
         
            -
                  if method_routes = @routes[method]
         
     | 
| 
      
 19 
     | 
    
         
            +
                  if method_routes = @schema.routes[method]
         
     | 
| 
       20 
20 
     | 
    
         
             
                    method_routes.each do |pattern, link|
         
     | 
| 
       21 
     | 
    
         
            -
                      if  
     | 
| 
       22 
     | 
    
         
            -
                         
     | 
| 
      
 21 
     | 
    
         
            +
                      if matches = pattern.match(path)
         
     | 
| 
      
 22 
     | 
    
         
            +
                        hash = Hash[matches.names.zip(matches.captures)]
         
     | 
| 
      
 23 
     | 
    
         
            +
                        return link, hash
         
     | 
| 
       23 
24 
     | 
    
         
             
                      end
         
     | 
| 
       24 
25 
     | 
    
         
             
                    end
         
     | 
| 
       25 
26 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -29,34 +30,5 @@ module Committee 
     | 
|
| 
       29 
30 
     | 
    
         
             
                def find_request_link(request)
         
     | 
| 
       30 
31 
     | 
    
         
             
                  find_link(request.request_method, request.path_info)
         
     | 
| 
       31 
32 
     | 
    
         
             
                end
         
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
                private
         
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
                def build_routes(schema)
         
     | 
| 
       36 
     | 
    
         
            -
                  routes = {}
         
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
                  schema.links.each do |link|
         
     | 
| 
       39 
     | 
    
         
            -
                    method, href = parse_link(link)
         
     | 
| 
       40 
     | 
    
         
            -
                    next unless method
         
     | 
| 
       41 
     | 
    
         
            -
                    routes[method] ||= []
         
     | 
| 
       42 
     | 
    
         
            -
                    routes[method] << [%r{^#{href}$}, link]
         
     | 
| 
       43 
     | 
    
         
            -
                  end
         
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
                  # recursively iterate through all `properties` subschemas to build a
         
     | 
| 
       46 
     | 
    
         
            -
                  # complete routing table
         
     | 
| 
       47 
     | 
    
         
            -
                  schema.properties.each do |_, subschema|
         
     | 
| 
       48 
     | 
    
         
            -
                    routes.merge!(build_routes(subschema)) { |_, r1, r2| r1 + r2 }
         
     | 
| 
       49 
     | 
    
         
            -
                  end
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
                  routes
         
     | 
| 
       52 
     | 
    
         
            -
                end
         
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
                def parse_link(link)
         
     | 
| 
       55 
     | 
    
         
            -
                  return nil, nil if !link.method || !link.href
         
     | 
| 
       56 
     | 
    
         
            -
                  method = link.method.to_s.upcase
         
     | 
| 
       57 
     | 
    
         
            -
                  # /apps/{id} --> /apps/([^/]+)
         
     | 
| 
       58 
     | 
    
         
            -
                  href = link.href.gsub(/\{(.*?)\}/, "[^/]+")
         
     | 
| 
       59 
     | 
    
         
            -
                  [method, href]
         
     | 
| 
       60 
     | 
    
         
            -
                end
         
     | 
| 
       61 
33 
     | 
    
         
             
              end
         
     | 
| 
       62 
34 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,10 +1,15 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # Attempts to coerce params given in the query hash (which are all strings) into
         
     | 
| 
       2 
     | 
    
         
            -
            # the types specified by the schema.
         
     | 
| 
       3 
     | 
    
         
            -
            # Currently supported types: null, integer, number and boolean.
         
     | 
| 
       4 
     | 
    
         
            -
            # +call+ returns a hash of all params which could be coerced - coercion errors
         
     | 
| 
       5 
     | 
    
         
            -
            # are simply ignored and expected to be handled later by schema validation.
         
     | 
| 
       6 
1 
     | 
    
         
             
            module Committee
         
     | 
| 
       7 
     | 
    
         
            -
               
     | 
| 
      
 2 
     | 
    
         
            +
              # StringParamsCoercer takes parameters that are specified over a medium that
         
     | 
| 
      
 3 
     | 
    
         
            +
              # can only accept strings (for example in a URL path or in query parameters)
         
     | 
| 
      
 4 
     | 
    
         
            +
              # and attempts to coerce them into known types based of a link's schema
         
     | 
| 
      
 5 
     | 
    
         
            +
              # definition.
         
     | 
| 
      
 6 
     | 
    
         
            +
              #
         
     | 
| 
      
 7 
     | 
    
         
            +
              # Currently supported types: null, integer, number and boolean.
         
     | 
| 
      
 8 
     | 
    
         
            +
              #
         
     | 
| 
      
 9 
     | 
    
         
            +
              # +call+ returns a hash of all params which could be coerced - coercion
         
     | 
| 
      
 10 
     | 
    
         
            +
              # errors are simply ignored and expected to be handled later by schema
         
     | 
| 
      
 11 
     | 
    
         
            +
              # validation.
         
     | 
| 
      
 12 
     | 
    
         
            +
              class StringParamsCoercer
         
     | 
| 
       8 
13 
     | 
    
         
             
                def initialize(query_hash, schema)
         
     | 
| 
       9 
14 
     | 
    
         
             
                  @query_hash = query_hash
         
     | 
| 
       10 
15 
     | 
    
         
             
                  @schema = schema
         
     | 
| 
         @@ -1,19 +1,42 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Committee::Test
         
     | 
| 
       2 
2 
     | 
    
         
             
              module Methods
         
     | 
| 
       3 
3 
     | 
    
         
             
                def assert_schema_conform
         
     | 
| 
       4 
     | 
    
         
            -
                   
     | 
| 
       5 
     | 
    
         
            -
                     
     | 
| 
       6 
     | 
    
         
            -
                     
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
      
 4 
     | 
    
         
            +
                  @committee_schema ||= begin
         
     | 
| 
      
 5 
     | 
    
         
            +
                    # The preferred option. The user has already parsed a schema elsewhere
         
     | 
| 
      
 6 
     | 
    
         
            +
                    # and we therefore don't have to worry about any performance
         
     | 
| 
      
 7 
     | 
    
         
            +
                    # implications of having to do it for every single test suite.
         
     | 
| 
      
 8 
     | 
    
         
            +
                    if committee_schema
         
     | 
| 
      
 9 
     | 
    
         
            +
                      committee_schema
         
     | 
| 
      
 10 
     | 
    
         
            +
                    else
         
     | 
| 
      
 11 
     | 
    
         
            +
                      schema = schema_contents
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                      if schema.is_a?(String)
         
     | 
| 
      
 14 
     | 
    
         
            +
                        warn_string_deprecated
         
     | 
| 
      
 15 
     | 
    
         
            +
                      elsif schema.is_a?(Hash)
         
     | 
| 
      
 16 
     | 
    
         
            +
                        warn_hash_deprecated
         
     | 
| 
      
 17 
     | 
    
         
            +
                      end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                      if schema.is_a?(String)
         
     | 
| 
      
 20 
     | 
    
         
            +
                        schema = JSON.parse(schema)
         
     | 
| 
      
 21 
     | 
    
         
            +
                      end
         
     | 
| 
       8 
22 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
      
 23 
     | 
    
         
            +
                      if schema.is_a?(Hash) || schema.is_a?(JsonSchema::Schema)
         
     | 
| 
      
 24 
     | 
    
         
            +
                        driver = Committee::Drivers::HyperSchema.new
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                        # The driver itself has its own special cases to be able to parse
         
     | 
| 
      
 27 
     | 
    
         
            +
                        # either a hash or JsonSchema::Schema object.
         
     | 
| 
      
 28 
     | 
    
         
            +
                        schema = driver.parse(schema)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                      schema
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
       13 
33 
     | 
    
         
             
                  end
         
     | 
| 
       14 
     | 
    
         
            -
                  @router ||= Committee::Router.new(@schema, prefix: schema_url_prefix)
         
     | 
| 
       15 
34 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
                   
     | 
| 
      
 35 
     | 
    
         
            +
                  @committee_router ||= Committee::Router.new(@committee_schema,
         
     | 
| 
      
 36 
     | 
    
         
            +
                    prefix: schema_url_prefix)
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  link, _ = @committee_router.find_request_link(last_request)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  unless link
         
     | 
| 
       17 
40 
     | 
    
         
             
                    response = "`#{last_request.request_method} #{last_request.path_info}` undefined in schema."
         
     | 
| 
       18 
41 
     | 
    
         
             
                    raise Committee::InvalidResponse.new(response)
         
     | 
| 
       19 
42 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -28,6 +51,12 @@ module Committee::Test 
     | 
|
| 
       28 
51 
     | 
    
         
             
                  Committee.warn_deprecated("Committee: use of #assert_schema_content_type is deprecated; use #assert_schema_conform instead.")
         
     | 
| 
       29 
52 
     | 
    
         
             
                end
         
     | 
| 
       30 
53 
     | 
    
         | 
| 
      
 54 
     | 
    
         
            +
                # Can be overridden with a different driver name for other API definition
         
     | 
| 
      
 55 
     | 
    
         
            +
                # formats.
         
     | 
| 
      
 56 
     | 
    
         
            +
                def committee_schema
         
     | 
| 
      
 57 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
       31 
60 
     | 
    
         
             
                # can be overridden alternatively to #schema_path in case the schema is
         
     | 
| 
       32 
61 
     | 
    
         
             
                # easier to access as a string
         
     | 
| 
       33 
62 
     | 
    
         
             
                # blob
         
     | 
| 
         @@ -36,15 +65,23 @@ module Committee::Test 
     | 
|
| 
       36 
65 
     | 
    
         
             
                end
         
     | 
| 
       37 
66 
     | 
    
         | 
| 
       38 
67 
     | 
    
         
             
                def schema_path
         
     | 
| 
       39 
     | 
    
         
            -
                  raise "Please override # 
     | 
| 
      
 68 
     | 
    
         
            +
                  raise "Please override #commitee_schema."
         
     | 
| 
       40 
69 
     | 
    
         
             
                end
         
     | 
| 
       41 
70 
     | 
    
         | 
| 
       42 
71 
     | 
    
         
             
                def schema_url_prefix
         
     | 
| 
       43 
72 
     | 
    
         
             
                  nil
         
     | 
| 
       44 
73 
     | 
    
         
             
                end
         
     | 
| 
       45 
74 
     | 
    
         | 
| 
      
 75 
     | 
    
         
            +
                def warn_hash_deprecated
         
     | 
| 
      
 76 
     | 
    
         
            +
                  Committee.warn_deprecated("Committee: returning a hash from " \
         
     | 
| 
      
 77 
     | 
    
         
            +
                    "#schema_contents and using #schema_path is deprecated; please " \
         
     | 
| 
      
 78 
     | 
    
         
            +
                    "override #committee_schema instead.")
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
       46 
81 
     | 
    
         
             
                def warn_string_deprecated
         
     | 
| 
       47 
     | 
    
         
            -
                  Committee.warn_deprecated("Committee: returning a string from  
     | 
| 
      
 82 
     | 
    
         
            +
                  Committee.warn_deprecated("Committee: returning a string from " \
         
     | 
| 
      
 83 
     | 
    
         
            +
                    "#schema_contents is deprecated; please override #committee_schema " \
         
     | 
| 
      
 84 
     | 
    
         
            +
                    "instead.")
         
     | 
| 
       48 
85 
     | 
    
         
             
                end
         
     | 
| 
       49 
86 
     | 
    
         | 
| 
       50 
87 
     | 
    
         
             
                def validate_response?(status)
         
     | 
    
        data/lib/committee.rb
    CHANGED
    
    | 
         @@ -3,7 +3,7 @@ require "json_schema" 
     | 
|
| 
       3 
3 
     | 
    
         
             
            require "rack"
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            require_relative "committee/errors"
         
     | 
| 
       6 
     | 
    
         
            -
            require_relative "committee/ 
     | 
| 
      
 6 
     | 
    
         
            +
            require_relative "committee/string_params_coercer"
         
     | 
| 
       7 
7 
     | 
    
         
             
            require_relative "committee/request_unpacker"
         
     | 
| 
       8 
8 
     | 
    
         
             
            require_relative "committee/request_validator"
         
     | 
| 
       9 
9 
     | 
    
         
             
            require_relative "committee/response_generator"
         
     | 
| 
         @@ -11,14 +11,28 @@ require_relative "committee/response_validator" 
     | 
|
| 
       11 
11 
     | 
    
         
             
            require_relative "committee/router"
         
     | 
| 
       12 
12 
     | 
    
         
             
            require_relative "committee/validation_error"
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
      
 14 
     | 
    
         
            +
            require_relative "committee/drivers"
         
     | 
| 
      
 15 
     | 
    
         
            +
            require_relative "committee/drivers/hyper_schema"
         
     | 
| 
      
 16 
     | 
    
         
            +
            require_relative "committee/drivers/open_api_2"
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
       14 
18 
     | 
    
         
             
            require_relative "committee/middleware/base"
         
     | 
| 
       15 
19 
     | 
    
         
             
            require_relative "committee/middleware/request_validation"
         
     | 
| 
       16 
20 
     | 
    
         
             
            require_relative "committee/middleware/response_validation"
         
     | 
| 
       17 
21 
     | 
    
         
             
            require_relative "committee/middleware/stub"
         
     | 
| 
       18 
22 
     | 
    
         | 
| 
      
 23 
     | 
    
         
            +
            require_relative "committee/bin/committee_stub"
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
       19 
25 
     | 
    
         
             
            require_relative "committee/test/methods"
         
     | 
| 
       20 
26 
     | 
    
         | 
| 
       21 
27 
     | 
    
         
             
            module Committee
         
     | 
| 
      
 28 
     | 
    
         
            +
              def self.debug?
         
     | 
| 
      
 29 
     | 
    
         
            +
                ENV["COMMITTEE_DEBUG"]
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              def self.log_debug(message)
         
     | 
| 
      
 33 
     | 
    
         
            +
                $stderr.puts(message) if debug?
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
       22 
36 
     | 
    
         
             
              def self.warn_deprecated(message)
         
     | 
| 
       23 
37 
     | 
    
         
             
                if !$VERBOSE.nil?
         
     | 
| 
       24 
38 
     | 
    
         
             
                  $stderr.puts(message)
         
     | 
| 
         @@ -0,0 +1,45 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative "../test_helper"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Committee::Bin::CommitteeStub do
         
     | 
| 
      
 4 
     | 
    
         
            +
              before do
         
     | 
| 
      
 5 
     | 
    
         
            +
                @bin = Committee::Bin::CommitteeStub.new
         
     | 
| 
      
 6 
     | 
    
         
            +
              end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              it "produces a Rack app" do
         
     | 
| 
      
 9 
     | 
    
         
            +
                app = @bin.get_app(hyper_schema, {})
         
     | 
| 
      
 10 
     | 
    
         
            +
                assert_kind_of Rack::Builder, app
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              it "parses command line options" do
         
     | 
| 
      
 14 
     | 
    
         
            +
                options, parser = @bin.get_options_parser
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                parser.parse!(["--help"])
         
     | 
| 
      
 17 
     | 
    
         
            +
                assert_equal true, options[:help]
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                parser.parse!([
         
     | 
| 
      
 20 
     | 
    
         
            +
                  "--driver", "open_api_2",
         
     | 
| 
      
 21 
     | 
    
         
            +
                  "--tolerant", "true",
         
     | 
| 
      
 22 
     | 
    
         
            +
                  "--port", "1234"
         
     | 
| 
      
 23 
     | 
    
         
            +
                ])
         
     | 
| 
      
 24 
     | 
    
         
            +
                assert_equal :open_api_2, options[:driver]
         
     | 
| 
      
 25 
     | 
    
         
            +
                assert_equal true, options[:tolerant]
         
     | 
| 
      
 26 
     | 
    
         
            +
                assert_equal "1234", options[:port]
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            describe Committee::Bin::CommitteeStub, "app" do
         
     | 
| 
      
 31 
     | 
    
         
            +
              include Rack::Test::Methods
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
              before do
         
     | 
| 
      
 34 
     | 
    
         
            +
                @bin = Committee::Bin::CommitteeStub.new
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              def app
         
     | 
| 
      
 38 
     | 
    
         
            +
                @bin.get_app(hyper_schema, {})
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              it "defaults to a 404" do
         
     | 
| 
      
 42 
     | 
    
         
            +
                get "/foos"
         
     | 
| 
      
 43 
     | 
    
         
            +
                assert_equal 404, last_response.status
         
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
            end
         
     | 
    
        data/test/bin_test.rb
    ADDED
    
    | 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative "test_helper"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            #
         
     | 
| 
      
 4 
     | 
    
         
            +
            # The purpose of this sets of tests is just to include our Ruby executables
         
     | 
| 
      
 5 
     | 
    
         
            +
            # where possible so that we can get very basic sanity checks on their syntax
         
     | 
| 
      
 6 
     | 
    
         
            +
            # (which is something that of course Ruby can't do by default).
         
     | 
| 
      
 7 
     | 
    
         
            +
            #
         
     | 
| 
      
 8 
     | 
    
         
            +
            # We can do this without actually executing them because they're gated by `if
         
     | 
| 
      
 9 
     | 
    
         
            +
            # $0 == __FILE__` statements.
         
     | 
| 
      
 10 
     | 
    
         
            +
            #
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            describe "executables in bin/" do
         
     | 
| 
      
 13 
     | 
    
         
            +
              before do
         
     | 
| 
      
 14 
     | 
    
         
            +
                @bin_dir = File.expand_path("../../bin", __FILE__)
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              it "has roughly valid Ruby structure for committee-stub" do
         
     | 
| 
      
 18 
     | 
    
         
            +
                load File.join(@bin_dir, "committee-stub")
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     |