strelka 0.0.1.pre177 → 0.0.1.pre184

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 (38) hide show
  1. data/ChangeLog +111 -16
  2. data/Manifest.txt +8 -8
  3. data/Rakefile +3 -3
  4. data/bin/leash +51 -28
  5. data/examples/{auth-demo.rb → apps/auth-demo} +3 -3
  6. data/examples/{auth-demo2.rb → apps/auth-demo2} +0 -0
  7. data/examples/{sessions-demo.rb → apps/sessions-demo} +0 -0
  8. data/examples/config.yml +5 -1
  9. data/examples/{examples.css → static/examples.css} +0 -0
  10. data/examples/{examples.html → static/examples.html} +0 -0
  11. data/examples/{auth-form.tmpl → templates/auth-form.tmpl} +0 -0
  12. data/examples/{auth-success.tmpl → templates/auth-success.tmpl} +0 -0
  13. data/examples/{layout.tmpl → templates/layout.tmpl} +0 -0
  14. data/lib/strelka/app/auth.rb +18 -8
  15. data/lib/strelka/app/errors.rb +3 -2
  16. data/lib/strelka/app/filters.rb +2 -0
  17. data/lib/strelka/app/negotiation.rb +2 -0
  18. data/lib/strelka/app/parameters.rb +18 -140
  19. data/lib/strelka/app/plugins.rb +84 -26
  20. data/lib/strelka/app/restresources.rb +26 -18
  21. data/lib/strelka/app/routing.rb +8 -2
  22. data/lib/strelka/app/sessions.rb +7 -0
  23. data/lib/strelka/app/templating.rb +1 -1
  24. data/lib/strelka/app.rb +25 -1
  25. data/lib/strelka/constants.rb +3 -1
  26. data/lib/strelka/paramvalidator.rb +251 -74
  27. data/lib/strelka/session/default.rb +1 -1
  28. data/spec/strelka/app/auth_spec.rb +37 -0
  29. data/spec/strelka/app/errors_spec.rb +0 -2
  30. data/spec/strelka/app/filters_spec.rb +1 -1
  31. data/spec/strelka/app/parameters_spec.rb +4 -92
  32. data/spec/strelka/app/plugins_spec.rb +64 -2
  33. data/spec/strelka/app/restresources_spec.rb +3 -0
  34. data/spec/strelka/app/routing_spec.rb +5 -5
  35. data/spec/strelka/paramvalidator_spec.rb +294 -385
  36. data.tar.gz.sig +0 -0
  37. metadata +126 -46
  38. metadata.gz.sig +0 -0
@@ -35,13 +35,6 @@ require 'strelka/paramvalidator'
35
35
  #
36
36
  # end # class UserManager
37
37
  #
38
- #
39
- # == To-Do
40
- #
41
- # _We may add support for other ways of passing parameters later,
42
- # e.g., via structured entity bodies like JSON, XML, YAML, etc_.
43
- #
44
- #
45
38
  module Strelka::App::Parameters
46
39
  extend Strelka::App::Plugin
47
40
 
@@ -52,114 +45,32 @@ module Strelka::App::Parameters
52
45
  # Class methods to add to classes with routing.
53
46
  module ClassMethods # :nodoc:
54
47
 
55
- # Pattern for matching route parameters
56
- PARAMETER_PATTERN = %r{/:(?<paramname>[a-z]\w*)}i
57
-
58
- # Param defaults
59
- PARAMETER_DEFAULT_OPTIONS = {
60
- :constraint => //,
61
- :required => false,
62
- :description => nil,
63
- }
64
-
65
- # Pattern to use to strip binding operators from parameter patterns so they
66
- # can be used in the middle of routing Regexps.
67
- PARAMETER_PATTERN_STRIP_RE = Regexp.union( '^', '$', '\\A', '\\z', '\\Z' )
68
-
69
- # Options that are passed as Symbols to .param
70
- FLAGS = [ :required, :untaint ]
71
-
72
-
73
- # Default parameters hash
74
- @parameters = {}
75
- @untaint_all_constraints = false
76
-
77
- # The hash of declared parameters
78
- attr_reader :parameters
79
-
80
- # The flag for untainting constrained parameters that match their constraints
81
- attr_writer :untaint_all_constraints
48
+ ##
49
+ # Default ParamValidator
50
+ @paramvalidator = Strelka::ParamValidator.new
51
+ attr_reader :paramvalidator
82
52
 
83
53
 
54
+ ### :call-seq:
55
+ ### param( name, *flags )
56
+ ### param( name, constraint, *flags )
57
+ ### param( name, description, *flags )
58
+ ### param( name, constraint, description, *flags )
59
+ ###
84
60
  ### Declare a parameter with the specified +name+ that will be validated using the given
85
61
  ### +constraint+. The +constraint+ can be any of the types supported by
86
62
  ### Strelka::ParamValidator.
87
- ### :call-seq:
88
- # param( name, *flags )
89
- # param( name, constraint, *flags )
90
- # param( name, description, *flags )
91
- # param( name, constraint, description, *flags )
92
63
  def param( name, *args )
93
64
  Strelka.log.debug "New param %p" % [ name ]
94
- name = name.to_sym
95
-
96
- # Consume the arguments
97
- constraint = args.shift unless args.first.is_a?( String ) || FLAGS.include?( args.first )
98
- constraint ||= name
99
- description = args.shift if args.first.is_a?( String )
100
- # description ||= name.to_s.capitalize
101
- flags = args
102
-
103
- # Give a regexp constraint a named capture group for the constraint name if it
104
- # doesn't already have one
105
- if constraint.is_a?( Regexp )
106
- constraint = Regexp.compile( "(?<#{name}>" + constraint.to_s + ")" ) unless
107
- constraint.names.include?( name.to_s )
108
- Strelka.log.debug " regex constraint is: %p" % [ constraint ]
109
- end
110
-
111
- # Merge the param into the parameters hash
112
- options = PARAMETER_DEFAULT_OPTIONS.dup
113
- options[ :constraint ] = constraint
114
- options[ :description ] = description
115
- options[ :required ] = true if flags.include?( :required )
116
- options[ :untaint ] = true if flags.include?( :untaint )
117
- Strelka.log.debug " param options are: %p" % [ options ]
118
-
119
- self.parameters[ name ] = options
65
+ self.paramvalidator.add( name, *args )
120
66
  end
121
67
 
122
68
 
123
69
  ### Get/set the untainting flag. If set, all parameters which match their constraints
124
70
  ### will also be untainted.
125
71
  def untaint_all_constraints( newval=nil )
126
- Strelka.log.debug "Untaint all constraints: %p:%p" % [ newval, @untaint_all_constraints ]
127
- @untaint_all_constraints = newval unless newval.nil?
128
- return @untaint_all_constraints
129
- end
130
-
131
-
132
- ### Turn the constraint associated with +name+ into a routing component.
133
- def extract_route_from_constraint( name )
134
- name.slice!( 0, 1 ) if name.start_with?( ':' )
135
- Strelka.log.debug " searching for a param for %p" % [ name ]
136
- param = self.parameters[ name.to_sym ] or
137
- raise ScriptError, "no parameter %p defined" % [ name ]
138
-
139
- # Munge the constraint into a Regexp
140
- constraint = param[ :constraint ]
141
- re = case constraint
142
- when Regexp
143
- constraint
144
- when Array
145
- sub_res = constraint.map( &self.method(:extract_route_from_constraint) )
146
- Regexp.union( sub_res )
147
- when Symbol
148
- re = Strelka::ParamValidator.pattern_for_constraint( constraint ) or
149
- raise ScriptError, "no pattern for %p constraint" % [ constraint ]
150
- /(?<#{name}>#{re})/
151
- else
152
- raise ScriptError,
153
- "can't route on a parameter with a %p constraint %p" % [ constraint.class ]
154
- end
155
-
156
- # Unbind the pattern from beginning or end of line.
157
- # :TODO: This is pretty ugly. Find a better way of modifying the regex.
158
- re_str = re.to_s.
159
- sub( %r{\(\?[\-mix]+:(.*)\)}, '\\1' ).
160
- gsub( PARAMETER_PATTERN_STRIP_RE, '' )
161
-
162
- return Regexp.new( re_str, re.options )
72
+ self.paramvalidator.untaint_all = newval unless newval.nil?
73
+ return self.paramvalidator.untaint_all?
163
74
  end
164
75
 
165
76
 
@@ -167,7 +78,7 @@ module Strelka::App::Parameters
167
78
  ### declarations, too.
168
79
  def inherited( subclass )
169
80
  super
170
- subclass.instance_variable_set( :@parameters, self.parameters.dup )
81
+ subclass.instance_variable_set( :@paramvalidator, self.paramvalidator.dup )
171
82
  end
172
83
 
173
84
  end # module ClassMethods
@@ -176,48 +87,15 @@ module Strelka::App::Parameters
176
87
 
177
88
  ### Add a ParamValidator to the given +request+ before passing it on.
178
89
  def handle_request( request, &block )
179
- profile = self.make_validator_profile( request )
180
- self.log.debug "Applying validator profile: %p" % [ profile ]
181
- validator = Strelka::ParamValidator.new( profile, request.params )
182
- self.log.debug " validator: %p" % [ validator ]
90
+ self.log.debug "[:parameters] Wrapping request with parameter validation."
183
91
 
92
+ validator = self.class.paramvalidator.dup
93
+ validator.validate( request.params )
184
94
  request.params = validator
185
- super
186
- end
187
-
188
-
189
-
190
- ### Make a validator profile for Strelka::ParamValidator for the specified
191
- ### +request+ using the declared parameters in the App, returning it as a Hash.
192
- def make_validator_profile( request )
193
- profile = {
194
- :required => [],
195
- :optional => [],
196
- :descriptions => {},
197
- :constraints => {},
198
- :untaint_constraint_fields => [],
199
- :untaint_all_constraints => self.class.untaint_all_constraints,
200
- }
201
95
 
202
- self.log.debug "Validator profile is: %p" % [ profile ]
203
-
204
- return self.class.parameters.inject( profile ) do |accum, (name, opts)|
205
- self.log.debug " adding parameter: %p: %p" % [ name, opts ]
206
- if opts[:required]
207
- accum[:required] << name
208
- else
209
- accum[:optional] << name
210
- end
211
-
212
- accum[:untaint_constraint_fields] << name if opts[:untaint]
213
- accum[:descriptions][ name ] = opts[:description] if opts[:description]
214
- accum[:constraints][ name ] = opts[:constraint]
215
-
216
- accum
217
- end
96
+ super
218
97
  end
219
98
 
220
-
221
99
  end # module Strelka::App::Parameters
222
100
 
223
101
 
@@ -118,16 +118,59 @@ class Strelka::App
118
118
  end
119
119
 
120
120
 
121
- ### Extension callback -- add instance variables to extending objects.
122
- def self::extended( object )
123
- super
124
- object.instance_variable_set( :@plugins, {} )
125
- end
126
-
127
-
128
121
  ### Class methods to add to classes with plugins.
129
122
  module ClassMethods
130
123
 
124
+ ##
125
+ # If plugins have already been installed, this will be the call frame
126
+ # they were first installed from. This is used to warn about installing
127
+ # plugins twice.
128
+ attr_accessor :plugins_installed_from
129
+
130
+
131
+ ### Returns +true+ if the plugins for the extended app class have already
132
+ ### been installed.
133
+ def plugins_installed?
134
+ return !self.plugins_installed_from.nil?
135
+ end
136
+
137
+
138
+ ### Extension callback -- add instance variables to extending objects.
139
+ def inherited( subclass )
140
+ super
141
+ @plugins ||= []
142
+ subclass.instance_variable_set( :@plugins, @plugins.dup )
143
+ subclass.instance_variable_set( :@plugins_installed_from, nil )
144
+ end
145
+
146
+
147
+ ### Load the plugins with the given +names+ and install them.
148
+ def plugins( *names )
149
+ Strelka.log.info "Adding plugins: %s" % [ names.flatten.map(&:to_s).join(', ') ]
150
+
151
+ # Load the associated Plugin Module objects
152
+ names.flatten.each {|name| self.load_plugin(name) }
153
+
154
+ # Add the name/s to the list of mixins to apply on startup
155
+ @plugins |= names
156
+
157
+ # Install the declarative half of the plugin immediately
158
+ names.each do |name|
159
+ plugin = nil
160
+
161
+ if name.is_a?( Module )
162
+ plugin = name
163
+ else
164
+ plugin = Strelka::App.loaded_plugins[ name ]
165
+ end
166
+
167
+ Strelka.log.debug " registering %p" % [ name ]
168
+ self.register_plugin( plugin )
169
+ end
170
+ end
171
+ alias_method :plugin, :plugins
172
+
173
+
131
174
  ### Load the plugin with the given +name+
132
175
  def load_plugin( name )
133
176
 
@@ -146,11 +189,10 @@ class Strelka::App
146
189
  end
147
190
 
148
191
 
149
- ### Install the plugin +mod+ in the receiving class.
150
- def install_plugin( mod )
151
- Strelka.log.debug " adding %p to %p" % [ mod, self ]
152
- include( mod )
153
-
192
+ ### Register the plugin +mod+ in the receiving class. This adds any
193
+ ### declaratives and class-level data necessary for configuring the
194
+ ### plugin.
195
+ def register_plugin( mod )
154
196
  if mod.const_defined?( :ClassMethods )
155
197
  cm_mod = mod.const_get(:ClassMethods)
156
198
  Strelka.log.debug " adding class methods from %p" % [ cm_mod ]
@@ -172,27 +214,43 @@ class Strelka::App
172
214
  end
173
215
 
174
216
 
175
- ### Load the plugins with the given +names+ and install them.
176
- def plugins( *names )
177
- # Load the associated Plugin Module objects
178
- names.flatten.each {|name| self.load_plugin(name) }
217
+ ### Install the mixin part of plugins immediately before the first instance
218
+ ### is created.
219
+ def new( * )
220
+ self.install_plugins unless self.plugins_installed?
221
+ super
222
+ end
223
+
224
+
225
+ ### Install the mixin part of the plugin, in the order determined by
226
+ ### the plugin registry based on the run_before and run_after specifications
227
+ ### of the plugins themselves.
228
+ def install_plugins
229
+ if self.plugins_installed?
230
+ Strelka.log.warn "Plugins were already installed for %p from %p" %
231
+ [ self, self.plugins_installed_from ]
232
+ Strelka.log.info "I'll attempt to install any new ones, but plugin ordering"
233
+ Strelka.log.info "and other functionality might exhibit strange behavior."
234
+ else
235
+ Strelka.log.info "Installing plugins for %p." % [ self ]
236
+ end
179
237
 
180
238
  sorted_plugins = Strelka::App.loaded_plugins.tsort.reverse
181
- Strelka.log.debug "Sorted plugins: app -> %p <- Mongrel2" % [ sorted_plugins ]
182
239
 
183
- # Install the plugins in reverse-sorted order
184
240
  sorted_plugins.each do |name|
185
- plugin = Strelka::App.loaded_plugins[ name ]
186
- Strelka.log.debug "Considering %p" % [ name ]
187
- if names.include?( name ) || names.include?( plugin )
188
- Strelka.log.debug " installing"
189
- self.install_plugin( plugin )
190
- else
191
- Strelka.log.debug " not used by this app; skipping"
241
+ mod = Strelka::App.loaded_plugins[ name ]
242
+
243
+ unless @plugins.include?( name ) || @plugins.include?( mod )
244
+ Strelka.log.debug " skipping %s" % [ name ]
245
+ next
192
246
  end
247
+
248
+ Strelka.log.info " including %p." % [ mod ]
249
+ include( mod )
193
250
  end
251
+
252
+ self.plugins_installed_from = caller( 1 ).first
194
253
  end
195
- alias_method :plugin, :plugins
196
254
 
197
255
  end # module ClassMethods
198
256
 
@@ -62,23 +62,6 @@ module Strelka::App::RestResources
62
62
  }.freeze
63
63
 
64
64
 
65
- ### Inclusion callback -- overridden to also install dependencies.
66
- def self::included( mod )
67
- super
68
-
69
- # Load the plugins this one depends on if they aren't already
70
- mod.plugins :routing, :negotiation, :parameters
71
-
72
- # Add validations for the limit and offset parameters
73
- mod.param :limit, :integer
74
- mod.param :offset, :integer
75
-
76
- # Use the 'exclusive' router instead of the more-flexible
77
- # Mongrel2-style default one
78
- mod.router :exclusive
79
- end
80
-
81
-
82
65
  # Class methods to add to classes with REST resources.
83
66
  module ClassMethods # :nodoc:
84
67
  include Sequel::Inflections
@@ -96,6 +79,23 @@ module Strelka::App::RestResources
96
79
  attr_reader :global_options
97
80
 
98
81
 
82
+ ### Extension callback -- overridden to also install dependencies.
83
+ def self::extended( obj )
84
+ super
85
+
86
+ # Load the plugins this one depends on if they aren't already
87
+ obj.plugins :routing, :negotiation, :parameters
88
+
89
+ # Add validations for the limit and offset parameters
90
+ obj.param :limit, :integer
91
+ obj.param :offset, :integer
92
+
93
+ # Use the 'exclusive' router instead of the more-flexible
94
+ # Mongrel2-style default one
95
+ obj.router :exclusive
96
+ end
97
+
98
+
99
99
  ### Set the prefix for all following resource routes to +route+.
100
100
  def resource_prefix( route )
101
101
  self.global_options[ :prefix ] = route
@@ -141,7 +141,7 @@ module Strelka::App::RestResources
141
141
  ### Add parameter declarations for parameters related to +rsrcobj+.
142
142
  def add_parameters( rsrcobj, options )
143
143
  Strelka.log.debug "Declaring validations for columns from %p" % [ rsrcobj ]
144
- self.untaint_all_constraints = true
144
+ self.untaint_all_constraints
145
145
  rsrcobj.db_schema.each do |col, config|
146
146
  Strelka.log.debug " %s (%p)" % [ col, config[:type] ]
147
147
  param col, config[:type]
@@ -155,9 +155,11 @@ module Strelka::App::RestResources
155
155
  # :TODO: Documentation for HTML mode (possibly using http://swagger.wordnik.com/)
156
156
  Strelka.log.debug "Adding OPTIONS handler for %p" % [ route, rsrcobj ]
157
157
  self.add_route( :OPTIONS, route, options ) do |req|
158
+ self.log.debug "OPTIONS handler!"
158
159
  verbs = self.class.resource_verbs[ route ].sort
159
160
  res = req.response
160
161
 
162
+ self.log.debug " making a reply with Allowed: %s" % [ verbs.join(', ') ]
161
163
  res.header.allowed = verbs.join(', ')
162
164
  res.content_type = 'text/plain'
163
165
  res.body = ''
@@ -498,4 +500,10 @@ module Strelka::App::RestResources
498
500
  end # module ClassMethods
499
501
 
500
502
 
503
+ ### This is just here for logging.
504
+ def handle_request( * ) # :nodoc:
505
+ self.log.debug "[:restresources] handling request for REST resource."
506
+ super
507
+ end
508
+
501
509
  end # module Strelka::App::RestResources
@@ -216,10 +216,12 @@ module Strelka::App::Routing
216
216
  return pattern.split( '/' ).collect do |component|
217
217
 
218
218
  if component.start_with?( ':' )
219
+ Strelka.log.debug "translating parameter component %p to a regexp" % [component]
219
220
  raise ScriptError,
220
221
  "parameter-based routing not supported without a 'parameters' plugin" unless
221
- self.respond_to?( :extract_route_from_constraint )
222
- self.extract_route_from_constraint( component )
222
+ self.respond_to?( :paramvalidator )
223
+ component = component.slice( 1..-1 )
224
+ self.paramvalidator.constraint_regexp_for( component )
223
225
  else
224
226
  component
225
227
  end
@@ -249,6 +251,8 @@ module Strelka::App::Routing
249
251
 
250
252
  ### Dispatch the request using the Router.
251
253
  def handle_request( request, &block )
254
+ self.log.debug "[:routing] Routing request using %p" % [ self.router.class ]
255
+
252
256
  if route = self.router.route_request( request )
253
257
  # Track which route was chosen for later plugins
254
258
  request.notes[:routing][:route] = route
@@ -257,6 +261,8 @@ module Strelka::App::Routing
257
261
  else
258
262
  finish_with HTTP::NOT_FOUND, "The requested resource was not found on this server."
259
263
  end
264
+
265
+ self.log.debug "[:routing] Done with routing."
260
266
  end
261
267
 
262
268
  end # module Strelka::App::Routing
@@ -172,6 +172,13 @@ module Strelka::App::Sessions
172
172
  return super
173
173
  end
174
174
 
175
+
176
+ ### This is just here for logging.
177
+ def handle_request( * ) # :nodoc:
178
+ self.log.debug "[:sessions] Adding sessions to the transaction."
179
+ super
180
+ end
181
+
175
182
  end # module Strelka::App::Sessions
176
183
 
177
184
 
@@ -90,7 +90,7 @@ module Strelka::App::Templating
90
90
  include Strelka::Constants
91
91
  extend Strelka::App::Plugin
92
92
 
93
- run_before :routing, :negotiation
93
+ run_before :routing, :negotiation, :errors
94
94
  run_after :filters
95
95
 
96
96
 
data/lib/strelka/app.rb CHANGED
@@ -53,8 +53,8 @@ class Strelka::App < Mongrel2::Handler
53
53
  Strelka.logger.level = Logger::DEBUG if $VERBOSE
54
54
  Strelka.logger.formatter = Strelka::Logging::ColorFormatter.new( Strelka.logger ) if $stderr.tty?
55
55
 
56
+ Strelka.log.info "Starting up with appid %p." % [ appid ]
56
57
  super( appid )
57
-
58
58
  end
59
59
 
60
60
 
@@ -172,6 +172,13 @@ class Strelka::App < Mongrel2::Handler
172
172
  ### I N S T A N C E M E T H O D S
173
173
  #################################################################
174
174
 
175
+ ### Dump the application stack when a new instance is created.
176
+ def initialize( * )
177
+ self.dump_application_stack
178
+ super
179
+ end
180
+
181
+
175
182
  ######
176
183
  public
177
184
  ######
@@ -192,11 +199,15 @@ class Strelka::App < Mongrel2::Handler
192
199
 
193
200
  # Dispatch the request after allowing plugins to to their thing
194
201
  status_info = catch( :finish ) do
202
+ self.log.debug "Starting dispatch of request %p" % [ request ]
195
203
 
196
204
  # Run fixup hooks on the request
197
205
  request = self.fixup_request( request )
206
+ self.log.debug " done with request fixup"
198
207
  response = self.handle_request( request )
208
+ self.log.debug " done with handler"
199
209
  response = self.fixup_response( response )
210
+ self.log.debug " done with response fixup"
200
211
 
201
212
  nil # rvalue for the catch
202
213
  end
@@ -337,5 +348,18 @@ class Strelka::App < Mongrel2::Handler
337
348
  return response
338
349
  end
339
350
 
351
+
352
+ ### Output the application stack into the logfile.
353
+ def dump_application_stack
354
+ stack = self.class.ancestors.
355
+ reverse.
356
+ drop_while {|mod| mod != Strelka::App }.
357
+ select {|mod| mod.respond_to?(:plugin_name) }.
358
+ reverse.
359
+ collect {|mod| mod.plugin_name }
360
+
361
+ self.log.info "Application stack: request -> %s" % [ stack.join(" -> ") ]
362
+ end
363
+
340
364
  end # class Strelka::App
341
365
 
@@ -12,7 +12,9 @@ module Strelka::Constants
12
12
  include Mongrel2::Constants
13
13
 
14
14
  # The data directory in the project if that exists, otherwise the gem datadir
15
- DATADIR = if File.directory?( 'data/strelka' )
15
+ DATADIR = if ENV['STRELKA_DATADIR']
16
+ Pathname( ENV['STRELKA_DATADIR'] )
17
+ elsif File.directory?( 'data/strelka' )
16
18
  Pathname( 'data/strelka' )
17
19
  elsif path = Gem.datadir('strelka')
18
20
  Pathname( path )