lurker 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.hound.yml +62 -0
- data/.rubocop.yml +62 -0
- data/Gemfile +1 -1
- data/Rakefile +2 -268
- data/features/atom_persistent_within_the_same_type.feature +0 -2
- data/features/controller_nested_schema_scaffolding.feature +0 -1
- data/features/controller_schema_scaffolding.feature +0 -1
- data/features/html_generation.feature +0 -1
- data/features/minitest.feature +0 -2
- data/features/multidomain_support.feature +0 -1
- data/features/multitype_request_support.feature +0 -2
- data/features/partials.feature +0 -1
- data/features/request_nested_schema_scaffolding.feature +0 -1
- data/features/request_schema_scaffolding.feature +0 -1
- data/features/schema_suffixes.feature +0 -1
- data/features/schema_updating_within_test_suite.feature +37 -2
- data/features/test_endpoint.feature +0 -1
- data/lib/lurker.rb +5 -1
- data/lib/lurker/endpoint.rb +121 -176
- data/lib/lurker/endpoint/http_parameters.rb +77 -0
- data/lib/lurker/endpoint/response_codes.rb +42 -0
- data/lib/lurker/erb_schema_context.rb +8 -6
- data/lib/lurker/jaml_descriptor.rb +8 -1
- data/lib/lurker/json_schema_hash.rb +48 -0
- data/lib/lurker/presenters/base_presenter.rb +1 -2
- data/lib/lurker/presenters/endpoint_presenter.rb +17 -10
- data/lib/lurker/presenters/json_presenter.rb +6 -6
- data/lib/lurker/presenters/schema_presenter.rb +15 -14
- data/lib/lurker/presenters/service_presenter.rb +5 -6
- data/lib/lurker/rendering_controller.rb +0 -1
- data/lib/lurker/request.rb +3 -1
- data/lib/lurker/sandbox.rb +0 -1
- data/lib/lurker/schema_modifier.rb +4 -2
- data/lib/lurker/schema_modifier/atom.rb +14 -5
- data/lib/lurker/server.rb +4 -5
- data/lib/lurker/service.rb +3 -4
- data/lib/lurker/spec_helper/rspec.rb +15 -57
- data/lib/lurker/spy.rb +9 -5
- data/lib/lurker/templates/layouts/_sidemenu.html.erb +1 -1
- data/lib/lurker/templates/lurker/rendering/show.html.erb +5 -1
- data/lib/lurker/templates/public/application.css +2 -2
- data/lib/lurker/templates/stylesheets/docs.css +0 -1
- data/lib/lurker/utils.rb +18 -0
- data/lib/lurker/validator.rb +3 -6
- data/lib/lurker/version.rb +1 -1
- data/tasks/build.rake +57 -0
- data/tasks/deploy.rake +139 -0
- data/tasks/generate.rake +78 -0
- data/templates/generate_stuff.rb +4 -4
- data/templates/lurker_app.rb +1 -1
- metadata +9 -2
- metadata.gz.sig +0 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
module Lurker
|
2
|
+
class Endpoint
|
3
|
+
class HttpParameters
|
4
|
+
extend Forwardable
|
5
|
+
include Lurker::Utils
|
6
|
+
|
7
|
+
ID = 'id'.freeze
|
8
|
+
TYPE = 'type'.freeze
|
9
|
+
OBJECT = 'object'.freeze
|
10
|
+
PROPERTIES = 'properties'.freeze
|
11
|
+
ADDITIONAL_PROPERTIES = 'additionalProperties'.freeze
|
12
|
+
|
13
|
+
delegate [:[], :key?, :keys, :empty?] => :http_parameters
|
14
|
+
|
15
|
+
attr_reader :errors
|
16
|
+
|
17
|
+
def initialize(schema, options = {})
|
18
|
+
@schema = schema
|
19
|
+
|
20
|
+
@schema_key = options.fetch(:schema_key)
|
21
|
+
@schema_id = options.fetch(:schema_id)
|
22
|
+
@human_name = options.fetch(:human_name, @schema_key)
|
23
|
+
|
24
|
+
@schema[@schema_key] ||= {}
|
25
|
+
@errors = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def add(parameters)
|
29
|
+
@schema[@schema_key] = Lurker::SchemaModifier.merge!(
|
30
|
+
Lurker::JsonSchemaHash.new(http_parameters, @schema_id), stringify_keys(parameters)
|
31
|
+
).to_h
|
32
|
+
end
|
33
|
+
|
34
|
+
# TODO : Split the collecting of errors and representations of errors
|
35
|
+
def validate(parameters)
|
36
|
+
errors = Lurker::Validator.new(schemify(http_parameters), stringify_keys(parameters),
|
37
|
+
record_errors: true).validate
|
38
|
+
|
39
|
+
@errors = errors.map { |error| "- #{error}" }
|
40
|
+
@errors.unshift(@human_name) unless @errors.empty?
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def http_parameters
|
46
|
+
@schema[@schema_key]
|
47
|
+
end
|
48
|
+
|
49
|
+
def schemify(object)
|
50
|
+
set_additional_properties_false_on(object).tap do |schema|
|
51
|
+
schema[ID] = "file://#{@schema_id}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def set_additional_properties_false_on(object)
|
56
|
+
if object.is_a? Hash
|
57
|
+
copy = object.dup
|
58
|
+
|
59
|
+
if object[TYPE] == OBJECT || object.key?(PROPERTIES)
|
60
|
+
copy[ADDITIONAL_PROPERTIES] ||= false
|
61
|
+
end
|
62
|
+
|
63
|
+
object.each do |key, value|
|
64
|
+
next if key == ADDITIONAL_PROPERTIES
|
65
|
+
copy[key] = set_additional_properties_false_on(value)
|
66
|
+
end
|
67
|
+
|
68
|
+
copy
|
69
|
+
elsif object.is_a? Array
|
70
|
+
copy = object.map { |value| set_additional_properties_false_on(value) }
|
71
|
+
else
|
72
|
+
object
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Lurker
|
2
|
+
class Endpoint
|
3
|
+
class ResponseCodes
|
4
|
+
RESPONSE_CODES = 'responseCodes'.freeze
|
5
|
+
DESCRIPTION = 'description'.freeze
|
6
|
+
SUCCESSFUL = 'successful'.freeze
|
7
|
+
STATUS = 'status'.freeze
|
8
|
+
|
9
|
+
def initialize(schema)
|
10
|
+
@schema = schema
|
11
|
+
@schema[RESPONSE_CODES] ||= []
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(status_code, successful, options = {})
|
15
|
+
response_code = {
|
16
|
+
STATUS => status_code,
|
17
|
+
SUCCESSFUL => successful,
|
18
|
+
DESCRIPTION => options.fetch(:description, '')
|
19
|
+
}
|
20
|
+
|
21
|
+
Lurker::SchemaModifier.append!(@schema[RESPONSE_CODES], response_code)
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate!(status_code, successful)
|
25
|
+
return if exists?(status_code, successful)
|
26
|
+
|
27
|
+
raise Lurker::UndocumentedResponseCode,
|
28
|
+
'Undocumented response: %s, successful: %s' % [
|
29
|
+
status_code, successful
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
def exists?(status_code, successful)
|
34
|
+
!!@schema[RESPONSE_CODES].detect do |response_code|
|
35
|
+
response_code[SUCCESSFUL] == successful &&
|
36
|
+
(response_code[STATUS] == status_code || # 200
|
37
|
+
response_code[STATUS].to_i == status_code) # "200 OK"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,9 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
module Lurker
|
2
|
+
class ErbSchemaContext
|
3
|
+
def allowed(*names)
|
4
|
+
"Allowed values: #{names.join(', ')}"
|
5
|
+
end
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
def get_binding
|
8
|
+
binding
|
9
|
+
end
|
8
10
|
end
|
9
11
|
end
|
@@ -2,6 +2,14 @@
|
|
2
2
|
# readed as dumped JSON
|
3
3
|
# to hack `open('...json').read`
|
4
4
|
class Lurker::JamlDescriptor
|
5
|
+
module Rescue
|
6
|
+
def open(uri)
|
7
|
+
Kernel.open(uri)
|
8
|
+
rescue Errno::ENOENT
|
9
|
+
Lurker::JamlDescriptor.new(uri)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
5
13
|
def initialize(uri)
|
6
14
|
@uri = uri.gsub(/\#$/, '').gsub(/\.json/, '.json.yml')
|
7
15
|
@fd = open(@uri)
|
@@ -11,4 +19,3 @@ class Lurker::JamlDescriptor
|
|
11
19
|
@read ||= JSON.dump(YAML.load(@fd.read))
|
12
20
|
end
|
13
21
|
end
|
14
|
-
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Lurker
|
2
|
+
class JsonSchemaHash
|
3
|
+
attr_accessor :schema_hash
|
4
|
+
include JamlDescriptor::Rescue
|
5
|
+
|
6
|
+
def initialize(schema_hash, uri)
|
7
|
+
@to_s = uri
|
8
|
+
@uri = URI.parse(uri)
|
9
|
+
@uri = URI.parse("file://#{uri}") if @uri.relative?
|
10
|
+
|
11
|
+
@schema_hash = Hash[schema_hash.map do |k, v|
|
12
|
+
if k == '$ref' && v.is_a?(String)
|
13
|
+
uri = @uri.merge(v)
|
14
|
+
schema_hash = JSON.parse(open(uri.to_s).read)
|
15
|
+
[k, JsonSchemaHash.new(schema_hash, uri.to_s)]
|
16
|
+
elsif v.is_a?(Hash)
|
17
|
+
[k, JsonSchemaHash.new(v, @to_s)]
|
18
|
+
else
|
19
|
+
[k, v]
|
20
|
+
end
|
21
|
+
end]
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond_to_missing?(method, include_private = false)
|
25
|
+
@schema_hash.send(:respond_to_missing?, method, include_private)
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(method, *args, &block)
|
29
|
+
@schema_hash.send method, *args, &block
|
30
|
+
end
|
31
|
+
|
32
|
+
def is_a?(*args)
|
33
|
+
@schema_hash.is_a?(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_h
|
37
|
+
Hash[@schema_hash.map do |k, v|
|
38
|
+
if JsonSchemaHash === v
|
39
|
+
[k, v.to_h]
|
40
|
+
elsif v.is_a?(Array)
|
41
|
+
[k, v.map { |i| JsonSchemaHash === i ? i.to_h : i }]
|
42
|
+
else
|
43
|
+
[k, v]
|
44
|
+
end
|
45
|
+
end]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -3,7 +3,6 @@ require 'json'
|
|
3
3
|
require 'forwardable'
|
4
4
|
|
5
5
|
class Lurker::BasePresenter
|
6
|
-
|
7
6
|
attr_reader :options
|
8
7
|
|
9
8
|
def initialize(options = {})
|
@@ -61,7 +60,7 @@ class Lurker::BasePresenter
|
|
61
60
|
def path_for_template(filename)
|
62
61
|
template_dir = options[:template_directory]
|
63
62
|
template_path = File.join(template_dir, filename) if template_dir
|
64
|
-
if template_path.nil? || !File.
|
63
|
+
if template_path.nil? || !File.exist?(template_path)
|
65
64
|
template_path = File.join(File.dirname(__FILE__), "../templates", filename)
|
66
65
|
end
|
67
66
|
template_path
|
@@ -2,9 +2,6 @@
|
|
2
2
|
class Lurker::EndpointPresenter < Lurker::BasePresenter
|
3
3
|
attr_accessor :service_presenter, :endpoint, :endpoint_presenter
|
4
4
|
|
5
|
-
extend Forwardable
|
6
|
-
def_delegators :endpoint, :description
|
7
|
-
|
8
5
|
def initialize(endpoint, options = {})
|
9
6
|
super(options)
|
10
7
|
@endpoint = endpoint
|
@@ -21,7 +18,7 @@ class Lurker::EndpointPresenter < Lurker::BasePresenter
|
|
21
18
|
end
|
22
19
|
|
23
20
|
def relative_path(extension = ".html")
|
24
|
-
'%s%s-%s%s' % [
|
21
|
+
'%s%s-%s%s' % [options[:prefix], endpoint.path, endpoint.verb, extension]
|
25
22
|
end
|
26
23
|
|
27
24
|
def url(extension = ".html")
|
@@ -29,7 +26,7 @@ class Lurker::EndpointPresenter < Lurker::BasePresenter
|
|
29
26
|
end
|
30
27
|
|
31
28
|
def title
|
32
|
-
|
29
|
+
description_parts.first.to_s
|
33
30
|
end
|
34
31
|
|
35
32
|
def prefix
|
@@ -38,10 +35,14 @@ class Lurker::EndpointPresenter < Lurker::BasePresenter
|
|
38
35
|
|
39
36
|
def zws_ify(str)
|
40
37
|
# zero-width-space, makes long lines friendlier for breaking
|
41
|
-
#str.gsub(/\//, '​/') if str
|
38
|
+
# str.gsub(/\//, '​/') if str
|
42
39
|
str
|
43
40
|
end
|
44
41
|
|
42
|
+
def description
|
43
|
+
description_parts[1..-1].join("\n")
|
44
|
+
end
|
45
|
+
|
45
46
|
def root_path
|
46
47
|
URI.parse("file://#{endpoint.endpoint_path}")
|
47
48
|
end
|
@@ -75,7 +76,7 @@ class Lurker::EndpointPresenter < Lurker::BasePresenter
|
|
75
76
|
return if endpoint.request_parameters.empty?
|
76
77
|
Lurker::JsonPresenter.new(
|
77
78
|
example_from_schema(endpoint.request_parameters, endpoint.schema)
|
78
|
-
.reject { |k,_| endpoint.url_params.keys.include? k }
|
79
|
+
.reject { |k, _| endpoint.url_params.keys.include? k }
|
79
80
|
)
|
80
81
|
end
|
81
82
|
|
@@ -208,20 +209,26 @@ class Lurker::EndpointPresenter < Lurker::BasePresenter
|
|
208
209
|
end
|
209
210
|
|
210
211
|
def example_from_array(array, parent=nil)
|
211
|
-
if array["items"].
|
212
|
+
if array["items"].is_a? Array
|
212
213
|
example = []
|
213
214
|
array["items"].each do |item|
|
214
215
|
example << example_from_schema(item, parent)
|
215
216
|
end
|
216
217
|
example
|
217
|
-
elsif (array["items"] || {})["type"].
|
218
|
+
elsif (array["items"] || {})["type"].is_a? Array
|
218
219
|
example = []
|
219
220
|
array["items"]["type"].each do |item|
|
220
221
|
example << example_from_schema(item, parent)
|
221
222
|
end
|
222
223
|
example
|
223
224
|
else
|
224
|
-
[
|
225
|
+
[example_from_schema(array["items"], parent)]
|
225
226
|
end
|
226
227
|
end
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
def description_parts
|
232
|
+
endpoint.description.to_s.strip.split(/\n+/)
|
233
|
+
end
|
227
234
|
end
|
@@ -8,14 +8,14 @@ class Lurker::JsonPresenter
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def to_html
|
11
|
-
if json.
|
11
|
+
if json.is_a? String
|
12
12
|
'<tt>"%s"</tt>' % json.gsub(/\"/, 'quot;')
|
13
|
-
elsif json.
|
14
|
-
json.
|
15
|
-
json.
|
13
|
+
elsif json.is_a?(Numeric) ||
|
14
|
+
json.is_a?(TrueClass) ||
|
15
|
+
json.is_a?(FalseClass)
|
16
16
|
'<tt>%s</tt>' % json
|
17
|
-
elsif json.
|
18
|
-
json.
|
17
|
+
elsif json.is_a?(Hash) ||
|
18
|
+
json.is_a?(Array)
|
19
19
|
'<pre><code>%s</code></pre>' % JSON.pretty_generate(json)
|
20
20
|
end
|
21
21
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# An BasePresenter for a JSON Schema fragment. Like most JSON
|
2
2
|
# schema things, has a tendency to recurse.
|
3
3
|
class Lurker::SchemaPresenter < Lurker::BasePresenter
|
4
|
+
attr_reader :schema
|
5
|
+
|
4
6
|
FORMATTED_KEYS = %w(
|
5
7
|
description
|
6
8
|
type
|
@@ -29,7 +31,7 @@ class Lurker::SchemaPresenter < Lurker::BasePresenter
|
|
29
31
|
options[:nested] > 0
|
30
32
|
end
|
31
33
|
|
32
|
-
def to_html
|
34
|
+
def to_html(parent_key=nil)
|
33
35
|
html = StringIO.new
|
34
36
|
|
35
37
|
html << '<span class="deprecated">Deprecated</span>' if deprecated?
|
@@ -39,21 +41,20 @@ class Lurker::SchemaPresenter < Lurker::BasePresenter
|
|
39
41
|
|
40
42
|
html << '<ul>'
|
41
43
|
begin
|
42
|
-
html << '<li>Required: %s</li>' % required? if nested?
|
44
|
+
html << '<li>Required: %s</li>' % required?(parent_key) if nested?
|
43
45
|
html << '<li>Type: %s</li>' % type if type
|
44
46
|
html << '<li>Format: %s</li>' % format if format
|
45
47
|
html << '<li>Example: %s</li>' % example.to_html if example
|
46
48
|
html << enum_html
|
47
49
|
|
48
50
|
(@schema.keys - FORMATTED_KEYS).each do |key|
|
49
|
-
html << '<li>%s: %s</li>' % [
|
51
|
+
html << '<li>%s: %s</li>' % [key, @schema[key]]
|
50
52
|
end
|
51
53
|
|
52
54
|
html << items_html
|
53
55
|
html << properties_html
|
54
56
|
end
|
55
57
|
|
56
|
-
|
57
58
|
html << '</ul>'
|
58
59
|
html << '</div>'
|
59
60
|
|
@@ -62,10 +63,10 @@ class Lurker::SchemaPresenter < Lurker::BasePresenter
|
|
62
63
|
|
63
64
|
def type
|
64
65
|
t = @schema["type"]
|
65
|
-
if t.
|
66
|
+
if t.is_a? Array
|
66
67
|
types = t.map do |type|
|
67
|
-
if type.
|
68
|
-
'<li>%s</li>' % self.class.new(type, options).to_html
|
68
|
+
if type.is_a? Hash
|
69
|
+
'<li>%s</li>' % self.class.new(type, options.merge(parent: self)).to_html
|
69
70
|
else
|
70
71
|
'<li>%s</li>' % type
|
71
72
|
end
|
@@ -82,17 +83,17 @@ class Lurker::SchemaPresenter < Lurker::BasePresenter
|
|
82
83
|
end
|
83
84
|
|
84
85
|
def example
|
85
|
-
return unless
|
86
|
+
return unless @schema.key?("example")
|
86
87
|
|
87
|
-
Lurker::JsonPresenter.new(
|
88
|
+
Lurker::JsonPresenter.new(@schema["example"])
|
88
89
|
end
|
89
90
|
|
90
91
|
def deprecated?
|
91
92
|
@schema["deprecated"]
|
92
93
|
end
|
93
94
|
|
94
|
-
def required?
|
95
|
-
|
95
|
+
def required?(parent_key=nil)
|
96
|
+
((options[:parent].schema['required'] || []).include?(parent_key)) ? "yes" : "no"
|
96
97
|
end
|
97
98
|
|
98
99
|
def enum_html
|
@@ -116,9 +117,9 @@ class Lurker::SchemaPresenter < Lurker::BasePresenter
|
|
116
117
|
html = ""
|
117
118
|
html << '<li>Items'
|
118
119
|
|
119
|
-
sub_options = options.merge(:nested => options[:nested] + 1)
|
120
|
+
sub_options = options.merge(:nested => options[:nested] + 1, :parent => self)
|
120
121
|
|
121
|
-
if items.
|
122
|
+
if items.is_a? Array
|
122
123
|
item.compact.each do |item|
|
123
124
|
html << self.class.new(item, sub_options).to_html
|
124
125
|
end
|
@@ -153,7 +154,7 @@ class Lurker::SchemaPresenter < Lurker::BasePresenter
|
|
153
154
|
'<tt>%s</tt>' % key,
|
154
155
|
schema_slug(key, property)
|
155
156
|
)
|
156
|
-
html << self.class.new(property, options.merge(:nested => options[:nested] + 1)).to_html
|
157
|
+
html << self.class.new(property, options.merge(:nested => options[:nested] + 1, :parent => self)).to_html(key)
|
157
158
|
html << '</li>'
|
158
159
|
end
|
159
160
|
|
@@ -5,7 +5,6 @@ class Lurker::ServicePresenter < Lurker::BasePresenter
|
|
5
5
|
extend Forwardable
|
6
6
|
def_delegators :service, :description, :discussion, :name, :service_dir
|
7
7
|
|
8
|
-
|
9
8
|
def initialize(service, options = {}, &block)
|
10
9
|
super(options)
|
11
10
|
@service = service
|
@@ -24,7 +23,7 @@ class Lurker::ServicePresenter < Lurker::BasePresenter
|
|
24
23
|
|
25
24
|
def domains
|
26
25
|
return service.domains if service.domains.present?
|
27
|
-
{ '/' => 'Local'}
|
26
|
+
{ '/' => 'Local' }
|
28
27
|
end
|
29
28
|
|
30
29
|
def default_domain
|
@@ -34,7 +33,7 @@ class Lurker::ServicePresenter < Lurker::BasePresenter
|
|
34
33
|
|
35
34
|
def name_as_link(options = {})
|
36
35
|
path = index_path
|
37
|
-
'<a href="%s">%s %s</a>' % [
|
36
|
+
'<a href="%s">%s %s</a>' % [path, options[:prefix], service.name]
|
38
37
|
end
|
39
38
|
|
40
39
|
def slug_name
|
@@ -42,11 +41,11 @@ class Lurker::ServicePresenter < Lurker::BasePresenter
|
|
42
41
|
end
|
43
42
|
|
44
43
|
def url(extension = ".html")
|
45
|
-
'%s-%s%s' % [
|
44
|
+
'%s-%s%s' % [@endpoint.path, @endpoint.verb, extension]
|
46
45
|
end
|
47
46
|
|
48
47
|
def endpoints
|
49
|
-
|
48
|
+
unless @endpoints
|
50
49
|
@endpoints = []
|
51
50
|
prefix = nil
|
52
51
|
|
@@ -73,7 +72,7 @@ class Lurker::ServicePresenter < Lurker::BasePresenter
|
|
73
72
|
|
74
73
|
def endpoints_by_prefix
|
75
74
|
@endpoints_by_prefix ||= begin
|
76
|
-
hash = Hash.new { |h,k| h[k] = Array.new }
|
75
|
+
hash = Hash.new { |h, k| h[k] = Array.new }
|
77
76
|
service.endpoints.sort_by(&:endpoint_path).each do |endpoint|
|
78
77
|
presenter = Lurker::EndpointPresenter.new(endpoint, options)
|
79
78
|
presenter.service_presenter = self
|