Skip to content

Commit

Permalink
Merge pull request #422 from json-api-dotnet/fix/breaking-change-in-attr
Browse files Browse the repository at this point in the history
undo breaking change for now
  • Loading branch information
jaredcnance authored Oct 4, 2018
2 parents 5049281 + 75ed2de commit 7efb3c5
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 16 deletions.
5 changes: 3 additions & 2 deletions src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,9 @@ private string GetResourceNameFromDbSetProperty(PropertyInfo property, Type reso
if (property.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute resourceAttribute)
return resourceAttribute.ResourceName;

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

private (bool isJsonApiResource, Type idType) GetIdType(Type resourceType)
Expand Down
3 changes: 3 additions & 0 deletions src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ private List<ResourceObject> IncludeRelationshipChain(
{
var requestedRelationship = relationshipChain[relationshipChainIndex];
var relationship = parentEntity.Relationships.FirstOrDefault(r => r.PublicRelationshipName == requestedRelationship);
if(relationship == null)
throw new JsonApiException(400, $"{parentEntity.EntityName} does not contain relationship {requestedRelationship}");

var navigationEntity = _jsonApiContext.ContextGraph.GetRelationship(parentResource, relationship.InternalRelationshipName);
if (navigationEntity is IEnumerable hasManyNavigationEntity)
{
Expand Down
6 changes: 1 addition & 5 deletions src/JsonApiDotNetCore/Formatters/JsonApiReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,12 @@ public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)

return InputFormatterResult.SuccessAsync(model);
}
catch (JsonSerializationException ex)
catch (Exception ex)
{
_logger?.LogError(new EventId(), ex, "An error occurred while de-serializing the payload");
context.ModelState.AddModelError(context.ModelName, ex, context.Metadata);
return InputFormatterResult.FailureAsync();
}
catch (JsonApiException)
{
throw;
}
}

private string GetRequestBody(Stream body)
Expand Down
24 changes: 23 additions & 1 deletion src/JsonApiDotNetCore/Graph/IResourceNameFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ public interface IResourceNameFormatter
/// Get the publicly visible name for the given property
/// </summary>
string FormatPropertyName(PropertyInfo property);

/// <summary>
/// Aoplies the desired casing convention to the internal string.
/// This is generally applied to the type name after pluralization.
/// </summary>
string ApplyCasingConvention(string properName);
}

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

return str.Dasherize(type.Name.Pluralize());
return ApplyCasingConvention(type.Name.Pluralize());
}
catch (InvalidOperationException e)
{
throw new InvalidOperationException($"Cannot define multiple {nameof(ResourceAttribute)}s on type '{type}'.", e);
}
}

/// <summary>
/// Aoplies the desired casing convention to the internal string.
/// This is generally applied to the type name after pluralization.
/// </summary>
///
/// <example>
/// <code>
/// _default.ApplyCasingConvention("TodoItems");
/// // > "todo-items"
///
/// _default.ApplyCasingConvention("TodoItem");
/// // > "todo-item"
/// </code>
/// </example>
public string ApplyCasingConvention(string properName) => str.Dasherize(properName);

/// <summary>
/// Uses the internal PropertyInfo to determine the external resource name.
/// By default the name will be formatted to kebab-case.
Expand Down
10 changes: 9 additions & 1 deletion src/JsonApiDotNetCore/Models/AttrAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ public AttrAttribute(string publicName = null, bool isImmutable = false, bool is
IsSortable = isSortable;
}

internal AttrAttribute(string publicName, string internalName, bool isImmutable = false)
/// <summary>
/// Do not use this overload in your applications.
/// Provides a method for instantiating instances of `AttrAttribute` and specifying
/// the internal property name.
/// The primary intent for this was to enable certain types of unit tests to be possible.
/// This overload will be deprecated and removed in future releases and an alternative
/// for unit tests will be provided.
/// </summary>
public AttrAttribute(string publicName, string internalName, bool isImmutable = false)
{
PublicAttributeName = publicName;
InternalAttributeName = internalName;
Expand Down
10 changes: 5 additions & 5 deletions test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Xunit;
using Person = JsonApiDotNetCoreExample.Models.Person;

namespace JsonApiDotNetCoreExampleTests.Acceptance
{
Expand All @@ -21,6 +20,7 @@ public class ManyToManyTests
private static readonly Faker<Article> _articleFaker = new Faker<Article>()
.RuleFor(a => a.Name, f => f.Random.AlphaNumeric(10))
.RuleFor(a => a.Author, f => new Author());

private static readonly Faker<Tag> _tagFaker = new Faker<Tag>().RuleFor(a => a.Name, f => f.Random.AlphaNumeric(10));

private TestFixture<TestStartup> _fixture;
Expand Down Expand Up @@ -66,9 +66,9 @@ public async Task Can_Create_Many_To_Many()
// arrange
var context = _fixture.GetService<AppDbContext>();
var tag = _tagFaker.Generate();
var author = new Person();
var author = new Author();
context.Tags.Add(tag);
context.People.Add(author);
context.Authors.Add(author);
await context.SaveChangesAsync();

var article = _articleFaker.Generate();
Expand All @@ -85,7 +85,7 @@ public async Task Can_Create_Many_To_Many()
{ "author", new {
data = new
{
type = "people",
type = "authors",
id = author.StringId
}
} },
Expand All @@ -111,7 +111,7 @@ public async Task Can_Create_Many_To_Many()
// assert
var body = await response.Content.ReadAsStringAsync();
Assert.True(HttpStatusCode.Created == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");

var articleResponse = _fixture.GetService<IJsonApiDeSerializer>().Deserialize<Article>(body);
Assert.NotNull(articleResponse);

Expand Down
2 changes: 2 additions & 0 deletions test/UnitTests/Builders/ContextGraphBuilder_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ public class RelatedResource : Identifiable { }

public class CamelCaseNameFormatter : IResourceNameFormatter
{
public string ApplyCasingConvention(string properName) => ToCamelCase(properName);

public string FormatPropertyName(PropertyInfo property) => ToCamelCase(property.Name);

public string FormatResourceName(Type resourceType) => ToCamelCase(resourceType.Name.Pluralize());
Expand Down
28 changes: 26 additions & 2 deletions test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,26 @@ public void AddResourceService_Throws_If_Type_Does_Not_Implement_Any_Interfaces(
Assert.Throws<JsonApiSetupException>(() => services.AddResourceService<int>());
}

private class IntResource : Identifiable { }
private class GuidResource : Identifiable<Guid> { }
[Fact]
public void AddJsonApi_With_Context_Uses_DbSet_PropertyName_If_NoOtherSpecified()
{
// arrange
var services = new ServiceCollection();

services.AddScoped<IScopedServiceProvider, TestScopedServiceProvider>();

// act
services.AddJsonApi<TestContext>();

// assert
var provider = services.BuildServiceProvider();
var graph = provider.GetService<IContextGraph>();
var resource = graph.GetContextEntity(typeof(IntResource));
Assert.Equal("resource", resource.EntityName);
}

public class IntResource : Identifiable { }
public class GuidResource : Identifiable<Guid> { }

private class IntResourceService : IResourceService<IntResource>
{
Expand All @@ -138,5 +156,11 @@ private class GuidResourceService : IResourceService<GuidResource, Guid>
public Task<GuidResource> UpdateAsync(Guid id, GuidResource entity) => throw new NotImplementedException();
public Task UpdateRelationshipsAsync(Guid id, string relationshipName, List<ResourceObject> relationships) => throw new NotImplementedException();
}


public class TestContext : DbContext
{
public DbSet<IntResource> Resource { get; set; }
}
}
}

0 comments on commit 7efb3c5

Please sign in to comment.