sinatra-schema 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MDY3YmZhNWJiOTc1YmI1NWU5OTk1MmVkZDg2ZjkyZTA3ZmZiMzZiOA==
4
+ MGZiZDNiMDcyM2EzNzM1MjdmZWUxOTkzZGNjY2QxNmVmN2RmODRiMg==
5
5
  data.tar.gz: !binary |-
6
- ODVkYjE3NDFiODdhMjdiOGNkYmRhYTU4NjIzMTE3Nzk4OGJjYThhZQ==
6
+ ODY0YjViYzM3YmJhYjIzODc3Y2U3ZmMwMzJmM2Q5YzRiN2M4ZjNmYw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZDhmNDgxZDhlY2FkODI3MDczMmJhMTE4OWFjYWZhMzM2MWQ2NTgzMWY2N2Zm
10
- MWEyYzU5ZWI2Y2Q5ZmQ5MGFkMzMwZWEyMmNhNDcyM2RiZjU4MTA4M2VjYzgy
11
- MDk2N2MwM2ZiNDA5MDRiYjBlMzdjODVjODM5ZWQ2MGNjZmE0ODI=
9
+ MDQwY2YwMzU3MTg3ZDkzMGNiMDY2MTFlNjM0MWFkNjYzNzdhZTQzMWVlYjg1
10
+ NzdhNzk2NjZlM2YyNjQ1MzQxZjA5ZTNiNWQ0NGI5MjYxYjI3MTUzYjYxODMx
11
+ NDJhYzMyNDI2NDZjZGJlNjVhYzQxMTQ0OTdjOTRkN2Q1ZmQ3NmY=
12
12
  data.tar.gz: !binary |-
13
- MTZjNDNjODZhYmE1OTEyODU4MmY3MjBmZTAyZWRlYTY3OWUzYjVhMDEyM2Iw
14
- YWY4OTE5MWU2ZGIxN2RlNzc3NDEwODllZDAwMWY2Y2VlNDdiMDAzNTA5ZmE1
15
- NDI4NTFjNjViYWI1ZTc5MDA5NzJhNWQwMGQyZGY2Y2U3YWU0NjQ=
13
+ YjE1MWY4MTVhYTMxYzEyODU0Mjk3Y2NiY2I5YTA5YjBkMTlmOGE4MmZlOTMw
14
+ NDlkNzU2MmU2OTg0ZGNiYzdkNWRkZjAwYmQ5OWE0MzNhNGE0MDVjZmVkZWFk
15
+ NzM3MDNkZmEwOGI1OTY4ZWQ3NTg2ODY0MjBiODRlNDU5YTZlOGE=
data/README.md CHANGED
@@ -65,6 +65,20 @@ resource("/albums") do |res|
65
65
  end
66
66
  ```
67
67
 
68
+ ### Nested params
69
+
70
+ These are also casted and validated as you'd expect:
71
+
72
+ ```ruby
73
+ resource("/albums") do |res|
74
+ resource.link(:post) do |link|
75
+ link.nested :artist do |a|
76
+ a.property.text :name
77
+ end
78
+ end
79
+ end
80
+ ```
81
+
68
82
  ### JSON Schema
69
83
 
70
84
  The extension will serve a JSON Schema dump at `GET /schema` for you.
@@ -1,7 +1,7 @@
1
1
  module Sinatra
2
2
  module Schema
3
3
  class Definition
4
- attr_accessor :description, :example, :format, :id, :type
4
+ attr_accessor :description, :example, :id, :type
5
5
 
6
6
  def initialize(options={})
7
7
  @description = options[:description]
@@ -15,10 +15,10 @@ module Sinatra
15
15
  return unless value
16
16
 
17
17
  case type
18
- when "string"
19
- value.to_s
20
18
  when "boolean"
21
19
  %w( t true 1 ).include?(value.to_s)
20
+ when "email", "string", "uuid"
21
+ value.to_s
22
22
  end
23
23
  end
24
24
 
@@ -27,10 +27,41 @@ module Sinatra
27
27
  return if value.nil?
28
28
 
29
29
  case type
30
+ when "boolean"
31
+ [true, false].include?(value)
32
+ when "email"
33
+ value.to_s =~ /\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/
30
34
  when "string"
31
35
  value.is_a?(String)
36
+ when "uuid"
37
+ value.to_s =~ /\A[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}\Z/
38
+ end
39
+ end
40
+
41
+ def to_schema
42
+ schema_type, schema_format = json_schema_type_and_format
43
+ attrs = { type: schema_type }
44
+ if schema_format
45
+ attrs[:format] = schema_format
46
+ end
47
+ if description
48
+ attrs[:description] = description
49
+ end
50
+ attrs
51
+ end
52
+
53
+ protected
54
+
55
+ def json_schema_type_and_format
56
+ case type
32
57
  when "boolean"
33
- [true, false].include?(value)
58
+ "boolean"
59
+ when "email"
60
+ ["string", "email"]
61
+ when "string"
62
+ "string"
63
+ when "uuid"
64
+ ["string", "uuid"]
34
65
  end
35
66
  end
36
67
  end
@@ -2,23 +2,40 @@ module Sinatra
2
2
  module Schema
3
3
  module DSL
4
4
  class Definitions
5
- attr_accessor :definition, :resource, :options
5
+ attr_accessor :definition, :resource, :options, :targets
6
6
 
7
- def initialize(resource, options={})
8
- @options = options
7
+ def initialize(resource, targets)
9
8
  @resource = resource
9
+ # array of hashes to receive the definition, first is the resource defs
10
+ @targets = targets
10
11
  end
11
12
 
12
- def text(id, local_options={})
13
- def_options = options.merge(local_options)
14
- def_options.merge!(id: id, type: "string")
15
- add Definition.new(def_options)
13
+ def text(id, options={})
14
+ options.merge!(id: id, type: "string")
15
+ add Definition.new(options)
16
16
  end
17
17
 
18
- def bool(id, local_options={})
19
- def_options = options.merge(local_options)
20
- def_options.merge!(id: id, type: "boolean")
21
- add Definition.new(def_options)
18
+ def bool(id, options={})
19
+ options.merge!(id: id, type: "boolean")
20
+ add Definition.new(options)
21
+ end
22
+
23
+ def uuid(id, options={})
24
+ options.merge!(id: id, type: "uuid")
25
+ add Definition.new(options)
26
+ end
27
+
28
+ def email(id, options={})
29
+ options.merge!(id: id, type: "email")
30
+ add Definition.new(options)
31
+ end
32
+
33
+ def nested(id)
34
+ # add a space in the definitions/properties for the nested def:
35
+ targets.each { |h| h[id] = {} }
36
+
37
+ # yield a new DSL with updated targets
38
+ yield Definitions.new(resource, targets.map { |h| h[id] })
22
39
  end
23
40
 
24
41
  def ref(id)
@@ -33,14 +50,12 @@ module Sinatra
33
50
  protected
34
51
 
35
52
  def add(definition, reference=false)
36
- unless reference
37
- @resource.defs[definition.id] = definition
38
- end
39
- if options[:serialize]
40
- @resource.properties[definition.id] = definition
41
- end
42
- if link = options[:link]
43
- link.properties[definition.id] = definition
53
+ targets.each_with_index do |hash, i|
54
+ # here's the trick, and here's why the first target is always the
55
+ # resource def: skip it when adding a reference (eg: it's already)
56
+ # in the resource def, just add the property!
57
+ next if reference && i == 0
58
+ hash[definition.id] = definition
44
59
  end
45
60
  end
46
61
  end
@@ -28,13 +28,13 @@ module Sinatra
28
28
  end
29
29
 
30
30
  def property
31
- DSL::Definitions.new(resource, link: link, serialize: false)
31
+ DSL::Definitions.new(resource, [resource.defs, link.properties])
32
32
  end
33
33
 
34
34
  protected
35
35
 
36
36
  def build_link
37
- full_href = "#{resource.path}/#{href.chomp("/")}".chomp("/")
37
+ full_href = "#{resource.path}/#{href}".gsub("//", "/").chomp("/")
38
38
  Link.new(resource: resource, method: method, href: full_href)
39
39
  end
40
40
  end
@@ -14,7 +14,7 @@ module Sinatra
14
14
  end
15
15
 
16
16
  def property
17
- DSL::Definitions.new(resource, serialize: true)
17
+ DSL::Definitions.new(resource, [resource.defs, resource.properties])
18
18
  end
19
19
 
20
20
  def link(method, href="/", &blk)
@@ -11,12 +11,16 @@ module Sinatra
11
11
  end
12
12
 
13
13
  def register(app)
14
+ app.send(method.downcase, href, &handler)
15
+ end
16
+
17
+ def handler
14
18
  link = self
15
- app.send(method.downcase, href) do
19
+ lambda do
16
20
  begin
17
21
  schema_params = parse_params(link.properties)
18
22
  validate_params!(schema_params, link.properties)
19
- res = link.action_block.call(schema_params)
23
+ res = instance_exec(schema_params, &link.action_block)
20
24
  link.resource.validate_response!(res)
21
25
  res
22
26
  rescue RuntimeError => e
@@ -1,6 +1,6 @@
1
1
  module Sinatra
2
2
  module Schema
3
- module ParamHandling
3
+ module ParamParsing
4
4
  def parse_params(properties)
5
5
  case request.media_type
6
6
  when nil, "application/json"
@@ -29,12 +29,18 @@ module Sinatra
29
29
  raise "Invalid JSON"
30
30
  end
31
31
 
32
- def cast_regular_params(properties)
33
- casted_params = params.inject({}) do |casted, (k, v)|
32
+ def cast_regular_params(properties, root=params)
33
+ casted_params = root.inject({}) do |casted, (k, v)|
34
34
  definition = properties[k.to_sym]
35
- # if there's no definition just leave the original param,
36
- # let the validation raise on this later:
37
- casted[k] = definition ? definition.cast(v) : v
35
+
36
+ # handle nested params
37
+ if definition.is_a?(Hash) || v.is_a?(Hash)
38
+ casted[k] = cast_regular_params(definition, v)
39
+ else
40
+ # if there's no definition just leave the original param,
41
+ # let the validation raise on this later:
42
+ casted[k] = definition ? definition.cast(v) : v
43
+ end
38
44
  casted
39
45
  end
40
46
  indifferent_params(casted_params)
@@ -13,8 +13,13 @@ module Sinatra
13
13
  end
14
14
 
15
15
  properties.each do |id, definition|
16
- unless definition.valid?(params[id])
17
- raise "Bad param: #{id}"
16
+ # handle nested params
17
+ if definition.is_a?(Hash)
18
+ validate_params!(params[id], definition)
19
+ else
20
+ unless definition.valid?(params[id])
21
+ raise "Bad param: #{id}"
22
+ end
18
23
  end
19
24
  end
20
25
  end
@@ -38,10 +38,7 @@ module Sinatra
38
38
  description: description,
39
39
  type: "object",
40
40
  definitions: defs.inject({}) { |h, (id, definition)|
41
- h[id] = {
42
- description: definition.description,
43
- type: definition.type,
44
- }
41
+ h[id] = definition.to_schema
45
42
  h
46
43
  },
47
44
  links: links.map { |link|
@@ -1,5 +1,5 @@
1
1
  module Sinatra
2
2
  module Schema
3
- VERSION = "0.0.2"
3
+ VERSION = "0.0.3"
4
4
  end
5
5
  end
@@ -5,7 +5,7 @@ require "multi_json"
5
5
 
6
6
  require "sinatra/schema/definition"
7
7
  require "sinatra/schema/link"
8
- require "sinatra/schema/param_handling"
8
+ require "sinatra/schema/param_parsing"
9
9
  require "sinatra/schema/param_validation"
10
10
  require "sinatra/schema/resource"
11
11
  require "sinatra/schema/root"
@@ -17,7 +17,7 @@ require "sinatra/schema/dsl/resources"
17
17
  module Sinatra
18
18
  module Schema
19
19
  def self.registered(app)
20
- app.helpers ParamHandling
20
+ app.helpers ParamParsing
21
21
  app.helpers ParamValidation
22
22
  app.get "/schema" do
23
23
  content_type("application/schema+json")
@@ -0,0 +1,64 @@
1
+ require "spec_helper"
2
+
3
+ describe Sinatra::Schema::Definition do
4
+ let(:definition) { Sinatra::Schema::Definition.new }
5
+
6
+ describe "#cast" do
7
+ it "casts booleans" do
8
+ definition.type = "boolean"
9
+ assert_equal true, definition.cast("t")
10
+ assert_equal true, definition.cast("true")
11
+ assert_equal true, definition.cast("1")
12
+ assert_equal false, definition.cast("0")
13
+ assert_equal false, definition.cast("false")
14
+ end
15
+
16
+ it "casts text" do
17
+ definition.type = "string"
18
+ assert_equal "123", definition.cast(123)
19
+ end
20
+ end
21
+
22
+ describe "#valid?" do
23
+ it "detects booleans" do
24
+ definition.type = "boolean"
25
+ assert definition.valid?(true)
26
+ assert definition.valid?(false)
27
+ refute definition.valid?("true")
28
+ end
29
+
30
+ it "detects emails" do
31
+ definition.type = "email"
32
+ assert definition.valid?("foo@bar.com")
33
+ refute definition.valid?("foobar.com")
34
+ end
35
+
36
+ it "detects text" do
37
+ definition.type = "string"
38
+ assert definition.valid?("foo")
39
+ refute definition.valid?(123)
40
+ end
41
+
42
+ it "detects uuids" do
43
+ definition.type = "uuid"
44
+ assert definition.valid?(SecureRandom.uuid)
45
+ refute definition.valid?("wrong")
46
+ end
47
+ end
48
+
49
+ describe "#to_schema" do
50
+ it "dumps emails" do
51
+ definition.type = "email"
52
+ schema = definition.to_schema
53
+ assert_equal "string", schema[:type]
54
+ assert_equal "email", schema[:format]
55
+ end
56
+
57
+ it "dumps uuids" do
58
+ definition.type = "uuid"
59
+ schema = definition.to_schema
60
+ assert_equal "string", schema[:type]
61
+ assert_equal "uuid", schema[:format]
62
+ end
63
+ end
64
+ end
@@ -2,8 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe Sinatra::Schema::DSL::Definitions do
4
4
  let(:resource) { Sinatra::Schema::Resource.new(path: "/foobar") }
5
- let(:dsl) { described_class.new(resource, options) }
6
- let(:options) { Hash.new }
5
+ let(:dsl) { described_class.new(resource, [resource.defs, resource.properties]) }
7
6
  let(:root) { Sinatra::Schema::Root.instance }
8
7
 
9
8
  it "adds a string definition to the resource" do
@@ -18,9 +17,18 @@ describe Sinatra::Schema::DSL::Definitions do
18
17
  assert_equal "boolean", resource.defs[:foobar].type
19
18
  end
20
19
 
20
+ it "adds nested definitions" do
21
+ dsl.nested(:user) do |prop|
22
+ prop.text :email
23
+ prop.bool :admin
24
+ end
25
+ assert_equal 2, resource.defs[:user].size
26
+ assert_equal "string", resource.defs[:user][:email].type
27
+ assert_equal "boolean", resource.defs[:user][:admin].type
28
+ end
29
+
21
30
  describe "#ref" do
22
- let(:definition) { Sinatra::Schema::Definition.new }
23
- before { options[:serialize] = true }
31
+ let(:definition) { Sinatra::Schema::Definition.new(id: :foobar) }
24
32
 
25
33
  it "adds a reference to another definition in the resource" do
26
34
  resource.defs[:foobar] = definition
@@ -21,5 +21,13 @@ describe Sinatra::Schema::DSL::Resources do
21
21
  assert_equal 1, dsl.resource.properties.size
22
22
  assert dsl.resource.properties.has_key?(:foo)
23
23
  end
24
+
25
+ it "supports nested properties" do
26
+ dsl.property.nested :user do |prop|
27
+ prop.text :email
28
+ prop.bool :admin
29
+ end
30
+ assert_equal 2, dsl.resource.properties[:user].size
31
+ end
24
32
  end
25
33
  end
@@ -1,9 +1,9 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Sinatra::Schema::ParamHandling do
3
+ describe Sinatra::Schema::ParamParsing do
4
4
  before do
5
5
  @rack_app = Sinatra.new do
6
- helpers Sinatra::Schema::ParamHandling
6
+ helpers Sinatra::Schema::ParamParsing
7
7
  post("/") do
8
8
  MultiJson.encode(parse_params($properties))
9
9
  end
@@ -31,6 +31,12 @@ describe Sinatra::Schema::ParamHandling do
31
31
  post "/", MultiJson.encode(params)
32
32
  assert_equal params, last_json
33
33
  end
34
+
35
+ it "preserves nested params" do
36
+ params = { "foo" => { "bar" => 42 }}
37
+ post "/", MultiJson.encode(params)
38
+ assert_equal params, last_json
39
+ end
34
40
  end
35
41
 
36
42
  describe "form-encoded params" do
@@ -43,8 +49,16 @@ describe Sinatra::Schema::ParamHandling do
43
49
  assert_equal({ "some_text" => "true", "some_bool" => true }, last_json)
44
50
  end
45
51
 
52
+ it "handles nested params" do
53
+ $properties = {
54
+ foo: { bar: Sinatra::Schema::Definition.new(type: "boolean") }
55
+ }
56
+ post "/", foo: { bar: "true" }
57
+ assert_equal({ "foo" => { "bar" => true }}, last_json)
58
+ end
59
+
46
60
  it "leaves params without the corresponding property untouched" do
47
- $properies = {}
61
+ $properties = {}
48
62
  params = { "foo" => "bar" }
49
63
  post "/", params
50
64
  assert_equal params, last_json
@@ -32,4 +32,12 @@ describe Sinatra::Schema::ParamValidation do
32
32
  post "/", MultiJson.encode(bool: "omg")
33
33
  end
34
34
  end
35
+
36
+ it "supports nested params" do
37
+ $properties = { foo: { bar: Sinatra::Schema::Definition.new(type: "boolean") }}
38
+ assert_raises(RuntimeError) do
39
+ post "/", MultiJson.encode(foo: { bar: "omg" })
40
+ puts last_response.body
41
+ end
42
+ end
35
43
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pedro Belo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-06 00:00:00.000000000 Z
11
+ date: 2014-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -139,19 +139,20 @@ files:
139
139
  - lib/sinatra/schema/dsl/links.rb
140
140
  - lib/sinatra/schema/dsl/resources.rb
141
141
  - lib/sinatra/schema/link.rb
142
- - lib/sinatra/schema/param_handling.rb
142
+ - lib/sinatra/schema/param_parsing.rb
143
143
  - lib/sinatra/schema/param_validation.rb
144
144
  - lib/sinatra/schema/resource.rb
145
145
  - lib/sinatra/schema/root.rb
146
146
  - lib/sinatra/schema/tasks/schema.rake
147
147
  - lib/sinatra/schema/utils.rb
148
148
  - lib/sinatra/schema/version.rb
149
+ - spec/definition_spec.rb
149
150
  - spec/dsl/definitions_spec.rb
150
151
  - spec/dsl/links_spec.rb
151
152
  - spec/dsl/resources_spec.rb
152
153
  - spec/integration_spec.rb
153
154
  - spec/json_schema_spec.rb
154
- - spec/param_handling_spec.rb
155
+ - spec/param_parsing_spec.rb
155
156
  - spec/param_validation_spec.rb
156
157
  - spec/resource_spec.rb
157
158
  - spec/spec_helper.rb