json_schema_tools 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []