jpie 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cursorrules +7 -3
- data/.overcommit.yml +35 -0
- data/CHANGELOG.md +14 -0
- data/README.md +58 -198
- data/examples/pagination.md +303 -0
- data/lib/jpie/controller/crud_actions.rb +23 -2
- data/lib/jpie/controller/error_handling/handler_setup.rb +124 -0
- data/lib/jpie/controller/error_handling/handlers.rb +109 -0
- data/lib/jpie/controller/error_handling.rb +6 -175
- data/lib/jpie/controller/json_api_validation.rb +55 -33
- data/lib/jpie/controller/parameter_parsing.rb +43 -0
- data/lib/jpie/controller/rendering.rb +95 -1
- data/lib/jpie/resource/attributable.rb +2 -7
- data/lib/jpie/resource.rb +22 -8
- data/lib/jpie/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0e1d1634f188bca4057e3550479af557bd00ba0d262c919ba221aa99997490e
|
4
|
+
data.tar.gz: 89db580da555f310a1b840daad204e2315fcb8b7828278b328271412d3bc321d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2ced2be2db661db82e08200be2e5df033c072dcbf249565719336d3b08532d2cb01c4bbcec78cfb5e14fbbbadddb0c127292b70a9b970da3ecc20891bc8c8ab
|
7
|
+
data.tar.gz: 5d1ac4453eb74ba6a8ea04036f00065e26a54c2ff85a603525dd90106bd4f82a839ed3985083c5ff60d087b6bb1239c7a618a7a024e7e3fa1955c82d863aefa8
|
data/.cursorrules
CHANGED
@@ -5,6 +5,10 @@
|
|
5
5
|
- Follow Ruby style guide and RuboCop rules defined in .rubocop.yml
|
6
6
|
- Prefer rubocop autocorrect
|
7
7
|
- Always pass rubocop before committing to git
|
8
|
+
- Use `{data:}` rather than `{data: data}`
|
9
|
+
- Only use code comments when abasolutely necessary
|
10
|
+
- Keep any code comments short and concise
|
11
|
+
- Don't update the .rubocop.yml unless it's absolutely necessarry
|
8
12
|
|
9
13
|
# Documentation requirements
|
10
14
|
- Keep README.md up to date with installation and usage instructions
|
@@ -33,6 +37,8 @@
|
|
33
37
|
- Place tests in spec/jpie/
|
34
38
|
- Use proper namespacing (JPie module)
|
35
39
|
- Follow Ruby gem best practices
|
40
|
+
- Do not update the spec/jpie/database.rb unless it's absolutely necessarry
|
41
|
+
- Do not update the spec/jpie/resources.rb unless it's absolutely necessarry
|
36
42
|
|
37
43
|
# Dependency management
|
38
44
|
- Keep dependencies minimal and justified
|
@@ -56,14 +62,12 @@
|
|
56
62
|
- Support Rails 8+ integration
|
57
63
|
- Follow JSON:API specification strictly
|
58
64
|
|
59
|
-
# Styleguide
|
60
|
-
- Use `{data:}` rather than `{data: data}`
|
61
|
-
|
62
65
|
# When implementing new features or refactoring
|
63
66
|
- Always read the .aiconfig file
|
64
67
|
- Always implement code slowly and methodically
|
65
68
|
- Always test as you go
|
66
69
|
- Always make sure rubocop passes
|
70
|
+
- Do not add any functionality for the new feature to existing tests unless absolutely necessarry.
|
67
71
|
|
68
72
|
# Examples
|
69
73
|
- The examples must _only_ include _required_ code
|
data/.overcommit.yml
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Use this file to configure the Overcommit hooks you wish to use. This will
|
2
|
+
# extend the default configuration defined in:
|
3
|
+
# https://github.com/sds/overcommit/blob/master/config/default.yml
|
4
|
+
|
5
|
+
# Global settings
|
6
|
+
verify_signatures: false
|
7
|
+
|
8
|
+
PreCommit:
|
9
|
+
ALL:
|
10
|
+
problem_on_unmodified_line: ignore
|
11
|
+
requires_files: true
|
12
|
+
quiet: false
|
13
|
+
|
14
|
+
# Run RuboCop on Ruby files before commit
|
15
|
+
RuboCop:
|
16
|
+
enabled: true
|
17
|
+
command: ['bundle', 'exec', 'rubocop']
|
18
|
+
flags: ['--force-exclusion']
|
19
|
+
on_warn: fail
|
20
|
+
|
21
|
+
# Check for trailing whitespace
|
22
|
+
TrailingWhitespace:
|
23
|
+
enabled: true
|
24
|
+
exclude:
|
25
|
+
- '**/*.md'
|
26
|
+
|
27
|
+
# Check for merge conflicts
|
28
|
+
MergeConflicts:
|
29
|
+
enabled: true
|
30
|
+
|
31
|
+
PrePush:
|
32
|
+
# Run RSpec tests before push
|
33
|
+
RSpec:
|
34
|
+
enabled: true
|
35
|
+
command: ['bundle', 'exec', 'rspec']
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [0.4.2] - 2025-01-25
|
11
|
+
|
12
|
+
### Added
|
13
|
+
- **Pagination Example**: Comprehensive pagination example demonstrating both simple and JSON:API standard pagination formats
|
14
|
+
- Simple pagination parameters (`page`, `per_page`)
|
15
|
+
- JSON:API standard pagination format (`page[number]`, `page[size]`)
|
16
|
+
- Pagination combined with sorting functionality
|
17
|
+
- Edge cases including last page and empty results
|
18
|
+
- Complete HTTP request/response examples following project format
|
19
|
+
|
20
|
+
### Enhanced
|
21
|
+
- **Documentation**: Improved example coverage with detailed pagination use cases
|
22
|
+
- **Developer Experience**: Clear examples for implementing pagination in JPie applications
|
23
|
+
|
10
24
|
## [0.4.1] - 2025-01-25
|
11
25
|
|
12
26
|
### Fixed
|
data/README.md
CHANGED
@@ -27,6 +27,63 @@ Add JPie to your Rails application:
|
|
27
27
|
bundle add jpie
|
28
28
|
```
|
29
29
|
|
30
|
+
## Development Setup
|
31
|
+
|
32
|
+
This project uses [Overcommit](https://github.com/sds/overcommit) to enforce code quality through Git hooks. After cloning the repository:
|
33
|
+
|
34
|
+
### Quick Setup (Recommended)
|
35
|
+
|
36
|
+
```bash
|
37
|
+
# One command to set up everything
|
38
|
+
./bin/setup-hooks
|
39
|
+
```
|
40
|
+
|
41
|
+
### Manual Setup
|
42
|
+
|
43
|
+
If you prefer to set up manually:
|
44
|
+
|
45
|
+
```bash
|
46
|
+
# 1. Install dependencies
|
47
|
+
bundle install
|
48
|
+
|
49
|
+
# 2. Install overcommit globally (one-time setup)
|
50
|
+
gem install overcommit
|
51
|
+
|
52
|
+
# 3. Install the Git hooks for this project
|
53
|
+
overcommit --install
|
54
|
+
|
55
|
+
# 4. Sign the configuration (required for security)
|
56
|
+
overcommit --sign
|
57
|
+
```
|
58
|
+
|
59
|
+
### 3. Automated Quality Checks
|
60
|
+
|
61
|
+
The following checks run automatically:
|
62
|
+
|
63
|
+
**Pre-commit hooks:**
|
64
|
+
- ✅ **RuboCop** - Code style and quality analysis
|
65
|
+
- ✅ **Trailing whitespace** - Prevents whitespace issues
|
66
|
+
- ✅ **Merge conflicts** - Catches unresolved conflicts
|
67
|
+
|
68
|
+
**Pre-push hooks:**
|
69
|
+
- ✅ **RSpec** - Full test suite execution
|
70
|
+
|
71
|
+
### 4. Manual Quality Checks
|
72
|
+
|
73
|
+
You can run these checks manually:
|
74
|
+
|
75
|
+
```bash
|
76
|
+
# Run RuboCop with auto-fix
|
77
|
+
bundle exec rubocop -A
|
78
|
+
|
79
|
+
# Run tests
|
80
|
+
bundle exec rspec
|
81
|
+
|
82
|
+
# Test hooks without committing
|
83
|
+
overcommit --run pre-commit
|
84
|
+
overcommit --run pre-push
|
85
|
+
```
|
86
|
+
|
30
87
|
## Quick Start
|
31
88
|
|
32
89
|
JPie works out of the box with minimal configuration:
|
@@ -131,27 +188,6 @@ end
|
|
131
188
|
| `--model=NAME` | Specify model class | `--model=Person` |
|
132
189
|
| `--skip-model` | Skip explicit model declaration | `--skip-model` |
|
133
190
|
|
134
|
-
## Core Features
|
135
|
-
|
136
|
-
### JSON:API Compliance
|
137
|
-
|
138
|
-
JPie automatically handles all JSON:API specification requirements:
|
139
|
-
|
140
|
-
#### Sorting
|
141
|
-
```http
|
142
|
-
GET /users?sort=name,-created_at
|
143
|
-
```
|
144
|
-
|
145
|
-
#### Includes
|
146
|
-
```http
|
147
|
-
GET /posts?include=author,comments,comments.author
|
148
|
-
```
|
149
|
-
|
150
|
-
#### Validation
|
151
|
-
- Request structure validation for POST/PATCH operations
|
152
|
-
- Include parameter validation
|
153
|
-
- Sort parameter validation with clear error messages
|
154
|
-
|
155
191
|
### Modern DSL
|
156
192
|
|
157
193
|
```ruby
|
@@ -180,183 +216,7 @@ class UserResource < JPie::Resource
|
|
180
216
|
end
|
181
217
|
```
|
182
218
|
|
183
|
-
|
184
|
-
|
185
|
-
JPie seamlessly supports Rails `:through` associations:
|
186
|
-
|
187
|
-
```ruby
|
188
|
-
class CarResource < JPie::Resource
|
189
|
-
attributes :make, :model, :year
|
190
|
-
|
191
|
-
# JPie handles the through association automatically
|
192
|
-
has_many :drivers, through: :car_drivers
|
193
|
-
end
|
194
|
-
```
|
195
|
-
|
196
|
-
The join table is completely hidden from the API response, providing a clean interface.
|
197
|
-
|
198
|
-
### Custom Attributes
|
199
|
-
|
200
|
-
Define custom computed attributes using method overrides:
|
201
|
-
|
202
|
-
```ruby
|
203
|
-
class UserResource < JPie::Resource
|
204
|
-
attributes :first_name, :last_name
|
205
|
-
attribute :full_name
|
206
|
-
|
207
|
-
private
|
208
|
-
|
209
|
-
def full_name
|
210
|
-
"#{object.first_name} #{object.last_name}"
|
211
|
-
end
|
212
|
-
end
|
213
|
-
```
|
214
|
-
|
215
|
-
### Authorization & Context
|
216
|
-
|
217
|
-
Pass context for authorization-aware responses:
|
218
|
-
|
219
|
-
```ruby
|
220
|
-
class UsersController < ApplicationController
|
221
|
-
include JPie::Controller
|
222
|
-
|
223
|
-
def show
|
224
|
-
user = User.find(params[:id])
|
225
|
-
render_jsonapi(user, context: { current_user: current_user })
|
226
|
-
end
|
227
|
-
end
|
228
|
-
```
|
229
|
-
|
230
|
-
## Error Handling
|
231
|
-
|
232
|
-
JPie provides robust error handling with full customization options:
|
233
|
-
|
234
|
-
### Default Error Handling
|
235
|
-
|
236
|
-
JPie automatically handles common errors:
|
237
|
-
|
238
|
-
| Error Type | HTTP Status | Description |
|
239
|
-
|------------|-------------|-------------|
|
240
|
-
| `JPie::Errors::Error` | Varies | Base JPie errors with custom status |
|
241
|
-
| `ActiveRecord::RecordNotFound` | 404 | Missing records |
|
242
|
-
| `ActiveRecord::RecordInvalid` | 422 | Validation failures |
|
243
|
-
|
244
|
-
### Customization Options
|
245
|
-
|
246
|
-
#### Override Specific Handlers
|
247
|
-
|
248
|
-
```ruby
|
249
|
-
class ApplicationController < ActionController::Base
|
250
|
-
# Define your handlers first
|
251
|
-
rescue_from ActiveRecord::RecordNotFound, with: :my_not_found_handler
|
252
|
-
|
253
|
-
include JPie::Controller
|
254
|
-
# JPie will detect existing handler and won't override it
|
255
|
-
end
|
256
|
-
```
|
257
|
-
|
258
|
-
#### Extend JPie Handlers
|
259
|
-
|
260
|
-
```ruby
|
261
|
-
class ApplicationController < ActionController::Base
|
262
|
-
include JPie::Controller
|
263
|
-
|
264
|
-
private
|
265
|
-
|
266
|
-
def render_jpie_validation_error(error)
|
267
|
-
# Add custom logging
|
268
|
-
Rails.logger.error "Validation failed: #{error.message}"
|
269
|
-
|
270
|
-
# Call the original method or implement your own
|
271
|
-
super
|
272
|
-
end
|
273
|
-
end
|
274
|
-
```
|
275
|
-
|
276
|
-
#### Disable All JPie Error Handlers
|
277
|
-
|
278
|
-
```ruby
|
279
|
-
class ApplicationController < ActionController::Base
|
280
|
-
include JPie::Controller
|
281
|
-
|
282
|
-
disable_jpie_error_handlers
|
283
|
-
|
284
|
-
# Define your own handlers
|
285
|
-
rescue_from StandardError, with: :handle_standard_error
|
286
|
-
end
|
287
|
-
```
|
288
|
-
|
289
|
-
### Custom JPie Errors
|
290
|
-
|
291
|
-
```ruby
|
292
|
-
class CustomBusinessError < JPie::Errors::Error
|
293
|
-
def initialize(detail: 'Business logic error')
|
294
|
-
super(status: 422, title: 'Business Error', detail: detail)
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
# Use in controllers
|
299
|
-
raise CustomBusinessError.new(detail: 'Custom validation failed')
|
300
|
-
```
|
301
|
-
|
302
|
-
## Advanced Features
|
303
|
-
|
304
|
-
### Polymorphic Associations
|
305
|
-
|
306
|
-
JPie supports polymorphic associations for complex data relationships:
|
307
|
-
|
308
|
-
```ruby
|
309
|
-
class CommentResource < JPie::Resource
|
310
|
-
attributes :content
|
311
|
-
has_one :author
|
312
|
-
|
313
|
-
# Polymorphic commentable (posts, articles, videos, etc.)
|
314
|
-
private
|
315
|
-
|
316
|
-
def author
|
317
|
-
object.author
|
318
|
-
end
|
319
|
-
end
|
320
|
-
```
|
321
|
-
|
322
|
-
### Single Table Inheritance
|
323
|
-
|
324
|
-
JPie automatically handles STI models:
|
325
|
-
|
326
|
-
```ruby
|
327
|
-
# Base resource
|
328
|
-
class VehicleResource < JPie::Resource
|
329
|
-
attributes :name, :brand, :year
|
330
|
-
end
|
331
|
-
|
332
|
-
# STI resources inherit automatically
|
333
|
-
class CarResource < VehicleResource
|
334
|
-
attributes :engine_size, :doors # Car-specific attributes
|
335
|
-
end
|
336
|
-
```
|
337
|
-
|
338
|
-
STI types are automatically inferred in JSON:API responses.
|
339
|
-
|
340
|
-
### Custom Sorting
|
341
|
-
|
342
|
-
Implement complex sorting logic:
|
343
|
-
|
344
|
-
```ruby
|
345
|
-
class PostResource < JPie::Resource
|
346
|
-
sortable :popularity do |query, direction|
|
347
|
-
query.joins(:likes, :comments)
|
348
|
-
.group('posts.id')
|
349
|
-
.order("COUNT(likes.id) + COUNT(comments.id) #{direction.to_s.upcase}")
|
350
|
-
end
|
351
|
-
end
|
352
|
-
```
|
353
|
-
|
354
|
-
## Performance & Best Practices
|
355
|
-
|
356
|
-
- **Efficient serialization** with automatic deduplication
|
357
|
-
- **Smart includes** with optimized queries
|
358
|
-
- **Validation caching** for improved performance
|
359
|
-
- **Error handling** that doesn't impact performance
|
219
|
+
See the examples folder for more examples of how to use the DSL to solve various serialization/deserialization scenarios.
|
360
220
|
|
361
221
|
## Contributing
|
362
222
|
|
@@ -0,0 +1,303 @@
|
|
1
|
+
# Pagination Example
|
2
|
+
|
3
|
+
This example demonstrates how to implement pagination with JPie resources using both simple and JSON:API standard pagination parameters.
|
4
|
+
|
5
|
+
## Setup
|
6
|
+
|
7
|
+
### 1. Model (`app/models/article.rb`)
|
8
|
+
```ruby
|
9
|
+
class Article < ActiveRecord::Base
|
10
|
+
validates :title, presence: true
|
11
|
+
validates :content, presence: true
|
12
|
+
end
|
13
|
+
```
|
14
|
+
|
15
|
+
### 2. Resource (`app/resources/article_resource.rb`)
|
16
|
+
```ruby
|
17
|
+
class ArticleResource < JPie::Resource
|
18
|
+
attributes :title, :content, :published_at
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
### 3. Controller (`app/controllers/articles_controller.rb`)
|
23
|
+
```ruby
|
24
|
+
class ArticlesController < ApplicationController
|
25
|
+
include JPie::Controller
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
### 4. Routes (`config/routes.rb`)
|
30
|
+
```ruby
|
31
|
+
Rails.application.routes.draw do
|
32
|
+
resources :articles
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
## HTTP Examples
|
37
|
+
|
38
|
+
### Simple Pagination Parameters
|
39
|
+
|
40
|
+
#### Get First Page with 5 Articles
|
41
|
+
```http
|
42
|
+
GET /articles?page=1&per_page=5
|
43
|
+
Accept: application/vnd.api+json
|
44
|
+
|
45
|
+
HTTP/1.1 200 OK
|
46
|
+
Content-Type: application/vnd.api+json
|
47
|
+
|
48
|
+
{
|
49
|
+
"data": [
|
50
|
+
{
|
51
|
+
"id": "1",
|
52
|
+
"type": "articles",
|
53
|
+
"attributes": {
|
54
|
+
"title": "First Article",
|
55
|
+
"content": "Content of first article",
|
56
|
+
"published_at": "2024-01-01T10:00:00Z"
|
57
|
+
}
|
58
|
+
},
|
59
|
+
{
|
60
|
+
"id": "2",
|
61
|
+
"type": "articles",
|
62
|
+
"attributes": {
|
63
|
+
"title": "Second Article",
|
64
|
+
"content": "Content of second article",
|
65
|
+
"published_at": "2024-01-02T10:00:00Z"
|
66
|
+
}
|
67
|
+
}
|
68
|
+
],
|
69
|
+
"meta": {
|
70
|
+
"pagination": {
|
71
|
+
"page": 1,
|
72
|
+
"per_page": 5,
|
73
|
+
"total_pages": 4,
|
74
|
+
"total_count": 20
|
75
|
+
}
|
76
|
+
},
|
77
|
+
"links": {
|
78
|
+
"self": "http://example.com/articles?page=1&per_page=5",
|
79
|
+
"first": "http://example.com/articles?page=1&per_page=5",
|
80
|
+
"last": "http://example.com/articles?page=4&per_page=5",
|
81
|
+
"next": "http://example.com/articles?page=2&per_page=5"
|
82
|
+
}
|
83
|
+
}
|
84
|
+
```
|
85
|
+
|
86
|
+
#### Get Second Page
|
87
|
+
```http
|
88
|
+
GET /articles?page=2&per_page=5
|
89
|
+
Accept: application/vnd.api+json
|
90
|
+
|
91
|
+
HTTP/1.1 200 OK
|
92
|
+
Content-Type: application/vnd.api+json
|
93
|
+
|
94
|
+
{
|
95
|
+
"data": [
|
96
|
+
{
|
97
|
+
"id": "6",
|
98
|
+
"type": "articles",
|
99
|
+
"attributes": {
|
100
|
+
"title": "Sixth Article",
|
101
|
+
"content": "Content of sixth article",
|
102
|
+
"published_at": "2024-01-06T10:00:00Z"
|
103
|
+
}
|
104
|
+
}
|
105
|
+
],
|
106
|
+
"meta": {
|
107
|
+
"pagination": {
|
108
|
+
"page": 2,
|
109
|
+
"per_page": 5,
|
110
|
+
"total_pages": 4,
|
111
|
+
"total_count": 20
|
112
|
+
}
|
113
|
+
},
|
114
|
+
"links": {
|
115
|
+
"self": "http://example.com/articles?page=2&per_page=5",
|
116
|
+
"first": "http://example.com/articles?page=1&per_page=5",
|
117
|
+
"last": "http://example.com/articles?page=4&per_page=5",
|
118
|
+
"prev": "http://example.com/articles?page=1&per_page=5",
|
119
|
+
"next": "http://example.com/articles?page=3&per_page=5"
|
120
|
+
}
|
121
|
+
}
|
122
|
+
```
|
123
|
+
|
124
|
+
### JSON:API Standard Pagination Parameters
|
125
|
+
|
126
|
+
#### Get First Page Using JSON:API Format
|
127
|
+
```http
|
128
|
+
GET /articles?page[number]=1&page[size]=3
|
129
|
+
Accept: application/vnd.api+json
|
130
|
+
|
131
|
+
HTTP/1.1 200 OK
|
132
|
+
Content-Type: application/vnd.api+json
|
133
|
+
|
134
|
+
{
|
135
|
+
"data": [
|
136
|
+
{
|
137
|
+
"id": "1",
|
138
|
+
"type": "articles",
|
139
|
+
"attributes": {
|
140
|
+
"title": "First Article",
|
141
|
+
"content": "Content of first article",
|
142
|
+
"published_at": "2024-01-01T10:00:00Z"
|
143
|
+
}
|
144
|
+
},
|
145
|
+
{
|
146
|
+
"id": "2",
|
147
|
+
"type": "articles",
|
148
|
+
"attributes": {
|
149
|
+
"title": "Second Article",
|
150
|
+
"content": "Content of second article",
|
151
|
+
"published_at": "2024-01-02T10:00:00Z"
|
152
|
+
}
|
153
|
+
},
|
154
|
+
{
|
155
|
+
"id": "3",
|
156
|
+
"type": "articles",
|
157
|
+
"attributes": {
|
158
|
+
"title": "Third Article",
|
159
|
+
"content": "Content of third article",
|
160
|
+
"published_at": "2024-01-03T10:00:00Z"
|
161
|
+
}
|
162
|
+
}
|
163
|
+
],
|
164
|
+
"meta": {
|
165
|
+
"pagination": {
|
166
|
+
"page": 1,
|
167
|
+
"per_page": 3,
|
168
|
+
"total_pages": 7,
|
169
|
+
"total_count": 20
|
170
|
+
}
|
171
|
+
},
|
172
|
+
"links": {
|
173
|
+
"self": "http://example.com/articles?page=1&per_page=3",
|
174
|
+
"first": "http://example.com/articles?page=1&per_page=3",
|
175
|
+
"last": "http://example.com/articles?page=7&per_page=3",
|
176
|
+
"next": "http://example.com/articles?page=2&per_page=3"
|
177
|
+
}
|
178
|
+
}
|
179
|
+
```
|
180
|
+
|
181
|
+
### Pagination with Sorting
|
182
|
+
|
183
|
+
#### Get Sorted and Paginated Results
|
184
|
+
```http
|
185
|
+
GET /articles?sort=-published_at&page=1&per_page=3
|
186
|
+
Accept: application/vnd.api+json
|
187
|
+
|
188
|
+
HTTP/1.1 200 OK
|
189
|
+
Content-Type: application/vnd.api+json
|
190
|
+
|
191
|
+
{
|
192
|
+
"data": [
|
193
|
+
{
|
194
|
+
"id": "20",
|
195
|
+
"type": "articles",
|
196
|
+
"attributes": {
|
197
|
+
"title": "Latest Article",
|
198
|
+
"content": "Most recent content",
|
199
|
+
"published_at": "2024-01-20T10:00:00Z"
|
200
|
+
}
|
201
|
+
},
|
202
|
+
{
|
203
|
+
"id": "19",
|
204
|
+
"type": "articles",
|
205
|
+
"attributes": {
|
206
|
+
"title": "Second Latest Article",
|
207
|
+
"content": "Second most recent content",
|
208
|
+
"published_at": "2024-01-19T10:00:00Z"
|
209
|
+
}
|
210
|
+
},
|
211
|
+
{
|
212
|
+
"id": "18",
|
213
|
+
"type": "articles",
|
214
|
+
"attributes": {
|
215
|
+
"title": "Third Latest Article",
|
216
|
+
"content": "Third most recent content",
|
217
|
+
"published_at": "2024-01-18T10:00:00Z"
|
218
|
+
}
|
219
|
+
}
|
220
|
+
],
|
221
|
+
"meta": {
|
222
|
+
"pagination": {
|
223
|
+
"page": 1,
|
224
|
+
"per_page": 3,
|
225
|
+
"total_pages": 7,
|
226
|
+
"total_count": 20
|
227
|
+
}
|
228
|
+
},
|
229
|
+
"links": {
|
230
|
+
"self": "http://example.com/articles?sort=-published_at&page=1&per_page=3",
|
231
|
+
"first": "http://example.com/articles?sort=-published_at&page=1&per_page=3",
|
232
|
+
"last": "http://example.com/articles?sort=-published_at&page=7&per_page=3",
|
233
|
+
"next": "http://example.com/articles?sort=-published_at&page=2&per_page=3"
|
234
|
+
}
|
235
|
+
}
|
236
|
+
```
|
237
|
+
|
238
|
+
### Last Page Response
|
239
|
+
|
240
|
+
#### Get Last Page
|
241
|
+
```http
|
242
|
+
GET /articles?page=4&per_page=5
|
243
|
+
Accept: application/vnd.api+json
|
244
|
+
|
245
|
+
HTTP/1.1 200 OK
|
246
|
+
Content-Type: application/vnd.api+json
|
247
|
+
|
248
|
+
{
|
249
|
+
"data": [
|
250
|
+
{
|
251
|
+
"id": "20",
|
252
|
+
"type": "articles",
|
253
|
+
"attributes": {
|
254
|
+
"title": "Last Article",
|
255
|
+
"content": "Content of last article",
|
256
|
+
"published_at": "2024-01-20T10:00:00Z"
|
257
|
+
}
|
258
|
+
}
|
259
|
+
],
|
260
|
+
"meta": {
|
261
|
+
"pagination": {
|
262
|
+
"page": 4,
|
263
|
+
"per_page": 5,
|
264
|
+
"total_pages": 4,
|
265
|
+
"total_count": 20
|
266
|
+
}
|
267
|
+
},
|
268
|
+
"links": {
|
269
|
+
"self": "http://example.com/articles?page=4&per_page=5",
|
270
|
+
"first": "http://example.com/articles?page=1&per_page=5",
|
271
|
+
"last": "http://example.com/articles?page=4&per_page=5",
|
272
|
+
"prev": "http://example.com/articles?page=3&per_page=5"
|
273
|
+
}
|
274
|
+
}
|
275
|
+
```
|
276
|
+
|
277
|
+
### Empty Results
|
278
|
+
|
279
|
+
#### Get Page Beyond Available Data
|
280
|
+
```http
|
281
|
+
GET /articles?page=10&per_page=5
|
282
|
+
Accept: application/vnd.api+json
|
283
|
+
|
284
|
+
HTTP/1.1 200 OK
|
285
|
+
Content-Type: application/vnd.api+json
|
286
|
+
|
287
|
+
{
|
288
|
+
"data": [],
|
289
|
+
"meta": {
|
290
|
+
"pagination": {
|
291
|
+
"page": 10,
|
292
|
+
"per_page": 5,
|
293
|
+
"total_pages": 4,
|
294
|
+
"total_count": 20
|
295
|
+
}
|
296
|
+
},
|
297
|
+
"links": {
|
298
|
+
"self": "http://example.com/articles?page=10&per_page=5",
|
299
|
+
"first": "http://example.com/articles?page=1&per_page=5",
|
300
|
+
"last": "http://example.com/articles?page=4&per_page=5"
|
301
|
+
}
|
302
|
+
}
|
303
|
+
```
|