Giter VIP home page Giter VIP logo

wash_out's Introduction

WashOut

WashOut is a gem that greatly simplifies creation of SOAP service providers.

Gem Version Travis CI Code Climate

But if you have a chance, please http://stopsoap.com/.

Compatibility

Rails 4.0 and higher is tested. Code is known to work with earlier versions but we don't bother testing outdated versions anymore - give it a try if you are THAT unlucky.

Installation

In your Gemfile, add this line:

gem 'wash_out'

Please read release details if you are upgrading. We break backward compatibility between large ticks but you can expect it to be specified at release notes.

Usage

A SOAP endpoint in WashOut is simply a Rails controller which includes the module WashOut::SOAP. Each SOAP action corresponds to a certain controller method; this mapping, as well as the argument definition, is defined by soap_action method. Check the method documentation for complete info; here, only a few examples will be demonstrated.

# app/controllers/rumbas_controller.rb
class RumbasController < ApplicationController
  soap_service namespace: 'urn:WashOut'

  # Simple case
  soap_action "integer_to_string",
              :args   => :integer,
              :return => :string
  def integer_to_string
    render :soap => params[:value].to_s
  end

  soap_action "concat",
              :args   => { :a => :string, :b => :string },
              :return => :string
  def concat
    render :soap => (params[:a] + params[:b])
  end

  # Complex structures
  soap_action "AddCircle",
              :args   => { :circle => { :center => { :x => :integer,
                                                     :y => :integer },
                                        :radius => :double } },
              :return => nil, # [] for wash_out below 0.3.0
              :to     => :add_circle
  def add_circle
    circle = params[:circle]

    raise SOAPError, "radius is too small" if circle[:radius] < 3.0

    Circle.new(circle[:center][:x], circle[:center][:y], circle[:radius])

    render :soap => nil
  end

  # Arrays
  soap_action "integers_to_boolean",
              :args => { :data => [:integer] },
              :return => [:boolean]
  def integers_to_boolean
    render :soap => params[:data].map{|i| i > 0}
  end

  # Params from XML attributes;
  # e.g. for a request to the 'AddCircle' action:
  #   <soapenv:Envelope>
  #     <soapenv:Body>
  #       <AddCircle>
  #         <Circle radius="5.0">
  #           <Center x="10" y="12" />
  #         </Circle>
  #       </AddCircle>
  #     </soapenv:Body>
  #   </soapenv:Envelope>
  soap_action "AddCircle",
              :args   => { :circle => { :center => { :@x => :integer,
                                                     :@y => :integer },
                                        :@radius => :double } },
              :return => nil, # [] for wash_out below 0.3.0
              :to     => :add_circle
  def add_circle
    circle = params[:circle]
    Circle.new(circle[:center][:x], circle[:center][:y], circle[:radius])

    render :soap => nil
  end

  # With a customised input tag name, in case params are wrapped;
  # e.g. for a request to the 'IntegersToBoolean' action:
  #   <soapenv:Envelope>
  #     <soapenv:Body>
  #       <MyRequest>  <!-- not <IntegersToBoolean> -->
  #         <Data>...</Data>
  #       </MyRequest>
  #     </soapenv:Body>
  #   </soapenv:Envelope>
  soap_action "integers_to_boolean",
              :args => { :my_request => { :data => [:integer] } },
              :as => 'MyRequest',
              :return => [:boolean]

  # You can use all Rails features like filtering, too. A SOAP controller
  # is just like a normal controller with a special routing.
  before_filter :dump_parameters
  def dump_parameters
    Rails.logger.debug params.inspect
  end
  
  
  # Rendering SOAP headers
  soap_action "integer_to_header_string",
              :args   => :integer,
              :return => :string,
              :header_return => :string
  def integer_to_header_string
    render :soap => params[:value].to_s, :header => (params[:value]+1).to_s
  end
  
  # Reading SOAP Headers
  # This is different than normal SOAP params, because we don't specify the incoming format of the header,
  # but we can still access it through `soap_request.headers`.  Note that the values are all strings or hashes.
  soap_action "AddCircleWithHeaderRadius",
              :args   => { :circle => { :center => { :x => :integer,
                                                     :y => :integer } } },
              :return => nil, # [] for wash_out below 0.3.0
              :to     => :add_circle
  # e.g. for a request to the 'AddCircleWithHeaderRadius' action:
  #   <soapenv:Envelope>
  #     <soap:Header>
  #       <radius>12345</radius>
  #     </soap:Header>
  #     <soapenv:Body>
  #       <AddCircle>
  #         <Circle radius="5.0">
  #           <Center x="10" y="12" />
  #         </Circle>
  #       </AddCircle>
  #     </soapenv:Body>
  #   </soapenv:Envelope>
  def add_circle_with_header_radius
    circle = params[:circle]
    radius = soap_request.headers[:radius]
    raise SOAPError, "radius must be specified in the SOAP header" if radius.blank?
    radius = radius.to_f
    raise SOAPError, "radius is too small" if radius < 3.0

    Circle.new(circle[:center][:x], circle[:center][:y], radius)

    render :soap => nil
  end
  
end
# config/routes.rb
WashOutSample::Application.routes.draw do
  wash_out :rumbas
end

In such a setup, the generated WSDL may be queried at path /rumbas/wsdl. So, with a gem like Savon, a request can be done using this path:

require 'savon'

client = Savon::Client.new(wsdl: "http://localhost:3000/rumbas/wsdl")

client.operations # => [:integer_to_string, :concat, :add_circle]

result = client.call(:concat, message: { :a => "123", :b => "abc" })

# actual wash_out
result.to_hash # => {:concat_reponse => {:value=>"123abc"}}

# wash_out below 0.3.0 (and this is malformed response so please update)
result.to_hash # => {:value=>"123abc"}

Reusable types

Basic inline types definition is fast and furious for the simple cases. You have an option to describe SOAP types inside separate classes for the complex ones. Here's the way to do that:

class Fluffy < WashOut::Type
  map :universe => {
        :name => :string,
        :age  => :integer
      }
end

class FluffyContainer < WashOut::Type
  type_name 'fluffy_con'
  map :fluffy => Fluffy
end

To use defined type inside your inline declaration, pass the class instead of type symbol (:fluffy => Fluffy).

Note that WashOut extends the ActiveRecord so every model you use is already a WashOut::Type and can be used inside your interface declarations.

WSSE Authentication

WashOut provides two mechanism for WSSE Authentication.

Static Authentication

You can configure the service to validate against a username and password with the following configuration:

soap_service namespace: "wash_out", wsse_username: "username", wsse_password: "password"

With this mechanism, all the actions in the controller will be authenticated against the specified username and password. If you need to authenticate different users, you can use the dynamic mechanism described below.

Dynamic Authentication

Dynamic authentication allows you to process the username and password any way you want, with the most common case being authenticating against a database. The configuration option for this mechanism is called wsse_auth_callback:

soap_service namespace: "wash_out", wsse_auth_callback: ->(username, password) {
  return !User.find_by(username: username).authenticate(password).blank?
}

Keep in mind that the password may already be hashed by the SOAP client, so you would have to check against that condition too as per spec

Configuration

Use config.wash_out... inside your environment configuration to setup WashOut globally. To override the values on a specific controller just add an override as part of the arguments to the soap_service method.

Available properties are:

  • parser: XML parser to use – :rexml or :nokogiri. The first one is default but the latter is much faster. Be sure to add gem nokogiri if you want to use it.
  • wsdl_style: sets WSDL style. Supported values are: 'document' and 'rpc'.
  • catch_xml_errors: intercept Rails parsing exceptions to return correct XML response for corrupt XML input. Default is false.
  • namespace: SOAP namespace to use. Default is urn:WashOut.
  • snakecase_input: Determines if WashOut should modify parameters keys to snakecase. Default is false.
  • camelize_wsdl: Determines if WashOut should camelize types within WSDL and responses. Supports true for CamelCase and :lower for camelCase. Default is false.
  • service_name: Allows to define a custom name for the SOAP service. By default, the name is set as service.

Camelization

Note that WSDL camelization will affect method names but only if they were given as a symbol:

soap_action :foo  # this will be affected
soap_action "foo" # this will be passed as is

Maintainers

Contributors (in random order)

License

It is free software, and may be redistributed under the terms of MIT license.

Bitdeli Badge

wash_out's People

Contributors

alexpetrov avatar badlamer avatar bogdanrada avatar clockwerx avatar ebeigarts avatar ericduminil avatar gorism avatar gsmetal avatar inossidabile avatar jasonbarnabe avatar jdsampayo avatar jeandenis-k avatar jesusabarca avatar jhonathas avatar klausmeyer avatar leonardoherbert avatar lostapathy avatar mbenabda avatar meanphil avatar merhard avatar mhenrixon avatar mhutter avatar rking avatar rmeritz avatar seawolf avatar sergiocampama avatar shlensky avatar simonkaluza avatar whitequark avatar yjukaku avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wash_out's Issues

stubs from a given WSDL

It would be too audacious to call it a feature request, but it would be great to have a something (a generator?) which generates controller stubs from a WSDL file.
Any ideas in this direction will be appreciated.
If this is the wrong place for such a discussion, please say so.
Thanks.

What to do on empty ENV['HTTP_SOAPACTION']?

Hey

I'm integrating a SOAP service to follow an already given WSDL. I know, wrong way, but.. äh.. well...

Anyway, I set up wash_out, controller, action etc. But shooting some given test request just fail. Any calls end up in #_invalid_action. After digging around I found out that's due to the fact that HTTP_SOAPACTION isn't filled within header -> ENV['HTTP_SOAPACTION'] is just an empty string -> WashOut::Router end in #_invalid_action

Q: is an empty HTTP_SOAPACTION against SOAP spec aka. the test request ist wrong, or is it up to the sever to handle this case?

cheers! (and thanks for wash_out it's promising!)

xsi:minOccurs vs minOccurs

Apparently, .NET doesn't like xsi: before minOccurs. I tried searching for xsi:minOccurs but couldn't find any examples of it. Most examples I found only uses "minOccurs". What do you think?

From app/helpers/wash_out_helper.rb

def wsdl_occurence(param, extend_with = {})
data = !param.multiplied ? {} : {
"xsi:minOccurs" => 0,
"xsi:maxOccurs" => 'unbounded'
}

extend_with.merge(data)

end

:integer vs :int

Our C# collagues informed us that using :integer also breaks C# code, it seems that :int works. Maybe that should be investigated and changed in examples?

Routing error: undefined local var `type'.

The error in /api/wsdl:

ActionController::RoutingError (undefined local variable or method `type' for WashOut::Param:Class):
  app/controllers/api_controller.rb:8:in `<class:ApiController>'
  app/controllers/api_controller.rb:1:in `<top (required)>
BintraMongo::Application.routes.draw do
  # The priority is based upon order of creation:
  # first created -> highest priority.
  root :to => "home#index"

  wash_out :api

  match '/test', :to => 'foo#test'

  resources :users   
  devise_for :user

  match ':controller(/:action(/:id))'
end

The controller:

class ApiController < ApplicationController
  include WashOut::SOAP

  soap_action "authenticate",
              :args    => {:login => :string, :password => :string, :comment => :string},
              :return  => User
  def authenticate
    # find user by params[:login] and params[:password]
    puts params
  end
end

Versions (full list):

Using rails (3.2.0)
Using wasabi (2.1.0) 
Using savon (0.9.9)
Using wash_out (0.3.7) from git://github.com/roundlake/wash_out.git (at master)

Fails when expected structure not given

While testing I found an error where WashOut fails if an expected struct(Hash) is missing. In this case it calls with_indifferent_access on nil or empty string which obviously fails:
https://github.com/roundlake/wash_out/blob/master/lib/wash_out/param.rb#L159

Here's a spec to reproduce:
https://github.com/rngtng/wash_out/commit/54eaf8c80c82c7d94a65bc997aee5334817f82df

My question is how to fix this correctly? What's the expected behavoir when wash_out get data which doesn't align to the expected structure??

can you change license?

sorry for my poor english

can you change license? like GPL or LGPL , even AGPL...
the license I can't fork and change anything...even fix bug or new features

Handling upper- and lower-case properly

I have been having some trouble handling upper- and lower-case when it is converted to snake-case.

<APIusername>username</APIusername>
<APIpassword>password</APIpassword>
<transID>Transaction_ID</transID>
<amount>amount_paid</amount>
<referenceField>Item_Code</referenceField>
<MSISDN>customer_Mobile_number</MSISDN>

I can get the "amount" just fine with the following code:

  soap_action "source_push",
               :args => { 
                  :transID => :string, 
                  :amount => :string, 
                  :referenceField => :string, 
                  :MSISDN => :string 
                  },
:return => { 
                  :transID => :string, 
                  :amount => :string, 
                  :referenceField => :string, 
                  :MSISDN => :string 
                  :message => :string}

 def source
     transaction = Transaction.new(
                                   :transaction_id => params[:trans_id],
                                   :amount => params["amount"],
                                   :reference_field => params[:referenceField],
                                   :MSISDN => params["MSISDN"]
                                   )
end

I have tried a few different combinations but cannot seem to get the case-settings to work. Any ideas?

Thanks a lot for this gem by the way.

Testing wash_out SOAP-services usingn cucumber ...

Hello
My Rails 3.2.X application use wash_out and set several SOAP-services.
I use cucumber and want to test my SOAP-services using savon gem.
For testing general rails app I use methods: visit, get, post etc...
But savon requires real URL to SOAP service.
For example:
client = Savon.client (http://localhost:300/my_soap_service)

But I want use:
client = Savon.client (my_soap_path) or client = Savon.client (my_soap_url)

Or how I can start rails app in test mode before start cucumber tests?
Do you know how test wash_out SOAP-services from cucumber steps-definitions using
simple method: visit, get, post etc...

API versioning

Any tips on how to have multiple versions of the SOAP API running smoothly?

Preferably in a way that enables you to only introduce new features and overwrite old ones without having to clone the whole thing for each version.

Something like this perhaps:

class V1Controller
  include SoapAPI::Base
end

class V2Controller 
  include SoapAPI::V1 
end

module SoapAPI
  module V1
    include Base
  end

  module V2
    include V1
  end
end

Suggest a way I can use wash-out

Currently the WSDL generated by the application using wash-out is:

xsd:sequence /xsd:sequence /xsd:complexType xsd:sequence /xsd:sequence /xsd:complexType xsd:sequence /xsd:sequence /xsd:complexType xsd:sequence /xsd:sequence /xsd:complexType xsd:sequence /xsd:sequence /xsd:complexType xsd:sequence /xsd:sequence /xsd:complexType xsd:sequence /xsd:sequence /xsd:complexType /xsd:schema

Is there any way, I can specify that the 1 or more elements are not mandatory. How can I achieve this? Please suggest a way.

Nil param error (without Savon gem)

If you use WashOut without Savon, you'll come to a Nil param error.

In lib/wash_out/dispatcher.rb when parsing request, a params[:envelope][:body] is expected to be present (hash keys are symbols downcase).
But the hash resulting contains params["Envelope"]["Body"] (keys are strings not downcase)

You have to configure Nori to convert xml tags as expected while parsing soap request.

If you add Savon as dependency (as in https://github.com/roundlake/wash_out-sample ), the problem is solved.
Savon includes (in lib/savon/soap/xml.rb) the following lines

Nori.configure do |config|
  config.strip_namespaces = true
  config.convert_tags_to { |tag| tag.snakecase.to_sym }
end

that convert tags as expected.

ActiveRecord associations (has_one, has_many) not being rendered

Hi,

I am trying to use washout to define an soap interface that includes a "get" method that has to return a nested ActiveRecord model similar to one shown below:

class Post < ActiveRecord::Base
belongs_to :discussion_thread
has_one :owner, :dependent => :destroy, :autosave => true
has_many :viewers, :dependent => :destroy, :autosave => true
attr_accessible :title, :etc
end

But the following code only renders a list of Post objects, but not the related owner and viewers information.

soap_action "getPosts",
:args => :integer,
:return => [Post],
:to => :get_posts
def get_posts
thread = DiscussionThread.find_by_thread_id(params[:value])
posts = thread.posts.includes(:owner,:viewers).all
# log the rerieved post info - confirms that owner and viewers info is retrieved
render :soap => posts
end

Is there anything equivalent to:
render :xml => posts.to_xml(:include=>[:owner,:viewers])

Regards
karthik

Nil param when calling soap action (from the example)

I'm running:

  • Rails 3.1.3
  • wash_out 0.2.3
  • Ruby 1.9.3

And I'm getting the following error (server-side) when calling either concat or integer_to_string:

NoMethodError (You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]): 
class ApiController < ApplicationController

  include WashOut::SOAP

  soap_action "integer_to_string",
              :args   => :integer,
              :return => :string
  def integer_to_string
    render :soap => params[:value].to_s
  end

  soap_action "concat",
              :args   => { :a => :string, :b => :string },
              :return => :string
  def concat
    render :soap => (params[:a] + params[:b])
  end
end

Parsing method name wrongly

Hi,

I'm getting this request

< SOAP-ENV:Body >
< GetServiceInfo xmlns="http://schemas.microsoft.com/office/Outlook/2006/OMS" / >
< /SOAP-ENV:Body >
< /SOAP-ENV:Envelope >

But the method name parsed wrongly as it rejected by following error, it seems that xmlns part inside method block is not handled:

Cannot find SOAP action mapping for http://schemas.microsoft.com/office/Outlook/2006/OMS/GetServiceInfo

As the method call originated is from 3PP that can't be changed, is there anyway to fix it in wash_out side ?

BR
Omid

Unicorn::TeeInput is not a valid input stream

Hi,

I have error

"Exception error: Unicorn::TeeInput is not a valid input stream. It must walk
like either a String, an IO, or a Source."

when I use Unicorn.

The error in WashOut::Dispatcher _parse_soap_parameters methods raw 26.

I replace request.body by request.body.read and it work fine.

WashOut::Type correct usage

Hi, i trying to redo my existing "actionwebservice" SOAP service in Rails 2 to Rails 3 with "wash_out".
Original Rails 2 WSDL is at the end.

I have problem to correctly setup model

 Lead < WashOut::Type
    map {:id => :integer, :rodne_cislo => :string, :email => :string}
 end

to have accesible attributes ("rodne_cislo","email" ....), which I can use in other controllers and models.
The "actionwebservice" have for this purpose method "member", which defined virtual atribute with accessors.

  member :id, :int
  member :rodne_cislo, :string

Can somebody point me on solution?

WSDL I woul like to implement is:

<definitions name="Soap" targetNamespace="urn:ActionWebService">
   <types>
     <xsd:schema targetNamespace="urn:ActionWebService">

       <xsd:complexType name="Lead">
         <xsd:all>
           <xsd:element name="shoda_adres" type="xsd:int"/>
           <xsd:element name="note" type="xsd:string"/>
           <xsd:element name="rodne_cislo" type="xsd:string"/>
           <xsd:element name="ulice_nem" type="xsd:string"/>
           <xsd:element name="provize_za_smlouvu" type="xsd:double"/>
           <xsd:element name="zdroj_kontaktu" type="xsd:string"/>
           <xsd:element name="email" type="xsd:string"/>
           <xsd:element name="ulice_kontk" type="xsd:string"/>
           <xsd:element name="typ_uveru" type="xsd:int"/>
           <xsd:element name="status" type="xsd:int"/>
           <xsd:element name="cislo_nem" type="xsd:string"/>
           <xsd:element name="errors" type="typens:StringArray"/>
         </xsd:all>
       </xsd:complexType>

       <xsd:complexType name="StringArray">
          <xsd:complexContent>
              <xsd:restriction base="soapenc:Array">
                <xsd:attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:string[]"/>
             </xsd:restriction>
         </xsd:complexContent>
        </xsd:complexType>

    </xsd:schema>
  </types>

  <message name="ShowLead">
    <part name="id" type="xsd:int"/>
    <part name="poradce" type="xsd:string"/>
  </message>

  <message name="ShowLeadResponse">
    <part name="return" type="typens:Lead"/>
  </message>

 <message name="AddLead">
   <part name="lead" type="typens:Lead"/>
 </message>

 <message name="AddLeadResponse">
   <part name="return" type="typens:Lead"/>
 </message>

<portType name="SoapSoapPort">

  <operation name="ShowLead">
    <input message="typens:ShowLead"/>
    <output message="typens:ShowLeadResponse"/>
  </operation>

  <operation name="AddLead">
    <input message="typens:AddLead"/>
    <output message="typens:AddLeadResponse"/>
  </operation>

 </portType>

 <binding name="SoapSoapBinding" type="typens:SoapSoapPort">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc"/>

       <operation name="ShowLead">
           <soap:operation soapAction="/soap/api/ShowLead"/>
           <input>
              <soap:body namespace="urn:ActionWebService" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
           </input>
           <output>
                <soap:body namespace="urn:ActionWebService" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </output>
      </operation>

      <operation name="AddLead">
        <soap:operation soapAction="/soap/api/AddLead"/>
            <input>
                <soap:body namespace="urn:ActionWebService" use="encoded"         encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </input>
            <output>
                <soap:body namespace="urn:ActionWebService" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </output>
        </operation>

    </binding>

    <service name="SoapService">
        <port name="SoapSoapPort" binding="typens:SoapSoapBinding">
            <soap:address location="https://data.smartcorporation.cz/soap/api"/>
        </port>
    </service>

</definitions>

Custom simpleType definition

I went/played through the code and read other issue or request messages.

I think what I'm going to ask will not make a lot of practical sense ; I'm more asking where do you think I should start to implement such a thing with wash_out.

I need to implement quite a large WSDL with a lot of custom simpleType definitions like these :

.
.
   <xs:simpleType name="codeIncidentType">
      <xs:restriction base="xs:string">
         <xs:pattern value="[0-9]{2}"/>
     </xs:restriction>
   </xs:simpleType>

   <xs:simpleType name="groupeDestinataireSecondaireType">
      <xs:restriction base="xs:string">
         <xs:pattern value="[0-9]{3}"/>
      </xs:restriction>
   </xs:simpleType>
.
.

Those are then used in complexType definitions.

Where do you I should start to hack wash_out to define these new simpleType ?
I need my WSDL to output these and to be able to use them in my complexType without Wash_out throwing an exception.

I started hacking here and there but I'm really not sure I'm taking the simplest or quickest road to achieve my goal.

I would greatly welcome any feedback from you :)

Snakecase option doesn't apply for response

The snakecase option auto converts CamelCase params of a request into snake_case. In same manner I expect this to happen for a response:

WashOut::Engine.snakecase = true

soap_action "my_action", {
      :args => {
          :custom_fields => {
                :my_name => :string
           }
      },
      :return => {
          :custom_fields => {
                :my_name => :string
            }
       }

def my_action
  render :soap => { :custom_fields => { :my_name => 'value' } }
end

expected response:

<soap:Body>
   <tns:MyActionResponse>
      <tns:CustomFields xsi:type="tns:custom_fields">
         <tns:MyName xsi:type="xsd:string">value</tns:name>
      </tns:CustomFields>
   </tns:checkResponse>
</soap:Body>

Sure the following Work Around works, but requires to convert the render keys manually:

WashOut::Engine.snakecase = true
soap_action "my_action", {
      :args => {
          :custom_fields => {
                :my_name => :string
           }
      },
      :return => {
          :CustomFields => {
                :MyName => :string
            }
       }

def my_action
  render :soap => { :CustomFields => { :MyName => 'value' } }
end

"Runtime Error: can't add a new key into hash during iteration" when using a complex parameter type in a soap_action

With JRuby 1.6.7.2 and Rails 3.2.8, using soapUI 4.5.1 on RHEL6.

I'm trying to use either a deeply nested hash (specifically the circle example in the readme) or an ActiveRecord subclass as input parameters to my soap_action. The WSDL is generated neatly and soapUI manages to create the example. But when I fill in the values and execute the request, wash_out bombs with that error.

Here's a gist with the stuff that's erroring out: https://gist.github.com/4327556

Array of complex structures

I'd like to define a web service that requires an array of complex structures as parameter. Such as

# Array of complex structures
  soap_action "AddCircles",
          :args   => { :circles => [ { :center => { :x => :integer, :y => :integer}, 
                                       :radius => :double } ],
          :return => nil, 
          :to     => ...

It seems to generate a correct wsdl, but raises a NoMethodError (undefined method with_indifferent_access' for #Array:0xa86c900):` while parsing the request

Invalid WashOut simple type: datetime

I'm trying to pass attributes for a model in order to create a record. For datetime attributes I keep getting "Invalid WashOut simple type: datetime"

Here's my soap_action:

soap_action "createEventRegistration",
          :args   => {:card_number => :string,
            :event_registration =>
            {
                :event_name => :string,
                :event_type => :string,
                :start_date => :datetime,
                :end_date => :datetime,
                :tickets_booked => :integer,
                :tickets_available => :integer,
                :pre_auth => :boolean,
                :played => :boolean,
                :status => :string,
                :no_show => :boolean,
                :code => :string,
                :block => :string,
                :center => :string,
                :authorizer => :string,
                :entered_by => :string
              }
          },
          :return => {
                :event_registration_id => :string
          },
      :to => :createEventRegistration 
 def createEventRegistration
   patron = Patron.find_by_card_number(params[:card_number])
   event_registration = EventRegistration.new(params[:patron_event_registration])
   event_registration.patron = patron
   event_registration.save
   render :soap => {:event_registration_id => event_registration.id}
 end

If I remove the 2 date attributes from the input args, it works fine.

nori 2.0 breaks washout

hey boris,

the recent release of nori 2.0 comes with backward incompatible changes to the public api.
i would recommend changing your gemspec and pin down nori to 1.x.

related: savonrb/savon#351

cheers,
daniel

document more return types

I don't know of there's a community of people to ask but how does one return complex data types, like, how does one return an array of hashes. BTW I am using this to replace a ColdFusion application that returns wsdls. It likes to return QueryBeans and I want to replace those as well.

Two different types under same name

class FooController < ApplicationController
  include WashOut::SOAP

  soap_action "Test",
              :args => {test: [{foo: :string}]},
              :return => nil

  def Foo
    render :soap => nil
  end

  soap_action "EnqueueGetSpecificArticleFeatures",
              :args => {test: [{foo: :string}]},
              :return => nil
  def Bar
    render :soap => nil
  end

end

which produces

<xsd:complexType name="test">
    <xsd:sequence>
        <xsd:element name="foo" type="xsd:string"/>
    </xsd:sequence>
</xsd:complexType>
<xsd:complexType name="test">
<xsd:sequence>
    <xsd:element name="foo" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>

This breaks generators from Mono and C#, since complex type is defined two times.

There is an even worse case scaenario:

class FooController < ApplicationController
  include WashOut::SOAP

  soap_action "Test",
              :args => {test: [{foo: :string}]},
              :return => nil

  def Foo
    render :soap => nil
  end

  soap_action "EnqueueGetSpecificArticleFeatures",
              :args => {test: [{bar: :string}]},
              :return => nil
  def Bar
    render :soap => nil
  end

end

this produces WSDL as so:

<xsd:complexType name="test">
    <xsd:sequence>
        <xsd:element name="foo" type="xsd:string"/>
    </xsd:sequence>
</xsd:complexType>
<xsd:complexType name="test">
<xsd:sequence>
    <xsd:element name="bar" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>

We have two different types under the same name.

Still, thanks for a cool library! :)

to_date throws error if there is no date

Hi,
Thanks for adding date in accepted params.

There is an issue with the same. If we pass correct date, it works correctly. However, if there is no date data in the SOAP request, to_date validation error is thrown. We can possibly check, if data is nil, we need not do to_date.

Override default namespace

Hello
Default namespace fro wash_out is : NAMESPACE = 'urn:WashOut'
How I can override this namespace ?

gem .net compatibility

I did a simple

gem 'wash_out'

in my Gemfile and used the example to create a wsdl which works when I get the url.
It also works when I make a savon client.

I used that in visual studio to create client stub code (add web service), instantiated the client and called the functions.

Expected: correct output from any function

Got: Empty strings only.

How to define minOccurs from the soap_action?

In my controller I have a method 'upload_program'. The soap action is defined as follows:
soap_action "upload_program",
:args => { :import_request => { :programmes => { :program => [{ :id => :string, :program_name => :string,
:jacs_code => :string, :school_name => :string, :faculty_name => :string,
:study_type => :string, :record_type => :string }] } } },
:return => :xml
def upload_program
...
end
I am testing using SoapUI and all the elements are treated as 'required=true'. I want to know how can I define any of the elements as non-mandatory. Foe example the 'faculty_name' might not be sent in the SOAP request

Complex return types is not described in the wsdl

soap_action "rumba",
:args => nil,
:return => { :plupp => {:xyz => :string} }

def rumba
render :soap => { :plupp => {:xyz => "123"} }
end

This should generate a wsdl that describes the type "plupp", but it doesn't. The wsdl only seems to describe the first level return types. (like strings or integers)

Supporting xsi-type date and xsi-enumeration

I went through the code and observed that wash_out supports only string, integer, double as xsi-types. Is there any specific reason to this? Date is a very common data type. We can use Ruby's to_date for date conversion in load function in lib/wash_out/param.rb.

Also, I have a query related to xsi-enumeration. Can I specify xsi-enumration values in soap action args?
Supposing my soap action args contain gender and I wish to restrict the values to (M, F) in WSDL, is there any way I can achieve this in wash_out?

Currently, I check all the values programmatically. However, if the restrictions are part of WSDL, then the client can validate their request and there would be lesser failures at server.
Let me know if you have time to look into this or I shall try forking the code.

complex structures giving nightmares

Hi,

I am stuck with couple of more issues. I am new to Ruby & Rails hence there are some other problems too.
I took the gem update & have wash-out 0.3.2. However, my request xml is tightly coupled to the args defined for soap_action. My request xml is a complex structure. I can have 1 or many program nodes with programmes. However, if I send a SOAP request with only 1 program node, it fails with the following error:

NoMethodError (undefined method `with_indifferent_access' for ["id", "1001"]:Array):

When I send request with multiple program nodes, it works well.

Second issue is, I had raised Issue 10 - you have closed it - however, if a tag is missing in the request, it still throws Required SOAP parameter 'program_name' is missing

Only if the data is missing within the tag, it passes and reaches my controller code.

My controller code:


class UnionWsController < ApplicationController
include WashOut::SOAP

soap_action "upload_student",
:args => {:import_student_request => { :students => { :student => [] } }},
:return => { :"import-response" => { :students => { :student => [{ :id => :integer, :status => :string }] } }}
def upload_student

@my_server = request.env["SERVER_NAME"]
idx = @my_server.index('.')
union_name = @my_server.slice!(0.. (idx-1))
puts "-- Union name -->"+union_name

request_obj = params[:import_student_request][:students]
puts "Arguments==>"+request_obj.to_s
@student = Student.new

response_hash = @student.save_student_for_ws(request_obj, union_name)

stud_status_array = Array.new(response_hash.length)
idx = 0

response_hash.each do |student_id, status| 
        puts "Student id-->"+student_id.to_s
        puts "Status-->"+status         
        stud_status_array[idx] = Hash.new
        stud_status_array[idx][:"id"] = student_id.to_s
        stud_status_array[idx][:"status"] = status
        idx +=1;
    end

output = { :"import-response"  => 
  {:students => 
    {:student =>  stud_status_array       
    }
  }
}
puts "---- Output ==>"+output.to_s

render :soap => output 

end

soap_action "upload_program",
:args => { :import_program_request => { :programmes => { :program => [{ :program_id => :string, :program_name => [:string],
:jacs_code => [:string], :school_name => [:string], :faculty_name => [:string],
:study_type => [:string], :record_type => :string }] } } },
:return => { :"import-response" => { :programmes => { :program => [{ :"program-id" => :string, :status => :string }] } }}
def upload_program

request_obj = params[:import_program_request][:programmes]
@program = Program.new

response_hash = @program.save_for_webservice(request_obj)

program_array = Array.new(response_hash.length)
idx = 0

response_hash.each do |program_id, status| 
        puts "Program id-->"+program_id
        puts "Status-->"+status         
        program_array[idx] = Hash.new
        program_array[idx][:"program-id"] = program_id
        program_array[idx][:"status"] = status
        idx +=1;
    end

output = { :"import-response"  => 
  {:programmes => 
    {:program =>  program_array       
    }
  }
}
puts "---- Output ==>"+output.to_s

render :soap => output

end
end


My SOAP request for upload_program

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:WashOut">
soapenv:Header/
soapenv:Body
<urn:upload_program soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<import_program_request xsi:type="urn:import_program_request">


<program_id xsi:type="xsd:string">P001</program_id>
<program_name xsi:type="xsd:string">a</program_name>
<jacs_code xsi:type="xsd:string">b</jacs_code>
<school_name xsi:type="xsd:string">c</school_name>
<faculty_name xsi:type="xsd:string">d</faculty_name>
<study_type xsi:type="xsd:string">e</study_type>
<record_type xsi:type="xsd:string">new</record_type>


</import_program_request>
/urn:upload_program
/soapenv:Body
/soapenv:Envelope


If you use the above request, it shall throw - NoMethodError (undefined method `with_indifferent_access' for ["program_id", "P001"]:Array):
If you add one more program data, it passes. It is possible that the client may send me 1 or many program data. How to I make it reach my controller?

my routes.rb contain;
wash_out :union_ws

One more query - my WSDL is accessible only on Rails server. When I try to access it on my Phusion Passenger server, I get 404 :( And you can understand if WSDL is not accessible, how can I access the soap action via Phusion Passenger...?

Routing Errors when Sprocktes disabled/missing

Interesting: when the assets pipeline is disabled config.assets.enabled = false - I get a bunch of failing specs due to routing error. No sure why exactly - havn't figured out yet.

Putting up a ticket in case somebody else experienced same or has an explanation..

The empty arguments problem.

Hi!

I have the following code

in my api controller

soap_action "BlockAccount",
:args => {:ils => :string, :asr_id => :string},
:return => {:asr_id => :string, :ils => :string},
:to => :block_account

def block_account
render :soap => {:ils => params[:ils], :asr_id => params[:asr_id]}
end

The request to the my service,

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:WashOut">
   <soapenv:Header/>
   <soapenv:Body>
      <urn:BlockAccount soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <ils xsi:type="xsd:string">test</ils>
         <asr_id xsi:type="xsd:string">test</asr_id>
      </urn:BlockAccount>
   </soapenv:Body>
</soapenv:Envelope>

response

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="urn:WashOut">
   <soap:Body>
      <tns:BlockAccount_response>
         <tns:asr_id xsi:type="xsd:string">test</tns:asr_id>
         <tns:ils xsi:type="xsd:string">test</tns:ils>
      </tns:BlockAccount_response>
   </soap:Body>
</soap:Envelope>

it works fine, but

the request with empty arguments

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:WashOut">
   <soapenv:Header/>
   <soapenv:Body>
      <urn:BlockAccount soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <ils xsi:type="xsd:string"></ils>
         <asr_id xsi:type="xsd:string"></asr_id>
      </urn:BlockAccount>
   </soapenv:Body>
</soapenv:Envelope>

response

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="urn:WashOut">
   <soap:Body>
      <tns:BlockAccount_response>
         <tns:asr_id xsi:type="xsd:string">{:"@xsi:type"=>"xsd:string"}</tns:asr_id>
         <tns:ils xsi:type="xsd:string">{:"@xsi:type"=>"xsd:string"}</tns:ils>
      </tns:BlockAccount_response>
   </soap:Body>
</soap:Envelope>

The value '{:"@xsi:type"=>"xsd:string"}' is unexpected for me.

When I get the request with empty argument, params[:argument] returns "{:"@xsi:type"=>"xsd:string"}'.

Using reusable types creates complex type with empty name

Creating a reusable type with washout I noticed that it creates a complex type with an empty name attribute? Is there anyway around this as it seems like it generates an invalid WSDL.

<xsd:complexType name="">
<xsd:sequence>
<xsd:element name="universe" type="tns:universe"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="universe">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="age" type="xsd:int"/>
</xsd:sequence>
</xsd:complexType>

Sample Code

 class Fluffy < WashOut::Type
  map :universe => {
        :name => :string,
        :age  => :int
      }
  end

  class FluffyContainer < WashOut::Type
    type_name 'fluffy_con'
    map :fluffy => Fluffy
  end

Module

  module soapActions
      def self.sample_request() 
       { :fluffy => Fluffy }
     end
   end

Controller

soap_action "SampleReqest", :args => SbActions.sample_request(), :return => :string

This is what should I would expect it to render

<xsd:complexType name="universe">
    <xsd:sequence>
    <xsd:element name="name" type="xsd:string"/>
    <xsd:element name="age" type="xsd:int"/>
  </xsd:sequence>
</xsd:complexType>

update to savon 2.0

hey boris,

not sure if you're interested in this, but if you are, i could come up with a pull request for you.

i only see one problem with this: since savon 2.0 requires nori 2.0, washout would need to depend on nori 2.x for this. i think the best way to solve this would be to pin nori to 1.x for the next bugfix release (to fix #77) and upgrade the dependency to 2.x for the next major/minor release.

let me know what you think.

cheers,
daniel

"xsi:type" value for Complex Types in return value not rendered as per generated WSDL

Hi,

Here is an simple example code to illustrate my problem

    soap_action "getAllUsers",
              :args => nil,
              :return => {:users=>[UserInfo]},
              :to => :get_all_users
    def get_all_users
        render :soap => {:users=>[
                              {:user_id=>"user1",
                               :home_address=>{:street=>"123, abc road", :city=>"Ashburn"},
                               :work_address=>{:street=>"456, def road", :city=>"Herndon"}},
                              {:user_id=>"user2",
                               :home_address=>{:street=>"789, geh road", :city=>"Reston"},
                               :work_address=>{:street=>"456, def road", :city=>"Herndon"}}
                             ]}
    end

    class UserInfo < WashOut::Type
         map :user_id => :string,
             :home_address => Address,
             :work_address => Address
    end

    class Address < WashOut::Type
        map :street => :string,
            :city => :string
    end

Generated WSDL Snippet

         <xsd:complexType name="userInfo">
            <xsd:sequence>
               <xsd:element type="xsd:string" name="userId"/>
               <xsd:element type="tns:address" name="workAddress"/>
               <xsd:element type="tns:address" name="homeAddress"/>
            </xsd:sequence>
         </xsd:complexType>
         <xsd:complexType name="address">
            <xsd:sequence>
               <xsd:element type="xsd:string" name="street"/>
               <xsd:element type="xsd:string" name="city"/>
            </xsd:sequence>
         </xsd:complexType>
         ......
         ......
      <operation name="getAllUsers">
         <input message="tns:getAllUsers"/>
         <output message="tns:getAllUsersResponse"/>
      </operation>
         ......
         ......
      <message name="getAllUsers"/>
      <message name="getAllUsersResponse">
         <part type="tns:userInfo" xsi:minOccurs="0" name="users" xsi:maxOccurs="unbounded"/>
      </message>

Rendered XML Response

     <soap:Body>
      <tns:getAllUsersResponse>
         <users xsi:type="tns:users">
            <workAddress xsi:type="tns:workAddress">
               <street xsi:type="xsd:string">456 def road</street>
               <city xsi:type="xsd:string">Herndon</city>
            </workAddress>
            <userId xsi:type="xsd:string">user1</userId>
            <homeAddress xsi:type="tns:homeAddress">
               <street xsi:type="xsd:string">123, abc road</street>
               <city xsi:type="xsd:string">Ashburn</city>
            </homeAddress>
         </users>
         <users xsi:type="tns:users">
            <workAddress xsi:type="tns:workAddress">
               <street xsi:type="xsd:string">456 def road</street>
               <city xsi:type="xsd:string">Herndon</city>
            </workAddress>
            <userId xsi:type="xsd:string">user2</userId>
            <homeAddress xsi:type="tns:homeAddress">
               <street xsi:type="xsd:string">789, geh road</street>
               <city xsi:type="xsd:string">Reston</city>
            </homeAddress>
         </users>
      </tns:getAllUsersResponse>
     </soap:Body>

As you can see the values for following xsi:type are rendered wrongly
tns:user instead of tns:userInfo
tns:homeAddress instead of tns:address
tns:workAddress instead of tns:address

Am I doing something wrong or is this a bug?

Thank You very much

Regards
Karthik

Complex Type Structure

If you define you action like this:
soap_action "import",
:args => {
:person => {
:name=> :string,
:address=>Address
….
}
}
and Address is a conventional ActiveRecord:Model, you get a WSDL entry like this

xsd:complexType name="address">

xsd:sequence>

xsd:element name="id" type="xsd:int"/>

xsd:element name="street" type="xsd:string"/>

xsd:element name="number" type="xsd:integer"/>

xsd:element name="city" type="xsd:string"/>

/xsd:sequence>

/xsd:complexType>

The Problem is, that the ID is included in the WSDL.
Of course you can ignore this in the controller/model but it seems a little nasty.

Define types outside of an action and reuse then in actions

When you generate a WSDL and use the same type many times it would be nice if you could define some types at the top of the WSDL and then just refer to them. It would be very nice if it was possible to have something like soap_action in the controller but named soap_types where you could define some types like this:

soap_types {
:Address => {
:City => :string,
:Street => :string
},

Person => {
:first_name => string,
:last_name => :string,
:address => :Address
}
}

and then:

soap_action "make_sale",
:args =>
:Order => {
:Buyer => :Person,
:Seller => :Person
},
:return => :string

I tried thinking about how to do this but couldn't quite figure it out. What do you guys think?

ActionView::Template::Error (undefined method `value' for #<WashOut::Param:0x00000002eb96d0>):

I get this error with rails 3.0.11 version of wash_out (0.2.2).
This happens only then I use hash included in args, for example:

:args => { :circle => { :center => { :x => :integer,
:y => :integer },
:radius => :float } }

Full error:

Rendered /usr/local/rvm/gems/ruby-1.9.2-p180/gems/wash_out-0.2.2/app/views/wash_with_soap/wsdl.builder (6.8ms)
Completed 500 Internal Server Error in 16ms

ActionView::Template::Error (undefined method `value' for #WashOut::Param:0x00000004827fb8):
11:
12: @map.each do |operation, formats|
13: formats[:in].each do |p|
14: wsdl_type xml, p
15: end
16: end
17: end

Rendered /usr/local/rvm/gems/ruby-1.9.2-p180/gems/actionpack-3.0.11/lib/action_dispatch/middleware/templates/rescues/_trace.erb (2.5ms)
Rendered /usr/local/rvm/gems/ruby-1.9.2-p180/gems/actionpack-3.0.11/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (2.3ms)
Rendered /usr/local/rvm/gems/ruby-1.9.2-p180/gems/actionpack-3.0.11/lib/action_dispatch/middleware/templates/rescues/template_error.erb within rescues/layout (10.7ms)

Contributors list

@rngtng @Bjorn-Nilsson

Guys, I've decided to add a contributors list to the README. I appreciate your help, thank you :). I'd like to ask which url should be linked to your usernames. It's github account link for now but if you want to change it, please let me know. Check your names either :)

Formatting issue in return

I need to format a return as "<tns:AuthenticateResult>", but I have not been able to find a way. Instead I get "<AuthenticateResult xsi:type="tns:AuthenticateResult">".
I've tried changing both snakecase and camelcase values. Any help would be greatly
appreciated.


What I need

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://developer.intuit.com/">
    <SOAP-ENV:Body>
        <ns1:authenticateResponse>
            <ns1:authenticateResult>
                <ns1:string>15c9ce293bd3f41b761c21635b14fa06</ns1:string>
                <ns1:string></ns1:string>
            </ns1:authenticateResult>
        </ns1:authenticateResponse>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

What I am getting

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://developer.intuit.com/">
  <soap:Body>
    <tns:authenticateResponse>
      <AuthenticateResult xsi:type="tns:AuthenticateResult">
        <String xsi:type="xsd:string">abf36037372fa6cf089dbdbc33b11771908afe57</String>
        <String xsi:type="xsd:string"></String>
      </AuthenticateResult>
    </tns:authenticateResponse>
  </soap:Body>
</soap:Envelope>

My code

soap_action "authenticate",
              :args => {:strUserName => :string, :strPassword => :string},
              :return => { :authenticate_result => [{string: [:string]}]}
  def authenticate
    username = params[:strUserName]
    password = params[:strPassword]
    if (Role.find_by_token(username))
      if QuickbooksImportStatus.find_by_token(username).blank?
        QuickbooksImportStatus.create(:username => User.find(Role.find_by_token(username).user_id).username, :password => password, :token => username, :returnqid => 25, :company_id => Role.find_by_token(username).company_id, :querystatus => "")
      end
      render :soap => {:authenticate_result => [{string: [username, '']}]}
end

Deploying wash-out on Phusion Passenger server

Hi

Thanks a lot for fixing #10, 11 and 12.

I had a query. When I start the rails server, I am able to access the wsdl along with the rest of the application. However, when I start the Phusion Passenger, I am unable to access the wsdl, although rest of my application is working. Is there any specific environment variable I need to set?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.