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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7ed53578697a31d2928747c5e315a6bf74e3a5b1
4
- data.tar.gz: 1c8fabb80b8ee50d14186838d1a29f53d4bc039b
3
+ metadata.gz: a34c267cb9a3dafd8ddf39079c29d49097587b6a
4
+ data.tar.gz: 0b004738e02c95aa6162f2a9a72cf308f4245c79
5
5
  SHA512:
6
- metadata.gz: e3a1e7c20a2737d577391748a479bddb1761183777c4797cf6dfc6009076c4caec7dc408aa174eaab7f382d34abc839f49a01728d99e2753e88e5a42c2c668ec
7
- data.tar.gz: 7e12db3a44fe1ee3574f5feb3d9fa47c676820d24587a9f85e5d391b7678574288460bd2cef166d6c26055701f147d43fceef2acc5a4ae491f12594af1cf11dd
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 schema as a JSON Schema to aid client generation and more!
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 your resource like:
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
- # note per definition above we need to serialize "email"
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] # schema params are casted accordingly!
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 params
65
+ ### Nested properties
68
66
 
69
67
  These are also casted and validated as you'd expect:
70
68
 
@@ -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, reference=false)
54
+ def add(definition)
56
55
  targets.each do |target|
57
56
  target[definition.id] ||= definition
58
57
  end
@@ -16,7 +16,7 @@ module Sinatra
16
16
  end
17
17
 
18
18
  def rel(rel)
19
- link.rel = rel
19
+ link.rel = rel.to_sym
20
20
  end
21
21
 
22
22
  def description(description)
@@ -13,6 +13,10 @@ module Sinatra
13
13
  @resource.description = description
14
14
  end
15
15
 
16
+ def id(id)
17
+ @resource.id = id.to_sym
18
+ end
19
+
16
20
  def property
17
21
  DSL::Definitions.new(resource, [resource.defs, resource.properties])
18
22
  end
@@ -20,5 +20,11 @@ module Sinatra
20
20
  super(msg)
21
21
  end
22
22
  end
23
+
24
+ class BadResponse < Error
25
+ def initialize(msg)
26
+ super(msg)
27
+ end
28
+ end
23
29
  end
24
30
  end
@@ -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 ||= ActiveSupport::Inflector.singularize(path.split("/").last).to_sym
14
+ @id ||= path.split("/").last.to_sym
15
15
  end
16
16
 
17
17
  def title
18
- @title ||= ActiveSupport::Inflector.singularize(path.split("/").last).capitalize
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
- unless res.is_a?(Hash)
27
- raise "Response should return a hash"
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
- unless properties.empty?
31
- Utils.validate_keys!(properties, res)
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
@@ -1,5 +1,5 @@
1
1
  module Sinatra
2
2
  module Schema
3
- VERSION = "0.1.0"
3
+ VERSION = "0.1.1"
4
4
  end
5
5
  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 a string definition to the resource" do
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 a datetime definition to the resource" do
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 nested definitions" do
27
- dsl[:user].text :email
28
- dsl[:user].bool :admin
29
- assert_equal 2, resource.defs[:user].size
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
@@ -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 "foo", dsl.link.rel
15
+ assert_equal :foo, dsl.link.rel
16
16
  end
17
17
 
18
18
  it "sets the link description" do
@@ -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
@@ -16,9 +16,9 @@ describe Sinatra::Schema do
16
16
  end
17
17
 
18
18
  it "support resource posts" do
19
- post "/accounts", email: "omg"
19
+ post "/accounts", email: "foo@baz.com"
20
20
  assert_equal 200, last_response.status
21
- assert_equal({ "email" => "omg" },
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"]["account"].class
49
- assert_equal "Account", @schema["definitions"]["account"]["title"]
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"]["account"]["description"]
51
+ @schema["definitions"]["accounts"]["description"]
52
52
  end
53
53
  end
54
54
 
@@ -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[:account] }
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 = "account/email"
16
+ ref.ref_spec = "accounts/email"
17
17
  assert_equal definition, ref.resolve!
18
18
  end
19
19
 
@@ -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 :artist, resource.id
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 "Artist", resource.title
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
@@ -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.text :email,
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.0
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
@@ -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