sinatra-schema 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem version](http://img.shields.io/gem/v/sinatra-schema.svg)](https://rubygems.org/gems/sinatra-schema)
|
4
4
|
[![Build Status](https://travis-ci.org/pedro/sinatra-schema.svg?branch=master)](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
|