55using JsonApiDotNetCore . Configuration ;
66using JsonApiDotNetCore . Middleware ;
77using JsonApiDotNetCore . Queries ;
8+ using JsonApiDotNetCore . Queries . Expressions ;
9+ using JsonApiDotNetCore . Queries . Internal . Parsing ;
810using JsonApiDotNetCore . QueryStrings ;
911using JsonApiDotNetCore . Resources ;
1012using JsonApiDotNetCore . Resources . Annotations ;
@@ -15,6 +17,9 @@ namespace JsonApiDotNetCore.Serialization.Building
1517{
1618 public class LinkBuilder : ILinkBuilder
1719 {
20+ private const string _pageSizeParameterName = "page[size]" ;
21+ private const string _pageNumberParameterName = "page[number]" ;
22+
1823 private readonly IResourceContextProvider _provider ;
1924 private readonly IRequestQueryStringAccessor _queryStringAccessor ;
2025 private readonly IJsonApiOptions _options ;
@@ -42,10 +47,10 @@ public TopLevelLinks GetTopLevelLinks()
4247 TopLevelLinks topLevelLinks = null ;
4348 if ( ShouldAddTopLevelLink ( resourceContext , LinkTypes . Self ) )
4449 {
45- topLevelLinks = new TopLevelLinks { Self = GetSelfTopLevelLink ( resourceContext ) } ;
50+ topLevelLinks = new TopLevelLinks { Self = GetSelfTopLevelLink ( resourceContext , null ) } ;
4651 }
4752
48- if ( ShouldAddTopLevelLink ( resourceContext , LinkTypes . Paging ) && _paginationContext . PageSize != null )
53+ if ( ShouldAddTopLevelLink ( resourceContext , LinkTypes . Paging ) && _paginationContext . PageSize != null && _request . IsCollection )
4954 {
5055 SetPageLinks ( resourceContext , topLevelLinks ??= new TopLevelLinks ( ) ) ;
5156 }
@@ -70,36 +75,38 @@ private bool ShouldAddTopLevelLink(ResourceContext resourceContext, LinkTypes li
7075
7176 private void SetPageLinks ( ResourceContext resourceContext , TopLevelLinks links )
7277 {
73- if ( _paginationContext . PageNumber . OneBasedValue > 1 )
78+ links . First = GetPageLink ( resourceContext , 1 , _paginationContext . PageSize ) ;
79+
80+ if ( _paginationContext . TotalPageCount > 0 )
7481 {
75- links . Prev = GetPageLink ( resourceContext , _paginationContext . PageNumber . OneBasedValue - 1 , _paginationContext . PageSize ) ;
82+ links . Last = GetPageLink ( resourceContext , _paginationContext . TotalPageCount . Value , _paginationContext . PageSize ) ;
7683 }
7784
78- if ( _paginationContext . PageNumber . OneBasedValue < _paginationContext . TotalPageCount )
85+ if ( _paginationContext . PageNumber . OneBasedValue > 1 )
7986 {
80- links . Next = GetPageLink ( resourceContext , _paginationContext . PageNumber . OneBasedValue + 1 , _paginationContext . PageSize ) ;
87+ links . Prev = GetPageLink ( resourceContext , _paginationContext . PageNumber . OneBasedValue - 1 , _paginationContext . PageSize ) ;
8188 }
8289
83- if ( _paginationContext . TotalPageCount > 0 )
90+ bool hasNextPage = _paginationContext . PageNumber . OneBasedValue < _paginationContext . TotalPageCount ;
91+ bool possiblyHasNextPage = _paginationContext . TotalPageCount == null && _paginationContext . IsPageFull ;
92+
93+ if ( hasNextPage || possiblyHasNextPage )
8494 {
85- links . Self = GetPageLink ( resourceContext , _paginationContext . PageNumber . OneBasedValue , _paginationContext . PageSize ) ;
86- links . First = GetPageLink ( resourceContext , 1 , _paginationContext . PageSize ) ;
87- links . Last = GetPageLink ( resourceContext , _paginationContext . TotalPageCount . Value , _paginationContext . PageSize ) ;
95+ links . Next = GetPageLink ( resourceContext , _paginationContext . PageNumber . OneBasedValue + 1 , _paginationContext . PageSize ) ;
8896 }
8997 }
9098
91- private string GetSelfTopLevelLink ( ResourceContext resourceContext )
99+ private string GetSelfTopLevelLink ( ResourceContext resourceContext , Action < Dictionary < string , string > > queryStringUpdateAction )
92100 {
93101 var builder = new StringBuilder ( ) ;
94102 builder . Append ( _request . BasePath ) ;
95103 builder . Append ( "/" ) ;
96104 builder . Append ( resourceContext . PublicName ) ;
97105
98- string resourceId = _request . PrimaryId ;
99- if ( resourceId != null )
106+ if ( _request . PrimaryId != null )
100107 {
101108 builder . Append ( "/" ) ;
102- builder . Append ( resourceId ) ;
109+ builder . Append ( _request . PrimaryId ) ;
103110 }
104111
105112 if ( _request . Relationship != null )
@@ -108,49 +115,102 @@ private string GetSelfTopLevelLink(ResourceContext resourceContext)
108115 builder . Append ( _request . Relationship . PublicName ) ;
109116 }
110117
111- builder . Append ( DecodeSpecialCharacters ( _queryStringAccessor . QueryString . Value ) ) ;
118+ string queryString = BuildQueryString ( queryStringUpdateAction ) ;
119+ builder . Append ( queryString ) ;
112120
113121 return builder . ToString ( ) ;
114122 }
115123
124+ private string BuildQueryString ( Action < Dictionary < string , string > > updateAction )
125+ {
126+ var parameters = _queryStringAccessor . Query . ToDictionary ( pair => pair . Key , pair => pair . Value . ToString ( ) ) ;
127+ updateAction ? . Invoke ( parameters ) ;
128+ string queryString = QueryString . Create ( parameters ) . Value ;
129+
130+ return DecodeSpecialCharacters ( queryString ) ;
131+ }
132+
133+ private static string DecodeSpecialCharacters ( string uri )
134+ {
135+ return uri . Replace ( "%5B" , "[" ) . Replace ( "%5D" , "]" ) . Replace ( "%27" , "'" ) . Replace ( "%3A" , ":" ) ;
136+ }
137+
116138 private string GetPageLink ( ResourceContext resourceContext , int pageOffset , PageSize pageSize )
117139 {
118- string queryString = BuildQueryString ( parameters =>
140+ return GetSelfTopLevelLink ( resourceContext , parameters =>
119141 {
120- if ( pageSize == null || pageSize . Equals ( _options . DefaultPageSize ) )
142+ var existingPageSizeParameterValue = parameters . ContainsKey ( _pageSizeParameterName )
143+ ? parameters [ _pageSizeParameterName ]
144+ : null ;
145+
146+ PageSize newTopPageSize = Equals ( pageSize , _options . DefaultPageSize ) ? null : pageSize ;
147+
148+ string newPageSizeParameterValue = ChangeTopPageSize ( existingPageSizeParameterValue , newTopPageSize ) ;
149+ if ( newPageSizeParameterValue == null )
121150 {
122- parameters . Remove ( "page[size]" ) ;
151+ parameters . Remove ( _pageSizeParameterName ) ;
123152 }
124153 else
125154 {
126- parameters [ "page[size]" ] = pageSize . ToString ( ) ;
155+ parameters [ _pageSizeParameterName ] = newPageSizeParameterValue ;
127156 }
128157
129158 if ( pageOffset == 1 )
130159 {
131- parameters . Remove ( "page[number]" ) ;
160+ parameters . Remove ( _pageNumberParameterName ) ;
132161 }
133162 else
134163 {
135- parameters [ "page[number]" ] = pageOffset . ToString ( ) ;
164+ parameters [ _pageNumberParameterName ] = pageOffset . ToString ( ) ;
136165 }
137166 } ) ;
138-
139- return $ "{ _request . BasePath } /{ resourceContext . PublicName } " + queryString ;
140167 }
141168
142- private string BuildQueryString ( Action < Dictionary < string , string > > updateAction )
169+ private string ChangeTopPageSize ( string pageSizeParameterValue , PageSize topPageSize )
143170 {
144- var parameters = _queryStringAccessor . Query . ToDictionary ( pair => pair . Key , pair => pair . Value . ToString ( ) ) ;
145- updateAction ( parameters ) ;
146- string queryString = QueryString . Create ( parameters ) . Value ;
171+ var elements = ParsePageSizeExpression ( pageSizeParameterValue ) ;
172+ var elementInTopScopeIndex = elements . FindIndex ( expression => expression . Scope == null ) ;
147173
148- return DecodeSpecialCharacters ( queryString ) ;
174+ if ( topPageSize != null )
175+ {
176+ var topPageSizeElement = new PaginationElementQueryStringValueExpression ( null , topPageSize . Value ) ;
177+
178+ if ( elementInTopScopeIndex != - 1 )
179+ {
180+ elements [ elementInTopScopeIndex ] = topPageSizeElement ;
181+ }
182+ else
183+ {
184+ elements . Insert ( 0 , topPageSizeElement ) ;
185+ }
186+ }
187+ else
188+ {
189+ if ( elementInTopScopeIndex != - 1 )
190+ {
191+ elements . RemoveAt ( elementInTopScopeIndex ) ;
192+ }
193+ }
194+
195+ var parameterValue = string . Join ( ',' ,
196+ elements . Select ( expression => expression . Scope == null ? expression . Value . ToString ( ) : $ "{ expression . Scope } :{ expression . Value } ") ) ;
197+
198+ return parameterValue == string . Empty ? null : parameterValue ;
149199 }
150200
151- private static string DecodeSpecialCharacters ( string uri )
201+ private List < PaginationElementQueryStringValueExpression > ParsePageSizeExpression ( string pageSizeParameterValue )
152202 {
153- return uri . Replace ( "%5B" , "[" ) . Replace ( "%5D" , "]" ) . Replace ( "%27" , "'" ) ;
203+ if ( pageSizeParameterValue == null )
204+ {
205+ return new List < PaginationElementQueryStringValueExpression > ( ) ;
206+ }
207+
208+ var requestResource = _request . SecondaryResource ?? _request . PrimaryResource ;
209+
210+ var parser = new PaginationParser ( _provider ) ;
211+ var paginationExpression = parser . Parse ( pageSizeParameterValue , requestResource ) ;
212+
213+ return new List < PaginationElementQueryStringValueExpression > ( paginationExpression . Elements ) ;
154214 }
155215
156216 /// <inheritdoc />
0 commit comments