praxis 0.15.0 → 0.16.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.
- 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
|
|