crystal_api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +7 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +50 -0
  8. data/Rakefile +1 -0
  9. data/crystal_api.gemspec +34 -0
  10. data/lib/crystal_api.rb +51 -0
  11. data/lib/crystal_api/attributes.rb +210 -0
  12. data/lib/crystal_api/category.rb +28 -0
  13. data/lib/crystal_api/descriptor.rb +11 -0
  14. data/lib/crystal_api/error_response.rb +17 -0
  15. data/lib/crystal_api/errors.rb +8 -0
  16. data/lib/crystal_api/hmac_request_signing.rb +40 -0
  17. data/lib/crystal_api/market_prices.rb +11 -0
  18. data/lib/crystal_api/message_verifier.rb +26 -0
  19. data/lib/crystal_api/money.rb +8 -0
  20. data/lib/crystal_api/paginated_collection.rb +13 -0
  21. data/lib/crystal_api/photo.rb +10 -0
  22. data/lib/crystal_api/product.rb +55 -0
  23. data/lib/crystal_api/product_descriptor.rb +10 -0
  24. data/lib/crystal_api/received_webhook_parser.rb +17 -0
  25. data/lib/crystal_api/report.rb +17 -0
  26. data/lib/crystal_api/store.rb +10 -0
  27. data/lib/crystal_api/store_endpoint.rb +82 -0
  28. data/lib/crystal_api/store_prefs.rb +54 -0
  29. data/lib/crystal_api/url.rb +9 -0
  30. data/lib/crystal_api/variant.rb +28 -0
  31. data/lib/crystal_api/variant_descriptor.rb +10 -0
  32. data/lib/crystal_api/version.rb +3 -0
  33. data/lib/crystal_api/webhook.rb +15 -0
  34. data/lib/crystal_api/webhook_envelope.rb +12 -0
  35. data/lib/crystal_api/webhook_registration.rb +34 -0
  36. data/lib/crystal_api/webhook_verifier.rb +20 -0
  37. data/spec/cassettes/CrystalApi_StoreEndpoint/_get/prefs/store/makes_the_request_to_the_endpoint.yml +67 -0
  38. data/spec/cassettes/CrystalApi_StoreEndpoint/_get/prefs/store/parses_the_returned_a_store_pref.yml +67 -0
  39. data/spec/cassettes/CrystalApi_StoreEndpoint/_get/prefs/store/returns_a_store_pref_instance.yml +67 -0
  40. data/spec/crystal_api/attributes_spec.rb +7 -0
  41. data/spec/crystal_api/category_spec.rb +112 -0
  42. data/spec/crystal_api/money_spec.rb +10 -0
  43. data/spec/crystal_api/paginated_collection_spec.rb +31 -0
  44. data/spec/crystal_api/photo_spec.rb +41 -0
  45. data/spec/crystal_api/product_spec.rb +209 -0
  46. data/spec/crystal_api/received_webhook_parser_spec.rb +23 -0
  47. data/spec/crystal_api/report_spec.rb +33 -0
  48. data/spec/crystal_api/store_endpoint_spec.rb +37 -0
  49. data/spec/crystal_api/store_prefs_spec.rb +119 -0
  50. data/spec/crystal_api/store_spec.rb +17 -0
  51. data/spec/crystal_api/variant_descriptor_spec.rb +16 -0
  52. data/spec/crystal_api/variant_spec.rb +85 -0
  53. data/spec/crystal_api/webhook_envelope_spec.rb +45 -0
  54. data/spec/crystal_api/webhook_registration_spec.rb +129 -0
  55. data/spec/crystal_api/webhook_spec.rb +54 -0
  56. data/spec/spec_helper.rb +25 -0
  57. data/spec/support/attribute_examples.rb +77 -0
  58. data/spec/support/vcr.rb +7 -0
  59. metadata +305 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 27cacb6013a18cec21a77b3fe9b85ef9af918f80
4
+ data.tar.gz: 2aacba745040e1048f1f584b92b45c0fe87af2ba
5
+ SHA512:
6
+ metadata.gz: 419ba56c0705c86cdf9fd9dd93a450f143deb3615c753cdc61f371f8b3ffd143e8dd3ef27bc5efd0f9e82bba03c359bbac87b248c5e9da9d689e05d29c51ebae
7
+ data.tar.gz: 0891392e5ff703c08f30d9c422f2882567cc39b61234235e2faa53028ce892593c7cf7442868f44127985567aad82108c5494013b9c83b6f6e319245a7435b49
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in crystal_api.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+ guard 'rspec', :version => 2 do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/crystal_api/attributes\.rb$}) { |m| "spec" }
4
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
5
+ watch('spec/spec_helper.rb') { "spec" }
6
+ watch('spec/support/*.rb') { "spec" }
7
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Donald Plummer
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # CrystalApi
2
+
3
+ A library for using the CrystalCommerce API in Ruby. See
4
+ http://apidocs.crystalcommerce.com for API documentation.
5
+
6
+ ## TODO
7
+
8
+ * Receiving Webhook payload verification
9
+ * Sending Webhook payload signing
10
+ * Switch to http client that the user of the gem can override the
11
+ backend, so that async http calls can be made (faraday?)
12
+ * Remove git ls-files usage from gemspec
13
+ * Add usage documentation to apidocs site
14
+ * Define behavior for http responses (404, 403, 500, etc)
15
+ * Allow user of gem to specify json parsing library (MultiJSON)
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ gem 'crystal_api'
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install crystal_api
30
+
31
+ ## Usage
32
+
33
+ store_endpoint = CrystalApi::StoreEndpoint.new(
34
+ base_url: "https://apitest-api.crystalcommerce.com/v1",
35
+ token: "your-oauth2-token"
36
+ )
37
+
38
+ store_prefs = store_endpoint.get("/prefs/store")
39
+
40
+ ## Contributing
41
+
42
+ 1. Fork it
43
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
44
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
45
+ 4. Push to the branch (`git push origin my-new-feature`)
46
+ 5. Create new Pull Request
47
+
48
+ ## License
49
+
50
+ See LICENSE.txt
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'crystal_api/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "crystal_api"
8
+ gem.version = CrystalApi::VERSION
9
+ gem.authors = ["Donald Plummer"]
10
+ gem.email = ["donald@crystalcommerce.com"]
11
+ gem.description = %q{A library for using the CrystalCommerce API.}
12
+ gem.summary = %q{A library for using the CrystalCommerce API.}
13
+ gem.homepage = "http://apidocs.crystalcommerce.com"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency "money", "~> 5"
21
+ gem.add_dependency "values", "~> 1.2.1"
22
+ gem.add_dependency "activesupport"
23
+ gem.add_dependency "httparty"
24
+ gem.add_dependency "multi_json"
25
+
26
+ gem.add_development_dependency 'rspec', '~> 2.11.0'
27
+ gem.add_development_dependency 'guard', '~> 1.2.3'
28
+ gem.add_development_dependency 'guard-rspec', '~> 1.2.1'
29
+ gem.add_development_dependency 'vcr', '~> 2.2.4'
30
+ gem.add_development_dependency 'webmock', '~> 1.8.0'
31
+ gem.add_development_dependency 'libnotify'
32
+ gem.add_development_dependency 'rb-inotify', '~> 0.8.8'
33
+ gem.add_development_dependency 'pry'
34
+ end
@@ -0,0 +1,51 @@
1
+ $: << (File.join(File.dirname(__FILE__)))
2
+
3
+ require "crystal_api/version"
4
+ require 'money'
5
+ require 'active_support'
6
+ require 'multi_json'
7
+
8
+ module CrystalApi
9
+ CannotParseJson = Class.new(StandardError)
10
+
11
+ def self.from_json(object)
12
+ find_klass(object.keys.first).from_json(object)
13
+ end
14
+
15
+ def self.find_klass(word)
16
+ camel_cased_word = ActiveSupport::Inflector.camelize(word)
17
+
18
+ if CrystalApi.const_defined?("#{camel_cased_word}")
19
+ CrystalApi.const_get(camel_cased_word)
20
+ else
21
+ raise CrystalApi::CannotParseJson.new("No objects to parse #{word}!")
22
+ end
23
+ end
24
+ end
25
+
26
+ require 'crystal_api/money'
27
+ require 'crystal_api/attributes'
28
+ require 'crystal_api/variant'
29
+ require 'crystal_api/webhook_envelope'
30
+ require 'crystal_api/store'
31
+ require 'crystal_api/received_webhook_parser'
32
+ require 'crystal_api/variant_descriptor'
33
+ require 'crystal_api/product'
34
+ require 'crystal_api/photo'
35
+ require 'crystal_api/url'
36
+ require 'crystal_api/product_descriptor'
37
+ require 'crystal_api/category'
38
+ require 'crystal_api/market_prices'
39
+ require 'crystal_api/descriptor'
40
+ require 'crystal_api/store_prefs'
41
+ require 'crystal_api/webhook'
42
+ require 'crystal_api/paginated_collection'
43
+ require 'crystal_api/report'
44
+
45
+ require 'crystal_api/error_response'
46
+ require 'crystal_api/store_endpoint'
47
+ require 'crystal_api/webhook_registration'
48
+
49
+ require 'crystal_api/hmac_request_signing'
50
+ require 'crystal_api/message_verifier'
51
+ require 'crystal_api/webhook_verifier'
@@ -0,0 +1,210 @@
1
+ require 'set'
2
+ require 'bigdecimal'
3
+
4
+ module CrystalApi
5
+ module Attributes
6
+ class NoRootElementDefined < StandardError
7
+ end
8
+
9
+ module ClassMethods
10
+ attr_reader :attributes
11
+
12
+ [:integer, :decimal, :string, :object, :datetime, :date, :url].each do |type|
13
+ define_method("#{type}_attribute") do |attribute_name|
14
+ attr_reader attribute_name
15
+ @attributes[attribute_name.to_s] = type
16
+ end
17
+ end
18
+
19
+ def boolean_attribute(attribute_name)
20
+ attr_reader attribute_name
21
+ @attributes[attribute_name.to_s] = :boolean
22
+
23
+ if matches = attribute_name.to_s.match(/^is_(.*)$/)
24
+ define_method("#{matches[1]}?") do
25
+ send(attribute_name)
26
+ end
27
+ else
28
+ alias_method "#{attribute_name}?", attribute_name
29
+ end
30
+ end
31
+
32
+ def embedded_attribute(attribute_name, type = :object)
33
+ attr_reader attribute_name
34
+ @attributes[attribute_name.to_s] = [:embedded, type]
35
+ end
36
+
37
+ def array_attribute(attribute_name, type = :object)
38
+ attr_reader attribute_name
39
+ @attributes[attribute_name.to_s] = [:array, type]
40
+ end
41
+
42
+ def hash_attribute(attribute_name, type)
43
+ attr_reader attribute_name
44
+ @attributes[attribute_name.to_s] = [:hash, type]
45
+ end
46
+
47
+ def attribute(attribute_name)
48
+ attr_reader attribute_name
49
+ @attributes[attribute_name.to_s] = :unknown
50
+ end
51
+
52
+ def root_element(elem)
53
+ @root_element = elem.to_s
54
+ end
55
+
56
+ def get_root_element
57
+ klass = self
58
+ @root_element || raise(NoRootElementDefined.new("No root element was defined for #{klass.name}"))
59
+ end
60
+
61
+ def from_json(json_hash)
62
+ json_attributes = json_hash.fetch(get_root_element, json_hash)
63
+ embedded = json_attributes.delete("_embedded") || {}
64
+ attrs = attributes.inject({}) do |acc, (attr, type)|
65
+ if Array(type).first == :embedded
66
+ val = embedded[attr]
67
+ else
68
+ val = json_attributes[attr]
69
+ end
70
+ acc[attr] = val unless val.nil?
71
+ acc
72
+ end
73
+ new(attrs)
74
+ end
75
+ end
76
+
77
+ def initialize(args = {})
78
+ unexpected_keys = []
79
+
80
+ args.each do |arg, value|
81
+ if self.class.attributes.has_key?(arg.to_s)
82
+ instance_variable_set("@#{arg}", parse_arg(arg, value))
83
+ else
84
+ unexpected_keys << arg
85
+ end
86
+ end
87
+
88
+ if !unexpected_keys.empty?
89
+ raise ArgumentError.new("Unexpected hash keys: #{unexpected_keys}")
90
+ end
91
+
92
+ self.freeze
93
+ end
94
+
95
+ def self.included(receiver)
96
+ receiver.extend(ClassMethods)
97
+ receiver.class_eval do
98
+ @attributes = {}
99
+ end
100
+ end
101
+
102
+ def ==(other)
103
+ self.eql?(other)
104
+ end
105
+
106
+ def eql?(other)
107
+ return false if other.class != self.class
108
+ self.class.attributes.keys.all? do |field|
109
+ self.send(field) == other.send(field)
110
+ end
111
+ end
112
+
113
+ def hash
114
+ result = 0
115
+ self.class.attributes.keys.each do |field|
116
+ result += self.send(field).hash
117
+ end
118
+ return result + self.class.hash
119
+ end
120
+
121
+ def as_json(options = {})
122
+ {
123
+ root_key => self.class.attributes.inject({}) { |acc, (key, _)|
124
+ unless options.fetch(:except, []).include?(key.to_sym)
125
+ value = send(key)
126
+ acc[key.to_s] = value.respond_to?(:as_json) ? value.as_json : value
127
+ end
128
+ acc
129
+ }
130
+ }
131
+ end
132
+
133
+ private
134
+
135
+ def root_key
136
+ self.class.get_root_element.to_s
137
+ end
138
+
139
+ def parse_arg(arg, value)
140
+ type, type2 = self.class.attributes[arg.to_s]
141
+ if type
142
+ if type2 && value
143
+ send("parse_#{type}", value, type2)
144
+ else
145
+ send("parse_#{type}", value)
146
+ end
147
+ end
148
+ end
149
+
150
+ def parse_integer(value)
151
+ value.to_i
152
+ end
153
+
154
+ def parse_decimal(value)
155
+ BigDecimal.new(value.to_s) unless value.nil?
156
+ end
157
+
158
+ def parse_boolean(value)
159
+ value == 'true' || value == '1' || value == true
160
+ end
161
+
162
+ def parse_string(value)
163
+ value.to_s
164
+ end
165
+
166
+ def parse_datetime(value)
167
+ DateTime.parse(value) if value
168
+ end
169
+
170
+ def parse_date(value)
171
+ Date.parse(value) if value
172
+ end
173
+
174
+ def parse_url(value)
175
+ Url.new(value)
176
+ end
177
+
178
+ def parse_array(value, type = :object)
179
+ value.map do |val|
180
+ send("parse_#{type}", val)
181
+ end
182
+ end
183
+
184
+ def parse_hash(value, type = :object)
185
+ value.inject({}) do |acc, (key, val)|
186
+ acc[key] = send("parse_#{type}", val)
187
+ acc
188
+ end
189
+ end
190
+
191
+ def parse_object(value)
192
+ return value unless value.is_a?(Hash)
193
+
194
+ klass = CrystalApi.find_klass(value.keys.first)
195
+ klass.from_json(value) if klass
196
+ end
197
+
198
+ def parse_embedded(value, type)
199
+ send("parse_#{type}", value)
200
+ end
201
+
202
+ def parse_unknown(value)
203
+ if value.is_a?(Hash)
204
+ parse_object(value)
205
+ elsif value.is_a?(Array)
206
+ parse_array(value)
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,28 @@
1
+ module CrystalApi
2
+ class Category
3
+ include Attributes
4
+
5
+ root_element :category
6
+
7
+ integer_attribute :id
8
+ integer_attribute :catalog_id
9
+
10
+ string_attribute :name
11
+ string_attribute :seoname
12
+
13
+ boolean_attribute :is_root
14
+ boolean_attribute :is_leaf
15
+ boolean_attribute :is_visible_for_selling
16
+ boolean_attribute :is_visible_for_buying
17
+
18
+ string_attribute :description
19
+ string_attribute :browse_layout
20
+
21
+ hash_attribute :catalog_links, :url
22
+ hash_attribute :buylist_links, :url
23
+
24
+ array_attribute :descriptors
25
+
26
+ embedded_attribute :ancestors, :array
27
+ end
28
+ end