Skip to content

Commit

Permalink
fix!: #445 incorrect constructor resolution for cloning ListResult ob…
Browse files Browse the repository at this point in the history
…jects. The `IListResult` and `IApiResult` interfaces has been removed and replace with a non-generic `ListResult` abstract base class.
  • Loading branch information
ascott18 committed Sep 17, 2024
1 parent ae326c0 commit 1c01ef1
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 34 deletions.
21 changes: 13 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,36 @@

## Breaking Changes

- Removed support for Knockout.js
- Support for Knockout.js, previously deprecated, has been fully removed.
- Drop all .NET targets except .NET 8

### Breaking: Backend/C#

- Removed automatic `DbContext.SaveChanges()` call from model instance method invocations. Model instance methods that intend to save their changes to the database now must inject a `DbContext` and perform this save explicitly. (#405)
- Removed automatic `DbContext.SaveChanges()` call from model instance method API endpoints. Model instance methods that intend to save any changes to the database now must inject a `DbContext` and perform this save explicitly. (#405)
- ASP.NET `ModelState` validation is now always performed against all inputs to all endpoints.
- `ClaimsPrincipalExtensions` has been removed. Claim names/types can vary significantly depending on the authentication schemes used within a project, so these are best left defined on a per-project basis.
- `ClaimsPrincipalExtensions` has been removed. Claim names/types can vary significantly depending on the authentication schemes used within a project, so these are best left defined on a per-project basis. Replacements are present in the new Coalesce project template.
- `IDataSource.GetItemAsync` and `IDataSource.GetListAsync` no longer return tuples. The IncludeTree return from these methods is now attached to the ItemResult/ListResult.
- `StandardBehaviors.AfterSave` is now `AfterSaveAsync` and has a different signature and semantics. Instead of modifying the resulting `item` and `includeTree` with `ref` parameters, these values can be optionally overridden by returning an ItemResult with its `Object` and `IncludeTree` properties populated with non-null values.
- Generated DTOs now generate separate Parameter DTOs (for inputs) and Response DTOs (for outputs). This results in much cleaner and more accurate OpenAPI definitions.
- `IntelliTect.Coalesce.AuditLogging` no longer utilizes `Z.EntityFramework.Plus` - the internal implementation is now purpose-built for Coalesce's needs. Most of the configuration options that were useful to Coalesce applications have been preserved with the same API.
- Audit logging by default now only merges changes when all changes are to non-discrete properties. For all other changes, new records are inserted every time. A string is an example of a non-discrete property, while a foreign key is an example of a discrete property.
- Audit logging by default now only merges changes when all changes are to non-discrete properties. For all other changes, new records are inserted every time. A plain string is an example of a non-discrete property, while a foreign key is an example of a discrete property.
- `ListResult<T>` constructors have been adjusted to prevent incorrect overload resolution when cloning ListResult objects. The `IListResult` and `IApiResult` interfaces has been removed and replace with a non-generic `ListResult` abstract base class. (#445)

### Breaking: Frontend/Typescript

- Many dependencies bumped to their latest versions, including `vue`, `vue-router`, `vuetify`, `typescript` (5.5+), `date-fns`, and more.
- Many dependency minimum versions increased, including `vue`, `vue-router`, `vuetify`, `typescript` (5.5+), `date-fns`, and more.
- `bindToQueryString`: some parameters were converted to an options object, overloads added for easier binding to a `ref` (#396)
- API callers (e.g. `ViewModel.$save`) now directly return the inner data from their response ListResult/ItemResult instead of returning the outer axios response. The raw response of the previous request is now available on `.rawResponse`.
- The content disposition of file downloads defaults "inline" instead of "attachment". This allows files to be opened for display in a browser tab. Forced downloads can still be achieved by placing the `download` attribute on the HTML `a` tag that links to the endpoint's URL, or by setting `ForceDownload` on the `File` object instantiated in your C# method.
- The content disposition of file downloads defaults to "inline" instead of "attachment". This allows files to be opened for display in a browser tab. Forced downloads can still be achieved by placing the `download` attribute on the HTML `a` tag that links to the endpoint's URL, or by setting `ForceDownload` on the `File` object instantiated in your C# method.
- `FilterParameters.filter` is always initialized to an empty object to make binding easier (no need to manually initialize it and sprinkle your code with null forgiveness operators).

## Deprecations

- Support for Vue 2 is officially deprecated. `coalesce-vue-vuetify2` will receive critical bugfixes, but will no longer receive new features that Vue 3 receives.
- Support for Vue 2 is officially deprecated. `coalesce-vue-vuetify2` will receive critical bugfixes, but will no longer receive new features that Vue 3 receives. If your application is still on Vue 2, you should [migrate ASAP](https://intellitect.github.io/Coalesce/stacks/vue/vue2-to-vue3.html).
- `ControllerActionAttribute` has been merged into `ExecuteAttribute`.
- `LoadFromDataSourceAttribute` has been merged into `ExecuteAttribute`.
- `CreateControllerAttribute` deprecated in favor of using either `[InternalUse]` or `[Create]`/`[Read]`/`[Edit]`/`[Delete]` to preclude the API endpoints of types.
- TypeScript API clients - `withSimultaneousRequestCaching` renamed to `useSimultaneousRequestCaching`.
- TypeScript API clients: `withSimultaneousRequestCaching` renamed to `useSimultaneousRequestCaching`.
- `SecurityPermissionLevels.AllowAuthorized` has been renamed to `SecurityPermissionLevels.AllowAuthenticated` (this enum is used by security attributes).
- `ControllerAttribute` is deprecated.

Expand Down Expand Up @@ -86,6 +87,10 @@
- Vite-plugin: handle nested `public` folder structures
- Remove opening delay when clicking a c-select.

---

Notice: Coalesce versions prior to v5 did not use a typical release cadence. The changes for these versions have been broken down by month, up through April 2023.

# 4.0.0 2024-07

- feat: support collections of primitives as data source parameters
Expand Down
81 changes: 81 additions & 0 deletions src/IntelliTect.Coalesce.Tests/Tests/Models/ListResultTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using IntelliTect.Coalesce.Models;
using IntelliTect.Coalesce.Tests.TargetClasses.TestDbContext;
using IntelliTect.Coalesce.Tests.Util;
using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace IntelliTect.Coalesce.Tests.Tests.Models
{
public class ListResultTests
{
[Fact]
public void Constructor_FromListResult_CopiesPagination()
{
var model = new ComplexModel();
var original = new ListResult<ComplexModel>()
{
Page = 1,
PageSize = 2,
TotalCount = 3,
IncludeTree = new IncludeTree(),
List = [model],
Message = "Test",
WasSuccessful = true,
};

var replica = new ListResult<TestDto<ComplexModel>>(original);

// List can't be copied over because the type is different.
Assert.Null(replica.List);
// Everything else can and should be copied.
Assert.Equal(original.IncludeTree, replica.IncludeTree);
Assert.Equal(original.Page, replica.Page);
Assert.Equal(original.PageSize, replica.PageSize);
Assert.Equal(original.PageCount, replica.PageCount);
Assert.Equal(original.TotalCount, replica.TotalCount);
Assert.Equal(original.WasSuccessful, replica.WasSuccessful);
Assert.Equal(original.Message, replica.Message);
}

[Fact]
public void Constructor_FromApiResult_CopiesProperties()
{
var original = new ApiResult(false, "error")
{
IncludeTree = new IncludeTree(),
};

var replica = new ListResult<TestDto<ComplexModel>>(original);

Assert.Equal(original.IncludeTree, replica.IncludeTree);
Assert.Equal(original.WasSuccessful, replica.WasSuccessful);
Assert.Equal(original.Message, replica.Message);
}

[Fact]
public void Constructor_FromQuery_ComputesPagination()
{
var query = new List<ComplexModel>(){
new(),
new(),
new() { Name = "Foo" },
new(),
new(),
new(),
new(),
new(),
}.AsQueryable();

var result = new ListResult<ComplexModel>(query, 2, 2);

Assert.True(result.WasSuccessful);
Assert.Null(result.Message);
Assert.Equal(2, result.Page);
Assert.Equal(2, result.PageSize);
Assert.Equal(4, result.PageCount);
Assert.Equal(8, result.TotalCount);
Assert.Equal("Foo", result.List[0].Name);
}
}
}
12 changes: 2 additions & 10 deletions src/IntelliTect.Coalesce/Models/ApiResult.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
using System;

namespace IntelliTect.Coalesce.Models
namespace IntelliTect.Coalesce.Models
{
public interface IApiResult
{
bool WasSuccessful { get; }
string? Message { get; }
}

public class ApiResult : IApiResult
public class ApiResult
{
public bool WasSuccessful { get; set; } = true;

Expand Down
45 changes: 29 additions & 16 deletions src/IntelliTect.Coalesce/Models/ListResult.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,54 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace IntelliTect.Coalesce.Models
{
public interface IListResult : IApiResult
{
int Page { get; }
int PageSize { get; }
int PageCount { get; }
int TotalCount { get; }
}

public class ListResult<T> : ApiResult, IListResult
public abstract class ListResult : ApiResult
{
public int Page { get; set; }

public int PageSize { get; set; }
public int PageCount =>
TotalCount == -1 ? -1
: PageSize == 0 ? 0

public int PageCount =>
TotalCount == -1 ? -1
: PageSize == 0 ? 0
: (TotalCount + PageSize - 1) / PageSize;

public int TotalCount { get; set; }

public ListResult() { }

public ListResult(bool wasSuccessful, string? message = null) : base(wasSuccessful, message) { }

public ListResult(string errorMessage) : base(errorMessage) { }

public ListResult(ApiResult result) : base(result) { }

public ListResult(ListResult result) : base(result)
{
Page = result.Page;
TotalCount = result.TotalCount;
PageSize = result.PageSize;
}
}

public class ListResult<T> : ListResult
{
public IList<T>? List { get; set; }

public ListResult(): base() { }
public ListResult() { }

public ListResult(bool wasSuccessful, string? message = null) : base(wasSuccessful, message) { }

public ListResult(string errorMessage) : base(errorMessage) { }

public ListResult(ApiResult result) : base(result) { }

public ListResult(IListResult result, IList<T>? items = null)
: this(items, page: result.Page, totalCount: result.TotalCount, pageSize: result.PageSize, wasSuccessful: result.WasSuccessful, message: result.Message) { }
public ListResult(ListResult result, IList<T>? items = null) : base(result)
{
List = items;
}

public ListResult(IList<T>? items, int page, int totalCount, int pageSize, bool wasSuccessful = true, string? message = null)
{
Expand Down

0 comments on commit 1c01ef1

Please sign in to comment.