sinatra-schema 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +14 -0
- data/lib/sinatra/schema/definition.rb +35 -4
- data/lib/sinatra/schema/dsl/definitions.rb +34 -19
- data/lib/sinatra/schema/dsl/links.rb +2 -2
- data/lib/sinatra/schema/dsl/resources.rb +1 -1
- data/lib/sinatra/schema/link.rb +6 -2
- data/lib/sinatra/schema/{param_handling.rb → param_parsing.rb} +12 -6
- data/lib/sinatra/schema/param_validation.rb +7 -2
- data/lib/sinatra/schema/resource.rb +1 -4
- data/lib/sinatra/schema/version.rb +1 -1
- data/lib/sinatra/schema.rb +2 -2
- data/spec/definition_spec.rb +64 -0
- data/spec/dsl/definitions_spec.rb +12 -4
- data/spec/dsl/resources_spec.rb +8 -0
- data/spec/{param_handling_spec.rb → param_parsing_spec.rb} +17 -3
- data/spec/param_validation_spec.rb +8 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MGZiZDNiMDcyM2EzNzM1MjdmZWUxOTkzZGNjY2QxNmVmN2RmODRiMg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ODY0YjViYzM3YmJhYjIzODc3Y2U3ZmMwMzJmM2Q5YzRiN2M4ZjNmYw==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MDQwY2YwMzU3MTg3ZDkzMGNiMDY2MTFlNjM0MWFkNjYzNzdhZTQzMWVlYjg1
|
10
|
+
NzdhNzk2NjZlM2YyNjQ1MzQxZjA5ZTNiNWQ0NGI5MjYxYjI3MTUzYjYxODMx
|
11
|
+
NDJhYzMyNDI2NDZjZGJlNjVhYzQxMTQ0OTdjOTRkN2Q1ZmQ3NmY=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YjE1MWY4MTVhYTMxYzEyODU0Mjk3Y2NiY2I5YTA5YjBkMTlmOGE4MmZlOTMw
|
14
|
+
NDlkNzU2MmU2OTg0ZGNiYzdkNWRkZjAwYmQ5OWE0MzNhNGE0MDVjZmVkZWFk
|
15
|
+
NzM3MDNkZmEwOGI1OTY4ZWQ3NTg2ODY0MjBiODRlNDU5YTZlOGE=
|
data/README.md
CHANGED
@@ -65,6 +65,20 @@ resource("/albums") do |res|
|
|
65
65
|
end
|
66
66
|
```
|
67
67
|
|
68
|
+
### Nested params
|
69
|
+
|
70
|
+
These are also casted and validated as you'd expect:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
resource("/albums") do |res|
|
74
|
+
resource.link(:post) do |link|
|
75
|
+
link.nested :artist do |a|
|
76
|
+
a.property.text :name
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
68
82
|
### JSON Schema
|
69
83
|
|
70
84
|
The extension will serve a JSON Schema dump at `GET /schema` for you.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Sinatra
|
2
2
|
module Schema
|
3
3
|
class Definition
|
4
|
-
attr_accessor :description, :example, :
|
4
|
+
attr_accessor :description, :example, :id, :type
|
5
5
|
|
6
6
|
def initialize(options={})
|
7
7
|
@description = options[:description]
|
@@ -15,10 +15,10 @@ module Sinatra
|
|
15
15
|
return unless value
|
16
16
|
|
17
17
|
case type
|
18
|
-
when "string"
|
19
|
-
value.to_s
|
20
18
|
when "boolean"
|
21
19
|
%w( t true 1 ).include?(value.to_s)
|
20
|
+
when "email", "string", "uuid"
|
21
|
+
value.to_s
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -27,10 +27,41 @@ module Sinatra
|
|
27
27
|
return if value.nil?
|
28
28
|
|
29
29
|
case type
|
30
|
+
when "boolean"
|
31
|
+
[true, false].include?(value)
|
32
|
+
when "email"
|
33
|
+
value.to_s =~ /\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/
|
30
34
|
when "string"
|
31
35
|
value.is_a?(String)
|
36
|
+
when "uuid"
|
37
|
+
value.to_s =~ /\A[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}\Z/
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_schema
|
42
|
+
schema_type, schema_format = json_schema_type_and_format
|
43
|
+
attrs = { type: schema_type }
|
44
|
+
if schema_format
|
45
|
+
attrs[:format] = schema_format
|
46
|
+
end
|
47
|
+
if description
|
48
|
+
attrs[:description] = description
|
49
|
+
end
|
50
|
+
attrs
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def json_schema_type_and_format
|
56
|
+
case type
|
32
57
|
when "boolean"
|
33
|
-
|
58
|
+
"boolean"
|
59
|
+
when "email"
|
60
|
+
["string", "email"]
|
61
|
+
when "string"
|
62
|
+
"string"
|
63
|
+
when "uuid"
|
64
|
+
["string", "uuid"]
|
34
65
|
end
|
35
66
|
end
|
36
67
|
end
|
@@ -2,23 +2,40 @@ module Sinatra
|
|
2
2
|
module Schema
|
3
3
|
module DSL
|
4
4
|
class Definitions
|
5
|
-
attr_accessor :definition, :resource, :options
|
5
|
+
attr_accessor :definition, :resource, :options, :targets
|
6
6
|
|
7
|
-
def initialize(resource,
|
8
|
-
@options = options
|
7
|
+
def initialize(resource, targets)
|
9
8
|
@resource = resource
|
9
|
+
# array of hashes to receive the definition, first is the resource defs
|
10
|
+
@targets = targets
|
10
11
|
end
|
11
12
|
|
12
|
-
def text(id,
|
13
|
-
|
14
|
-
|
15
|
-
add Definition.new(def_options)
|
13
|
+
def text(id, options={})
|
14
|
+
options.merge!(id: id, type: "string")
|
15
|
+
add Definition.new(options)
|
16
16
|
end
|
17
17
|
|
18
|
-
def bool(id,
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
def bool(id, options={})
|
19
|
+
options.merge!(id: id, type: "boolean")
|
20
|
+
add Definition.new(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def uuid(id, options={})
|
24
|
+
options.merge!(id: id, type: "uuid")
|
25
|
+
add Definition.new(options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def email(id, options={})
|
29
|
+
options.merge!(id: id, type: "email")
|
30
|
+
add Definition.new(options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def nested(id)
|
34
|
+
# add a space in the definitions/properties for the nested def:
|
35
|
+
targets.each { |h| h[id] = {} }
|
36
|
+
|
37
|
+
# yield a new DSL with updated targets
|
38
|
+
yield Definitions.new(resource, targets.map { |h| h[id] })
|
22
39
|
end
|
23
40
|
|
24
41
|
def ref(id)
|
@@ -33,14 +50,12 @@ module Sinatra
|
|
33
50
|
protected
|
34
51
|
|
35
52
|
def add(definition, reference=false)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
if link = options[:link]
|
43
|
-
link.properties[definition.id] = definition
|
53
|
+
targets.each_with_index do |hash, i|
|
54
|
+
# here's the trick, and here's why the first target is always the
|
55
|
+
# resource def: skip it when adding a reference (eg: it's already)
|
56
|
+
# in the resource def, just add the property!
|
57
|
+
next if reference && i == 0
|
58
|
+
hash[definition.id] = definition
|
44
59
|
end
|
45
60
|
end
|
46
61
|
end
|
@@ -28,13 +28,13 @@ module Sinatra
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def property
|
31
|
-
DSL::Definitions.new(resource,
|
31
|
+
DSL::Definitions.new(resource, [resource.defs, link.properties])
|
32
32
|
end
|
33
33
|
|
34
34
|
protected
|
35
35
|
|
36
36
|
def build_link
|
37
|
-
full_href = "#{resource.path}/#{href.
|
37
|
+
full_href = "#{resource.path}/#{href}".gsub("//", "/").chomp("/")
|
38
38
|
Link.new(resource: resource, method: method, href: full_href)
|
39
39
|
end
|
40
40
|
end
|
data/lib/sinatra/schema/link.rb
CHANGED
@@ -11,12 +11,16 @@ module Sinatra
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def register(app)
|
14
|
+
app.send(method.downcase, href, &handler)
|
15
|
+
end
|
16
|
+
|
17
|
+
def handler
|
14
18
|
link = self
|
15
|
-
|
19
|
+
lambda do
|
16
20
|
begin
|
17
21
|
schema_params = parse_params(link.properties)
|
18
22
|
validate_params!(schema_params, link.properties)
|
19
|
-
res = link.action_block
|
23
|
+
res = instance_exec(schema_params, &link.action_block)
|
20
24
|
link.resource.validate_response!(res)
|
21
25
|
res
|
22
26
|
rescue RuntimeError => e
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Sinatra
|
2
2
|
module Schema
|
3
|
-
module
|
3
|
+
module ParamParsing
|
4
4
|
def parse_params(properties)
|
5
5
|
case request.media_type
|
6
6
|
when nil, "application/json"
|
@@ -29,12 +29,18 @@ module Sinatra
|
|
29
29
|
raise "Invalid JSON"
|
30
30
|
end
|
31
31
|
|
32
|
-
def cast_regular_params(properties)
|
33
|
-
casted_params =
|
32
|
+
def cast_regular_params(properties, root=params)
|
33
|
+
casted_params = root.inject({}) do |casted, (k, v)|
|
34
34
|
definition = properties[k.to_sym]
|
35
|
-
|
36
|
-
#
|
37
|
-
|
35
|
+
|
36
|
+
# handle nested params
|
37
|
+
if definition.is_a?(Hash) || v.is_a?(Hash)
|
38
|
+
casted[k] = cast_regular_params(definition, v)
|
39
|
+
else
|
40
|
+
# if there's no definition just leave the original param,
|
41
|
+
# let the validation raise on this later:
|
42
|
+
casted[k] = definition ? definition.cast(v) : v
|
43
|
+
end
|
38
44
|
casted
|
39
45
|
end
|
40
46
|
indifferent_params(casted_params)
|
@@ -13,8 +13,13 @@ module Sinatra
|
|
13
13
|
end
|
14
14
|
|
15
15
|
properties.each do |id, definition|
|
16
|
-
|
17
|
-
|
16
|
+
# handle nested params
|
17
|
+
if definition.is_a?(Hash)
|
18
|
+
validate_params!(params[id], definition)
|
19
|
+
else
|
20
|
+
unless definition.valid?(params[id])
|
21
|
+
raise "Bad param: #{id}"
|
22
|
+
end
|
18
23
|
end
|
19
24
|
end
|
20
25
|
end
|
@@ -38,10 +38,7 @@ module Sinatra
|
|
38
38
|
description: description,
|
39
39
|
type: "object",
|
40
40
|
definitions: defs.inject({}) { |h, (id, definition)|
|
41
|
-
h[id] =
|
42
|
-
description: definition.description,
|
43
|
-
type: definition.type,
|
44
|
-
}
|
41
|
+
h[id] = definition.to_schema
|
45
42
|
h
|
46
43
|
},
|
47
44
|
links: links.map { |link|
|
data/lib/sinatra/schema.rb
CHANGED
@@ -5,7 +5,7 @@ require "multi_json"
|
|
5
5
|
|
6
6
|
require "sinatra/schema/definition"
|
7
7
|
require "sinatra/schema/link"
|
8
|
-
require "sinatra/schema/
|
8
|
+
require "sinatra/schema/param_parsing"
|
9
9
|
require "sinatra/schema/param_validation"
|
10
10
|
require "sinatra/schema/resource"
|
11
11
|
require "sinatra/schema/root"
|
@@ -17,7 +17,7 @@ require "sinatra/schema/dsl/resources"
|
|
17
17
|
module Sinatra
|
18
18
|
module Schema
|
19
19
|
def self.registered(app)
|
20
|
-
app.helpers
|
20
|
+
app.helpers ParamParsing
|
21
21
|
app.helpers ParamValidation
|
22
22
|
app.get "/schema" do
|
23
23
|
content_type("application/schema+json")
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Sinatra::Schema::Definition do
|
4
|
+
let(:definition) { Sinatra::Schema::Definition.new }
|
5
|
+
|
6
|
+
describe "#cast" do
|
7
|
+
it "casts booleans" do
|
8
|
+
definition.type = "boolean"
|
9
|
+
assert_equal true, definition.cast("t")
|
10
|
+
assert_equal true, definition.cast("true")
|
11
|
+
assert_equal true, definition.cast("1")
|
12
|
+
assert_equal false, definition.cast("0")
|
13
|
+
assert_equal false, definition.cast("false")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "casts text" do
|
17
|
+
definition.type = "string"
|
18
|
+
assert_equal "123", definition.cast(123)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#valid?" do
|
23
|
+
it "detects booleans" do
|
24
|
+
definition.type = "boolean"
|
25
|
+
assert definition.valid?(true)
|
26
|
+
assert definition.valid?(false)
|
27
|
+
refute definition.valid?("true")
|
28
|
+
end
|
29
|
+
|
30
|
+
it "detects emails" do
|
31
|
+
definition.type = "email"
|
32
|
+
assert definition.valid?("foo@bar.com")
|
33
|
+
refute definition.valid?("foobar.com")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "detects text" do
|
37
|
+
definition.type = "string"
|
38
|
+
assert definition.valid?("foo")
|
39
|
+
refute definition.valid?(123)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "detects uuids" do
|
43
|
+
definition.type = "uuid"
|
44
|
+
assert definition.valid?(SecureRandom.uuid)
|
45
|
+
refute definition.valid?("wrong")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#to_schema" do
|
50
|
+
it "dumps emails" do
|
51
|
+
definition.type = "email"
|
52
|
+
schema = definition.to_schema
|
53
|
+
assert_equal "string", schema[:type]
|
54
|
+
assert_equal "email", schema[:format]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "dumps uuids" do
|
58
|
+
definition.type = "uuid"
|
59
|
+
schema = definition.to_schema
|
60
|
+
assert_equal "string", schema[:type]
|
61
|
+
assert_equal "uuid", schema[:format]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -2,8 +2,7 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
describe Sinatra::Schema::DSL::Definitions do
|
4
4
|
let(:resource) { Sinatra::Schema::Resource.new(path: "/foobar") }
|
5
|
-
let(:dsl) { described_class.new(resource,
|
6
|
-
let(:options) { Hash.new }
|
5
|
+
let(:dsl) { described_class.new(resource, [resource.defs, resource.properties]) }
|
7
6
|
let(:root) { Sinatra::Schema::Root.instance }
|
8
7
|
|
9
8
|
it "adds a string definition to the resource" do
|
@@ -18,9 +17,18 @@ describe Sinatra::Schema::DSL::Definitions do
|
|
18
17
|
assert_equal "boolean", resource.defs[:foobar].type
|
19
18
|
end
|
20
19
|
|
20
|
+
it "adds nested definitions" do
|
21
|
+
dsl.nested(:user) do |prop|
|
22
|
+
prop.text :email
|
23
|
+
prop.bool :admin
|
24
|
+
end
|
25
|
+
assert_equal 2, resource.defs[:user].size
|
26
|
+
assert_equal "string", resource.defs[:user][:email].type
|
27
|
+
assert_equal "boolean", resource.defs[:user][:admin].type
|
28
|
+
end
|
29
|
+
|
21
30
|
describe "#ref" do
|
22
|
-
let(:definition) { Sinatra::Schema::Definition.new }
|
23
|
-
before { options[:serialize] = true }
|
31
|
+
let(:definition) { Sinatra::Schema::Definition.new(id: :foobar) }
|
24
32
|
|
25
33
|
it "adds a reference to another definition in the resource" do
|
26
34
|
resource.defs[:foobar] = definition
|
data/spec/dsl/resources_spec.rb
CHANGED
@@ -21,5 +21,13 @@ describe Sinatra::Schema::DSL::Resources do
|
|
21
21
|
assert_equal 1, dsl.resource.properties.size
|
22
22
|
assert dsl.resource.properties.has_key?(:foo)
|
23
23
|
end
|
24
|
+
|
25
|
+
it "supports nested properties" do
|
26
|
+
dsl.property.nested :user do |prop|
|
27
|
+
prop.text :email
|
28
|
+
prop.bool :admin
|
29
|
+
end
|
30
|
+
assert_equal 2, dsl.resource.properties[:user].size
|
31
|
+
end
|
24
32
|
end
|
25
33
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
-
describe Sinatra::Schema::
|
3
|
+
describe Sinatra::Schema::ParamParsing do
|
4
4
|
before do
|
5
5
|
@rack_app = Sinatra.new do
|
6
|
-
helpers Sinatra::Schema::
|
6
|
+
helpers Sinatra::Schema::ParamParsing
|
7
7
|
post("/") do
|
8
8
|
MultiJson.encode(parse_params($properties))
|
9
9
|
end
|
@@ -31,6 +31,12 @@ describe Sinatra::Schema::ParamHandling do
|
|
31
31
|
post "/", MultiJson.encode(params)
|
32
32
|
assert_equal params, last_json
|
33
33
|
end
|
34
|
+
|
35
|
+
it "preserves nested params" do
|
36
|
+
params = { "foo" => { "bar" => 42 }}
|
37
|
+
post "/", MultiJson.encode(params)
|
38
|
+
assert_equal params, last_json
|
39
|
+
end
|
34
40
|
end
|
35
41
|
|
36
42
|
describe "form-encoded params" do
|
@@ -43,8 +49,16 @@ describe Sinatra::Schema::ParamHandling do
|
|
43
49
|
assert_equal({ "some_text" => "true", "some_bool" => true }, last_json)
|
44
50
|
end
|
45
51
|
|
52
|
+
it "handles nested params" do
|
53
|
+
$properties = {
|
54
|
+
foo: { bar: Sinatra::Schema::Definition.new(type: "boolean") }
|
55
|
+
}
|
56
|
+
post "/", foo: { bar: "true" }
|
57
|
+
assert_equal({ "foo" => { "bar" => true }}, last_json)
|
58
|
+
end
|
59
|
+
|
46
60
|
it "leaves params without the corresponding property untouched" do
|
47
|
-
$
|
61
|
+
$properties = {}
|
48
62
|
params = { "foo" => "bar" }
|
49
63
|
post "/", params
|
50
64
|
assert_equal params, last_json
|
@@ -32,4 +32,12 @@ describe Sinatra::Schema::ParamValidation do
|
|
32
32
|
post "/", MultiJson.encode(bool: "omg")
|
33
33
|
end
|
34
34
|
end
|
35
|
+
|
36
|
+
it "supports nested params" do
|
37
|
+
$properties = { foo: { bar: Sinatra::Schema::Definition.new(type: "boolean") }}
|
38
|
+
assert_raises(RuntimeError) do
|
39
|
+
post "/", MultiJson.encode(foo: { bar: "omg" })
|
40
|
+
puts last_response.body
|
41
|
+
end
|
42
|
+
end
|
35
43
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinatra-schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pedro Belo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-12-
|
11
|
+
date: 2014-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -139,19 +139,20 @@ files:
|
|
139
139
|
- lib/sinatra/schema/dsl/links.rb
|
140
140
|
- lib/sinatra/schema/dsl/resources.rb
|
141
141
|
- lib/sinatra/schema/link.rb
|
142
|
-
- lib/sinatra/schema/
|
142
|
+
- lib/sinatra/schema/param_parsing.rb
|
143
143
|
- lib/sinatra/schema/param_validation.rb
|
144
144
|
- lib/sinatra/schema/resource.rb
|
145
145
|
- lib/sinatra/schema/root.rb
|
146
146
|
- lib/sinatra/schema/tasks/schema.rake
|
147
147
|
- lib/sinatra/schema/utils.rb
|
148
148
|
- lib/sinatra/schema/version.rb
|
149
|
+
- spec/definition_spec.rb
|
149
150
|
- spec/dsl/definitions_spec.rb
|
150
151
|
- spec/dsl/links_spec.rb
|
151
152
|
- spec/dsl/resources_spec.rb
|
152
153
|
- spec/integration_spec.rb
|
153
154
|
- spec/json_schema_spec.rb
|
154
|
-
- spec/
|
155
|
+
- spec/param_parsing_spec.rb
|
155
156
|
- spec/param_validation_spec.rb
|
156
157
|
- spec/resource_spec.rb
|
157
158
|
- spec/spec_helper.rb
|