Skip to content

Query tweaks #1257

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 14 commits into from
Mar 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
]
},
"dotnet-reportgenerator-globaltool": {
"version": "5.1.15",
"version": "5.1.19",
"commands": [
"reportgenerator"
]
},
"docfx": {
"version": "2.60.2",
"version": "2.62.2",
"commands": [
"docfx"
]
Expand Down
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<AspNetVersion>6.0.*</AspNetVersion>
<EFCoreVersion>7.0.*</EFCoreVersion>
<EFCorePostgresVersion>7.0.*</EFCorePostgresVersion>
<MicrosoftCodeAnalysisVersion>4.4.*</MicrosoftCodeAnalysisVersion>
<MicrosoftCodeAnalysisVersion>4.5.*</MicrosoftCodeAnalysisVersion>
<HumanizerVersion>2.14.1</HumanizerVersion>
<JsonApiDotNetCoreVersionPrefix>5.1.3</JsonApiDotNetCoreVersionPrefix>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodingGuidelines.ruleset</CodeAnalysisRuleSet>
Expand All @@ -17,7 +17,7 @@

<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" PrivateAssets="All" />
<PackageReference Include="CSharpGuidelinesAnalyzer" Version="3.8.2" PrivateAssets="All" />
<PackageReference Include="CSharpGuidelinesAnalyzer" Version="3.8.3" PrivateAssets="All" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CSharpGuidelinesAnalyzer.config" Visible="False" />
</ItemGroup>

Expand Down
6 changes: 4 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
image:
- Ubuntu2004
- Visual Studio 2022
# Downgrade to workaround error NETSDK1194 during 'dotnet pack': The "--output" option isn't supported when building a solution.
# https://stackoverflow.com/questions/75453953/how-to-fix-github-actions-dotnet-publish-workflow-error-the-output-option-i
- Previous Visual Studio 2022

version: '{build}'

Expand Down Expand Up @@ -32,7 +34,7 @@ for:
-
matrix:
only:
- image: Visual Studio 2022
- image: Previous Visual Studio 2022
services:
- postgresql15
install:
Expand Down
4 changes: 2 additions & 2 deletions docs/usage/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ options.AllowClientGeneratedIds = true;

## Pagination

The default page size used for all resources can be overridden in options (10 by default). To disable paging, set it to `null`.
The default page size used for all resources can be overridden in options (10 by default). To disable pagination, set it to `null`.
The maximum page size and number allowed from client requests can be set too (unconstrained by default).

You can also include the total number of resources in each response.
Expand All @@ -38,7 +38,7 @@ options.IncludeTotalResourceCount = true;
```

To retrieve the total number of resources on secondary and relationship endpoints, the reverse of the relationship must to be available. For example, in `GET /customers/1/orders`, both the relationships `[HasMany] Customer.Orders` and `[HasOne] Order.Customer` must be defined.
If `IncludeTotalResourceCount` is set to `false` (or the inverse relationship is unavailable on a non-primary endpoint), best-effort paging links are returned instead. This means no `last` link and the `next` link only occurs when the current page is full.
If `IncludeTotalResourceCount` is set to `false` (or the inverse relationship is unavailable on a non-primary endpoint), best-effort pagination links are returned instead. This means no `last` link and the `next` link only occurs when the current page is full.

## Relative Links

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ public enum LinkTypes
{
Self = 1 << 0,
Related = 1 << 1,
Paging = 1 << 2,
Pagination = 1 << 2,
NotConfigured = 1 << 3,
None = 1 << 4,
All = Self | Related | Paging
All = Self | Related | Pagination
}
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public interface IJsonApiOptions
bool IncludeTotalResourceCount { get; }

/// <summary>
/// The page size (10 by default) that is used when not specified in query string. Set to <c>null</c> to not use paging by default.
/// The page size (10 by default) that is used when not specified in query string. Set to <c>null</c> to not use pagination by default.
/// </summary>
PageSize? DefaultPageSize { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ internal sealed class ResourceDescriptorAssemblyCache

public void RegisterAssembly(Assembly assembly)
{
if (!_resourceDescriptorsPerAssembly.ContainsKey(assembly))
{
_resourceDescriptorsPerAssembly[assembly] = null;
}
_resourceDescriptorsPerAssembly.TryAdd(assembly, null);
}

public IReadOnlyCollection<ResourceDescriptor> GetResourceDescriptors()
Expand Down
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public virtual async Task<IActionResult> PostAsync([FromBody] TResource resource
TResource? newResource = await _create.CreateAsync(resource, cancellationToken);

string resourceId = (newResource ?? resource).StringId!;
string locationUrl = $"{HttpContext.Request.Path}/{resourceId}";
string locationUrl = HttpContext.Request.Path.Add($"/{resourceId}");

if (newResource == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ namespace JsonApiDotNetCore.Errors;
[PublicAPI]
public sealed class CannotClearRequiredRelationshipException : JsonApiException
{
public CannotClearRequiredRelationshipException(string relationshipName, string resourceId, string resourceType)
public CannotClearRequiredRelationshipException(string relationshipName, string resourceType)
: base(new ErrorObject(HttpStatusCode.BadRequest)
{
Title = "Failed to clear a required relationship.",
Detail = $"The relationship '{relationshipName}' on resource type '{resourceType}' " +
$"with ID '{resourceId}' cannot be cleared because it is a required relationship."
Detail = $"The relationship '{relationshipName}' on resource type '{resourceType}' cannot be cleared because it is a required relationship."
})
{
}
Expand Down
8 changes: 4 additions & 4 deletions src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ public void Apply(ApplicationModel application)
$"resource type '{resourceClrType}', which does not exist in the resource graph.");
}

if (_controllerPerResourceTypeMap.ContainsKey(resourceType))
if (_controllerPerResourceTypeMap.TryGetValue(resourceType, out ControllerModel? existingModel))
{
throw new InvalidConfigurationException(
$"Multiple controllers found for resource type '{resourceType}': '{_controllerPerResourceTypeMap[resourceType].ControllerType}' and '{controller.ControllerType}'.");
$"Multiple controllers found for resource type '{resourceType}': '{existingModel.ControllerType}' and '{controller.ControllerType}'.");
}

_resourceTypePerControllerTypeMap.Add(controller.ControllerType, resourceType);
Expand All @@ -119,10 +119,10 @@ public void Apply(ApplicationModel application)

string template = TemplateFromResource(controller) ?? TemplateFromController(controller);

if (_registeredControllerNameByTemplate.ContainsKey(template))
if (_registeredControllerNameByTemplate.TryGetValue(template, out string? controllerName))
{
throw new InvalidConfigurationException(
$"Cannot register '{controller.ControllerType.FullName}' for template '{template}' because '{_registeredControllerNameByTemplate[template]}' was already registered for this template.");
$"Cannot register '{controller.ControllerType.FullName}' for template '{template}' because '{controllerName}' was already registered for this template.");
}

_registeredControllerNameByTemplate.Add(template, controller.ControllerType.FullName!);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@ namespace JsonApiDotNetCore.Queries.Expressions;
[PublicAPI]
public class LiteralConstantExpression : IdentifierExpression
{
public string Value { get; }
private readonly string _stringValue;

public LiteralConstantExpression(string text)
public object TypedValue { get; }

public LiteralConstantExpression(object typedValue)
: this(typedValue, typedValue.ToString()!)
{
}

public LiteralConstantExpression(object typedValue, string stringValue)
{
ArgumentGuard.NotNull(text);
ArgumentGuard.NotNull(typedValue);
ArgumentGuard.NotNull(stringValue);

Value = text;
TypedValue = typedValue;
_stringValue = stringValue;
}

public override TResult Accept<TArgument, TResult>(QueryExpressionVisitor<TArgument, TResult> visitor, TArgument argument)
Expand All @@ -24,8 +33,8 @@ public override TResult Accept<TArgument, TResult>(QueryExpressionVisitor<TArgum

public override string ToString()
{
string value = Value.Replace("\'", "\'\'");
return $"'{value}'";
string escapedValue = _stringValue.Replace("\'", "\'\'");
return $"'{escapedValue}'";
}

public override string ToFullString()
Expand All @@ -47,11 +56,11 @@ public override bool Equals(object? obj)

var other = (LiteralConstantExpression)obj;

return Value == other.Value;
return Equals(TypedValue, other.TypedValue) && _stringValue == other._stringValue;
}

public override int GetHashCode()
{
return Value.GetHashCode();
return HashCode.Combine(TypedValue, _stringValue);
}
}
13 changes: 7 additions & 6 deletions src/JsonApiDotNetCore/Queries/IPaginationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
namespace JsonApiDotNetCore.Queries;

/// <summary>
/// Tracks values used for pagination, which is a combined effort from options, query string parsing and fetching the total number of rows.
/// Tracks values used for top-level pagination, which is a combined effort from options, query string parsing, resource definition callbacks and
/// fetching the total number of rows.
/// </summary>
public interface IPaginationContext
{
/// <summary>
/// The value 1, unless specified from query string. Never null. Cannot be higher than options.MaximumPageNumber.
/// The value 1, unless overridden from query string or resource definition. Should not be higher than <see cref="IJsonApiOptions.MaximumPageNumber" />.
/// </summary>
PageNumber PageNumber { get; set; }

/// <summary>
/// The default page size from options, unless specified in query string. Can be <c>null</c>, which means no paging. Cannot be higher than
/// options.MaximumPageSize.
/// The default page size from options, unless overridden from query string or resource definition. Should not be higher than
/// <see cref="IJsonApiOptions.MaximumPageSize" />. Can be <c>null</c>, which means pagination is disabled.
/// </summary>
PageSize? PageSize { get; set; }

Expand All @@ -25,12 +26,12 @@ public interface IPaginationContext
bool IsPageFull { get; set; }

/// <summary>
/// The total number of resources. <c>null</c> when <see cref="IJsonApiOptions.IncludeTotalResourceCount" /> is set to <c>false</c>.
/// The total number of resources, or <c>null</c> when <see cref="IJsonApiOptions.IncludeTotalResourceCount" /> is set to <c>false</c>.
/// </summary>
int? TotalResourceCount { get; set; }

/// <summary>
/// The total number of resource pages. <c>null</c> when <see cref="IJsonApiOptions.IncludeTotalResourceCount" /> is set to <c>false</c> or
/// The total number of resource pages, or <c>null</c> when <see cref="IJsonApiOptions.IncludeTotalResourceCount" /> is set to <c>false</c> or
/// <see cref="PageSize" /> is <c>null</c>.
/// </summary>
int? TotalPageCount { get; }
Expand Down
Loading