/// <summary> /// Additional processing required for server deserialization. Flags a processed attribute or relationship as updated using /// <see cref="ITargetedFields" />. /// </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) { bool isCreatingResource = IsCreatingResource(); bool isUpdatingResource = IsUpdatingResource(); if (field is AttrAttribute attr) { if (isCreatingResource && !attr.Capabilities.HasFlag(AttrCapabilities.AllowCreate)) { throw new JsonApiSerializationException("Setting the initial value of the requested attribute is not allowed.", $"Setting the initial value of '{attr.PublicName}' is not allowed.", atomicOperationIndex: AtomicOperationIndex); } if (isUpdatingResource && !attr.Capabilities.HasFlag(AttrCapabilities.AllowChange)) { throw new JsonApiSerializationException("Changing the value of the requested attribute is not allowed.", $"Changing the value of '{attr.PublicName}' is not allowed.", atomicOperationIndex: AtomicOperationIndex); } _targetedFields.Attributes.Add(attr); } else if (field is RelationshipAttribute relationship) { _targetedFields.Relationships.Add(relationship); } }
/// <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) { // 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 == null || _document.Included.Count == 0) { return; } if (field is HasOneAttribute hasOneAttr) { // add attributes and relationships of a parsed HasOne relationship var rio = data.SingleData; hasOneAttr.SetValue(resource, rio == null ? null : ParseIncludedRelationship(hasOneAttr, rio), _resourceFactory); } else if (field is HasManyAttribute hasManyAttr) { // add attributes and relationships of a parsed HasMany relationship var items = data.ManyData.Select(rio => ParseIncludedRelationship(hasManyAttr, rio)); var values = items.CopyToTypedCollection(hasManyAttr.Property.PropertyType); hasManyAttr.SetValue(resource, values, _resourceFactory); } }
/// <summary> /// Additional processing required for server deserialization. Flags a /// processed attribute or relationship as updated using <see cref="ITargetedFields"/>. /// </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) { if (field is AttrAttribute attr) { if (_httpContextAccessor.HttpContext.Request.Method == HttpMethod.Post.Method && !attr.Capabilities.HasFlag(AttrCapabilities.AllowCreate)) { throw new JsonApiSerializationException( "Setting the initial value of the requested attribute is not allowed.", $"Setting the initial value of '{attr.PublicName}' is not allowed."); } if (_httpContextAccessor.HttpContext.Request.Method == HttpMethod.Patch.Method && !attr.Capabilities.HasFlag(AttrCapabilities.AllowChange)) { throw new JsonApiSerializationException( "Changing the value of the requested attribute is not allowed.", $"Changing the value of '{attr.PublicName}' is not allowed."); } _targetedFields.Attributes.Add(attr); } else if (field is RelationshipAttribute relationship) { _targetedFields.Relationships.Add(relationship); } }
/// <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); } } }
public PropertySelector(ResourceFieldAttribute field, QueryLayer nextLayer = null) { ArgumentGuard.NotNull(field, nameof(field)); OriginatingField = field; NextLayer = nextLayer; Property = field is HasManyThroughAttribute hasManyThrough ? hasManyThrough.ThroughProperty : field.Property; }
protected override IReadOnlyCollection <ResourceFieldAttribute> OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) { ResourceFieldAttribute field = ChainResolver.GetField(path, _resourceContext, path); _validateSingleFieldCallback?.Invoke(field, _resourceContext, path); return(field.AsArray()); }
protected void ValidateSingleField(ResourceFieldAttribute field, ResourceContext resourceContext, string path) { if (field is AttrAttribute attribute && !attribute.Capabilities.HasFlag(AttrCapabilities.AllowSort)) { throw new InvalidQueryStringParameterException(_lastParameterName, "Sorting on the requested attribute is not allowed.", $"Sorting on attribute '{attribute.PublicName}' is not allowed."); } }
public PropertySelector(ResourceFieldAttribute field, QueryLayer nextLayer = null) { OriginatingField = field ?? throw new ArgumentNullException(nameof(field)); NextLayer = nextLayer; Property = field is HasManyThroughAttribute hasManyThrough ? hasManyThrough.ThroughProperty : field.Property; }
public ResourceFieldChainExpression(ResourceFieldAttribute field) { if (field == null) { throw new ArgumentNullException(nameof(field)); } Fields = new[] { field }; }
protected ResourceContext GetResourceContextForScope(ResourceFieldChainExpression scope) { if (scope == null) { return(RequestResource); } ResourceFieldAttribute lastField = scope.Fields.Last(); Type type = lastField is RelationshipAttribute relationship ? relationship.RightType : lastField.Property.PropertyType; return(_resourceContextProvider.GetResourceContext(type)); }
protected SparseFieldSetExpression ParseSparseFieldSet() { var fields = new Dictionary <string, ResourceFieldAttribute>(); while (TokenStack.Any()) { if (fields.Count > 0) { EatSingleCharacterToken(TokenKind.Comma); } ResourceFieldChainExpression nextChain = ParseFieldChain(FieldChainRequirements.EndsInAttribute, "Field name expected."); ResourceFieldAttribute nextField = nextChain.Fields.Single(); fields[nextField.PublicName] = nextField; } return(fields.Any() ? new SparseFieldSetExpression(fields.Values) : null); }
/// <summary> /// Additional processing required for server deserialization. Flags a /// processed attribute or relationship as updated using <see cref="ITargetedFields"/>. /// </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) { if (field is AttrAttribute attr) { if (attr.Capabilities.HasFlag(AttrCapabilities.AllowChange)) { _targetedFields.Attributes.Add(attr); } else { throw new InvalidRequestBodyException( "Changing the value of the requested attribute is not allowed.", $"Changing the value of '{attr.PublicName}' is not allowed.", null); } } else if (field is RelationshipAttribute relationship) { _targetedFields.Relationships.Add(relationship); } }
private static string GetPropertyName(ResourceFieldAttribute field) { return(field is HasManyThroughAttribute hasManyThrough ? hasManyThrough.ThroughProperty.Name : field.Property.Name); }
private static string GetPropertyName(ResourceFieldAttribute field) { // In case of a HasManyThrough access (from count() or has() function), we only need to look at the number of entries in the join table. return(field is HasManyThroughAttribute hasManyThrough ? hasManyThrough.ThroughProperty.Name : field.Property.Name); }
private static SparseFieldSetExpression IncludeField(SparseFieldSetExpression sparseFieldSet, ResourceFieldAttribute fieldToInclude) { if (sparseFieldSet == null || sparseFieldSet.Fields.Contains(fieldToInclude)) { return(sparseFieldSet); } HashSet <ResourceFieldAttribute> fieldSet = sparseFieldSet.Fields.ToHashSet(); fieldSet.Add(fieldToInclude); return(new SparseFieldSetExpression(fieldSet)); }
public ResourceFieldChainExpression(ResourceFieldAttribute field) { ArgumentGuard.NotNull(field, nameof(field)); Fields = field.AsArray(); }
protected override void AfterProcessField(IIdentifiable resource, ResourceFieldAttribute field, RelationshipEntry data = null) { }
private static SparseFieldSetExpression ExcludeField(SparseFieldSetExpression sparseFieldSet, ResourceFieldAttribute fieldToExclude) { // Design tradeoff: When the sparse fieldset is empty, it means all fields will be selected. // Adding an exclusion in that case is a no-op, which results in still retrieving the excluded field from data store. // But later, when serializing the response, the sparse fieldset is first populated with all fields, // so then the exclusion will actually be applied and the excluded field is not returned to the client. if (sparseFieldSet == null || !sparseFieldSet.Fields.Contains(fieldToExclude)) { return(sparseFieldSet); } HashSet <ResourceFieldAttribute> fieldSet = sparseFieldSet.Fields.ToHashSet(); fieldSet.Remove(fieldToExclude); return(new SparseFieldSetExpression(fieldSet)); }