Skip to content

Kontrol is a DSL to define validations for JSON data

License

Notifications You must be signed in to change notification settings

Ragmaanir/kontrol

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

kontrol Build StatusDependency Status

Version: 0.2.3

Kontrol is a DSL to define validations for JSON data.

Installation

Add this to your application's shard.yml:

dependencies:
  kontrol:
    github: ragmaanir/kontrol

Usage

require "kontrol"
# To define a validation you can use the `Kontrol.object` macro.
# Validations can be defined by expressions:

val, _ = Kontrol.object(
  name: {type: String, length: v.size > 4},
)

# When the constraint `length` is violated it returns:

j = json(name: "a")
assert val.call(j) == {"name" => [:length]}

# The value of the property `name` can be accessed via `v` in the validation expression.

# For the type validation there are two shortcuts since they are so common:

# Shortcut 1
Kontrol.object(
  name: {type: String},
)

# Shortcut 2
Kontrol.object(
  name: String,
)

# You always have to specify the type (might change).

# You can also define root-level validations for an object:

val, _ = Kontrol.object(
  {length: v["name"].as_s.size == v["name_length"].as_i},
  name: String,
  name_length: Int64
)

# The `length` validation checks whether the length of the name string matches the name_length.
# The root-level validations are only executed when all properties are valid to prevent
# them raising exceptions caused by invalid data. A root level validation error
# is stored under the key "@", because the object might not have a name. So in this case:
j = json(
  name: "test",
  name_length: 3
)

assert val.call(j) == {
  "@" => [:length],
}

Simple example:

# First object is the validator, second is the converter that can be used
# to convert the JSON to nested named tuples.
val, con = Kontrol.object(
  name: String,
  percentage: {type: Int64, min: v > 0, max: v <= 100}
)

# invalid since percentage violates :min constraint
assert val.call(json(
  name: "test",
  percentage: -1
)) == {"percentage" => [:min]}

# invalid since name violates :type-constraint and :min is violated
assert val.call(json(
  name: 2,
  percentage: -1
)) == {"name" => [:type], "percentage" => [:min]}

# invalid since the required attributes are missing or nil
assert val.call(json(
  name: nil
)) == {"name" => [:required], "percentage" => [:required]}

# valid
assert val.call(json(
  name: "test",
  percentage: 45
)).empty?

# convert input to nested named tuples
assert con.call(json(
  name: "test",
  percentage: 45
)) == {name: "test", percentage: 45}

Advanced example (nested objects and root-level-validations):

val, _ = object(
  {
    my_book: v["author"].as_s == v["book"]["author"].as_s,
  },
  author: String,
  book: object(
    author: String
  )
)

assert val.call(json(
  author: "Bob"
)) == {"book" => [:required]}

assert val.call(json(
  author: "Bob",
  book: {
    author: 1337,
  }
)) == {"book.author" => [:type]}

assert val.call(json(
  author: "Bob",
  book: {
    author: "Bobby",
  }
)) == {"@" => [:my_book]}

assert val.call(json(
  author: "Bob",
  book: {
    author: "Bob",
  }
)).empty?

TODOs

  • Support arrays
  • Support/test required/optional attributes
  • Consistent handling of unspecified attributes (reject? ignore? errors?)
  • Cleanup/simplify macros
  • Use rule-class instances instead of closures?

Contributing

  1. Fork it ( https://github.com/ragmaanir/kontrol/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors

About

Kontrol is a DSL to define validations for JSON data

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published