feat: bump to 1.100.0 — new models, breaking cleanups, and Jackson fixes#14
Conversation
Breaking changes: - AbstractExportPost: rename scope()/format() to getScopeString()/getFormatString() - ApiShareInfo.sharedItemName: corrected type from Long to String - FieldPut: remove shadowed content field; now inherits from FieldPost - ActivitySearchResult, DocumentSearchResult, FileSearchResult, FormSearchResult, ShareSearchResult: change from @value to @DaTa @NoArgsConstructor for correct Jackson deserialisation Added: - UserInfo model (sysadmin user-listing endpoint) - UserSearchResult model (paginated UserInfo wrapper) - GroupSearchResult model (paginated GroupInfo wrapper) - AGENTS.md: single source of truth for contributors and AI agents Fixed: - @JsonIgnoreProperties(ignoreUnknown = true) on all response POJOs via IdentifiableNameable and individually on non-hierarchy classes - User.java: replace @value @NoArgsConstructor with @DaTa @NoArgsConstructor - ISO8601DateSerialiser: replace per-call SimpleDateFormat with thread-safe static DateTimeFormatter - ApiShareInfo: split multi-field Long declaration; sharedItemName is now a separate String field - Removed stale swagger-codegen DO NOT EDIT headers from source files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Bumps the library to 1.100.0 and performs a broad cleanup of the response/request POJO layer: it fixes Jackson deserialisation issues by replacing @Value with @Data @NoArgsConstructor on several models, sprinkles @JsonIgnoreProperties(ignoreUnknown = true) across response types, fixes a thread-unsafe SimpleDateFormat, splits a malformed multi-field declaration in ApiShareInfo, removes dead shadowed state from FieldPut, renames scope()/format() on AbstractExportPost, adds new UserInfo/UserSearchResult/GroupSearchResult models, and introduces an AGENTS.md contributor guide.
Changes:
- Breaking API cleanups:
AbstractExportPostmethod renames,ApiShareInfo.sharedItemNametype fix,FieldPutremoves shadowedcontent, search-result classes converted from@Valueto@Data @NoArgsConstructor. - Deserialisation hardening:
@JsonIgnorePropertiesadded widely,Userswitched to@Data,ISO8601DateSerialiserrewritten with a static thread-safeDateTimeFormatter,PaginatedResultListgainspageSize. - New models + docs:
UserInfo,UserSearchResult,GroupSearchResult; newAGENTS.md; new tests/JSON fixtures; cleanup of stale swagger-codegen headers.
Reviewed changes
Copilot reviewed 46 out of 46 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| pom.xml | Version bump to 1.100.0. |
| CHANGELOG.md | Documents 1.100.0 breaking changes/additions/fixes. |
| AGENTS.md | New contributor & AI-agent guide (partly stale vs. the PR's own changes). |
| src/main/java/.../AbstractExportPost.java | Rename scope()/format() → getScopeString()/getFormatString(). |
| src/main/java/.../Activity.java, ExportJob.java, ExportJobResult.java, LinkItem.java, Status.java, ApiSharingResult.java, ApiShareInfo.java, UserGroupInfo.java | Add @JsonIgnoreProperties(ignoreUnknown = true) (and split ApiShareInfo Long/String fields). |
| src/main/java/.../IdentifiableNameable.java | Add @JsonIgnoreProperties; drop unused import. |
| src/main/java/.../PaginatedResultList.java | Strip stale codegen header; add pageSize. |
| src/main/java/.../ActivitySearchResult.java, DocumentSearchResult.java, FileSearchResult.java, FormSearchResult.java, ShareSearchResult.java | Convert @Value → @Data @NoArgsConstructor + @JsonIgnoreProperties. |
| src/main/java/.../Document*.java, Folder.java, FolderTreeItemInfo.java, FolderTreeItemListing.java, Field.java, ApiFile.java | Remove stale codegen headers; FolderTreeItemListing gains @NoArgsConstructor + @JsonIgnoreProperties. |
| src/main/java/.../FieldPut.java | Remove shadowed content field; add explicit constructors. |
| src/main/java/.../MoveRequest.java | @JsonProperty("docId") on recordId; Javadoc cleanup. |
| src/main/java/.../User.java | Replace @Value @NoArgsConstructor with @Data @NoArgsConstructor. |
| src/main/java/.../UserInfo.java | New POJO for sysadmin user listing (missing username field). |
| src/main/java/.../UserSearchResult.java, GroupSearchResult.java | New paginated wrappers. |
| src/main/java/.../UserPost.java | Inline comment about server constraint on apiKey. |
| src/main/java/.../jackson/ISO8601DateSerialiser.java | Replace per-call SimpleDateFormat with static DateTimeFormatter. |
| src/test/.../*Test.java | Replace System.err.println debug with proper assertions; add UserTest, GroupInfoTest, FolderTreeItemListingTest, MoveRequestTest, OID serialisation test. |
| src/test/resources/User.json, FolderTreeItemListing.json | New JSON fixtures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public class UserInfo extends IdentifiableNameable { | ||
|
|
||
| private String email; | ||
| private String firstName; | ||
| private String lastName; | ||
| private UserRole role; | ||
| private String affiliation; | ||
| private boolean enabled; | ||
| private Long homeFolderId; | ||
|
|
| **Immutable search results** use: `@Value @EqualsAndHashCode(callSuper=true)` — see note below | ||
|
|
||
| ### `@Value` vs `@Data` — know the difference | ||
|
|
||
| `@Value` generates an immutable class: all fields `final`, no setters, all-args constructor. | ||
| Jackson cannot deserialise into a `@Value` class without a custom deserialiser because | ||
| there are no setters and the all-args constructor requires every field by position. | ||
|
|
||
| **Only use `@Value` for classes where you are certain Jackson will never try to deserialise them.** | ||
|
|
||
| Current `@Value` classes: `ActivitySearchResult`, `DocumentSearchResult`, | ||
| `FileSearchResult`, `FormSearchResult`, `ShareSearchResult`. | ||
| These work because `AbstractModelTest.readFileToClass()` disables `FAIL_ON_UNKNOWN_PROPERTIES` | ||
| and the fields are initialised to `new ArrayList<>()` which Jackson appends to. | ||
| This is fragile — see IMPROVEMENT.md item 1.1 for the correct fix. | ||
|
|
||
| **`User.java` uses `@Value @NoArgsConstructor` which is a bug — see IMPROVEMENT.md item 1.3.** | ||
|
|
| private static final DateTimeFormatter FORMATTER = | ||
| DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.systemDefault()); |
|
|
||
| - **`AbstractExportPost`**: renamed `scope()` → `getScopeString()` and `format()` → `getFormatString()` to avoid confusion with Lombok-generated getters. Update any callers of the old method names. | ||
| - **`ApiShareInfo.sharedItemName`**: field type corrected from `Long` to `String`. Any code storing or passing `getSharedItemName()` as a `Long` must be updated. | ||
| - **`FieldPut`**: removed the shadowed `content` field; `FieldPut` now inherits `content` from `FieldPost`. The `@AllArgsConstructor`-generated constructor signature changes from `FieldPut(String content, Long id)` to `FieldPut(Long id)`. Replace any positional constructor calls with the no-args constructor + `setContent()`. |
| public class UserSearchResult extends PaginatedResultList { | ||
| private List<User> users = new ArrayList<>(); | ||
|
|
| @Data | ||
| @EqualsAndHashCode(callSuper = true) | ||
| @NoArgsConstructor | ||
| @JsonIgnoreProperties(ignoreUnknown = true) |
| // Server accepts 16–32 alphanumeric characters; verify exact constraint in com.researchspace.api.v1.model.ApiUserPost | ||
| @Size(min = 16, max = 32) |
| private Long totalHits = null; | ||
| private Long totalHits = null; | ||
| private Integer pageNumber = null; | ||
| private Integer pageSize = null; |
| @Data | ||
| @EqualsAndHashCode(callSuper=false) | ||
| @AllArgsConstructor | ||
| @NoArgsConstructor | ||
| /** | ||
| * Extends FieldPost with a field Id property to specify the Field whose content is to be updated. | ||
| * Extends FieldPost with a field Id property to specify the Field, whose content is to be updated. | ||
| * @author rspace | ||
| * | ||
| */ | ||
| public class FieldPut extends FieldPost { |
| MoveRequest deserialized = om.readValue(json, MoveRequest.class); | ||
| assertEquals(original, deserialized); |
- UserInfo: add missing username field + @tostring(callSuper=true) - UserInfoTest, UserSearchResultTest, GroupSearchResultTest: new deserialisation tests with JSON fixtures (UserInfo.json, UserSearchResult.json, GroupSearchResult.json) - ISO8601DateSerialiser: pin timezone to UTC (was systemDefault); add Javadoc explaining the UTC behaviour - FieldPut: move class Javadoc before annotations (was misplaced) - CHANGELOG: reword FieldPut breaking change — constructor preserved, describe actual shadow-removal semantics - DocumentSearchResult.json: add pageSize field; assert on it in test - AGENTS.md: update taxonomy table (search results -> @DaTa, User fixed, add UserInfo/UserSearchResult/GroupSearchResult); update Lombok rules section to remove stale @value guidance; add pageSize to PaginatedResultList inheritance note - UserPost: remove unverified 'alphanumeric' claim from apiKey comment - MoveRequestTest: fix British spelling (deserialized -> deserialised) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| @NoArgsConstructor | ||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||
| public class UserSearchResult extends PaginatedResultList { | ||
| private List<User> users = new ArrayList<>(); |
| private static final DateTimeFormatter FORMATTER = | ||
| DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneOffset.UTC); | ||
|
|
||
| public ISO8601DateSerialiser() { | ||
| this(null); | ||
| } | ||
|
|
||
| public ISO8601DateSerialiser(Class<Date> t) { | ||
| super(t); | ||
| } | ||
|
|
||
| @Override | ||
| public void serialize(Date value, JsonGenerator gen, SerializerProvider provider) | ||
| throws IOException { | ||
| gen.writeString(FORMATTER.format(value.toInstant())); |
| public class UserInfo extends IdentifiableNameable { | ||
|
|
||
| private String username; |
| void testUserInfoIgnoresUnknownFields() throws IOException { | ||
| UserInfo user = readFileToClass(fixture, UserInfo.class); | ||
| // fixture has unknown fields if added in future — @JsonIgnoreProperties ensures no exception | ||
| assertEquals("jsmith", user.getUsername()); | ||
| } |
| public void serialize(Date value, JsonGenerator gen, SerializerProvider provider) | ||
| throws IOException { | ||
| gen.writeString(FORMATTER.format(value.toInstant())); | ||
| } |
|
|
||
| private Long id; | ||
| private Long sharedItemId; | ||
| private String sharedItemName; |
| /** Constructor for updating a specific field by ID only (content left as default). */ | ||
| public FieldPut(Long id) { | ||
| this.id = id; | ||
| } | ||
|
|
||
| /** Constructor for updating the content of a specific field by ID. */ | ||
| public FieldPut(String content, Long id) { | ||
| super(content); | ||
| this.id = id; | ||
| } |
| "users": [ | ||
| { | ||
| "id": 1, | ||
| "username": "admin", | ||
| "email": "admin@example.com", | ||
| "firstName": "Admin", | ||
| "lastName": "User", | ||
| "homeFolderId": 5 | ||
| } | ||
| ], |
| public String getScopeString() { | ||
| return scope.name().toLowerCase(); | ||
| } | ||
| public String format () { | ||
|
|
||
| public String getFormatString() { | ||
| return format.name().toLowerCase(); | ||
| } |
| private String email; | ||
| private String firstName; | ||
| private String lastName; | ||
| private UserRole role; |
- AbstractExportPost: add @JsonIgnore to getScopeString()/getFormatString() so Jackson does not serialise them as unintended scopeString/formatString properties in export request payloads - UserSearchResult: change List<User> to List<UserInfo> to match the sysadmin endpoint response; update fixture and test accordingly - DocumentSearchResult.documents: add missing private modifier - FormSearchResult.forms: add missing private modifier - FieldPut: fix @EqualsAndHashCode(callSuper=false) → callSuper=true so inherited content field participates in equality checks - ShareSearchResult, FileSearchResult, FolderTreeItemListing: add @tostring(callSuper=true) for consistency with other PaginatedResultList subclasses - UserInfo.json: add role field (ROLE_USER) to exercise enum deserialisation - UserInfoTest: replace duplicate fixture-read test with a genuine unknown- fields test using inline JSON; add role assertion; add null-role test - UserSearchResult.json: update to UserInfo-shaped objects with all fields - UserSearchResultTest: assert UserInfo-specific fields including role - CHANGELOG: move User.java @value→@DaTa to Breaking Changes section; add ISO8601DateSerialiser UTC timezone change as a breaking change; remove User.java entry from Fixed section - AGENTS.md: remove stale IMPROVEMENT.md cross-repo references Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- UserInfo: clarify getName() vs getUsername() in Javadoc — name is the display name (from IdentifiableNameable), username is the login identifier - UserPost: remove unverifiable cross-repo reference from apiKey comment Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Breaking changes:
Added:
Fixed: