sinatra-schema 0.0.2 → 0.0.3
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 +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
|