sinatra-schema 0.1.0 → 0.1.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 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