sinatra-schema 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,7 @@
1
+ module Sinatra
2
+ module Schema
3
+ class Definition
4
+ attr_accessor :description, :example, :format, :type
5
+ end
6
+ end
7
+ end
@@ -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
@@ -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: