private OperationContainer ParseForDeleteResourceOperation(AtomicOperationObject operation, OperationKind kind) { AssertElementHasType(operation.Ref, "ref"); AssertElementHasIdOrLid(operation.Ref, "ref", true); ResourceContext primaryResourceContext = GetExistingResourceContext(operation.Ref.Type); AssertCompatibleId(operation.Ref, primaryResourceContext.IdentityType); IIdentifiable primaryResource = ResourceFactory.CreateInstance(primaryResourceContext.ResourceType); primaryResource.StringId = operation.Ref.Id; primaryResource.LocalId = operation.Ref.Lid; var request = new JsonApiRequest { Kind = EndpointKind.AtomicOperations, BasePath = _request.BasePath, PrimaryId = primaryResource.StringId, PrimaryResource = primaryResourceContext, OperationKind = kind }; return(new OperationContainer(kind, primaryResource, new TargetedFields(), request)); }
private OperationContainer DeserializeOperation(AtomicOperationObject operation) { _targetedFields.Attributes.Clear(); _targetedFields.Relationships.Clear(); AssertHasNoHref(operation); OperationKind kind = GetOperationKind(operation); switch (kind) { case OperationKind.CreateResource: case OperationKind.UpdateResource: { return(ParseForCreateOrUpdateResourceOperation(operation, kind)); } case OperationKind.DeleteResource: { return(ParseForDeleteResourceOperation(operation, kind)); } } bool requireToManyRelationship = kind == OperationKind.AddToRelationship || kind == OperationKind.RemoveFromRelationship; return(ParseForRelationshipOperation(operation, kind, requireToManyRelationship)); }
private OperationKind GetOperationKind(AtomicOperationObject operation) { switch (operation.Code) { case AtomicOperationCode.Add: { if (operation.Ref != null && operation.Ref.Relationship == null) { throw new JsonApiSerializationException("The 'ref.relationship' element is required.", null, atomicOperationIndex: AtomicOperationIndex); } return(operation.Ref == null ? OperationKind.CreateResource : OperationKind.AddToRelationship); } case AtomicOperationCode.Update: { return(operation.Ref?.Relationship != null ? OperationKind.SetRelationship : OperationKind.UpdateResource); } case AtomicOperationCode.Remove: { if (operation.Ref == null) { throw new JsonApiSerializationException("The 'ref' element is required.", null, atomicOperationIndex: AtomicOperationIndex); } return(operation.Ref.Relationship != null ? OperationKind.RemoveFromRelationship : OperationKind.DeleteResource); } } throw new NotSupportedException($"Unknown operation code '{operation.Code}'."); }
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 AssertHasNoHref(AtomicOperationObject operation) { if (operation.Href != null) { throw new JsonApiSerializationException("Usage of the 'href' element is not supported.", null, atomicOperationIndex: AtomicOperationIndex); } }
private OperationContainer ParseForCreateOrUpdateResourceOperation(AtomicOperationObject operation, OperationKind kind) { ResourceObject resourceObject = GetRequiredSingleDataForResourceOperation(operation); AssertElementHasType(resourceObject, "data"); AssertElementHasIdOrLid(resourceObject, "data", kind != OperationKind.CreateResource); ResourceContext primaryResourceContext = GetExistingResourceContext(resourceObject.Type); AssertCompatibleId(resourceObject, primaryResourceContext.IdentityType); if (operation.Ref != null) { // For resource update, 'ref' is optional. But when specified, it must match with 'data'. AssertElementHasType(operation.Ref, "ref"); AssertElementHasIdOrLid(operation.Ref, "ref", true); ResourceContext resourceContextInRef = GetExistingResourceContext(operation.Ref.Type); if (resourceContextInRef != primaryResourceContext) { throw new JsonApiSerializationException("Resource type mismatch between 'ref.type' and 'data.type' element.", $"Expected resource of type '{resourceContextInRef.PublicName}' in 'data.type', instead of '{primaryResourceContext.PublicName}'.", atomicOperationIndex: AtomicOperationIndex); } AssertSameIdentityInRefData(operation, resourceObject); } var request = new JsonApiRequest { Kind = EndpointKind.AtomicOperations, #pragma warning disable CS0618 // Type or member is obsolete BasePath = _request.BasePath, #pragma warning restore CS0618 // Type or member is obsolete PrimaryResource = primaryResourceContext, OperationKind = kind }; _request.CopyFrom(request); IIdentifiable primaryResource = ParseResourceObject(operation.SingleData); _resourceDefinitionAccessor.OnDeserialize(primaryResource); request.PrimaryId = primaryResource.StringId; _request.CopyFrom(request); var targetedFields = new TargetedFields { Attributes = _targetedFields.Attributes.ToHashSet(), Relationships = _targetedFields.Relationships.ToHashSet() }; AssertResourceIdIsNotTargeted(targetedFields); return(new OperationContainer(kind, primaryResource, targetedFields, request)); }
private OperationContainer ParseForRelationshipOperation(AtomicOperationObject operation, OperationKind kind, bool requireToMany) { AssertElementHasType(operation.Ref, "ref"); AssertElementHasIdOrLid(operation.Ref, "ref", true); var primaryResourceContext = GetExistingResourceContext(operation.Ref.Type); AssertCompatibleId(operation.Ref, primaryResourceContext.IdentityType); var primaryResource = ResourceFactory.CreateInstance(primaryResourceContext.ResourceType); primaryResource.StringId = operation.Ref.Id; primaryResource.LocalId = operation.Ref.Lid; var relationship = GetExistingRelationship(operation.Ref, primaryResourceContext); if (requireToMany && relationship is HasOneAttribute) { throw new JsonApiSerializationException( $"Only to-many relationships can be targeted in '{operation.Code.ToString().Camelize()}' operations.", $"Relationship '{operation.Ref.Relationship}' must be a to-many relationship.", atomicOperationIndex: AtomicOperationIndex); } var secondaryResourceContext = ResourceContextProvider.GetResourceContext(relationship.RightType); var request = new JsonApiRequest { Kind = EndpointKind.AtomicOperations, BasePath = _request.BasePath, PrimaryId = primaryResource.StringId, PrimaryResource = primaryResourceContext, SecondaryResource = secondaryResourceContext, Relationship = relationship, IsCollection = relationship is HasManyAttribute, OperationKind = kind }; _request.CopyFrom(request); _targetedFields.Relationships.Add(relationship); ParseDataForRelationship(relationship, secondaryResourceContext, operation, primaryResource); var targetedFields = new TargetedFields { Attributes = _targetedFields.Attributes.ToHashSet(), Relationships = _targetedFields.Relationships.ToHashSet() }; return(new OperationContainer(kind, primaryResource, targetedFields, request)); }
private ResourceObject GetRequiredSingleDataForResourceOperation(AtomicOperationObject operation) { if (operation.Data == null) { throw new JsonApiSerializationException("The 'data' element is required.", null, atomicOperationIndex: AtomicOperationIndex); } if (operation.SingleData == null) { throw new JsonApiSerializationException("Expected single data element for create/update resource operation.", null, atomicOperationIndex: AtomicOperationIndex); } return(operation.SingleData); }
private void ParseDataForRelationship(RelationshipAttribute relationship, ResourceContext secondaryResourceContext, AtomicOperationObject operation, IIdentifiable primaryResource) { if (relationship is HasOneAttribute) { if (operation.ManyData != null) { throw new JsonApiSerializationException( "Expected single data element for to-one relationship.", $"Expected single data element for '{relationship.PublicName}' relationship.", atomicOperationIndex: AtomicOperationIndex); } if (operation.SingleData != null) { ValidateSingleDataForRelationship(operation.SingleData, secondaryResourceContext, "data"); var secondaryResource = ParseResourceObject(operation.SingleData); relationship.SetValue(primaryResource, secondaryResource); } } else if (relationship is HasManyAttribute) { if (operation.ManyData == null) { throw new JsonApiSerializationException( "Expected data[] element for to-many relationship.", $"Expected data[] element for '{relationship.PublicName}' relationship.", atomicOperationIndex: AtomicOperationIndex); } var secondaryResources = new List <IIdentifiable>(); foreach (var resourceObject in operation.ManyData) { ValidateSingleDataForRelationship(resourceObject, secondaryResourceContext, "data[]"); var secondaryResource = ParseResourceObject(resourceObject); secondaryResources.Add(secondaryResource); } var rightResources = TypeHelper.CopyToTypedCollection(secondaryResources, relationship.Property.PropertyType); relationship.SetValue(primaryResource, rightResources); } }