sinatra-schema 0.0.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 +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:
|