praxis 0.16.1 → 0.17.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 (399) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +2 -1
  5. data/CHANGELOG.md +41 -0
  6. data/CONTRIBUTING.md +3 -0
  7. data/lib/api_browser/Gruntfile.js +20 -4
  8. data/lib/api_browser/app/bower_components/angular-mocks/.bower.json +6 -6
  9. data/lib/api_browser/app/bower_components/angular-mocks/README.md +11 -5
  10. data/lib/api_browser/app/bower_components/angular-mocks/angular-mocks.js +475 -216
  11. data/lib/api_browser/app/bower_components/angular-mocks/bower.json +2 -2
  12. data/lib/api_browser/app/bower_components/angular-mocks/ngAnimateMock.js +2 -0
  13. data/lib/api_browser/app/bower_components/angular-mocks/ngMock.js +2 -0
  14. data/lib/api_browser/app/bower_components/angular-mocks/ngMockE2E.js +2 -0
  15. data/lib/api_browser/app/bower_components/angular-mocks/package.json +1 -1
  16. data/lib/api_browser/app/bower_components/angular-sanitize/.bower.json +8 -8
  17. data/lib/api_browser/app/bower_components/angular-sanitize/README.md +19 -5
  18. data/lib/api_browser/app/bower_components/angular-sanitize/angular-sanitize.js +186 -127
  19. data/lib/api_browser/app/bower_components/angular-sanitize/angular-sanitize.min.js +12 -10
  20. data/lib/api_browser/app/bower_components/angular-sanitize/angular-sanitize.min.js.map +3 -3
  21. data/lib/api_browser/app/bower_components/angular-sanitize/bower.json +3 -2
  22. data/lib/api_browser/app/bower_components/angular-sanitize/index.js +2 -0
  23. data/lib/api_browser/app/bower_components/angular-sanitize/package.json +26 -0
  24. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/.bower.json +15 -8
  25. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/bower.json +11 -3
  26. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/ui-bootstrap-csp.css +6 -0
  27. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/ui-bootstrap-tpls.js +1177 -453
  28. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/ui-bootstrap-tpls.min.js +4 -4
  29. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/ui-bootstrap.js +1066 -404
  30. data/lib/api_browser/app/bower_components/angular-ui-bootstrap-bower/ui-bootstrap.min.js +3 -3
  31. data/lib/api_browser/app/bower_components/angular-ui-router/.bower.json +5 -6
  32. data/lib/api_browser/app/bower_components/angular-ui-router/CHANGELOG.md +208 -3
  33. data/lib/api_browser/app/bower_components/angular-ui-router/CONTRIBUTING.md +65 -0
  34. data/lib/api_browser/app/bower_components/angular-ui-router/LICENSE +1 -1
  35. data/lib/api_browser/app/bower_components/angular-ui-router/README.md +36 -71
  36. data/lib/api_browser/app/bower_components/angular-ui-router/api/angular-ui-router.d.ts +126 -0
  37. data/lib/api_browser/app/bower_components/angular-ui-router/bower.json +1 -1
  38. data/lib/api_browser/app/bower_components/angular-ui-router/release/angular-ui-router.js +1902 -755
  39. data/lib/api_browser/app/bower_components/angular-ui-router/release/angular-ui-router.min.js +2 -2
  40. data/lib/api_browser/app/bower_components/angular-ui-router/src/common.js +69 -23
  41. data/lib/api_browser/app/bower_components/angular-ui-router/src/resolve.js +15 -5
  42. data/lib/api_browser/app/bower_components/angular-ui-router/src/state.js +556 -295
  43. data/lib/api_browser/app/bower_components/angular-ui-router/src/stateDirectives.js +101 -42
  44. data/lib/api_browser/app/bower_components/angular-ui-router/src/stateFilters.js +6 -2
  45. data/lib/api_browser/app/bower_components/angular-ui-router/src/templateFactory.js +2 -2
  46. data/lib/api_browser/app/bower_components/angular-ui-router/src/urlMatcherFactory.js +822 -97
  47. data/lib/api_browser/app/bower_components/angular-ui-router/src/urlRouter.js +274 -120
  48. data/lib/api_browser/app/bower_components/angular-ui-router/src/viewDirective.js +33 -20
  49. data/lib/api_browser/app/bower_components/angular-ui-router/src/viewScroll.js +1 -1
  50. data/lib/api_browser/app/bower_components/angular/.bower.json +5 -5
  51. data/lib/api_browser/app/bower_components/angular/README.md +2 -5
  52. data/lib/api_browser/app/bower_components/angular/angular-csp.css +5 -8
  53. data/lib/api_browser/app/bower_components/angular/angular.js +12975 -6996
  54. data/lib/api_browser/app/bower_components/angular/angular.min.js +285 -213
  55. data/lib/api_browser/app/bower_components/angular/angular.min.js.gzip +0 -0
  56. data/lib/api_browser/app/bower_components/angular/angular.min.js.map +3 -3
  57. data/lib/api_browser/app/bower_components/angular/bower.json +1 -1
  58. data/lib/api_browser/app/bower_components/angular/index.js +2 -0
  59. data/lib/api_browser/app/bower_components/angular/package.json +2 -2
  60. data/lib/api_browser/app/bower_components/bootstrap-sass/.bower.json +31 -16
  61. data/lib/api_browser/app/bower_components/bootstrap-sass/CHANGELOG.md +108 -0
  62. data/lib/api_browser/app/bower_components/bootstrap-sass/CONTRIBUTING.md +55 -37
  63. data/lib/api_browser/app/bower_components/bootstrap-sass/README.md +147 -206
  64. data/lib/api_browser/app/bower_components/bootstrap-sass/bower.json +19 -8
  65. data/lib/api_browser/app/bower_components/bootstrap-sass/{dist/fonts → vendor/assets/fonts/bootstrap}/glyphicons-halflings-regular.eot +0 -0
  66. data/lib/api_browser/app/bower_components/bootstrap-sass/{dist/fonts → vendor/assets/fonts/bootstrap}/glyphicons-halflings-regular.svg +0 -0
  67. data/lib/api_browser/app/bower_components/bootstrap-sass/{dist/fonts → vendor/assets/fonts/bootstrap}/glyphicons-halflings-regular.ttf +0 -0
  68. data/lib/api_browser/app/bower_components/bootstrap-sass/{dist/fonts → vendor/assets/fonts/bootstrap}/glyphicons-halflings-regular.woff +0 -0
  69. data/lib/api_browser/app/bower_components/bootstrap-sass/vendor/assets/javascripts/bootstrap.js +12 -0
  70. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/affix.js +1 -1
  71. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/alert.js +1 -1
  72. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/button.js +11 -5
  73. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/carousel.js +5 -5
  74. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/collapse.js +1 -1
  75. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/dropdown.js +5 -5
  76. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/modal.js +1 -1
  77. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/popover.js +1 -1
  78. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/scrollspy.js +2 -2
  79. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/tab.js +1 -1
  80. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/tooltip.js +1 -1
  81. data/lib/api_browser/app/bower_components/bootstrap-sass/{js → vendor/assets/javascripts/bootstrap}/transition.js +1 -1
  82. data/lib/api_browser/app/bower_components/bootstrap-sass/vendor/assets/stylesheets/bootstrap.scss +1 -0
  83. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_alerts.scss +0 -0
  84. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_badges.scss +6 -6
  85. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_breadcrumbs.scss +0 -0
  86. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_button-groups.scss +7 -33
  87. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_buttons.scss +2 -5
  88. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_carousel.scss +1 -0
  89. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_close.scss +0 -0
  90. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_code.scss +0 -0
  91. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_component-animations.scss +0 -0
  92. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_dropdowns.scss +3 -8
  93. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_forms.scss +11 -0
  94. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_glyphicons.scss +5 -5
  95. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_grid.scss +12 -26
  96. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_input-groups.scss +1 -1
  97. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_jumbotron.scss +8 -2
  98. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_labels.scss +6 -0
  99. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_list-group.scss +0 -0
  100. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_media.scss +0 -0
  101. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_mixins.scss +38 -51
  102. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_modals.scss +2 -5
  103. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_navbar.scss +41 -53
  104. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_navs.scss +0 -20
  105. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_normalize.scss +0 -0
  106. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_pager.scss +0 -0
  107. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_pagination.scss +0 -0
  108. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_panels.scss +11 -1
  109. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_popovers.scss +0 -0
  110. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_print.scss +0 -0
  111. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_progress-bars.scss +0 -12
  112. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_responsive-utilities.scss +0 -0
  113. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_scaffolding.scss +0 -0
  114. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_tables.scss +5 -18
  115. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_theme.scss +2 -2
  116. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_thumbnails.scss +9 -3
  117. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_tooltip.scss +0 -0
  118. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_type.scss +54 -52
  119. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_utilities.scss +0 -0
  120. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_variables.scss +20 -11
  121. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/_wells.scss +0 -0
  122. data/lib/api_browser/app/bower_components/bootstrap-sass/{lib → vendor/assets/stylesheets/bootstrap}/bootstrap.scss +0 -0
  123. data/lib/api_browser/app/bower_components/lodash/.bower.json +9 -13
  124. data/lib/api_browser/app/bower_components/lodash/LICENSE.txt +3 -3
  125. data/lib/api_browser/app/bower_components/lodash/bower.json +4 -7
  126. data/lib/api_browser/app/bower_components/lodash/lodash.js +12235 -0
  127. data/lib/api_browser/app/bower_components/lodash/lodash.min.js +98 -0
  128. data/lib/api_browser/app/index.html +0 -1
  129. data/lib/api_browser/app/js/app.js +2 -5
  130. data/lib/api_browser/app/js/controllers/action.js +21 -37
  131. data/lib/api_browser/app/js/controllers/controller.js +23 -1
  132. data/lib/api_browser/app/js/controllers/menu.js +46 -14
  133. data/lib/api_browser/app/js/controllers/type.js +2 -9
  134. data/lib/api_browser/app/js/directives/attribute_description.js +15 -5
  135. data/lib/api_browser/app/js/directives/attribute_table.js +6 -6
  136. data/lib/api_browser/app/js/directives/fixed_if_fits.js +20 -0
  137. data/lib/api_browser/app/js/directives/no_container.js +6 -6
  138. data/lib/api_browser/app/js/directives/type_placeholder.js +21 -0
  139. data/lib/api_browser/app/js/factories/Configuration.js +13 -0
  140. data/lib/api_browser/app/js/factories/Documentation.js +0 -3
  141. data/lib/api_browser/app/js/factories/normalize_attributes.js +19 -0
  142. data/lib/api_browser/app/js/factories/template_for.js +113 -0
  143. data/lib/api_browser/app/sass/modules/_body.scss +26 -4
  144. data/lib/api_browser/app/sass/modules/_sidebar.scss +68 -1
  145. data/lib/api_browser/app/sass/praxis.scss +1 -5
  146. data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +13 -4
  147. data/lib/api_browser/app/views/action.html +13 -17
  148. data/lib/api_browser/app/views/controller.html +32 -4
  149. data/lib/api_browser/app/views/directives/attribute_description.html +1 -1
  150. data/lib/api_browser/app/views/directives/attribute_description/{_default.html → default.html} +0 -0
  151. data/lib/api_browser/app/views/directives/attribute_description/example.html +13 -0
  152. data/lib/api_browser/app/views/directives/attribute_description/headers.html +8 -0
  153. data/lib/api_browser/app/views/directives/attribute_description/member_options.html +4 -0
  154. data/lib/api_browser/app/views/directives/attribute_description/values.html +14 -0
  155. data/lib/api_browser/app/views/directives/attribute_table.html +2 -2
  156. data/lib/api_browser/app/views/home.html +1 -3
  157. data/lib/api_browser/app/views/layout.html +3 -36
  158. data/lib/api_browser/app/views/menu.html +45 -0
  159. data/lib/api_browser/app/views/navbar.html +1 -1
  160. data/lib/api_browser/app/views/type.html +2 -2
  161. data/lib/api_browser/app/views/type/{_details.html → details.html} +6 -6
  162. data/lib/api_browser/app/views/types/embedded/default.html +10 -0
  163. data/lib/api_browser/app/views/types/embedded/links.html +11 -0
  164. data/lib/api_browser/app/views/types/embedded/struct.html +2 -0
  165. data/lib/api_browser/app/views/types/label/link.html +1 -0
  166. data/lib/api_browser/app/views/types/label/primitive.html +1 -0
  167. data/lib/api_browser/app/views/types/label/primitive_collection.html +1 -0
  168. data/lib/api_browser/app/views/types/label/type.html +1 -0
  169. data/lib/api_browser/app/views/types/label/type_collection.html +1 -0
  170. data/lib/api_browser/app/views/{directives/request_body/_default.html → types/standalone/default.html} +1 -1
  171. data/lib/api_browser/app/views/types/standalone/struct.html +1 -0
  172. data/lib/api_browser/bower.json +9 -9
  173. data/lib/api_browser/package.json +1 -1
  174. data/lib/praxis.rb +10 -4
  175. data/lib/praxis/action_definition.rb +16 -4
  176. data/lib/praxis/action_definition/headers_dsl_compiler.rb +5 -2
  177. data/lib/praxis/api_definition.rb +3 -1
  178. data/lib/praxis/api_general_info.rb +49 -5
  179. data/lib/praxis/application.rb +12 -4
  180. data/lib/praxis/bootloader.rb +1 -0
  181. data/lib/praxis/bootloader_stages/environment.rb +2 -0
  182. data/lib/praxis/bootloader_stages/routing.rb +1 -1
  183. data/lib/praxis/bootloader_stages/subgroup_loader.rb +1 -0
  184. data/lib/praxis/exceptions/validation.rb +7 -0
  185. data/lib/praxis/handlers/plain.rb +16 -0
  186. data/lib/praxis/handlers/xml.rb +4 -4
  187. data/lib/praxis/links.rb +13 -3
  188. data/lib/praxis/media_type_identifier.rb +3 -0
  189. data/lib/praxis/multipart/parser.rb +41 -48
  190. data/lib/praxis/multipart/part.rb +196 -3
  191. data/lib/praxis/request.rb +14 -11
  192. data/lib/praxis/request_stages/request_stage.rb +4 -0
  193. data/lib/praxis/request_stages/response.rb +10 -9
  194. data/lib/praxis/request_stages/validate.rb +1 -7
  195. data/lib/praxis/request_stages/validate_params_and_headers.rb +30 -5
  196. data/lib/praxis/request_stages/validate_payload.rb +14 -5
  197. data/lib/praxis/resource_definition.rb +117 -15
  198. data/lib/praxis/response.rb +6 -5
  199. data/lib/praxis/response_definition.rb +51 -5
  200. data/lib/praxis/responses/http.rb +5 -0
  201. data/lib/praxis/responses/multipart_ok.rb +51 -0
  202. data/lib/praxis/responses/validation_error.rb +7 -7
  203. data/lib/praxis/restful_doc_generator.rb +9 -4
  204. data/lib/praxis/route.rb +3 -2
  205. data/lib/praxis/router.rb +26 -16
  206. data/lib/praxis/router/rack.rb +51 -0
  207. data/lib/praxis/router/simple.rb +146 -0
  208. data/lib/praxis/routing_config.rb +2 -2
  209. data/lib/praxis/trait.rb +1 -1
  210. data/lib/praxis/types/fuzzy_hash.rb +49 -0
  211. data/lib/praxis/types/media_type_common.rb +1 -1
  212. data/lib/praxis/types/multipart.rb +47 -12
  213. data/lib/praxis/types/multipart_array.rb +320 -0
  214. data/lib/praxis/types/multipart_array/part_definition.rb +52 -0
  215. data/lib/praxis/validation_handler.rb +10 -0
  216. data/lib/praxis/version.rb +2 -2
  217. data/praxis.gemspec +3 -3
  218. data/spec/api_browser/directives/type_placeholder_spec.js +134 -0
  219. data/spec/api_browser/factories/normalize_attributes_spec.js +97 -0
  220. data/spec/api_browser/factories/template_for_spec.js +67 -0
  221. data/spec/functional_spec.rb +111 -45
  222. data/spec/praxis/action_definition_spec.rb +31 -7
  223. data/spec/praxis/api_definition_spec.rb +2 -2
  224. data/spec/praxis/api_general_info_spec.rb +25 -0
  225. data/spec/praxis/application_spec.rb +24 -11
  226. data/spec/praxis/handlers/xml_spec.rb +55 -33
  227. data/spec/praxis/links_spec.rb +18 -1
  228. data/spec/praxis/media_type_collection_spec.rb +1 -1
  229. data/spec/praxis/media_type_spec.rb +2 -2
  230. data/spec/praxis/multipart/parser_spec.rb +21 -13
  231. data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +1 -1
  232. data/spec/praxis/request_spec.rb +52 -24
  233. data/spec/praxis/{request_stages_action_spec.rb → request_stages/action_spec.rb} +1 -1
  234. data/spec/praxis/{request_stage_spec.rb → request_stages/request_stage_spec.rb} +0 -0
  235. data/spec/praxis/{request_stages_validate_spec.rb → request_stages/validate_spec.rb} +1 -1
  236. data/spec/praxis/resource_definition_spec.rb +30 -4
  237. data/spec/praxis/response_definition_spec.rb +60 -19
  238. data/spec/praxis/response_spec.rb +2 -2
  239. data/spec/praxis/responses/validation_error_spec.rb +33 -16
  240. data/spec/praxis/route_spec.rb +4 -2
  241. data/spec/praxis/router_spec.rb +28 -12
  242. data/spec/praxis/routing_config_spec.rb +11 -5
  243. data/spec/praxis/types/collection_spec.rb +1 -1
  244. data/spec/praxis/types/fuzzy_hash_spec.rb +20 -0
  245. data/spec/praxis/types/multipart_array/part_definition_spec.rb +5 -0
  246. data/spec/praxis/types/multipart_array_spec.rb +334 -0
  247. data/spec/praxis/types/multipart_spec.rb +14 -5
  248. data/spec/spec_app/app/controllers/instances.rb +20 -10
  249. data/spec/spec_app/app/controllers/volumes.rb +8 -4
  250. data/spec/spec_app/app/responses/bulk_response.rb +0 -6
  251. data/spec/spec_app/config/environment.rb +13 -0
  252. data/spec/spec_app/design/api.rb +7 -10
  253. data/spec/spec_app/design/media_types/instance.rb +3 -1
  254. data/spec/spec_app/design/resources/instances.rb +50 -41
  255. data/spec/spec_app/design/resources/volume_snapshots.rb +39 -0
  256. data/spec/spec_app/design/resources/volumes.rb +11 -6
  257. data/spec/spec_helper.rb +3 -1
  258. metadata +125 -218
  259. data/lib/api_browser/app/bower_components/angular-ui-router/src/compat.js +0 -146
  260. data/lib/api_browser/app/bower_components/bootstrap-sass/CNAME +0 -1
  261. data/lib/api_browser/app/bower_components/bootstrap-sass/DOCS-LICENSE +0 -319
  262. data/lib/api_browser/app/bower_components/bootstrap-sass/Gemfile +0 -5
  263. data/lib/api_browser/app/bower_components/bootstrap-sass/Gemfile.lock +0 -14
  264. data/lib/api_browser/app/bower_components/bootstrap-sass/Gruntfile.js +0 -244
  265. data/lib/api_browser/app/bower_components/bootstrap-sass/LICENSE +0 -176
  266. data/lib/api_browser/app/bower_components/bootstrap-sass/LICENSE-MIT +0 -21
  267. data/lib/api_browser/app/bower_components/bootstrap-sass/Rakefile +0 -44
  268. data/lib/api_browser/app/bower_components/bootstrap-sass/_config.yml +0 -25
  269. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/ads.html +0 -1
  270. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/footer.html +0 -34
  271. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/header.html +0 -42
  272. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-about.html +0 -12
  273. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-components.html +0 -137
  274. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-css.html +0 -99
  275. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-customize.html +0 -40
  276. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-getting-started.html +0 -44
  277. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-javascript.html +0 -88
  278. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/nav-main.html +0 -37
  279. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/old-bs-docs.html +0 -8
  280. data/lib/api_browser/app/bower_components/bootstrap-sass/_includes/social-buttons.html +0 -16
  281. data/lib/api_browser/app/bower_components/bootstrap-sass/_layouts/default.html +0 -79
  282. data/lib/api_browser/app/bower_components/bootstrap-sass/_layouts/home.html +0 -47
  283. data/lib/api_browser/app/bower_components/bootstrap-sass/about.html +0 -93
  284. data/lib/api_browser/app/bower_components/bootstrap-sass/browserstack.json +0 -37
  285. data/lib/api_browser/app/bower_components/bootstrap-sass/components.html +0 -3689
  286. data/lib/api_browser/app/bower_components/bootstrap-sass/composer.json +0 -28
  287. data/lib/api_browser/app/bower_components/bootstrap-sass/css.html +0 -2674
  288. data/lib/api_browser/app/bower_components/bootstrap-sass/customize.html +0 -1715
  289. data/lib/api_browser/app/bower_components/bootstrap-sass/dist/css/bootstrap-theme.css +0 -427
  290. data/lib/api_browser/app/bower_components/bootstrap-sass/dist/css/bootstrap-theme.min.css +0 -1
  291. data/lib/api_browser/app/bower_components/bootstrap-sass/dist/css/bootstrap.css +0 -6350
  292. data/lib/api_browser/app/bower_components/bootstrap-sass/dist/css/bootstrap.min.css +0 -1
  293. data/lib/api_browser/app/bower_components/bootstrap-sass/dist/js/bootstrap.js +0 -2002
  294. data/lib/api_browser/app/bower_components/bootstrap-sass/dist/js/bootstrap.min.js +0 -9
  295. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/css/docs.css +0 -1195
  296. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/css/pygments-manni.css +0 -66
  297. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/ico/apple-touch-icon-144-precomposed.png +0 -0
  298. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/ico/favicon.png +0 -0
  299. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/application.js +0 -103
  300. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/customizer.js +0 -332
  301. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/filesaver.js +0 -169
  302. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/holder.js +0 -404
  303. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/ie8-responsive-file-warning.js +0 -12
  304. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/jszip.js +0 -1467
  305. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/less.js +0 -9
  306. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/raw-files.js +0 -3
  307. data/lib/api_browser/app/bower_components/bootstrap-sass/docs-assets/js/uglify.js +0 -14
  308. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/carousel/carousel.css +0 -148
  309. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/carousel/index.html +0 -206
  310. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/grid/grid.css +0 -28
  311. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/grid/index.html +0 -148
  312. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/jumbotron-narrow/index.html +0 -82
  313. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/jumbotron-narrow/jumbotron-narrow.css +0 -79
  314. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/jumbotron/index.html +0 -99
  315. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/jumbotron/jumbotron.css +0 -5
  316. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/justified-nav/index.html +0 -83
  317. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/justified-nav/justified-nav.css +0 -88
  318. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/navbar-fixed-top/index.html +0 -91
  319. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/navbar-fixed-top/navbar-fixed-top.css +0 -4
  320. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/navbar-static-top/index.html +0 -92
  321. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/navbar-static-top/navbar-static-top.css +0 -7
  322. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/navbar/index.html +0 -88
  323. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/navbar/navbar.css +0 -8
  324. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/non-responsive/index.html +0 -101
  325. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/non-responsive/non-responsive.css +0 -116
  326. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/offcanvas/index.html +0 -130
  327. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/offcanvas/offcanvas.css +0 -50
  328. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/offcanvas/offcanvas.js +0 -5
  329. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/carousel.jpg +0 -0
  330. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/grid.jpg +0 -0
  331. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/jumbotron-narrow.jpg +0 -0
  332. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/jumbotron.jpg +0 -0
  333. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/justified-nav.jpg +0 -0
  334. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/navbar-fixed.jpg +0 -0
  335. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/navbar-static.jpg +0 -0
  336. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/navbar.jpg +0 -0
  337. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/non-responsive.jpg +0 -0
  338. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/offcanvas.jpg +0 -0
  339. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/sign-in.jpg +0 -0
  340. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/starter-template.jpg +0 -0
  341. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/sticky-footer-navbar.jpg +0 -0
  342. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/sticky-footer.jpg +0 -0
  343. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/screenshots/theme.jpg +0 -0
  344. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/signin/index.html +0 -50
  345. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/signin/signin.css +0 -40
  346. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/starter-template/index.html +0 -68
  347. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/starter-template/starter-template.css +0 -7
  348. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/sticky-footer-navbar/index.html +0 -91
  349. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/sticky-footer-navbar/sticky-footer-navbar.css +0 -45
  350. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/sticky-footer/index.html +0 -55
  351. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/sticky-footer/sticky-footer.css +0 -38
  352. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/theme/index.html +0 -384
  353. data/lib/api_browser/app/bower_components/bootstrap-sass/examples/theme/theme.css +0 -14
  354. data/lib/api_browser/app/bower_components/bootstrap-sass/fonts/glyphicons-halflings-regular.eot +0 -0
  355. data/lib/api_browser/app/bower_components/bootstrap-sass/fonts/glyphicons-halflings-regular.svg +0 -229
  356. data/lib/api_browser/app/bower_components/bootstrap-sass/fonts/glyphicons-halflings-regular.ttf +0 -0
  357. data/lib/api_browser/app/bower_components/bootstrap-sass/fonts/glyphicons-halflings-regular.woff +0 -0
  358. data/lib/api_browser/app/bower_components/bootstrap-sass/getting-started.html +0 -1021
  359. data/lib/api_browser/app/bower_components/bootstrap-sass/index.html +0 -16
  360. data/lib/api_browser/app/bower_components/bootstrap-sass/javascript.html +0 -1983
  361. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/index.html +0 -52
  362. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/affix.js +0 -25
  363. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/alert.js +0 -62
  364. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/button.js +0 -116
  365. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/carousel.js +0 -87
  366. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/collapse.js +0 -164
  367. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/dropdown.js +0 -219
  368. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/modal.js +0 -196
  369. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/phantom.js +0 -69
  370. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/popover.js +0 -133
  371. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/scrollspy.js +0 -37
  372. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/tab.js +0 -86
  373. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/tooltip.js +0 -437
  374. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/unit/transition.js +0 -13
  375. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/vendor/jquery.js +0 -6
  376. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/vendor/qunit.css +0 -232
  377. data/lib/api_browser/app/bower_components/bootstrap-sass/js/tests/vendor/qunit.js +0 -1510
  378. data/lib/api_browser/app/bower_components/bootstrap-sass/package.json +0 -40
  379. data/lib/api_browser/app/bower_components/lodash/dist/lodash.compat.js +0 -7157
  380. data/lib/api_browser/app/bower_components/lodash/dist/lodash.compat.min.js +0 -61
  381. data/lib/api_browser/app/bower_components/lodash/dist/lodash.js +0 -6785
  382. data/lib/api_browser/app/bower_components/lodash/dist/lodash.min.js +0 -56
  383. data/lib/api_browser/app/bower_components/lodash/dist/lodash.underscore.js +0 -4979
  384. data/lib/api_browser/app/bower_components/lodash/dist/lodash.underscore.min.js +0 -39
  385. data/lib/api_browser/app/js/directives/attribute_table_row.js +0 -17
  386. data/lib/api_browser/app/js/directives/request_body.js +0 -25
  387. data/lib/api_browser/app/js/directives/request_headers.js +0 -17
  388. data/lib/api_browser/app/js/directives/request_parameters.js +0 -17
  389. data/lib/api_browser/app/js/directives/type_label.js +0 -52
  390. data/lib/api_browser/app/js/factories/PayloadTemplates.js +0 -10
  391. data/lib/api_browser/app/js/factories/TemplateProvider.js +0 -45
  392. data/lib/api_browser/app/js/factories/TypeTemplates.js +0 -11
  393. data/lib/api_browser/app/views/directives/attribute_description/_example.html +0 -13
  394. data/lib/api_browser/app/views/directives/attribute_description/_headers.html +0 -8
  395. data/lib/api_browser/app/views/directives/attribute_table_row/_default.html +0 -10
  396. data/lib/api_browser/app/views/directives/attribute_table_row/_links.html +0 -11
  397. data/lib/api_browser/app/views/directives/attribute_table_row/_struct.html +0 -2
  398. data/lib/api_browser/app/views/directives/request_body/_struct.html +0 -1
  399. data/lib/api_browser/app/views/resource/_actions.html +0 -27
@@ -1,9 +1,17 @@
1
1
  module Praxis
2
2
  class ApiGeneralInfo
3
3
 
4
- def initialize(global_info=nil)
5
- @global_info = global_info
4
+ attr_reader :version
5
+
6
+ def initialize(global_info=nil, version: nil)
6
7
  @data = Hash.new
8
+ @global_info = global_info
9
+ @version = version
10
+
11
+ if @global_info.nil? # this *is* the global info
12
+ version_with [:header, :params]
13
+ end
14
+
7
15
  end
8
16
 
9
17
  def get(k)
@@ -40,14 +48,49 @@ module Praxis
40
48
  end
41
49
  end
42
50
 
51
+ def version_with(val=nil)
52
+ if val.nil?
53
+ get(:version_with)
54
+ else
55
+ if @global_info.nil? # this *is* the global info
56
+ Application.instance.versioning_scheme = val
57
+ set(:version_with, val)
58
+ else
59
+ raise "Use of version_with is only allowed in the global part of " \
60
+ "the API definition (but you are attempting to use it in the API " \
61
+ "definition of version #{self.version}"
62
+ end
63
+ end
64
+ end
65
+
66
+ def endpoint(val=nil)
67
+ if val.nil?
68
+ get(:endpoint)
69
+ else
70
+ if @global_info.nil? # this *is* the global info
71
+ set(:endpoint, val)
72
+ else
73
+ raise "Use of endpoint is only allowed in the global part of " \
74
+ "the API definition (but you are attempting to use it in the API " \
75
+ "definition of version #{self.version}"
76
+ end
77
+ end
78
+ end
79
+
80
+
43
81
  def base_path(val=nil)
44
82
  if val
45
83
  return set(:base_path, val)
46
84
  end
47
85
 
48
- if @global_info
86
+ if @global_info # this is for a specific version
87
+ global_path = @global_info.base_path
88
+ if version_with == :path
89
+ global_pattern = Mustermann.new(global_path)
90
+ global_path = global_pattern.expand(Request::API_VERSION_PARAM_NAME => self.version)
91
+ end
92
+
49
93
  version_path = @data.fetch(:base_path,'')
50
- global_path = @global_info.get(:base_path)
51
94
  "#{global_path}#{version_path}"
52
95
  else
53
96
  @data.fetch(:base_path,'')
@@ -62,9 +105,10 @@ module Praxis
62
105
  end
63
106
  end
64
107
 
108
+
65
109
  def describe
66
110
  hash = { schema_version: "1.0".freeze }
67
- [:name, :title, :description, :base_path].each do |attr|
111
+ [:name, :title, :description, :base_path, :version_with, :endpoint].each do |attr|
68
112
  val = self.__send__(attr)
69
113
  hash[attr] = val unless val.nil?
70
114
  end
@@ -20,6 +20,9 @@ module Praxis
20
20
  attr_accessor :handlers
21
21
  attr_accessor :root
22
22
  attr_accessor :error_handler
23
+ attr_accessor :validation_handler
24
+
25
+ attr_accessor :versioning_scheme
23
26
 
24
27
 
25
28
  def self.configure
@@ -31,6 +34,7 @@ module Praxis
31
34
  @resource_definitions = Set.new
32
35
 
33
36
  @error_handler = ErrorHandler.new
37
+ @validation_handler = ValidationHandler.new
34
38
 
35
39
  @router = Router.new(self)
36
40
 
@@ -46,13 +50,17 @@ module Praxis
46
50
  @root = nil
47
51
  @logger = Logger.new(STDOUT)
48
52
  end
49
-
53
+
54
+
50
55
  def setup(root: '.')
56
+ return self unless @app.nil?
57
+
51
58
  @root = Pathname.new(root).expand_path
52
59
 
53
60
  builtin_handlers = {
54
- 'json' => Praxis::Handlers::JSON,
55
- 'x-www-form-urlencoded' => Praxis::Handlers::WWWForm
61
+ 'plain' => Praxis::Handlers::Plain,
62
+ 'json' => Praxis::Handlers::JSON,
63
+ 'x-www-form-urlencoded' => Praxis::Handlers::WWWForm
56
64
  }
57
65
  # Register built-in handlers unless the app already provided its own
58
66
  builtin_handlers.each_pair do |name, handler|
@@ -120,6 +128,6 @@ module Praxis
120
128
  def config=(config)
121
129
  @config.set(config)
122
130
  end
123
-
131
+
124
132
  end
125
133
  end
@@ -49,6 +49,7 @@ module Praxis
49
49
  after(:app) do
50
50
  Praxis::Mapper.finalize!
51
51
  Praxis::Blueprint.finalize!
52
+ Praxis::ResourceDefinition.finalize!
52
53
  end
53
54
 
54
55
  end
@@ -42,6 +42,8 @@ module Praxis
42
42
  application.config do
43
43
  attribute :praxis do
44
44
  attribute :validate_responses, Attributor::Boolean, default: false
45
+ attribute :validate_response_bodies, Attributor::Boolean, default: false
46
+
45
47
  attribute :show_exceptions, Attributor::Boolean, default: false
46
48
  attribute :x_cascade, Attributor::Boolean, default: true
47
49
  end
@@ -14,7 +14,7 @@ module Praxis
14
14
 
15
15
  def call(request)
16
16
  request.action = @action
17
- dispatcher = Dispatcher.current( application: @application)
17
+ dispatcher = Dispatcher.current(application: @application)
18
18
 
19
19
  dispatcher.dispatch(@controller, @action, request)
20
20
  end
@@ -20,6 +20,7 @@ module Praxis
20
20
  order.each do |group|
21
21
  @stages << FileLoader.new(group, application, path: [name, group])
22
22
  end
23
+ setup_deferred_callbacks!
23
24
  end
24
25
  end
25
26
 
@@ -1,6 +1,13 @@
1
1
  module Praxis
2
2
  module Exceptions
3
3
  class Validation < Exception
4
+
5
+ attr_accessor :errors
6
+ def initialize(message, errors: nil)
7
+ super(message)
8
+ @errors = errors
9
+ end
10
+
4
11
  end
5
12
  end
6
13
  end
@@ -0,0 +1,16 @@
1
+ module Praxis
2
+ module Handlers
3
+ class Plain
4
+ # no-op
5
+ def parse(entity)
6
+ entity
7
+ end
8
+
9
+ # no-op
10
+ def generate(structured_data)
11
+ structured_data
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -11,7 +11,7 @@ module Praxis
11
11
  ActiveSupport::XmlMini.backend = 'Nokogiri'
12
12
  rescue LoadError
13
13
  raise Praxis::Exceptions::InvalidConfiguration,
14
- "XML handler depends on builder ~> 3.2 and nokogiri ~> 1.6; please add them to your Gemfile"
14
+ "XML handler depends on builder ~> 3.2 and nokogiri ~> 1.6; please add them to your Gemfile"
15
15
  end
16
16
 
17
17
  # Parse an XML document into structured data.
@@ -40,14 +40,14 @@ module Praxis
40
40
 
41
41
  case type
42
42
  when nil
43
- if node.children.size == 1 && node.child.text?
43
+ if (node.children.size == 1 && node.child.text?) || node.children.size == 0
44
44
  # leaf text node
45
45
  return node.content
46
46
  else
47
47
  # A hash
48
48
  return node.children.each_with_object({}) do |child, hash|
49
49
  next unless child.element? # There might be text fragments like newlines...spaces
50
- hash[child.name] = process(child, child.attributes['type'])
50
+ hash[child.name.underscore] = process(child, child.attributes['type'])
51
51
  end
52
52
  end
53
53
  when "array"
@@ -75,4 +75,4 @@ module Praxis
75
75
  end
76
76
  end
77
77
  end
78
- end
78
+ end
data/lib/praxis/links.rb CHANGED
@@ -49,14 +49,16 @@ module Praxis
49
49
  self
50
50
  end
51
51
 
52
- def self.describe(shallow=false)
53
- super(false) # Links must always describe attributes
52
+ def self.describe(shallow=false,**opts)
53
+ super(false,**opts) # Links must always describe attributes
54
54
  end
55
-
55
+
56
56
  def self._finalize!
57
57
  super
58
58
  if @attribute
59
+ # Master and default views must be set for all attributes, always using their :link view
59
60
  self.define_default_view
61
+ self.define_master_view
60
62
  self.fixup_reference_struct_methods
61
63
  end
62
64
  end
@@ -95,6 +97,14 @@ module Praxis
95
97
  end
96
98
  end
97
99
 
100
+ def self.define_master_view
101
+
102
+ view(:master) {}
103
+ self.attributes.each do |name, attribute|
104
+ view(:master).attribute(name, view: :link)
105
+ end
106
+ end
107
+
98
108
  # Define methods on the inner Struct class for
99
109
  # links that do not have corresponding top-level attributes.
100
110
  # This is primarily necessary only for example generation.
@@ -130,6 +130,9 @@ module Praxis
130
130
  s
131
131
  end
132
132
 
133
+ alias_method :to_str, :to_s
134
+
135
+
133
136
  # If parameters are empty, return self; otherwise return a new object consisting of this type
134
137
  # minus parameters.
135
138
  #
@@ -4,18 +4,23 @@ require 'rack/utils'
4
4
 
5
5
  module Praxis
6
6
  class MultipartParser
7
- EOL = "\r\n"
8
- MULTIPART_BOUNDARY = "AaB03x"
9
- MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
10
- TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
11
- CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
12
- DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})/
13
- RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
14
- BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
15
- BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
16
- MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
17
- MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name="?([^\";]*)"?/ni
18
- MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
7
+ EOL = "\r\n".freeze
8
+ MULTIPART_BOUNDARY = "AaB03x".freeze
9
+ MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n.freeze
10
+ TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/.freeze
11
+ CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i.freeze
12
+ DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})/.freeze
13
+ RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i.freeze
14
+ BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i.freeze
15
+ BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i.freeze
16
+ MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni.freeze
17
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name="?([^\";]*)"?/ni.freeze
18
+ MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni.freeze
19
+
20
+ HEADER_KV = /(?<k>[^:]+): (?<v>.*)/.freeze
21
+ UNTIL_NEWLINE = /\A([^\n]*\n)/.freeze
22
+ TERMINAL_CRLF = /\r\n$/.freeze
23
+
19
24
 
20
25
  BUFSIZE = 16384
21
26
 
@@ -30,7 +35,7 @@ module Praxis
30
35
  @io << chunk
31
36
  end
32
37
  @io.rewind
33
- @parts = Hash.new
38
+ @parts = Array.new
34
39
  end
35
40
 
36
41
  def parse
@@ -53,13 +58,15 @@ module Praxis
53
58
  filename, data = get_data(filename, body, content_type, name, head)
54
59
 
55
60
  parsed_headers = head.split(EOL).each_with_object(Hash.new) do |line, hash|
56
- match = /(?<k>[^:]+): (?<v>.*)/.match(line)
61
+ match = HEADER_KV.match(line)
57
62
  k = match[:k]
58
63
  v = match[:v]
59
64
  hash[k] = v
60
65
  end
61
66
 
62
- @parts[name] = MultipartPart.new(data, parsed_headers, filename: filename)
67
+ part = MultipartPart.new(data, parsed_headers, name: name, filename: filename)
68
+
69
+ @parts << part
63
70
 
64
71
  # break if we're at the end of a buffer, but not if it is the end of a field
65
72
  break if (@buf.empty? && $1 != EOL) || @content_length == -1
@@ -72,17 +79,16 @@ module Praxis
72
79
 
73
80
  private
74
81
  def setup_parse
75
- return false unless @headers['Content-Type'] =~ MULTIPART
82
+ unless (match = MULTIPART.match @headers['Content-Type'])
83
+ return false
84
+ end
76
85
 
77
- @boundary = "--#{$1}"
86
+ @boundary = "--#{match[1]}"
78
87
 
79
88
  @buf = ""
80
89
 
81
90
  @params = Rack::Utils::KeySpaceConstrainedParams.new
82
91
 
83
- #@io = @env['rack.input']
84
- #@io.rewind
85
-
86
92
  @boundary_size = Rack::Utils.bytesize(@boundary) + EOL.size
87
93
 
88
94
  if @content_length = @headers['Content-Length']
@@ -107,10 +113,10 @@ module Praxis
107
113
  raise EOFError, "bad content body" unless content
108
114
  @buf << content
109
115
 
110
- while @buf.gsub!(/\A([^\n]*\n)/, '')
116
+ while @buf.gsub!(UNTIL_NEWLINE, '')
111
117
  read_buffer = $1
112
118
  if read_buffer == full_boundary
113
- return preamble.gsub!(/\r\n$/,'')
119
+ return preamble.gsub!(TERMINAL_CRLF,'')
114
120
  else
115
121
  preamble << read_buffer
116
122
  end
@@ -140,7 +146,7 @@ module Praxis
140
146
 
141
147
  if filename
142
148
  body = Tempfile.new("RackMultipart")
143
- body.binmode if body.respond_to?(:binmode)
149
+ body.binmode if body.respond_to?(:binmode)
144
150
  end
145
151
 
146
152
  next
@@ -182,32 +188,19 @@ module Praxis
182
188
  end
183
189
 
184
190
  def get_data(filename, body, content_type, name, head)
185
- data = nil
186
- if filename == ""
187
- # filename is blank which means no file has been selected
188
- return data
189
- elsif filename
190
- body.rewind
191
-
192
- # Take the basename of the upload's original filename.
193
- # This handles the full Windows paths given by Internet Explorer
194
- # (and perhaps other broken user agents) without affecting
195
- # those which give the lone filename.
196
- filename = filename.split(/[\/\\]/).last
197
-
198
- data = {:filename => filename, :type => content_type,
199
- :name => name, :tempfile => body, :head => head}
200
- elsif !filename && content_type && body.is_a?(IO)
201
- body.rewind
202
-
203
- # Generic multipart cases, not coming from a form
204
- data = {:type => content_type,
205
- :name => name, :tempfile => body, :head => head}
206
- else
207
- data = body
208
- end
191
+ # filename is blank which means no file has been selected
192
+ return nil if filename == ""
193
+
194
+ # Take the basename of the upload's original filename.
195
+ # This handles the full Windows paths given by Internet Explorer
196
+ # (and perhaps other broken user agents) without affecting
197
+ # those which give the lone filename.
198
+ filename = filename.split(/[\/\\]/).last if filename
199
+
200
+ # Rewind any IO bodies so the app can read them at its leisure
201
+ body.rewind if body.is_a?(IO)
209
202
 
210
- [filename, data]
203
+ [filename, body]
211
204
  end
212
205
  end
213
206
  end
@@ -1,21 +1,140 @@
1
1
  module Praxis
2
2
  class MultipartPart
3
+ include Attributor::Type
3
4
 
4
5
  attr_accessor :body
5
6
  attr_accessor :headers
6
7
  attr_accessor :filename
7
-
8
- def initialize(body, headers={}, filename: nil)
8
+ attr_accessor :name
9
+ attr_accessor :payload_attribute
10
+ attr_accessor :headers_attribute
11
+ attr_accessor :filename_attribute
12
+
13
+ def self.check_option!(name, definition)
14
+ case name
15
+ when :payload_attribute, :headers_attribute, :filename_attribute
16
+ unless definition.nil? || definition.kind_of?(Attributor::Attribute)
17
+ raise Attributor::AttributorException.new("Value for option #{name.inspect} must be an Attribute. Got #{definition.class.name}")
18
+ end
19
+ else
20
+ return :unknown
21
+ end
22
+
23
+ :ok
24
+ end
25
+
26
+ def self.native_type
27
+ self
28
+ end
29
+
30
+ def self.example(context=nil, options:{})
31
+ if (payload_attribute = options[:payload_attribute])
32
+ payload = payload_attribute.example(context + ['payload'])
33
+ end
34
+
35
+ headers = if (headers_attribute = options[:headers_attribute])
36
+ headers_attribute.example(context + ['headers'])
37
+ else
38
+ {}
39
+ end
40
+
41
+ name = options[:name]
42
+
43
+ filename = if (filename_attribute = options[:filename_attribute])
44
+ filename_attribute.example(context + ['filename'])
45
+ else
46
+ nil
47
+ end
48
+
49
+ self.new(payload, headers, name: name, filename: filename,
50
+ payload_attribute: payload_attribute,
51
+ headers_attribute: headers_attribute,
52
+ filename_attribute: filename_attribute)
53
+ end
54
+
55
+
56
+ def self.describe(shallow=true, example: nil, options:{})
57
+ hash = super(shallow, example: example)
58
+
59
+ if (payload_attribute = options[:payload_attribute])
60
+ payload_example = example.payload if example
61
+ hash[:payload] = payload_attribute.describe(shallow, example: payload_example)
62
+ end
63
+ if (headers_attribute = options[:headers_attribute])
64
+ headers_example = example.headers if example
65
+ hash[:headers] = headers_attribute.describe(shallow, example: headers_example)
66
+ end
67
+ if (filename_attribute = options[:filename_attribute])
68
+ filename_example = example.filename if example
69
+ hash[:filename] = filename_attribute.describe(shallow, example: filename_example)
70
+ end
71
+
72
+ hash
73
+ end
74
+
75
+ def initialize(body, headers={}, name: nil, filename: nil, payload_attribute: nil, headers_attribute: nil, filename_attribute: nil)
76
+ @name = name
9
77
  @body = body
10
78
  @headers = headers
79
+
80
+ if content_type.nil?
81
+ self.content_type = 'text/plain'
82
+ end
83
+
11
84
  @filename = filename
85
+
86
+ @payload_attribute = payload_attribute
87
+ @headers_attribute = headers_attribute
88
+ @filename_attribute = filename_attribute
89
+
90
+ reset_content_disposition
91
+ end
92
+
93
+ alias_method :payload, :body
94
+ alias_method :payload=, :body=
95
+
96
+ def attribute=(attribute)
97
+ unless self.kind_of?(attribute.type)
98
+ raise ArgumentError, "invalid attribute type #{attribute.type}"
99
+ end
100
+
101
+ if attribute.options.key? :payload_attribute
102
+ @payload_attribute = attribute.options[:payload_attribute]
103
+ end
104
+
105
+ if attribute.options.key? :headers_attribute
106
+ @headers_attribute = attribute.options[:headers_attribute]
107
+ end
108
+
109
+ if attribute.options.key? :filename_attribute
110
+ @filename_attribute = attribute.options[:filename_attribute]
111
+ end
112
+
113
+ end
114
+
115
+ def load_payload(context=Attributor::DEFAULT_ROOT_CONTEXT)
116
+ if self.payload_attribute
117
+ value = if self.payload.kind_of?(String)
118
+ handler.parse(self.payload)
119
+ else
120
+ self.payload
121
+ end
122
+
123
+ self.payload = self.payload_attribute.load(value)
124
+ end
125
+ end
126
+
127
+ def load_headers(context=Attributor::DEFAULT_ROOT_CONTEXT)
128
+ if self.headers_attribute
129
+ self.headers = self.headers_attribute.load(self.headers)
130
+ end
12
131
  end
13
132
 
14
133
  # Determine the content type of this response.
15
134
  #
16
135
  # @return [MediaTypeIdentifier]
17
136
  def content_type
18
- MediaTypeIdentifier.load(headers['Content-Type']).freeze
137
+ @content_type ||= MediaTypeIdentifier.load(headers['Content-Type']).freeze
19
138
  end
20
139
 
21
140
  # Set the content type for this response.
@@ -24,6 +143,7 @@ module Praxis
24
143
  # @return [String]
25
144
  # @param [String,MediaTypeIdentifier] identifier
26
145
  def content_type=(identifier)
146
+ @content_type = nil
27
147
  headers['Content-Type'] = MediaTypeIdentifier.load(identifier).to_s
28
148
  end
29
149
 
@@ -34,9 +154,82 @@ module Praxis
34
154
  def encode!
35
155
  case @body
36
156
  when Hash, Array
157
+ # response payload is structured data; transform it into an entity using the handler
158
+ # implied by the response's media type. If no handler is registered for this
159
+ # name, assume JSON as a default handler.
37
160
  @body = JSON.pretty_generate(@body)
38
161
  end
39
162
  end
40
163
 
164
+ def validate_headers(context=Attributor::DEFAULT_ROOT_CONTEXT)
165
+ return [] unless self.headers_attribute
166
+
167
+ self.headers_attribute.validate(headers, context + ['headers'])
168
+ end
169
+
170
+ def validate_payload(context=Attributor::DEFAULT_ROOT_CONTEXT)
171
+ return [] unless self.payload_attribute
172
+
173
+ self.payload_attribute.validate(payload, context + ['payload'])
174
+ end
175
+
176
+ def validate_filename(context=Attributor::DEFAULT_ROOT_CONTEXT)
177
+ return [] unless self.filename_attribute
178
+
179
+ self.filename_attribute.validate(filename, context + ['filename'])
180
+ end
181
+
182
+ def validate(context=Attributor::DEFAULT_ROOT_CONTEXT)
183
+ errors = validate_headers(context)
184
+ errors.push *validate_payload(context)
185
+ errors.push *validate_filename(context)
186
+ end
187
+
188
+ def reset_content_disposition
189
+ self.headers['Content-Disposition'] = begin
190
+ disposition = "form-data; name=#{name}"
191
+ if filename
192
+ disposition += "; filename=#{filename}"
193
+ end
194
+
195
+ disposition
196
+ end
197
+ end
198
+
199
+
200
+ def name=(name)
201
+ @name = name
202
+ reset_content_disposition
203
+ name
204
+ end
205
+
206
+ def filename=(filename)
207
+ @filename = filename
208
+ reset_content_disposition
209
+ filename
210
+ end
211
+
212
+ def handler
213
+ handlers = Praxis::Application.instance.handlers
214
+ (content_type && handlers[content_type.handler_name]) || handlers['json']
215
+ end
216
+
217
+ def dump(**opts)
218
+ header_string = self.headers.collect do |name, value|
219
+ "#{name}: #{value}"
220
+ end.join("\r\n")
221
+
222
+ body = self.payload_attribute.dump(self.payload, **opts)
223
+
224
+ body_string = case body
225
+ when Hash, Array
226
+ handler.generate(body)
227
+ else
228
+ body
229
+ end
230
+
231
+ "#{header_string}\r\n\r\n#{body_string}"
232
+ end
233
+
41
234
  end
42
235
  end