From: Patrick Chanezon Date: 2005-06-23T22:52:42+09:00 Subject: soap4r mapping problem when using google adwords api The Google Adwords sample that Nahi included in the samples at http://dev.ctor.org/soap4r/file/trunk/sample/wsdl/googleAdwords/client.rb does not work. In the course of fixing it I ran into a roadblock. Here's my current fix. I highlight the problems I have fixed, and areas where I'm not very happy with the fix I found. --- #!/usr/local/bin/ruby require 'soap/wsdlDriver' require 'soap/header/simplehandler' #wsdl = 'https://adwords.google.com/api/adwords/v2/CampaignService?WSDL' wsdl = 'CampaignService.wsdl' class HeaderHandler < SOAP::Header::SimpleHandler def initialize(tag, value) super(XSD::QName.new(nil, tag)) @tag = tag @value = value end #the initial handler from the sample was wrong, it generated 2 level of tags def on_simple_outbound @value end end #problem: wsdl2ruby does not pick up soap/property file, so yields a certificate error when running against this https url #run it on local file # To generate deafult.rb, do like this; # % wsdl2ruby.rb --wsdl "https://adwords.google.com/api/adwords/v2/CampaignService?WSDL" --classdef --force require 'default' # I don't have an account of AdWords so the following code is not tested. # Please tell me (nahi@ruby-lang.org) if you will get good/bad result in # communicating with AdWords Server... drv = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver #problem: http protocol properties set in soap/property from rubyhome are not picked up drv.loadproperty('soap/property') #print drv.options["client"]["ssl_config"].inspect #problem: wiredump_dev does not output anything when running in SSL mode drv.wiredump_dev = STDOUT #the file based logger works drv.wiredump_file_base = "log" drv.headerhandler << HeaderHandler.new('email', 'patlist@chanezon.com') drv.headerhandler << HeaderHandler.new('password', 'your_password_here') drv.headerhandler << HeaderHandler.new('useragent', 'P@ playing with the API from ruby') drv.headerhandler << HeaderHandler.new('token', 'your_token_here') camplist = drv.call("getAllAdWordsCampaigns", GetAllAdWordsCampaigns.new(123)) print camplist --- When I run that with a valid adwords developer account, the call is made and the correct repsonse is sent back, as seen in the filke log. However the mapper barfs. I incldue the relevant data. Any idea of where to look for next is welcome. I run soap4r soap4r-1_5_4, which seem to have fixed a few things regarding doc/literal encoding style. stack trace /usr/lib/ruby/1.8/soap/mapping/wsdlliteralregistry.rb:73: warning: Object#type is deprecated; use Object#class /usr/lib/ruby/1.8/soap/mapping/wsdlliteralregistry.rb:73:in `soap2obj': cannot map SOAP::SOAPElement to Ruby object (SOAP::Mapping::MappingError) from /usr/lib/ruby/1.8/soap/mapping/mapping.rb:126:in `_soap2obj' from /usr/lib/ruby/1.8/soap/mapping/mapping.rb:49:in `soap2obj' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:417:in `response_doc_lit' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:416:in `collect' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:416:in `each' from /usr/lib/ruby/1.8/soap/baseData.rb:473:in `each' from /usr/lib/ruby/1.8/soap/baseData.rb:473:in `each' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:416:in `collect' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:416:in `response_doc_lit' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:382:in `response_doc' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:289:in `response_obj' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:129:in `call' from /usr/lib/ruby/1.8/soap/rpc/driver.rb:177:in `call' from ./client.rb:44 in soap2obj # the trace in soap2obj is a print node.inspect here is the response log 233 6000213Campaign #1Active2005-04-16T21:27:50.000Z2011-01-01T07:59:59.000Z1000000truetrueen ... It seems to barf on Here is the rb file generated by wsdl2ruby require 'xsd/qname' # {https://adwords.google.com/api/adwords/v2}getCampaign class GetCampaign @@schema_type = "getCampaign" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["id", "SOAP::SOAPInt"]] attr_accessor :id def initialize(id = nil) @id = id end end # {https://adwords.google.com/api/adwords/v2}getCampaignResponse class GetCampaignResponse @@schema_type = "getCampaignResponse" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["getCampaignReturn", "Campaign"]] attr_accessor :getCampaignReturn def initialize(getCampaignReturn = nil) @getCampaignReturn = getCampaignReturn end end # {https://adwords.google.com/api/adwords/v2}getCampaignList class GetCampaignList @@schema_type = "getCampaignList" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["ids", "SOAP::SOAPInt[]"]] attr_accessor :ids def initialize(ids = []) @ids = ids end end # {https://adwords.google.com/api/adwords/v2}getCampaignListResponse class GetCampaignListResponse @@schema_type = "getCampaignListResponse" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["getCampaignListReturn", "Campaign[]"]] attr_accessor :getCampaignListReturn def initialize(getCampaignListReturn = []) @getCampaignListReturn = getCampaignListReturn end end # {https://adwords.google.com/api/adwords/v2}getAllAdWordsCampaigns class GetAllAdWordsCampaigns @@schema_type = "getAllAdWordsCampaigns" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["dummy", "SOAP::SOAPInt"]] attr_accessor :dummy def initialize(dummy = nil) @dummy = dummy end end # {https://adwords.google.com/api/adwords/v2}getAllAdWordsCampaignsResponse class GetAllAdWordsCampaignsResponse @@schema_type = "getAllAdWordsCampaignsResponse" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["getAllAdWordsCampaignsReturn", "Campaign[]"]] attr_accessor :getAllAdWordsCampaignsReturn def initialize(getAllAdWordsCampaignsReturn = []) @getAllAdWordsCampaignsReturn = getAllAdWordsCampaignsReturn end end # {https://adwords.google.com/api/adwords/v2}addCampaign class AddCampaign @@schema_type = "addCampaign" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["campaign", "Campaign"]] attr_accessor :campaign def initialize(campaign = nil) @campaign = campaign end end # {https://adwords.google.com/api/adwords/v2}addCampaignResponse class AddCampaignResponse @@schema_type = "addCampaignResponse" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["addCampaignReturn", "Campaign"]] attr_accessor :addCampaignReturn def initialize(addCampaignReturn = nil) @addCampaignReturn = addCampaignReturn end end # {https://adwords.google.com/api/adwords/v2}addCampaignList class AddCampaignList @@schema_type = "addCampaignList" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["campaigns", "Campaign[]"]] attr_accessor :campaigns def initialize(campaigns = []) @campaigns = campaigns end end # {https://adwords.google.com/api/adwords/v2}addCampaignListResponse class AddCampaignListResponse @@schema_type = "addCampaignListResponse" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["addCampaignListReturn", "Campaign[]"]] attr_accessor :addCampaignListReturn def initialize(addCampaignListReturn = []) @addCampaignListReturn = addCampaignListReturn end end # {https://adwords.google.com/api/adwords/v2}updateCampaign class UpdateCampaign @@schema_type = "updateCampaign" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["campaign", "Campaign"]] attr_accessor :campaign def initialize(campaign = nil) @campaign = campaign end end # {https://adwords.google.com/api/adwords/v2}updateCampaignResponse class UpdateCampaignResponse @@schema_type = "updateCampaignResponse" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [] def initialize end end # {https://adwords.google.com/api/adwords/v2}updateCampaignList class UpdateCampaignList @@schema_type = "updateCampaignList" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["campaigns", "Campaign[]"]] attr_accessor :campaigns def initialize(campaigns = []) @campaigns = campaigns end end # {https://adwords.google.com/api/adwords/v2}updateCampaignListResponse class UpdateCampaignListResponse @@schema_type = "updateCampaignListResponse" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [] def initialize end end # {https://adwords.google.com/api/adwords/v2}getOptimizeAdServing class GetOptimizeAdServing @@schema_type = "getOptimizeAdServing" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["campaignId", "SOAP::SOAPInt"]] attr_accessor :campaignId def initialize(campaignId = nil) @campaignId = campaignId end end # {https://adwords.google.com/api/adwords/v2}getOptimizeAdServingResponse class GetOptimizeAdServingResponse @@schema_type = "getOptimizeAdServingResponse" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["getOptimizeAdServingReturn", "SOAP::SOAPBoolean"]] attr_accessor :getOptimizeAdServingReturn def initialize(getOptimizeAdServingReturn = nil) @getOptimizeAdServingReturn = getOptimizeAdServingReturn end end # {https://adwords.google.com/api/adwords/v2}setOptimizeAdServing class SetOptimizeAdServing @@schema_type = "setOptimizeAdServing" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["campaignId", "SOAP::SOAPInt"], ["enable", "SOAP::SOAPBoolean"]] attr_accessor :campaignId attr_accessor :enable def initialize(campaignId = nil, enable = nil) @campaignId = campaignId @enable = enable end end # {https://adwords.google.com/api/adwords/v2}setOptimizeAdServingResponse class SetOptimizeAdServingResponse @@schema_type = "setOptimizeAdServingResponse" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [] def initialize end end # {https://adwords.google.com/api/adwords/v2}getCampaignStats class GetCampaignStats @@schema_type = "getCampaignStats" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["campaignIds", "SOAP::SOAPInt[]"], ["start", "SOAP::SOAPDateTime"], ["v_end", ["SOAP::SOAPDateTime", XSD::QName.new(nil, "end")]]] attr_accessor :campaignIds attr_accessor :start def end @v_end end def end=(value) @v_end = value end def initialize(campaignIds = [], start = nil, v_end = nil) @campaignIds = campaignIds @start = start @v_end = v_end end end # {https://adwords.google.com/api/adwords/v2}getCampaignStatsResponse class GetCampaignStatsResponse @@schema_type = "getCampaignStatsResponse" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["getCampaignStatsReturn", "StatsRecord[]"]] attr_accessor :getCampaignStatsReturn def initialize(getCampaignStatsReturn = []) @getCampaignStatsReturn = getCampaignStatsReturn end end # {https://adwords.google.com/api/adwords/v2}Keyword class Keyword @@schema_type = "Keyword" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["type", "KeywordType"], ["maxCpc", "SOAP::SOAPLong"], ["adGroupId", "SOAP::SOAPInt"], ["language", "SOAP::SOAPString"], ["status", "KeywordStatus"], ["negative", "SOAP::SOAPBoolean"], ["destinationUrl", "SOAP::SOAPString"], ["text", "SOAP::SOAPString"], ["id", "SOAP::SOAPInt"], ["exemptionRequest", "SOAP::SOAPString"]] attr_accessor :type attr_accessor :maxCpc attr_accessor :adGroupId attr_accessor :language attr_accessor :status attr_accessor :negative attr_accessor :destinationUrl attr_accessor :text attr_accessor :id attr_accessor :exemptionRequest def initialize(type = nil, maxCpc = nil, adGroupId = nil, language = nil, status = nil, negative = nil, destinationUrl = nil, text = nil, id = nil, exemptionRequest = nil) @type = type @maxCpc = maxCpc @adGroupId = adGroupId @language = language @status = status @negative = negative @destinationUrl = destinationUrl @text = text @id = id @exemptionRequest = exemptionRequest end end # {https://adwords.google.com/api/adwords/v2}LanguageTarget class LanguageTarget < ::Array @@schema_type = "string" @@schema_ns = "http://www.w3.org/2001/XMLSchema" end # {https://adwords.google.com/api/adwords/v2}GeoTarget class GeoTarget @@schema_type = "GeoTarget" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["countries", "SOAP::SOAPString[]"], ["regions", "SOAP::SOAPString[]"], ["metros", "SOAP::SOAPString[]"], ["cities", "SOAP::SOAPString[]"]] attr_accessor :countries attr_accessor :regions attr_accessor :metros attr_accessor :cities def initialize(countries = [], regions = [], metros = [], cities = []) @countries = countries @regions = regions @metros = metros @cities = cities end end # {https://adwords.google.com/api/adwords/v2}Campaign class Campaign @@schema_type = "Campaign" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["id", "SOAP::SOAPInt"], ["name", "SOAP::SOAPString"], ["status", "CampaignStatus"], ["startDate", "SOAP::SOAPDateTime"], ["endDate", "SOAP::SOAPDateTime"], ["dailyBudget", "SOAP::SOAPLong"], ["optInSearchNetwork", "SOAP::SOAPBoolean"], ["optInContentNetwork", "SOAP::SOAPBoolean"], ["campaignNegativeKeywords", "Keyword[]"], ["languageTargeting", "LanguageTarget"], ["geoTargeting", "GeoTarget"]] attr_accessor :id attr_accessor :name attr_accessor :status attr_accessor :startDate attr_accessor :endDate attr_accessor :dailyBudget attr_accessor :optInSearchNetwork attr_accessor :optInContentNetwork attr_accessor :campaignNegativeKeywords attr_accessor :languageTargeting attr_accessor :geoTargeting def initialize(id = nil, name = nil, status = nil, startDate = nil, endDate = nil, dailyBudget = nil, optInSearchNetwork = nil, optInContentNetwork = nil, campaignNegativeKeywords = [], languageTargeting = nil, geoTargeting = nil) @id = id @name = name @status = status @startDate = startDate @endDate = endDate @dailyBudget = dailyBudget @optInSearchNetwork = optInSearchNetwork @optInContentNetwork = optInContentNetwork @campaignNegativeKeywords = campaignNegativeKeywords @languageTargeting = languageTargeting @geoTargeting = geoTargeting end end # {https://adwords.google.com/api/adwords/v2}ApiException class ApiException @@schema_type = "ApiException" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["code", "SOAP::SOAPInt"], ["internal", "SOAP::SOAPBoolean"], ["message", "SOAP::SOAPString"], ["trigger", "SOAP::SOAPString"], ["violations", "SOAP::SOAPString"]] attr_accessor :code attr_accessor :internal attr_accessor :message attr_accessor :trigger attr_accessor :violations def initialize(code = nil, internal = nil, message = nil, trigger = nil, violations = nil) @code = code @internal = internal @message = message @trigger = trigger @violations = violations end end # {https://adwords.google.com/api/adwords/v2}StatsRecord class StatsRecord @@schema_type = "StatsRecord" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["averagePosition", "SOAP::SOAPDouble"], ["clicks", "SOAP::SOAPLong"], ["conversionRate", "SOAP::SOAPDouble"], ["conversions", "SOAP::SOAPLong"], ["cost", "SOAP::SOAPLong"], ["id", "SOAP::SOAPLong"], ["impressions", "SOAP::SOAPLong"]] attr_accessor :averagePosition attr_accessor :clicks attr_accessor :conversionRate attr_accessor :conversions attr_accessor :cost attr_accessor :id attr_accessor :impressions def initialize(averagePosition = nil, clicks = nil, conversionRate = nil, conversions = nil, cost = nil, id = nil, impressions = nil) @averagePosition = averagePosition @clicks = clicks @conversionRate = conversionRate @conversions = conversions @cost = cost @id = id @impressions = impressions end end # {https://adwords.google.com/api/adwords/v2}KeywordStatus module KeywordStatus Deleted = "Deleted" Disabled = "Disabled" Disapproved = "Disapproved" InTrial = "InTrial" Normal = "Normal" OnHold = "OnHold" end # {https://adwords.google.com/api/adwords/v2}KeywordType module KeywordType Broad = "Broad" Exact = "Exact" Phrase = "Phrase" end # {https://adwords.google.com/api/adwords/v2}CampaignStatus module CampaignStatus Active = "Active" Deleted = "Deleted" Ended = "Ended" Paused = "Paused" Pending = "Pending" Suspended = "Suspended" end The error happens in soap/mapping/wsdlliteralregistry.rb # node should be a SOAPElement def soap2obj(node, obj_class = nil) print "in soap2obj #{node.inspect}" unless obj_class.nil? raise MappingError.new("must not reach here") end begin return any2obj(node) rescue MappingError end if @excn_handler_soap2obj begin return @excn_handler_soap2obj.call(node) { |yield_node| Mapping._soap2obj(yield_node, self) } rescue Exception end end raise MappingError.new("cannot map #{node.type.name} to Ruby object") end I guess the problem must lie in the fact that the class GetAllAdWordsCampaignsResponse is not known to the registry, to be applied to the response element {https://adwords.google.com/api/adwords/v2}getAllAdWordsCampaignsResponse> # {https://adwords.google.com/api/adwords/v2}getAllAdWordsCampaignsResponse class GetAllAdWordsCampaignsResponse @@schema_type = "getAllAdWordsCampaignsResponse" @@schema_ns = "https://adwords.google.com/api/adwords/v2" @@schema_element = [["getAllAdWordsCampaignsReturn", "Campaign[]"]] How is this registry informed about the classes that must be used for mapping when using the wsdl files? Is there a message to be sent to drv in order to let it know about default.rb classes? A few hours later after finding this other sample I now generate the client driver myself using wsdl2ruby wsdl2ruby.rb --wsdl CampaignService.wsdl --type client Now I understand where the mapping is defined. Still the new program yields the same error --- #!/usr/local/bin/ruby require 'soap/wsdlDriver' require 'soap/header/simplehandler' wsdl = 'https://adwords.google.com/api/adwords/v2/CampaignService?WSDL' class HeaderHandler < SOAP::Header::SimpleHandler def initialize(tag, value) super(XSD::QName.new(nil, tag)) @tag = tag @value = value end #the initial handler from the sample was wrong, it generated 2 level of tags def on_simple_outbound @value end end #problem: wsdl2ruby does not pick up soap/property file, so yields a certificate error when running against this https url #run it on local file # To generate deafult.rb, do like this; # % wsdl2ruby.rb --wsdl "https://adwords.google.com/api/adwords/v2/CampaignService?WSDL" --classdef --force #require 'default' # I don't have an account of AdWords so the following code is not tested. # Please tell me (nahi@ruby-lang.org) if you will get good/bad result in # communicating with AdWords Server... #drv = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver require 'defaultDriver.rb' require 'yaml' prefs = YAML::load( File.open('adwords.yaml', 'r')) headers = prefs['headers'] drv = CampaignService.new() #problem: http protocol properties set in soap/property from rubyhome are not picked up drv.loadproperty('soap/property') #problem: wiredump_dev does not output anything when running in SSL mode #drv.wiredump_dev = STDOUT #the file based logger works drv.wiredump_file_base = "log" headers.each {|key, value| drv.headerhandler << HeaderHandler.new(key, value)} puts drv.getAllAdWordsCampaigns(GetAllAdWordsCampaigns.new(123)) --- /usr/lib/ruby/1.8/soap/mapping/wsdlliteralregistry.rb:73:in `soap2obj': cannot map SOAP::SOAPElement to Ruby object (SOAP::Mapping::MappingError) from /usr/lib/ruby/1.8/soap/mapping/mapping.rb:126:in `_soap2obj' from /usr/lib/ruby/1.8/soap/mapping/mapping.rb:49:in `soap2obj' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:417:in `response_doc_lit' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:416:in `collect' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:416:in `each' from /usr/lib/ruby/1.8/soap/baseData.rb:473:in `each' from /usr/lib/ruby/1.8/soap/baseData.rb:473:in `each' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:416:in `collect' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:416:in `response_doc_lit' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:382:in `response_doc' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:289:in `response_obj' from /usr/lib/ruby/1.8/soap/rpc/proxy.rb:129:in `call' from /usr/lib/ruby/1.8/soap/rpc/driver.rb:177:in `call' from /usr/lib/ruby/1.8/soap/rpc/driver.rb:231:in `getAllAdWordsCampaigns' from /usr/lib/ruby/1.8/soap/rpc/driver.rb:226:in `getAllAdWordsCampaigns' from ./client.rb:39 in soap2obj # It is as if the mapping defined in CampaignService is not used. Thanks in advance for any hints. P@