Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Copy Markdown
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)
{
}
}
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)
: base(message)
{
}

public SalesForceJobException(string message, Exception inner)
: 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)
{
}

public SalesForceJobFailedException(string message, Exception inner) : base(message, inner)
{
}
}
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
Copy Markdown
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,
InProgress,
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