sinatra-schema 0.1.0 → 0.1.1
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
- data/README.md +5 -7
- data/lib/sinatra/schema.rb +0 -2
- data/lib/sinatra/schema/dsl/definitions.rb +18 -19
- data/lib/sinatra/schema/dsl/links.rb +1 -1
- data/lib/sinatra/schema/dsl/resources.rb +4 -0
- data/lib/sinatra/schema/error.rb +6 -0
- data/lib/sinatra/schema/link.rb +1 -1
- data/lib/sinatra/schema/resource.rb +32 -7
- data/lib/sinatra/schema/version.rb +1 -1
- data/spec/dsl/definitions_spec.rb +26 -14
- data/spec/dsl/links_spec.rb +2 -2
- data/spec/dsl/resources_spec.rb +5 -0
- data/spec/integration_spec.rb +5 -5
- data/spec/reference_spec.rb +2 -2
- data/spec/resource_spec.rb +60 -2
- data/spec/support/test_app.rb +1 -1
- metadata +1 -22
- data/lib/sinatra/schema/utils.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a34c267cb9a3dafd8ddf39079c29d49097587b6a
|
4
|
+
data.tar.gz: 0b004738e02c95aa6162f2a9a72cf308f4245c79
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3321031aea9981da9d327b73d6cd474e099a6bf513158d5864975af2acf52b6f6a7b40fd1fe858604b20fbd87bd31cccd32122544a1a51124f39c2c3d8b0316f
|
7
|
+
data.tar.gz: 75b6bf7740d69bc5eaebb2a98bd2ced7e8e8fb491caba601ffa73e6fc0774f12a1fdebe692bad7f5996c0749c1c5444af772c72f8dccdbb64792d08525be7a8d
|
data/README.md
CHANGED
@@ -3,12 +3,12 @@
|
|
3
3
|
[](https://rubygems.org/gems/sinatra-schema)
|
4
4
|
[](https://travis-ci.org/pedro/sinatra-schema)
|
5
5
|
|
6
|
-
Define a schema for your Sinatra application to get requests and responses validated. Dump it
|
6
|
+
Define a schema for your Sinatra application to get requests and responses validated. Dump it as a JSON Schema to aid client generation and more!
|
7
7
|
|
8
8
|
|
9
9
|
## Usage
|
10
10
|
|
11
|
-
Register `Sinatra::Schema` to define
|
11
|
+
Register `Sinatra::Schema` to define resources, like:
|
12
12
|
|
13
13
|
```ruby
|
14
14
|
class MyApi < Sinatra::Base
|
@@ -19,7 +19,7 @@ class MyApi < Sinatra::Base
|
|
19
19
|
|
20
20
|
res.get do |link|
|
21
21
|
link.action do
|
22
|
-
#
|
22
|
+
# per definition above we need to serialize "email"
|
23
23
|
MultiJson.encode(email: current_user.email)
|
24
24
|
end
|
25
25
|
end
|
@@ -33,8 +33,6 @@ Links can have properties too:
|
|
33
33
|
|
34
34
|
```ruby
|
35
35
|
resource("/account") do |res|
|
36
|
-
res.property.text :email
|
37
|
-
|
38
36
|
res.post do |link|
|
39
37
|
link.property.ref :email # reuse the property defined above
|
40
38
|
link.property.text :role, optional: true
|
@@ -42,7 +40,7 @@ resource("/account") do |res|
|
|
42
40
|
|
43
41
|
link.action do |data|
|
44
42
|
user = User.new(email: data[:email])
|
45
|
-
if data[:admin] #
|
43
|
+
if data[:admin] # this is a boolean, params are casted accordingly!
|
46
44
|
# ...
|
47
45
|
end
|
48
46
|
end
|
@@ -64,7 +62,7 @@ resource("/albums") do |res|
|
|
64
62
|
end
|
65
63
|
```
|
66
64
|
|
67
|
-
### Nested
|
65
|
+
### Nested properties
|
68
66
|
|
69
67
|
These are also casted and validated as you'd expect:
|
70
68
|
|
data/lib/sinatra/schema.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require "active_support/inflector"
|
2
1
|
require "sinatra/base"
|
3
2
|
require "singleton"
|
4
3
|
require "multi_json"
|
@@ -13,7 +12,6 @@ require "sinatra/schema/param_validation"
|
|
13
12
|
require "sinatra/schema/reference"
|
14
13
|
require "sinatra/schema/resource"
|
15
14
|
require "sinatra/schema/root"
|
16
|
-
require "sinatra/schema/utils"
|
17
15
|
require "sinatra/schema/dsl/definitions"
|
18
16
|
require "sinatra/schema/dsl/links"
|
19
17
|
require "sinatra/schema/dsl/resources"
|
@@ -10,21 +10,11 @@ module Sinatra
|
|
10
10
|
@targets = targets
|
11
11
|
end
|
12
12
|
|
13
|
-
def text(id, options={})
|
14
|
-
options.merge!(id: id, type: "string")
|
15
|
-
add Definition.new(options)
|
16
|
-
end
|
17
|
-
|
18
13
|
def bool(id, options={})
|
19
14
|
options.merge!(id: id, type: "boolean")
|
20
15
|
add Definition.new(options)
|
21
16
|
end
|
22
17
|
|
23
|
-
def uuid(id, options={})
|
24
|
-
options.merge!(id: id, type: "uuid")
|
25
|
-
add Definition.new(options)
|
26
|
-
end
|
27
|
-
|
28
18
|
def datetime(id, options={})
|
29
19
|
options.merge!(id: id, type: "datetime")
|
30
20
|
add Definition.new(options)
|
@@ -35,24 +25,33 @@ module Sinatra
|
|
35
25
|
add Definition.new(options)
|
36
26
|
end
|
37
27
|
|
28
|
+
# support references to other properties that are lazily evaluated
|
29
|
+
def ref(id, ref_to=nil)
|
30
|
+
add Reference.new(resource, id, ref_to)
|
31
|
+
end
|
32
|
+
|
33
|
+
def text(id, options={})
|
34
|
+
options.merge!(id: id, type: "string")
|
35
|
+
add Definition.new(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def uuid(id, options={})
|
39
|
+
options.merge!(id: id, type: "uuid")
|
40
|
+
add Definition.new(options)
|
41
|
+
end
|
42
|
+
|
38
43
|
# support nested properties. eg: property[:foo].text :bar
|
39
44
|
def [](id)
|
40
|
-
# make sure all targets have a sub-hash for this nested def
|
45
|
+
# make sure all targets have a sub-hash for this nested def
|
41
46
|
targets.each { |h| h[id] ||= {} }
|
42
47
|
|
43
|
-
# return a new DSL with updated targets so it can be chained
|
48
|
+
# return a new DSL with updated targets so it can be chained
|
44
49
|
Definitions.new(resource, targets.map { |h| h[id] })
|
45
50
|
end
|
46
51
|
|
47
|
-
def ref(id, ref_to=nil)
|
48
|
-
add Reference.new(resource, id, ref_to), true
|
49
|
-
end
|
50
|
-
|
51
|
-
# TODO support other types
|
52
|
-
|
53
52
|
protected
|
54
53
|
|
55
|
-
def add(definition
|
54
|
+
def add(definition)
|
56
55
|
targets.each do |target|
|
57
56
|
target[definition.id] ||= definition
|
58
57
|
end
|
data/lib/sinatra/schema/error.rb
CHANGED
data/lib/sinatra/schema/link.rb
CHANGED
@@ -21,7 +21,7 @@ module Sinatra
|
|
21
21
|
schema_params = parse_params(link.properties)
|
22
22
|
validate_params!(schema_params, link.properties)
|
23
23
|
res = instance_exec(schema_params, &link.action_block)
|
24
|
-
link.resource.validate_response!(res)
|
24
|
+
link.resource.validate_response!(link.rel, res)
|
25
25
|
res
|
26
26
|
end
|
27
27
|
end
|
@@ -11,24 +11,49 @@ module Sinatra
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def id
|
14
|
-
@id ||=
|
14
|
+
@id ||= path.split("/").last.to_sym
|
15
15
|
end
|
16
16
|
|
17
17
|
def title
|
18
|
-
@title ||=
|
18
|
+
@title ||= id.to_s.capitalize
|
19
19
|
end
|
20
20
|
|
21
|
-
def validate_response!(raw)
|
21
|
+
def validate_response!(rel, raw)
|
22
22
|
# only validate responses in tests
|
23
23
|
return unless ENV["RACK_ENV"] == "test"
|
24
24
|
|
25
25
|
res = MultiJson.decode(raw)
|
26
|
-
|
27
|
-
|
26
|
+
|
27
|
+
if rel == :instances
|
28
|
+
unless res.is_a?(Array)
|
29
|
+
raise BadResponse.new("Response should return an array")
|
30
|
+
end
|
31
|
+
if sample = res.first
|
32
|
+
validate_properties!(sample)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
unless res.is_a?(Hash)
|
36
|
+
raise BadResponse.new("Response should return a hash")
|
37
|
+
end
|
38
|
+
validate_properties!(res)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def validate_properties!(received)
|
43
|
+
missing = properties.keys.map(&:to_s).sort - received.keys.map(&:to_s).sort
|
44
|
+
unless missing.empty?
|
45
|
+
raise BadResponse.new("Missing properties: #{missing}")
|
46
|
+
end
|
47
|
+
|
48
|
+
extra = received.keys.map(&:to_s).sort - properties.keys.map(&:to_s).sort
|
49
|
+
unless extra.empty?
|
50
|
+
raise BadResponse.new("Unexpected properties: #{extra}")
|
28
51
|
end
|
29
52
|
|
30
|
-
|
31
|
-
|
53
|
+
properties.each do |id, definition|
|
54
|
+
unless definition.valid?(received[id.to_s])
|
55
|
+
raise BadResponse.new("Bad response property: #{id}")
|
56
|
+
end
|
32
57
|
end
|
33
58
|
end
|
34
59
|
end
|
@@ -5,30 +5,22 @@ describe Sinatra::Schema::DSL::Definitions do
|
|
5
5
|
let(:dsl) { described_class.new(resource, [resource.defs, resource.properties]) }
|
6
6
|
let(:root) { Sinatra::Schema::Root.instance }
|
7
7
|
|
8
|
-
it "adds
|
9
|
-
dsl.text(:foobar)
|
10
|
-
assert_equal 1, resource.defs.size
|
11
|
-
assert_equal "string", resource.defs[:foobar].type
|
12
|
-
end
|
13
|
-
|
14
|
-
it "adds a boolean definition to the resource" do
|
8
|
+
it "adds boolean definition to the resource" do
|
15
9
|
dsl.bool(:foobar)
|
16
10
|
assert_equal 1, resource.defs.size
|
17
11
|
assert_equal "boolean", resource.defs[:foobar].type
|
18
12
|
end
|
19
13
|
|
20
|
-
it "adds
|
14
|
+
it "adds datetime definition to the resource" do
|
21
15
|
dsl.datetime(:foobar)
|
22
16
|
assert_equal 1, resource.defs.size
|
23
17
|
assert_equal "datetime", resource.defs[:foobar].type
|
24
18
|
end
|
25
19
|
|
26
|
-
it "adds
|
27
|
-
dsl
|
28
|
-
|
29
|
-
assert_equal
|
30
|
-
assert_equal "string", resource.defs[:user][:email].type
|
31
|
-
assert_equal "boolean", resource.defs[:user][:admin].type
|
20
|
+
it "adds email definition to the resource" do
|
21
|
+
dsl.email(:foobar)
|
22
|
+
assert_equal 1, resource.defs.size
|
23
|
+
assert_equal "email", resource.defs[:foobar].type
|
32
24
|
end
|
33
25
|
|
34
26
|
it "adds references" do
|
@@ -37,6 +29,26 @@ describe Sinatra::Schema::DSL::Definitions do
|
|
37
29
|
assert_instance_of Sinatra::Schema::Reference, resource.defs[:another_property]
|
38
30
|
end
|
39
31
|
|
32
|
+
it "adds text definition to the resource" do
|
33
|
+
dsl.text(:foobar)
|
34
|
+
assert_equal 1, resource.defs.size
|
35
|
+
assert_equal "string", resource.defs[:foobar].type
|
36
|
+
end
|
37
|
+
|
38
|
+
it "adds uuid definition to the resource" do
|
39
|
+
dsl.uuid(:foobar)
|
40
|
+
assert_equal 1, resource.defs.size
|
41
|
+
assert_equal "uuid", resource.defs[:foobar].type
|
42
|
+
end
|
43
|
+
|
44
|
+
it "adds nested definitions" do
|
45
|
+
dsl[:user].text :email
|
46
|
+
dsl[:user].bool :admin
|
47
|
+
assert_equal 2, resource.defs[:user].size
|
48
|
+
assert_equal "string", resource.defs[:user][:email].type
|
49
|
+
assert_equal "boolean", resource.defs[:user][:admin].type
|
50
|
+
end
|
51
|
+
|
40
52
|
it "sets other options" do
|
41
53
|
dsl.text(:foobar, optional: true)
|
42
54
|
assert_equal true, resource.defs[:foobar].optional
|
data/spec/dsl/links_spec.rb
CHANGED
@@ -10,9 +10,9 @@ describe Sinatra::Schema::DSL::Links do
|
|
10
10
|
assert_equal "foo", dsl.link.title
|
11
11
|
end
|
12
12
|
|
13
|
-
it "sets the link rel" do
|
13
|
+
it "sets the link rel, cast to symbol" do
|
14
14
|
dsl.rel("foo")
|
15
|
-
assert_equal
|
15
|
+
assert_equal :foo, dsl.link.rel
|
16
16
|
end
|
17
17
|
|
18
18
|
it "sets the link description" do
|
data/spec/dsl/resources_spec.rb
CHANGED
@@ -9,6 +9,11 @@ describe Sinatra::Schema::DSL::Resources do
|
|
9
9
|
assert_equal "This is a foobar", dsl.resource.description
|
10
10
|
end
|
11
11
|
|
12
|
+
it "sets the id casting to symbol" do
|
13
|
+
dsl.id("account")
|
14
|
+
assert_equal :account, dsl.resource.id
|
15
|
+
end
|
16
|
+
|
12
17
|
describe "#property" do
|
13
18
|
it "adds new definitions to the resource" do
|
14
19
|
dsl.property.text :foo
|
data/spec/integration_spec.rb
CHANGED
@@ -16,9 +16,9 @@ describe Sinatra::Schema do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
it "support resource posts" do
|
19
|
-
post "/accounts", email: "
|
19
|
+
post "/accounts", email: "foo@baz.com"
|
20
20
|
assert_equal 200, last_response.status
|
21
|
-
assert_equal({ "email" => "
|
21
|
+
assert_equal({ "email" => "foo@baz.com" },
|
22
22
|
MultiJson.decode(last_response.body))
|
23
23
|
end
|
24
24
|
end
|
@@ -45,10 +45,10 @@ describe Sinatra::Schema do
|
|
45
45
|
|
46
46
|
it "generates definitions" do
|
47
47
|
assert_equal Hash, @schema["definitions"].class
|
48
|
-
assert_equal Hash, @schema["definitions"]["
|
49
|
-
assert_equal "
|
48
|
+
assert_equal Hash, @schema["definitions"]["accounts"].class
|
49
|
+
assert_equal "Accounts", @schema["definitions"]["accounts"]["title"]
|
50
50
|
assert_equal "An account represents an individual signed up to use the service",
|
51
|
-
@schema["definitions"]["
|
51
|
+
@schema["definitions"]["accounts"]["description"]
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
data/spec/reference_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Sinatra::Schema::Reference do
|
4
|
-
let(:resource) { Sinatra::Schema::Root.instance.resources[:
|
4
|
+
let(:resource) { Sinatra::Schema::Root.instance.resources[:accounts] }
|
5
5
|
let(:ref) { described_class.new(resource, :foo, :email) }
|
6
6
|
let(:definition) { resource.defs[:email] }
|
7
7
|
|
@@ -13,7 +13,7 @@ describe Sinatra::Schema::Reference do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
it "finds other properties elsewhere in the schema" do
|
16
|
-
ref.ref_spec = "
|
16
|
+
ref.ref_spec = "accounts/email"
|
17
17
|
assert_equal definition, ref.resolve!
|
18
18
|
end
|
19
19
|
|
data/spec/resource_spec.rb
CHANGED
@@ -5,13 +5,71 @@ describe Sinatra::Schema::Resource do
|
|
5
5
|
|
6
6
|
describe "#id" do
|
7
7
|
it "is inferred from the path" do
|
8
|
-
assert_equal :
|
8
|
+
assert_equal :artists, resource.id
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
12
|
describe "#title" do
|
13
13
|
it "is inferred from the path" do
|
14
|
-
assert_equal "
|
14
|
+
assert_equal "Artists", resource.title
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#validate_response!" do
|
19
|
+
before do
|
20
|
+
resource.properties[:email] = Sinatra::Schema::Definition.new(type: "email")
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "instances rel" do
|
24
|
+
it "ensures the response is an array" do
|
25
|
+
assert_raises Sinatra::Schema::BadResponse do
|
26
|
+
resource.validate_response!(:instances, "{}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "allows empty responses" do
|
31
|
+
resource.validate_response!(:instances, "[]")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "validates the first element when available" do
|
35
|
+
res = MultiJson.encode([ { email: "foo@bar.com" } ])
|
36
|
+
resource.validate_response!(:instances, res)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "raises when response has a missing property" do
|
40
|
+
res = MultiJson.encode([ {} ])
|
41
|
+
assert_raises Sinatra::Schema::BadResponse do
|
42
|
+
resource.validate_response!(:instances, res)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "raises when response has an extra property" do
|
47
|
+
res = MultiJson.encode([ { email: "foo@bar.com", other: true } ])
|
48
|
+
assert_raises Sinatra::Schema::BadResponse do
|
49
|
+
resource.validate_response!(:instances, res)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "self/other rel" do
|
55
|
+
it "ensures the response is a hash" do
|
56
|
+
assert_raises Sinatra::Schema::BadResponse do
|
57
|
+
resource.validate_response!(:self, "[]")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "raises when response has a missing property" do
|
62
|
+
assert_raises Sinatra::Schema::BadResponse do
|
63
|
+
resource.validate_response!(:self, "{}")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "raises when response has an extra property" do
|
68
|
+
res = MultiJson.encode(email: "foo@bar.com", other: true)
|
69
|
+
assert_raises Sinatra::Schema::BadResponse do
|
70
|
+
resource.validate_response!(:self, res)
|
71
|
+
end
|
72
|
+
end
|
15
73
|
end
|
16
74
|
end
|
17
75
|
end
|
data/spec/support/test_app.rb
CHANGED
@@ -8,7 +8,7 @@ class TestApp < Sinatra::Base
|
|
8
8
|
resource("/accounts") do |res|
|
9
9
|
res.description "An account represents an individual signed up to use the service"
|
10
10
|
|
11
|
-
res.property.
|
11
|
+
res.property.email :email,
|
12
12
|
description: "unique email address of account",
|
13
13
|
example: "username@example.com",
|
14
14
|
format: "email"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinatra-schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pedro Belo
|
@@ -10,26 +10,6 @@ bindir: bin
|
|
10
10
|
cert_chain: []
|
11
11
|
date: 2014-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: activesupport
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '4.0'
|
20
|
-
- - ">="
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: '4.0'
|
23
|
-
type: :runtime
|
24
|
-
prerelease: false
|
25
|
-
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
requirements:
|
27
|
-
- - "~>"
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '4.0'
|
30
|
-
- - ">="
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '4.0'
|
33
13
|
- !ruby/object:Gem::Dependency
|
34
14
|
name: multi_json
|
35
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -147,7 +127,6 @@ files:
|
|
147
127
|
- lib/sinatra/schema/resource.rb
|
148
128
|
- lib/sinatra/schema/root.rb
|
149
129
|
- lib/sinatra/schema/tasks/schema.rake
|
150
|
-
- lib/sinatra/schema/utils.rb
|
151
130
|
- lib/sinatra/schema/version.rb
|
152
131
|
- spec/definition_spec.rb
|
153
132
|
- spec/dsl/definitions_spec.rb
|
data/lib/sinatra/schema/utils.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
module Sinatra
|
2
|
-
module Schema
|
3
|
-
class Utils
|
4
|
-
def self.validate_keys!(properties, received)
|
5
|
-
missing = properties.keys.map(&:to_s).sort - received.keys.map(&:to_s).sort
|
6
|
-
unless missing.empty?
|
7
|
-
raise "Missing properties: #{missing}"
|
8
|
-
end
|
9
|
-
|
10
|
-
extra = received.keys.map(&:to_s).sort - properties.keys.map(&:to_s).sort
|
11
|
-
unless extra.empty?
|
12
|
-
raise "Unexpected properties: #{extra}"
|
13
|
-
end
|
14
|
-
|
15
|
-
properties.each do |id, definition|
|
16
|
-
unless definition.valid?(received[id.to_s])
|
17
|
-
raise "Bad response property: #{id}"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|