5
5
using JsonApiDotNetCore . Configuration ;
6
6
using JsonApiDotNetCore . Middleware ;
7
7
using JsonApiDotNetCore . Queries ;
8
+ using JsonApiDotNetCore . Queries . Expressions ;
9
+ using JsonApiDotNetCore . Queries . Internal . Parsing ;
8
10
using JsonApiDotNetCore . QueryStrings ;
9
11
using JsonApiDotNetCore . Resources ;
10
12
using JsonApiDotNetCore . Resources . Annotations ;
@@ -15,6 +17,9 @@ namespace JsonApiDotNetCore.Serialization.Building
15
17
{
16
18
public class LinkBuilder : ILinkBuilder
17
19
{
20
+ private const string _pageSizeParameterName = "page[size]" ;
21
+ private const string _pageNumberParameterName = "page[number]" ;
22
+
18
23
private readonly IResourceContextProvider _provider ;
19
24
private readonly IRequestQueryStringAccessor _queryStringAccessor ;
20
25
private readonly IJsonApiOptions _options ;
@@ -42,10 +47,10 @@ public TopLevelLinks GetTopLevelLinks()
42
47
TopLevelLinks topLevelLinks = null ;
43
48
if ( ShouldAddTopLevelLink ( resourceContext , LinkTypes . Self ) )
44
49
{
45
- topLevelLinks = new TopLevelLinks { Self = GetSelfTopLevelLink ( resourceContext ) } ;
50
+ topLevelLinks = new TopLevelLinks { Self = GetSelfTopLevelLink ( resourceContext , null ) } ;
46
51
}
47
52
48
- if ( ShouldAddTopLevelLink ( resourceContext , LinkTypes . Paging ) && _paginationContext . PageSize != null )
53
+ if ( ShouldAddTopLevelLink ( resourceContext , LinkTypes . Paging ) && _paginationContext . PageSize != null && _request . IsCollection )
49
54
{
50
55
SetPageLinks ( resourceContext , topLevelLinks ??= new TopLevelLinks ( ) ) ;
51
56
}
@@ -70,36 +75,38 @@ private bool ShouldAddTopLevelLink(ResourceContext resourceContext, LinkTypes li
70
75
71
76
private void SetPageLinks ( ResourceContext resourceContext , TopLevelLinks links )
72
77
{
73
- if ( _paginationContext . PageNumber . OneBasedValue > 1 )
78
+ links . First = GetPageLink ( resourceContext , 1 , _paginationContext . PageSize ) ;
79
+
80
+ if ( _paginationContext . TotalPageCount > 0 )
74
81
{
75
- links . Prev = GetPageLink ( resourceContext , _paginationContext . PageNumber . OneBasedValue - 1 , _paginationContext . PageSize ) ;
82
+ links . Last = GetPageLink ( resourceContext , _paginationContext . TotalPageCount . Value , _paginationContext . PageSize ) ;
76
83
}
77
84
78
- if ( _paginationContext . PageNumber . OneBasedValue < _paginationContext . TotalPageCount )
85
+ if ( _paginationContext . PageNumber . OneBasedValue > 1 )
79
86
{
80
- links . Next = GetPageLink ( resourceContext , _paginationContext . PageNumber . OneBasedValue + 1 , _paginationContext . PageSize ) ;
87
+ links . Prev = GetPageLink ( resourceContext , _paginationContext . PageNumber . OneBasedValue - 1 , _paginationContext . PageSize ) ;
81
88
}
82
89
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 )
84
94
{
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 ) ;
88
96
}
89
97
}
90
98
91
- private string GetSelfTopLevelLink ( ResourceContext resourceContext )
99
+ private string GetSelfTopLevelLink ( ResourceContext resourceContext , Action < Dictionary < string , string > > queryStringUpdateAction )
92
100
{
93
101
var builder = new StringBuilder ( ) ;
94
102
builder . Append ( _request . BasePath ) ;
95
103
builder . Append ( "/" ) ;
96
104
builder . Append ( resourceContext . PublicName ) ;
97
105
98
- string resourceId = _request . PrimaryId ;
99
- if ( resourceId != null )
106
+ if ( _request . PrimaryId != null )
100
107
{
101
108
builder . Append ( "/" ) ;
102
- builder . Append ( resourceId ) ;
109
+ builder . Append ( _request . PrimaryId ) ;
103
110
}
104
111
105
112
if ( _request . Relationship != null )
@@ -108,49 +115,102 @@ private string GetSelfTopLevelLink(ResourceContext resourceContext)
108
115
builder . Append ( _request . Relationship . PublicName ) ;
109
116
}
110
117
111
- builder . Append ( DecodeSpecialCharacters ( _queryStringAccessor . QueryString . Value ) ) ;
118
+ string queryString = BuildQueryString ( queryStringUpdateAction ) ;
119
+ builder . Append ( queryString ) ;
112
120
113
121
return builder . ToString ( ) ;
114
122
}
115
123
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
+
116
138
private string GetPageLink ( ResourceContext resourceContext , int pageOffset , PageSize pageSize )
117
139
{
118
- string queryString = BuildQueryString ( parameters =>
140
+ return GetSelfTopLevelLink ( resourceContext , parameters =>
119
141
{
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 )
121
150
{
122
- parameters . Remove ( "page[size]" ) ;
151
+ parameters . Remove ( _pageSizeParameterName ) ;
123
152
}
124
153
else
125
154
{
126
- parameters [ "page[size]" ] = pageSize . ToString ( ) ;
155
+ parameters [ _pageSizeParameterName ] = newPageSizeParameterValue ;
127
156
}
128
157
129
158
if ( pageOffset == 1 )
130
159
{
131
- parameters . Remove ( "page[number]" ) ;
160
+ parameters . Remove ( _pageNumberParameterName ) ;
132
161
}
133
162
else
134
163
{
135
- parameters [ "page[number]" ] = pageOffset . ToString ( ) ;
164
+ parameters [ _pageNumberParameterName ] = pageOffset . ToString ( ) ;
136
165
}
137
166
} ) ;
138
-
139
- return $ "{ _request . BasePath } /{ resourceContext . PublicName } " + queryString ;
140
167
}
141
168
142
- private string BuildQueryString ( Action < Dictionary < string , string > > updateAction )
169
+ private string ChangeTopPageSize ( string pageSizeParameterValue , PageSize topPageSize )
143
170
{
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 ) ;
147
173
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 ;
149
199
}
150
200
151
- private static string DecodeSpecialCharacters ( string uri )
201
+ private List < PaginationElementQueryStringValueExpression > ParsePageSizeExpression ( string pageSizeParameterValue )
152
202
{
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 ) ;
154
214
}
155
215
156
216
/// <inheritdoc />
0 commit comments