praxis 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +15 -2
  4. data/CHANGELOG.md +54 -1
  5. data/bin/praxis +49 -2
  6. data/lib/api_browser/Gruntfile.js +247 -90
  7. data/lib/api_browser/app/bower_components/angular-mocks/.bower.json +19 -0
  8. data/lib/api_browser/app/bower_components/angular-mocks/README.md +57 -0
  9. data/lib/api_browser/app/bower_components/angular-mocks/angular-mocks.js +2193 -0
  10. data/lib/api_browser/app/bower_components/angular-mocks/bower.json +9 -0
  11. data/lib/api_browser/app/bower_components/angular-mocks/package.json +27 -0
  12. data/lib/api_browser/app/bower_components/angular/.bower.json +6 -5
  13. data/lib/api_browser/app/bower_components/angular/README.md +23 -4
  14. data/lib/api_browser/app/bower_components/angular/angular-csp.css +6 -0
  15. data/lib/api_browser/app/bower_components/angular/angular.js +2287 -1597
  16. data/lib/api_browser/app/bower_components/angular/angular.min.js +212 -205
  17. data/lib/api_browser/app/bower_components/angular/angular.min.js.gzip +0 -0
  18. data/lib/api_browser/app/bower_components/angular/angular.min.js.map +3 -3
  19. data/lib/api_browser/app/bower_components/angular/bower.json +2 -1
  20. data/lib/api_browser/app/bower_components/angular/package.json +25 -0
  21. data/lib/api_browser/app/bower_components/showdown/.bower.json +39 -0
  22. data/lib/api_browser/app/bower_components/showdown/.jshintignore +2 -0
  23. data/lib/api_browser/app/bower_components/showdown/.travis.yml +8 -0
  24. data/lib/api_browser/app/bower_components/showdown/Gruntfile.js +100 -0
  25. data/lib/api_browser/app/bower_components/showdown/README.md +317 -0
  26. data/lib/api_browser/app/bower_components/showdown/bower.json +26 -0
  27. data/lib/api_browser/app/bower_components/showdown/compressed/Showdown.js +1606 -0
  28. data/lib/api_browser/app/bower_components/showdown/compressed/Showdown.js.map +1 -0
  29. data/lib/api_browser/app/bower_components/showdown/compressed/Showdown.min.js +2 -0
  30. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/github.min.js +2 -0
  31. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/github.min.js.map +1 -0
  32. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/prettify.min.js +2 -0
  33. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/prettify.min.js.map +1 -0
  34. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/table.min.js +2 -0
  35. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/table.min.js.map +1 -0
  36. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/twitter.min.js +2 -0
  37. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/twitter.min.js.map +1 -0
  38. data/lib/api_browser/app/bower_components/showdown/license.txt +34 -0
  39. data/lib/api_browser/app/bower_components/showdown/package.json +47 -0
  40. data/lib/api_browser/app/bower_components/showdown/src/extensions/github.js +25 -0
  41. data/lib/api_browser/app/bower_components/showdown/src/extensions/prettify.js +29 -0
  42. data/lib/api_browser/app/bower_components/showdown/src/extensions/table.js +106 -0
  43. data/lib/api_browser/app/bower_components/showdown/src/extensions/twitter.js +42 -0
  44. data/lib/api_browser/app/bower_components/showdown/src/ng-showdown.js +150 -0
  45. data/lib/api_browser/app/bower_components/showdown/src/showdown.js +1454 -0
  46. data/lib/api_browser/app/index.html +6 -4
  47. data/lib/api_browser/app/js/app.js +1 -2
  48. data/lib/api_browser/app/js/controllers/action.js +4 -4
  49. data/lib/api_browser/app/js/controllers/controller.js +1 -1
  50. data/lib/api_browser/app/js/controllers/menu.js +5 -3
  51. data/lib/api_browser/app/js/controllers/type.js +5 -5
  52. data/lib/api_browser/app/js/directives/attribute_description.js +5 -5
  53. data/lib/api_browser/app/js/directives/attribute_table.js +1 -1
  54. data/lib/api_browser/app/js/directives/attribute_table_row.js +2 -2
  55. data/lib/api_browser/app/js/directives/no_container.js +1 -1
  56. data/lib/api_browser/app/js/directives/request_body.js +5 -5
  57. data/lib/api_browser/app/js/directives/request_headers.js +3 -6
  58. data/lib/api_browser/app/js/directives/request_parameters.js +3 -6
  59. data/lib/api_browser/app/js/directives/type_label.js +4 -5
  60. data/lib/api_browser/app/js/factories/Documentation.js +4 -4
  61. data/lib/api_browser/app/js/factories/PayloadTemplates.js +2 -2
  62. data/lib/api_browser/app/js/factories/TypeTemplates.js +3 -3
  63. data/lib/api_browser/app/js/filters/markdown.js +6 -0
  64. data/lib/api_browser/app/js/filters/resource_name.js +2 -2
  65. data/lib/api_browser/app/sass/modules/_header.scss +2 -7
  66. data/lib/api_browser/app/sass/{main.scss → praxis.scss} +0 -0
  67. data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +370 -367
  68. data/lib/api_browser/app/views/action.html +2 -2
  69. data/lib/api_browser/app/views/controller.html +2 -2
  70. data/lib/api_browser/app/views/directives/attribute_description.html +1 -1
  71. data/lib/api_browser/app/views/layout.html +2 -11
  72. data/lib/api_browser/app/views/navbar.html +9 -0
  73. data/lib/api_browser/app/views/resource/_actions.html +1 -1
  74. data/lib/api_browser/app/views/type.html +2 -2
  75. data/lib/api_browser/app/views/type/_details.html +2 -1
  76. data/lib/api_browser/bower.json +5 -0
  77. data/lib/api_browser/package.json +18 -7
  78. data/lib/praxis.rb +8 -3
  79. data/lib/praxis/action_definition.rb +28 -6
  80. data/lib/praxis/api_definition.rb +30 -2
  81. data/lib/praxis/api_general_info.rb +36 -0
  82. data/lib/praxis/bootloader.rb +1 -0
  83. data/lib/praxis/collection.rb +34 -0
  84. data/lib/praxis/controller.rb +7 -0
  85. data/lib/praxis/dispatcher.rb +3 -0
  86. data/lib/praxis/links.rb +2 -8
  87. data/lib/praxis/media_type.rb +6 -24
  88. data/lib/praxis/media_type_collection.rb +6 -2
  89. data/lib/praxis/plugin_concern.rb +2 -1
  90. data/lib/praxis/request.rb +24 -15
  91. data/lib/praxis/request_stages/request_stage.rb +19 -4
  92. data/lib/praxis/request_stages/validate_params_and_headers.rb +1 -1
  93. data/lib/praxis/request_stages/validate_payload.rb +1 -1
  94. data/lib/praxis/resource_definition.rb +45 -10
  95. data/lib/praxis/response_definition.rb +46 -27
  96. data/lib/praxis/restful_doc_generator.rb +94 -7
  97. data/lib/praxis/simple_media_type.rb +2 -9
  98. data/lib/praxis/stage.rb +1 -4
  99. data/lib/praxis/tasks/api_docs.rb +51 -19
  100. data/lib/praxis/tasks/routes.rb +19 -15
  101. data/lib/praxis/types/media_type_common.rb +31 -0
  102. data/lib/praxis/types/multipart.rb +4 -4
  103. data/lib/praxis/version.rb +1 -1
  104. data/praxis.gemspec +2 -2
  105. data/spec/api_browser/factories/documentation_spec.js +50 -0
  106. data/spec/api_browser/filters/attribute_name_spec.js +23 -0
  107. data/spec/functional_spec.rb +62 -10
  108. data/spec/praxis/action_definition_spec.rb +12 -4
  109. data/spec/praxis/api_definition_spec.rb +159 -0
  110. data/spec/praxis/api_general_info_spec.rb +36 -0
  111. data/spec/praxis/bootloader_spec.rb +10 -1
  112. data/spec/praxis/media_type_collection_spec.rb +46 -53
  113. data/spec/praxis/media_type_spec.rb +6 -6
  114. data/spec/praxis/request_stage_spec.rb +7 -2
  115. data/spec/praxis/request_stages_validate_spec.rb +12 -7
  116. data/spec/praxis/resource_definition_spec.rb +62 -0
  117. data/spec/praxis/response_definition_spec.rb +26 -16
  118. data/spec/praxis/stage_spec.rb +4 -8
  119. data/spec/praxis/types/collection_spec.rb +144 -0
  120. data/spec/spec_app/app/controllers/instances.rb +8 -2
  121. data/spec/spec_app/design/api.rb +11 -0
  122. data/spec/spec_app/design/media_types/instance.rb +12 -0
  123. data/spec/spec_app/design/media_types/volume.rb +9 -2
  124. data/spec/spec_app/design/media_types/volume_snapshot.rb +9 -6
  125. data/spec/spec_app/design/resources/instances.rb +25 -10
  126. data/spec/support/spec_media_types.rb +1 -1
  127. data/spec/support/spec_resource_definitions.rb +2 -0
  128. data/tasks/thor/app.rb +15 -10
  129. data/tasks/thor/example.rb +115 -115
  130. data/tasks/thor/templates/generator/empty_app/.gitignore +2 -0
  131. data/tasks/thor/templates/generator/empty_app/docs/app.js +1 -0
  132. data/tasks/thor/templates/generator/empty_app/docs/styles.scss +3 -0
  133. metadata +50 -9
  134. data/lib/api_browser/app/css/main.css +0 -4511
  135. data/lib/praxis/types/collection.rb +0 -17
data/lib/praxis/links.rb CHANGED
@@ -58,7 +58,7 @@ module Praxis
58
58
  end
59
59
  end
60
60
 
61
- def self.define_blueprint_reader!(name)
61
+ def self.define_reader!(name)
62
62
  # it's faster to use define_method in this case than module_eval
63
63
  # because we save the attribute lookup on every access.
64
64
  attribute = self.attributes[name]
@@ -79,12 +79,6 @@ module Praxis
79
79
  self.__send__(name)
80
80
  end
81
81
  end
82
-
83
- @reference.attribute.type.instance_eval do
84
- define_method(using) do
85
- self.__send__(name)
86
- end
87
- end
88
82
  end
89
83
 
90
84
  end
@@ -103,7 +97,7 @@ module Praxis
103
97
  # This is primarily necessary only for example generation.
104
98
  def self.fixup_reference_struct_methods
105
99
  self.links.each do |name, using|
106
- next if @reference.attribute.attributes.has_key?(name)
100
+ next if @reference.attribute.attributes.has_key?(using)
107
101
  @reference.attribute.type.instance_eval do
108
102
  define_method(using) do
109
103
  return nil unless attributes[:links]
@@ -1,5 +1,6 @@
1
1
  module Praxis
2
2
  class MediaType < Praxis::Blueprint
3
+ include Types::MediaTypeCommon
3
4
 
4
5
  class DSLCompiler < Attributor::DSLCompiler
5
6
  def links(&block)
@@ -7,25 +8,6 @@ module Praxis
7
8
  end
8
9
  end
9
10
 
10
- def self.description(text=nil)
11
- @description = text if text
12
- @description
13
- end
14
-
15
- def self.identifier(identifier=nil)
16
- return @identifier unless identifier
17
- # TODO: parse the string and extract things like collection , and format type?...
18
- @identifier = identifier
19
- end
20
-
21
- def self.describe(shallow = false)
22
- hash = super
23
- unless shallow
24
- hash.merge!(identifier: @identifier, description: @description)
25
- end
26
- hash
27
- end
28
-
29
11
  def self.attributes(opts={}, &block)
30
12
  super(opts.merge(dsl_compiler: MediaType::DSLCompiler), &block)
31
13
  end
@@ -34,16 +16,16 @@ module Praxis
34
16
  super
35
17
  if @attribute && self.attributes.key?(:links) && self.attributes[:links].type < Praxis::Links
36
18
  # Only define out special links accessor if it was setup using the special DSL
37
- # (we might have an app defining an attribute called `links` on its own, in which
19
+ # (we might have an app defining an attribute called `links` on its own, in which
38
20
  # case we leave it be)
39
21
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
40
- def links
41
- self.class::Links.new(@object)
42
- end
22
+ def links
23
+ self.class::Links.new(@object)
24
+ end
43
25
  RUBY
44
26
  end
45
27
  end
46
-
28
+
47
29
  end
48
30
 
49
31
  end
@@ -26,7 +26,12 @@ module Praxis
26
26
  class << self
27
27
  attr_accessor :member_attribute
28
28
  end
29
-
29
+
30
+ def self.inherited(klass)
31
+ warn "DEPRECATION: MediaTypeCollection is deprecated and will be removed by 1.0"
32
+ super
33
+ end
34
+
30
35
  def self._finalize!
31
36
  super
32
37
 
@@ -35,7 +40,6 @@ module Praxis
35
40
  include StructCollection
36
41
  end
37
42
  end
38
-
39
43
  end
40
44
 
41
45
  def self.member_type(type=nil)
@@ -12,7 +12,8 @@ module Praxis
12
12
  :Request,
13
13
  :Controller,
14
14
  :ResourceDefinition,
15
- :ActionDefinition
15
+ :ActionDefinition,
16
+ :Response
16
17
  ]
17
18
 
18
19
  def setup!
@@ -4,15 +4,25 @@ module Praxis
4
4
  attr_reader :env, :query
5
5
  attr_accessor :route_params, :action
6
6
 
7
+ PATH_VERSION_PREFIX = "/v".freeze
8
+ CONTENT_TYPE_NAME = 'CONTENT_TYPE'.freeze
9
+ PATH_INFO_NAME = 'PATH_INFO'.freeze
10
+ REQUEST_METHOD_NAME = 'REQUEST_METHOD'.freeze
11
+ QUERY_STRING_NAME = 'QUERY_STRING'.freeze
12
+ API_VERSION_HEADER_NAME = "HTTP_X_API_VERSION".freeze
13
+ API_VERSION_PARAM_NAME = 'api_version'.freeze
14
+ API_NO_VERSION_NAME = 'n/a'.freeze
15
+ VERSION_USING_DEFAULTS = [:header,:params].freeze
16
+
7
17
  def initialize(env)
8
18
  @env = env
9
- @query = Rack::Utils.parse_nested_query(env['QUERY_STRING'.freeze])
19
+ @query = Rack::Utils.parse_nested_query(env[QUERY_STRING_NAME])
10
20
  @route_params = {}
11
21
  @path_version_matcher = path_version_matcher
12
22
  end
13
23
 
14
24
  def content_type
15
- @env['CONTENT_TYPE'.freeze]
25
+ @env[CONTENT_TYPE_NAME]
16
26
  end
17
27
 
18
28
  # The media type (type/subtype) portion of the CONTENT_TYPE header
@@ -26,27 +36,24 @@ module Praxis
26
36
  end
27
37
 
28
38
  def path
29
- @env['PATH_INFO'.freeze]
39
+ @env[PATH_INFO_NAME]
30
40
  end
31
41
 
32
42
  attr_accessor :headers, :params, :payload
33
43
 
34
44
  def params_hash
35
45
  return {} if params.nil?
36
-
37
- params.attributes.each_with_object({}) do |(k,v),hash|
38
- hash[k] = v
39
- end
46
+ params.attributes
40
47
  end
41
48
 
42
49
  def verb
43
- @env['REQUEST_METHOD'.freeze]
50
+ @env[REQUEST_METHOD_NAME]
44
51
  end
45
52
 
46
53
  def raw_params
47
54
  @raw_params ||= begin
48
55
  params = query.merge(route_params)
49
- params.delete('api_version'.freeze)
56
+ params.delete(API_VERSION_PARAM_NAME)
50
57
  params
51
58
  end
52
59
  end
@@ -66,21 +73,23 @@ module Praxis
66
73
  end
67
74
 
68
75
  def self.path_version_prefix
69
- "/v".freeze
76
+ PATH_VERSION_PREFIX
70
77
  end
78
+
79
+ PATH_VERSION_MATCHER = %r{^#{self.path_version_prefix}(?<version>[^\/]+)\/}.freeze
71
80
 
72
81
  def path_version_matcher
73
- %r{^#{Request.path_version_prefix}(?<version>[^\/]+)\/}.freeze
82
+ PATH_VERSION_MATCHER
74
83
  end
75
84
 
76
- def version(using: [:header,:params].freeze)
85
+ def version(using: VERSION_USING_DEFAULTS )
77
86
  result = nil
78
87
  Array(using).find do |mode|
79
88
  case mode
80
89
  when :header ;
81
- result = env["HTTP_X_API_VERSION".freeze]
90
+ result = env[API_VERSION_HEADER_NAME]
82
91
  when :params ;
83
- result = @query['api_version'.freeze]
92
+ result = @query[API_VERSION_PARAM_NAME]
84
93
  when :path ;
85
94
  m = self.path.match(@path_version_matcher)
86
95
  result = m[:version] unless m.nil?
@@ -88,7 +97,7 @@ module Praxis
88
97
  raise "Unknown method for retrieving the API version: #{mode}"
89
98
  end
90
99
  end
91
- return result || 'n/a'.freeze
100
+ return result || API_NO_VERSION_NAME
92
101
  end
93
102
 
94
103
  def load_headers(context)
@@ -7,11 +7,10 @@ module Praxis
7
7
  class RequestStage < Stage
8
8
  extend Forwardable
9
9
 
10
- def_delegators :@context, :controller, :action, :request
11
10
  alias :dispatcher :application # it's technically application in the base Stage
12
11
 
13
12
  def path
14
- [name]
13
+ @the_path ||= [name].freeze
15
14
  end
16
15
 
17
16
  def execute_controller_callbacks(callbacks)
@@ -31,9 +30,25 @@ module Praxis
31
30
  nil
32
31
  end
33
32
 
34
- def run
35
- setup!
33
+ def setup!
36
34
  setup_deferred_callbacks!
35
+ end
36
+
37
+ # Avoid using delegators, and create the explicit functions:
38
+ # def_delegators :@context, :controller, :action, :request
39
+ # they allocate all kinds of things and we don't need the generality here
40
+ def controller
41
+ @context.controller
42
+ end
43
+ def action
44
+ @context.action
45
+ end
46
+ def request
47
+ @context.request
48
+ end
49
+
50
+
51
+ def run
37
52
 
38
53
  # stage-level callbacks (typically empty) will never shortcut
39
54
  execute_callbacks(self.before_callbacks)
@@ -11,7 +11,7 @@ module Praxis
11
11
 
12
12
 
13
13
  def path
14
- @parent.path + [name]
14
+ @_path ||= ( @parent.path + [name] )
15
15
  end
16
16
 
17
17
  def execute
@@ -11,7 +11,7 @@ module Praxis
11
11
  end
12
12
 
13
13
  def path
14
- @parent.path + [name]
14
+ @_path ||= ( @parent.path + [name] )
15
15
  end
16
16
 
17
17
  def execute
@@ -1,11 +1,11 @@
1
1
  require 'active_support/concern'
2
2
  require 'active_support/inflector'
3
3
 
4
-
5
4
  module Praxis
6
5
  module ResourceDefinition
7
6
  extend ActiveSupport::Concern
8
-
7
+ DEFAULT_RESOURCE_HREF_ACTION = :show
8
+
9
9
  included do
10
10
  @version = 'n/a'.freeze
11
11
  @actions = Hash.new
@@ -21,7 +21,7 @@ module Praxis
21
21
  attr_reader :routing_config
22
22
  attr_reader :responses
23
23
  attr_reader :version_options
24
-
24
+
25
25
  # opaque hash of user-defined medata, used to decorate the definition,
26
26
  # and also available in the generated JSON documents
27
27
  attr_reader :metadata
@@ -42,24 +42,55 @@ module Praxis
42
42
  @media_type = media_type
43
43
  end
44
44
 
45
- def version(version=nil, options= { using: [:header,:params] }.freeze )
45
+ def version(version=nil, options=nil)
46
46
  return @version unless version
47
47
  @version = version
48
- @version_options = options
48
+ @version_options = options || {using: [:header,:params]}
49
+ end
50
+
51
+ def canonical_path( action_name=nil )
52
+ if action_name
53
+ raise "Canonical path for #{self.name} is already defined as: '#{@canonical_action_name}'. 'canonical_path' can only be defined once." if @canonical_action_name
54
+ @canonical_action_name = action_name
55
+ else
56
+ # Resolution of the actual action definition needs to be done lazily, since we can use the `canonical_path` stanza
57
+ # at the top of the resource, well before the actual action is defined.
58
+ unless @canonical_action
59
+ href_action = @canonical_action_name || DEFAULT_RESOURCE_HREF_ACTION
60
+ @canonical_action = actions.fetch(href_action) do
61
+ raise "Error: trying to set canonical_href of #{self.name}. Action '#{href_action}' does not exist"
62
+ end
63
+ end
64
+ return @canonical_action
65
+ end
66
+ end
67
+
68
+ def to_href( params )
69
+ canonical_path.primary_route.path.expand(params)
49
70
  end
50
71
 
72
+ def parse_href(path)
73
+ param_values = canonical_path.primary_route.path.params(path)
74
+ attrs = canonical_path.params.attributes
75
+ param_values.each_with_object({}) do |(key,value),hash|
76
+ hash[key.to_sym] = attrs[key.to_sym].load(value,[key])
77
+ end
78
+ rescue => e
79
+ raise Praxis::Exception.new("Error parsing or coercing parameters from href: #{path}\n"+e.message)
80
+ end
81
+
51
82
  def action_defaults(&block)
52
83
  return @action_defaults unless block_given?
53
84
 
54
85
  @action_defaults << block
55
86
  end
56
-
87
+
57
88
  def params(type=Attributor::Struct, **opts, &block)
58
89
  warn 'DEPRECATION: ResourceDefinition.params is deprecated. Use it in action_defaults instead.'
59
90
  action_defaults do
60
91
  params type, **opts, &block
61
92
  end
62
- end
93
+ end
63
94
 
64
95
  def payload(type=Attributor::Struct, **opts, &block)
65
96
  warn 'DEPRECATION: ResourceDefinition.payload is deprecated. Use action_defaults instead.'
@@ -74,7 +105,7 @@ module Praxis
74
105
  headers **opts, &block
75
106
  end
76
107
  end
77
-
108
+
78
109
  def response(name, **args)
79
110
  warn 'DEPRECATION: ResourceDefinition.response is deprecated. Use action_defaults instead.'
80
111
  action_defaults do
@@ -84,6 +115,7 @@ module Praxis
84
115
 
85
116
  def action(name, &block)
86
117
  raise ArgumentError, "can not create ActionDefinition without block" unless block_given?
118
+ raise ArgumentError, "Action names must be defined using symbols (Got: #{name} (of type #{name.class}))" unless name.is_a? Symbol
87
119
  @actions[name] = ActionDefinition.new(name, self, &block)
88
120
  end
89
121
 
@@ -92,13 +124,16 @@ module Praxis
92
124
  @description
93
125
  end
94
126
 
95
-
127
+ def id
128
+ self.name.gsub('::'.freeze,'-'.freeze)
129
+ end
96
130
 
97
131
  def describe
98
132
  {}.tap do |hash|
99
133
  hash[:description] = description
100
- hash[:media_type] = media_type.name if media_type
134
+ hash[:media_type] = media_type.id if media_type
101
135
  hash[:actions] = actions.values.map(&:describe)
136
+ hash[:name] = self.name
102
137
  hash[:metadata] = metadata
103
138
  end
104
139
  end
@@ -36,23 +36,23 @@ module Praxis
36
36
  return @spec[:media_type] if media_type.nil?
37
37
 
38
38
  @spec[:media_type] = case media_type
39
- when String
40
- SimpleMediaType.new(media_type)
41
- when Class
42
- if media_type < Praxis::MediaType
43
- media_type
44
- else
45
- raise Exceptions::InvalidConfiguration.new(
46
- 'Invalid media_type specification. media_type must be a Praxis::MediaType'
47
- )
48
- end
49
- when SimpleMediaType
39
+ when String
40
+ SimpleMediaType.new(media_type)
41
+ when Class
42
+ if media_type < Praxis::Types::MediaTypeCommon
50
43
  media_type
51
44
  else
52
45
  raise Exceptions::InvalidConfiguration.new(
53
- 'Invalid media_type specification. media_type must be a String, MediaType or SimpleMediaType'
46
+ 'Invalid media_type specification. media_type must be a Praxis::MediaType'
54
47
  )
55
48
  end
49
+ when SimpleMediaType
50
+ media_type
51
+ else
52
+ raise Exceptions::InvalidConfiguration.new(
53
+ 'Invalid media_type specification. media_type must be a String, MediaType or SimpleMediaType'
54
+ )
55
+ end
56
56
  end
57
57
 
58
58
  def location(loc=nil)
@@ -94,11 +94,11 @@ module Praxis
94
94
  )
95
95
  end
96
96
  @spec[:headers][k] = v
97
- end
97
+ end
98
98
  else
99
99
  raise Exceptions::InvalidConfiguration.new(
100
100
  "A header definition can only take a String (to match the name) or" +
101
- " a Hash (to match both the name and the value). Received: #{hdr.inspect}"
101
+ " a Hash (to match both the name and the value). Received: #{hdr.inspect}"
102
102
  )
103
103
  end
104
104
  end
@@ -122,7 +122,7 @@ module Praxis
122
122
  end
123
123
  unless headers == nil
124
124
  headers.each do |name, value|
125
- content[:headers][name] = _describe_header(value)
125
+ content[:headers][name] = _describe_header(value)
126
126
  end
127
127
  end
128
128
  unless parts == nil
@@ -134,7 +134,7 @@ module Praxis
134
134
  def _describe_header(data)
135
135
  data_type = data.is_a?(Regexp) ? :regexp : :string
136
136
  data_value = data.is_a?(Regexp) ? data.inspect : data
137
- { :value => data_value, :type => data_type }
137
+ { :value => data_value, :type => data_type }
138
138
  end
139
139
 
140
140
  def validate( response )
@@ -151,15 +151,15 @@ module Praxis
151
151
  raise ArgumentError, "Parts definition for response #{name} needs a :like argument or a block/proc" if !args.empty?
152
152
  return @parts
153
153
  end
154
- if like && a_proc
154
+ if like && a_proc
155
155
  raise ArgumentError, "Parts definition for response #{name} does not allow :like and a block simultaneously"
156
156
  end
157
157
  if like
158
- template = ApiDefinition.instance.response(like)
159
- @parts = template.compile(nil, **args)
160
- else # block
161
- @parts = Praxis::ResponseDefinition.new('anonymous', **args, &a_proc)
162
- end
158
+ template = ApiDefinition.instance.response(like)
159
+ @parts = template.compile(nil, **args)
160
+ else # block
161
+ @parts = Praxis::ResponseDefinition.new('anonymous', **args, &a_proc)
162
+ end
163
163
  end
164
164
 
165
165
  # Validates Status code
@@ -238,17 +238,36 @@ module Praxis
238
238
  def validate_content_type!(response)
239
239
  return unless media_type
240
240
 
241
- extracted_identifier = nil
241
+ response_content_type = {}
242
242
  if response.headers['Content-Type']
243
- extracted_identifier = Praxis::ContentTypeParser.parse(response.headers['Content-Type'])[:type]
243
+ response_content_type = Praxis::ContentTypeParser.parse(response.headers['Content-Type'])
244
244
  end
245
245
 
246
- if media_type.identifier != extracted_identifier
246
+ expected_content_type = Praxis::ContentTypeParser.parse(media_type.identifier)
247
+
248
+ unless response_content_type[:type] == expected_content_type[:type]
247
249
  raise Exceptions::Validation.new(
248
- "Bad Content-Type header. Returned type #{extracted_identifier}" +
249
- " does not match type #{media_type.identifier} as described in response: #{self.name}"
250
+ "Bad Content-Type header. #{response.headers['Content-Type']}" +
251
+ " does not match type #{expected_content_type[:type]} as described in response: #{self.name}"
250
252
  )
251
253
  end
254
+
255
+ if (expected_params = expected_content_type[:params])
256
+ expected_params.each do |param_name,expected_param|
257
+ response_param = response_content_type[:params].fetch(param_name) do
258
+ raise Exceptions::Validation.new(
259
+ "Bad Content-Type header: #{response.headers['Content-Type']}" +
260
+ " does not contain expected param '#{param_name}' as described in response: #{self.name}"
261
+ )
262
+ end
263
+ unless response_param == expected_param
264
+ raise Exceptions::Validation.new(
265
+ "Bad Content-Type header: #{response.headers['Content-Type']}" +
266
+ " param: #{param_name} does not match expected value #{expected_param} as described in response: #{self.name}"
267
+ )
268
+ end
269
+ end
270
+ end
252
271
  end
253
272
 
254
273
  def validate_parts!(response)