json_schema_tools 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/Gemfile +2 -0
- data/README.md +73 -0
- data/Rakefile +12 -0
- data/json_schema_tools.gemspec +25 -0
- data/lib/json_schema_tools.rb +1 -0
- data/lib/schema_tools/cleaner.rb +35 -0
- data/lib/schema_tools/hash.rb +6 -0
- data/lib/schema_tools/modules/hash.rb +98 -0
- data/lib/schema_tools/modules/read.rb +58 -0
- data/lib/schema_tools/reader.rb +10 -0
- data/lib/schema_tools/version.rb +3 -0
- data/lib/schema_tools.rb +20 -0
- data/spec/fixtures/address.json +85 -0
- data/spec/fixtures/client.json +317 -0
- data/spec/fixtures/page.json +73 -0
- data/spec/schema_tools/hash_spec.rb +25 -0
- data/spec/schema_tools/reader_spec.rb +44 -0
- data/spec/spec_helper.rb +24 -0
- metadata +152 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# JSON Schema Tools
|
2
|
+
|
3
|
+
{<img src="https://secure.travis-ci.org/salesking/json_schema_tools.png?branch=master" alt="Build Status" />}[http://travis-ci.org/salesking/json_schema_tools]
|
4
|
+
|
5
|
+
Set of tools to help working with JSON Schemata:
|
6
|
+
|
7
|
+
* read schema files into a ruby hash
|
8
|
+
* add schema properties to a class
|
9
|
+
* convert a model(class instance) into it's schema markup
|
10
|
+
* clean parameters according to a schema (e.g. in an api controller)
|
11
|
+
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Hook the gem into your app
|
16
|
+
|
17
|
+
gem 'json_schema_tools'
|
18
|
+
|
19
|
+
### Read Schema
|
20
|
+
|
21
|
+
Before the fun begins with any of the tools one or multiple JSON schema files
|
22
|
+
must be read(into a hash). So first provide a base path where the .json files
|
23
|
+
can be found:
|
24
|
+
|
25
|
+
SchemaTools.schema_path = '/path/to/schema-json-files'
|
26
|
+
|
27
|
+
No you can read a single or multiple schemas:
|
28
|
+
|
29
|
+
schema = SchemaTools::Reader.read :client
|
30
|
+
# read all *.json files in schema path
|
31
|
+
schemata = SchemaTools::Reader.read_all
|
32
|
+
# see schema cached in registry
|
33
|
+
SchemaTools::Reader.registry[:client]
|
34
|
+
|
35
|
+
Read files from another path?
|
36
|
+
|
37
|
+
schema = SchemaTools::Reader.read :client, 'my/path/to/json-files'
|
38
|
+
schemata = SchemaTools::Reader.read_all 'my/path/to/json-files'
|
39
|
+
|
40
|
+
Don't like the global path and registry? Go local:
|
41
|
+
|
42
|
+
reader = SchemaTools::Reader.new
|
43
|
+
reader.read :client, 'from/path'
|
44
|
+
reader.registry
|
45
|
+
|
46
|
+
|
47
|
+
## Object to schema markup
|
48
|
+
|
49
|
+
A schema provides a (public) contract about an object definition. It is
|
50
|
+
therefore a common task to convert an internal object to its schema version.
|
51
|
+
Before the object can be represented as a json string, it is converted to a
|
52
|
+
hash containing only the properties(keys) from its schema definition:
|
53
|
+
|
54
|
+
Following uses client.json schema(same as client.class name) from global
|
55
|
+
schema_path and adds properties to the clients_hash simply calling
|
56
|
+
client.send('property-name'):
|
57
|
+
|
58
|
+
peter = Client.new name: 'Peter'
|
59
|
+
client_hash = SchemaTools::Hash.from_schema(peter)
|
60
|
+
# to_json is up to you .. or your rails controller
|
61
|
+
|
62
|
+
TODO: explain Options for parsing:
|
63
|
+
* custom class name
|
64
|
+
* fields include/exclude
|
65
|
+
* custom schema name
|
66
|
+
|
67
|
+
## Test
|
68
|
+
|
69
|
+
bundle install
|
70
|
+
bundle exec rake spec
|
71
|
+
|
72
|
+
|
73
|
+
Copyright 2012-1013, Georg Leciejewski, MIT License
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
desc 'Default: run specs.'
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
desc "Run specs"
|
9
|
+
RSpec::Core::RakeTask.new do |t|
|
10
|
+
t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
|
11
|
+
# Put spec opts in a file named .rspec in root
|
12
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'schema_tools/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'json_schema_tools'
|
7
|
+
s.version = SchemaBuilder::VERSION
|
8
|
+
s.authors = ['Georg Leciejewski']
|
9
|
+
s.email = ['gl@salesking.eu']
|
10
|
+
s.homepage = 'http://www.salesking.eu/dev'
|
11
|
+
s.summary = %q{JSON Schema API tools.}
|
12
|
+
s.description = %q{Want to create a JSON Schema powered API? This toolset provides methods to read schemata, render objects as defined in schema, clean parameters according to schema, ...}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_dependency 'json'
|
20
|
+
s.add_dependency 'activesupport'
|
21
|
+
|
22
|
+
s.add_development_dependency 'rake'
|
23
|
+
s.add_development_dependency 'simplecov'
|
24
|
+
s.add_development_dependency 'rspec'
|
25
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'schema_tools'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module SchemaTools
|
4
|
+
class Cleaner
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
# Clean a hash before a new object is created from it. Can be used in
|
9
|
+
# your ruby controllers where new objects are created from a params-hash
|
10
|
+
# Directly CHANGES incoming params-hash!
|
11
|
+
#
|
12
|
+
# @param [String|Symbol] obj_name of the object/schema
|
13
|
+
# @param [Hash{String|Symbol=>Mixed}] params properties for the object
|
14
|
+
# @param [Object] opts
|
15
|
+
# @options opts [Array<String|Symbol>] :keep properties not being kicked
|
16
|
+
# even if defined as readonly
|
17
|
+
def clean_params!(obj_name, params, opts={})
|
18
|
+
schema = SchemaTools::Reader.read(obj_name)
|
19
|
+
setters = []
|
20
|
+
# gather allowed properties
|
21
|
+
schema[:properties].each{ |k,v| setters << k if !v['readonly'] }
|
22
|
+
setters += opts[:keep] if opts[:keep] && opts[:keep].is_a?(Array)
|
23
|
+
# kick readonly
|
24
|
+
params.delete_if { |k,v| !setters.include?("#{k}") }
|
25
|
+
#convert to type in schema
|
26
|
+
params.each do |k,v|
|
27
|
+
if schema[:properties]["#{k}"]['type'] == 'string' && !v.is_a?(String)
|
28
|
+
params[k] = "#{v}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
4
|
+
require 'active_support/core_ext/string/inflections'
|
5
|
+
|
6
|
+
module SchemaTools
|
7
|
+
module Modules
|
8
|
+
module Hash
|
9
|
+
|
10
|
+
# Create a Hash with the available (api)object attributes defined in the
|
11
|
+
# according schema properties. This is the meat of the
|
12
|
+
# object-to-api-markup workflow
|
13
|
+
#
|
14
|
+
# === Example
|
15
|
+
# obj = Invoice.new(:title =>'hello world', :number=>'4711')
|
16
|
+
#
|
17
|
+
# obj_hash = SchemaTools::Hash.from_schema(obj, 'v1.0')
|
18
|
+
# => { 'invoice' =>{'title'=>'hello world', 'number'=>'4711' } }
|
19
|
+
#
|
20
|
+
# obj_hash = Schema.to_hash_from_schema(obj, 'v1.0', :fields=>['title'])
|
21
|
+
# => { 'invoice' =>{'title'=>'hello world' } }
|
22
|
+
#
|
23
|
+
# obj_hash = Schema.to_hash_from_schema(obj, 'v1.0', :class_name=>:document)
|
24
|
+
# => { 'document' =>{'title'=>'hello world' } }
|
25
|
+
#
|
26
|
+
# === Parameter
|
27
|
+
# obj<Object>:: An ruby object which is returned as hash
|
28
|
+
# version<String>:: the schema version, must be a valid folder name see
|
29
|
+
# #self.read
|
30
|
+
# opts<Hash{Symbol=>Mixed} >:: additional options
|
31
|
+
#
|
32
|
+
# ==== opts Parameter
|
33
|
+
# class_name<String|Symbol>:: Name of the class to use as hash key. Should be
|
34
|
+
# a lowered, underscored name and it MUST have an existing schema file.
|
35
|
+
# Use it to override the default, which is obj.class.name
|
36
|
+
# fields<Array[String]>:: Fields/properties to return. If not set all
|
37
|
+
# schema's properties are used.
|
38
|
+
#
|
39
|
+
# === Return
|
40
|
+
# <Hash{String=>{String=>Mixed}}>:: The object as hash:
|
41
|
+
# { invoice =>{'title'=>'hello world', 'number'=>'4711' } }
|
42
|
+
# @param [Object] obj
|
43
|
+
# @param [Object] opts
|
44
|
+
def from_schema(obj, opts={})
|
45
|
+
fields = opts[:fields]
|
46
|
+
# get objects class name without inheritance
|
47
|
+
real_class_name = obj.class.name.split('::').last.underscore
|
48
|
+
class_name = opts[:class_name] || real_class_name
|
49
|
+
|
50
|
+
return obj if ['array', 'hash'].include? class_name
|
51
|
+
|
52
|
+
data = {}
|
53
|
+
# get schema
|
54
|
+
schema = SchemaTools::Reader.read(class_name)
|
55
|
+
# iterate over the defined schema fields
|
56
|
+
schema['properties'].each do |field, prop|
|
57
|
+
next if fields && !fields.include?(field)
|
58
|
+
if prop['type'] == 'array'
|
59
|
+
data[field] = [] # always set an empty array
|
60
|
+
if rel_objects = obj.send( field )
|
61
|
+
rel_objects.each do |rel_obj|
|
62
|
+
data[field] << to_hash_from_schema(rel_obj, version)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
elsif prop['type'] == 'object' # a singular related object
|
66
|
+
data[field] = nil # always set empty val
|
67
|
+
if rel_obj = obj.send( field )
|
68
|
+
#dont nest field to prevent => client=>{client=>{data} }
|
69
|
+
data[field] = to_hash_from_schema(rel_obj, version)
|
70
|
+
end
|
71
|
+
else # a simple field is only added if the object knows it
|
72
|
+
data[field] = obj.send(field) if obj.respond_to?(field)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
hsh = { "#{class_name}" => data }
|
76
|
+
#add links if present
|
77
|
+
links = parse_links(obj, schema)
|
78
|
+
links && hsh['links'] = links
|
79
|
+
hsh
|
80
|
+
end
|
81
|
+
|
82
|
+
# Parse the link section of the schema by replacing {id} in urls
|
83
|
+
# === Returns
|
84
|
+
# <Array[Hash{String=>String}]>::
|
85
|
+
# <nil>:: no links present
|
86
|
+
def parse_links(obj, schema)
|
87
|
+
links = []
|
88
|
+
schema['links'] && schema['links'].each do |link|
|
89
|
+
links << { 'rel' => link['rel'], 'href' => link['href'].gsub(/\{id\}/, "#{obj.id}") }
|
90
|
+
end
|
91
|
+
links.uniq
|
92
|
+
# return links only if not empty
|
93
|
+
links.empty? ? nil : links
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
4
|
+
require 'active_support/core_ext/string/inflections'
|
5
|
+
|
6
|
+
module SchemaTools
|
7
|
+
module Modules
|
8
|
+
# Read schemas into a hash
|
9
|
+
module Read
|
10
|
+
|
11
|
+
# Variable remembering already read-in schema's
|
12
|
+
# {
|
13
|
+
# :invoice =>{schema}
|
14
|
+
# :credit_note =>{schema}
|
15
|
+
# }
|
16
|
+
# }
|
17
|
+
# @return [Hash{String=>Hash{Symbol=>HashWithIndifferentAccess}}]
|
18
|
+
def registry
|
19
|
+
@registry ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def registry_reset
|
23
|
+
@registry = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# Read a schema and return it as hash. You can supply a path or the
|
27
|
+
# global path defined in #SchemaTools.schema_path is used.
|
28
|
+
# Schemata returned from cache in #registry to prevent filesystem
|
29
|
+
# round-trips. The cache can be resented with #registry_reset
|
30
|
+
#
|
31
|
+
# @param [String|Symbol] schema name to be read from schema path directory
|
32
|
+
# @param [String] path
|
33
|
+
# @return[HashWithIndifferentAccess] schema as hash
|
34
|
+
def read(schema, path=nil)
|
35
|
+
schema = schema.to_sym
|
36
|
+
return registry[schema] if registry[schema]
|
37
|
+
file_path = File.join(path || SchemaTools.schema_path, "#{schema}.json")
|
38
|
+
plain_data = File.open(file_path, 'r'){|f| f.read}
|
39
|
+
registry[schema] = ActiveSupport::JSON.decode(plain_data).with_indifferent_access
|
40
|
+
end
|
41
|
+
|
42
|
+
# Read all available schemas from a given path(folder) and return
|
43
|
+
# them as array
|
44
|
+
#
|
45
|
+
# @param [String] path to schema files
|
46
|
+
# @return [Array<HashWithIndifferentAccess>] array of schemas as hash
|
47
|
+
def read_all(path=nil)
|
48
|
+
schemas = []
|
49
|
+
file_path = File.join(path || SchemaTools.schema_path, '*.json')
|
50
|
+
Dir.glob( file_path ).each do |file|
|
51
|
+
schema_name = File.basename(file, '.json').to_sym
|
52
|
+
schemas << read(schema_name, path)
|
53
|
+
end
|
54
|
+
schemas
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module SchemaTools
|
3
|
+
# Read schemas into a hash.
|
4
|
+
# Use as instance if you have multiple different schema sources/versions
|
5
|
+
# which may collide. Go for class methods if you have globally unique schemata
|
6
|
+
class Reader
|
7
|
+
include SchemaTools::Modules::Read
|
8
|
+
extend SchemaTools::Modules::Read
|
9
|
+
end
|
10
|
+
end
|
data/lib/schema_tools.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'schema_tools/version'
|
3
|
+
require 'schema_tools/modules/read'
|
4
|
+
require 'schema_tools/modules/hash'
|
5
|
+
require 'schema_tools/reader'
|
6
|
+
require 'schema_tools/hash'
|
7
|
+
|
8
|
+
|
9
|
+
module SchemaTools
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# @param [String] path to schema json files
|
13
|
+
def schema_path=(path)
|
14
|
+
@schema_path = path
|
15
|
+
end
|
16
|
+
def schema_path
|
17
|
+
@schema_path
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
{ "type":"object",
|
2
|
+
"title": "address",
|
3
|
+
"name": "address",
|
4
|
+
"description":"An address in SK is maintained within it's parent object(client, company). The first address(default_address) is used inside the address field of new documents. With multiple addresses sorting(first) is done by date, newest first. So if you add a new adddress it will be the new default one. See order and type property for details about ordering and accessing parcel work or home addresses.",
|
5
|
+
"properties":{
|
6
|
+
"id":{
|
7
|
+
"description":"Unique identifier - UUID",
|
8
|
+
"identity":true,
|
9
|
+
"readonly":true,
|
10
|
+
"type":"string",
|
11
|
+
"maxLength": 22,
|
12
|
+
"minLength":22
|
13
|
+
},
|
14
|
+
"city":{
|
15
|
+
"description": "City for the address. Must at least be present for an address.",
|
16
|
+
"required":true,
|
17
|
+
"type":"string",
|
18
|
+
"maxLength": 100
|
19
|
+
},
|
20
|
+
"address1":{
|
21
|
+
"description": "Should contain the street or otherwise primary address information.",
|
22
|
+
"type":"string",
|
23
|
+
"maxLength": 100
|
24
|
+
},
|
25
|
+
"address2":{
|
26
|
+
"description": "Additional address information, like floor number",
|
27
|
+
"type":"string",
|
28
|
+
"maxLength": 100
|
29
|
+
},
|
30
|
+
"pobox":{
|
31
|
+
"description": "Post office box number",
|
32
|
+
"type":"string",
|
33
|
+
"maxLength": 10
|
34
|
+
},
|
35
|
+
"zip":{
|
36
|
+
"description": "Zip number of the city. Length must be between 4..10",
|
37
|
+
"type":"string",
|
38
|
+
"maxLength": 10
|
39
|
+
},
|
40
|
+
"state":{
|
41
|
+
"description": "Country state of address",
|
42
|
+
"type":"string",
|
43
|
+
"maxLength": 100
|
44
|
+
},
|
45
|
+
"country":{
|
46
|
+
"description": "Country of the address.",
|
47
|
+
"type":"string",
|
48
|
+
"maxLength": 100
|
49
|
+
},
|
50
|
+
"created_at":{
|
51
|
+
"description": "Date the object was created in SK. Never changes afterwards",
|
52
|
+
"format":"date-time",
|
53
|
+
"readonly":true,
|
54
|
+
"type":"string"
|
55
|
+
},
|
56
|
+
"updated_at":{
|
57
|
+
"description": "Date the object was edited in SK.",
|
58
|
+
"format":"date-time",
|
59
|
+
"readonly":true,
|
60
|
+
"type":"string"
|
61
|
+
},
|
62
|
+
"address_type":{
|
63
|
+
"description": "Type of the address, as seen by vCard definition. There can only be one type. Inside of SK you can use placeholders like client.parcel_address.city to access the first parcel adr(Same for work_ / home_). <br/>Besides the placeholder default_address, always returns the first address found. Sorting is done by the order(if set) else by date, newest first.",
|
64
|
+
"enum":["work","home", "parcel"],
|
65
|
+
"type":"string"
|
66
|
+
},
|
67
|
+
"order":{
|
68
|
+
"description": "Addresses are normally sorted by date, newest first. If you need to strongly rely on their sorting e.g. default_address (always first) or have multiple addresses of one type(3 parcel), use this field for all adrs. <br/>Single adrs are used in placeholders like: addresses.2.city, returning the second adr found(not necessarily one with order=2).",
|
69
|
+
"type":"integer"
|
70
|
+
},
|
71
|
+
"lat":{
|
72
|
+
"description": "Geo-Location latitude",
|
73
|
+
"type":"number"
|
74
|
+
},
|
75
|
+
"long":{
|
76
|
+
"description": "Geo-Location longitude",
|
77
|
+
"type":"number"
|
78
|
+
},
|
79
|
+
"_destroy":{
|
80
|
+
"description": "When set an existing address will be deleted. This switch is only used when addresses are passed-in nested inside their parent object(a contact).",
|
81
|
+
"type":"boolean"
|
82
|
+
}
|
83
|
+
},
|
84
|
+
"links":[]
|
85
|
+
}
|
@@ -0,0 +1,317 @@
|
|
1
|
+
{"type":"object",
|
2
|
+
"title": "client",
|
3
|
+
"name": "client",
|
4
|
+
"description": "A client as seen by SalesKing",
|
5
|
+
"properties":{
|
6
|
+
"id":{
|
7
|
+
"description":"Unique identifier - UUID",
|
8
|
+
"identity":true,
|
9
|
+
"readonly":true,
|
10
|
+
"type":"string",
|
11
|
+
"maxLength": 22,
|
12
|
+
"minLength":22
|
13
|
+
},
|
14
|
+
"number":{
|
15
|
+
"description": "Unique number, auto-created by SK for new client without number.",
|
16
|
+
"type":"string",
|
17
|
+
"maxLength": 50
|
18
|
+
},
|
19
|
+
"organisation":{
|
20
|
+
"description": "Name of a company. This or lastname must be present",
|
21
|
+
"required" : true,
|
22
|
+
"type":"string",
|
23
|
+
"maxLength": 100
|
24
|
+
},
|
25
|
+
"last_name":{
|
26
|
+
"description": "Last name of a person. At least this or the organisation field must be filled for new records",
|
27
|
+
"type":"string",
|
28
|
+
"maxLength": 50
|
29
|
+
},
|
30
|
+
"first_name":{
|
31
|
+
"description": "First name of a person.",
|
32
|
+
"type":"string",
|
33
|
+
"maxLength": 50
|
34
|
+
},
|
35
|
+
"gender":{
|
36
|
+
"description": "Can be empty for a company. Is used in salutation",
|
37
|
+
"enum":["male", "female"],
|
38
|
+
"type":"string"
|
39
|
+
},
|
40
|
+
"notes":{
|
41
|
+
"description": "Notes for a contact. For day to day information you should use comments instead.",
|
42
|
+
"type":"string",
|
43
|
+
"format": "text"
|
44
|
+
},
|
45
|
+
"position":{
|
46
|
+
"description": "Position of a person in a company.",
|
47
|
+
"type":"string",
|
48
|
+
"maxLength": 50
|
49
|
+
},
|
50
|
+
"title":{
|
51
|
+
"description": "Academical title of a person e.g. Dr., Prof",
|
52
|
+
"type":"string",
|
53
|
+
"maxLength": 50
|
54
|
+
},
|
55
|
+
"tax_number":{
|
56
|
+
"description": "Tax number, normally applies to a private person",
|
57
|
+
"type":"string",
|
58
|
+
"maxLength": 30
|
59
|
+
},
|
60
|
+
"vat_number":{
|
61
|
+
"description": "VAT number, for a company or person paying value added taxes.",
|
62
|
+
"type":"string",
|
63
|
+
"maxLength": 30
|
64
|
+
},
|
65
|
+
"email":{
|
66
|
+
"description": "Email address of the contact.",
|
67
|
+
"type":"string",
|
68
|
+
"maxLength": 100
|
69
|
+
},
|
70
|
+
"url":{
|
71
|
+
"description": "An url associated with the person, e.g its company website.",
|
72
|
+
"type":"string",
|
73
|
+
"maxLength": 255
|
74
|
+
},
|
75
|
+
"birthday":{
|
76
|
+
"format":"date",
|
77
|
+
"type":"string"
|
78
|
+
},
|
79
|
+
"tag_list":{
|
80
|
+
"description": "Space separated list of tags. Are split and saved as Tag objects on create, update.",
|
81
|
+
"type":"string"
|
82
|
+
},
|
83
|
+
"created_at":{
|
84
|
+
"description": "Date the record was created in SK. Never changes afterwards.",
|
85
|
+
"format":"date-time",
|
86
|
+
"readonly":true,
|
87
|
+
"type":"string"
|
88
|
+
},
|
89
|
+
"updated_at":{
|
90
|
+
"description": "Last date when the record was edited.",
|
91
|
+
"format":"date-time",
|
92
|
+
"readonly":true,
|
93
|
+
"type":"string"
|
94
|
+
},
|
95
|
+
"language":{
|
96
|
+
"description": "Should be a valid language short-code: de-DE, fr, en-GB; like defined in your account language menu. When the client is emailed, a localized version of a multi-language template(email, pdf) will be used if available. The language will also be set for new documents.",
|
97
|
+
"type":"string",
|
98
|
+
"maxLength": 10
|
99
|
+
},
|
100
|
+
"currency":{
|
101
|
+
"description": "Currency code as defined by the ISO 4217 standard(3-letter UPCASE: EUR, USD). If set the currency is taken for new documents.",
|
102
|
+
"type":"string",
|
103
|
+
"maxLength": 3,
|
104
|
+
"minLength": 3
|
105
|
+
},
|
106
|
+
"payment_method":{
|
107
|
+
"description": "Default payment method for used for new documemts",
|
108
|
+
"enum":["cash","bank_transfer","credit_card","paypal","direct_debit","cheque", "moneybookers", "premium_sms"],
|
109
|
+
"type":"string"
|
110
|
+
},
|
111
|
+
"bank_name":{
|
112
|
+
"description": "Bank name",
|
113
|
+
"type":"string",
|
114
|
+
"maxLength": 70
|
115
|
+
},
|
116
|
+
"bank_number":{
|
117
|
+
"description": "Bank number",
|
118
|
+
"type":"string",
|
119
|
+
"maxLength": 35
|
120
|
+
},
|
121
|
+
"bank_account_number":{
|
122
|
+
"description": "Bank account number.",
|
123
|
+
"type":"string",
|
124
|
+
"maxLength": 35
|
125
|
+
},
|
126
|
+
"bank_iban":{
|
127
|
+
"description": "IBAN Number of the bank account. Is validated",
|
128
|
+
"type":"string",
|
129
|
+
"maxLength": 35
|
130
|
+
},
|
131
|
+
"bank_swift":{
|
132
|
+
"description": "SWIFT BIC- Bank Identifier Code",
|
133
|
+
"type":"string",
|
134
|
+
"maxLength": 11
|
135
|
+
},
|
136
|
+
"bank_owner":{
|
137
|
+
"description": "Bank account owner",
|
138
|
+
"type":"string",
|
139
|
+
"maxLength": 70
|
140
|
+
},
|
141
|
+
"phone_fax":{
|
142
|
+
"description": "Fax number",
|
143
|
+
"type":"string",
|
144
|
+
"maxLength": 30
|
145
|
+
},
|
146
|
+
"phone_office":{
|
147
|
+
"description": "Office phone number",
|
148
|
+
"type":"string",
|
149
|
+
"maxLength": 30
|
150
|
+
},
|
151
|
+
"phone_home":{
|
152
|
+
"description": "Private phone number",
|
153
|
+
"type":"string",
|
154
|
+
"maxLength": 30
|
155
|
+
},
|
156
|
+
"phone_mobile":{
|
157
|
+
"description": "Mobile phone number",
|
158
|
+
"type":"string",
|
159
|
+
"maxLength": 30
|
160
|
+
},
|
161
|
+
"lock_version":{
|
162
|
+
"description": "Increased on every edit, so SK can detect/prevent a concurrent edit by another user. First save wins.",
|
163
|
+
"type":"integer"
|
164
|
+
},
|
165
|
+
"cash_discount":{
|
166
|
+
"description": "Default cash discount for new invoices.",
|
167
|
+
"type":"number"
|
168
|
+
},
|
169
|
+
"due_days":{
|
170
|
+
"description": "Default due days for new invoices.",
|
171
|
+
"type":"integer"
|
172
|
+
},
|
173
|
+
"address_field":{
|
174
|
+
"description": "Returns the address field used on new docs. Consist of Organisation name and default(first) address",
|
175
|
+
"readonly":true,
|
176
|
+
"type":"string"
|
177
|
+
},
|
178
|
+
"addresses":{
|
179
|
+
"description": "A client can have many addresses, sorted by date descending(new first). Default address is the most recent one.",
|
180
|
+
"type":"array",
|
181
|
+
"properties" : {"$ref":"./address.json#properties"}
|
182
|
+
},
|
183
|
+
"team_id":{
|
184
|
+
"description": "A team uuid. If set only the team and its parent teams can see the record.",
|
185
|
+
"type":"string",
|
186
|
+
"maxLength": 22,
|
187
|
+
"minLength":22
|
188
|
+
}
|
189
|
+
},
|
190
|
+
"links":[
|
191
|
+
{ "rel": "self",
|
192
|
+
"href": "clients/{id}"
|
193
|
+
},
|
194
|
+
{ "rel": "instances",
|
195
|
+
"href": "clients",
|
196
|
+
"properties" : {
|
197
|
+
"page":{
|
198
|
+
"title" : "Page",
|
199
|
+
"description": "In paginated results set the page to look for",
|
200
|
+
"type":"number"
|
201
|
+
},
|
202
|
+
"per_page":{
|
203
|
+
"title" : "Per page",
|
204
|
+
"description": "Results per page. Default is 10, max is 100",
|
205
|
+
"type":"number"
|
206
|
+
},
|
207
|
+
"filter[q]":{
|
208
|
+
"title" : "Search",
|
209
|
+
"description": "Wildcard search in first, last_name, organisation, email, number",
|
210
|
+
"type":"string"
|
211
|
+
},
|
212
|
+
"filter[tags]":{
|
213
|
+
"title" : "Tags",
|
214
|
+
"description": "Filter by a space delimited list of tags",
|
215
|
+
"type":"string"
|
216
|
+
},
|
217
|
+
"filter[ids]":{
|
218
|
+
"title" : "Clients",
|
219
|
+
"description": "A single or a list of client uuids, comma separated",
|
220
|
+
"type" : "string"
|
221
|
+
},
|
222
|
+
"filter[created_at_from]":{
|
223
|
+
"title" : "From date",
|
224
|
+
"description": "Objects with a creation date after the date, including given datetime. ISO 8601 format YYY-MM-DDThh:mm:ss+z",
|
225
|
+
"format" : "date-time",
|
226
|
+
"type" : "string"
|
227
|
+
},
|
228
|
+
"filter[created_at_to]":{
|
229
|
+
"title" : "To date",
|
230
|
+
"description": "Objects with a creation date before the date, including given datetime. ISO 8601 format YYY-MM-DDThh:mm:ss+z",
|
231
|
+
"format" : "date-time",
|
232
|
+
"type" : "string"
|
233
|
+
},
|
234
|
+
"filter[birthday_from]":{
|
235
|
+
"title" : "From birthday date",
|
236
|
+
"description": "Contacts with a birthday after and on the date. Leave the birthday-to date blank to only search on this day.",
|
237
|
+
"format" : "date",
|
238
|
+
"type" : "string"
|
239
|
+
},
|
240
|
+
"filter[birthday_to]":{
|
241
|
+
"title" : "To birthday date",
|
242
|
+
"description": "Contacts with a birthday date before and on the date.",
|
243
|
+
"format" : "date",
|
244
|
+
"type" : "string"
|
245
|
+
},
|
246
|
+
"filter[creator_ids]":{
|
247
|
+
"title" : "Creator",
|
248
|
+
"description": "Objects created by the given users uuids, comma separated",
|
249
|
+
"type" : "string"
|
250
|
+
},
|
251
|
+
"filter[number]":{
|
252
|
+
"title" : "By number",
|
253
|
+
"description": "Search by number where the number is matched from the start: number%",
|
254
|
+
"type" : "string"
|
255
|
+
},
|
256
|
+
"filter[languages]":{
|
257
|
+
"title" : "Languages",
|
258
|
+
"description": "A single or a list of language codes, comma separated",
|
259
|
+
"type" : "string"
|
260
|
+
},
|
261
|
+
"sort_by":{
|
262
|
+
"title" : "Sort by",
|
263
|
+
"description": "Sort the results by the given field => number",
|
264
|
+
"enum":["organisation", "number","email","first_name","last_name", "created_at", "updated_at"],
|
265
|
+
"type": "string"
|
266
|
+
},
|
267
|
+
"sort":{
|
268
|
+
"title" : "Sort",
|
269
|
+
"enum":["ASC","DESC"],
|
270
|
+
"description": "Sort the results in ASC or DESC"
|
271
|
+
}
|
272
|
+
}
|
273
|
+
},
|
274
|
+
{ "rel": "destroy",
|
275
|
+
"href": "clients/{id}",
|
276
|
+
"method": "DELETE"
|
277
|
+
},
|
278
|
+
{ "rel": "update",
|
279
|
+
"href": "clients/{id}",
|
280
|
+
"method": "PUT"
|
281
|
+
},
|
282
|
+
{ "rel": "create",
|
283
|
+
"href": "clients",
|
284
|
+
"method": "POST"
|
285
|
+
},
|
286
|
+
{ "rel": "documents",
|
287
|
+
"href": "clients/{id}/documents"
|
288
|
+
},
|
289
|
+
{ "rel": "attachments",
|
290
|
+
"href": "clients/{id}/attachments"
|
291
|
+
},
|
292
|
+
{ "rel": "invoices",
|
293
|
+
"href": "clients/{id}/invoices"
|
294
|
+
},
|
295
|
+
{ "rel": "estimates",
|
296
|
+
"href": "clients/{id}/estimates"
|
297
|
+
},
|
298
|
+
{ "rel": "orders",
|
299
|
+
"href": "clients/{id}/orders"
|
300
|
+
},
|
301
|
+
{ "rel": "credit_notes",
|
302
|
+
"href": "clients/{id}/credit_notes"
|
303
|
+
},
|
304
|
+
{ "rel": "recurrings",
|
305
|
+
"href": "clients/{id}/recurrings"
|
306
|
+
},
|
307
|
+
{ "rel": "payment_reminders",
|
308
|
+
"href": "clients/{id}/payment_reminders"
|
309
|
+
},
|
310
|
+
{ "rel": "comments",
|
311
|
+
"href": "clients/{id}/comments"
|
312
|
+
},
|
313
|
+
{ "rel": "emails",
|
314
|
+
"href": "clients/{id}/emails"
|
315
|
+
}
|
316
|
+
]
|
317
|
+
}
|
@@ -0,0 +1,73 @@
|
|
1
|
+
{
|
2
|
+
"type": "object",
|
3
|
+
"title": "Page",
|
4
|
+
"name": "page",
|
5
|
+
"description": "A page of a PDF which can either belong to a PDF template or archived PDF.",
|
6
|
+
"properties": {
|
7
|
+
"id": {
|
8
|
+
"description": "The object identifier.",
|
9
|
+
"identity": true,
|
10
|
+
"readonly": true,
|
11
|
+
"type": "integer"
|
12
|
+
},
|
13
|
+
"number": {
|
14
|
+
"description": "Number of this page inside the document",
|
15
|
+
"readonly": true,
|
16
|
+
"type": "integer"
|
17
|
+
},
|
18
|
+
"related_object_id": {
|
19
|
+
"description": "ID of the PDF object the page belongs to.",
|
20
|
+
"readonly": true,
|
21
|
+
"type": "integer"
|
22
|
+
},
|
23
|
+
"related_object_type": {
|
24
|
+
"description": "Type of the related object Pdf or Pdt (camelCased class name)",
|
25
|
+
"readonly": true,
|
26
|
+
"type": "string",
|
27
|
+
"maxlength": 20
|
28
|
+
},
|
29
|
+
"width": {
|
30
|
+
"description": "Page width",
|
31
|
+
"type": "number"
|
32
|
+
},
|
33
|
+
"created_at": {
|
34
|
+
"description": "Creation date.",
|
35
|
+
"readonly": true,
|
36
|
+
"type": "string",
|
37
|
+
"format": "date-time"
|
38
|
+
},
|
39
|
+
"updated_at": {
|
40
|
+
"description": "Update date.",
|
41
|
+
"readonly": true,
|
42
|
+
"type": "string",
|
43
|
+
"format": "date-time"
|
44
|
+
},
|
45
|
+
"user_id": {
|
46
|
+
"description": "User who created the object.",
|
47
|
+
"readonly": true,
|
48
|
+
"type": "integer"
|
49
|
+
},
|
50
|
+
"screenshot": {
|
51
|
+
"description": "Screenshot file url png",
|
52
|
+
"type": "string",
|
53
|
+
"maxlength": 128
|
54
|
+
},
|
55
|
+
"account_id": {
|
56
|
+
"description": "Account this objects belongs to.",
|
57
|
+
"type": "integer"
|
58
|
+
},
|
59
|
+
"blocks": {
|
60
|
+
"description": "Placeholder blocks on this page,, if it belongs to a PDF Template",
|
61
|
+
"type": "array",
|
62
|
+
"properties": {"$ref":"./block.json#properties"}
|
63
|
+
}
|
64
|
+
|
65
|
+
},
|
66
|
+
"links": [
|
67
|
+
{
|
68
|
+
"rel": "self",
|
69
|
+
"method": "GET",
|
70
|
+
"href": "/pages/{id}"
|
71
|
+
}
|
72
|
+
]
|
73
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Client
|
4
|
+
attr_accessor :first_name, :last_name, :addresses, :id
|
5
|
+
end
|
6
|
+
|
7
|
+
describe SchemaTools::Hash do
|
8
|
+
|
9
|
+
context 'from_schema' do
|
10
|
+
let(:client){Client.new}
|
11
|
+
before :each do
|
12
|
+
client.first_name = 'Peter'
|
13
|
+
client.last_name = 'Paul'
|
14
|
+
end
|
15
|
+
after :each do
|
16
|
+
SchemaTools::Reader.registry_reset
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should return hash' do
|
20
|
+
hash = SchemaTools::Hash.from_schema(client)
|
21
|
+
hash['client']['last_name'].should == 'Paul'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SchemaTools::Reader do
|
4
|
+
|
5
|
+
context 'class methods' do
|
6
|
+
after :each do
|
7
|
+
SchemaTools::Reader.registry_reset
|
8
|
+
end
|
9
|
+
it 'should read a single schema' do
|
10
|
+
schema = SchemaTools::Reader.read(:page)
|
11
|
+
schema[:name].should == 'page'
|
12
|
+
schema[:properties].should_not be_empty
|
13
|
+
SchemaTools::Reader.registry.should_not be_empty
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'instance methods' do
|
18
|
+
|
19
|
+
let(:reader){ SchemaTools::Reader.new }
|
20
|
+
|
21
|
+
it 'should read a single schema' do
|
22
|
+
schema = reader.read(:client)
|
23
|
+
schema[:name].should == 'client'
|
24
|
+
schema[:properties].should_not be_empty
|
25
|
+
reader.registry[:client].should_not be_empty
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should populate instance registry' do
|
29
|
+
reader.read(:client)
|
30
|
+
reader.read(:address)
|
31
|
+
keys = reader.registry.keys
|
32
|
+
keys.length.should == 2
|
33
|
+
keys.should include(:client, :address)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should not populate class registry' do
|
37
|
+
reader.read(:client)
|
38
|
+
SchemaTools::Reader.registry.should be_empty
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'simplecov'
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
SimpleCov.start do
|
6
|
+
root File.join(File.dirname(__FILE__), '..')
|
7
|
+
add_filter "/bin/"
|
8
|
+
add_filter "/spec/"
|
9
|
+
end
|
10
|
+
|
11
|
+
$:.unshift(File.dirname(__FILE__))
|
12
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
13
|
+
require 'json_schema_tools'
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
#config.treat_symbols_as_metadata_keys_with_true_values = true
|
17
|
+
#config.run_all_when_everything_filtered = true
|
18
|
+
#config.filter_run :focus
|
19
|
+
#config.after(:all) do
|
20
|
+
#end
|
21
|
+
end
|
22
|
+
|
23
|
+
# set global json schema path for examples
|
24
|
+
SchemaTools.schema_path = File.expand_path('../fixtures', __FILE__)
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: json_schema_tools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Georg Leciejewski
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activesupport
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: simplecov
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: Want to create a JSON Schema powered API? This toolset provides methods
|
95
|
+
to read schemata, render objects as defined in schema, clean parameters according
|
96
|
+
to schema, ...
|
97
|
+
email:
|
98
|
+
- gl@salesking.eu
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- .gitignore
|
104
|
+
- Gemfile
|
105
|
+
- README.md
|
106
|
+
- Rakefile
|
107
|
+
- json_schema_tools.gemspec
|
108
|
+
- lib/json_schema_tools.rb
|
109
|
+
- lib/schema_tools.rb
|
110
|
+
- lib/schema_tools/cleaner.rb
|
111
|
+
- lib/schema_tools/hash.rb
|
112
|
+
- lib/schema_tools/modules/hash.rb
|
113
|
+
- lib/schema_tools/modules/read.rb
|
114
|
+
- lib/schema_tools/reader.rb
|
115
|
+
- lib/schema_tools/version.rb
|
116
|
+
- spec/fixtures/address.json
|
117
|
+
- spec/fixtures/client.json
|
118
|
+
- spec/fixtures/page.json
|
119
|
+
- spec/schema_tools/hash_spec.rb
|
120
|
+
- spec/schema_tools/reader_spec.rb
|
121
|
+
- spec/spec_helper.rb
|
122
|
+
homepage: http://www.salesking.eu/dev
|
123
|
+
licenses: []
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
segments:
|
135
|
+
- 0
|
136
|
+
hash: 3313090340920384455
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
segments:
|
144
|
+
- 0
|
145
|
+
hash: 3313090340920384455
|
146
|
+
requirements: []
|
147
|
+
rubyforge_project:
|
148
|
+
rubygems_version: 1.8.24
|
149
|
+
signing_key:
|
150
|
+
specification_version: 3
|
151
|
+
summary: JSON Schema API tools.
|
152
|
+
test_files: []
|