Project

saphyr

0.0
The project is in a healthy, maintained state
The purpose of Saphyr is to provide a nice and simple DSL to easily and quickly design a validation schema for JSON document.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 13.0
~> 3.0
 Project Readme

Testing Gem Version License

Saphyr

The purpose of Saphyr gem is to provide a simple DSL to easily and quickly design a validation schema for JSON document.

Note :

There are already a bunch of gem doing this job, like: json-schema, json_schema, json_schemer all based on the JSON Standard to define a validation schema. It's a really nice crafted standard but his usage is not really intuitive.

Actually the Saphyr gem volontary does not support the json-schema standard to describe the validation schema and focus on simplicity with an easy to use DSL.

Generate documentation

Generate the documentation in ./doc folder:

$ yard

Start a http server and serve the documentation:

$ yard serve

Run tests

rspec
rake
rake spec

Installation

Add it to your Gemfile:

$ bundle add saphyr
$ bundle install

Install the gem:

$ gem install saphyr

Example of Usage

Some short examples to give a picture of Saphyr:

The most simple usage

data = {                      # --- Rules ---
  "id" => 236,                # Integer > 0
  "type" => 1,                # Integer, possible values are : 1, 2 or 3
  "price" => 32.10,           # Float > 0
  "name" => "my item"         # String with: 5 >= size <= 15
  "active" => true            # Boolean
}

class ItemValidator < Saphyr::Validator
  field :id,      :integer,  gt: 0
  field :type,    :integer,  in: [1, 2, 3]
  field :price,   :float,    gt: 0
  field :name,    :string,   min: 5, max: 15
  field :active,  :boolean
end
v = ItemValidator.new

if v.validate data
  puts "Validation : SUCCESS", "\n"
else
  puts "Validation : FAILED", "\n"
  Saphyr::Helpers::Format.errors_to_text v.errors
end

# Or use: v.parse_and_validate json

A more advanced usage

data = {                                 # --- Rules ---
  "id" => 236,                           # Integer > 0
  "name" => "my item",                   # String with: 5 >= size <= 15
  "uploads" => [                         # This array can only have 2 or 3 elements
    {
      "id" => 34,                        # Interger > 0
      "name" => "orig.gif",              # String, with: 7 <= size <= 15
      "mime" => "image/gif",             # String, possible values: ['image/jpeg', 'image/png', 'image/gif']
      "size" => 753745,                  # Integer > 0
      "timestamps" => {
        "created_at" => 1665241021,      # Integer > 0 (unix timestamp)
        "modified_at" => 1665241021      # Integer > 0 (unix timestamp)
      }
    },
    {
      "id" => 376,
      "name" => "medium.jpg",
      "mime" => "image/png",
      "size" => 8946653,
      "timestamps" => {
        "created_at" => 1669215446,
        "modified_at" => 1670943462
      }
    }
  ],
  "timestamps" => {
    "created_at" => 1669215446,
    "modified_at" => 1670943462
  },
  "active" => true,
}

class ItemValidator < Saphyr::Validator
  schema :timestamp do
    field :created_at,   :integer,  gt: 0
    field :modified_at,  :integer,  gt: 0
  end

  schema :upload do
    field :id,          :integer,  gt: 0
    field :name,        :string,   min: 7, max: 15
    field :mime,        :string,   in: ['image/jpeg', 'image/png', 'image/gif']
    field :size,        :integer,  gt: 0
    field :timestamps,  :schema,   name: :timestamp
  end

  field :id,          :integer,  gt: 0
  field :name,        :string,   min: 7, max: 15
  field :uploads,     :array,    min: 2, max: 3, of_schema: :upload
  field :timestamps,  :schema,   name: :timestamp
  field :active,      :boolean,  eq: true
end

When root is an array

By default validator root are set to :object, but this can be customized.

In this case, only one virtual field must be defined : :_root_ and it must be of type :array

Example with :of_type :

data = ['fr', 'en', 'es']

class ItemValidator < Saphyr::Validator
  root :array

  field :_root_,  :array,  min: 2,  max: 5,  of_type: :string,  opts: {len: 2}
end

Example with :of_schema :

data = [
  { "id" => 12, "label" => "tag1" },
  { "id" => 15, "label" => "tag2" },
]

class ItemValidator < Saphyr::Validator
  root :array

  schema :tag do
    field :id,     :integer,  gt: 0
    field :label,  :string,   min: 2,  max: 30
  end

  field :_root_,  :array,  min: 2,  max: 4,  of_schema: :tag
end

Conditional fields

Sometime, we can have different fields depending on a specitic field value.

data_1 = {
  "id" => 145,
  "type" => "file",

  "name" => "Lipsum ...",    # Condtionals fields:
  "mime" => "image/png",     #   must be defined only if type == 'file'
}

data_2 = {
  "id" => 145,
  "type" => "post",

  "content" => "Lipsum ...",  # Condtionals fields:
  "author" => "Lipsum ...",   #   must be defined only if type == 'post'
}

Example of Validator with conditional fields:

class ItemValidator < Saphyr::Validator
  field :id,    :integer,  gt: 0
  field :type,  :string,   in: ['post', 'file']

  # Using a method call
  conditional :is_file? do           # Will call 'is_file?' method to check the conddtion
    field :name,  :string,  min: 2
    field :mime,  :string,  in: ['image/png', 'image/jpg']
  end

  # Using a lambda
  conditional -> { get(:type) == 'post' } do
    field :content,  :string
    field :author,   :string
  end

  private

    def is_file?                     # Must return: true | false
      get(:type) == 'file'
    end
end

Field casting

data = {
  "id"          => 1,
  "name"        => "Lipsum ...",
  "created_at"  => "2023-10-26 10:57:05.29685 +0200",   # Cast to unix timestamp
  "active"      => "Yes",                               # Cast to Boolean
}

Casting is applied before validation.

class ItemValidator < Saphyr::Validator

  # Using a method call
  cast :created_at, :created_at_cast

  # Using a lambda
  cast :active, -> (value) {
      return true if ['yes', 'y'].include? value.downcase
      return false if ['no', 'n'].include? value.downcase
      value      # Unknown value, returning it back
  }

  # -----

  field :id,          :integer,  gt: 0
  field :name,        :string,   min: 5
  field :created_at,  :integer,  gt: 0
  field :active,      :boolean

  private

    def created_at_cast(value)
      begin
        return DateTime.parse(value).to_time.to_i
      end
      value
    end
end

Documentation and HowTo

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/odelbos/saphyr.

License

The gem is available as open source under the terms of the MIT License.

Roadmap

  • Add field class
  • Add internal basic fields (integer, float, string, boolean, array)
  • Add validator class
  • Add local and global schema
  • Add validation engine
  • Add conditional field
  • Add field casting
  • Add default value to field
  • Add more internal fields (b64, b62, uuid, ipv4, ipv4, ...)

Author

@odelbos