Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge "Default values support in class JsonMessage."

  • Loading branch information...
commit 214da64406298dcb9076ddbe0db47ef612d4ab28 2 parents 3e2d261 + 96429b7
@kowshik kowshik authored Gerrit Code Review committed
View
67 lib/json_message.rb
@@ -1,4 +1,4 @@
-# Copyright (c) 2009-2011 VMware, Inc.
+# Copyright (c) 2009-2011 VMware, Inc
require 'rubygems'
require 'yajl'
@@ -6,10 +6,15 @@
require 'json_schema'
class JsonMessage
- # Base error class that all other JsonMessage related errors should inherit from
+ # Base error class that all other JsonMessage related errors should
+ # inherit from
class Error < StandardError
end
+ # Fields not defined properly.
+ class DefinitionError < Error
+ end
+
# Failed to parse json during +decode+
class ParseError < Error
end
@@ -27,12 +32,23 @@ def to_s
end
class Field
- attr_reader :name, :schema, :required
+ attr_reader :name, :schema, :required, :default
+
+ def initialize(name, schema, required = true, default = nil)
+ if required && default
+ msg = "Cannot define a default value: #{default}"
+ msg << " for the required field: #{name}."
+ raise DefinitionError.new(msg)
+ end
- def initialize(name, schema, required=true)
@name = name
@schema = schema.is_a?(JsonSchema) ? schema : JsonSchema.new(schema)
@required = required
+ if default
+ errs = @schema.validate(default)
+ raise ValidationError.new({name => errs}) if errs
+ end
+ @default = default
end
end
@@ -58,8 +74,8 @@ def from_decoded_json(dec_json)
errs = {}
- # Treat null values as if the keys aren't present. This isn't as strict as one would like,
- # but conforms to typical use cases.
+ # Treat null values as if the keys aren't present. This isn't as strict
+ # as one would like, but conforms to typical use cases.
dec_json.delete_if {|k, v| v == nil}
# Collect errors by field
@@ -79,23 +95,24 @@ def from_decoded_json(dec_json)
new(dec_json)
end
- def required(field_name, schema=JsonSchema::WILDCARD)
+ def required(field_name, schema = JsonSchema::WILDCARD)
define_field(field_name, schema, true)
end
- def optional(field_name, schema=JsonSchema::WILDCARD)
- define_field(field_name, schema, false)
+ def optional(field_name, schema = JsonSchema::WILDCARD, default = nil)
+ define_field(field_name, schema, false, default)
end
protected
- def define_field(name, schema, required)
+ def define_field(name, schema, required, default = nil)
name = name.to_sym
@fields ||= {}
- @fields[name] = Field.new(name, schema, required)
+ @fields[name] = Field.new(name, schema, required, default)
define_method name.to_sym do
+ set_default(name)
@msg[name]
end
@@ -108,13 +125,17 @@ def define_field(name, schema, required)
def initialize(fields={})
@msg = {}
fields.each {|k, v| set_field(k, v)}
+ set_defaults
end
def encode
if self.class.fields
+ set_defaults
missing_fields = {}
self.class.fields.each do |name, field|
- missing_fields[name] = "Missing field #{name}" unless (!field.required || @msg.has_key?(name))
+ unless (!field.required || @msg.has_key?(name))
+ missing_fields[name] = "Missing field #{name}"
+ end
end
raise ValidationError.new(missing_fields) unless missing_fields.empty?
end
@@ -130,10 +151,30 @@ def extract
def set_field(field, value)
field = field.to_sym
- raise ValidationError.new({field => "Unknown field #{field}"}) unless self.class.fields.has_key?(field)
+ unless self.class.fields && self.class.fields.has_key?(field)
+ raise ValidationError.new({field => "Unknown field #{field}"})
+ end
errs = self.class.fields[field].schema.validate(value)
raise ValidationError.new({field => errs}) if errs
@msg[field] = value
end
+
+ def set_defaults
+ if self.class.fields
+ self.class.fields.each do |name, field|
+ set_default(name)
+ end
+ end
+ end
+
+ def set_default(name)
+ if !@msg.include?(name)
+ if self.class.fields
+ if self.class.fields.include?(name) && self.class.fields[name].default
+ @msg[name] = self.class.fields[name].default
+ end
+ end
+ end
+ end
end
View
206 spec/unit/json_message_spec.rb
@@ -0,0 +1,206 @@
+# Copyright (c) 2009-2012 VMware, Inc
+require 'spec_helper'
+
+describe JsonMessage::Field do
+ it 'should raise an error when a required field is defined with a default' do
+ block = lambda { JsonMessage::Field.new("key", String, true, "default") }
+ expect(&block).to raise_error(JsonMessage::DefinitionError)
+ end
+
+ expected = 'should raise a schema validation error when schema validation'
+ expected << ' fails for the default value of an optional field'
+ it expected do
+ block = lambda { JsonMessage::Field.new("optional", Hash, false,
+ "default") }
+ expect(&block).to raise_error(JsonMessage::ValidationError)
+ end
+
+ expected = 'should not raise a schema validation error when default value'
+ expected << ' is absent for an optional field'
+ it expected do
+ block = lambda { JsonMessage::Field.new("optional", String, false) }
+ expect(&block).to_not raise_error
+ end
+end
+
+describe JsonMessage do
+ describe '#required' do
+ before :each do
+ @klass = Class.new(JsonMessage)
+ end
+
+ it 'should define the field accessor' do
+ @klass.required :required, String
+ msg = @klass.new
+ msg.required.should == nil
+ expect { msg.required = "required" }.to_not raise_error
+ end
+
+ it 'should define the field to be required' do
+ @klass.required :required, String
+ msg = @klass.new
+ block = lambda { msg.encode }
+ expect(&block).to raise_error(JsonMessage::ValidationError)
+ end
+
+ it 'should assume wildcard when schema is not defined' do
+ @klass.required :required
+ msg = @klass.new
+ msg.required = Object.new
+ end
+ end
+
+ describe '#optional' do
+ before :each do
+ @klass = Class.new(JsonMessage)
+ end
+
+ it 'should define the field accessor' do
+ @klass.optional :optional, String
+ msg = @klass.new
+ msg.optional.should == nil
+ expect { msg.optional = "optional" }.to_not raise_error
+ end
+
+ it 'should define the field to be optional' do
+ @klass.optional :optional, String
+ msg = @klass.new
+ block = lambda { msg.encode }
+ expect(&block).to_not raise_error(JsonMessage::ValidationError)
+ end
+
+ it 'should define a default value' do
+ @klass.optional :optional, String, "default"
+ msg = @klass.new
+ msg.optional.should == "default"
+ end
+
+ expected = 'should assume nil as default value when not defined'
+ it expected do
+ @klass.optional :optional, String
+ msg = @klass.new
+ msg.encode.should == Yajl::Encoder.encode({})
+ end
+
+ it 'should assume wildcard when schema is not defined' do
+ @klass.optional :optional
+ msg = @klass.new
+ msg.optional = Object.new
+ end
+ end
+
+ describe '#initialize' do
+ before :each do
+ @klass = Class.new(JsonMessage)
+ end
+
+ it 'should raise an error when undefined field is used' do
+ block = lambda { @klass.new({"undefined" => "undefined"}) }
+ expect(&block).to raise_error(JsonMessage::ValidationError)
+ end
+
+ expected = 'should set default value for optional field which is'
+ expected << ' defined, but not initialized in constructor'
+ it expected do
+ @klass.optional :optional, String, "default"
+ msg = @klass.new
+ msg.optional.should == "default"
+ end
+
+ expected = 'should set default value for optional field defined after'
+ expected << ' object is initialized'
+ it expected do
+ msg = @klass.new
+ @klass.optional :optional, String, "default"
+ msg.optional.should == "default"
+ end
+
+ it 'should replace a default value with a defined value' do
+ @klass.optional :optional, String, "default"
+ msg = @klass.new({"optional" => "defined"})
+ msg.optional.should == "defined"
+ end
+
+ it 'should not set a default for a field without a default value' do
+ @klass.optional :optional, String
+ msg = @klass.new
+ msg.optional.should == nil
+ end
+ end
+
+ describe '#encode' do
+ before :each do
+ @klass = Class.new(JsonMessage)
+ end
+
+ it 'should encode uninitialized optional attribute with default value' do
+ msg = @klass.new
+ @klass.optional :optional, String, "default"
+ msg.encode.should == Yajl::Encoder.encode({"optional" => "default"})
+ end
+
+ it 'should raise an error when required field is missing' do
+ @klass.required :required, String
+ msg = @klass.new
+ block = lambda { msg.encode }
+ expect(&block).to raise_error(JsonMessage::ValidationError)
+ end
+
+ it 'should encode fields' do
+ @klass.required :required, String
+ @klass.optional :without_default, String
+ @klass.optional :with_default, String, "default"
+ msg = @klass.new
+ msg.required = "required"
+ expected = {"required" => "required", "with_default" => "default"}
+ received = Yajl::Parser.parse(msg.encode)
+ received.should == expected
+ end
+ end
+
+ describe '#decode' do
+ before :each do
+ @klass = Class.new(JsonMessage)
+ end
+
+ it 'should raise a parse error when json passed is nil' do
+ expect { @klass.decode(nil) }.to raise_error(JsonMessage::ParseError)
+ end
+
+ it 'should raise a validation error when required field is missing' do
+ @klass.required :required, String
+ block = lambda { @klass.decode(Yajl::Encoder.encode({})) }
+ expect(&block).to raise_error(JsonMessage::ValidationError)
+ end
+
+ it 'should decode json' do
+ @klass.required :required, String
+ @klass.optional :optional_with_default, String, "default"
+ @klass.optional :optional_without_default, String
+ msg = @klass.new
+ encoded = Yajl::Encoder.encode({
+ "required" => "required",
+ "optional_without_default" => "blah"
+ })
+ decoded = @klass.decode(encoded)
+ decoded.required.should == "required"
+ decoded.optional_with_default.should == "default"
+ decoded.optional_without_default.should == "blah"
+ end
+ end
+
+ describe '#extract' do
+ before :each do
+ @klass = Class.new(JsonMessage)
+ end
+
+ it 'should extract fields' do
+ @klass.required :required, String
+ @klass.optional :optional, String, "default"
+ msg = @klass.new
+ msg.required = "required"
+ extracted = msg.extract
+ extracted.should == {:required => "required", :optional => "default"}
+ end
+ end
+end
View
4 vcap_common.gemspec
@@ -1,7 +1,7 @@
spec = Gem::Specification.new do |s|
s.name = 'vcap_common'
- s.version = '1.0.13'
- s.date = '2012-05-25'
+ s.version = '1.0.14'
+ s.date = '2012-07-05'
s.summary = 'vcap common'
s.homepage = "http://github.com/vmware-ac/core"
s.description = 'common vcap classes/methods'
Please sign in to comment.
Something went wrong with that request. Please try again.