cxml 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ *.tmproj
5
+ *~
6
+ .DS_Store
7
+ .\#*
8
+ .bundle
9
+ .config
10
+ .yardoc
11
+ Gemfile.lock
12
+ InstalledFiles
13
+ \#*
14
+ _yardoc
15
+ coverage
16
+ doc/
17
+ lib/bundler/man
18
+ pkg
19
+ rdoc
20
+ spec/reports
21
+ test/tmp
22
+ test/version_tmp
23
+ tmp
24
+ tmtags
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format=nested
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2012 Dan Sosedoff.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,57 @@
1
+ # cXML
2
+
3
+ Ruby implementation of cXML protocol.
4
+
5
+ ## Documentation
6
+
7
+ Procotol specifications could be found here [http://xml.cxml.org/current/cXMLUsersGuide.pdf](http://xml.cxml.org/current/cXMLUsersGuide.pdf)
8
+
9
+ ## Parsing cXML
10
+
11
+ To parse cXML, simply pass the raw text of the document to CXML.parse
12
+
13
+ CXML.parse("<xml namespace=...")
14
+
15
+ This will return a well-constructed hash based on the conents of the document.
16
+
17
+ ## Commerce
18
+
19
+ cXML library can be used to handle inbound cXML requests via the Commerce.dispatch function. E.g.
20
+
21
+ class CommerceController < ApplicationController
22
+
23
+ def handle
24
+ Commerce.dispatch(request.raw_post) do
25
+ order_request do |order_request|
26
+ respond_to do |format|
27
+ format.xml { render xml: Commerce::Response.success }
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ ## Building documents
36
+
37
+ To build request documents, you can use the document builder. E.g.
38
+
39
+ ConfirmationRequest.new(type: 'reject').render
40
+
41
+ or send the response to a server (via RestClient)
42
+
43
+ ShipNoticeRequest.new.send("http://example.com/cxml")
44
+
45
+ ## Running Tests
46
+
47
+ Install dependencies:
48
+
49
+ ```
50
+ bundle install
51
+ ```
52
+
53
+ Run suite:
54
+
55
+ ```
56
+ rake test
57
+ ```
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:test) do |t|
6
+ t.pattern = 'spec/*_spec.rb'
7
+ t.verbose = false
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,24 @@
1
+ require File.expand_path('../lib/cxml/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "cxml"
5
+ s.version = CXML::VERSION
6
+ s.summary = "Ruby library to work with cXML protocol"
7
+ s.description = "Ruby library to work with cXML protocol"
8
+ s.homepage = "http://github.com/sosedoff/cxml"
9
+ s.authors = ["Dan Sosedoff","Geoff Hayes"]
10
+ s.email = ["dan.sosedoff@gmail.com", "hayesgm@gmail.com"]
11
+
12
+ s.add_development_dependency 'rake'
13
+ s.add_development_dependency 'rspec', '~> 2.11'
14
+ s.add_development_dependency 'simplecov', '~> 0.4'
15
+
16
+ s.add_dependency 'nokogiri'
17
+ s.add_dependency 'xml-simple'
18
+ s.add_dependency 'hashr'
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
23
+ s.require_paths = ["lib"]
24
+ end
@@ -0,0 +1,90 @@
1
+ # The Commerce module is used for quickly building documents and dispatching inbound requests
2
+ module Commerce
3
+
4
+ # Define errors
5
+ module CommerceError
6
+ class CommerceError < StandardError; end
7
+ class UnableToProcessError < CommerceError; end
8
+ class InvalidRequestError < CommerceError; end
9
+ end
10
+
11
+ # Block object used for binding in dispatch blocks
12
+ class BlockObject
13
+ def initialize
14
+ @procs = {}
15
+ end
16
+
17
+ def procs
18
+ @procs
19
+ end
20
+
21
+ def method_missing(method, *args, &block)
22
+ raise CommerceError::CommerceError, "Missing dispatch block" unless block_given?
23
+
24
+ @procs[method.to_sym] = block
25
+ end
26
+ end
27
+
28
+ # Response used for status-responses
29
+ # TODO: Move to cxml/documents
30
+ class Response
31
+ def self.success
32
+ d = CXML::Document.new('Response' => { 'Status' => { 'code' => 200, 'text' => 'OK' } } )
33
+ d.setup
34
+ d.render
35
+ end
36
+
37
+ def self.failure(message)
38
+ # TODO: Get failure up to specs
39
+ d = CXML::Document.new('Response' => { 'Status' => { 'code' => 400, 'text' => message } } )
40
+ d.setup
41
+ d.render
42
+ end
43
+ end
44
+
45
+ # Dispatch can be used to handle incoming cXML requests
46
+ #
47
+ # E.g.
48
+ # Commerce.dispatch(request.raw_post) do
49
+ # order_request do |order_request|
50
+ # # Process order request
51
+ # render xml: Commerce::Response.success
52
+ # end
53
+ # end
54
+ def self.dispatch(xml, &block)
55
+ raise CommerceError::CommerceError, "Missing xml" if xml.blank?
56
+ raise CommerceError::CommerceError, "Missing dispatch block" unless block_given?
57
+
58
+ cxml = CXML.parse(xml)
59
+ request = cxml['Request']
60
+
61
+ raise CommerceError::InvalidRequestError, "No request element" if request.nil?
62
+
63
+ deployment_mode = request.delete('deploymentMode')
64
+ id = request.delete('Id')
65
+
66
+ raise CommerceError::InvalidRequestError, "Invalid request object: #{request}" if request.keys.count != 1
67
+
68
+ request_type, request_item = request.first
69
+
70
+ Commerce.debug [ 'Commerce::Dispatch', 'Received request item', request_type, request_item ]
71
+
72
+ block_object = Commerce::BlockObject.new
73
+ block_object.instance_eval(&block)
74
+
75
+ processor = block_object.procs[request_type.underscore.to_sym]
76
+
77
+ raise CommerceError::CommerceError, "Missing handler for #{request_type.underscore}" unless processor
78
+
79
+ obj = block.binding.eval("self") # Grab self of caller
80
+ obj.instance_exec(request_item, &processor)
81
+ end
82
+
83
+ # Send debug messages
84
+ def self.debug(*args)
85
+ if defined?(Rails)
86
+ Rails.logger.debug *args if Rails.logger
87
+ p *args if Rails.env && Rails.env.development?
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,29 @@
1
+ require 'cxml/version'
2
+ require 'cxml/errors'
3
+ require 'time'
4
+ require 'nokogiri'
5
+ require 'commerce'
6
+ require 'cxml/documents/request_doc'
7
+ require 'cxml/documents/confirmation_request'
8
+ require 'cxml/documents/ship_notice_request'
9
+
10
+ module CXML
11
+ autoload :Protocol, 'cxml/protocol'
12
+ autoload :Document, 'cxml/document'
13
+ autoload :Header, 'cxml/header'
14
+ autoload :Credential, 'cxml/credential'
15
+ autoload :CredentialMac, 'cxml/credential_mac'
16
+ autoload :Sender, 'cxml/sender'
17
+ autoload :Status, 'cxml/status'
18
+ autoload :Request, 'cxml/request'
19
+ autoload :Response, 'cxml/response'
20
+ autoload :Parser, 'cxml/parser'
21
+
22
+ def self.parse(str)
23
+ CXML::Parser.new.parse(str)
24
+ end
25
+
26
+ def self.builder
27
+ Nokogiri::XML::Builder.new(:encoding => "UTF-8")
28
+ end
29
+ end
@@ -0,0 +1,62 @@
1
+ # This element contains identification and authentication values.
2
+ # Credential contains an Identity element and optionally a SharedSecret or a CredentialMac
3
+ # element. The Identity element states who the Credential represents, while the optional
4
+ # authentication elements verify the identity of the party
5
+ #
6
+ # Credential has the following attributes:
7
+ #
8
+ # *domain*
9
+ # Specifies the type of credential. This attribute allows
10
+ # documents to contain multiple types of credentials for multiple
11
+ # authentication domains.
12
+ # For messages sent on the Ariba Supplier Network, for
13
+ # instance, the domain can be AribaNetworkUserId to indicate an
14
+ # email address, DUNS for a D-U-N-S number, or NetworkId for a
15
+ # preassigned ID.
16
+ #
17
+ # *type* - optional
18
+ # Requests to or from a marketplace identify both the
19
+ # marketplace and the member company in From or To Credential
20
+ # elements. In this case, the credential for the marketplace uses
21
+ # the type attribute, which is set to the value “marketplace”
22
+ #
23
+ # *SharedSecred*
24
+ # The SharedSecret element is used when the Sender has a password that the requester recognizes.
25
+ #
26
+ # *CredentialMac*
27
+ # The CredentialMac element is used for the Message Authentication Code (MAC)
28
+ # authentication method. This authentication method is used in situations where the
29
+ # sender must prove to the receiver that it has been authenticated by shared secret by a
30
+ # trusted third party. For example, a direct PunchOut request can travel directly from a
31
+ # buyer to a supplier without going through a network commerce hub, because it
32
+ # contains a MAC (generated by the network commerce hub) that allows the supplier to
33
+ # authenticate it.
34
+
35
+ module CXML
36
+ class Credential
37
+ attr_accessor :domain
38
+ attr_accessor :type
39
+ attr_accessor :shared_secret
40
+ attr_accessor :credential_mac
41
+ attr_accessor :identity
42
+
43
+ # Initialize a new Credential instance
44
+ # @param data [Hash] optional initial data
45
+ def initialize(data={})
46
+ if data.kind_of?(Hash) && !data.empty?
47
+ @domain = data['domain']
48
+ @type = data['type']
49
+ @identity = data['Identity']
50
+ @shared_secret = data['SharedSecret']
51
+ end
52
+ end
53
+
54
+ def render(node)
55
+ node.Credential('domain' => domain) do |c|
56
+ c.Identity(@identity)
57
+ c.SharedSecret(@shared_secret) if @shared_secret
58
+ end
59
+ node
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,22 @@
1
+ # The CredentialMac element is used for the Message Authentication Code (MAC)
2
+ # authentication method. This authentication method is used in situations where the
3
+ # sender must prove to the receiver that it has been authenticated by shared secret by a
4
+ # trusted third party. For example, a direct PunchOut request can travel directly from a
5
+ # buyer to a supplier without going through a network commerce hub, because it
6
+ # contains a MAC (generated by the network commerce hub) that allows the supplier to
7
+ # authenticate it.
8
+ # The trusted third party computes the MAC and transfers it to the sender through the
9
+ # Profile transaction. The MAC is opaque to the sender (it is secure and non-reversible).
10
+ # To see how the MAC is transmitted from the trusted third party to the sender, see
11
+ # "ProfileResponse" on page 63.
12
+ #
13
+ # Page 45 cXML reference
14
+
15
+ module CXML
16
+ class CredentialMac
17
+ attr_accessor :type
18
+ attr_accessor :algorithm
19
+ attr_accessor :creation_date
20
+ attr_accessor :expiration_date
21
+ end
22
+ end
@@ -0,0 +1,62 @@
1
+ module CXML
2
+ class Document
3
+ attr_accessor :version
4
+ attr_accessor :payload_id
5
+ attr_accessor :timestamp
6
+
7
+ attr_accessor :header
8
+ attr_accessor :request
9
+ attr_accessor :response
10
+
11
+ def initialize(data={})
12
+ if data.kind_of?(Hash) && !data.empty?
13
+ @version = data['version']
14
+ @payload_id = data['payloadID']
15
+
16
+ if data['timestamp']
17
+ @timestamp = Time.parse(data['timestamp'])
18
+ end
19
+
20
+ if data['Header']
21
+ @header = CXML::Header.new(data['Header'])
22
+ end
23
+
24
+ if data['Request']
25
+ @request = CXML::Request.new(data['Request'])
26
+ end
27
+
28
+ if data['Response']
29
+ @response = CXML::Response.new(data['Response'])
30
+ end
31
+ end
32
+ end
33
+
34
+ def setup
35
+ @version = CXML::Protocol.version
36
+ @timestamp = Time.now.utc
37
+ @payload_id = "#{@timestamp.to_i}.process.#{Process.pid}@domain.com"
38
+ end
39
+
40
+ # Check if document is request
41
+ # @return [Boolean]
42
+ def request?
43
+ !request.nil?
44
+ end
45
+
46
+ # Check if document is a response
47
+ # @return [Boolean]
48
+ def response?
49
+ !response.nil?
50
+ end
51
+
52
+ def render
53
+ node = CXML.builder
54
+ node.cXML('version' => version, 'payloadID' => payload_id, 'timestamp' => timestamp.iso8601) do |doc|
55
+ doc.Header { |n| @header.render(n) } if @header
56
+ @request.render(node) if @request
57
+ @response.render(node) if @response
58
+ end
59
+ node
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,19 @@
1
+ # Builder for ConfirmationRequest object
2
+ class ConfirmationRequest < RequestDoc
3
+ @@defaults = {
4
+ type: 'accept',
5
+ payload_id: nil,
6
+ confirm_id: nil,
7
+ invoice_id: nil
8
+ }
9
+
10
+ def features(node)
11
+ node.ConfirmationRequest({}.merge(@opts[:confirm_id] ? { confirmID: @opts[:confirm_id] } : {}).merge(@opts[:invoice_id] ? { invoice_id: @opts[:invoice_id] } : {})) {
12
+ node.ConfirmationHeader(type: @opts[:type], noticeDate: Time.now.iso8601)
13
+ node.OrderReference {
14
+ node.DocumentReference(@opts[:payload_id] ? { payloadID: @opts[:payload_id] } : {})
15
+ }
16
+ }
17
+ end
18
+
19
+ end
@@ -0,0 +1,36 @@
1
+ # Default parent for all Request builders
2
+ class RequestDoc
3
+ attr_accessor :opts
4
+ class_attribute :defaults
5
+
6
+ def initialize(opts={})
7
+ @opts = opts.with_indifferent_access.reverse_merge(self.class.class_variable_defined?("@@defaults") ? self.class.class_variable_get("@@defaults") : {})
8
+ end
9
+
10
+ def [](key)
11
+ @opts[key]
12
+ end
13
+
14
+ def []=(key,val)
15
+ @opts[key] = val
16
+ end
17
+
18
+ def render
19
+ doc = CXML::Document.new('Request' => { 'builder' => proc { |node|
20
+ features(node)
21
+ }})
22
+
23
+ doc.setup
24
+ doc.render.to_xml
25
+ end
26
+
27
+ def features
28
+ raise NotImplementedError, "Missing features function for #{self.class.name}"
29
+ end
30
+
31
+ def send(endpoint)
32
+ RestClient.post endpoint, self.render, content_type: :xml
33
+
34
+ # TODO: Abstract and verify response
35
+ end
36
+ end
@@ -0,0 +1,12 @@
1
+ # Builder for ShipNoticeRequest object
2
+ class ShipNoticeRequest < RequestDoc
3
+ @@defaults = {
4
+ shipment_id: nil
5
+ }
6
+
7
+ def features(node)
8
+ node.ShipNoticeRequest {
9
+ node.ShipNoticeHeader({noticeDate: Time.now.iso8601, shipmentDate: Time.now.iso8601}.merge(@opts[:shipment_id] ? { shipmentID: @opts[:shipment_id]} : {}))
10
+ }
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ module CXML
2
+ class Error < StandardError ; end
3
+ class ParseError < Error ; end
4
+ end
@@ -0,0 +1,41 @@
1
+ # The Header element contains addressing and authentication information. The Header
2
+ # element is the same regardless of the specific Request or Response within the body of
3
+ # the cXML message. Applications need the requestor's identity, but not validation that
4
+ # the information provided for identity is correct.
5
+ #
6
+ # The From and To elements are synonymous with From and To in SMTP mail
7
+ # messages; they are the logical source and destination of the messages. Sender is the
8
+ # party that opens the HTTP connection and sends the cXML document.
9
+ # Sender contains the Credential element, which allows the receiving party to authenticate
10
+ # the sending party. This credential allows strong authentication without requiring a
11
+ # public-key end-to-end digital certificate infrastructure. Only a user name and
12
+ # password need to be issued by the receiving party to allow the sending party to
13
+ # perform Requests.
14
+ #
15
+ # When the document is initially sent, Sender and From are the same, However, if the
16
+ # cXML document travels through e-commerce network hubs, the Sender element
17
+ # changes to indicate current sending party.
18
+
19
+ module CXML
20
+ class Header
21
+ attr_accessor :from
22
+ attr_accessor :to
23
+ attr_accessor :sender
24
+
25
+ def initialize(data={})
26
+ if data.kind_of?(Hash) && !data.empty?
27
+ @from = CXML::Credential.new(data['From']['Credential'])
28
+ @to = CXML::Credential.new(data['To']['Credential'])
29
+ @sender = CXML::Sender.new(data['Sender'])
30
+ end
31
+ end
32
+
33
+ def render(node)
34
+ node.From { |n| @from.render(n) }
35
+ node.To { |n| @to.render(n) }
36
+
37
+ @sender.render(node)
38
+ node
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,23 @@
1
+ # The Money element has three possible attributes: currency, alternateAmount,
2
+ # alternateCurrency. The attributes currency and alternateCurrecy must be a three-letter ISO
3
+ # 4217 currency code. The content of the Money element and of the aternateAmount
4
+ # attribute should be a numeric value. For example:
5
+ # <Money currency="USD">12.34</Money>
6
+ # The optional alternateCurrency and alternateAmount attributes are used together to specify
7
+ # an amount in an alternate currency. These can be used to support dual-currency
8
+ # requirements such as the euro. For example:
9
+ # <Money currency="USD" alternateCurrency=”EUR” alternateAmount=”14.28”>12.34
10
+ # </Money>
11
+ # Note: You can optionally use commas as thousands separators. Do not use
12
+ # commas as decimal separators.
13
+ #
14
+ # Page: 59
15
+
16
+ module CXML
17
+ class Money
18
+ attr_accessor :currency
19
+ attr_accessor :amount
20
+ attr_accessor :alternative_currency
21
+ attr_accessor :alternative_amount
22
+ end
23
+ end
@@ -0,0 +1,10 @@
1
+ require 'nokogiri'
2
+ require 'xmlsimple'
3
+
4
+ module CXML
5
+ class Parser
6
+ def parse(data)
7
+ XmlSimple.xml_in(data, {'ForceArray' => false})
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,55 @@
1
+ module CXML
2
+ module Protocol
3
+ VERSION = '1.2.011'
4
+
5
+ REQUEST_ELEMENTS = [
6
+ 'OrderRequest',
7
+ 'ProfileRequest',
8
+ 'PunchOutSetupRequest',
9
+ 'StatusUpdateRequest',
10
+ 'GetPendingRequest',
11
+ 'ConfirmationRequest',
12
+ 'ShipNoticeRequest',
13
+ 'ProviderSetupRequest',
14
+ 'PaymentRemittanceRequest',
15
+ ]
16
+
17
+ RESPONSE_ELEMENTS = [
18
+ 'ProfileResponse',
19
+ 'PunchOutSetupResponse',
20
+ 'GetPendingResponse',
21
+ ]
22
+
23
+ STATUS_CODES = [
24
+ 200, 201, 204, 280, 281,
25
+ 400, 401, 402, 403, 406, 409, 412, 417, 450, 475, 476, 477,
26
+ 500, 550, 551, 560
27
+ ]
28
+
29
+ class << self
30
+ # Get current protocol version
31
+ # @return [String]
32
+ def version
33
+ VERSION
34
+ end
35
+
36
+ # Get available request elements
37
+ # @return [Array<String>]
38
+ def request_elements
39
+ REQUEST_ELEMENTS
40
+ end
41
+
42
+ # Get available response elements
43
+ # @return [Array<String>]
44
+ def response_elements
45
+ RESPONSE_ELEMENTS
46
+ end
47
+
48
+ # Get available status codes
49
+ # @return [Array<Fixnum>]
50
+ def status_codes
51
+ STATUS_CODES
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,24 @@
1
+ # Clients send requests for operations. Only one Request element is allowed for each
2
+ # cXML envelope element, which simplifies the server implementations, because no
3
+ # demultiplexing needs to occur when reading cXML documents. The Request element
4
+ # can contain virtually any type of XML data.
5
+
6
+ module CXML
7
+ class Request
8
+ attr_accessor :id
9
+ attr_accessor :deployment_mode
10
+ attr_accessor :builder
11
+
12
+ def initialize(data={})
13
+ if data.kind_of?(Hash) && !data.empty?
14
+ @id = data['id']
15
+ @deployment_mode = data['deploymentMode']
16
+ @builder = data['builder']
17
+ end
18
+ end
19
+
20
+ def render(node)
21
+ @builder.yield(node) if @builder
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # Servers send responses to inform clients of the results of operations. Because the
2
+ # result of some requests might not have any data, the Response element can optionally
3
+ # contain nothing but a Status element. A Response element can also contain any
4
+ # application-level data. During PunchOut for example, the application-level data is
5
+ # contained in a PunchOutSetupResponse element.
6
+
7
+ module CXML
8
+ class Response
9
+ attr_accessor :id
10
+ attr_accessor :status
11
+
12
+ def initialize(data={})
13
+ if data.kind_of?(Hash) && !data.empty?
14
+ @status = CXML::Status.new(data['Status'])
15
+ end
16
+ end
17
+
18
+ def render(node)
19
+ options = {:id => @id}
20
+ options.keep_if { |k,v| !v.nil? }
21
+ node.Response(options) { |n| @status.render(n) }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ module CXML
2
+ class Sender
3
+ attr_accessor :credential
4
+ attr_accessor :user_agent
5
+
6
+ def initialize(data={})
7
+ if data.kind_of?(Hash) && !data.empty?
8
+ @credential = CXML::Credential.new(data['Credential'])
9
+ @user_agent = data['UserAgent']
10
+ end
11
+ end
12
+
13
+ def render(node)
14
+ node.Sender do |n|
15
+ n.UserAgent(@user_agent)
16
+ @credential.render(n)
17
+ end
18
+ node
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ module CXML
2
+ class Status
3
+ attr_accessor :code
4
+ attr_accessor :text
5
+ attr_accessor :xml_lang
6
+
7
+ # Initialize a new Status instance
8
+ # @params data [Hash] optional hash with attributes
9
+ def initialize(data={})
10
+ data = CXML.parse(data) if data.kind_of?(String)
11
+
12
+ if data.kind_of?(Hash) && !data.empty?
13
+ @code = data['code'].to_i
14
+ @text = data['text']
15
+ @xml_lang = data['xml:lang']
16
+ end
17
+ end
18
+
19
+ # Check if status is success
20
+ # @return [Boolean]
21
+ def success?
22
+ [200, 201, 204, 280, 281].include?(code)
23
+ end
24
+
25
+ # Check if status is failure
26
+ # @return [Boolean]
27
+ def failure?
28
+ !success?
29
+ end
30
+
31
+ def render(node)
32
+ node.Status(:code => @code, :text => @text)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module CXML
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe CXML do
4
+
5
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe CXML::Document do
4
+ let(:parser) { CXML::Parser.new }
5
+
6
+ it { should respond_to :version }
7
+ it { should respond_to :payload_id }
8
+ it { should respond_to :timestamp }
9
+
10
+ describe '#parse' do
11
+ it 'sets document attributes' do
12
+ data = parser.parse(fixture('envelope3.xml'))
13
+ doc = nil
14
+
15
+ expect { doc = CXML::Document.new(data) }.not_to raise_error
16
+
17
+ doc.version.should eq(CXML::Protocol::VERSION)
18
+ doc.payload_id.should_not be_nil
19
+ doc.timestamp.should be_a Time
20
+ doc.timestamp.should eq(Time.parse('2012-09-04T02:37:49-05:00'))
21
+
22
+ doc.header.should be_a CXML::Header
23
+ doc.request.should be_a CXML::Request
24
+ doc.response.should be_nil
25
+ end
26
+ end
27
+
28
+ describe '#render' do
29
+ it 'returns and xml result' do
30
+ doc = CXML::Document.new(parser.parse(fixture('envelope3.xml')))
31
+ expect { doc.render }.not_to raise_error
32
+ end
33
+ end
34
+ end
File without changes
@@ -0,0 +1,31 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE cXML SYSTEM "http://xml.cxml.org/schemas/cXML/1.2.014/cXML.dtd">
3
+ <cXML timestamp="2000-12-28T16:56:03-08:00" payloadID="12345666@10.10.83.39">
4
+ <Header>
5
+ <From>
6
+ <Credential domain="DUNS">
7
+ <Identity>123456789</Identity>
8
+ </Credential>
9
+ </From>
10
+ <To>
11
+ <Credential domain="NetworkID">
12
+ <Identity>AN01000000001</Identity>
13
+ </Credential>
14
+ </To>
15
+ <Sender>
16
+ <Credential domain="DUNS">
17
+ <Identity>123456789</Identity>
18
+ <SharedSecret>abracadabra</SharedSecret>
19
+ </Credential>
20
+ </Sender>
21
+ </Header>
22
+ <Request>
23
+ <CatalogUploadRequest operation="new">
24
+ <CatalogName xml:lang="en">Winter Prices</CatalogName>
25
+ <Description xml:lang="en">premiere-level prices</Description>
26
+ <Attachment>
27
+ <URL>cid:part2.PCO28.975@saturn.workchairs.com</URL>
28
+ </Attachment>
29
+ </CatalogUploadRequest>
30
+ </Request>
31
+ </cXML>
@@ -0,0 +1,36 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE cXML SYSTEM "http://xml.cxml.org/schemas/cXML/1.2.014/cXML.dtd">
3
+ <cXML payloadID="123@sendercompany.com"
4
+ timestamp="2003-11-20T23:59:45-07:00">
5
+ <Header>
6
+ <From>
7
+ <!-- Sender -->
8
+ <Credential domain="AribaNetworkUserId">
9
+ <Identity>sender@sendercompany.com</Identity>
10
+ </Credential>
11
+ </From>
12
+ <To>
13
+ <!-- Recipient -->
14
+ <Credential domain="AribaNetworkUserId">
15
+ <Identity>recipient@recipientcompany.com</Identity>
16
+ </Credential>
17
+ </To>
18
+ <Sender>
19
+ <!-- Sender -->
20
+ <Credential domain="AribaNetworkUserId">
21
+ <Identity>sender@sendercompany.com</Identity>
22
+ <SharedSecret>abracadabra</SharedSecret>
23
+ </Credential>
24
+ <UserAgent>Sender Application 1.0</UserAgent>
25
+ </Sender>
26
+ </Header>
27
+ <Request deploymentMode="production">
28
+ <CopyRequest>
29
+ <cXMLAttachment>
30
+ <Attachment>
31
+ <URL>cid:222@sendercompany.com</URL>
32
+ </Attachment>
33
+ </cXMLAttachment>
34
+ </CopyRequest>
35
+ </Request>
36
+ </cXML>
@@ -0,0 +1,77 @@
1
+ <?xml version="1.0"?>
2
+ <cXML version="1.2.011" payloadID="1346769469000.process.162998590@officedepot.com" timestamp="2012-09-04T02:37:49-05:00">
3
+ <Header>
4
+ <From>
5
+ <Credential domain="NetworkID">
6
+ <Identity>AN01000000147</Identity>
7
+ </Credential>
8
+ </From>
9
+ <To>
10
+ <Credential domain="NetworkId">
11
+ <Identity>customerID</Identity>
12
+ </Credential>
13
+ </To>
14
+ <Sender>
15
+ <Credential domain="NetworkID">
16
+ <Identity>AN01000000147</Identity>
17
+ <SharedSecret>welcome</SharedSecret>
18
+ </Credential>
19
+ <UserAgent>BSD 8.12</UserAgent>
20
+ </Sender>
21
+ </Header>
22
+ <Request deploymentMode="production">
23
+ <ConfirmationRequest>
24
+ <ConfirmationHeader confirmID="" operation="new" type="detail" noticeDate="2012-09-04T02:37:49-05:00" invoiceID="623612976001">
25
+ <Contact role="shipTo" addressID="0170-74629-00">
26
+ <Name xml:lang="en-US">HOSPITAL BUILDING</Name>
27
+ <PostalAddress>
28
+ <DeliverTo>Yvonne Smith</DeliverTo>
29
+ <DeliverTo>541813031</DeliverTo>
30
+ <Street>3601 Mile Road</Street>
31
+ <Street>BUILDING: 0170-74629-00</Street>
32
+ <City>Royal Oak</City>
33
+ <State>MI</State>
34
+ <PostalCode>48073</PostalCode>
35
+ <Country isoCountryCode="US">United States</Country>
36
+ </PostalAddress>
37
+ </Contact>
38
+ </ConfirmationHeader>
39
+ <OrderReference orderID="400-2591453-0">
40
+ <DocumentReference payloadID="20120904023747.cXMLPurchaseOrder.984556224@eprosvcs.com"/>
41
+ </OrderReference>
42
+ <ConfirmationItem quantity="20" lineNumber="1">
43
+ <UnitOfMeasure>RM</UnitOfMeasure>
44
+ <ConfirmationStatus quantity="20" type="accept" shipmentDate="2012-09-05T12:00:00-05:00" deliveryDate="2012-09-05T12:00:00-05:00">
45
+ <UnitOfMeasure>RM</UnitOfMeasure>
46
+ <UnitPrice>
47
+ <Money currency="USD">4.44</Money>
48
+ </UnitPrice>
49
+ <Comments></Comments>
50
+ <Extrinsic name="SupplierPartID">751441</Extrinsic>
51
+ </ConfirmationStatus>
52
+ </ConfirmationItem>
53
+ <ConfirmationItem quantity="12" lineNumber="2">
54
+ <UnitOfMeasure>BX</UnitOfMeasure>
55
+ <ConfirmationStatus quantity="12" type="accept" shipmentDate="2012-09-05T12:00:00-05:00" deliveryDate="2012-09-05T12:00:00-05:00">
56
+ <UnitOfMeasure>BX</UnitOfMeasure>
57
+ <UnitPrice>
58
+ <Money currency="USD">1.52</Money>
59
+ </UnitPrice>
60
+ <Comments></Comments>
61
+ <Extrinsic name="SupplierPartID">429175</Extrinsic>
62
+ </ConfirmationStatus>
63
+ </ConfirmationItem>
64
+ <ConfirmationItem quantity="2" lineNumber="3">
65
+ <UnitOfMeasure>EA</UnitOfMeasure>
66
+ <ConfirmationStatus quantity="2" type="accept" shipmentDate="2012-09-05T12:00:00-05:00" deliveryDate="2012-09-05T12:00:00-05:00">
67
+ <UnitOfMeasure>EA</UnitOfMeasure>
68
+ <UnitPrice>
69
+ <Money currency="USD">4.14</Money>
70
+ </UnitPrice>
71
+ <Comments></Comments>
72
+ <Extrinsic name="SupplierPartID">908194</Extrinsic>
73
+ </ConfirmationStatus>
74
+ </ConfirmationItem>
75
+ </ConfirmationRequest>
76
+ </Request>
77
+ </cXML>
@@ -0,0 +1,5 @@
1
+ <cXML version="1.2.011" payloadID="1352382581000.process.454701738@domain.com" timestamp="2012-11-08T01:49:41-05:00">
2
+ <Response>
3
+ <Status code="400" text="java.lang.Exception: missing required parameter 'document'"/>
4
+ </Response>
5
+ </cXML>
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe CXML::Parser do
4
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe CXML::Protocol do
4
+ it { should respond_to :version }
5
+ it { should respond_to :request_elements }
6
+ it { should respond_to :response_elements }
7
+ it { should respond_to :status_codes }
8
+
9
+ context '#version' do
10
+ it 'returns current protocol version' do
11
+ subject.version.should eq('1.2.011')
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ $:.unshift File.expand_path("../..", __FILE__)
2
+
3
+ require 'cxml'
4
+
5
+ RSpec.configure do |conf|
6
+ end
7
+
8
+ def fixture_path(filename=nil)
9
+ path = File.expand_path("../fixtures", __FILE__)
10
+ filename.nil? ? path : File.join(path, filename)
11
+ end
12
+
13
+ def fixture(file)
14
+ File.read(File.join(fixture_path, file))
15
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe CXML::Status do
4
+ describe 'instance' do
5
+ it { should respond_to :code }
6
+ it { should respond_to :text }
7
+ it { should respond_to :xml_lang }
8
+ it { should respond_to :success? }
9
+ it { should respond_to :failure? }
10
+ end
11
+
12
+ describe '#initialize' do
13
+ it 'assigns attributes from string' do
14
+ str = '<Status xml:lang="en-US" code="200" text="OK"></Status>'
15
+ status = CXML::Status.new(str)
16
+
17
+ status.code.should eq(200)
18
+ status.xml_lang.should eq('en-US')
19
+ status.text.should eq('OK')
20
+ end
21
+
22
+ it 'assigns attributes from hash' do
23
+ hash = {'xml:lang' => 'en-US', 'code' => "200", 'text' => 'OK'}
24
+ status = CXML::Status.new(hash)
25
+
26
+ status.code.should eq(200)
27
+ status.xml_lang.should eq('en-US')
28
+ status.text.should eq('OK')
29
+ end
30
+ end
31
+
32
+ describe '#success?' do
33
+ it 'returns true on 2xx codes' do
34
+ CXML::Status.new('code' => '200').success?.should be_true
35
+ CXML::Status.new('code' => '201').success?.should be_true
36
+ CXML::Status.new('code' => '281').success?.should be_true
37
+ end
38
+
39
+ it 'returns false on non 2xx codes' do
40
+ CXML::Status.new('code' => '400').success?.should be_false
41
+ CXML::Status.new('code' => '475').success?.should be_false
42
+ CXML::Status.new('code' => '500').success?.should be_false
43
+ end
44
+ end
45
+
46
+ describe '#failure?' do
47
+ it 'returns false on 2xx codes' do
48
+ CXML::Status.new('code' => '200').failure?.should be_false
49
+ end
50
+ end
51
+ end
metadata ADDED
@@ -0,0 +1,191 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cxml
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dan Sosedoff
9
+ - Geoff Hayes
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-07-06 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rake
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: rspec
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ~>
37
+ - !ruby/object:Gem::Version
38
+ version: '2.11'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: '2.11'
47
+ - !ruby/object:Gem::Dependency
48
+ name: simplecov
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '0.4'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '0.4'
63
+ - !ruby/object:Gem::Dependency
64
+ name: nokogiri
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ - !ruby/object:Gem::Dependency
80
+ name: xml-simple
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :runtime
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: hashr
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Ruby library to work with cXML protocol
112
+ email:
113
+ - dan.sosedoff@gmail.com
114
+ - hayesgm@gmail.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - .gitignore
120
+ - .rspec
121
+ - .travis.yml
122
+ - Gemfile
123
+ - LICENSE
124
+ - README.md
125
+ - Rakefile
126
+ - cxml.gemspec
127
+ - lib/commerce.rb
128
+ - lib/cxml.rb
129
+ - lib/cxml/credential.rb
130
+ - lib/cxml/credential_mac.rb
131
+ - lib/cxml/document.rb
132
+ - lib/cxml/documents/confirmation_request.rb
133
+ - lib/cxml/documents/request_doc.rb
134
+ - lib/cxml/documents/ship_notice_request.rb
135
+ - lib/cxml/errors.rb
136
+ - lib/cxml/header.rb
137
+ - lib/cxml/money.rb
138
+ - lib/cxml/parser.rb
139
+ - lib/cxml/protocol.rb
140
+ - lib/cxml/request.rb
141
+ - lib/cxml/response.rb
142
+ - lib/cxml/sender.rb
143
+ - lib/cxml/status.rb
144
+ - lib/cxml/version.rb
145
+ - spec/cxml_spec.rb
146
+ - spec/document_spec.rb
147
+ - spec/fixtures/.gitkeep
148
+ - spec/fixtures/envelope.xml
149
+ - spec/fixtures/envelope2.xml
150
+ - spec/fixtures/envelope3.xml
151
+ - spec/fixtures/response_status_400.xml
152
+ - spec/parser_spec.rb
153
+ - spec/protocol_spec.rb
154
+ - spec/spec_helper.rb
155
+ - spec/status_spec.rb
156
+ homepage: http://github.com/sosedoff/cxml
157
+ licenses: []
158
+ post_install_message:
159
+ rdoc_options: []
160
+ require_paths:
161
+ - lib
162
+ required_ruby_version: !ruby/object:Gem::Requirement
163
+ none: false
164
+ requirements:
165
+ - - ! '>='
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ required_rubygems_version: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ requirements: []
175
+ rubyforge_project:
176
+ rubygems_version: 1.8.25
177
+ signing_key:
178
+ specification_version: 3
179
+ summary: Ruby library to work with cXML protocol
180
+ test_files:
181
+ - spec/cxml_spec.rb
182
+ - spec/document_spec.rb
183
+ - spec/fixtures/.gitkeep
184
+ - spec/fixtures/envelope.xml
185
+ - spec/fixtures/envelope2.xml
186
+ - spec/fixtures/envelope3.xml
187
+ - spec/fixtures/response_status_400.xml
188
+ - spec/parser_spec.rb
189
+ - spec/protocol_spec.rb
190
+ - spec/spec_helper.rb
191
+ - spec/status_spec.rb