-
-
Notifications
You must be signed in to change notification settings - Fork 163
Expand file tree
/
Copy pathResourceFieldAttribute.cs
More file actions
160 lines (139 loc) · 4.88 KB
/
ResourceFieldAttribute.cs
File metadata and controls
160 lines (139 loc) · 4.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
using System.Reflection;
using JetBrains.Annotations;
using JsonApiDotNetCore.Configuration;
// ReSharper disable NonReadonlyMemberInGetHashCode
namespace JsonApiDotNetCore.Resources.Annotations;
/// <summary>
/// Used to expose a property on a resource class as a JSON:API field (attribute or relationship). See
/// https://jsonapi.org/format/#document-resource-object-fields.
/// </summary>
[PublicAPI]
public abstract class ResourceFieldAttribute : Attribute
{
// These are definitely assigned after building the resource graph, which is why their public equivalents are declared as non-nullable.
private string? _publicName;
private PropertyInfo? _property;
private ResourceType? _type;
/// <summary>
/// The publicly exposed name of this JSON:API field. When not explicitly set, the configured naming convention is applied on the property name.
/// </summary>
public string PublicName
{
get => _publicName!;
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("Exposed name cannot be null, empty or contain only whitespace.", nameof(value));
}
_publicName = value;
}
}
/// <summary>
/// The resource property that this attribute is declared on.
/// </summary>
public PropertyInfo Property
{
get => _property!;
internal set
{
ArgumentNullException.ThrowIfNull(value);
_property = value;
}
}
/// <summary>
/// The containing resource type in which this field is declared.
/// </summary>
public ResourceType Type
{
get => _type!;
internal set
{
ArgumentNullException.ThrowIfNull(value);
_type = value;
}
}
/// <summary>
/// Gets the value of this field on the specified resource instance. Throws if the property is write-only or if the field does not belong to the
/// specified resource instance.
/// </summary>
public object? GetValue(object resource)
{
ArgumentNullException.ThrowIfNull(resource);
AssertIsIdentifiable(resource);
if (Property.GetMethod == null)
{
throw new InvalidOperationException($"Property '{Property.DeclaringType?.Name}.{Property.Name}' is write-only.");
}
try
{
return Property.GetValue(resource);
}
catch (TargetException exception)
{
throw new InvalidOperationException(
$"Unable to get property value of '{Property.DeclaringType!.Name}.{Property.Name}' on instance of type '{resource.GetType().Name}'.",
exception.InnerException ?? exception);
}
}
/// <summary>
/// Sets the value of this field on the specified resource instance. Throws if the property is read-only or if the field does not belong to the specified
/// resource instance.
/// </summary>
public virtual void SetValue(object resource, object? newValue)
{
ArgumentNullException.ThrowIfNull(resource);
AssertIsIdentifiable(resource);
if (Property.SetMethod == null)
{
throw new InvalidOperationException($"Property '{Property.DeclaringType?.Name}.{Property.Name}' is read-only.");
}
try
{
Property.SetValue(resource, newValue);
}
catch (TargetException exception)
{
throw new InvalidOperationException(
$"Unable to set property value of '{Property.DeclaringType!.Name}.{Property.Name}' on instance of type '{resource.GetType().Name}'.",
exception.InnerException ?? exception);
}
}
protected void AssertIsIdentifiable(object? resource)
{
if (resource is not null and not IIdentifiable)
{
#pragma warning disable CA1062 // Validate arguments of public methods
throw new InvalidOperationException($"Resource of type '{resource.GetType()}' does not implement {nameof(IIdentifiable)}.");
#pragma warning restore CA1062 // Validate arguments of public methods
}
}
/// <inheritdoc />
public override string? ToString()
{
return _publicName ?? (_property != null ? _property.Name : base.ToString());
}
public string ToFullString()
{
return $"{_type?.PublicName}:{ToString()}";
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj is null || GetType() != obj.GetType())
{
return false;
}
var other = (ResourceFieldAttribute)obj;
return _publicName == other._publicName && _property == other._property;
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(_publicName, _property);
}
}