zipmark 0.0.1.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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: []