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 ADDED
@@ -0,0 +1,10 @@
1
+ .rvmrc
2
+ coverage
3
+ rdoc/*
4
+ pkg/*
5
+ .DS_Store
6
+ Gemfile.lock
7
+ .yardoc
8
+ doc/
9
+ tmp/
10
+ *.*~
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
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,6 @@
1
+ # encoding: utf-8
2
+ module SchemaTools
3
+ class Hash
4
+ extend SchemaTools::Modules::Hash
5
+ end
6
+ 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
@@ -0,0 +1,3 @@
1
+ module SchemaBuilder
2
+ VERSION = '0.0.1'
3
+ end
@@ -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
+
@@ -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: []