Skip to content

Add example projects and tests #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
99e7d50
Add example projects and tests
mrnkr Jan 1, 2021
c53096a
throw proper error when fetching relationships
mrnkr Jan 2, 2021
b42b381
relationship creation tests
mrnkr Jan 2, 2021
c815c29
fix: unsupported relationships message
mrnkr Jan 2, 2021
c3f9dcf
test to-many relationship creation
mrnkr Jan 2, 2021
f348ff9
test unsupported filters
mrnkr Jan 2, 2021
1b49d01
test for readonly attributes
mrnkr Jan 2, 2021
fc94301
suggested changes to validation and IModel implementation
mrnkr Jan 3, 2021
ab7fed5
cleanup last commit
mrnkr Jan 3, 2021
970692b
remove unnecessary MongoDBRefs
mrnkr Jan 3, 2021
3ec805d
added update relationship tests
mrnkr Jan 3, 2021
0a3604e
address review comments
mrnkr Jan 4, 2021
ca2588d
update README.md
mrnkr Jan 4, 2021
1bcef1f
address review comments
mrnkr Jan 6, 2021
e384b4e
bring latest changes in IntegrationTestContext
mrnkr Jan 6, 2021
aa140cb
Fixed handling DateTimes with ambiguous kind.
Jan 6, 2021
310b972
Added test for resource meta
Jan 6, 2021
f3e63b2
Cleanup solution: Move GettingStarted to the root and make it the def…
Jan 6, 2021
09456b0
Simplified code
Jan 6, 2021
465d720
fix README.md
mrnkr Jan 6, 2021
054501b
remove unnecessary test for sparse fieldsets
mrnkr Jan 6, 2021
2446ae4
remove unnecessary tests from CreateResourceTests
mrnkr Jan 6, 2021
a7b8713
address review comments
mrnkr Jan 6, 2021
c97d773
fix: custom query parameter support
mrnkr Jan 7, 2021
78f058c
fix: sort on HasMany test passing
mrnkr Jan 7, 2021
72430bc
added tests for relationship support in SparseFieldSetTests
mrnkr Jan 7, 2021
99192c1
remove unnecessary ReadWrite tests
mrnkr Jan 7, 2021
afb71a5
fix: sort ids descending test
mrnkr Jan 7, 2021
a3742bd
cleanup sparse fieldset tests
mrnkr Jan 7, 2021
5341ba9
move MongoDatabaseExtensions to test project
mrnkr Jan 8, 2021
27f72f7
fix: sparse fieldset test
mrnkr Jan 8, 2021
e4b94bd
cleanup tests
mrnkr Jan 8, 2021
aae4eeb
latest requested changes minus ReadWrite tests
mrnkr Jan 9, 2021
0f3272b
add missing ReadWrite tests
mrnkr Jan 9, 2021
a9e1bc8
Fixed failing tests
Jan 10, 2021
4e5bf56
address review comments
mrnkr Jan 10, 2021
33add8b
Fixed: do not return relationship links in response json
Jan 11, 2021
2a73164
Fixed: the Task returned from Collection.InsertOneAsync was not await…
Jan 11, 2021
11646fc
address review comments
mrnkr Jan 11, 2021
461eb32
Cleanup IoC registrations to properly detect when non-string IDs are …
Jan 12, 2021
4e50bc3
Removed unused types
Jan 12, 2021
d7c7520
Updated example
Jan 12, 2021
3451619
rename public static class ServiceCollectionExtensions.cs to ServiceC…
mrnkr Jan 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Net;
using JsonApiDotNetCore.Errors;
using JsonApiDotNetCore.Serialization.Objects;

namespace JsonApiDotNetCore.MongoDb.Errors
{
public sealed class UnsupportedComparisonExpressionException : JsonApiException
{
public UnsupportedComparisonExpressionException()
: base(new Error(HttpStatusCode.BadRequest)
{
Title = "Comparing attributes against each other is not supported when using MongoDB."
})
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Net;
using JsonApiDotNetCore.Errors;
using JsonApiDotNetCore.Serialization.Objects;

namespace JsonApiDotNetCore.MongoDb.Errors
{
public sealed class UnsupportedRelationshipException : JsonApiException
{
public UnsupportedRelationshipException()
: base(new Error(HttpStatusCode.BadRequest)
{
Title = "Relationships are not supported when using MongoDB."
})
{
}
}
}
32 changes: 32 additions & 0 deletions src/JsonApiDotNetCore.MongoDb/Internal/MongoDbModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JsonApiDotNetCore.Configuration;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;

namespace JsonApiDotNetCore.MongoDb.Internal
{
internal sealed class MongoDbModel : IModel
{
private readonly IResourceContextProvider _resourceContextProvider;

public object this[string name] => throw new NotImplementedException();

public MongoDbModel(IResourceContextProvider resourceContextProvider)
{
_resourceContextProvider = resourceContextProvider ?? throw new ArgumentNullException(nameof(resourceContextProvider));
}

public IEnumerable<IEntityType> GetEntityTypes()
{
var resourceContexts = _resourceContextProvider.GetResourceContexts();
return resourceContexts.Select(resourceContext => new MongoEntityType(resourceContext, this));
}

public IAnnotation FindAnnotation(string name) => throw new NotImplementedException();
public IEnumerable<IAnnotation> GetAnnotations() => throw new NotImplementedException();
public IEntityType FindEntityType(string name) => throw new NotImplementedException();
public IEntityType FindEntityType(string name, string definingNavigationName, IEntityType definingEntityType) => throw new NotImplementedException();
}
}
32 changes: 32 additions & 0 deletions src/JsonApiDotNetCore.MongoDb/Internal/MongoDbProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;

namespace JsonApiDotNetCore.MongoDb.Internal
{
internal sealed class MongoDbProperty : IProperty
{
public IEntityType DeclaringEntityType { get; }
public PropertyInfo PropertyInfo { get; }

public string Name => throw new NotImplementedException();
public Type ClrType => throw new NotImplementedException();
public FieldInfo FieldInfo => throw new NotImplementedException();
public ITypeBase DeclaringType => throw new NotImplementedException();
public bool IsNullable => throw new NotImplementedException();
public ValueGenerated ValueGenerated => throw new NotImplementedException();
public bool IsConcurrencyToken => throw new NotImplementedException();
public object this[string name] => throw new NotImplementedException();

public MongoDbProperty(PropertyInfo propertyInfo, MongoEntityType owner)
{
DeclaringEntityType = owner ?? throw new ArgumentNullException(nameof(owner));
PropertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo));
}

public IAnnotation FindAnnotation(string name) => throw new NotImplementedException();
public IEnumerable<IAnnotation> GetAnnotations() => throw new NotImplementedException();
}
}
47 changes: 47 additions & 0 deletions src/JsonApiDotNetCore.MongoDb/Internal/MongoEntityType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JsonApiDotNetCore.Configuration;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;

namespace JsonApiDotNetCore.MongoDb.Internal
{
internal sealed class MongoEntityType : IEntityType
{
private readonly ResourceContext _resourceContext;

public IModel Model { get; }
public Type ClrType => _resourceContext.ResourceType;

public string Name => throw new NotImplementedException();
public IEntityType BaseType => throw new NotImplementedException();
public string DefiningNavigationName => throw new NotImplementedException();
public IEntityType DefiningEntityType => throw new NotImplementedException();
public object this[string name] => throw new NotImplementedException();

public MongoEntityType(ResourceContext resourceContext, MongoDbModel owner)
{
_resourceContext = resourceContext ?? throw new ArgumentNullException(nameof(resourceContext));
Model = owner ?? throw new ArgumentNullException(nameof(owner));
}

public IEnumerable<IProperty> GetProperties()
{
return _resourceContext.Attributes.Select(attr => new MongoDbProperty(attr.Property, this));
}

public IAnnotation FindAnnotation(string name) => throw new NotImplementedException();
public IEnumerable<IAnnotation> GetAnnotations() => throw new NotImplementedException();
public IKey FindPrimaryKey() => throw new NotImplementedException();
public IKey FindKey(IReadOnlyList<IProperty> properties) => throw new NotImplementedException();
public IEnumerable<IKey> GetKeys() => throw new NotImplementedException();
public IForeignKey FindForeignKey(IReadOnlyList<IProperty> properties, IKey principalKey, IEntityType principalEntityType) => throw new NotImplementedException();
public IEnumerable<IForeignKey> GetForeignKeys() => throw new NotImplementedException();
public IIndex FindIndex(IReadOnlyList<IProperty> properties) => throw new NotImplementedException();
public IEnumerable<IIndex> GetIndexes() => throw new NotImplementedException();
public IProperty FindProperty(string name) => throw new NotImplementedException();
public IServiceProperty FindServiceProperty(string name) => throw new NotImplementedException();
public IEnumerable<IServiceProperty> GetServiceProperties() => throw new NotImplementedException();
}
}
92 changes: 16 additions & 76 deletions src/JsonApiDotNetCore.MongoDb/MongoDbRepository.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Errors;
using JsonApiDotNetCore.MongoDb.Errors;
using JsonApiDotNetCore.MongoDb.Internal;
using JsonApiDotNetCore.MongoDb.Queries.Expressions;
using JsonApiDotNetCore.Queries.Internal.QueryableBuilding;
using JsonApiDotNetCore.Repositories;
using JsonApiDotNetCore.Resources;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using JsonApiDotNetCore.Queries;
using JsonApiDotNetCore.Queries.Expressions;
using JsonApiDotNetCore.Serialization.Objects;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace JsonApiDotNetCore.MongoDb
{
Expand All @@ -30,7 +28,7 @@ public class MongoDbRepository<TResource, TId>
private readonly ITargetedFields _targetedFields;
private readonly IResourceContextProvider _resourceContextProvider;
private readonly IResourceFactory _resourceFactory;

public MongoDbRepository(
IMongoDatabase mongoDatabase,
ITargetedFields targetedFields,
Expand All @@ -50,20 +48,12 @@ public virtual async Task<IReadOnlyCollection<TResource>> GetAsync(QueryLayer la
CancellationToken cancellationToken)
{
if (layer == null) throw new ArgumentNullException(nameof(layer));

var queryExpressionValidator = new MongoDbQueryExpressionValidator();
queryExpressionValidator.Validate(layer);

try
{
var resources = await ApplyQueryLayer(layer).ToListAsync(cancellationToken);
return resources.AsReadOnly();
}
catch (ArgumentException e)
{
throw new JsonApiException(new Error(HttpStatusCode.BadRequest)
{
Title = "MongoDB does not allow comparing two fields to each other, only constants.",
Detail = e.Message
});
}
var resources = await ApplyQueryLayer(layer).ToListAsync(cancellationToken);
return resources.AsReadOnly();
}

/// <inheritdoc />
Expand All @@ -82,7 +72,9 @@ public virtual Task<int> CountAsync(FilterExpression topFilter, CancellationToke
protected virtual IMongoQueryable<TResource> ApplyQueryLayer(QueryLayer layer)
{
if (layer == null) throw new ArgumentNullException(nameof(layer));
AssertNoInclude(layer);

var queryExpressionValidator = new MongoDbQueryExpressionValidator();
queryExpressionValidator.Validate(layer);

var source = GetAll();

Expand All @@ -94,26 +86,11 @@ protected virtual IMongoQueryable<TResource> ApplyQueryLayer(QueryLayer layer)
nameFactory,
_resourceFactory,
_resourceContextProvider,
DummyModel.Instance);
new MongoDbModel(_resourceContextProvider));

var expression = builder.ApplyQuery(layer);
return (IMongoQueryable<TResource>)source.Provider.CreateQuery<TResource>(expression);
}

private void AssertNoInclude(QueryLayer layer)
{
if (layer.Include != null && layer.Include.Elements.Count > 0)
{
throw new JsonApiException(new Error(HttpStatusCode.BadRequest)
{
Title = "Relationships are not supported when using MongoDB.",
Source = new ErrorSource
{
Parameter = "include"
}
});
}
}

protected virtual IQueryable<TResource> GetAll()
{
Expand Down Expand Up @@ -153,10 +130,7 @@ private void AssertNoRelationship(TResource resourceFromRequest)
var rightResources = relationship.GetValue(resourceFromRequest);
if (rightResources != null)
{
throw new JsonApiException(new Error(HttpStatusCode.BadRequest)
{
Title = "Relationships are not supported when using MongoDB."
});
throw new UnsupportedRelationshipException();
}
}
}
Expand All @@ -174,6 +148,8 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r
if (resourceFromRequest == null) throw new ArgumentNullException(nameof(resourceFromRequest));
if (resourceFromDatabase == null) throw new ArgumentNullException(nameof(resourceFromDatabase));

AssertNoRelationship(resourceFromRequest);

foreach (var attr in _targetedFields.Attributes)
attr.SetValue(resourceFromDatabase, attr.GetValue(resourceFromRequest));

Expand Down Expand Up @@ -239,40 +215,4 @@ public MongoDbRepository(
{
}
}

internal sealed class DummyModel : IModel
{
public static IModel Instance { get; } = new DummyModel();

public object this[string name] => throw new NotImplementedException();

private DummyModel()
{
}

public IAnnotation FindAnnotation(string name)
{
throw new NotImplementedException();
}

public IEnumerable<IAnnotation> GetAnnotations()
{
throw new NotImplementedException();
}

public IEnumerable<IEntityType> GetEntityTypes()
{
throw new NotImplementedException();
}

public IEntityType FindEntityType(string name)
{
throw new NotImplementedException();
}

public IEntityType FindEntityType(string name, string definingNavigationName, IEntityType definingEntityType)
{
throw new NotImplementedException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Linq;
using System.Net;
using JsonApiDotNetCore.Errors;
using JsonApiDotNetCore.MongoDb.Errors;
using JsonApiDotNetCore.Queries;
using JsonApiDotNetCore.Queries.Expressions;
using JsonApiDotNetCore.Resources.Annotations;
using JsonApiDotNetCore.Serialization.Objects;

namespace JsonApiDotNetCore.MongoDb.Queries.Expressions
{
internal sealed class MongoDbQueryExpressionValidator : QueryExpressionRewriter<object>
{
public void Validate(QueryLayer layer)
{
if (layer == null) throw new ArgumentNullException(nameof(layer));

bool hasIncludes = layer.Include?.Elements.Any() == true;
var hasSparseRelationshipSets = layer.Projection?.Any(pair => pair.Key is RelationshipAttribute) == true;

if (hasIncludes || hasSparseRelationshipSets)
{
throw new UnsupportedRelationshipException();
}

ValidateExpression(layer.Filter);
ValidateExpression(layer.Sort);
ValidateExpression(layer.Pagination);
}

private void ValidateExpression(QueryExpression expression)
{
if (expression != null)
{
Visit(expression, null);
}
}

public override QueryExpression VisitResourceFieldChain(ResourceFieldChainExpression expression, object argument)
{
if (expression != null)
{
if (expression.Fields.Count > 1 || expression.Fields.First() is RelationshipAttribute)
{
throw new UnsupportedRelationshipException();
}
}

return base.VisitResourceFieldChain(expression, argument);
}

public override QueryExpression VisitComparison(ComparisonExpression expression, object argument)
{
if (expression?.Left is ResourceFieldChainExpression && expression.Right is ResourceFieldChainExpression)
{
// https://jira.mongodb.org/browse/CSHARP-1592
throw new UnsupportedComparisonExpressionException();
}

return base.VisitComparison(expression, argument);
}
}
}
Loading