praxis 0.20.1 → 0.21

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +2 -2
  4. data/CHANGELOG.md +36 -0
  5. data/lib/api_browser/Gruntfile.js +33 -3
  6. data/lib/api_browser/app/index.html +3 -0
  7. data/lib/api_browser/app/js/factories/Example.js +4 -0
  8. data/lib/api_browser/app/js/factories/template_for.js +14 -0
  9. data/lib/api_browser/app/js/filters/attribute_name.js +3 -2
  10. data/lib/api_browser/app/js/filters/header_info.js +9 -0
  11. data/lib/api_browser/app/views/action.html +11 -2
  12. data/lib/api_browser/app/views/controller.html +5 -5
  13. data/lib/api_browser/app/views/menu.html +1 -1
  14. data/lib/api_browser/app/views/type.html +4 -23
  15. data/lib/api_browser/app/views/type/details.html +7 -4
  16. data/lib/api_browser/app/views/types/main/array.html +22 -0
  17. data/lib/api_browser/app/views/types/main/default.html +23 -0
  18. data/lib/api_browser/app/views/types/main/hash.html +23 -0
  19. data/lib/praxis.rb +2 -0
  20. data/lib/praxis/action_definition.rb +0 -8
  21. data/lib/praxis/api_definition.rb +11 -6
  22. data/lib/praxis/api_general_info.rb +13 -0
  23. data/lib/praxis/bootloader.rb +11 -5
  24. data/lib/praxis/docs/generator.rb +31 -11
  25. data/lib/praxis/docs/link_builder.rb +30 -0
  26. data/lib/praxis/extensions/field_expansion.rb +2 -2
  27. data/lib/praxis/media_type.rb +1 -1
  28. data/lib/praxis/middleware_app.rb +30 -0
  29. data/lib/praxis/resource_definition.rb +24 -2
  30. data/lib/praxis/response.rb +2 -1
  31. data/lib/praxis/response_definition.rb +2 -2
  32. data/lib/praxis/responses/http.rb +28 -92
  33. data/lib/praxis/responses/validation_error.rb +4 -1
  34. data/lib/praxis/tasks/api_docs.rb +11 -24
  35. data/lib/praxis/trait.rb +12 -7
  36. data/lib/praxis/validation_handler.rb +2 -1
  37. data/lib/praxis/version.rb +1 -1
  38. data/praxis.gemspec +11 -7
  39. data/spec/api_browser/filters/attribute_name_spec.js +2 -2
  40. data/spec/praxis/action_definition_spec.rb +23 -1
  41. data/spec/praxis/bootloader_spec.rb +28 -0
  42. data/spec/praxis/extensions/field_expansion_spec.rb +10 -0
  43. data/spec/praxis/middleware_app_spec.rb +55 -0
  44. data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +8 -3
  45. data/spec/praxis/resource_definition_spec.rb +51 -2
  46. data/spec/praxis/response_definition_spec.rb +16 -4
  47. data/spec/praxis/response_spec.rb +1 -1
  48. data/spec/praxis/trait_spec.rb +13 -0
  49. data/spec/spec_app/config/environment.rb +11 -1
  50. metadata +30 -25
  51. data/lib/praxis/restful_doc_generator.rb +0 -439
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3cc6641a8ee79fff261135fc27c46987c5118963
4
- data.tar.gz: 23e6e0f445620738409e10818044f66f923c940a
3
+ metadata.gz: d0a0928d3b9a59294d32767a333b73c03a0a7955
4
+ data.tar.gz: 840261c0c55f18280f3c32d53a62053ed8582834
5
5
  SHA512:
6
- metadata.gz: 2f956167d5bf702c127b12b632a1b4d7ac3a404088d9f1fc226c813ffcd5bb93d4c857747bcd24acd07f449fb4123240e768716c0f433182588fd33d85143682
7
- data.tar.gz: 956e4d47a1b7fecdb9f41da2343422f1f243b777b64402fccd9fd3c2e92a0b878b2c907f7c79d39b7d38b1a81f2a8a7564fc5f80ad43acce3144d778c46c6b6f
6
+ metadata.gz: 2c7d30737b31ec8204e030ca43577d8a4620b4a16ee3f9ec00c40ef740f08db8c7c21d9bc1d06dc7ddc56e2223dc0f9aaa990526a6f92f7297493dcf02158c11
7
+ data.tar.gz: b1e794d55ef40a616c21adeb8a72cea53b796e4a943636c5a16a576f282298c6f573b730f708ab2a0bbb050784c56c3b1fe53f760488c3100d98958a2b50f6eb
@@ -1 +1 @@
1
- 2.2.2
1
+ 2.3
@@ -2,8 +2,8 @@ language: ruby
2
2
  cache: bundler
3
3
  sudo: false
4
4
  rvm:
5
- - 2.1.5
6
- - 2.2.2
5
+ - 2.2.5
6
+ - 2.3.1
7
7
  node_js:
8
8
  - "0.10"
9
9
  branches:
@@ -2,6 +2,42 @@
2
2
 
3
3
  ## next
4
4
 
5
+ ## 0.21
6
+
7
+ * Protect against `MediaType`s that do not have any links defined.
8
+ * More robust scanning of existing types when generating docs. Some types might have not been
9
+ properly reported in the `schemas` section of the JSON docs if they were only used somewhere
10
+ deep in some other type or action hierarchy
11
+ * Build doc browser support for defining top-level home pages for types.
12
+ Apps can achieve the override by registering templates that respond to the ‘main’ type (instead of
13
+ the other existing ‘label’,’embedded’ and ‘standalone’ modes).
14
+ * Enhance doc browser to show header and location expectations on action responses that have them
15
+ defined
16
+ * Allow Plugin registration without requiring config_key
17
+ * registration will select a default config_key based on the class name
18
+ * A new `documentation_url` global directive is exposed for authors to be able to
19
+ indicate where documentation will be hosted.
20
+ * If this is provided, the default *validation handler* will add a `documentation`
21
+ key to the response pointing at a url that should correspond to the documentation
22
+ for the resource the user was requesting.
23
+ * `Praxis::Docs::LinkBuilder` can be used to generate these documentation urls from
24
+ the praxis application.
25
+ * You can now switch your doc browser to use HTML5 style urls (i.e.
26
+ `/1.0/type/V1-MediaTypes-PriceFilter` instead of
27
+ `/index.html#/1.0/type/V1-MediaTypes-PriceFilter`).
28
+ * Remove deprecated rake tasks.
29
+ * Remove some remaining inline styling in doc browser.
30
+ * Adds a `ExampleProvider.removeHandlersForKey` call. You can use `ExampleProvider.removeHandlersForKey('general')`
31
+ to get rid of the default example if required.
32
+ * Make Traits accumulate block definitions for `params`,`headers` and `payload` rather than overriding them.
33
+ * Switch to lazy evaluation of `base_params` from `ApiDefinition` to properly inherit them into the resources
34
+ and their corresponding actions even before the application's `MediaTtypes` have been finalized.
35
+ * Built the `MiddlewareApp` class to make it easy to run a Praxis app mounted as an intercepting
36
+ middleware which will only forward requests down the stack if they didn't match any of its routes.
37
+ * Note: it properly skips forwarding when 404s are purposedly returned by the application itself.
38
+ * Note2: it also respects the `X-Cascade=pass` conventions.
39
+
40
+
5
41
  ## 0.20.1
6
42
 
7
43
  * Doc generation: handle SimpleMediaTypes so that they don’t show up in the generated schemas.
@@ -13,7 +13,8 @@ module.exports = function(grunt) {
13
13
  pluginPaths = process.env.PLUGIN_PATHS ? process.env.PLUGIN_PATHS.split(':') : [],
14
14
  buildPath = userDocsPath + '/output',
15
15
  browserPort = process.env.DOC_PORT || '9090',
16
- bowerPaths = pluginPaths.concat([userDocsPath]).map(function(f) { return f + '/bower.json'; });
16
+ bowerPaths = pluginPaths.concat([userDocsPath]).map(function(f) { return f + '/bower.json'; }),
17
+ basePath = process.env.BASE_PATH || '';
17
18
 
18
19
  var cssOutput = {};
19
20
  cssOutput[buildPath + '/css/main.css'] = userDocsPath + '/styles.scss';
@@ -75,7 +76,28 @@ module.exports = function(grunt) {
75
76
  '.tmp',
76
77
  '.data',
77
78
  'app'
78
- ])
79
+ ]),
80
+ // This is to handle support of using $locationProvider.html5Mode(true).
81
+ middleware: function(connect, options, middlewares) {
82
+ middlewares.push(function(req, res, next) {
83
+ var segments = require('url').parse(req.url).pathname.split('/'),
84
+ last = segments[segments.length - 1];
85
+ if (last.split('.').length === 1) {
86
+ try {
87
+ var index = grunt.file.read('.tmp/index.html');
88
+ res.setHeader("Content-Type", "text/html");
89
+ res.write(index, 'utf8');
90
+ res.end()
91
+ } catch(e) {
92
+ next(e);
93
+ }
94
+ } else {
95
+ next();
96
+ }
97
+ });
98
+
99
+ return middlewares;
100
+ }
79
101
  }
80
102
  },
81
103
  dist: {
@@ -362,7 +384,15 @@ module.exports = function(grunt) {
362
384
  html: [buildPath + '/index.html'],
363
385
  css: [buildPath + '/css/{,*/}*.css'],
364
386
  options: {
365
- assetsDirs: [buildPath]
387
+ assetsDirs: [buildPath],
388
+ blockReplacements: {
389
+ base: function(block) {
390
+ if (basePath !== '') {
391
+ return '<base href="' + basePath + '">';
392
+ }
393
+ return '';
394
+ }
395
+ }
366
396
  }
367
397
  },
368
398
 
@@ -6,6 +6,9 @@
6
6
  <title ng-bind="subtitle ? title + ' – ' + subtitle : title">API Browser</title>
7
7
  <meta name="description" content="">
8
8
  <meta name="viewport" content="width=device-width">
9
+ <!-- build:base base -->
10
+ <base src="/">
11
+ <!-- endbuild -->
9
12
  <!-- build:css({.tmp,app,/}) css/other.css -->
10
13
  <!-- bower:css -->
11
14
  <!-- endbower -->
@@ -10,6 +10,10 @@ app.provider('Examples', function() {
10
10
  });
11
11
  };
12
12
 
13
+ this.removeHandlersForKey = function(key) {
14
+ registry[key] = [];
15
+ };
16
+
13
17
  this.register('general', 'General', function() {
14
18
  return 'views/examples/general.html';
15
19
  });
@@ -80,6 +80,20 @@ app.provider('templateFor', function() {
80
80
  }
81
81
  });
82
82
 
83
+ this.register(function mainPageResolver($family, $requestedTemplate) {
84
+ 'ngInject';
85
+ if ($requestedTemplate === 'main') {
86
+ switch ($family) {
87
+ case 'hash':
88
+ return 'views/types/main/hash.html';
89
+ case 'array':
90
+ return 'views/types/main/array.html';
91
+ default:
92
+ return 'views/types/main/default.html';
93
+ }
94
+ }
95
+ });
96
+
83
97
  this.$get = function($injector, prepareTemplate) {
84
98
  return function(type, templateType) {
85
99
  for (var i = 0; i < resolvers.length; i++) {
@@ -1,8 +1,9 @@
1
1
  app.filter('attributeName', function($sce) {
2
2
  return function(input) {
3
3
  var parts = input.split('.');
4
- if (parts.length == 2) {
5
- return $sce.trustAsHtml('<span class="attribute-prefix">' + parts[0] + '.</span>' + parts[1]);
4
+ if (parts.length > 1) {
5
+ var prefix = _.take(parts, parts.length-1).join('.');
6
+ return $sce.trustAsHtml('<span class="attribute-prefix">' + prefix + '.</span>' + _.last(parts));
6
7
  }
7
8
  return $sce.trustAsHtml(input);
8
9
  };
@@ -0,0 +1,9 @@
1
+ app.filter('headerInfo', function() {
2
+ return function(info) {
3
+ if (info.value !== true) {
4
+ return info.value + ' (' + info.type + ')';
5
+ } else {
6
+ return '(any value)';
7
+ }
8
+ };
9
+ });
@@ -56,12 +56,21 @@
56
56
  <h5>Description</h5>
57
57
  <div ng-bind-html="response.description | markdown"></div>
58
58
  </div>
59
- <h5>Status</h5>
60
- <p>{{ response.status }}</p>
59
+ <h5>Status: {{ response.status }}</h5>
61
60
  <div ng-if="response.examples.json">
62
61
  <h5>Content-Type</h5>
63
62
  <p>{{ response.examples.json.content_type }}</p>
64
63
  </div>
64
+ <div ng-if="response.location">
65
+ <h5>Location:</h5>
66
+ <p>{{response.location.value}} <em>({{response.location.type}})</em></p>
67
+ </div>
68
+ <div ng-hide="response.headers | isEmpty">
69
+ <h5>Headers:</h5>
70
+ <div ng-repeat="(hname, hinfo) in response.headers">
71
+ <p> {{hname}}: {{ hinfo | headerInfo }}</p>
72
+ </div>
73
+ </div>
65
74
  <div ng-if="response.payload.id">
66
75
  <h5>Media Type</h5>
67
76
  <p><type-placeholder template="label" type="response.payload"></type-placeholder></p>
@@ -5,7 +5,7 @@
5
5
  <div class="row">
6
6
  <div class="col-lg-12">
7
7
  <h1 class="page-header">
8
- {{ controller.name | resourceName }}
8
+ {{ controller.display_name || (controller.name | resourceName) }}
9
9
  </h1>
10
10
 
11
11
  <p ng-bind-html="controller.description | markdown"></p>
@@ -16,7 +16,7 @@
16
16
  </p>
17
17
  </div>
18
18
  </div>
19
- <div class="row" ng-if="controller.actions.length">
19
+ <div class="row actions-overview" ng-if="controller.actions.length">
20
20
  <div class="col-lg-12">
21
21
  <h2>Actions</h2>
22
22
  <div class="table-responsive">
@@ -30,13 +30,13 @@
30
30
  </thead>
31
31
  <tbody>
32
32
  <tr ng-repeat="action in controller.actions">
33
- <td>
33
+ <td class="action-overview-name">
34
34
  <a ui-sref="root.action({action: action.name, controller: controllerName, version: apiVersion})">{{ action.name }}</a>
35
35
  </td>
36
- <td>
36
+ <td class="action-overview-url">
37
37
  <url action="action"></url>
38
38
  </td>
39
- <td style="font-size: 110%;">
39
+ <td class="action-overview-description">
40
40
  <p ng-bind-html="action.description | markdown"></p>
41
41
  </td>
42
42
  </tr>
@@ -2,7 +2,7 @@
2
2
  <div class="row">
3
3
  <div class="col-sm-12">
4
4
  <p>
5
- <div ng-if="versions.length > 1" dropdown auto-close="outsideClick">
5
+ <div ng-if="versions.length > 1" dropdown dropdown-append-to-body>
6
6
  <button type="button" class="btn btn-success" dropdown-toggle ng-disabled="disabled">
7
7
  {{:: versionLabel}}: {{selectedVersion}} <span class="caret"></span>
8
8
  </button>
@@ -1,25 +1,6 @@
1
- <!-- Type -->
2
-
3
- <div ng-if="error" class="alert alert-danger">
4
- <p>The requested type could not be found.</p>
5
- </div>
6
- <div ng-if="type">
7
- <div class="row">
8
- <div class="col-lg-12">
9
- <h1>
10
- {{ type.name | resourceName }}
11
- </h1>
12
- </div>
13
- </div>
14
- <div class="row" ng-if="controllers.length">
15
- <div class="col-lg-12">
16
- <h3>Served by</h3>
17
- <ul>
18
- <li ng-repeat="controller in controllers">
19
- <a ui-sref="root.controller({version: apiVersion, controller: controller.id})">{{ controller.name | resourceName }}</a>
20
- </li>
21
- </ul>
22
- </div>
1
+ <div ng-if="type">
2
+ <div ng-if="error" class="alert alert-danger">
3
+ <p>The requested type could not be found.</p>
23
4
  </div>
24
- <ng-include src="'views/type/details.html'" />
5
+ <type-placeholder type="type" template="main" details="type.attributes"></type-placeholder>
25
6
  </div>
@@ -1,10 +1,13 @@
1
1
  <div class="row" ng-if="type">
2
2
  <div class="col-lg-12">
3
- <h2>Schema</h2>
4
- <p ng-if="type.identifier"><b>Media-type identifier: {{ type.identifier }}</b></p>
5
- <div ng-if="type.description" ng-bind-html="type.description | markdown"></div>
3
+ <div ng-if="type.attributes">
4
+ <h2>Schema</h2>
5
+ <p ng-if="type.identifier"><b>Media-type identifier: {{ type.identifier }}</b></p>
6
+ <div ng-if="type.description" ng-bind-html="type.description | markdown"></div>
6
7
 
7
- <attribute-table attributes="type.attributes"></attribute-table>
8
+ <attribute-table attributes="type.attributes"></attribute-table>
9
+ </div>
10
+ <div ng-if="type.attributes === undefined" type-placeholder type="type" template="standalone" name="type.name" details="type"></div>
8
11
  </div>
9
12
  </div>
10
13
  <div class="row" ng-if="views.length">
@@ -0,0 +1,22 @@
1
+ <div ng-if="type">
2
+ <div class="row">
3
+ <div class="col-lg-12">
4
+ <h1>
5
+ {{ type.name | resourceName }}
6
+ </h1>
7
+ </div>
8
+ </div>
9
+ <div class="row" ng-if="controllers.length">
10
+ <div class="col-lg-12">
11
+ <h3>Served by</h3>
12
+ <ul>
13
+ <li ng-repeat="controller in controllers">
14
+ <a ui-sref="root.controller({version: apiVersion, controller: controller.id})">{{ controller.name | resourceName }}</a>
15
+ </li>
16
+ </ul>
17
+ </div>
18
+ </div>
19
+ <p>A Collection of:</p>
20
+
21
+ <type-placeholder type="type.member_attribute.type" template="standalone" details="type.member_attribute.type.attributes"></type-placeholder>
22
+ </div>
@@ -0,0 +1,23 @@
1
+ <div ng-if="error" class="alert alert-danger">
2
+ <p>The requested type could not be found.</p>
3
+ </div>
4
+ <div ng-if="type">
5
+ <div class="row">
6
+ <div class="col-lg-12">
7
+ <h1>
8
+ {{ type.name | resourceName }}
9
+ </h1>
10
+ </div>
11
+ </div>
12
+ <div class="row" ng-if="controllers.length">
13
+ <div class="col-lg-12">
14
+ <h3>Served by</h3>
15
+ <ul>
16
+ <li ng-repeat="controller in controllers">
17
+ <a ui-sref="root.controller({version: apiVersion, controller: controller.id})">{{ controller.name | resourceName }}</a>
18
+ </li>
19
+ </ul>
20
+ </div>
21
+ </div>
22
+ <ng-include src="'views/type/details.html'" />
23
+ </div>
@@ -0,0 +1,23 @@
1
+ <div ng-if="error" class="alert alert-danger">
2
+ <p>The requested type could not be found.</p>
3
+ </div>
4
+ <div ng-if="type">
5
+ <div class="row">
6
+ <div class="col-lg-12">
7
+ <h1>
8
+ {{ type.name | resourceName }}
9
+ </h1>
10
+ </div>
11
+ </div>
12
+ <div class="row" ng-if="controllers.length">
13
+ <div class="col-lg-12">
14
+ <h3>Served by</h3>
15
+ <ul>
16
+ <li ng-repeat="controller in controllers">
17
+ <a ui-sref="root.controller({version: apiVersion, controller: controller.id})">{{ controller.name | resourceName }}</a>
18
+ </li>
19
+ </ul>
20
+ </div>
21
+ </div>
22
+ <ng-include src="'views/type/details.html'" />
23
+ </div>
@@ -45,10 +45,12 @@ module Praxis
45
45
 
46
46
  autoload :Stats, 'praxis/stats'
47
47
  autoload :Notifications, 'praxis/notifications'
48
+ autoload :MiddlewareApp, 'praxis/middleware_app'
48
49
 
49
50
  autoload :RestfulDocGenerator, 'praxis/restful_doc_generator'
50
51
  module Docs
51
52
  autoload :Generator, 'praxis/docs/generator'
53
+ autoload :LinkBuilder, 'praxis/docs/link_builder'
52
54
  end
53
55
 
54
56
  # types
@@ -118,14 +118,6 @@ module Praxis
118
118
  update_attribute(@params, opts, block)
119
119
  else
120
120
  @params = create_attribute(type, **opts, &block)
121
- if (api_info = ApiDefinition.instance.info(resource_definition.version))
122
- if api_info.base_params
123
- base_attrs = api_info.base_params.attributes
124
- @params.attributes.merge!(base_attrs) do |key, oldval, newval|
125
- oldval
126
- end
127
- end
128
- end
129
121
  end
130
122
 
131
123
  @params
@@ -92,17 +92,22 @@ module Praxis
92
92
  end
93
93
 
94
94
  define do |api|
95
- api.response_template :ok do |media_type: |
96
- media_type media_type
95
+ api.response_template :ok do |media_type: , location: nil, headers: nil, description: nil |
97
96
  status 200
98
- description 'Standard response for successful HTTP requests.'
97
+ description( description || 'Standard response for successful HTTP requests.' )
98
+
99
+ media_type media_type
100
+ location location
101
+ headers headers if headers
99
102
  end
100
103
 
101
- api.response_template :created do |location: nil, media_type: nil|
104
+ api.response_template :created do |media_type: nil, location: nil, headers: nil, description: nil|
105
+ status 201
106
+ description( description || 'The request has been fulfilled and resulted in a new resource being created.' )
107
+
102
108
  media_type media_type if media_type
103
109
  location location
104
- status 201
105
- description 'The request has been fulfilled and resulted in a new resource being created.'
110
+ headers headers if headers
106
111
  end
107
112
  end
108
113