private void AssertSameIdentityInRefData(AtomicOperationObject operation, ResourceIdentifierObject resourceIdentifierObject)
        {
            if (operation.Ref.Id != null && resourceIdentifierObject.Id != null && resourceIdentifierObject.Id != operation.Ref.Id)
            {
                throw new JsonApiSerializationException("Resource ID mismatch between 'ref.id' and 'data.id' element.",
                                                        $"Expected resource with ID '{operation.Ref.Id}' in 'data.id', instead of '{resourceIdentifierObject.Id}'.",
                                                        atomicOperationIndex: AtomicOperationIndex);
            }

            if (operation.Ref.Lid != null && resourceIdentifierObject.Lid != null && resourceIdentifierObject.Lid != operation.Ref.Lid)
            {
                throw new JsonApiSerializationException("Resource local ID mismatch between 'ref.lid' and 'data.lid' element.",
                                                        $"Expected resource with local ID '{operation.Ref.Lid}' in 'data.lid', instead of '{resourceIdentifierObject.Lid}'.",
                                                        atomicOperationIndex: AtomicOperationIndex);
            }

            if (operation.Ref.Id != null && resourceIdentifierObject.Lid != null)
            {
                throw new JsonApiSerializationException("Resource identity mismatch between 'ref.id' and 'data.lid' element.",
                                                        $"Expected resource with ID '{operation.Ref.Id}' in 'data.id', instead of '{resourceIdentifierObject.Lid}' in 'data.lid'.",
                                                        atomicOperationIndex: AtomicOperationIndex);
            }

            if (operation.Ref.Lid != null && resourceIdentifierObject.Id != null)
            {
                throw new JsonApiSerializationException("Resource identity mismatch between 'ref.lid' and 'data.id' element.",
                                                        $"Expected resource with local ID '{operation.Ref.Lid}' in 'data.lid', instead of '{resourceIdentifierObject.Id}' in 'data.id'.",
                                                        atomicOperationIndex: AtomicOperationIndex);
            }
        }
 private void AssertElementHasType(ResourceIdentifierObject resourceIdentifierObject, string elementPath)
 {
     if (resourceIdentifierObject.Type == null)
     {
         throw new JsonApiSerializationException($"The '{elementPath}.type' element is required.", null, atomicOperationIndex: AtomicOperationIndex);
     }
 }
Esempio n. 3
0
        private void AssertHasIdOrLid(ResourceIdentifierObject resourceIdentifierObject, RelationshipAttribute relationship)
        {
            if (AtomicOperationIndex != null)
            {
                bool hasNone = resourceIdentifierObject.Id == null && resourceIdentifierObject.Lid == null;
                bool hasBoth = resourceIdentifierObject.Id != null && resourceIdentifierObject.Lid != null;

                if (hasNone || hasBoth)
                {
                    throw new JsonApiSerializationException("Request body must include 'id' or 'lid' element.",
                                                            $"Expected 'id' or 'lid' element in '{relationship.PublicName}' relationship.",
                                                            atomicOperationIndex: AtomicOperationIndex);
                }
            }
            else
            {
                if (resourceIdentifierObject.Id == null)
                {
                    throw new JsonApiSerializationException("Request body must include 'id' element.",
                                                            $"Expected 'id' element in '{relationship.PublicName}' relationship.",
                                                            atomicOperationIndex: AtomicOperationIndex);
                }

                AssertHasNoLid(resourceIdentifierObject);
            }
        }
        private IIdentifiable GetIncludedRelationship(ResourceIdentifierObject relatedResourceIdentifier, List <DocumentData> includedResources, RelationshipAttribute relationshipAttr)
        {
            // at this point we can be sure the relationshipAttr.Type is IIdentifiable because we were able to successfully build the ContextGraph
            var relatedInstance = relationshipAttr.Type.New <IIdentifiable>();

            relatedInstance.StringId = relatedResourceIdentifier.Id;

            // can't provide any more data other than the rio since it is not contained in the included section
            if (includedResources == null || includedResources.Count == 0)
            {
                return(relatedInstance);
            }

            var includedResource = GetLinkedResource(relatedResourceIdentifier, includedResources);

            if (includedResource == null)
            {
                return(relatedInstance);
            }

            var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(relationshipAttr.Type);

            if (contextEntity == null)
            {
                throw new JsonApiException(400, $"Included type '{relationshipAttr.Type}' is not a registered json:api resource.");
            }

            SetEntityAttributes(relatedInstance, contextEntity, includedResource.Attributes);

            return(relatedInstance);
        }
 private void AssertHasNoLid(ResourceIdentifierObject resourceIdentifierObject)
 {
     if (resourceIdentifierObject.Lid != null)
     {
         throw new JsonApiSerializationException("Local IDs cannot be used at this endpoint.", null, atomicOperationIndex: AtomicOperationIndex);
     }
 }
Esempio n. 6
0
        /// <summary>
        /// Additional processing required for client deserialization, responsible for parsing the <see cref="Document.Included" /> property. When a relationship
        /// value is parsed, it goes through the included list to set its attributes and relationships.
        /// </summary>
        /// <param name="resource">
        /// The resource that was constructed from the document's body.
        /// </param>
        /// <param name="field">
        /// The metadata for the exposed field.
        /// </param>
        /// <param name="data">
        /// Relationship data for <paramref name="resource" />. Is null when <paramref name="field" /> is not a <see cref="RelationshipAttribute" />.
        /// </param>
        protected override void AfterProcessField(IIdentifiable resource, ResourceFieldAttribute field, RelationshipEntry data = null)
        {
            ArgumentGuard.NotNull(resource, nameof(resource));
            ArgumentGuard.NotNull(field, nameof(field));

            // Client deserializers do not need additional processing for attributes.
            if (field is AttrAttribute)
            {
                return;
            }

            // if the included property is empty or absent, there is no additional data to be parsed.
            if (Document.Included.IsNullOrEmpty())
            {
                return;
            }

            if (data != null)
            {
                if (field is HasOneAttribute hasOneAttr)
                {
                    // add attributes and relationships of a parsed HasOne relationship
                    ResourceIdentifierObject rio = data.SingleData;
                    hasOneAttr.SetValue(resource, rio == null ? null : ParseIncludedRelationship(rio));
                }
                else if (field is HasManyAttribute hasManyAttr)
                {
                    // add attributes and relationships of a parsed HasMany relationship
                    IEnumerable <IIdentifiable> items = data.ManyData.Select(ParseIncludedRelationship);
                    IEnumerable values = CollectionConverter.CopyToTypedCollection(items, hasManyAttr.Property.PropertyType);
                    hasManyAttr.SetValue(resource, values);
                }
            }
        }
 private void AssertHasId(ResourceIdentifierObject resourceIdentifierObject, RelationshipAttribute relationship)
 {
     if (resourceIdentifierObject.Id == null)
     {
         throw new JsonApiSerializationException("Request body must include 'id' element.",
                                                 $"Expected 'id' element in '{relationship.PublicName}' relationship.");
     }
 }
        private void AssertHasType(ResourceIdentifierObject resourceIdentifierObject, RelationshipAttribute relationship)
        {
            if (resourceIdentifierObject.Type == null)
            {
                var details = relationship != null
                    ? $"Expected 'type' element in '{relationship.PublicName}' relationship."
                    : "Expected 'type' element in 'data' element.";

                throw new JsonApiSerializationException("Request body must include 'type' element.", details);
            }
        }
        private void AssertElementHasIdOrLid(ResourceIdentifierObject resourceIdentifierObject, string elementPath, bool isRequired)
        {
            bool hasNone = resourceIdentifierObject.Id == null && resourceIdentifierObject.Lid == null;
            bool hasBoth = resourceIdentifierObject.Id != null && resourceIdentifierObject.Lid != null;

            if (isRequired ? hasNone || hasBoth : hasBoth)
            {
                throw new JsonApiSerializationException($"The '{elementPath}.id' or '{elementPath}.lid' element is required.", null,
                                                        atomicOperationIndex: AtomicOperationIndex);
            }
        }
 private DocumentData GetLinkedResource(ResourceIdentifierObject relatedResourceIdentifier, List <DocumentData> includedResources)
 {
     try
     {
         return(includedResources.SingleOrDefault(r => r.Type == relatedResourceIdentifier.Type && r.Id == relatedResourceIdentifier.Id));
     }
     catch (InvalidOperationException e)
     {
         throw new JsonApiException(400, $"A compound document MUST NOT include more than one resource object for each type and id pair."
                                    + $"The duplicate pair was '{relatedResourceIdentifier.Type}, {relatedResourceIdentifier.Id}'", e);
     }
 }
 private ResourceObject GetLinkedResource(ResourceIdentifierObject relatedResourceIdentifier)
 {
     try
     {
         return(_document.Included.SingleOrDefault(r => r.Type == relatedResourceIdentifier.Type && r.Id == relatedResourceIdentifier.Id));
     }
     catch (InvalidOperationException e)
     {
         throw new InvalidOperationException("A compound document MUST NOT include more than one resource object for each type and id pair."
                                             + $"The duplicate pair was '{relatedResourceIdentifier.Type}, {relatedResourceIdentifier.Id}'", e);
     }
 }
 private void AssertCompatibleId(ResourceIdentifierObject resourceIdentifierObject, Type idType)
 {
     if (resourceIdentifierObject.Id != null)
     {
         try
         {
             RuntimeTypeConverter.ConvertType(resourceIdentifierObject.Id, idType);
         }
         catch (FormatException exception)
         {
             throw new JsonApiSerializationException(null, exception.Message, null, AtomicOperationIndex);
         }
     }
 }
        public void Setting_ExposeData_To_RIO_Sets_SingleData()
        {
            // Arrange
            var relationshipData = new RelationshipEntry();
            var relationship     = new ResourceIdentifierObject {
                Id   = "9",
                Type = "authors"
            };

            // Act
            relationshipData.Data = relationship;

            // Assert
            Assert.NotNull(relationshipData.SingleData);
            Assert.Equal("authors", relationshipData.SingleData.Type);
            Assert.Equal("9", relationshipData.SingleData.Id);
            Assert.False(relationshipData.IsManyData);
        }
        private IIdentifiable CreateRightResource(RelationshipAttribute relationship,
                                                  ResourceIdentifierObject resourceIdentifierObject)
        {
            if (resourceIdentifierObject != null)
            {
                AssertHasType(resourceIdentifierObject, relationship);
                AssertHasId(resourceIdentifierObject, relationship);

                var rightResourceContext = GetExistingResourceContext(resourceIdentifierObject.Type);
                AssertRightTypeIsCompatible(rightResourceContext, relationship);

                var rightInstance = ResourceFactory.CreateInstance(rightResourceContext.ResourceType);
                rightInstance.StringId = resourceIdentifierObject.Id;

                return(rightInstance);
            }

            return(null);
        }
Esempio n. 15
0
        private IIdentifiable GetIncludedRelationship(ResourceIdentifierObject relatedResourceIdentifier,
                                                      List <ResourceObject> includedResources,
                                                      RelationshipAttribute relationshipAttr,
                                                      List <string> includePaths = null)
        {
            // at this point we can be sure the relationshipAttr.Type is IIdentifiable because we were able to successfully build the ResourceGraph
            var relatedInstance = relationshipAttr.DependentType.New <IIdentifiable>();

            relatedInstance.StringId = relatedResourceIdentifier.Id;

            // can't provide any more data other than the rio since it is not contained in the included section
            if (includedResources == null || includedResources.Count == 0)
            {
                return(relatedInstance);
            }

            var includedResource = GetLinkedResource(relatedResourceIdentifier, includedResources);

            if (includedResource == null)
            {
                return(relatedInstance);
            }

            var contextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationshipAttr.DependentType);

            if (contextEntity == null)
            {
                throw new JsonApiException(400, $"Included type '{relationshipAttr.DependentType}' is not a registered json:api resource.");
            }

            SetEntityAttributes(relatedInstance, contextEntity, includedResource.Attributes);

            var tmp = includedResources.FirstOrDefault(x => x.Id.Equals(relatedResourceIdentifier.Id) && x.Type.Equals(relatedResourceIdentifier.Type));

            if (tmp != null)
            {
                relatedInstance = SetRelationships(relatedInstance, contextEntity, tmp.Relationships, includedResources, includePaths) as IIdentifiable;
            }
            return(relatedInstance);
        }
        /// <summary>
        /// Searches for and parses the included relationship.
        /// </summary>
        private IIdentifiable ParseIncludedRelationship(ResourceIdentifierObject relatedResourceIdentifier)
        {
            var relatedResourceContext = ResourceContextProvider.GetResourceContext(relatedResourceIdentifier.Type);

            if (relatedResourceContext == null)
            {
                throw new InvalidOperationException($"Included type '{relatedResourceIdentifier.Type}' is not a registered JSON:API resource.");
            }

            var relatedInstance = ResourceFactory.CreateInstance(relatedResourceContext.ResourceType);

            relatedInstance.StringId = relatedResourceIdentifier.Id;

            var includedResource = GetLinkedResource(relatedResourceIdentifier);

            if (includedResource != null)
            {
                SetAttributes(relatedInstance, includedResource.Attributes, relatedResourceContext.Attributes);
                SetRelationships(relatedInstance, includedResource.Relationships, relatedResourceContext.Relationships);
            }

            return(relatedInstance);
        }
        protected RelationshipEntry CreateRelationshipData(string relatedType = null, bool isToManyData = false, string id = "10")
        {
            var entry = new RelationshipEntry();

            ResourceIdentifierObject rio = relatedType == null
                ? null
                : new ResourceIdentifierObject
            {
                Id   = id,
                Type = relatedType
            };

            if (isToManyData)
            {
                entry.Data = relatedType != null?rio.AsList() : new List <ResourceIdentifierObject>();
            }
            else
            {
                entry.Data = rio;
            }

            return(entry);
        }
Esempio n. 18
0
        public void BuildIncluded_DeeplyNestedCircularChainOfSingleData_CanBuild()
        {
            // Arrange
            (Article article, Person author, _, Person reviewer, _) = GetAuthorChainInstances();
            IReadOnlyCollection <RelationshipAttribute> authorChain = GetIncludedRelationshipsChain("author.blogs.reviewer.favoriteFood");
            IncludedResourceObjectBuilder builder = GetBuilder();

            // Act
            builder.IncludeRelationshipChain(authorChain, article);
            IList <ResourceObject> result = builder.Build();

            // Assert
            Assert.Equal(6, result.Count);

            ResourceObject           authorResourceObject = result.Single(ro => ro.Type == "people" && ro.Id == author.StringId);
            ResourceIdentifierObject authorFoodRelation   = authorResourceObject.Relationships["favoriteFood"].SingleData;

            Assert.Equal(author.FavoriteFood.StringId, authorFoodRelation.Id);

            ResourceObject           reviewerResourceObject = result.Single(ro => ro.Type == "people" && ro.Id == reviewer.StringId);
            ResourceIdentifierObject reviewerFoodRelation   = reviewerResourceObject.Relationships["favoriteFood"].SingleData;

            Assert.Equal(reviewer.FavoriteFood.StringId, reviewerFoodRelation.Id);
        }
 private bool HasLocalId(ResourceIdentifierObject rio) => string.IsNullOrEmpty(rio.LocalId) == false;
        /// <summary>
        /// Sets the value of the navigation property for the related resource.
        /// If the resource has been included, all attributes will be set.
        /// If the resource has not been included, only the id will be set.
        /// </summary>
        private void SetHasOneNavigationPropertyValue(object entity, HasOneAttribute hasOneAttr, ResourceIdentifierObject rio, List <DocumentData> included)
        {
            // if the resource identifier is null, there should be no reason to instantiate an instance
            if (rio != null && rio.Id != null)
            {
                // we have now set the FK property on the resource, now we need to check to see if the
                // related entity was included in the payload and update its attributes
                var includedRelationshipObject = GetIncludedRelationship(rio, included, hasOneAttr);
                if (includedRelationshipObject != null)
                {
                    hasOneAttr.SetValue(entity, includedRelationshipObject);
                }

                // we need to store the fact that this relationship was included in the payload
                // for EF, the repository will use these pointers to make ensure we don't try to
                // create resources if they already exist, we just need to create the relationship
                _jsonApiContext.HasOneRelationshipPointers.Add(hasOneAttr, includedRelationshipObject);
            }
        }
        private void SetHasOneForeignKeyValue(object entity, HasOneAttribute hasOneAttr, PropertyInfo foreignKeyProperty, ResourceIdentifierObject rio)
        {
            var foreignKeyPropertyValue = rio?.Id ?? null;

            if (foreignKeyProperty != null)
            {
                // in the case of the HasOne independent side of the relationship, we should still create the shell entity on the other side
                // we should not actually require the resource to have a foreign key (be the dependent side of the relationship)

                // e.g. PATCH /articles
                // {... { "relationships":{ "Owner": { "data": null } } } }
                if (rio == null && Nullable.GetUnderlyingType(foreignKeyProperty.PropertyType) == null)
                {
                    throw new JsonApiException(400, $"Cannot set required relationship identifier '{hasOneAttr.IdentifiablePropertyName}' to null because it is a non-nullable type.");
                }

                var convertedValue = TypeHelper.ConvertType(foreignKeyPropertyValue, foreignKeyProperty.PropertyType);
                foreignKeyProperty.SetValue(entity, convertedValue);
                _jsonApiContext.RelationshipsToUpdate[hasOneAttr] = convertedValue;
            }
        }
Esempio n. 22
0
        /// <summary>
        /// Searches for and parses the included relationship
        /// </summary>
        private IIdentifiable ParseIncludedRelationship(RelationshipAttribute relationshipAttr, ResourceIdentifierObject relatedResourceIdentifier)
        {
            var relatedInstance = relationshipAttr.RightType.New <IIdentifiable>();

            relatedInstance.StringId = relatedResourceIdentifier.Id;

            var includedResource = GetLinkedResource(relatedResourceIdentifier);

            if (includedResource == null)
            {
                return(relatedInstance);
            }

            var resourceContext = _provider.GetResourceContext(relatedResourceIdentifier.Type);

            if (resourceContext == null)
            {
                throw new InvalidOperationException($"Included type '{relationshipAttr.RightType}' is not a registered json:api resource.");
            }

            SetAttributes(relatedInstance, includedResource.Attributes, resourceContext.Attributes);
            SetRelationships(relatedInstance, includedResource.Relationships, resourceContext.Relationships);
            return(relatedInstance);
        }
Esempio n. 23
0
        /// <summary>
        /// Sets the value of the navigation property for the related resource.
        /// If the resource has been included, all attributes will be set.
        /// If the resource has not been included, only the id will be set.
        /// </summary>
        private void SetHasOneNavigationPropertyValue(object entity, HasOneAttribute hasOneAttr, ResourceIdentifierObject rio, List <ResourceObject> included)
        {
            // if the resource identifier is null, there should be no reason to instantiate an instance
            if (rio != null && rio.Id != null)
            {
                // we have now set the FK property on the resource, now we need to check to see if the
                // related entity was included in the payload and update its attributes
                var includedRelationshipObject = GetIncludedRelationship(rio,
                                                                         included,
                                                                         hasOneAttr);

                if (includedRelationshipObject != null)
                {
                    hasOneAttr.SetValue(entity, includedRelationshipObject);
                }

                /// todo: as a part of the process of decoupling JADNC (specifically
                /// through the decoupling IJsonApiContext), we now no longer need to
                /// store the updated relationship values in this property. For now
                /// just assigning null as value, will remove this property later as a whole.
                /// see #512
                _jsonApiContext.HasOneRelationshipPointers.Add(hasOneAttr, null);
            }
        }
Esempio n. 24
0
        private void SetHasOneForeignKeyValue(object entity, HasOneAttribute hasOneAttr, PropertyInfo foreignKeyProperty, ResourceIdentifierObject rio)
        {
            var foreignKeyPropertyValue = rio?.Id ?? null;

            if (foreignKeyProperty != null)
            {
                // in the case of the HasOne independent side of the relationship, we should still create the shell entity on the other side
                // we should not actually require the resource to have a foreign key (be the dependent side of the relationship)

                // e.g. PATCH /articles
                // {... { "relationships":{ "Owner": { "data": null } } } }
                bool foreignKeyPropertyIsNullableType = Nullable.GetUnderlyingType(foreignKeyProperty.PropertyType) != null ||
                                                        foreignKeyProperty.PropertyType == typeof(string);
                if (rio == null && !foreignKeyPropertyIsNullableType)
                {
                    throw new JsonApiException(400, $"Cannot set required relationship identifier '{hasOneAttr.IdentifiablePropertyName}' to null because it is a non-nullable type.");
                }

                var convertedValue = TypeHelper.ConvertType(foreignKeyPropertyValue, foreignKeyProperty.PropertyType);
                /// todo: as a part of the process of decoupling JADNC (specifically
                /// through the decoupling IJsonApiContext), we now no longer need to
                /// store the updated relationship values in this property. For now
                /// just assigning null as value, will remove this property later as a whole.
                /// see #512
                if (convertedValue == null)
                {
                    _jsonApiContext.HasOneRelationshipPointers.Add(hasOneAttr, null);
                }
            }
        }