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 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: