using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; using Octokit.Internal; #if !PORTABLE using System.Collections.Concurrent; #endif namespace Octokit { /// /// Base class for classes which represent query string parameters to certain API endpoints. /// public abstract class RequestParameters { #if PORTABLE static readonly ConcurrentCache> _propertiesMap = new ConcurrentCache>(); #else static readonly ConcurrentDictionary> _propertiesMap = new ConcurrentDictionary>(); #endif /// /// Converts the derived object into a dictionary that can be used to supply query string parameters. /// /// public virtual IDictionary ToParametersDictionary() { var map = _propertiesMap.GetOrAdd(GetType(), GetPropertyParametersForType); return (from property in map let value = property.GetValue(this) let key = property.Key where value != null select new { key, value }).ToDictionary(kvp => kvp.key, kvp => kvp.value); } static List GetPropertyParametersForType(Type type) { return type.GetAllProperties() .Where(p => p.Name != "DebuggerDisplay") .Select(p => new PropertyParameter(p)) .ToList(); } [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "GitHub API depends on lower case strings")] static Func GetValueFunc(Type propertyType) { if (typeof(IEnumerable).IsAssignableFrom(propertyType)) { return (prop, value) => { var list = ((IEnumerable)value).ToArray(); return !list.Any() ? null : string.Join(",", list); }; } if (propertyType.IsDateTimeOffset()) { return (prop, value) => { if (value == null) return null; return ((DateTimeOffset)value).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture); }; } if (propertyType.IsEnumeration()) { var enumToAttributeDictionary = Enum.GetNames(propertyType) .ToDictionary(name => name, name => GetParameterAttributeValueForEnumName(propertyType, name)); return (prop, value) => { if (value == null) return null; string attributeValue; return enumToAttributeDictionary.TryGetValue(value.ToString(), out attributeValue) ? attributeValue ?? value.ToString().ToLowerInvariant() : value.ToString().ToLowerInvariant(); }; } if (typeof(bool).IsAssignableFrom(propertyType)) { // GitHub does not recognize title-case boolean values as arguments. // We need to convert them to lowercase. return (prop, value) => value != null ? value.ToString().ToLowerInvariant() : null; } return (prop, value) => value != null ? value.ToString() : null; } static string GetParameterAttributeValueForEnumName(Type enumType, string name) { var member = enumType.GetMember(name).FirstOrDefault(); if (member == null) return null; var attribute = member.GetCustomAttributes(typeof(ParameterAttribute), false) .Cast() .FirstOrDefault(); return attribute != null ? attribute.Value : null; } class PropertyParameter { readonly Func _valueFunc; readonly PropertyInfo _property; public PropertyParameter(PropertyInfo property) { _property = property; Key = GetParameterKeyFromProperty(property); _valueFunc = GetValueFunc(property.PropertyType); } public string Key { get; private set; } public string GetValue(object instance) { return _valueFunc(_property, _property.GetValue(instance, null)); } [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "GitHub API depends on lower case strings")] static string GetParameterKeyFromProperty(PropertyInfo property) { var attribute = property.GetCustomAttributes(typeof(ParameterAttribute), false) .Cast() .FirstOrDefault(attr => attr.Key != null); return attribute == null ? property.Name.ToLowerInvariant() : attribute.Key; } } } }