Giter VIP home page Giter VIP logo

ex_force's Introduction

ex_force's People

Contributors

chulkilee avatar dirksierd avatar dustinfarris avatar gabrielpra1 avatar jeremy-hanna avatar jeroenvisser101 avatar kianmeng avatar leoleitesc avatar rschef 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

Watchers

 avatar  avatar  avatar  avatar

ex_force's Issues

Can't send Text/CSV request body

So I'm honestly not sure if this is an ExForce issue or Tesla.

I'm trying to use ExForce to send some Bulk API 2.0 requests (example). You can see in step 3 "Uploading your CSV data" the content-type header is text/csv. However, the following fails because when the client is built the JSON middleware is set, and I believe is encoding the csv_data.

ExForce.Client.request(client,
  method: :put,
  url: "jobs/ingest/#{job_id}/batches/",
  headers: ["Content-Type": "text/csv", Accept: "application/json"],
  body: csv_data
)

Would solving this just be a matter of writing a CSV Tesla middleware or is this currently outside the scope of what ExForce can handle?

`query_stream` should handle possible `{:error, _}` response

We encountered a case where a query_stream can return an :error.

For us it occurred when an SObject had fields that had been renamed since the last time we connected and we had used a query for an old field name.

We have added case statements for the possible :error return state.

Because ex_force was not expecting a {:error, something} result, it would throw a runtime exception, which would bring down the parent GenServer.

We will now be able to expect a possible :error return ourselves and handle gracefully. In our specific case, we will make a new metadata SOAP request to update the known field names for the objects we wish to query. This is a side-effect of not being able to issue SELECT * FROM something queries in Salesforce query language.

PR with fix here: #48

Use own struct, not Tesla's

Currently ExForce returns plain Tesla struct, such as Tesla.Env. That works well for ad-hoc client, but since ExForce is standalone application, it may be better to use own struct.

Split request build and perform steps

Currently functions take arguments and ExForce.Config (for authentication), perform the request, and return optionally transformed response.

config = %ExForce.Config{}

{:ok, resp} = ExForce.query(soql, config)
{:ok, resp} = ExForce.describe_sobject("Account", config)

The main drawback of this approach is that all functions need ExForce.Config for authentication.

A way to avoid that is to make a request struct, and use separate general request handler taking the request.

config = %ExForce.Config{}

{:ok, result} = soql |> ExForce.query() |> ExForce.request(config)

stream = soql |> ExForce.query() |> ExForce.stream_query(config)

ex_aws is taking this approach.

There are several way to implement this interface.

Use general-purpose request struct

  • pros: simple interface
  • cons: no type information
@spec get_sobject(sobject_id, sobject_name, list) :: Request.t()

@spec request(Request.t()) :: {:ok, any} | {:error, any}

Use different request struct

  • pros: keep type information for each request type
  • cons: need to define request types for different response type
@spec get_sobject(sobject_id, sobject_name, list) :: SObjectRequest.t()

@spec request(SObjectRequest.t()) :: {:ok, SObject.t()} | {:error, any}

For example, ExAws uses ExAws.Operation protocol for perform(ops, config) - ref.

Return func

  • pros: keep type information at function level
  • cons: hard to instrument the request
@type request_func :: ((config_or_func) -> {:ok, any} | {:error, any})

@spec get_sobject(sobject_id, sobject_name, list) :: ((config_or_func) -> {:ok, SObject.t()} | {:error, any})

@spec request(request_func) :: {:ok, any} | {:error, any}

Add support for the new Tesla version

We updated the Tesla on our project to 1.6, and apparently this operation broke the ExForce dependency. We started to receive a decode error when executing the function ExForce.OAuth.get_token/2:

image

Tried with the version 1.5 as well and didn't worked. Tesla 1.4 worked as expected.
Apparently the problem is with some change made in Tesla after the version 1.5.0.

Missing headers information

I need the headers as part of the response. So far only the status and the body are passed. There are many reason why someone would want the headers

  1. So per Salesforce doc https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_rest_conditional_requests.htm?search_text=etag

This is needed to make use of the version/revision of an object and detect conflicts

  1. The API quota limit are return along the request in the headers per doc
    https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/headers_api_usage.htm?search_text=limit

I tried just adding the header as such

defp transform_response(%Response{body: body, status_code: status, headers: headers}), do: {status, body, headers}

This would work beside the number of test to fix ๐Ÿ˜… . What do you think?

`Jason.DecodeError` from `ExForce.OAuth.get_token/2`

This just started happening today:

{:error,
 {Tesla.Middleware.JSON, :decode,
  %Jason.DecodeError{
    data: <<...REDACTED...>>,
    position: 0,
    token: nil
  }}}

Dug in a bit, and the order of the Tesla.Middleware makes a difference here for some reason.

These don't work:

ExForce.OAuth.get_token(
  instance_url,
  grant_type: "refresh_token",
  client_id: client_id,
  client_secret: client_secret,
  refresh_token: refresh_token
)
instance_url
|> ExForce.Client.build_oauth_client()
|> Tesla.post(
  instance_url <> "/services/oauth2/token",
  %{
    grant_type: "refresh_token",
    client_id: client_id,
    client_secret: client_secret,
    refresh_token: refresh_token
  }
)
[
  {Tesla.Middleware.BaseUrl, instance_url},
  {Tesla.Middleware.Compression, format: "gzip"},
  Tesla.Middleware.FormUrlencoded,
  {Tesla.Middleware.DecodeJson, engine: Jason},
  {Tesla.Middleware.Headers, []}
]
|> Tesla.client()
|> Tesla.post(
  "/services/oauth2/token",
  %{
    grant_type: "refresh_token",
    client_id: client_id,
    client_secret: client_secret,
    refresh_token: refresh_token
  }
)

(This one ^^^ is basically ExForce.Client.Tesla.build_oauth_client/2)

But moving Tesla.Middleware.DecodeJson first in the list of middleware does, and yields an {:ok, %Tesla.Env{}} tuple with decoded JSON body:

[
  {Tesla.Middleware.DecodeJson, engine: Jason},
  {Tesla.Middleware.BaseUrl, instance_url},
  {Tesla.Middleware.Compression, format: "gzip"},
  Tesla.Middleware.FormUrlencoded,
  {Tesla.Middleware.Headers, []}
]
|> Tesla.client()
|> Tesla.post(
  "/services/oauth2/token",
  %{
  grant_type: "refresh_token",
  client_id: client_id,
  client_secret: client_secret,
  refresh_token: refresh_token
  }
)

Is it possible to add `Tesla.Middleware.Logger` to my requests?

I'd like to add Tesla.Middleware.Logger to the client when making requests. Is that possible?

It doesn't look like ExForce.Client.Tesla.build_client/2 supports it:

def build_client(instance_url_or_map, opts \\ [headers: [{"user-agent", @default_user_agent}]])

I was also looking into whether I could add the Middleware to the client after I get it back, but I couldn't find a way to modify a Tesla.Client after it is built.

Thanks!

Fix the typespec for the 'build_client' function

I'm using the main branch since the hmac is deprecated in Erlang 24, and I found a bug when building my own build_client function. It's possible to see this bug mainly in the Dialyzer:

lib/.../adapters/tesla/tesla.ex:24:callback_type_mismatch
Type mismatch for @callback build_client/1 in ExForce.Client behaviour.
Expected type:
atom()
Actual type:
%Tesla.Client{
  :adapter => nil | {:fn, (... -> any)},
  :fun => nil,
  :post => [{_, _} | {_, _, _}],
  :pre => [{_, _} | {_, _, _}]
}

The problem is a recent change that modify the type of the build_client function to be module, but Elixir internally sees this type as an atom: https://hexdocs.pm/elixir/1.12/typespecs.html#built-in-types

I think the best solution is modify this type to any again.

JWT Oauth Flow?

It looks like ex_force doesn't presently support the JWT Oauth flow offered by Salesforce... I use this authentication flow quite often connecting to Salesforce for integration projects - it would be useful for me and perhaps others.

I am an experienced Salesforce developer, but a beginner with Elixir. I would be happy to try and get a PR going to add JWT support to ex_force, if you would be open to it - what do you think?

Thanks!
Scott

Handle bad gzip body

Could not reproduced it, but anyway ExForce needs to handle bad gzip body.

** (ErlangError) Erlang error: :data_error
    :zlib.inflateEnd_nif(#Reference<0.2868773574.1493303297.176518>)
    :zlib.gunzip/1
    (ex_force) lib/ex_force/client.ex:51: ExForce.Client.gunzip/1
    (ex_force) lib/ex_force/client.ex:32: ExForce.Client.request!/5
    (ex_force) lib/ex_force.ex:216: ExForce.query_retrieve/2
    (ex_force) lib/ex_force.ex:281: ExForce.stream_unfold/1

Issue running the tests

I just pulled master and one of the test is not passing

mix test
..........

  1. test get with load_from_system_env: success (ExForce.AuthTest)
    test/ex_force/auth_test.exs:60
    env vars are not set for test; see env.global section of .travis.yml
    code: assert System.get_env("SALESFORCE_ENDPOINT") == "http://127.0.0.1:1234",
    stacktrace:
    test/ex_force/auth_test.exs:61: (test)

................................

Release 0.2.2

Hi! Would you be able to release a new version that includes #27?

Insert / Upsert Functionality

@chulkilee
Did you have a plan for adding Insert or Upsert functionality with this API client?

I'm spending some time building an API client in Tesla for an application who's data I'll be persisting to Salesforce. The Tesla client is moving along nicely, but I'm still looking for a good way to insert and update Salesforce records from Elixir.

The quick fix would be to implement the insert functionality in your library using HTTPoison 0.13, but if the time to implement the whole library in #17 Tesla is approachable, I'd be willing to invest time towards that end as well.

Composable Request/Response Structs to use Composite Resources

Figured this might be better off as a separate issue/proposal stemming from #25 .

The idea is to decouple the implementation of building API requests from Tesla with an ExForce.Request{} struct that can be composed into an ExForce.CompositeRequest or ExForce.BatchRequest. This could enable more explicit behavior specs as described in #26 and ideally allowing for easier switching out of the HTTP library.

Started an initial mock of what the structs could look like here:

As the Composite Resource responses also have sub-responses an ExForce.Response{} like described in #25 would allow for a ExForce.CompositeResponse with the list of ExForce.Response{} structs to be returned.

@spec composite_request(Client.t(), list(%ExForce.Request{}), boolean()) :: {:ok, %ExForce.CompositeResponse{}} | {:error, any}
def composite_request(client, requests, all_or_none) do
  ...
end

As described, this would be a large surface area change. Maybe an interim option is to just use the current approach (Tesla.Env{} responses) and add Composite Resources functions like shown here. The trade-off being a lot of the Salesforce API implementation bleeds into the consuming application by having to build a compatible data structure for the resource (e.g. %{"allOrNone" => "true", "compositeRequest" => composite_requests}) and parse out the response bodies.

create_sobject returning error

I am using

ExForce.create_sobject("Account", data, config)

and the salesforce operation seems to be successful, but the result of the function is:

{:error,
 %{"errors" => [], "id" => "0013600001WHtN8AAL", "success" => true,
   "warnings" => []}}

shouldn't this be {:ok, ...}?

/cc @epinault

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.