README_V3.md in schemacop-3.0.0.rc1 vs README_V3.md in schemacop-3.0.0.rc2
- old
+ new
@@ -1,17 +1,13 @@
# Schemacop schema V3
-Please note that Schemacop v3 is still a work in progress, especially the documentation.
+## Table of Contents
-Use at your own discretion.
-
-# Table of Contents
-1. [Introduction](#Introduction)
-2. [Validation](#validation)
-3. [Exceptions](#exceptions)
-4. [Generic Keywords](#generic-keywords)
-5. [Nodes](#nodes)
+1. [Validation](#validation)
+2. [Exceptions](#exceptions)
+3. [Generic Keywords](#generic-keywords)
+4. [Nodes](#nodes)
1. [String](#string)
2. [Integer](#integer)
3. [Number](#number)
4. [Symbol](#symbol)
5. [Boolean](#boolean)
@@ -21,17 +17,13 @@
9. [AllOf](#allOf)
10. [AnyOf](#anyOf)
11. [OneOf](#oneOf)
12. [IsNot](#isNot)
13. [Reference](#reference)
-6. [Context](#context)
-7. [External schemas](#external-schemas)
+5. [Context](#context)
+6. [External schemas](#external-schemas)
-## Introduction
-
-TODO: Write short section about using schemacop V3
-
## Validation
Using schemacop, you can either choose to validate the data either using the
graceful `validate` method, or the bang variant, `validate!`.
@@ -72,24 +64,126 @@
schema.validate!('Foo') # => Schemacop::Exceptions::ValidationError: /: String does not match format "date".
```
## Exceptions
-TODO: Describe the exceptions raised by schemacop
+Schemacop can raise the following exceptions:
-`Schemacop::Exceptions::ValidationError`
-`Schemacop::Exceptions::InvalidSchemaError`
+* `Schemacop::Exceptions::ValidationError`: This exception is raised when the `validate!`
+ method is used, and the data that was passed in is invalid. The exception message contains
+ additional informations why the validation failed.
+ Example:
+
+ ```ruby
+ schema = Schemacop::Schema3.new :hash do
+ int! :foo
+ end
+
+ schema.validate!(foo: 'bar')
+ # => Schemacop::Exceptions::ValidationError: /foo: Invalid type, expected "integer".
+ ```
+
+* `Schemacop::Exceptions::InvalidSchemaError`: This exception is raised when the schema
+ itself is not valid. The exception message contains additional informations why the
+ validation failed.
+
+ Example:
+
+ ```ruby
+ Schemacop::Schema3.new :hash do
+ int!
+ end
+
+ # => Schemacop::Exceptions::InvalidSchemaError: Child nodes must have a name.
+ ```
+
## Generic Keywords
-TODO: Complete this
+The nodes in Schemacop v3 also support generic keywords, similar to JSON schema:
-* enum
-* title
-* description
-* examples
+* `title`: Short string, should be self-explanatory
+* `description`: Description of the schema
+* `examples`: Here, you can provide examples which will be valid for the schema
+* `enum`: Here, you may enumerate values which will be valid, if the provided
+ value is not in the array, the validation will fail
+* `default`: You may provide a default value for items that will be set if the
+ value is not given
+
+The three keywords `title`, `description` and `examples` aren't used for validation,
+but can be used to document the schema. They will be included in the JSON output
+when you use the `as_json` method:
+
+```ruby
+schema = Schemacop::Schema3.new :hash do
+ str! :name, title: 'Name', description: 'Holds the name of the user', examples: ['Joe', 'Anna']
+end
+
+schema.as_json
+
+# => {"properties"=>{"name"=>{"type"=>"string", "title"=>"Name", "examples"=>["Joe", "Anna"], "description"=>"Holds the name of the user"}}, "additionalProperties"=>false, "required"=>["name"], "type"=>"object"}
+```
+
+The `enum` keyword can be used to only allow a subset of values:
+
+```ruby
+schema = Schemacop::Schema3.new :string, enum: ['foo', 'bar']
+
+schema.validate!('foo') # => "foo"
+schema.validate!('bar') # => "bar"
+schema.validate!('baz') # => Schemacop::Exceptions::ValidationError: /: Value not included in enum ["foo", "bar"].
+```
+
+Please note, that you can also specify values in the enum that are not valid for
+the schema. This means that the validation will still fail:
+
+```ruby
+schema = Schemacop::Schema3.new :string, enum: ['foo', 'bar', 42]
+
+schema.validate!('foo') # => "foo"
+schema.validate!('bar') # => "bar"
+schema.validate!(42) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "string".
+```
+
+The enum will also be provided in the json output:
+
+```ruby
+schema = Schemacop::Schema3.new :string, enum: ['foo', 'bar']
+
+schema.as_json
+# => {"type"=>"string", "enum"=>["foo", "bar", 42]}
+```
+
+And finally, the `default` keyword lets you set a default value to use when no
+value is provided:
+
+```ruby
+schema = Schemacop::Schema3.new :string, default: 'Schemacop'
+
+schema.validate!('foo') # => "foo"
+schema.validate!(nil) # => "Schemacop"
+```
+
+The default value will also be provided in the json output:
+
+```ruby
+schema = Schemacop::Schema3.new :string, default: 'Schemacop'
+
+schema.as_json
+# => {"type"=>"string", "default"=>"Schemacop"}
+```
+
+Note that the default value you use is also validated against the schema:
+
+```ruby
+schema = Schemacop::Schema3.new :string, default: 42
+
+schema.validate!('foo') # => "foo"
+schema.validate!(nil) # => Schemacop::Exceptions::ValidationError: /: Invalid type, expected "string".
+```
+
## Nodes
### String
Type: `:string`\
@@ -521,15 +615,65 @@
str! :foo # Is a required property
int? :bar # Is an optional property
end
schema.validate!({}) # => Schemacop::Exceptions::ValidationError: /foo: Value must be given.
-schema.validate!({foo: 'str'}) # => {:foo=>"str"}
-schema.validate!({foo: 'str', bar: 42}) # => {:foo=>"str", :bar=>42}
+schema.validate!({foo: 'str'}) # => {"foo"=>"str"}
+schema.validate!({foo: 'str', bar: 42}) # => {"foo"=>"str", "bar"=>42}
schema.validate!({bar: 42}) # => Schemacop::Exceptions::ValidationError: /foo: Value must be given.
```
+The name of the properties may either be a string or a symbol, and you can pass
+in the property either identified by a symbol or a string:
+
+The following two schemas are equal:
+
+```ruby
+schema = Schemacop::Schema3.new :hash do
+ int! :foo
+end
+
+schema.validate!(foo: 42) # => {"foo"=>42}
+schema.validate!('foo' => 42) # => {"foo"=>42}
+
+schema = Schemacop::Schema3.new :hash do
+ int! 'foo'
+end
+
+schema.validate!(foo: 42) # => {"foo"=>42}
+schema.validate!('foo' => 42) # => {"foo"=>42}
+```
+
+The result in both cases will be a
+[HashWithIndifferentAccess](https://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html),
+which means that you can access the data in the hash with the symbol as well
+as the string representation:
+
+```ruby
+schema = Schemacop::Schema3.new :hash do
+ int! :foo
+end
+
+result = schema.validate!(foo: 42)
+
+result.class # => ActiveSupport::HashWithIndifferentAccess
+result[:foo] # => 42
+result['foo'] # 42
+```
+
+Please note, that if you specify the value twice in the data you want to validate,
+once with the key being a symbol and once being a string, Schemacop will raise an
+error:
+
+```ruby
+schema = Schemacop::Schema3.new :hash do
+ int! :foo
+end
+
+schema.validate!(foo: 42, 'foo' => 43) # => Schemacop::Exceptions::ValidationError: /: Has 1 ambiguous properties: [:foo].
+```
+
##### Pattern properties
In addition to symbols, property keys can also be a regular expression. Here,
you may only use the optional `?` suffix for the property. This allows any
property, which matches the type and the name of the property matches the
@@ -541,12 +685,12 @@
# name starts with `id_`.
int? /^id_.*$/
end
schema.validate!({}) # => {}
-schema.validate!({id_foo: 1}) # => {:id_foo=>1}
-schema.validate!({id_foo: 1, id_bar: 2}) # => {:id_foo=>1, :id_bar=>2}
+schema.validate!({id_foo: 1}) # => {"id_foo"=>1}
+schema.validate!({id_foo: 1, id_bar: 2}) # => {"id_foo"=>1, "id_bar"=>2}
schema.validate!({foo: 3}) # => Schemacop::Exceptions::ValidationError: /: Obsolete property "foo".
```
##### Additional properties & property names
@@ -560,11 +704,11 @@
```ruby
# This schema will accept any additional properties
schema = Schemacop::Schema3.new :hash, additional_properties: true
schema.validate!({}) # => {}
-schema.validate!({foo: :bar, baz: 42}) # => {:foo=>:bar, :baz=>42}
+schema.validate!({foo: :bar, baz: 42}) # => {"foo"=>:bar, "baz"=>42}
```
Using the DSL method `add` in the hash-node's body however, you can specify
an additional schema to which additional properties must adhere:
@@ -576,36 +720,36 @@
# Allow any additional properties besides `id`, but their value must be a
# string.
add :string
end
-schema.validate!({id: 1}) # => {:id=>1}
-schema.validate!({id: 1, foo: 'bar'}) # => {:foo=>"bar", :id=>1}
+schema.validate!({id: 1}) # => {"id"=>1}
+schema.validate!({id: 1, foo: 'bar'}) # => {"id"=>1, "foo"=>"bar"}
schema.validate!({id: 1, foo: 42}) # => Schemacop::Exceptions::ValidationError: /foo: Invalid type, expected "string".
```
Using the option `property_names`, you can additionaly specify a pattern that
any additional property **keys** must adhere to:
```ruby
# The following schema allows any number of properties, but all keys must
# consist of downcase letters from a-z.
-schema = Schemacop::Schema3.new :hash, additional_properties: :true, property_names: '^[a-z]+$'
+schema = Schemacop::Schema3.new :hash, additional_properties: true, property_names: '^[a-z]+$'
schema.validate!({}) # => {}
-schema.validate!({foo: 123}) # => {:foo=>123}
-schema.validate!({Foo: 'bar'}) # => Schemacop::Exceptions::ValidationError: /: Property name :Foo does not match "^[a-z]+$".
+schema.validate!({foo: 123}) # => {"foo"=>123}
+schema.validate!({Foo: 'bar'}) # => Schemacop::Exceptions::ValidationError: /: Property name "Foo" does not match "^[a-z]+$".
# The following schema allows any number of properties, but all keys must
# consist of downcase letters from a-z AND the properties must be arrays.
schema = Schemacop::Schema3.new :hash, additional_properties: true, property_names: '^[a-z]+$' do
add :array
end
schema.validate!({}) # => {}
-schema.validate!({foo: [1, 2, 3]}) # => {:foo=>[1, 2, 3]}
+schema.validate!({foo: [1, 2, 3]}) # => {"foo"=>[1, 2, 3]}
schema.validate!({foo: :bar}) # => Schemacop::Exceptions::ValidationError: /foo: Invalid type, expected "array".
schema.validate!({Foo: :bar}) # => Schemacop::Exceptions::ValidationError: /: Property name :Foo does not match "^[a-z]+$". /Foo: Invalid type, expected "array".
```
##### Dependencies
@@ -625,11 +769,11 @@
dep :credit_card, :billing_address, :phone_number
dep :billing_address, :credit_card
end
schema.validate!({}) # => Schemacop::Exceptions::ValidationError: /name: Value must be given.
-schema.validate!({name: 'Joe Doe'}) # => {:name=>"Joe Doe"}
+schema.validate!({name: 'Joe Doe'}) # => {"name"=>"Joe Doe"}
schema.validate!({
name: 'Joe Doe',
billing_address: 'Street 42'
})
# => Schemacop::Exceptions::ValidationError: /: Missing property "credit_card" because "billing_address" is given.
@@ -644,19 +788,19 @@
name: 'Joe Doe',
billing_address: 'Street 42',
phone_number: '000-000-00-00',
credit_card: 'XXXX XXXX XXXX XXXX X'
})
-# => {:name=>"Joe Doe", :credit_card=>"XXXX XXXX XXXX XXXX X", :billing_address=>"Street 42", :phone_number=>"000-000-00-00"}
+# => {"name"=>"Joe Doe", "credit_card"=>"XXXX XXXX XXXX XXXX X", "billing_address"=>"Street 42", "phone_number"=>"000-000-00-00"}
```
### Object
Type: `:object`\
DSL: `obj`
-The object type represents a ruby `Object`. Please note that the `as_json? method
+The object type represents a ruby `Object`. Please note that the `as_json` method
on nodes of this type will just return `{}` (an empty JSON object), as there isn't
a useful way to represent a ruby object without conflicting with the `Hash` type.
If you want to represent an JSON object, you should use the `Hash` node.
In the most basic form, this node will accept anything:
@@ -882,11 +1026,11 @@
location: 'Washington DC',
country: 'USA'
}
})
-# => {:shipping_address=>{:street=>"Example Street 42", :zip_code=>"12345", :location=>"London", :country=>"United Kingdom"}, :billing_address=>{:street=>"Main St.", :zip_code=>"54321", :location=>"Washington DC", :country=>"USA"}}
+# => {"shipping_address"=>{"street"=>"Example Street 42", "zip_code"=>"12345", "location"=>"London", "country"=>"United Kingdom"}, "billing_address"=>{"street"=>"Main St.", "zip_code"=>"54321", "location"=>"Washington DC", "country"=>"USA"}}
```
Note that if you use the reference node with the long type name `reference`,
e.g. in an array, you need to specify the "name" of the schema in the
`path` option:
@@ -900,11 +1044,11 @@
list :reference, path: :User
end
schema.validate!([]) # => []
-schema.validate!([{first_name: 'Joe', last_name: 'Doe'}]) # => [{:first_name=>"Joe", :last_name=>"Doe"}]
+schema.validate!([{first_name: 'Joe', last_name: 'Doe'}]) # => [{"first_name"=>"Joe", "last_name"=>"Doe"}]
schema.validate!([id: 42, first_name: 'Joe']) # => Schemacop::Exceptions::ValidationError: /[0]/last_name: Value must be given. /[0]: Obsolete property "id".
```
## Context
@@ -938,11 +1082,11 @@
# Validate the data in the context we defined before, where we need the first_name
# and last_name of a person, as well as an optional info hash with the born_at date
# of the person.
Schemacop.with_context context do
schema.validate!({first_name: 'Joe', last_name: 'Doe', info: { born_at: '1980-01-01'} })
- # => {:first_name=>"Joe", :last_name=>"Doe", :info=>{:born_at=>Tue, 01 Jan 1980}}
+ # => {"first_name"=>"Joe", "last_name"=>"Doe", "info"=>{"born_at"=>Tue, 01 Jan 1980}}
end
# Now we might want another context, where the person is more anonymous, and as
# such, we need another schema
other_context = Schemacop::V3::Context.new
@@ -959,11 +1103,11 @@
# => Schemacop::Exceptions::ValidationError: /nickname: Value must be given.
# /: Obsolete property "first_name".
# /: Obsolete property "last_name".
# /: Obsolete property "info".
- schema.validate!({nickname: 'J.'}) # => {:nickname=>"J."}
+ schema.validate!({nickname: 'J.'}) # => {"nickname"=>"J."}
end
```
As one can see, we validated the data against the same schema, but because we
defined the referenced schemas differently in the two contexts, we were able
@@ -1030,16 +1174,16 @@
schema = Schemacop::Schema3.new :hash do
ref! :usr, :user
end
schema.validate!({usr: {first_name: 'Joe', last_name: 'Doe'}})
- # => {:usr=>{:first_name=>"Joe", :last_name=>"Doe"}}
+ # => {"usr"=>{"first_name"=>"Joe", "last_name"=>"Doe"}}
schema.validate!({usr: {first_name: 'Joe', last_name: 'Doe', groups: []}})
- # => {:usr=>{:first_name=>"Joe", :last_name=>"Doe", :groups=>[]}}
+ # => {"usr"=>{"first_name"=>"Joe", "last_name"=>"Doe", "groups"=>[]}}
schema.validate!({usr: {first_name: 'Joe', last_name: 'Doe', groups: [{name: 'foo'}, {name: 'bar'}]}})
- # => {:usr=>{:first_name=>"Joe", :last_name=>"Doe", :groups=>[{:name=>"foo"}, {:name=>"bar"}]}}
+ # => {"usr"=>{"first_name"=>"Joe", "last_name"=>"Doe", "groups"=>[{"name"=>"foo"}, {"name"=>"bar"}]}}
```
### Non-Rails applications
Usage in non-Rails applications is the same as with usage in Rails applications,
\ No newline at end of file