sinatra-schema 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/README.md +33 -0
- data/lib/sinatra/schema/definition.rb +7 -0
- data/lib/sinatra/schema/link.rb +47 -0
- data/lib/sinatra/schema/resource.rb +44 -0
- data/lib/sinatra/schema/utils.rb +17 -0
- data/lib/sinatra/schema.rb +33 -0
- data/spec/integration_spec.rb +41 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/test_app.rb +41 -0
- metadata +135 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MjFmZjFhMTc3NTMzOTFiNjcwZDc0NGVmMjc0MzYyNmE4MjU4ZDU5MA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NjljZDE3YjZkMGJhYzA2YmE2MjE2NjFjMGQzMDIyYmU3ODRkYzY2Nw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NmRjN2E4YjRjNzBiZGNhZTBhNTVlM2I1NWUxZGRlYzFjODBkOTQwMzQxMjFi
|
10
|
+
ZDQ0YTViMTQ0MzAzZDc0OTk3M2E2NzgzNzRlOGI0MWY4NTE4YTg1M2E5ZDNi
|
11
|
+
YzQzMWM1OGI3NDE0NWZkODkzMjYxN2EyM2M2NTUwOTM1MDkwNTQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YWM3ZTI2YmE3OTk3NTdlOTRmNTQwNzM4ZGQzOTE3ODNlY2VlNDJmMmQyZDU3
|
14
|
+
MTQ2MGRjNDg1MzA0OWUzODJiMzAwZWNlOGYxMzU3NWU2YmE2NjZlNzgxZmUz
|
15
|
+
YjI0M2ViYTZkY2Q3MmM1NjFkOWZlYTBjYTQxMjI5NTg2ZjY5OTA=
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Sinatra Schema
|
2
|
+
|
3
|
+
Define a schema for your Sinatra application to get requests and responses validated. Dump it schema as a JSON Schema to aid client generation and more!
|
4
|
+
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
Register `Sinatra::Schema` and define resources like:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
class MyApi < Sinatra::Base
|
12
|
+
register Sinatra::Schema
|
13
|
+
|
14
|
+
resource("/accounts") do
|
15
|
+
title "Account"
|
16
|
+
description "The account of a user signed up to our service"
|
17
|
+
serializer AccountSerializer
|
18
|
+
|
19
|
+
res.link(:post) do
|
20
|
+
title "Create"
|
21
|
+
description "Create a new account"
|
22
|
+
action do
|
23
|
+
Account.create(email: params[:email])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
|
31
|
+
## See also
|
32
|
+
|
33
|
+
- [sinatra-param](https://github.com/mattt/sinatra-param): nice take on validating request parameters.
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Schema
|
3
|
+
class Link
|
4
|
+
attr_accessor :resource, :title, :description, :href, :method, :properties, :rel, :action
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@resource = options[:resource]
|
8
|
+
@method = options[:method]
|
9
|
+
@href = options[:href]
|
10
|
+
end
|
11
|
+
|
12
|
+
def action(&blk)
|
13
|
+
@action = blk
|
14
|
+
end
|
15
|
+
|
16
|
+
def action_block
|
17
|
+
@action
|
18
|
+
end
|
19
|
+
|
20
|
+
def register(app)
|
21
|
+
link = self
|
22
|
+
app.send(method.downcase, href) do
|
23
|
+
begin
|
24
|
+
link.validate_params!(params)
|
25
|
+
res = link.action_block.call(params)
|
26
|
+
link.resource.validate_response!(res)
|
27
|
+
MultiJson.encode(res)
|
28
|
+
rescue RuntimeError => e
|
29
|
+
halt(400)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def validate_params!(params)
|
35
|
+
unless properties
|
36
|
+
if params.empty?
|
37
|
+
return
|
38
|
+
else
|
39
|
+
raise "Did not expect params"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Utils.validate_keys!(properties, params)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Schema
|
3
|
+
class Resource
|
4
|
+
attr_accessor :id, :path, :title, :description, :properties
|
5
|
+
|
6
|
+
def initialize(app, path)
|
7
|
+
@app = app
|
8
|
+
@path = path.chomp("/")
|
9
|
+
@links = []
|
10
|
+
@defs = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def define(id)
|
14
|
+
@defs[id] = Definition.new
|
15
|
+
yield @defs[id]
|
16
|
+
end
|
17
|
+
|
18
|
+
def link(method, href="/", &blk)
|
19
|
+
href = "#{path}/#{href.chomp("/")}".chomp("/")
|
20
|
+
link = Link.new(resource: self, method: method, href: href)
|
21
|
+
yield(link)
|
22
|
+
link.register(@app)
|
23
|
+
@links << link
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate_response!(res)
|
27
|
+
unless res.is_a?(Hash)
|
28
|
+
raise "Response should return a hash"
|
29
|
+
end
|
30
|
+
|
31
|
+
if properties
|
32
|
+
Utils.validate_keys!(properties, res)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_schema
|
37
|
+
{
|
38
|
+
title: title,
|
39
|
+
description: description
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Schema
|
3
|
+
class Utils
|
4
|
+
def self.validate_keys!(expected, received)
|
5
|
+
missing = expected.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 - expected.map(&:to_s).sort
|
11
|
+
unless extra.empty?
|
12
|
+
raise "Unexpected properties: #{extra}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "sinatra/schema/definition"
|
2
|
+
require "sinatra/schema/link"
|
3
|
+
require "sinatra/schema/resource"
|
4
|
+
require "sinatra/schema/utils"
|
5
|
+
|
6
|
+
module Sinatra
|
7
|
+
module Schema
|
8
|
+
def self.registered(app)
|
9
|
+
app.get "/schema" do
|
10
|
+
MultiJson.encode(
|
11
|
+
"$schema" => "http://json-schema.org/draft-04/hyper-schema",
|
12
|
+
"definitions" => app.resources.inject({}) { |result, (id, resource)|
|
13
|
+
result[id] = resource.to_schema
|
14
|
+
result
|
15
|
+
}
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def resources
|
21
|
+
@resources ||= {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def resource(path)
|
25
|
+
res = Resource.new(self, path)
|
26
|
+
yield(res)
|
27
|
+
resources[res.id] = res
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
register Schema
|
33
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Sinatra::Schema do
|
4
|
+
it "still works as a regular sinatra app" do
|
5
|
+
get "/regular"
|
6
|
+
assert_equal 200, last_response.status
|
7
|
+
assert_equal "hi", last_response.body
|
8
|
+
end
|
9
|
+
|
10
|
+
it "support resource links" do
|
11
|
+
get "/accounts"
|
12
|
+
assert_equal 200, last_response.status
|
13
|
+
assert_equal({ "email" => "foo@bar.com" },
|
14
|
+
MultiJson.decode(last_response.body))
|
15
|
+
end
|
16
|
+
|
17
|
+
it "support resource links" do
|
18
|
+
post "/accounts", email: "omg"
|
19
|
+
assert_equal 200, last_response.status
|
20
|
+
assert_equal({ "email" => "omg" },
|
21
|
+
MultiJson.decode(last_response.body))
|
22
|
+
end
|
23
|
+
|
24
|
+
it "validates input" do
|
25
|
+
post "/accounts", foo: "bar"
|
26
|
+
assert_equal 400, last_response.status
|
27
|
+
end
|
28
|
+
|
29
|
+
it "exposes the json schema" do
|
30
|
+
get "/schema"
|
31
|
+
assert_equal 200, last_response.status
|
32
|
+
schema = MultiJson.decode(last_response.body)
|
33
|
+
assert_equal "http://json-schema.org/draft-04/hyper-schema",
|
34
|
+
schema["$schema"]
|
35
|
+
assert_equal Hash, schema["definitions"].class
|
36
|
+
assert_equal Hash, schema["definitions"]["account"].class
|
37
|
+
assert_equal "Account", schema["definitions"]["account"]["title"]
|
38
|
+
assert_equal "An account represents an individual signed up to use the service",
|
39
|
+
schema["definitions"]["account"]["description"]
|
40
|
+
end
|
41
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
ENV["RACK_ENV"] = "test"
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "bundler"
|
5
|
+
|
6
|
+
Bundler.require(:default, :test)
|
7
|
+
|
8
|
+
require "sinatra"
|
9
|
+
require "sinatra/schema"
|
10
|
+
|
11
|
+
Dir["./spec/support/*"].each { |f| require(f) }
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.include Rack::Test::Methods
|
15
|
+
config.expect_with :minitest
|
16
|
+
|
17
|
+
def app
|
18
|
+
TestApp
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class TestApp < Sinatra::Base
|
2
|
+
register Sinatra::Schema
|
3
|
+
|
4
|
+
get "/regular" do
|
5
|
+
"hi"
|
6
|
+
end
|
7
|
+
|
8
|
+
resource("/accounts") do |res|
|
9
|
+
res.id = :account
|
10
|
+
res.title = "Account"
|
11
|
+
res.description = "An account represents an individual signed up to use the service"
|
12
|
+
|
13
|
+
res.define(:email) do |d|
|
14
|
+
d.description = "unique email address of account"
|
15
|
+
d.example = "username@example.com"
|
16
|
+
d.format = "email"
|
17
|
+
d.type = :string
|
18
|
+
end
|
19
|
+
|
20
|
+
res.properties = [:email]
|
21
|
+
|
22
|
+
res.link(:get) do |link|
|
23
|
+
link.title = "Info"
|
24
|
+
link.rel = "self"
|
25
|
+
link.description = "Info for account"
|
26
|
+
link.action do
|
27
|
+
{ email: "foo@bar.com" }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
res.link(:post) do |link|
|
32
|
+
link.title = "Create"
|
33
|
+
link.rel = "create"
|
34
|
+
link.description = "Create a new account"
|
35
|
+
link.properties = [:email]
|
36
|
+
link.action do |params|
|
37
|
+
{ email: params[:email] }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sinatra-schema
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pedro Belo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: multi_json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.9'
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.9.3
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.9'
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.9.3
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: sinatra
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ~>
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.4'
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.4.4
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ~>
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.4'
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.4.4
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: rack-test
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ~>
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0.6'
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.6.2
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.6'
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 0.6.2
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: rspec
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ~>
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '3.1'
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.1.0
|
83
|
+
type: :development
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.1'
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 3.1.0
|
93
|
+
description: Define a schema to validate requests and responses, expose it as JSON
|
94
|
+
Schema
|
95
|
+
email:
|
96
|
+
- pedrobelo@gmail.com
|
97
|
+
executables: []
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- README.md
|
102
|
+
- lib/sinatra/schema.rb
|
103
|
+
- lib/sinatra/schema/definition.rb
|
104
|
+
- lib/sinatra/schema/link.rb
|
105
|
+
- lib/sinatra/schema/resource.rb
|
106
|
+
- lib/sinatra/schema/utils.rb
|
107
|
+
- spec/integration_spec.rb
|
108
|
+
- spec/spec_helper.rb
|
109
|
+
- spec/support/test_app.rb
|
110
|
+
homepage: https://github.com/pedro/sinatra-schema
|
111
|
+
licenses:
|
112
|
+
- MIT
|
113
|
+
metadata: {}
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ! '>='
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ! '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
requirements: []
|
129
|
+
rubyforge_project:
|
130
|
+
rubygems_version: 2.2.2
|
131
|
+
signing_key:
|
132
|
+
specification_version: 4
|
133
|
+
summary: Sinatra extension to support schemas
|
134
|
+
test_files: []
|
135
|
+
has_rdoc:
|