praxis 0.20.1 → 0.21

Sign up to get free protection for your applications and to get access to all the features.
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