crystal_api 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.
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