Simple Elixir wrapper for Salesforce REST API.
See the documentation at https://hexdocs.pm/ex_force.
A Salesforce REST API wrapper for Elixir
Home Page: https://hex.pm/packages/ex_force
License: MIT License
Simple Elixir wrapper for Salesforce REST API.
See the documentation at https://hexdocs.pm/ex_force.
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
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
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.
@spec get_sobject(sobject_id, sobject_name, list) :: Request.t()
@spec request(Request.t()) :: {:ok, any} | {:error, any}
@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.
@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}
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
This is needed to make use of the version/revision of an object and detect conflicts
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?
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.
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
:
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
.
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
}
)
I noticed that ExForce does not have a function to request_sobject_by_external_id. We do have the get_sobject_by_external_id, but it only implements the "GET" method for the REST API endpoint.
I'll make a PR adding this function, so that we can request sObjects by external id. This will enable upserting sObjects by external id, for example.
Hi, @chulkilee!
First of all, congrats on this lib, it's amazing. What do you think about adding support to recently viewed information? I started working on View Recently Viewed Records as a prof-of-concept and would like to open a pull request on it.
With the merge of #55, this is now compatible with erlang OTP 24, however there is still not a new version published to hex for this. Figured I'd go ahead and create this issue to move that along.
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.
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:
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!
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.
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?
Salesforce Streaming API is based on Bayeux / ComeD. Unfortunately, I couldn't find a library to handle that :(
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
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
I am looking at the list of function but I dont see a way to create a sobject? Does the update_sobject does the a create?
@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.
Hi! Would you be able to release a new version that includes #27?
Thank you so much for this library.
Could you please include in your usage documentation that password needs to be password+token?
I was struggling with trying to get the library working for hours when I finally stumbled on this - https://developer.salesforce.com/forums/?id=906F000000099liIAA - combined with some other same JS code where I realized I needed cat password+token.
I just pulled master and one of the test is not passing
mix test
..........
................................
do you support username-password flow only? Or do you also support web server flow?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.