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
21 changes: 12 additions & 9 deletions src/Core/HubSpotBaseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using RapidCore.Network;
using Skarp.HubSpotClient.Core.Interfaces;
using Skarp.HubSpotClient.Core.Requests;
using Skarp.HubSpotClient.CustomObjects.Interfaces;

namespace Skarp.HubSpotClient.Core
{
Expand Down Expand Up @@ -46,19 +47,21 @@ protected async Task<T> PostAsync<T>(string absoluteUriPath, IHubSpotEntity enti
where T : IHubSpotEntity, new()
{
Logger.LogDebug("Post async for uri path: '{0}' with type: '{1}'", absoluteUriPath, entity.GetType().Name);
var httpMethod = HttpMethod.Post;

return await PutOrPost<T>(absoluteUriPath, entity, true);
return await SerializeAndRunRequest<T>(absoluteUriPath, entity, HttpMethod.Post);
}

protected async Task<T> PutAsync<T>(string absoluteUriPath, IHubSpotEntity entity)
where T : IHubSpotEntity, new()
{
Logger.LogDebug("Post async for uri path: '{0}' with type: '{1}'", absoluteUriPath, entity.GetType().Name);
var json = _serializer.SerializeEntity(entity);
var httpMethod = HttpMethod.Post;
return await SerializeAndRunRequest<T>(absoluteUriPath, entity, HttpMethod.Put);
}

return await PutOrPost<T>(absoluteUriPath, entity, false);
protected async Task<T> PatchAsync<T>(string absoluteUriPath, IHubSpotEntity entity)
where T : ICustomObjectHubSpotEntity, IHubSpotEntity, new()
{
Logger.LogDebug("Patch async for uri path: '{0}' with type: '{1}'", absoluteUriPath, entity.GetType().Name);
return await SerializeAndRunRequest<T>(absoluteUriPath, entity, new HttpMethod("PATCH"));
}

/// <summary>
Expand All @@ -67,16 +70,16 @@ protected async Task<T> PutAsync<T>(string absoluteUriPath, IHubSpotEntity entit
/// <typeparam name="T"></typeparam>
/// <param name="absoluteUriPath"></param>
/// <param name="entity"></param>
/// <param name="usePost"></param>
/// <param name="httpMethod"></param>
/// <returns></returns>
private async Task<T> PutOrPost<T>(string absoluteUriPath, IHubSpotEntity entity, bool usePost)
private async Task<T> SerializeAndRunRequest<T>(string absoluteUriPath, IHubSpotEntity entity, HttpMethod httpMethod)
where T : IHubSpotEntity, new()
{
var json = _serializer.SerializeEntity(entity);

var data = await SendRequestAsync<T>(
absoluteUriPath,
usePost ? HttpMethod.Post : HttpMethod.Put,
httpMethod,
json,
responseData => (T)_serializer.DeserializeEntity<T>(responseData));

Expand Down
75 changes: 60 additions & 15 deletions src/Core/Requests/RequestDataConverter.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Logging;
using Skarp.HubSpotClient.Core.Interfaces;
using Skarp.HubSpotClient.CustomObjects.Interfaces;

namespace Skarp.HubSpotClient.Core.Requests
{
Expand Down Expand Up @@ -50,6 +50,7 @@ public dynamic ToHubspotDataEntity(IHubSpotEntity entity, bool preformConversion
// string value of it, but simply pass the object along - it will be serialized later as JSON...
var propValue = prop.GetValue(entity);
var value = propValue.IsComplexType() ? propValue : propValue?.ToString();

var item = new HubspotDataEntityProp
{
Property = propSerializedName,
Expand All @@ -76,6 +77,44 @@ public dynamic ToHubspotDataEntity(IHubSpotEntity entity, bool preformConversion
return mapped;
}

/// <summary>
/// Converts the given <paramref name="entity"/> to a hubspot data entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
public dynamic ToHubspotDataCustomEntity(IHubSpotEntity entity)
{
_logger.LogDebug("Convert ToHubspotDataCustomEntity");
dynamic mapped = new ExpandoObject();
var propDictionary = new Dictionary<string, object>();
mapped.Properties = propDictionary;

_logger.LogDebug("Use nameValue mapping?: {0}", entity.IsNameValue);

var allProps = entity.GetType().GetProperties();
_logger.LogDebug("Have {0} props to map", allProps.Length);

foreach (var prop in allProps)
{
if (prop.HasIgnoreDataMemberAttribute() || prop.Name.Equals("id", StringComparison.CurrentCultureIgnoreCase)) { continue; }

var propSerializedName = prop.GetPropSerializedName();
_logger.LogDebug("Mapping prop: '{0}' with serialization name: '{1}'", prop.Name, propSerializedName);
if (prop.Name.Equals("RouteBasePath") || prop.Name.Equals("IsNameValue") || prop.Name.Equals("ObjectTypeId")) { continue; }

// IF we have an complex type on the entity that we are trying to convert, let's NOT get the
// string value of it, but simply pass the object along - it will be serialized later as JSON...
var propValue = prop.GetValue(entity);
var value = propValue.IsComplexType() ? propValue : propValue?.ToString();

propDictionary.Add(propSerializedName, value);
}

_logger.LogDebug("Mapping complete, returning data");

return mapped;
}

/// <summary>
/// Converts the given <paramref name="entity"/> to a simple list of name/values.
/// </summary>
Expand Down Expand Up @@ -120,11 +159,12 @@ public IEnumerable<HubspotDataEntityProp> ToNameValueList(object entity)
}

/// <summary>
/// Convert from the dynamicly typed <see cref="ExpandoObject"/> into a strongly typed <see cref="IHubSpotEntity"/>
/// Convert from the dynamically typed <see cref="ExpandoObject"/> into a strongly typed <see cref="IHubSpotEntity"/>
/// </summary>
/// <param name="dynamicObject">The <see cref="ExpandoObject"/> representation of the returned json</param>
/// <param name="isCustomObject"></param>
/// <returns></returns>
public T FromHubSpotResponse<T>(ExpandoObject dynamicObject) where T : IHubSpotEntity, new()
public T FromHubSpotResponse<T>(ExpandoObject dynamicObject, bool isCustomObject = false) where T : IHubSpotEntity, new()
{
var data = (T)ConvertSingleEntity(dynamicObject, new T());
return data;
Expand Down Expand Up @@ -169,7 +209,7 @@ public IEnumerable<HubspotDataEntityProp> ToNameValueList(object entity)

// Convert all the entities
var jsonEntities = expandoDict[propSerializedName];
foreach (var entry in jsonEntities as List<object>)
foreach (var entry in (List<object>) jsonEntities)
{
// convert single entity
var expandoEntry = entry as ExpandoObject;
Expand Down Expand Up @@ -236,6 +276,14 @@ internal object ConvertSingleEntity(ExpandoObject dynamicObject, object dto)
var expandoDict = (IDictionary<string, object>)dynamicObject;
var dtoProps = dto.GetType().GetProperties();

// custom objects are returned with "Id"
if (expandoDict.TryGetValue("id", out var cId))
{
// TODO use properly serialized name of prop to find it
var vidProp = dtoProps.SingleOrDefault(q => q.GetPropSerializedName() == "Id");
vidProp?.SetValue(dto, Convert.ToInt64(cId));
}

// The vid is the "id" of the entity
if (expandoDict.TryGetValue("vid", out var vidData))
{
Expand Down Expand Up @@ -269,32 +317,29 @@ internal object ConvertSingleEntity(ExpandoObject dynamicObject, object dto)
}

// The Properties object in the json / response data contains all the props we wish to map - if that does not exist
// we cannot proceeed
// we cannot proceed
if (!expandoDict.TryGetValue("properties", out var dynamicProperties)) return dto;

foreach (var dynamicProp in dynamicProperties as ExpandoObject)
foreach (var dynamicProp in (ExpandoObject) dynamicProperties)
{
// prop.Key contains the name of the property we wish to map into the DTO
// prop.Value contains the data returned by HubSpot, which is also an object
// in there we need to go get the "value" prop to get the actual value
_logger.LogDebug("Looking at dynamic prop: {0}", dynamicProp.Key);

if (!((IDictionary<string, Object>)dynamicProp.Value).TryGetValue("value", out object dynamicValue))
{
continue;
}
object dynamicValue = null;

dynamicValue = dynamicProp.Value is IDictionary<string, object> objects ? objects.TryGetValue("value", out dynamicValue) : dynamicProp.Value;

// TODO use properly serialized name of prop to find and set it's value
var targetProp =
dtoProps.SingleOrDefault(q => q.GetPropSerializedName() == dynamicProp.Key);
_logger.LogDebug("Have target prop? '{0}' with name: '{1}' and actual value: '{2}'", targetProp != null,
dynamicProp.Key, dynamicValue);
var targetProp = dtoProps.SingleOrDefault(q => q.GetPropSerializedName() == dynamicProp.Key);
_logger.LogDebug("Have target prop? '{0}' with name: '{1}' and actual value: '{2}'", targetProp != null, dynamicProp.Key, dynamicValue);

if (targetProp != null)
{
// property type
var pt = targetProp.PropertyType;
var val = dynamicValue.ToString();
var val = dynamicValue?.ToString();

// skip any nullable properties with a empty value
if (Nullable.GetUnderlyingType(pt) != null && string.IsNullOrEmpty(val))
Expand Down
41 changes: 24 additions & 17 deletions src/Core/Requests/RequestSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Skarp.HubSpotClient.Core.Interfaces;
using Skarp.HubSpotClient.CustomObjects.Interfaces;

namespace Skarp.HubSpotClient.Core.Requests
{
Expand Down Expand Up @@ -38,7 +39,7 @@ public RequestSerializer(
/// <summary>
/// Serializes the entity to JSON.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="obj">The entity.</param>
/// <returns>The serialized entity</returns>
public virtual string SerializeEntity(object obj)
{
Expand All @@ -48,37 +49,43 @@ public virtual string SerializeEntity(object obj)
/// <summary>
/// Serializes the entity to JSON.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="preformConversion">This by defines whether or not a data conversion is performed on the object so that the HubSpot "property-value" syntax will be used for serialization. If this parameter is set to false no data conversion is performed and a standard object serialization is performed. </param>
/// <param name="obj">The entity.</param>
/// <param name="performConversion">This by defines whether or not a data conversion is performed on the object so that the HubSpot "property-value" syntax will be used for serialization. If this parameter is set to false no data conversion is performed and a standard object serialization is performed. </param>
/// <returns>The serialized entity</returns>
public virtual string SerializeEntity(object obj, bool performConversion)
{
if (obj is IHubSpotEntity entity)
switch (obj)
{
var converted = _requestDataConverter.ToHubspotDataEntity(entity, performConversion);
case ICustomObjectHubSpotEntity customEntity:
{
var converted = _requestDataConverter.ToHubspotDataCustomEntity(customEntity);
customEntity.ToHubSpotDataEntity(ref converted);

entity.ToHubSpotDataEntity(ref converted);
return JsonConvert.SerializeObject(converted, _jsonSerializerSettings);
}
case IHubSpotEntity entity:
{
var converted = _requestDataConverter.ToHubspotDataEntity(entity, performConversion);

return JsonConvert.SerializeObject(
converted,
_jsonSerializerSettings);
}
entity.ToHubSpotDataEntity(ref converted);

return JsonConvert.SerializeObject(
obj,
_jsonSerializerSettings);
return JsonConvert.SerializeObject(converted,_jsonSerializerSettings);
}
default:
return JsonConvert.SerializeObject(obj, _jsonSerializerSettings);
}
}

/// <summary>
/// Serializes entity list to JSON.
/// </summary>
/// <param name="entities">The list of entities.</param>
/// <param name="objs">The list of entities.</param>
/// <returns>The serialized list of entities</returns>
public virtual string SerializeEntities<T>(List<T> objs)
{
var result = new StringBuilder("[");

for (int i = 0; i < objs.Count; i++)
for (var i = 0; i < objs.Count; i++)
{
var obj = objs[i];
if (obj is IHubSpotEntity entity)
Expand Down Expand Up @@ -110,13 +117,13 @@ public virtual string SerializeEntities<T>(List<T> objs)
/// <summary>
/// Serializes entity list to name/value JSON.
/// </summary>
/// <param name="entities">The list of entities.</param>
/// <param name="objs">The list of entities.</param>
/// <returns>The serialized list of entities</returns>
public virtual string SerializeEntitiesToNameValueList<T>(IList<T> objs)
{
var result = new StringBuilder("[");

for (int i = 0; i < objs.Count; i++)
for (var i = 0; i < objs.Count; i++)
{
var obj = objs[i];

Expand Down
44 changes: 44 additions & 0 deletions src/CustomObjects/CustomObjectRequestOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;

namespace Skarp.HubSpotClient.CustomObjects
{
public class CustomObjectRequestOptions
{
private int _numberOfItemsToReturn = 20;

/// <summary>
/// Gets or sets the number of custom objects to return.
/// </summary>
/// <remarks>
/// Defaults to 20 which is also the hubspot api default. Max value is 100
/// </remarks>
/// <value>
/// The number of custom objects to return.
/// </value>
public int NumberOfObjectsToReturn
{
get => _numberOfItemsToReturn;
set
{
if (value < 1 || value > 100)
{
throw new ArgumentException(
$"Number of custom objects to return must be a positive integer greater than 0 - you provided {value}");
}
_numberOfItemsToReturn = value;
}
}

/// <summary>
/// Get or set the continuation offset when calling list many times to enumerate all your items
/// </summary>
/// <remarks>
/// The return DTO from List of objects the current "offset" that you can inject into your next list call
/// to continue the listing process
/// </remarks>
public long? ItemsOffset { get; set; } = null;
public List<string> PropertiesToInclude { get; set; } = new List<string>();
public bool UseCustomKeyProperty { get; set; }
}
}
13 changes: 13 additions & 0 deletions src/CustomObjects/Dto/CustomObjectHubSpotEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Skarp.HubSpotClient.CustomObjects.Interfaces;

namespace Skarp.HubSpotClient.CustomObjects.Dto;

public class CustomObjectHubSpotEntity : ICustomObjectHubSpotEntity
{
public long? Id { get; set; }
public string ObjectTypeId { get; set; }
public string RouteBasePath => "/crm/v3/objects";
public bool IsNameValue => false;
public void ToHubSpotDataEntity(ref dynamic dataEntity) { }
public void FromHubSpotDataEntity(dynamic hubspotData) { }
}
Loading