Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add salesforce connector #57

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System;
using System.Linq;
using System.Text.RegularExpressions;

namespace Arcane.Framework.Sources.CdmChangeFeedSource.Extensions;
namespace Arcane.Framework.Sources.Extensions;

/// <summary>
/// Contains operations for parsing CSV files.
Expand All @@ -16,7 +16,7 @@ internal static class CsvOperations
/// <param name="headerCount">Number of expected headers.</param>
/// <param name="delimiter">Delimiter used in the line.</param>
/// <returns>A string array of individual values.</returns>
public static string[] ParseCsvLine(string line, int headerCount, char delimiter = ',')
public static string[] ParseCsvLine(this string line, int headerCount, char delimiter = ',')
{
var result = new string[headerCount];
var fieldCounter = 0;
Expand Down Expand Up @@ -91,7 +91,7 @@ public static bool IsComplete(string csvLine)
/// </summary>
/// <param name="csvLine">A CSV line to purge newlines from.</param>
/// <returns>A CSV line without newline characters inside column values.</returns>
public static string ReplaceQuotedNewlines(string csvLine)
public static string ReplaceQuotedNewlines(this string csvLine)
{
return Regex.Replace(csvLine, "\"[^\"]*(?:\"\"[^\"]*)*\"", m => m.Value.Replace("\n", "")).Replace("\r", "");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Diagnostics.CodeAnalysis;

namespace Arcane.Framework.Sources.SalesForce.Exceptions;

/// <summary>
/// Thrown if the Salesforce job was aborted.
/// </summary>
[ExcludeFromCodeCoverage(Justification = "Trivial")]
public class SalesForceJobAbortedException : SalesForceJobException
{
public SalesForceJobAbortedException(string message) : base(message)

Check warning on line 12 in src/Sources/SalesForce/Exceptions/SalesForceJobAbortedException.cs

View workflow job for this annotation

GitHub Actions / Validate commit

Missing XML comment for publicly visible type or member 'SalesForceJobAbortedException.SalesForceJobAbortedException(string)'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warnings about comment should be fixed, here and below.

{
}

public SalesForceJobAbortedException(string message, Exception inner) : base(message, inner)

Check warning on line 16 in src/Sources/SalesForce/Exceptions/SalesForceJobAbortedException.cs

View workflow job for this annotation

GitHub Actions / Validate commit

Missing XML comment for publicly visible type or member 'SalesForceJobAbortedException.SalesForceJobAbortedException(string, Exception)'
{
}
}
21 changes: 21 additions & 0 deletions src/Sources/SalesForce/Exceptions/SalesForceJobException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Diagnostics.CodeAnalysis;

namespace Arcane.Framework.Sources.SalesForce.Exceptions;

/// <summary>
/// Base class for all Salesforce-related exceptions
/// </summary>
[ExcludeFromCodeCoverage(Justification = "Trivial")]
public class SalesForceJobException : Exception
{
public SalesForceJobException(string message)

Check warning on line 12 in src/Sources/SalesForce/Exceptions/SalesForceJobException.cs

View workflow job for this annotation

GitHub Actions / Validate commit

Missing XML comment for publicly visible type or member 'SalesForceJobException.SalesForceJobException(string)'
: base(message)
{
}

public SalesForceJobException(string message, Exception inner)

Check warning on line 17 in src/Sources/SalesForce/Exceptions/SalesForceJobException.cs

View workflow job for this annotation

GitHub Actions / Validate commit

Missing XML comment for publicly visible type or member 'SalesForceJobException.SalesForceJobException(string, Exception)'
: base(message, inner)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Diagnostics.CodeAnalysis;

namespace Arcane.Framework.Sources.SalesForce.Exceptions;

/// <summary>
/// Thrown if the Salesforce job return with failed status.
/// </summary>
[ExcludeFromCodeCoverage(Justification = "Trivial")]
public class SalesForceJobFailedException : SalesForceJobException
{
public SalesForceJobFailedException(string message) : base(message)

Check warning on line 12 in src/Sources/SalesForce/Exceptions/SalesForceJobFailedException.cs

View workflow job for this annotation

GitHub Actions / Validate commit

Missing XML comment for publicly visible type or member 'SalesForceJobFailedException.SalesForceJobFailedException(string)'
{
}

public SalesForceJobFailedException(string message, Exception inner) : base(message, inner)

Check warning on line 16 in src/Sources/SalesForce/Exceptions/SalesForceJobFailedException.cs

View workflow job for this annotation

GitHub Actions / Validate commit

Missing XML comment for publicly visible type or member 'SalesForceJobFailedException.SalesForceJobFailedException(string, Exception)'
{
}
}
93 changes: 93 additions & 0 deletions src/Sources/SalesForce/Models/SalesForceAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Arcane.Framework.Sources.SalesForce.Models;

/// <summary>
/// Represents Salesforce attribute
/// </summary>
public class SalesForceAttribute
{
private static readonly Dictionary<string, Type> salesforceTypeMap = new()
{
{ "string", typeof(string) },
{ "id", typeof(string) },
{ "datetime", typeof(DateTime) },
{ "date", typeof(DateTime) },
{ "decimal", typeof(decimal) },
{ "integer", typeof(int) },
{ "long", typeof(long) },
{ "double", typeof(double) },
{ "boolean", typeof(bool) },
};

/// <summary>
/// Attribute name
/// </summary>
[JsonPropertyName("Name")]
public string Name { get; set; }

/// <summary>
/// Attribute data type
/// </summary>
[JsonPropertyName("ValueTypeId")]
public string DataType { get; set; }


/// <summary>
/// Attribute comparer
/// </summary>
public static IEqualityComparer<SalesForceAttribute> SalesForceAttributeComparer { get; } =
new SalesForceAttributeEqualityComparer();

/// <summary>
/// Maps Salesforce type to .NET type
/// </summary>
/// <param name="salesforceTypeName">Salesforce type name</param>
/// <returns>.NET type instance</returns>
/// <exception cref="InvalidOperationException">Thrown if type is not supported</exception>
public static Type MapSalesforceType(string salesforceTypeName)
{
if (salesforceTypeMap.ContainsKey(salesforceTypeName.ToLower()))
{
return salesforceTypeMap[salesforceTypeName.ToLower()];
}

throw new InvalidOperationException($"Unsupported type: {salesforceTypeName}");
}

private sealed class SalesForceAttributeEqualityComparer : IEqualityComparer<SalesForceAttribute>
{
public bool Equals(SalesForceAttribute x, SalesForceAttribute y)
{
if (ReferenceEquals(x, y))
{
return true;
}

if (ReferenceEquals(x, null))
{
return false;
}

if (ReferenceEquals(y, null))
{
return false;
}

if (x.GetType() != y.GetType())
{
return false;
}

return x.Name == y.Name &&
x.DataType == y.DataType;
}

public int GetHashCode(SalesForceAttribute obj)
{
return HashCode.Combine(obj.Name, obj.DataType);
}
}
}
98 changes: 98 additions & 0 deletions src/Sources/SalesForce/Models/SalesForceEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text.Json;

namespace Arcane.Framework.Sources.SalesForce.Models;

/// <summary>
/// Represents Salesforce entity
/// </summary>
public class SalesForceEntity
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The classes that are not intended for use by plugins should always be internal, not public. This is because the documentation for internal classes is not published as API docs

{
/// <summary>
/// Entity name
/// </summary>
public string EntityName { get; set; }


/// <summary>
/// Attributes collection
/// </summary>
public SalesForceAttribute[] Attributes { get; set; }

/// <summary>
/// Comparer class
/// </summary>
public static IEqualityComparer<SalesForceEntity> SalesForceEntityComparer { get; } =
new SalesForceEntityEqualityComparer();

/// <summary>
/// Parse Salesforce entity from a JSON document
/// </summary>
/// <param name="entityName">Name of the Salesforce entity </param>
/// <param name="document">Json document to parse</param>
/// <returns>Parsed SalesForceEntity object</returns>
public static SalesForceEntity FromJson(string entityName, JsonDocument document)
{
var entity = new SalesForceEntity
{
EntityName = entityName,
Attributes = document.RootElement.GetProperty("records").Deserialize<SalesForceAttribute[]>()
};


return entity;
}

/// <summary>
/// Create DataReader for the entity
/// </summary>
/// <returns>DataReader instance</returns>
public IDataReader GetReader()
{
var dt = new DataTable();

foreach (var attr in this.Attributes)
{
dt.Columns.Add(new DataColumn(attr.Name, SalesForceAttribute.MapSalesforceType(attr.DataType)));
}

return dt.CreateDataReader();
}

private sealed class SalesForceEntityEqualityComparer : IEqualityComparer<SalesForceEntity>
{
public bool Equals(SalesForceEntity x, SalesForceEntity y)
{
if (ReferenceEquals(x, y))
{
return true;
}

if (ReferenceEquals(x, null))
{
return false;
}

if (ReferenceEquals(y, null))
{
return false;
}

if (x.GetType() != y.GetType())
{
return false;
}

return x.EntityName == y.EntityName
&& x.Attributes.SequenceEqual(y.Attributes, SalesForceAttribute.SalesForceAttributeComparer);
}

public int GetHashCode(SalesForceEntity obj)
{
return HashCode.Combine(obj.EntityName, obj.Attributes);
}
}
}
55 changes: 55 additions & 0 deletions src/Sources/SalesForce/Models/SalesForceJob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Text.Json.Serialization;

namespace Arcane.Framework.Sources.SalesForce.Models;

/// <summary>
/// Job status types
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum SalesforceJobStatus
{
UploadComplete,

Check warning on line 11 in src/Sources/SalesForce/Models/SalesForceJob.cs

View workflow job for this annotation

GitHub Actions / Validate commit

Missing XML comment for publicly visible type or member 'SalesforceJobStatus.UploadComplete'
InProgress,

Check warning on line 12 in src/Sources/SalesForce/Models/SalesForceJob.cs

View workflow job for this annotation

GitHub Actions / Validate commit

Missing XML comment for publicly visible type or member 'SalesforceJobStatus.InProgress'
Aborted,

Check warning on line 13 in src/Sources/SalesForce/Models/SalesForceJob.cs

View workflow job for this annotation

GitHub Actions / Validate commit

Missing XML comment for publicly visible type or member 'SalesforceJobStatus.Aborted'
JobComplete,
Failed,
None
}

/// <summary>
/// Represents Salesforce job
/// </summary>
public class SalesForceJob
{
/// <summary>
/// Id
/// </summary>
[JsonPropertyName("id")]
public string Id { get; set; }


/// <summary>
/// Job status
/// </summary>
[JsonPropertyName("state")]
public SalesforceJobStatus Status { get; set; }

/// <summary>
/// object
/// </summary>
[JsonPropertyName("object")]
public string Object { get; set; }

/// <summary>
/// Total processing time of the job
/// </summary>
[JsonPropertyName("totalProcessingTime")]
public long? TotalProcessingTime { get; set; }

/// <summary>
/// Numbers of records processed by the job
/// </summary>
[JsonPropertyName("numberRecordsProcessed")]
public long? NumberRecordsProcessed { get; set; }

}
Loading
Loading