praxis 0.15.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -2
- data/lib/api_browser/Gruntfile.js +6 -1
- data/lib/praxis.rb +2 -14
- data/lib/praxis/action_definition.rb +45 -16
- data/lib/praxis/api_definition.rb +36 -17
- data/lib/praxis/api_general_info.rb +59 -16
- data/lib/praxis/request_stages/request_stage.rb +2 -6
- data/lib/praxis/resource_definition.rb +50 -17
- data/lib/praxis/router.rb +10 -11
- data/lib/praxis/routing_config.rb +65 -0
- data/lib/praxis/tasks/routes.rb +8 -4
- data/lib/praxis/trait.rb +107 -0
- data/lib/praxis/types/media_type_common.rb +1 -1
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +1 -1
- data/spec/functional_spec.rb +16 -3
- data/spec/praxis/action_definition_spec.rb +107 -5
- data/spec/praxis/api_definition_spec.rb +67 -44
- data/spec/praxis/api_general_info_spec.rb +28 -14
- data/spec/praxis/media_type_spec.rb +33 -6
- data/spec/praxis/resource_definition_spec.rb +46 -12
- data/spec/praxis/router_spec.rb +9 -15
- data/spec/praxis/routing_config_spec.rb +89 -0
- data/spec/praxis/trait_spec.rb +38 -0
- data/spec/spec_app/app/controllers/instances.rb +6 -2
- data/spec/spec_app/design/resources/instances.rb +19 -10
- data/spec/spec_app/design/resources/volumes.rb +3 -3
- data/spec/support/spec_resource_definitions.rb +11 -6
- data/tasks/thor/example.rb +43 -35
- metadata +8 -6
- data/lib/praxis/skeletor/restful_routing_config.rb +0 -55
- data/spec/praxis/restful_routing_config_spec.rb +0 -101
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2498e7738d76689742c8f8d3486e1e194c3c0e23
|
4
|
+
data.tar.gz: 989b01e640dd2ad1dbc8d67a320add322d76ec36
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0007fdd60eb2186c06e2d8652a4daa1e09bd8dd7d7f1c84109605d946bf32070fccb177c3fdaf4129d498610a957b44ca4a3aed4f53ddb9699ff9abbd7f2a7c
|
7
|
+
data.tar.gz: 729ea4407e325d64658f690e8f6ae9e6716bb494b36b0cf97a33e2f372901e65cf4e4330e4a4648ce867f60db3e0cbf34ae7f2d906455c83ae7bc1e2563c6660
|
data/CHANGELOG.md
CHANGED
@@ -2,17 +2,43 @@
|
|
2
2
|
|
3
3
|
## next
|
4
4
|
|
5
|
+
## 0.16.0
|
6
|
+
|
7
|
+
* Overhauled traits: they're now represented by a `Trait` class, which are created from `ApiDefinition#trait`.
|
8
|
+
* `ApiDefinition#describe` will also include details of the defined traits.
|
9
|
+
* `ResourceDefinition#describe` and `ActionDefinition#describe` will also include the names of the used traits.
|
10
|
+
* *Note*: this may break some existing trait use cases, as they are now more-defined in their behavior, rather than simply stored blocks that are `instance_eval`-ed on the target.
|
11
|
+
* Deprecated `ResourceDefinition.routing`. Use `ResourceDefinition.prefix` to define resource-level route prefixes instead.
|
12
|
+
* Significantly refactored route generation.
|
13
|
+
* The `base_path` property defined in `ApiDefinition#info` will now appear in the routing paths 'base' (instead of simply being used for documentation purposes).
|
14
|
+
*Note*: unlike other info at that level, a global (unversioned) `base_path` is *not* overriden by specific version, rather the specific version's path is appended to the global path.
|
15
|
+
* Any prefixes set on a `ResourceDefinition` or inside a `routing` block of an ActionDefinition are now additive. For example:
|
16
|
+
* Setting a "/myresource" prefix in a "MyResource" definition, and setting a "/myaction" prefix within an action of that resource definition will result in a route containing the following segments ".../myresource/myaction...".
|
17
|
+
* Prefixes can be equally set by including `Traits`, which will follow exactly the same additive rules.
|
18
|
+
* To break the additive nature of the prefixes one can use a couple of different options:
|
19
|
+
* Define the action route path with "//" to make it absolute, i.e. a path like "//people" would not include any defined prefix.
|
20
|
+
* Explicitly clear the prefix by setting the prefix to `''` or `'//'`.
|
21
|
+
* Added `base_params` to `ApiDefinition#info` as a way to share common action params
|
22
|
+
* `base_params` may be defined for a specific Api version, which will make sharing params across all Resource definitions of that version)
|
23
|
+
* or `base_params` may be defined in the Global Api section, which will make the parameters shared across all actions of all defined Api versions.
|
24
|
+
* Fixed `MediaType#describe` to include the correct string representation of its identifier.
|
25
|
+
* Allow route options to be passed to the underlying router (i.e. Mustermann at the moment)
|
26
|
+
* routes defined in the `routing` blocks can now take any extra options which will be passed down to the Mustermann routing engine. Unknown options will be ignored!
|
27
|
+
* Displaying routes (`praxis routes` or `rake praxis:routes`) will now include any options defined in a route.
|
28
|
+
* Added an example on the instances resource of the embedded spec_app to show how to use the advanced `*` pattern and the `:except` Mustermann options (along with the required `:splat` attribute).
|
29
|
+
* Spruced up the example app (generator) to use the latest `prefix` and `trait` changes
|
30
|
+
|
5
31
|
## 0.15.0
|
6
32
|
|
7
33
|
* Fixed handling of no app or design file groups defined in application layout.
|
8
|
-
* Handled and added warning message for doc generation task when no routing block is defined for an action.
|
34
|
+
* Handled and added warning message for doc generation task when no routing block is defined for an action.
|
9
35
|
* Improved `link` method in `MediaType` attribute definition to support inheriting the type from the `:using` option if if that specifies an attribute. For example: `link :posts, using: :posts_summary` would use the type of the `:posts_summary` attribute.
|
10
36
|
* Fixed generated `Links` accessors to properly load the returned value.
|
11
37
|
* Added `MediaTypeIdentifier` class to parse and manipulate Content-Type headers and Praxis::MediaType identifiers.
|
12
38
|
* Created a registry for media type handlers that parse and generate document bodies with formats other than JSON.
|
13
39
|
* Given a structured-data response, Praxis will convert it to JSON, XML or other formats based on the handler indicated by its Content-Type.
|
14
40
|
* Given a request, Praxis will use the handler indicated by its Content-Type header to parse the body into structured data.
|
15
|
-
* Fixed bug allowing "praxis new" to work when Praxis is installed as a system (non-bundled) gem.
|
41
|
+
* Fixed bug allowing "praxis new" to work when Praxis is installed as a system (non-bundled) gem.
|
16
42
|
* Fixed doc generation code for custom types
|
17
43
|
* Hardened Multipart type loading
|
18
44
|
|
data/lib/praxis.rb
CHANGED
@@ -7,17 +7,6 @@ require 'active_support/concern'
|
|
7
7
|
|
8
8
|
$:.unshift File.dirname(__FILE__)
|
9
9
|
|
10
|
-
module Attributor
|
11
|
-
class DSLCompiler
|
12
|
-
def use(name)
|
13
|
-
unless Praxis::ApiDefinition.instance.traits.has_key? name
|
14
|
-
raise Exceptions::InvalidTrait.new("Trait #{name} not found in the system")
|
15
|
-
end
|
16
|
-
self.instance_eval(&Praxis::ApiDefinition.instance.traits[name])
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
10
|
require 'mime'
|
22
11
|
module MIME
|
23
12
|
class Header
|
@@ -48,8 +37,10 @@ module Praxis
|
|
48
37
|
autoload :ResponseTemplate, 'praxis/response_template'
|
49
38
|
autoload :Route, 'praxis/route'
|
50
39
|
autoload :Router, 'praxis/router'
|
40
|
+
autoload :RoutingConfig, 'praxis/routing_config'
|
51
41
|
autoload :SimpleMediaType, 'praxis/simple_media_type'
|
52
42
|
autoload :Stage, 'praxis/stage'
|
43
|
+
autoload :Trait, 'praxis/trait'
|
53
44
|
|
54
45
|
autoload :Stats, 'praxis/stats'
|
55
46
|
autoload :Notifications, 'praxis/notifications'
|
@@ -121,7 +112,4 @@ module Praxis
|
|
121
112
|
autoload :Response, 'praxis/request_stages/response'
|
122
113
|
end
|
123
114
|
|
124
|
-
module Skeletor
|
125
|
-
autoload :RestfulRoutingConfig, 'praxis/skeletor/restful_routing_config'
|
126
|
-
end
|
127
115
|
end
|
@@ -6,6 +6,7 @@
|
|
6
6
|
#
|
7
7
|
# Plugins may be used to extend this Config DSL.
|
8
8
|
#
|
9
|
+
|
9
10
|
module Praxis
|
10
11
|
class ActionDefinition
|
11
12
|
|
@@ -15,6 +16,7 @@ module Praxis
|
|
15
16
|
attr_reader :primary_route
|
16
17
|
attr_reader :named_routes
|
17
18
|
attr_reader :responses
|
19
|
+
attr_reader :traits
|
18
20
|
|
19
21
|
# opaque hash of user-defined medata, used to decorate the definition,
|
20
22
|
# and also available in the generated JSON documents
|
@@ -36,6 +38,7 @@ module Praxis
|
|
36
38
|
@responses = Hash.new
|
37
39
|
@metadata = Hash.new
|
38
40
|
@routes = []
|
41
|
+
@traits = []
|
39
42
|
|
40
43
|
if (media_type = resource_definition.media_type)
|
41
44
|
if media_type.kind_of?(Class) && media_type < Praxis::Types::MediaTypeCommon
|
@@ -43,19 +46,40 @@ module Praxis
|
|
43
46
|
end
|
44
47
|
end
|
45
48
|
|
46
|
-
resource_definition.
|
47
|
-
|
49
|
+
version = resource_definition.version
|
50
|
+
api_info = ApiDefinition.instance.info(resource_definition.version)
|
51
|
+
|
52
|
+
version_prefix = "#{api_info.base_path}#{resource_definition.version_prefix}"
|
53
|
+
prefix = Array(resource_definition.prefix)
|
54
|
+
|
55
|
+
@routing_config = RoutingConfig.new(version: version, base: version_prefix, prefix: prefix)
|
56
|
+
|
57
|
+
resource_definition.traits.each do |trait|
|
58
|
+
self.trait(trait)
|
48
59
|
end
|
49
60
|
|
61
|
+
resource_definition.action_defaults.apply!(self)
|
62
|
+
|
50
63
|
self.instance_eval(&block) if block_given?
|
51
64
|
end
|
52
65
|
|
66
|
+
def trait(trait_name)
|
67
|
+
unless ApiDefinition.instance.traits.has_key? trait_name
|
68
|
+
raise Exceptions::InvalidTrait.new("Trait #{trait_name} not found in the system")
|
69
|
+
end
|
70
|
+
|
71
|
+
trait = ApiDefinition.instance.traits.fetch(trait_name)
|
72
|
+
trait.apply!(self)
|
73
|
+
traits << trait_name
|
74
|
+
end
|
75
|
+
alias_method :use, :trait
|
76
|
+
|
53
77
|
def update_attribute(attribute, options, block)
|
54
78
|
attribute.options.merge!(options)
|
55
79
|
attribute.type.attributes(options, &block)
|
56
80
|
end
|
57
81
|
|
58
|
-
def response(
|
82
|
+
def response(name, **args)
|
59
83
|
template = ApiDefinition.instance.response(name)
|
60
84
|
@responses[name] = template.compile(self, **args)
|
61
85
|
end
|
@@ -68,13 +92,6 @@ module Praxis
|
|
68
92
|
return Attributor::Attribute.new(type, opts, &block)
|
69
93
|
end
|
70
94
|
|
71
|
-
def use(trait_name)
|
72
|
-
unless ApiDefinition.instance.traits.has_key? trait_name
|
73
|
-
raise Exceptions::InvalidTrait.new("Trait #{trait_name} not found in the system")
|
74
|
-
end
|
75
|
-
self.instance_eval(&ApiDefinition.instance.traits[trait_name])
|
76
|
-
end
|
77
|
-
|
78
95
|
def params(type=Attributor::Struct, **opts, &block)
|
79
96
|
return @params if !block && type == Attributor::Struct
|
80
97
|
|
@@ -87,7 +104,17 @@ module Praxis
|
|
87
104
|
update_attribute(@params, opts, block)
|
88
105
|
else
|
89
106
|
@params = create_attribute(type, **opts, &block)
|
107
|
+
if (api_info = ApiDefinition.instance.info(resource_definition.version))
|
108
|
+
if api_info.base_params
|
109
|
+
base_attrs = api_info.base_params.attributes
|
110
|
+
@params.attributes.merge!(base_attrs) do |key, oldval, newval|
|
111
|
+
oldval
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
90
115
|
end
|
116
|
+
|
117
|
+
@params
|
91
118
|
end
|
92
119
|
|
93
120
|
def payload(type=Attributor::Struct, **opts, &block)
|
@@ -112,8 +139,8 @@ module Praxis
|
|
112
139
|
else
|
113
140
|
type = Attributor::Hash.of(key:String) unless type
|
114
141
|
@headers = create_attribute(type,
|
115
|
-
|
116
|
-
|
142
|
+
dsl_compiler: HeadersDSLCompiler, case_insensitive_load: true,
|
143
|
+
**opts, &block)
|
117
144
|
end
|
118
145
|
@precomputed_header_keys_for_rack = nil #clear memoized data
|
119
146
|
end
|
@@ -133,11 +160,11 @@ module Praxis
|
|
133
160
|
|
134
161
|
|
135
162
|
def routing(&block)
|
136
|
-
routing_config
|
163
|
+
@routing_config.instance_eval &block
|
137
164
|
|
138
|
-
@routes = routing_config.routes
|
139
|
-
@primary_route = routing_config.routes.first
|
140
|
-
@named_routes = routing_config.routes.each_with_object({}) do |route, hash|
|
165
|
+
@routes = @routing_config.routes
|
166
|
+
@primary_route = @routing_config.routes.first
|
167
|
+
@named_routes = @routing_config.routes.each_with_object({}) do |route, hash|
|
141
168
|
next if route.name.nil?
|
142
169
|
hash[route.name] = route
|
143
170
|
end
|
@@ -166,6 +193,8 @@ module Praxis
|
|
166
193
|
memo[response.name] = response.describe
|
167
194
|
memo
|
168
195
|
end
|
196
|
+
hash[:traits] = traits if traits.any?
|
197
|
+
|
169
198
|
self.class.doc_decorations.each do |callback|
|
170
199
|
callback.call(self, hash)
|
171
200
|
end
|
@@ -10,6 +10,7 @@ module Praxis
|
|
10
10
|
attr_reader :traits
|
11
11
|
attr_reader :responses
|
12
12
|
attr_reader :infos
|
13
|
+
attr_reader :global_info
|
13
14
|
|
14
15
|
def self.define(&block)
|
15
16
|
if block.arity == 0
|
@@ -22,8 +23,12 @@ module Praxis
|
|
22
23
|
def initialize
|
23
24
|
@responses = Hash.new
|
24
25
|
@traits = Hash.new
|
26
|
+
@base_path = ''
|
27
|
+
|
28
|
+
@global_info = ApiGeneralInfo.new
|
29
|
+
|
25
30
|
@infos = Hash.new do |hash, version|
|
26
|
-
|
31
|
+
hash[version] = ApiGeneralInfo.new(@global_info)
|
27
32
|
end
|
28
33
|
end
|
29
34
|
|
@@ -41,35 +46,49 @@ module Praxis
|
|
41
46
|
if self.traits.has_key? name
|
42
47
|
raise Exceptions::InvalidTrait.new("Overwriting a previous trait with the same name (#{name})")
|
43
48
|
end
|
44
|
-
self.traits[name] = block
|
49
|
+
self.traits[name] = Trait.new(&block)
|
45
50
|
end
|
46
51
|
|
47
|
-
# Setting info to the nil version, means setting it for all versions (if they don't
|
48
|
-
def info(
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
+
# Setting info to the nil version, means setting it for all versions (if they don't override them)
|
53
|
+
def info(version=nil, &block)
|
54
|
+
if version.nil?
|
55
|
+
if block_given?
|
56
|
+
@global_info.instance_eval(&block)
|
57
|
+
else
|
58
|
+
@global_info
|
59
|
+
end
|
60
|
+
else
|
61
|
+
i = @infos[version]
|
62
|
+
if block_given?
|
63
|
+
i.instance_eval(&block)
|
64
|
+
end
|
65
|
+
i
|
66
|
+
end
|
52
67
|
end
|
53
|
-
|
68
|
+
|
54
69
|
def describe
|
55
|
-
global_info = @infos[nil].describe
|
56
70
|
data = Hash.new do |hash, version|
|
57
71
|
hash[version] = Hash.new
|
58
72
|
end
|
73
|
+
|
74
|
+
data[:global][:info] = @global_info.describe
|
75
|
+
|
59
76
|
# Fill in the "info" portion
|
60
77
|
@infos.each do |version,info|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
78
|
+
data[version][:info] = info.describe
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
if traits.any?
|
83
|
+
data[:traits] = {}
|
84
|
+
traits.each do |name, trait|
|
85
|
+
data[:traits][name] = trait.describe
|
65
86
|
end
|
66
|
-
data[version][:info] = info_hash
|
67
87
|
end
|
68
|
-
|
69
|
-
# Maybe report the traits?...or somehow the registered response_templates ...
|
88
|
+
|
70
89
|
data
|
71
90
|
end
|
72
|
-
|
91
|
+
|
73
92
|
define do |api|
|
74
93
|
api.response_template :ok do |media_type: |
|
75
94
|
media_type media_type
|
@@ -1,36 +1,79 @@
|
|
1
1
|
module Praxis
|
2
2
|
class ApiGeneralInfo
|
3
|
-
|
3
|
+
|
4
|
+
def initialize(global_info=nil)
|
5
|
+
@global_info = global_info
|
6
|
+
@data = Hash.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def get(k)
|
10
|
+
return @data[k] if @data.key?(k)
|
11
|
+
return @global_info.get(k) if @global_info
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def set(k, v)
|
16
|
+
@data[k] = v
|
17
|
+
end
|
18
|
+
|
4
19
|
def name(val=nil)
|
5
|
-
|
6
|
-
|
20
|
+
if val.nil?
|
21
|
+
get(:name)
|
22
|
+
else
|
23
|
+
set(:name, val)
|
24
|
+
end
|
7
25
|
end
|
8
26
|
|
9
27
|
def title(val=nil)
|
10
|
-
|
11
|
-
|
28
|
+
if val.nil?
|
29
|
+
get(:title)
|
30
|
+
else
|
31
|
+
set(:title, val)
|
32
|
+
end
|
12
33
|
end
|
13
34
|
|
14
35
|
def description(val=nil)
|
15
|
-
|
16
|
-
|
36
|
+
if val.nil?
|
37
|
+
get(:description)
|
38
|
+
else
|
39
|
+
set(:description, val)
|
40
|
+
end
|
17
41
|
end
|
18
42
|
|
19
43
|
def base_path(val=nil)
|
20
|
-
|
21
|
-
|
44
|
+
if val
|
45
|
+
return set(:base_path, val)
|
46
|
+
end
|
47
|
+
|
48
|
+
if @global_info
|
49
|
+
version_path = @data.fetch(:base_path,'')
|
50
|
+
global_path = @global_info.get(:base_path)
|
51
|
+
"#{global_path}#{version_path}"
|
52
|
+
else
|
53
|
+
@data.fetch(:base_path,'')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def base_params(type=Attributor::Struct, **opts, &block)
|
58
|
+
if !block && type == Attributor::Struct
|
59
|
+
get(:base_params)
|
60
|
+
else
|
61
|
+
set(:base_params, Attributor::Attribute.new(type, opts, &block) )
|
62
|
+
end
|
22
63
|
end
|
23
|
-
|
64
|
+
|
24
65
|
def describe
|
25
66
|
hash = { schema_version: "1.0".freeze }
|
26
|
-
|
27
67
|
[:name, :title, :description, :base_path].each do |attr|
|
28
|
-
val = self.__send__(attr)
|
68
|
+
val = self.__send__(attr)
|
29
69
|
hash[attr] = val unless val.nil?
|
30
70
|
end
|
71
|
+
if base_params
|
72
|
+
hash[:base_params] = base_params.describe[:type][:attributes]
|
73
|
+
end
|
31
74
|
hash
|
32
|
-
end
|
33
|
-
|
75
|
+
end
|
76
|
+
|
34
77
|
end
|
35
|
-
|
36
|
-
end
|
78
|
+
|
79
|
+
end
|
@@ -5,7 +5,6 @@ module Praxis
|
|
5
5
|
# 1- Run specific controller callbacks (in addition to any normal callbacks)
|
6
6
|
# 2- Shortcut the controller callback chain if any returns a Response object
|
7
7
|
class RequestStage < Stage
|
8
|
-
extend Forwardable
|
9
8
|
|
10
9
|
alias :dispatcher :application # it's technically application in the base Stage
|
11
10
|
|
@@ -34,22 +33,19 @@ module Praxis
|
|
34
33
|
setup_deferred_callbacks!
|
35
34
|
end
|
36
35
|
|
37
|
-
# Avoid using delegators, and create the explicit functions:
|
38
|
-
# def_delegators :@context, :controller, :action, :request
|
39
|
-
# they allocate all kinds of things and we don't need the generality here
|
40
36
|
def controller
|
41
37
|
@context.controller
|
42
38
|
end
|
39
|
+
|
43
40
|
def action
|
44
41
|
@context.action
|
45
42
|
end
|
43
|
+
|
46
44
|
def request
|
47
45
|
@context.request
|
48
46
|
end
|
49
47
|
|
50
|
-
|
51
48
|
def run
|
52
|
-
|
53
49
|
# stage-level callbacks (typically empty) will never shortcut
|
54
50
|
execute_callbacks(self.before_callbacks)
|
55
51
|
|