praxis 0.11.2 → 0.13.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 +24 -0
- data/lib/api_browser/app/js/controllers/action.js +2 -2
- data/lib/api_browser/app/js/controllers/type.js +2 -2
- data/lib/praxis/action_definition.rb +11 -0
- data/lib/praxis/api_definition.rb +2 -0
- data/lib/praxis/bootloader_stages/environment.rb +1 -0
- data/lib/praxis/controller.rb +2 -11
- data/lib/praxis/media_type.rb +12 -3
- data/lib/praxis/media_type_collection.rb +0 -2
- data/lib/praxis/plugins/praxis_mapper_plugin.rb +27 -5
- data/lib/praxis/request_stages/request_stage.rb +53 -32
- data/lib/praxis/request_stages/response.rb +2 -3
- data/lib/praxis/resource_definition.rb +11 -1
- data/lib/praxis/responses/http.rb +108 -20
- data/lib/praxis/responses/internal_server_error.rb +1 -1
- data/lib/praxis/responses/validation_error.rb +1 -1
- data/lib/praxis/restful_doc_generator.rb +27 -3
- data/lib/praxis/router.rb +16 -2
- data/lib/praxis/skeletor/restful_routing_config.rb +3 -0
- data/lib/praxis/stage.rb +1 -1
- data/lib/praxis/tasks/api_docs.rb +1 -1
- data/lib/praxis/tasks/console.rb +21 -3
- data/lib/praxis/tasks/routes.rb +19 -7
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +3 -5
- data/spec/functional_spec.rb +12 -0
- data/spec/praxis/action_definition_spec.rb +17 -0
- data/spec/praxis/media_type_spec.rb +22 -2
- data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +62 -15
- data/spec/praxis/request_stage_spec.rb +147 -67
- data/spec/praxis/resource_definition_spec.rb +18 -2
- data/spec/praxis/restful_routing_config_spec.rb +1 -1
- data/spec/praxis/router_spec.rb +115 -37
- data/spec/spec_app/design/resources/instances.rb +1 -1
- data/spec/support/spec_media_types.rb +2 -0
- metadata +8 -22
@@ -8,7 +8,17 @@ module Praxis
|
|
8
8
|
@inspected_types = Set.new
|
9
9
|
API_DOCS_DIRNAME = 'api_docs'
|
10
10
|
|
11
|
-
EXCLUDED_TYPES_FROM_TOP_LEVEL = Set.new(
|
11
|
+
EXCLUDED_TYPES_FROM_TOP_LEVEL = Set.new([
|
12
|
+
Attributor::Boolean,
|
13
|
+
Attributor::CSV,
|
14
|
+
Attributor::DateTime,
|
15
|
+
Attributor::Float,
|
16
|
+
Attributor::Hash,
|
17
|
+
Attributor::Ids,
|
18
|
+
Attributor::Integer,
|
19
|
+
Attributor::Object,
|
20
|
+
Attributor::String
|
21
|
+
]).freeze
|
12
22
|
|
13
23
|
def self.inspect_attributes(the_type)
|
14
24
|
|
@@ -66,6 +76,9 @@ module Praxis
|
|
66
76
|
|
67
77
|
# Collect reachable types from the params and payload definitions
|
68
78
|
@controller_config.actions.each do |name, action_config|
|
79
|
+
# skip actions with doc_visibility of :none
|
80
|
+
next if action_config.metadata[:doc_visibility] == :none
|
81
|
+
|
69
82
|
add_to_reachable RestfulDocGenerator.inspect_attributes(action_config.params)
|
70
83
|
add_to_reachable RestfulDocGenerator.inspect_attributes(action_config.payload)
|
71
84
|
end
|
@@ -111,6 +124,9 @@ module Praxis
|
|
111
124
|
|
112
125
|
def load_resources
|
113
126
|
Praxis::Application.instance.resource_definitions.map do |resource|
|
127
|
+
# skip resources with doc_visibility of :none
|
128
|
+
next if resource.metadata[:doc_visibility] == :none
|
129
|
+
|
114
130
|
@resources << Resource.new(resource)
|
115
131
|
end
|
116
132
|
|
@@ -129,13 +145,21 @@ module Praxis
|
|
129
145
|
|
130
146
|
def write_resources
|
131
147
|
@resources.each do |r|
|
148
|
+
|
132
149
|
filename = File.join(@doc_root_dir, r.version, "resources","#{r.id}.json")
|
133
150
|
#puts "Dumping #{r.id} to #{filename}"
|
134
151
|
base = File.dirname(filename)
|
135
152
|
FileUtils.mkdir_p base unless File.exists? base
|
136
153
|
resource_description = r.controller_config.describe
|
154
|
+
|
155
|
+
# strip actions with doc_visibility of :none
|
156
|
+
resource_description[:actions].reject! { |a| a[:metadata][:doc_visibility] == :none }
|
157
|
+
|
137
158
|
# Go through the params/payload of each action and generate an example for them (then stick it into the description hash)
|
138
159
|
r.controller_config.actions.each do |action_name, action|
|
160
|
+
# skip actions with doc_visibility of :none
|
161
|
+
next if action.metadata[:doc_visibility] == :none
|
162
|
+
|
139
163
|
generated_examples = {}
|
140
164
|
if action.params
|
141
165
|
generated_examples[:params] = dump_example_for( r.id, action.params )
|
@@ -222,7 +246,7 @@ module Praxis
|
|
222
246
|
# Discard any mediatypes that we've already seen and processed as controller related
|
223
247
|
reportable_types = types - media_types_seen_from_controllers - EXCLUDED_TYPES_FROM_TOP_LEVEL
|
224
248
|
#TODO: think about these special cases, is it needed?
|
225
|
-
reportable_types.reject!{|type| type < Praxis::Links || type < Praxis::MediaTypeCollection }
|
249
|
+
reportable_types.reject!{|type| type < Praxis::Links || type < Praxis::MediaTypeCollection }
|
226
250
|
|
227
251
|
reportable_types.each do |type|
|
228
252
|
index[version] ||= Hash.new
|
@@ -309,4 +333,4 @@ module Praxis
|
|
309
333
|
end
|
310
334
|
|
311
335
|
end
|
312
|
-
end
|
336
|
+
end
|
data/lib/praxis/router.rb
CHANGED
@@ -64,7 +64,17 @@ module Praxis
|
|
64
64
|
end
|
65
65
|
|
66
66
|
verb = request.verb
|
67
|
-
|
67
|
+
r = ( @routes.key?(verb) ? @routes[verb] : nil ) # Exact verb match
|
68
|
+
result = r.call(request) if r
|
69
|
+
# If we didn't have an exact verb route, or if we did but the rest or route conditions didn't match
|
70
|
+
if( r == nil || result == :not_found )
|
71
|
+
# Fallback to a wildcard router, if there is one registered
|
72
|
+
result = if @routes.key?('ANY')
|
73
|
+
@routes['ANY'].call(request)
|
74
|
+
else
|
75
|
+
:not_found
|
76
|
+
end
|
77
|
+
end
|
68
78
|
|
69
79
|
if result == :not_found
|
70
80
|
# no need to try :path as we cannot really know if you've attempted to pass a version through it here
|
@@ -81,7 +91,11 @@ module Praxis
|
|
81
91
|
pretty_versions = attempted_versions.collect(&:inspect).join(', ')
|
82
92
|
body += " Available versions = #{pretty_versions}."
|
83
93
|
end
|
84
|
-
|
94
|
+
headers = {"Content-Type" => "text/plain"}
|
95
|
+
if Praxis::Application.instance.config.praxis.x_cascade
|
96
|
+
headers['X-Cascade'] = 'pass'
|
97
|
+
end
|
98
|
+
result = [404, headers, [body]]
|
85
99
|
end
|
86
100
|
result
|
87
101
|
end
|
@@ -39,6 +39,9 @@ module Praxis
|
|
39
39
|
def trace(path, opts={}) add_route 'TRACE', path, opts end
|
40
40
|
def connect(path, opts={}) add_route 'CONNECT', path, opts end
|
41
41
|
def patch(path, opts={}) add_route 'PATCH', path, opts end
|
42
|
+
def any(path, opts={}) add_route 'ANY', path, opts end
|
43
|
+
|
44
|
+
|
42
45
|
|
43
46
|
def add_route(verb, path, options={})
|
44
47
|
path = Mustermann.new(prefix + path)
|
data/lib/praxis/stage.rb
CHANGED
data/lib/praxis/tasks/console.rb
CHANGED
@@ -1,11 +1,29 @@
|
|
1
1
|
namespace :praxis do
|
2
|
-
|
2
|
+
desc "Run interactive pry/irb console"
|
3
|
+
task :console do
|
4
|
+
have_pry = false
|
5
|
+
|
3
6
|
begin
|
7
|
+
# Use pry if available; require pry _before_ environment to maximize
|
8
|
+
# debuggability.
|
4
9
|
require 'pry'
|
5
|
-
|
10
|
+
have_pry = true
|
6
11
|
rescue LoadError
|
12
|
+
# Fall back on irb
|
7
13
|
require 'irb'
|
8
|
-
|
14
|
+
require 'irb/ext/multi-irb'
|
15
|
+
end
|
16
|
+
|
17
|
+
Rake::Task['praxis:environment'].invoke
|
18
|
+
|
19
|
+
if have_pry
|
20
|
+
Praxis::Application.instance.pry
|
21
|
+
else
|
22
|
+
# Use some special initialization magic to ensure that 'self' in the
|
23
|
+
# IRB session refers to Praxis::Application.instance.
|
24
|
+
IRB.setup nil
|
25
|
+
IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
|
26
|
+
IRB.irb(nil, Praxis::Application.instance)
|
9
27
|
end
|
10
28
|
end
|
11
29
|
end
|
data/lib/praxis/tasks/routes.rb
CHANGED
@@ -2,11 +2,16 @@ namespace :praxis do
|
|
2
2
|
|
3
3
|
desc 'List routes, format=json or table, default table'
|
4
4
|
task :routes, [:format] => [:environment] do |t, args|
|
5
|
-
require '
|
5
|
+
require 'terminal-table'
|
6
6
|
|
7
|
-
table = Table(:version, :path, :verb, :resource,
|
8
|
-
:action, :implementation, :name, :primary)
|
9
7
|
|
8
|
+
table = Terminal::Table.new title: "Routes",
|
9
|
+
headings: [
|
10
|
+
"Version", "Path", "Verb",
|
11
|
+
"Resource", "Action", "Implementation", "Name", "Primary"
|
12
|
+
]
|
13
|
+
|
14
|
+
rows = []
|
10
15
|
Praxis::Application.instance.resource_definitions.each do |resource_definition|
|
11
16
|
resource_definition.actions.each do |name, action|
|
12
17
|
method = begin
|
@@ -14,11 +19,11 @@ namespace :praxis do
|
|
14
19
|
rescue
|
15
20
|
nil
|
16
21
|
end
|
17
|
-
|
22
|
+
|
18
23
|
method_name = method ? "#{method.owner.name}##{method.name}" : 'n/a'
|
19
24
|
|
20
25
|
action.routes.each do |route|
|
21
|
-
|
26
|
+
rows << {
|
22
27
|
resource: resource_definition.name,
|
23
28
|
version: route.version,
|
24
29
|
verb: route.verb,
|
@@ -32,11 +37,18 @@ namespace :praxis do
|
|
32
37
|
end
|
33
38
|
end
|
34
39
|
|
40
|
+
|
41
|
+
|
42
|
+
|
35
43
|
case args[:format] || "table"
|
36
44
|
when "json"
|
37
|
-
puts JSON.pretty_generate(
|
45
|
+
puts JSON.pretty_generate(rows)
|
38
46
|
when "table"
|
39
|
-
|
47
|
+
rows.each do |row|
|
48
|
+
table.add_row(row.values_at(:version, :path, :verb, :resource,
|
49
|
+
:action, :implementation, :name, :primary))
|
50
|
+
end
|
51
|
+
puts table
|
40
52
|
else
|
41
53
|
raise "unknown output format: #{args[:format]}"
|
42
54
|
end
|
data/lib/praxis/version.rb
CHANGED
data/praxis.gemspec
CHANGED
@@ -7,7 +7,6 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = "praxis"
|
8
8
|
spec.version = Praxis::VERSION
|
9
9
|
spec.authors = ["Josep M. Blanquer","Dane Jensen"]
|
10
|
-
spec.date = "2014-08-19"
|
11
10
|
spec.summary = 'Building APIs the way you want it.'
|
12
11
|
|
13
12
|
spec.email = ["blanquer@gmail.com","dane.jensen@gmail.com"]
|
@@ -24,11 +23,10 @@ Gem::Specification.new do |spec|
|
|
24
23
|
spec.add_dependency 'rack', '~> 1'
|
25
24
|
spec.add_dependency 'mustermann', '~> 0'
|
26
25
|
spec.add_dependency 'activesupport', '>= 3'
|
27
|
-
spec.add_dependency 'ruport', '~> 1'
|
28
26
|
spec.add_dependency 'mime', '~> 0'
|
29
|
-
spec.add_dependency 'praxis-mapper', '~> 3.
|
30
|
-
spec.add_dependency 'praxis-blueprints', '~> 1.
|
31
|
-
spec.add_dependency 'attributor', '~> 2.
|
27
|
+
spec.add_dependency 'praxis-mapper', '~> 3.3'
|
28
|
+
spec.add_dependency 'praxis-blueprints', '~> 1.2'
|
29
|
+
spec.add_dependency 'attributor', '~> 2.5.0'
|
32
30
|
spec.add_dependency 'thor', '~> 0.18'
|
33
31
|
spec.add_dependency 'terminal-table', '~> 1.4'
|
34
32
|
spec.add_dependency 'harness', '~> 2'
|
data/spec/functional_spec.rb
CHANGED
@@ -256,6 +256,18 @@ describe 'Functional specs' do
|
|
256
256
|
end
|
257
257
|
end
|
258
258
|
|
259
|
+
context 'wildcard routing' do
|
260
|
+
it 'can terminate instances with POST' do
|
261
|
+
post '/clouds/23/instances/1/terminate?api_version=1.0', nil, 'global_session' => session
|
262
|
+
expect(last_response.status).to eq(200)
|
263
|
+
end
|
264
|
+
it 'can terminate instances with DELETE' do
|
265
|
+
post '/clouds/23/instances/1/terminate?api_version=1.0', nil, 'global_session' => session
|
266
|
+
expect(last_response.status).to eq(200)
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
|
259
271
|
context 'auth_plugin' do
|
260
272
|
it 'can terminate' do
|
261
273
|
post '/clouds/23/instances/1/terminate?api_version=1.0', nil, 'global_session' => session
|
@@ -38,6 +38,7 @@ describe Praxis::ActionDefinition do
|
|
38
38
|
its('payload.attributes') { should have_key :inherited }
|
39
39
|
its('headers.attributes') { should have_key "X_REQUESTED_WITH" }
|
40
40
|
its('headers.attributes') { should have_key "Inherited" }
|
41
|
+
its('metadata') { should_not have_key :doc_visibility }
|
41
42
|
end
|
42
43
|
|
43
44
|
context '#responses' do
|
@@ -140,4 +141,20 @@ describe Praxis::ActionDefinition do
|
|
140
141
|
end
|
141
142
|
|
142
143
|
end
|
144
|
+
|
145
|
+
context 'with nodoc!' do
|
146
|
+
before do
|
147
|
+
action.nodoc!
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'has :doc_visibility set in metadata' do
|
151
|
+
expect(action.metadata[:doc_visibility]).to be(:none)
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'is exposed by describe' do
|
155
|
+
expect(action.describe[:metadata][:doc_visibility]).to be(:none)
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
143
160
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Praxis::MediaType do
|
4
|
-
let(:owner_resource) {
|
5
|
-
let(:manager_resource) {
|
4
|
+
let(:owner_resource) { instance_double(Person, id: 100, name: /[:name:]/.gen, href: '/', links: ['one','two']) }
|
5
|
+
let(:manager_resource) { instance_double(Person, id: 101, name: /[:name:]/.gen, href: '/', links: []) }
|
6
6
|
|
7
7
|
let(:resource) do
|
8
8
|
double('address', id: 1, name: 'Home',owner: owner_resource, manager: manager_resource)
|
@@ -10,6 +10,7 @@ describe Praxis::MediaType do
|
|
10
10
|
|
11
11
|
subject(:address) { Address.new(resource) }
|
12
12
|
|
13
|
+
|
13
14
|
context 'attributes' do
|
14
15
|
its(:id) { should eq(1) }
|
15
16
|
its(:name) { should eq('Home') }
|
@@ -23,11 +24,28 @@ describe Praxis::MediaType do
|
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
27
|
+
|
28
|
+
|
26
29
|
context 'accessor methods' do
|
27
30
|
subject(:address_klass) { address.class }
|
28
31
|
|
29
32
|
its(:identifier) { should be_kind_of(String) }
|
30
33
|
its(:description) { should be_kind_of(String) }
|
34
|
+
|
35
|
+
context 'links' do
|
36
|
+
context 'with an custom links attribute' do
|
37
|
+
subject(:person) { Person.new(owner_resource) }
|
38
|
+
|
39
|
+
its(:links) { should be_kind_of(Array) }
|
40
|
+
its(:links) { should eq(owner_resource.links) }
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'using the links DSL' do
|
44
|
+
subject(:address) { Address.new(resource) }
|
45
|
+
its(:links) { should be_kind_of(Address::Links) }
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
31
49
|
end
|
32
50
|
|
33
51
|
context "rendering" do
|
@@ -74,7 +92,9 @@ describe Praxis::MediaType do
|
|
74
92
|
end
|
75
93
|
end
|
76
94
|
|
95
|
+
|
77
96
|
context 'using blueprint caching' do
|
78
97
|
it 'has specs'
|
79
98
|
end
|
99
|
+
|
80
100
|
end
|
@@ -9,7 +9,7 @@ describe Praxis::Plugins::PraxisMapperPlugin do
|
|
9
9
|
context 'configuration' do
|
10
10
|
subject { config }
|
11
11
|
its(:log_stats) { should eq 'skip' }
|
12
|
-
|
12
|
+
its(:stats_log_level) { should eq :info }
|
13
13
|
its(:repositories) { should have_key("default") }
|
14
14
|
|
15
15
|
context 'default repository' do
|
@@ -23,6 +23,17 @@ describe Praxis::Plugins::PraxisMapperPlugin do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
context 'Request' do
|
27
|
+
|
28
|
+
it 'should have identity_map accessors' do
|
29
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Request.instance_methods).to include(:identity_map,:identity_map=)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should have silence_mapper_stats accessors' do
|
33
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Request.instance_methods)
|
34
|
+
.to include(:silence_mapper_stats,:silence_mapper_stats=)
|
35
|
+
end
|
36
|
+
end
|
26
37
|
context 'functional test' do
|
27
38
|
|
28
39
|
def app
|
@@ -42,7 +53,7 @@ describe Praxis::Plugins::PraxisMapperPlugin do
|
|
42
53
|
|
43
54
|
it 'logs stats' do
|
44
55
|
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:log).
|
45
|
-
with(kind_of(Praxis::Mapper::IdentityMap), 'detailed').
|
56
|
+
with(kind_of(Praxis::Request),kind_of(Praxis::Mapper::IdentityMap), 'detailed').
|
46
57
|
and_call_original
|
47
58
|
|
48
59
|
get '/clouds/1/instances/2?junk=foo&api_version=1.0', nil, 'global_session' => session
|
@@ -54,24 +65,60 @@ describe Praxis::Plugins::PraxisMapperPlugin do
|
|
54
65
|
|
55
66
|
context 'Statistics' do
|
56
67
|
context '.log' do
|
57
|
-
let(:
|
68
|
+
let(:queries){ { some: :queries } }
|
69
|
+
let(:identity_map) { double('identity_map', queries: queries) }
|
70
|
+
let(:log_stats){ 'detailed' }
|
71
|
+
let(:request){ double('request', silence_mapper_stats: false ) }
|
58
72
|
|
59
|
-
|
60
|
-
|
61
|
-
Praxis::Plugins::PraxisMapperPlugin::Statistics.log(identity_map, 'detailed')
|
73
|
+
after do
|
74
|
+
Praxis::Plugins::PraxisMapperPlugin::Statistics.log(request, identity_map, log_stats)
|
62
75
|
end
|
63
76
|
|
64
|
-
|
65
|
-
|
66
|
-
|
77
|
+
context 'when the request silences mapper stats' do
|
78
|
+
let(:request){ double('request', silence_mapper_stats: true ) }
|
79
|
+
it 'should not log anything' do
|
80
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:to_logger)
|
81
|
+
end
|
67
82
|
end
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
83
|
+
|
84
|
+
context 'without the request silencing mapper stats' do
|
85
|
+
context 'when log_stats = detailed' do
|
86
|
+
it 'should call the detailed method' do
|
87
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:detailed).with(identity_map)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'when log_stats = short' do
|
92
|
+
let(:log_stats){ 'short' }
|
93
|
+
it 'should call the short method' do
|
94
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:short).with(identity_map)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'when log_stats = skip' do
|
99
|
+
let(:log_stats){ 'skip' }
|
100
|
+
|
101
|
+
it 'should not log anything' do
|
102
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:to_logger)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'when there is no identity map' do
|
107
|
+
let(:identity_map) { nil }
|
108
|
+
it 'should not log anything' do
|
109
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:to_logger)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'when no queries are logged in the identity map' do
|
114
|
+
let(:queries){ {} }
|
115
|
+
it 'should log a special message' do
|
116
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:to_logger)
|
117
|
+
.with("No database interactions observed.")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
73
121
|
end
|
74
|
-
|
75
122
|
end
|
76
123
|
|
77
124
|
it 'has specs for testing the detailed log output'
|