praxis 0.13.0 → 0.14.0

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.
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)