Skip to content
Open
13 changes: 13 additions & 0 deletions InfluxData.Net.Common/Attributes/FieldAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;

namespace InfluxData.Net.Common.Attributes
{
public class FieldAttribute : InfluxBaseAttribute
{
public FieldAttribute([CallerMemberName]string name = null)
: base(name) { }
}
}
19 changes: 19 additions & 0 deletions InfluxData.Net.Common/Attributes/InfluxBaseAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace InfluxData.Net.Common.Attributes
{
public class InfluxBaseAttribute : Attribute
{

public InfluxBaseAttribute() { }

public InfluxBaseAttribute(string name)
{
Name = name;
}

public string Name { get; set; }
}
}
8 changes: 8 additions & 0 deletions InfluxData.Net.Common/Attributes/MeasurementAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace InfluxData.Net.Common.Attributes
{
public class MeasurementAttribute : InfluxBaseAttribute
{
}
}
13 changes: 13 additions & 0 deletions InfluxData.Net.Common/Attributes/TagAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;

namespace InfluxData.Net.Common.Attributes
{
public class TagAttribute : InfluxBaseAttribute
{
public TagAttribute([CallerMemberName]string name = null)
: base(name) { }
}
}
12 changes: 12 additions & 0 deletions InfluxData.Net.Common/Attributes/TimestampAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace InfluxData.Net.Common.Attributes
{
public class TimestampAttribute : InfluxBaseAttribute
{
public TimestampAttribute()
: base("time") { }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace InfluxData.Net.Common.Infrastructure
{
public class MissingExpectedAttributeException : Exception
{
public MissingExpectedAttributeException(Type attributeType)
: base($"The expected attribute: {attributeType.Name} is missing")
{
}
}
}
187 changes: 187 additions & 0 deletions InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
using InfluxData.Net.Common.Attributes;
using InfluxData.Net.InfluxDb.Models;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using InfluxData.Net.Common.Infrastructure;
using System.Reflection;

namespace InfluxData.Net.InfluxDb.Helpers
{
public static class PointExtensions
{
/// <summary>
/// Allows for converting attribute decorated types into a <see cref="Point"/> <para />
/// Attribute rules: <para />
/// 1) Must have exactly ONE [Measurement] attribute <para />
/// 2) Must not have more than ONE [Timestamp] attribute <para />
/// 3) Must have at least ONE [Field] attribute <para />
/// 4) [Tag] attribute is optional <para />
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <param name="model"></param>
/// <returns></returns>
/// <example>
/// Example of valid type:
/// <code>
///
/// public class MyType
/// {
/// [Measurement]
/// public string MyMeasurement { get; set; }
///
/// [Timestamp]
/// public DateTime Time { get; set; }
///
/// [Tag]
/// public string SignalName { get; set; }
///
/// [Field]
/// public double Value { get; set; }
/// }
///
/// </code>
/// </example>
public static Point ToPoint<TModel>(this TModel model)
{
var type = model.GetType();

Point point = new Point();

var properties = type.GetProperties();

point.TrySetTimestamp(model, properties);
point.TrySetMeasurement(model, properties);
point.TrySetTags(model, properties);
point.TrySetFields(model, properties);

return point;
}

private static Point TrySetTimestamp<TModel>(this Point point, TModel model, PropertyInfo[] properties)
{
var timestampProperties = properties.Where(x => x.IsDefined(typeof(TimestampAttribute), false));

// Make sure only one TimestampAttribute is defined
if (timestampProperties.Any())
{
if (timestampProperties.Count() != 1)
throw new InvalidOperationException($"Cannot have multiple {typeof(TimestampAttribute).Name} attributes defined");

var timestampProperty = timestampProperties.FirstOrDefault();
var timestampPropertyValue = timestampProperty.GetValue(model);

if (!timestampProperty.PropertyType.Equals(typeof(DateTime)))
throw new InvalidOperationException($"{nameof(timestampProperty.Name)} is not of type {typeof(DateTime).Name}");

if (timestampPropertyValue == null)
throw new InvalidOperationException($"{nameof(timestampProperty.Name)} cannot be null");

point.Timestamp = (DateTime)timestampPropertyValue;
}

return point;
}

private static Point TrySetMeasurement<TModel>(this Point point, TModel model, PropertyInfo[] properties)
{
var measurementProperties = properties.Where(x => x.IsDefined(typeof(MeasurementAttribute), false));

if(!measurementProperties.Any())
{
throw new MissingExpectedAttributeException(typeof(MeasurementAttribute));
}

// Make sure only one MeasurementAttribute is defined
if (measurementProperties.Count() != 1)
{
throw new InvalidOperationException($"Must have exactly one {typeof(MeasurementAttribute).Name} attribute defined");
}

var measurementProperty = measurementProperties.FirstOrDefault();
var measurementPropertyValue = measurementProperty.GetValue(model);

if (!measurementProperty.PropertyType.Equals(typeof(string)))
{
throw new InvalidOperationException($"{nameof(measurementProperty.Name)} is not of type {typeof(string).Name}");
}

if ((string.IsNullOrWhiteSpace((string)measurementPropertyValue)))
{
throw new InvalidOperationException($"{nameof(measurementProperty.Name)} cannot be null or whitespace");
}

point.Name = (string)measurementPropertyValue;

return point;
}

private static Point TrySetTags<TModel>(this Point point, TModel model, PropertyInfo[] properties)
{
var tagProperties = properties.Where(x => x.IsDefined(typeof(TagAttribute), false));

if (tagProperties.Any(x => !x.PropertyType.Equals(typeof(string))))
{
throw new InvalidOperationException($"Tags can only be string values");
}

foreach (var tagProperty in tagProperties)
{
var tagType = tagProperty.PropertyType;
var tagValue = tagProperty.GetValue(model);

if (tagValue == null)
continue;

var converted = Convert.ChangeType(tagValue, tagType);

var propertyName = tagProperty.GetCustomAttribute<TagAttribute>().Name;

point.Tags.Add(propertyName, converted);
}

return point;
}

private static Point TrySetFields<TModel>(this Point point, TModel model, PropertyInfo[] properties)
{
var fieldProperties = properties.Where(x => x.IsDefined(typeof(FieldAttribute), false));

// Make sure at least one FieldAttribute is defined
if (!fieldProperties.Any())
{
throw new MissingExpectedAttributeException(typeof(FieldAttribute));
}

if (fieldProperties.Any(x => !x.PropertyType.IsSimple()))
{
throw new InvalidOperationException($"Fields can only be primitive or string values");
}

foreach (var fieldProperty in fieldProperties)
{
var fieldType = fieldProperty.PropertyType;
var fieldValue = fieldProperty.GetValue(model);

if (fieldValue == null)
continue;

var converted = Convert.ChangeType(fieldValue, fieldType);

var propertyName = fieldProperty.GetCustomAttribute<FieldAttribute>().Name;

point.Fields.Add(propertyName, converted);
}

return point;
}

private static bool IsSimple(this Type type)
{
return
type.IsPrimitive ||
type.Equals(typeof(String));
}
}
}
Loading