예제 #1
0
        protected override IList <JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var properties             = base.CreateProperties(type, memberSerialization);
            var declaredPropertiesCopy = TypeCache.GetDeclaredProperties(type).ToDictionary(p => p.Key, p => p.Value);

            foreach (var property in properties)
            {
                if (property.PropertyName is string name)
                {
                    declaredPropertiesCopy.Remove(name);
                }
            }
            var additionalProperties = declaredPropertiesCopy
                                       .Values
                                       .Where(p => !p.Hidden)
                                       .Select(p => new JsonProperty
            {
                PropertyType           = p.Type,
                PropertyName           = p.Name,
                Readable               = p.IsReadable,
                Writable               = p.IsWritable,
                ValueProvider          = new DefaultValueProvider(p),
                ObjectCreationHandling = p.ReplaceOnUpdate ? ObjectCreationHandling.Replace : ObjectCreationHandling.Reuse,
                NullValueHandling      = p.HiddenIfNull ? NullValueHandling.Ignore : NullValueHandling.Include,
                Order = p.Order
            });

            return(properties.Union(additionalProperties).ToList());
        }
예제 #2
0
        internal BinaryResource(BinarySelector <T> binarySelector, TypeCache typeCache)
        {
            Name             = typeof(T).GetRESTableTypeName() ?? throw new Exception();
            Type             = typeof(T);
            AvailableMethods = new[] { Method.GET };
            var attribute = typeof(T).GetCustomAttribute <RESTableAttribute>();

            IsInternal                = attribute is RESTableInternalAttribute;
            InterfaceType             = typeof(T).GetRESTableInterfaceType();
            ResourceKind              = ResourceKind.BinaryResource;
            (_, ConditionBindingRule) = typeof(T).GetDynamicConditionHandling(attribute);
            if (attribute != null)
            {
                Description       = attribute.Description;
                BinarySelector    = binarySelector;
                Members           = typeCache.GetDeclaredProperties(typeof(T));
                GETAvailableToAll = attribute.GETAvailableToAll;
            }
            var typeName = typeof(T).FullName;

            if (typeName?.Contains("+") == true)
            {
                IsInnerResource = true;
                var location = typeName.LastIndexOf('+');
                ParentResourceName = typeName.Substring(0, location).Replace('+', '.');
                Name = typeName.Replace('+', '.');
            }
        }
예제 #3
0
        /// <summary>
        /// All types covered by RESTable are selected and validated here
        ///
        /// Resources
        ///   entity
        ///     regular
        ///     wrapper
        ///   terminal
        ///   binary
        /// Views
        ///
        /// </summary>
        /// <returns></returns>
        private void ValidateAndBuildTypeLists(out List <Type> regularTypes, out List <Type> wrapperTypes, out List <Type> terminalTypes,
                                               out List <Type> binaryTypes, out List <Type> eventTypes)
        {
            var allTypes      = typeof(object).GetSubclasses().ToList();
            var resourceTypes = allTypes.Where(t => t.HasAttribute <RESTableAttribute>(out var a) && a is not RESTableProceduralAttribute).ToArray();
            var viewTypes     = allTypes.Where(t => t.HasAttribute <RESTableViewAttribute>()).ToArray();

            if (resourceTypes.Union(viewTypes).ContainsDuplicates(t => t.GetRESTableTypeName()?.ToLower() ?? "unknown", out var dupe))
            {
                throw new InvalidResourceDeclarationException("Types used by RESTable must have unique case insensitive names. Found " +
                                                              $"multiple types with case insensitive name '{dupe}'.");
            }

            void ValidateViewTypes(ICollection <Type> _viewTypes)
            {
                foreach (var viewType in _viewTypes)
                {
                    var resource = viewType.DeclaringType;
                    if (!viewType.IsClass || !viewType.IsNestedPublic || resource == null)
                    {
                        throw new InvalidResourceViewDeclarationException(viewType,
                                                                          "Resource view types must be declared as public classes nested within the the " +
                                                                          "resource type they are views for");
                    }
                    if (viewType.IsSubclassOf(resource))
                    {
                        throw new InvalidResourceViewDeclarationException(viewType, "Views cannot inherit from their resource types");
                    }
                    if (typeof(IResourceWrapper).IsAssignableFrom(resource))
                    {
                        var wrapped = resource.GetWrappedType();
                        if (!viewType.ImplementsGenericInterface(typeof(ISelector <>), out var param) || param[0] != wrapped)
                        {
                            throw new InvalidResourceViewDeclarationException(viewType,
                                                                              $"Expected view type to implement ISelector<{wrapped.GetRESTableTypeName()}>");
                        }
                    }
                    else if (!viewType.ImplementsGenericInterface(typeof(ISelector <>), out var param) || param[0] != resource)
                    {
                        throw new InvalidResourceViewDeclarationException(viewType,
                                                                          $"Expected view type to implement ISelector<{resource.GetRESTableTypeName()}>");
                    }
                    var resourceProperties = TypeCache.GetDeclaredProperties(resource);
                    foreach (var property in TypeCache.FindAndParseDeclaredProperties(viewType).Where(prop => resourceProperties.ContainsKey(prop.Name)))
                    {
                        throw new InvalidResourceViewDeclarationException(viewType,
                                                                          $"Invalid property '{property.Name}'. Resource view types must not contain any public instance " +
                                                                          "properties with the same name (case insensitive) as a property of the corresponding resource. " +
                                                                          "All properties in the resource are automatically inherited for use in conditions with the view.");
                    }
                }
            }

            (regularTypes, wrapperTypes, terminalTypes, binaryTypes, eventTypes) = ResourceValidator.Validate(resourceTypes);
            ValidateViewTypes(viewTypes);
        }
예제 #4
0
파일: View.cs 프로젝트: erikvk/RESTable
        internal View(Type viewType, TypeCache typeCache)
        {
            var viewAttribute = viewType.GetCustomAttribute <RESTableViewAttribute>();

            Type = viewType;
            if (viewAttribute != null)
            {
                Name                 = viewAttribute.CustomName ?? viewType.Name;
                ViewSelector         = DelegateMaker.GetDelegate <ViewSelector <TResource> >(viewType);
                AsyncViewSelector    = DelegateMaker.GetDelegate <AsyncViewSelector <TResource> >(viewType);
                Members              = typeCache.GetDeclaredProperties(viewType);
                Description          = viewAttribute.Description;
                ConditionBindingRule = viewAttribute.AllowDynamicConditions
                    ? TermBindingRule.DeclaredWithDynamicFallback
                    : TermBindingRule.OnlyDeclared;
            }
        }
예제 #5
0
        internal static bool IsValid(IEntityResource resource, TypeCache typeCache, out string reason)
        {
            if (resource.InterfaceType != null)
            {
                var interfaceName = resource.InterfaceType.GetRESTableTypeName();
                var members       = typeCache.GetDeclaredProperties(resource.InterfaceType);
                if (members.ContainsKey("objectno"))
                {
                    reason = $"Invalid Interface '{interfaceName}' assigned to resource '{resource.Name}'. " +
                             "Interface contained a property with a reserved name: 'ObjectNo'";
                    return(false);
                }
                if (members.ContainsKey("objectid"))
                {
                    reason = $"Invalid Interface '{interfaceName}' assigned to resource '{resource.Name}'. " +
                             "Interface contained a property with a reserved name: 'ObjectID'";
                    return(false);
                }
            }

            reason = null;
            return(true);
        }
예제 #6
0
        internal TerminalResource(TypeCache typeCache)
        {
            Name             = typeof(T).GetRESTableTypeName() ?? throw new Exception();
            Type             = typeof(T);
            AvailableMethods = new[] { Method.GET };
            IsInternal       = false;
            IsGlobal         = true;
            var attribute = typeof(T).GetCustomAttribute <RESTableAttribute>();

            InterfaceType             = typeof(T).GetRESTableInterfaceType();
            ResourceKind              = ResourceKind.TerminalResource;
            (_, ConditionBindingRule) = typeof(T).GetDynamicConditionHandling(attribute);
            Description = attribute?.Description;
            Members     = typeCache.GetDeclaredProperties(typeof(T));
            Constructor = typeof(T).GetConstructors().First();
            ConstructorParameterIndexes = new Dictionary <string, int>(StringComparer.OrdinalIgnoreCase);
            ConstructorParameterInfos   = Constructor.GetParameters();
            for (var i = 0; i < ConstructorParameterInfos.Length; i += 1)
            {
                HasConstructorParameters = true;
                var parameter = ConstructorParameterInfos[i];
                ConstructorParameterIndexes[parameter.Name] = i;
            }
            GETAvailableToAll = attribute?.GETAvailableToAll == true;
            IsDynamicTerminal = typeof(IDictionary <string, object>).IsAssignableFrom(typeof(T));

            var typeName = typeof(T).FullName;

            if (typeName?.Contains('+') == true)
            {
                IsInnerResource = true;
                var location = typeName.LastIndexOf('+');
                ParentResourceName = typeName.Substring(0, location).Replace('+', '.');
                Name = typeName.Replace('+', '.');
            }
        }
예제 #7
0
        /// <inheritdoc />
        public override async Task <long> SerializeCollection <T>(IAsyncEnumerable <T> collection, Stream stream, IRequest request, CancellationToken cancellationToken)
            where T : class
        {
            if (collection == null)
            {
                return(0);
            }
            try
            {
                using var package = new ExcelPackage(stream);
                var currentRow = 1;
                var worksheet  = package.Workbook.Worksheets.Add(request?.Resource.Name ?? "Sheet1");

                async Task writeEntities(IAsyncEnumerable <object> entities)
                {
                    switch (entities)
                    {
                    case IAsyncEnumerable <IDictionary <string, object> > dicts:
                        var columns = new Dictionary <string, int>(StringComparer.OrdinalIgnoreCase);
                        await foreach (var dict in dicts.ConfigureAwait(false))
                        {
                            currentRow += 1;
                            foreach (var(key, value) in dict)
                            {
                                if (!columns.TryGetValue(key, out var column))
                                {
                                    column       = columns.Count + 1;
                                    columns[key] = column;
                                    var cell = worksheet.Cells[1, column];
                                    cell.Style.Font.Bold = true;
                                    cell.Value           = key;
                                }
                                WriteExcelCell(worksheet.Cells[currentRow, column], value);
                            }
                        }
                        break;

                    case IAsyncEnumerable <JObject> jobjects:
                        var _columns = new Dictionary <string, int>(StringComparer.OrdinalIgnoreCase);
                        await foreach (var jobject in jobjects.ConfigureAwait(false))
                        {
                            currentRow += 1;
                            foreach (var(key, value) in jobject)
                            {
                                if (!_columns.TryGetValue(key, out var column))
                                {
                                    column        = _columns.Count + 1;
                                    _columns[key] = column;
                                    var cell = worksheet.Cells[1, column];
                                    cell.Style.Font.Bold = true;
                                    cell.Value           = key;
                                }
                                WriteExcelCell(worksheet.Cells[currentRow, column], value.ToObject <object>());
                            }
                        }
                        break;

                    default:
                        var properties  = TypeCache.GetDeclaredProperties(typeof(T)).Values.Where(p => !p.Hidden).ToList();
                        var columnIndex = 1;
                        foreach (var property in properties)
                        {
                            var cell = worksheet.Cells[1, columnIndex];
                            cell.Style.Font.Bold = true;
                            cell.Value           = property.Name;
                            columnIndex         += 1;
                        }
                        await foreach (var entity in entities.ConfigureAwait(false))
                        {
                            currentRow += 1;
                            columnIndex = 1;
                            foreach (var property in properties)
                            {
                                WriteExcelCell(worksheet.Cells[currentRow, columnIndex], GetCellValue(property, entity));
                                columnIndex += 1;
                            }
                        }
                        break;
                    }
                }

                await writeEntities(collection).ConfigureAwait(false);

                if (currentRow == 1)
                {
                    return(0);
                }
                worksheet.Cells.AutoFitColumns(0);
                package.Save();
                return((long)currentRow - 1);
            }
            catch (Exception e)
            {
                throw new ExcelFormatException(e.Message, e);
            }
        }
예제 #8
0
        public (List <Type> regular, List <Type> wrappers, List <Type> terminals, List <Type> binaries, List <Type> events) Validate(params Type[] types)
        {
            var entityTypes = types
                              .Where(t => !typeof(Terminal).IsAssignableFrom(t) &&
                                     !typeof(IEvent).IsAssignableFrom(t) &&
                                     !t.ImplementsGenericInterface(typeof(IBinary <>)))
                              .ToList();
            var regularTypes = entityTypes
                               .Where(t => !typeof(IResourceWrapper).IsAssignableFrom(t))
                               .ToList();
            var wrapperTypes = entityTypes
                               .Where(t => typeof(IResourceWrapper).IsAssignableFrom(t))
                               .ToList();
            var terminalTypes = types
                                .Where(t => typeof(Terminal).IsAssignableFrom(t))
                                .ToList();
            var binaryTypes = types
                              .Where(t => t.ImplementsGenericInterface(typeof(IBinary <>)))
                              .ToList();
            var eventTypes = types
                             .Where(t => !t.IsAbstract && typeof(IEvent).IsAssignableFrom(t))
                             .ToList();

            void ValidateCommon(Type type)
            {
                #region Check general stuff

                if (type.FullName == null)
                {
                    throw new InvalidResourceDeclarationException(
                              "Encountered an unknown type. No further information is available.");
                }

                if (type.IsGenericTypeDefinition)
                {
                    throw new InvalidResourceDeclarationException(
                              $"Found a generic resource type '{type.GetRESTableTypeName()}'. RESTable resource types must be non-generic");
                }

                if (type.FullName.Count(c => c == '+') >= 2)
                {
                    throw new InvalidResourceDeclarationException($"Invalid resource '{type.GetRESTableTypeName()}'. " +
                                                                  "Inner resources cannot have their own inner resources");
                }

                if (type.HasAttribute <RESTableViewAttribute>())
                {
                    throw new InvalidResourceDeclarationException(
                              $"Invalid resource type '{type.GetRESTableTypeName()}'. Resource types cannot be " +
                              "decorated with the 'RESTableViewAttribute'");
                }

                if (type.Namespace == null)
                {
                    throw new InvalidResourceDeclarationException($"Invalid type '{type.GetRESTableTypeName()}'. Unknown namespace");
                }

                if (Configuration.ReservedNamespaces.Contains(type.Namespace.ToLower()) &&
                    type.Assembly != typeof(RESTableConfigurator).Assembly)
                {
                    throw new InvalidResourceDeclarationException(
                              $"Invalid namespace for resource type '{type.GetRESTableTypeName()}'. Namespace '{type.Namespace}' is reserved by RESTable");
                }

                if ((!type.IsClass || !type.IsPublic && !type.IsNestedPublic) && type.Assembly != typeof(Resource).Assembly)
                {
                    throw new InvalidResourceDeclarationException(
                              $"Invalid type '{type.GetRESTableTypeName()}'. Resource types must be public classes");
                }

                if (type.GetRESTableInterfaceType() is Type interfaceType)
                {
                    if (!interfaceType.IsInterface)
                    {
                        throw new InvalidResourceDeclarationException(
                                  $"Invalid Interface of type '{interfaceType.GetRESTableTypeName()}' assigned to resource '{type.GetRESTableTypeName()}'. " +
                                  "Type is not an interface");
                    }

                    if (interfaceType.GetProperties()
                        .Select(p => p.Name)
                        .ContainsDuplicates(StringComparer.OrdinalIgnoreCase, out var interfacePropDupe))
                    {
                        throw new InvalidResourceMemberException(
                                  $"Invalid Interface of type '{interfaceType.GetRESTableTypeName()}' assigned to resource '{type.GetRESTableTypeName()}'. " +
                                  $"Interface contained properties with duplicate names matching '{interfacePropDupe}' (case insensitive).");
                    }

                    var interfaceName = interfaceType.GetRESTableTypeName();
                    foreach (var method in type.GetInterfaceMap(interfaceType).TargetMethods)
                    {
                        if (!method.IsSpecialName)
                        {
                            continue;
                        }
                        var interfaceProperty = interfaceType
                                                .GetProperties()
                                                .First(p => p.GetGetMethod()?.Name is string getname && method.Name.EndsWith(getname) ||
                                                       p.GetSetMethod()?.Name is string setname && method.Name.EndsWith(setname));

                        Type propertyType = null;
                        if (method.IsPrivate && method.Name.StartsWith($"{interfaceName}.get_") || method.Name.StartsWith("get_"))
                        {
                            propertyType = method.ReturnType;
                        }
                        else if (method.IsPrivate && method.Name.StartsWith($"{interfaceName}.set_") || method.Name.StartsWith("set_"))
                        {
                            propertyType = method.GetParameters()[0].ParameterType;
                        }

                        if (propertyType == null)
                        {
                            throw new InvalidResourceDeclarationException(
                                      $"Invalid implementation of interface '{interfaceType.GetRESTableTypeName()}' assigned to resource '{type.GetRESTableTypeName()}'. " +
                                      $"Unable to determine the type for interface property '{interfaceProperty.Name}'");
                        }

                        PropertyInfo projectedProperty;
                        if (method.Name.StartsWith($"{interfaceName}.get_"))
                        {
                            projectedProperty = method.GetInstructions()
                                                .Select(i => i.OpCode == OpCodes.Call && i.Operand is MethodInfo calledMethod && method.IsSpecialName
                                    ? type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                                        .FirstOrDefault(p => p.GetGetMethod() == calledMethod)
                                    : null)
                                                .LastOrDefault(p => p != null);
                        }
                        else if (method.Name.StartsWith($"{interfaceName}.set_"))
                        {
                            projectedProperty = method.GetInstructions()
                                                .Select(i => i.OpCode == OpCodes.Call && i.Operand is MethodInfo calledMethod && method.IsSpecialName
                                    ? type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                                        .FirstOrDefault(p => p.GetSetMethod() == calledMethod)
                                    : null)
                                                .LastOrDefault(p => p != null);
                        }
                        else
                        {
                            continue;
                        }

                        if (projectedProperty == null)
                        {
                            throw new InvalidResourceDeclarationException(
                                      $"Invalid implementation of interface '{interfaceType.GetRESTableTypeName()}' assigned to resource '{type.GetRESTableTypeName()}'. " +
                                      $"RESTable was unable to determine which property of '{type.GetRESTableTypeName()}' that is exposed by interface " +
                                      $"property '{interfaceProperty.Name}'. For getters, RESTable will look for the last IL instruction " +
                                      "in the method body that fetches a property value from the resource type. For setters, RESTable will look " +
                                      "for the last IL instruction in the method body that sets a property value in the resource type.");
                        }

                        if (projectedProperty.PropertyType != propertyType)
                        {
                            throw new InvalidResourceDeclarationException(
                                      $"Invalid implementation of interface '{interfaceType.GetRESTableTypeName()}' assigned to resource '{type.GetRESTableTypeName()}'. " +
                                      $"RESTable matched interface property '{interfaceProperty.Name}' with resource property '{projectedProperty.Name}' " +
                                      "using the interface property matching rules, but these properties have a type mismatch. Expected " +
                                      $"'{projectedProperty.PropertyType.GetRESTableTypeName()}' but found '{propertyType.GetRESTableTypeName()}' in interface");
                        }
                    }
                }

                #endregion

                #region Check for invalid IDictionary implementation

                var validTypes = new[] { typeof(string), typeof(object) };
                if (type.ImplementsGenericInterface(typeof(IDictionary <,>), out var typeParams) &&
                    !type.IsSubclassOf(typeof(JObject)) &&
                    !typeParams.SequenceEqual(validTypes))
                {
                    throw new InvalidResourceDeclarationException(
                              $"Invalid resource declaration for type '{type.GetRESTableTypeName()}'. All resource types implementing " +
                              "the generic 'System.Collections.Generic.IDictionary`2' interface must either be subclasses of " +
                              "Newtonsoft.Json.Linq.JObject or have System.String as first type parameter and System.Object as " +
                              $"second type parameter. Found {typeParams[0].GetRESTableTypeName()} and {typeParams[1].GetRESTableTypeName()}");
                }

                #endregion

                #region Check for invalid IEnumerable implementation

                if ((type.ImplementsGenericInterface(typeof(IEnumerable <>)) || typeof(IEnumerable).IsAssignableFrom(type)) &&
                    !type.ImplementsGenericInterface(typeof(IDictionary <,>)))
                {
                    throw new InvalidResourceDeclarationException(
                              $"Invalid resource declaration for type '{type.GetRESTableTypeName()}'. The type has an invalid imple" +
                              $"mentation of an IEnumerable interface. The resource '{type.GetRESTableTypeName()}' (or any of its base types) " +
                              "cannot implement the \'System.Collections.Generic.IEnumerable`1\' or \'System.Collections.IEnume" +
                              "rable\' interfaces without also implementing the \'System.Collections.Generic.IDictionary`2\' interface."
                              );
                }

                #endregion

                #region Check for public instance fields

                var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
                if (fields.Any())
                {
                    throw new InvalidResourceMemberException(
                              $"A RESTable resource cannot have public instance fields, only properties. Resource: '{type.GetRESTableTypeName()}' had " +
                              $"fields: {string.Join(", ", fields.Select(f => $"'{f.Name}'"))} in resource '{type.GetRESTableTypeName()}'"
                              );
                }

                #endregion

                #region Check for properties with duplicate case insensitive names

                if (TypeCache.FindAndParseDeclaredProperties(type).ContainsDuplicates(DeclaredProperty.NameComparer, out var duplicate))
                {
                    throw new InvalidResourceMemberException(
                              $"Invalid properties for resource '{type.GetRESTableTypeName()}'. Names of public instance properties must " +
                              $"be unique (case insensitive). Two or more property names were equivalent to '{duplicate.Name}'."
                              );
                }

                #endregion
            }

            void ValidateEntityDeclarations(List <Type> regularResources)
            {
                foreach (var type in regularResources)
                {
                    ValidateCommon(type);
                }
            }

            void ValidateWrapperDeclaration(List <Type> wrappers)
            {
                if (wrappers.Select(type => (type, wrapped: type.GetWrappedType())).ContainsDuplicates(pair => pair.wrapped, out var dupe))
                {
                    throw new InvalidResourceWrapperException(dupe, "must wrap unique types. Found multiple wrapper declarations for " +
                                                              $"wrapped type '{dupe.wrapped.GetRESTableTypeName()}'.");
                }

                foreach (var wrapper in wrappers)
                {
                    var wrapped = wrapper.GetWrappedType();
                    var _types  = (wrapper, wrapped);
                    var members = wrapper.GetMembers(BindingFlags.Public | BindingFlags.Instance);
                    if (members.OfType <PropertyInfo>().Any() || members.OfType <FieldInfo>().Any())
                    {
                        throw new InvalidResourceWrapperException(_types, "cannot contain public instance properties or fields");
                    }
                    ValidateCommon(wrapper);
                    if (wrapper.GetInterfaces()
                        .Where(i => typeof(IOperationsInterface).IsAssignableFrom(i))
                        .Any(i => i.IsGenericType && i.GenericTypeArguments[0] != wrapped))
                    {
                        throw new InvalidResourceWrapperException(_types, "cannot implement operations interfaces for types other than " +
                                                                  $"'{wrapped.GetRESTableTypeName()}'.");
                    }
                    if (wrapped.FullName?.Contains("+") == true)
                    {
                        throw new InvalidResourceWrapperException(_types, "cannot wrap types that are declared within the scope of some other class.");
                    }
                    if (wrapped.HasAttribute <RESTableAttribute>())
                    {
                        throw new InvalidResourceWrapperException(_types, "cannot wrap types already decorated with the 'RESTableAttribute' attribute");
                    }
                    if (wrapper.Assembly == typeof(RESTableConfigurator).Assembly)
                    {
                        throw new InvalidResourceWrapperException(_types, "cannot wrap RESTable types");
                    }
                }
            }

            void ValidateTerminalDeclarations(List <Type> terminals)
            {
                foreach (var terminal in terminals)
                {
                    ValidateCommon(terminal);
                    var constructors = terminal.GetConstructors();
                    if (constructors.Length != 1)
                    {
                        throw new InvalidTerminalDeclarationException(terminal, "must have exactly one public constructor. Found " + constructors.Length);
                    }
                    var constructorParameterNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase);
                    var properties = TypeCache.GetDeclaredProperties(terminal);
                    foreach (var parameter in constructors[0].GetParameters())
                    {
                        if (!constructorParameterNames.Add(parameter.Name))
                        {
                            throw new InvalidTerminalDeclarationException(terminal, "must not define multiple constructor parameters with the same case " +
                                                                          $"insensitive parameter name. Found duplicate of '{parameter.Name.ToLowerInvariant()}'");
                        }
                        if (!properties.ContainsKey(parameter.Name))
                        {
                            throw new InvalidTerminalDeclarationException(terminal, "must not define a constructor parameter with a name that does not equal the name of a " +
                                                                          "public instance property on the same type (case insensitive). Found parameter " +
                                                                          $"'{parameter.Name.ToLowerInvariant()}' with no matching public instance property.");
                        }
                    }
                    if (terminal.ImplementsGenericInterface(typeof(IEnumerable <>)))
                    {
                        throw new InvalidTerminalDeclarationException(terminal, "must not be collections");
                    }
                    if (terminal.HasResourceProviderAttribute())
                    {
                        throw new InvalidTerminalDeclarationException(terminal, "must not be decorated with a resource provider attribute");
                    }
                    if (typeof(IOperationsInterface).IsAssignableFrom(terminal))
                    {
                        throw new InvalidTerminalDeclarationException(terminal, "must not implement any other RESTable operations interfaces");
                    }
                }
            }

            void ValidateBinaryDeclarations(List <Type> binaries)
            {
                foreach (var binary in binaries)
                {
                    ValidateCommon(binary);
                    if (binary.ImplementsGenericInterface(typeof(IEnumerable <>)))
                    {
                        throw new InvalidBinaryDeclarationException(binary, "must not be collections");
                    }
                    if (binary.HasResourceProviderAttribute())
                    {
                        throw new InvalidBinaryDeclarationException(binary, "must not be decorated with a resource provider attribute");
                    }
                    if (typeof(IOperationsInterface).IsAssignableFrom(binary))
                    {
                        throw new InvalidBinaryDeclarationException(binary, "must not implement any other RESTable operations interfaces");
                    }
                }
            }

            void ValidateEventDeclarations(List <Type> events)
            {
                foreach (var @event in events)
                {
                    ValidateCommon(@event);
                    if (!typeof(IEvent).IsAssignableFrom(@event))
                    {
                        throw new InvalidEventDeclarationException(@event, "must inherit from 'RESTable.Resources.Event<T>'");
                    }
                    if (@event.ImplementsGenericInterface(typeof(IEnumerable <>)))
                    {
                        throw new InvalidEventDeclarationException(@event, "must not be collections");
                    }
                    if (@event.HasResourceProviderAttribute())
                    {
                        throw new InvalidEventDeclarationException(@event, "must not be decorated with a resource provider attribute");
                    }
                    if (typeof(IOperationsInterface).IsAssignableFrom(@event))
                    {
                        throw new InvalidEventDeclarationException(@event, "must not implement any RESTable operations interfaces");
                    }
                }
            }

            ValidateEntityDeclarations(entityTypes);
            ValidateWrapperDeclaration(wrapperTypes);
            ValidateTerminalDeclarations(terminalTypes);
            ValidateBinaryDeclarations(binaryTypes);
            ValidateEventDeclarations(eventTypes);

            return(regularTypes, wrapperTypes, terminalTypes, binaryTypes, eventTypes);
        }
예제 #9
0
파일: Metadata.cs 프로젝트: erikvk/RESTable
        public static Metadata GetMetadata(MetadataLevel level, AccessRights rights, RootAccess rootAccess, ResourceCollection resourceCollection, TypeCache typeCache)
        {
            var domain          = rights?.Keys ?? resourceCollection.AllResources;
            var entityResources = domain
                                  .OfType <IEntityResource>()
                                  .Where(r => r.IsGlobal)
                                  .OrderBy(r => r.Name)
                                  .ToList();
            var terminalResources = domain
                                    .OfType <ITerminalResource>()
                                    .ToList();

            if (level == MetadataLevel.OnlyResources)
            {
                return new Metadata
                       {
                           CurrentAccessScope = new Dictionary <IResource, Method[]>(rights ?? rootAccess),
                           EntityResources    = entityResources.ToArray(),
                           TerminalResources  = terminalResources.ToArray()
                       }
            }
            ;

            var checkedTypes = new HashSet <Type>();

            void parseType(Type type)
            {
                switch (type)
                {
                case var _ when type.IsEnum:
                    checkedTypes.Add(type);
                    break;

                case var _ when type.IsNullable(out var baseType):
                    parseType(baseType);

                    break;

                case var _ when type.ImplementsGenericInterface(typeof(IEnumerable <>), out var param) && param.Any():
                    if (param[0].ImplementsGenericInterface(typeof(IEnumerable <>)))
                    {
                        break;
                    }
                    parseType(param[0]);

                    break;

                case var _ when type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair <,>):
                case var _ when IsPrimitive(type):
                case var _ when type == typeof(object):
                    break;

                case var _ when checkedTypes.Add(type):
                {
                    var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance)
                                 .Where(p => !p.RESTableIgnored())
                                 .Select(p => p.FieldType);
                    foreach (var field in fields)
                    {
                        parseType(field);
                    }
                    var properties = typeCache.GetDeclaredProperties(type).Values
                                     .Where(p => !p.Hidden)
                                     .Select(p => p.Type);
                    foreach (var property in properties)
                    {
                        parseType(property);
                    }
                    break;
                }
                }
            }

            var entityTypes   = entityResources.Select(r => r.Type).ToHashSet();
            var terminalTypes = terminalResources.Select(r => r.Type).ToHashSet();

            foreach (var type in entityTypes)
            {
                parseType(type);
            }
            checkedTypes.ExceptWith(entityTypes);
            foreach (var type in terminalTypes)
            {
                parseType(type);
            }
            checkedTypes.ExceptWith(terminalTypes);

            return(new Metadata
            {
                CurrentAccessScope = new Dictionary <IResource, Method[]>(rights ?? rootAccess),
                EntityResources = entityResources.ToArray(),
                TerminalResources = terminalResources.ToArray(),
                EntityResourceTypes = new ReadOnlyDictionary <Type, Member[]>(entityTypes.ToDictionary(t => t, type =>
                                                                                                       typeCache.GetDeclaredProperties(type).Values.Cast <Member>().ToArray())),
                PeripheralTypes = new ReadOnlyDictionary <Type, Member[]>(checkedTypes.ToDictionary(t => t, type =>
                {
                    var props = typeCache.GetDeclaredProperties(type).Values;
                    var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance)
                                 .Where(p => !p.RESTableIgnored())
                                 .Select(f => new Field(f));
                    return props.Union <Member>(fields).ToArray();
                }))
            });
        }