zipmark 0.0.1.beta.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/CONTRIBUTING.md ADDED
@@ -0,0 +1 @@
1
+ fork -> test + code -> pull request
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Zipmark Ruby Client
2
+
3
+ The Zipmark Ruby Client library is used to interact with Zipmark's [API](https://dev.zipmark.com).
4
+
5
+ ## Installation
6
+
7
+ ### Requirements
8
+
9
+ ## Initialization
10
+
11
+ ## Usage Examples
12
+
13
+ ### Instantiating a client
14
+
15
+ Application Identifier and Application Secret should be replaced with the vendor application identifier and secret provided by Zipmark.
16
+
17
+ ### Production Mode
18
+
19
+ ### Loading a Bill from a known Bill ID
20
+
21
+
22
+ ### Discovering available resources
23
+
24
+ Resources will contain an array of all available resources.
25
+
26
+ ### Creating a new Bill
27
+
28
+ Create a bill object, set required attributes, send it to Zipmark
29
+
30
+ As an alternative, it is possible to build an object first and then save it afterwards
31
+
32
+
33
+ ### Updating an existing Bill
34
+
35
+ Get the bill, make a change, send it back to Zipmark
36
+
37
+ ### Retrieving a list of all Bills
38
+
39
+ Retrieve a list of all bills.
40
+
41
+ Get the number of objects available.
42
+
43
+ ### Basic Iterator
44
+
45
+ The Zipmark_Iterator class understands Zipmark's pagination system. It loads one page of objects at a time and will retrieve more objects as necessary while iterating through the objects.
46
+
47
+ Get the current object (returns null if the iterator has passed either end of the list)
48
+
49
+ Get the next/previous object (returns null if the next/previous object would pass either end of the list)
50
+
51
+ ### Iterating through a list of all Bills
52
+
53
+ The Zipmark_Iterator can be used to iterate through all objects of a given resource type.
54
+
55
+ ### Callback processing
56
+
57
+ The client is able to process, verify and extract data from callbacks received from the Zipmark service.
58
+
59
+ #### Loading a callback response
60
+
61
+ #### Verifying a callback
62
+
63
+ #### Retrieving the callback data
64
+
65
+ Valid callbacks contain events, object types and objects. The below functions will return their respective values/objects, or null if the callback is invalid.
66
+
67
+ ## API Documentation
68
+
69
+ Please see the [Zipmark API](https://dev.zipmark.com) or contact Zipmark Support via [email](mailto:developers@zipmark.com) or [chat](http://bit.ly/zipmarkAPIchat) for more information.
70
+
71
+ ## Unit/Acceptance Tests
@@ -0,0 +1,5 @@
1
+ module Zipmark
2
+ class Action
3
+ attr_accessor :name, :method, :href, :accepts, :inputs
4
+ end
5
+ end
@@ -0,0 +1,47 @@
1
+ module Zipmark
2
+ module Adapters
3
+ class HTTPartyAdapter
4
+ begin
5
+ require 'httparty'
6
+ include HTTParty
7
+ rescue LoadError
8
+ puts 'You must install httparty >= \'0.9.0\' to use Zipmark::Adapters::HTTPartyAdapter '
9
+ end
10
+
11
+ attr_accessor :username, :password, :production
12
+
13
+ def api_endpoint
14
+ production ? PRODUCTION_API_ENDPOINT : SANDBOX_API_ENDPOINT
15
+ end
16
+
17
+ def api_accept_mime
18
+ "application/vnd.com.zipmark.#{API_VERSION}+json"
19
+ end
20
+
21
+ def get(path)
22
+ self.class.get(path, adapter_options)
23
+ end
24
+
25
+ def post(path, body)
26
+ self.class.post(path, adapter_options.merge(:body => body.to_json))
27
+ end
28
+
29
+ def put(path, body)
30
+ self.class.put(path, adapter_options.merge(:body => body.to_json))
31
+ end
32
+
33
+ def delete(path)
34
+ self.class.delete(path, adapter_options)
35
+ end
36
+
37
+ private
38
+ def adapter_options
39
+ {
40
+ :base_uri => api_endpoint,
41
+ :digest_auth => { :username => username, :password => password },
42
+ :headers => { 'Content-Type' => 'application/json', 'Accept' => api_accept_mime }
43
+ }
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,61 @@
1
+ module Zipmark
2
+ module Adapters
3
+ class HTTPClientAdapter
4
+ begin
5
+ require 'httpclient'
6
+ rescue LoadError
7
+ puts 'You must install httpclient to use Zipmark::Adapters::HTTClientAdapter '
8
+ end
9
+
10
+ attr_accessor :username, :password, :production
11
+
12
+ def httpclient
13
+ @httpclient ||= initialize_client
14
+ end
15
+
16
+ def initialize_client
17
+ client = HTTPClient.new
18
+ client.set_auth(api_endpoint, username, password)
19
+ return client
20
+ end
21
+
22
+ def api_endpoint
23
+ production ? PRODUCTION_API_ENDPOINT : SANDBOX_API_ENDPOINT
24
+ end
25
+
26
+ def api_accept_mime
27
+ "application/vnd.com.zipmark.#{API_VERSION}+json"
28
+ end
29
+
30
+ def get(path)
31
+ httpclient.get(url_for(path), adapter_options)
32
+ end
33
+
34
+ def post(path, body)
35
+ httpclient.post(url_for(path), adapter_options.merge(:body => body.to_json))
36
+ end
37
+
38
+ def put(path, body)
39
+ httpclient.put(url_for(path), adapter_options.merge(:body => body.to_json))
40
+ end
41
+
42
+ def delete(path)
43
+ httpclient.delete(url_for(path), adapter_options)
44
+ end
45
+
46
+ def url_for(path)
47
+ path = URI.parse(path)
48
+ if path.kind_of? URI::Generic
49
+ path = URI.parse(api_endpoint) + path
50
+ end
51
+ end
52
+
53
+ private
54
+ def adapter_options
55
+ {
56
+ :header => { 'Content-Type' => 'application/json', 'Accept' => api_accept_mime }
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,6 @@
1
+ module Zipmark
2
+ module Auth
3
+ class Digest
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,72 @@
1
+ module Zipmark
2
+ class Callback
3
+ attr_accessor :request, :errors
4
+
5
+ def initialize(request, options = {})
6
+ raise ArgumentError, "Request cannot be nil" unless request
7
+ @request = request
8
+ @errors = {}
9
+ end
10
+
11
+ def body
12
+ @request.raw_post
13
+ end
14
+
15
+ def event
16
+ raise "unimplemented"
17
+ end
18
+
19
+ def object
20
+ raise "unimplemented"
21
+ end
22
+
23
+ def object_type
24
+ raise "unimplemented"
25
+ end
26
+
27
+ def hashed_content
28
+ Digest::MD5.hexdigest(body) if body
29
+ end
30
+
31
+ def date
32
+ Time.parse(@request.headers["Date"]) if @request.headers["Date"]
33
+ end
34
+
35
+ def uri
36
+ @request.fullpath
37
+ end
38
+
39
+ def date_within_range?
40
+ date && date < Time.now + 15.minutes && date > Time.now - 15.minutes
41
+ end
42
+
43
+ def valid?
44
+ validate_date && validate_authorization
45
+ end
46
+
47
+ def validate_authorization
48
+ string_to_sign = ["POST",hashed_content,'application/json',date.rfc2822,uri,identifier].join("\n")
49
+ signed_string = ActiveSupport::Base64.encode64s(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), secret, string_to_sign))
50
+ valid_authorization = "ZM #{ActiveSupport::Base64.encode64s(identifier)}:#{signed_string}"
51
+ if authorization_header == valid_authorization
52
+ return true
53
+ else
54
+ @errors[:authorization] = "Signature does not match."
55
+ return false
56
+ end
57
+ end
58
+
59
+ def validate_date
60
+ if date_within_range?
61
+ return true
62
+ else
63
+ @errors[:date] = "Date is not within bounds."
64
+ return false
65
+ end
66
+ end
67
+
68
+ def authorization_header
69
+ @request.headers["Authorization"]
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,71 @@
1
+ module Zipmark
2
+ # Public: The Zipmark API Client.
3
+ #
4
+ # Examples
5
+ #
6
+ # client = Zipmark::Client.new("app-id", "app-secret", :production => false)
7
+ # client.get("/")
8
+ #
9
+ class Client
10
+ # Public: Gets the Adapter.
11
+ attr_reader :adapter
12
+
13
+ # Public: Initialize a Zipmark Client
14
+ #
15
+ # application_id - The Identifier for your application
16
+ # application_secret - The Secret for your Application
17
+ # options - Hash options used to configure the Client (default: {})
18
+ # :adapter - The Instance of an Adapter that wraps your preferred HTTP Client
19
+ # :production - The Boolean determining if Production Mode is enabled
20
+ def initialize(application_id, application_secret, options = {})
21
+ @adapter = options[:adapter] || Zipmark::Adapters::HTTPClientAdapter.new
22
+ adapter.production = options[:production]
23
+ adapter.username = application_id
24
+ adapter.password = application_secret
25
+ @resources = load_resources
26
+ end
27
+
28
+
29
+ # Public: Send a GET Request to the given API Path
30
+ #
31
+ # path - A String which can be a relative path to the API root, or a full URL
32
+ def get(path)
33
+ adapter.get(path)
34
+ end
35
+
36
+ # Public: Send a POST Request to the given API Path
37
+ #
38
+ # path - A String which can be a relative path to the API root, or a full URL
39
+ # body - A Object which responds to to_json and represents the body of the POST
40
+ def post(path, body)
41
+ adapter.post(path, body)
42
+ end
43
+
44
+ # Public: Send a PUT Request to the given API Path
45
+ #
46
+ # path - A String which can be a relative path to the API root, or a full URL
47
+ # body - A Object which responds to to_json and represents the body of the PUT
48
+ def put(path, body)
49
+ adapter.put(path, body)
50
+ end
51
+
52
+ # Public: Send a DELETE Request to the given API Path
53
+ #
54
+ # path - A String which can be a relative path to the API root, or a full URL
55
+ def delete(path)
56
+ adapter.delete(path)
57
+ end
58
+
59
+ def method_missing(meth, *args, &block)
60
+ @resources[meth.to_s] || raise(NoMethodError, "No resource or method: '#{meth}'")
61
+ end
62
+
63
+ private
64
+ def load_resources
65
+ hash = {}
66
+ response = JSON.parse(get("/").body)
67
+ response["vendor_root"]["links"].each {|link| hash[link["rel"]] = Resource.new({ :client => self }.merge(link)) }
68
+ hash
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,27 @@
1
+ module Zipmark
2
+ class Collection
3
+ include Enumerable
4
+
5
+ attr_accessor :iterator
6
+
7
+ def initialize(resource, iterator_class)
8
+ fetched_resource = resource.client.get(resource.href).body
9
+ @iterator = iterator_class.new(fetched_resource, :resource_name => resource.rel, :client => resource.client)
10
+ end
11
+
12
+ def items
13
+ iterator.current_items
14
+ end
15
+
16
+ def length
17
+ iterator.total_items
18
+ end
19
+
20
+ def each
21
+ # Wrapping in the being block ensures that the current_item is yielded once before next_item is called
22
+ begin
23
+ yield iterator.current_item
24
+ end while iterator.next_item
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,4 @@
1
+ module Zipmark
2
+ class Documentation
3
+ end
4
+ end
@@ -0,0 +1,59 @@
1
+ module Zipmark
2
+ class Entity
3
+ attr_accessor :attributes, :client, :resource_type, :errors, :dirty_attributes
4
+
5
+ def initialize(options={})
6
+ @errors = {}
7
+ @attributes = Util.stringify_keys(options)
8
+ @dirty_attributes = {}
9
+ @client = @attributes.delete("client")
10
+ @resource_type = @attributes.delete("resource_type")
11
+ end
12
+
13
+ def method_missing(meth, *args, &block)
14
+ if meth =~ /=$/
15
+ dirty_attributes[meth.to_s.sub(/=$/, '')] = args.first
16
+ else
17
+ dirty_attributes[meth.to_s] || attributes[meth.to_s]
18
+ end
19
+ end
20
+
21
+ def save
22
+ if url
23
+ response = client.put(url, resource_type => dirty_attributes)
24
+ else
25
+ response = client.post("/#{resource_type}s", resource_type => attributes)
26
+ end
27
+ apply_response(response)
28
+ return self
29
+ end
30
+
31
+ def apply_response(response)
32
+ object = JSON.parse(response.body)
33
+ if response.ok?
34
+ @attributes = object[resource_type]
35
+ elsif response.code == 422
36
+ @errors = object
37
+ else
38
+ raise Zipmark::Error.new(object)
39
+ end
40
+ end
41
+
42
+ def valid?
43
+ !!(id && errors.empty?)
44
+ end
45
+
46
+ def url
47
+ link = links.detect {|link| link["rel"] == "self" } if links && !links.empty?
48
+ link["href"] if link
49
+ end
50
+
51
+ def updated_at
52
+ Time.parse(attributes["updated_at"]) if attributes["updated_at"]
53
+ end
54
+
55
+ def created_at
56
+ Time.parse(attributes["created_at"]) if attributes["created_at"]
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,5 @@
1
+ module Zipmark
2
+ class Error < StandardError
3
+ attr_accessor :classification, :messages, :code
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Zipmark
2
+ class Input
3
+ attr_accessor :type, :required, :name, :title, :placeholder, :value
4
+ end
5
+ end
@@ -0,0 +1,71 @@
1
+ require 'json'
2
+
3
+ module Zipmark
4
+ class Iterator
5
+ attr_accessor :current_item, :json, :options
6
+
7
+ def initialize(json, options = {})
8
+ @json = JSON.parse(json)
9
+ @options = options
10
+ @current_item = items.first
11
+ end
12
+
13
+ def client
14
+ options[:client]
15
+ end
16
+
17
+ def collection
18
+ json[options[:resource_name]]
19
+ end
20
+
21
+ def metadata
22
+ json["meta"]
23
+ end
24
+
25
+ def links
26
+ json["links"]
27
+ end
28
+
29
+ def items
30
+ @items ||= collection.map {|item| Entity.new(item.merge(:client => options[:client], :resource_type => resource_singular)) }
31
+ end
32
+
33
+ def resource_singular
34
+ options[:resource_name].sub(/s$/, '')
35
+ end
36
+
37
+ def pagination
38
+ @pagination ||= Pagination.new(metadata["pagination"] ) if metadata
39
+ end
40
+
41
+ def next_item
42
+ item = @items.fetch(@items.index(current_item) + 1) rescue nil
43
+ if item
44
+ @current_item = item
45
+ else
46
+ @current_item = fetch_item_from_next_page ? @items.fetch(0) : nil
47
+ end
48
+ end
49
+
50
+ def fetch_item_from_next_page
51
+ return if pagination.last_page?
52
+ self.json = JSON.parse(client.get(next_page["href"]).body)
53
+ @items = nil
54
+ @pagination = nil
55
+ @current_item = items.first
56
+ return true
57
+ end
58
+
59
+ def next_page
60
+ links.detect {|l| l["rel"] == "next"}
61
+ end
62
+
63
+ def total_items
64
+ pagination.total if pagination
65
+ end
66
+
67
+ def pages
68
+ pagination.pages if pagination
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,4 @@
1
+ module Zipmark
2
+ class Link
3
+ end
4
+ end
@@ -0,0 +1,34 @@
1
+ module Zipmark
2
+ class Pagination
3
+ attr_accessor :pagination_hash
4
+
5
+ def initialize(hash)
6
+ raise ArgumentError, "expected hash" unless hash && hash.kind_of?(Hash)
7
+ @pagination_hash = hash
8
+ end
9
+
10
+ def pages
11
+ pagination_hash["total_pages"]
12
+ end
13
+
14
+ def total
15
+ pagination_hash["total"]
16
+ end
17
+
18
+ def per_page
19
+ pagination_hash["per_page"]
20
+ end
21
+
22
+ def current_page
23
+ pagination_hash["page"]
24
+ end
25
+
26
+ def first_page?
27
+ pagination_hash["first_page"]
28
+ end
29
+
30
+ def last_page?
31
+ pagination_hash["last_page"]
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,45 @@
1
+ module Zipmark
2
+ class Resource
3
+ attr_accessor :options
4
+
5
+ def initialize(options={})
6
+ @options = Util.stringify_keys(options)
7
+ end
8
+
9
+ def all
10
+ Zipmark::Collection.new(self, Zipmark::Iterator)
11
+ end
12
+
13
+ def find(id)
14
+ json = client.get("/" + rel + "/" + id).body
15
+ object = JSON.parse(json)
16
+ Zipmark::Entity.new(object[resource_name].merge(:client => client, :resource_type => resource_name))
17
+ end
18
+
19
+ def build(options)
20
+ Zipmark::Entity.new(options.merge(:client => client, :resource_type => resource_name))
21
+ end
22
+
23
+ def create(options)
24
+ entity = build(options)
25
+ entity.save
26
+ end
27
+
28
+ def href
29
+ options["href"] || raise(Zipmark::ResourceError, "Resource did not specify href")
30
+ end
31
+
32
+ def rel
33
+ options["rel"] || raise(Zipmark::ResourceError, "Resource did not specify rel")
34
+ end
35
+
36
+ def resource_name
37
+ #TODO: This is a hack
38
+ rel.gsub(/s$/, '')
39
+ end
40
+
41
+ def client
42
+ options["client"] || raise(Zipmark::ClientError, "You must pass :client as an option")
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,16 @@
1
+ module Zipmark
2
+ # Public: Utility methods for the Zipmark API Client
3
+ class Util
4
+ # Public: Method which converts all of a hash's keys to strings
5
+ #
6
+ # hash - The hash whose keys you would like to convert
7
+ #
8
+ # Returns the converted Hash
9
+ def self.stringify_keys(hash)
10
+ hash.inject({}) do |options, (key, value)|
11
+ options[key.to_s] = value
12
+ options
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Zipmark
2
+ VERSION = '0.0.1.beta.1'
3
+ end
data/lib/zipmark.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'zipmark/callback'
2
+ require 'zipmark/client'
3
+ require 'zipmark/collection'
4
+ require 'zipmark/entity'
5
+ require 'zipmark/error'
6
+ require 'zipmark/iterator'
7
+ require 'zipmark/pagination'
8
+ require 'zipmark/resource'
9
+ require 'zipmark/util'
10
+ require 'zipmark/version'
11
+
12
+ require 'zipmark/adapters/httpclient_adapter'
13
+
14
+ # Public: Main module for the Zipmark API Client
15
+ module Zipmark
16
+ # Public: URI String for the Zipmark Production API Endpoint.
17
+ PRODUCTION_API_ENDPOINT = 'https://api.zipmark.com'
18
+
19
+ # Public: URI String for the Zipmark Sandbox API Endpoint.
20
+ SANDBOX_API_ENDPOINT = 'https://sandbox.zipmark.com'
21
+
22
+ # Public: String Representing the Current Zipmark API Version
23
+ API_VERSION = 'v2'
24
+
25
+ # Public: Error that is raised when a client is expected but not found
26
+ class ClientError < StandardError; end
27
+
28
+ # Public: Error that is raised when a Resource is malformed or invalid
29
+ class ResourceError < StandardError; end
30
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zipmark
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.beta.1
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Jake Howerton
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-01 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Simple Client Library to connect to the Zipmark API
15
+ email: jake@zipmark.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/zipmark/action.rb
21
+ - lib/zipmark/adapters/httparty_adapter.rb
22
+ - lib/zipmark/adapters/httpclient_adapter.rb
23
+ - lib/zipmark/auth.rb
24
+ - lib/zipmark/callback.rb
25
+ - lib/zipmark/client.rb
26
+ - lib/zipmark/collection.rb
27
+ - lib/zipmark/documentation.rb
28
+ - lib/zipmark/entity.rb
29
+ - lib/zipmark/error.rb
30
+ - lib/zipmark/input.rb
31
+ - lib/zipmark/iterator.rb
32
+ - lib/zipmark/link.rb
33
+ - lib/zipmark/pagination.rb
34
+ - lib/zipmark/resource.rb
35
+ - lib/zipmark/util.rb
36
+ - lib/zipmark/version.rb
37
+ - lib/zipmark.rb
38
+ - CONTRIBUTING.md
39
+ - README.md
40
+ homepage: http://rubygems.org/gems/zipmark
41
+ licenses: []
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>'
56
+ - !ruby/object:Gem::Version
57
+ version: 1.3.1
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 1.8.15
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: Zipmark API Client Library
64
+ test_files: []