lurker 0.6.1 → 0.6.2
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
- 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
|