Skip to content

Commit 75ed2de

Browse files
committed
various fixes
1 parent 2639702 commit 75ed2de

File tree

6 files changed

+58
-10
lines changed

6 files changed

+58
-10
lines changed

src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,9 @@ private string GetResourceNameFromDbSetProperty(PropertyInfo property, Type reso
267267
if (property.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute resourceAttribute)
268268
return resourceAttribute.ResourceName;
269269

270-
// fallback to dsherized...this should actually check for a custom IResourceNameFormatter
271-
return _resourceNameFormatter.FormatResourceName(resourceType);
270+
// fallback to the established convention using the DbSet Property.Name
271+
// e.g DbSet<FooBar> FooBars { get; set; } => "foo-bars"
272+
return _resourceNameFormatter.ApplyCasingConvention(property.Name);
272273
}
273274

274275
private (bool isJsonApiResource, Type idType) GetIdType(Type resourceType)

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,9 @@ private List<ResourceObject> IncludeRelationshipChain(
214214
{
215215
var requestedRelationship = relationshipChain[relationshipChainIndex];
216216
var relationship = parentEntity.Relationships.FirstOrDefault(r => r.PublicRelationshipName == requestedRelationship);
217+
if(relationship == null)
218+
throw new JsonApiException(400, $"{parentEntity.EntityName} does not contain relationship {requestedRelationship}");
219+
217220
var navigationEntity = _jsonApiContext.ContextGraph.GetRelationship(parentResource, relationship.InternalRelationshipName);
218221
if (navigationEntity is IEnumerable hasManyNavigationEntity)
219222
{

src/JsonApiDotNetCore/Formatters/JsonApiReader.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,12 @@ public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
4646

4747
return InputFormatterResult.SuccessAsync(model);
4848
}
49-
catch (JsonSerializationException ex)
49+
catch (Exception ex)
5050
{
5151
_logger?.LogError(new EventId(), ex, "An error occurred while de-serializing the payload");
5252
context.ModelState.AddModelError(context.ModelName, ex, context.Metadata);
5353
return InputFormatterResult.FailureAsync();
5454
}
55-
catch (JsonApiException)
56-
{
57-
throw;
58-
}
5955
}
6056

6157
private string GetRequestBody(Stream body)

src/JsonApiDotNetCore/Graph/IResourceNameFormatter.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ public interface IResourceNameFormatter
2222
/// Get the publicly visible name for the given property
2323
/// </summary>
2424
string FormatPropertyName(PropertyInfo property);
25+
26+
/// <summary>
27+
/// Aoplies the desired casing convention to the internal string.
28+
/// This is generally applied to the type name after pluralization.
29+
/// </summary>
30+
string ApplyCasingConvention(string properName);
2531
}
2632

2733
public class DefaultResourceNameFormatter : IResourceNameFormatter
@@ -45,14 +51,30 @@ public string FormatResourceName(Type type)
4551
if (type.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute attribute)
4652
return attribute.ResourceName;
4753

48-
return str.Dasherize(type.Name.Pluralize());
54+
return ApplyCasingConvention(type.Name.Pluralize());
4955
}
5056
catch (InvalidOperationException e)
5157
{
5258
throw new InvalidOperationException($"Cannot define multiple {nameof(ResourceAttribute)}s on type '{type}'.", e);
5359
}
5460
}
5561

62+
/// <summary>
63+
/// Aoplies the desired casing convention to the internal string.
64+
/// This is generally applied to the type name after pluralization.
65+
/// </summary>
66+
///
67+
/// <example>
68+
/// <code>
69+
/// _default.ApplyCasingConvention("TodoItems");
70+
/// // > "todo-items"
71+
///
72+
/// _default.ApplyCasingConvention("TodoItem");
73+
/// // > "todo-item"
74+
/// </code>
75+
/// </example>
76+
public string ApplyCasingConvention(string properName) => str.Dasherize(properName);
77+
5678
/// <summary>
5779
/// Uses the internal PropertyInfo to determine the external resource name.
5880
/// By default the name will be formatted to kebab-case.

test/UnitTests/Builders/ContextGraphBuilder_Tests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ public class RelatedResource : Identifiable { }
139139

140140
public class CamelCaseNameFormatter : IResourceNameFormatter
141141
{
142+
public string ApplyCasingConvention(string properName) => ToCamelCase(properName);
143+
142144
public string FormatPropertyName(PropertyInfo property) => ToCamelCase(property.Name);
143145

144146
public string FormatResourceName(Type resourceType) => ToCamelCase(resourceType.Name.Pluralize());

test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,26 @@ public void AddResourceService_Throws_If_Type_Does_Not_Implement_Any_Interfaces(
112112
Assert.Throws<JsonApiSetupException>(() => services.AddResourceService<int>());
113113
}
114114

115-
private class IntResource : Identifiable { }
116-
private class GuidResource : Identifiable<Guid> { }
115+
[Fact]
116+
public void AddJsonApi_With_Context_Uses_DbSet_PropertyName_If_NoOtherSpecified()
117+
{
118+
// arrange
119+
var services = new ServiceCollection();
120+
121+
services.AddScoped<IScopedServiceProvider, TestScopedServiceProvider>();
122+
123+
// act
124+
services.AddJsonApi<TestContext>();
125+
126+
// assert
127+
var provider = services.BuildServiceProvider();
128+
var graph = provider.GetService<IContextGraph>();
129+
var resource = graph.GetContextEntity(typeof(IntResource));
130+
Assert.Equal("resource", resource.EntityName);
131+
}
132+
133+
public class IntResource : Identifiable { }
134+
public class GuidResource : Identifiable<Guid> { }
117135

118136
private class IntResourceService : IResourceService<IntResource>
119137
{
@@ -138,5 +156,11 @@ private class GuidResourceService : IResourceService<GuidResource, Guid>
138156
public Task<GuidResource> UpdateAsync(Guid id, GuidResource entity) => throw new NotImplementedException();
139157
public Task UpdateRelationshipsAsync(Guid id, string relationshipName, List<ResourceObject> relationships) => throw new NotImplementedException();
140158
}
159+
160+
161+
public class TestContext : DbContext
162+
{
163+
public DbSet<IntResource> Resource { get; set; }
164+
}
141165
}
142166
}

0 commit comments

Comments
 (0)