Skip to content

Feat: Add basic field validations to domain entities#61

Merged
pol-rivero merged 42 commits intoUdL-EPS-SoftArch-Igualada:mainfrom
MireiaTerri:main
Mar 11, 2026
Merged

Feat: Add basic field validations to domain entities#61
pol-rivero merged 42 commits intoUdL-EPS-SoftArch-Igualada:mainfrom
MireiaTerri:main

Conversation

@xertrec
Copy link
Contributor

@xertrec xertrec commented Mar 2, 2026

Closes #35

Copilot AI review requested due to automatic review settings March 2, 2026 21:33
@udl-softarch
Copy link

udl-softarch bot commented Mar 2, 2026

Thank you for your PR @xertrec! Now, you should wait for the automated code analysis by CodeRabbit, Copilot and SonarQube.
Please review all warnings carefully, as some of them might be false positives.

Once you are confident that you have fixed all the detected issues and this PR is ready to be merged, add a comment with exactly one word: ready.

@udl-softarch udl-softarch bot added the pr-not-ready This PR cannot be merged until you have reviewed the code analysis results. label Mar 2, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a DomainValidation utility and DomainValidationException; introduces validated public static create(...) factory methods and protected no‑arg constructors across many domain entities; restructures Team and TeamMember (relations and factories); updates services, tests, and step definitions to use the new factories.

Changes

Cohort / File(s) Summary
Validation infra
src/main/java/cat/udl/eps/softarch/fll/domain/DomainValidation.java, src/main/java/cat/udl/eps/softarch/fll/domain/DomainValidationException.java
New non-instantiable validator utility and runtime exception with requireNonBlank, requireValidEmail, numeric/date checks and related helpers used by domain factories.
Entity factories & protected ctors
src/main/java/cat/udl/eps/softarch/fll/domain/{Award.java,Coach.java,CompetitionTable.java,Edition.java,Floater.java,MatchResult.java,MediaContent.java,Record.java,Referee.java,ScientificProject.java,Venue.java,Judge.java}
Added public static create(...) factory methods with DomainValidation checks; many classes now have protected no‑arg constructors (via Lombok or explicit). Import reorderings and minor formatting.
Team model & membership
src/main/java/cat/udl/eps/softarch/fll/domain/{Team.java,TeamMember.java}
Team: replaced previous ctor with protected no‑arg, added Team.create(...), added many‑to‑many sets trainedBy and floaters, reintroduced getId(); TeamMember: added TeamMember.create(...), protected ctor, and restored getId().
Volunteer helpers
src/main/java/cat/udl/eps/softarch/fll/domain/Volunteer.java
Added protected no‑arg constructor, moved setters to field level for some fields, added validateFields(...) and initFields(...), and explicit setPhoneNumber with validation.
User security & factories
src/main/java/cat/udl/eps/softarch/fll/domain/User.java
Added User.create(id,email,password) with DomainValidation checks and password encoding; implemented UserDetails-related methods (username, authorities, account flags).
Service call-site updates
src/main/java/cat/udl/eps/softarch/fll/service/MatchScoreRegistrationService.java
Replaced manual MatchResult construction with MatchResult.create(...) in build paths; formatting/indentation adjustments only.
Domain tests
src/test/java/cat/udl/eps/softarch/fll/domain/*ValidationTest.java
Added ~15 JUnit5 test classes covering valid and invalid construction cases for new factories and DomainValidation rules (null/blank, invalid email, negative numbers, required refs).
Tests & stepdefs updated to factories
src/test/java/cat/udl/eps/softarch/fll/{steps,service,controller,...}/*
Replaced many direct new + setters in tests/stepdefs with factory calls (Team.create, TeamMember.create, Edition.create, MediaContent.create, MatchResult.create, Referee.create, Judge.create, Floater.create); some step defs now inject repositories and mark fields final.
Misc edits
various src/main/java/... and src/test/java/...
Minor import reordering, Lombok annotation adjustments, visibility tweaks, and formatting changes across domain and test files to support factories and protected ctors.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding basic field validations to domain entities, which matches the primary objective of the PR.
Description check ✅ Passed The description is minimal but related to the changeset; it references issue #35 which defines the validation requirements being implemented.
Linked Issues check ✅ Passed The PR implements all core requirements from issue #35: adds validation in domain layer via DomainValidation and DomainValidationException, creates factory methods for controlled instantiation, covers NULL_ID, EMPTY_NAME, INVALID_EMAIL, NEGATIVE_SCORE, and NULL_REQUIRED_REFERENCE cases with comprehensive unit tests.
Out of Scope Changes check ✅ Passed All changes align with issue #35 requirements: domain validation implementation, factory methods for entities, and test coverage. No unrelated refactoring or scope creep detected beyond the validation feature.
Code Style ✅ Passed All code identifiers are in English with self-documenting names; validation methods follow simple patterns with appropriate indentation levels; no forbidden inline comments detected.
Has Tests ✅ Passed PR contains comprehensive validation tests covering all five key error scenarios (NULL_ID, EMPTY_NAME, INVALID_EMAIL, NEGATIVE_SCORE, NULL_REQUIRED_REFERENCE) across 15 domain entities with both positive and negative test cases.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/test/java/cat/udl/eps/softarch/fll/steps/TeamSteps.java (1)

43-47: 🧹 Nitpick | 🔵 Trivial

Prefer full Team.create(...) to avoid transient partially-initialized teams.

Using the single-arg factory and then setters weakens the “validate at creation time” goal. Use the 4-arg factory directly in these flows.

♻️ Proposed refactor
- currentTeam = Team.create(name);
- currentTeam.setCity(city);
- currentTeam.setFoundationYear(2000);
- currentTeam.setCategory("Challenge");
+ currentTeam = Team.create(name, city, 2000, "Challenge");
- Team team = Team.create(name);
- team.setCity(city);
- team.setFoundationYear(2000);
- team.setCategory("Challenge");
+ Team team = Team.create(name, city, 2000, "Challenge");
- currentTeam = Team.create(name);
- currentTeam.setCity("Igualada");
- currentTeam.setFoundationYear(2000);
- currentTeam.setCategory("Challenge");
+ currentTeam = Team.create(name, "Igualada", 2000, "Challenge");

Also applies to: 78-82, 95-99


ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37b58df and da735a9.

📒 Files selected for processing (34)
  • src/main/java/cat/udl/eps/softarch/fll/domain/Award.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Coach.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/CompetitionTable.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/DomainValidation.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/DomainValidationException.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Edition.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Floater.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/MatchResult.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/MediaContent.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Record.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Referee.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/ScientificProject.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Team.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/TeamMember.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/User.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Venue.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Volunteer.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/AwardValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/CoachValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/CompetitionTableValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/DomainValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/EditionValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/FloaterValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/MatchResultValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/MediaContentValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/RecordValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/RefereeValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/ScientificProjectValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/TeamMemberValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/TeamValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/UserValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/VenueValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/steps/TeamSteps.java
  • src/test/java/cat/udl/eps/softarch/fll/steps/VolunteerStepDefs.java

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements creation-time domain validations to prevent constructing invalid domain entities, aligning with issue #35’s goal of enforcing domain invariants in the domain layer.

Changes:

  • Introduces DomainValidation + DomainValidationException and uses them from new create(...) factory methods across multiple domain entities.
  • Updates Cucumber step definitions to use the new factories (e.g., Team.create(...)) instead of constructors.
  • Adds unit tests covering valid construction and expected validation failures for multiple domain entities and the validation helpers.

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/test/java/cat/udl/eps/softarch/fll/steps/VolunteerStepDefs.java Updates team creation to use Team.create(...) in step defs.
src/test/java/cat/udl/eps/softarch/fll/steps/TeamSteps.java Updates team creation to use Team.create(...) in step defs.
src/test/java/cat/udl/eps/softarch/fll/domain/VenueValidationTest.java Adds validation tests for Venue.create(...).
src/test/java/cat/udl/eps/softarch/fll/domain/UserValidationTest.java Adds validation tests for User.create(...).
src/test/java/cat/udl/eps/softarch/fll/domain/TeamValidationTest.java Adds validation tests for Team.create(...).
src/test/java/cat/udl/eps/softarch/fll/domain/TeamMemberValidationTest.java Adds validation tests for TeamMember.create(...).
src/test/java/cat/udl/eps/softarch/fll/domain/ScientificProjectValidationTest.java Adds validation tests for ScientificProject.create(...).
src/test/java/cat/udl/eps/softarch/fll/domain/RefereeValidationTest.java Adds validation tests for Referee.create(...).
src/test/java/cat/udl/eps/softarch/fll/domain/RecordValidationTest.java Adds validation tests for Record.create(...).
src/test/java/cat/udl/eps/softarch/fll/domain/MediaContentValidationTest.java Adds validation tests for MediaContent.create(...).
src/test/java/cat/udl/eps/softarch/fll/domain/MatchResultValidationTest.java Adds validation tests for MatchResult.create(...).
src/test/java/cat/udl/eps/softarch/fll/domain/FloaterValidationTest.java Adds validation tests for Floater.create(...).
src/test/java/cat/udl/eps/softarch/fll/domain/EditionValidationTest.java Adds validation tests for Edition.create(...).
src/test/java/cat/udl/eps/softarch/fll/domain/DomainValidationTest.java Adds tests for the DomainValidation helper methods.
src/test/java/cat/udl/eps/softarch/fll/domain/CompetitionTableValidationTest.java Adds validation tests for CompetitionTable.create(...).
src/test/java/cat/udl/eps/softarch/fll/domain/CoachValidationTest.java Adds validation tests for Coach.create(...).
src/test/java/cat/udl/eps/softarch/fll/domain/AwardValidationTest.java Adds validation tests for Award.create(...).
src/main/java/cat/udl/eps/softarch/fll/domain/Volunteer.java Centralizes volunteer field validation/initialization for subclasses.
src/main/java/cat/udl/eps/softarch/fll/domain/Venue.java Adds Venue.create(...) with domain validations.
src/main/java/cat/udl/eps/softarch/fll/domain/User.java Adds User.create(...) with domain validations.
src/main/java/cat/udl/eps/softarch/fll/domain/TeamMember.java Adds TeamMember.create(...) with domain validations.
src/main/java/cat/udl/eps/softarch/fll/domain/Team.java Adds Team.create(...) factory overloads with domain validations and refactors mappings placement.
src/main/java/cat/udl/eps/softarch/fll/domain/ScientificProject.java Adds ScientificProject.create(...) with domain validations.
src/main/java/cat/udl/eps/softarch/fll/domain/Referee.java Adds Referee.create(...) leveraging shared volunteer initialization.
src/main/java/cat/udl/eps/softarch/fll/domain/Record.java Adds Record.create(...) with domain validations.
src/main/java/cat/udl/eps/softarch/fll/domain/MediaContent.java Adds MediaContent.create(...) with domain validations.
src/main/java/cat/udl/eps/softarch/fll/domain/MatchResult.java Adds MatchResult.create(...) with domain validations.
src/main/java/cat/udl/eps/softarch/fll/domain/Floater.java Adds Floater.create(...) leveraging shared volunteer initialization.
src/main/java/cat/udl/eps/softarch/fll/domain/Edition.java Adds Edition.create(...) with domain validations.
src/main/java/cat/udl/eps/softarch/fll/domain/DomainValidationException.java Introduces a dedicated exception type for domain validation failures.
src/main/java/cat/udl/eps/softarch/fll/domain/DomainValidation.java Introduces reusable validation helpers (non-blank, email, non-negative, non-null).
src/main/java/cat/udl/eps/softarch/fll/domain/CompetitionTable.java Adds CompetitionTable.create(...) with domain validations.
src/main/java/cat/udl/eps/softarch/fll/domain/Coach.java Adds Coach.create(...) with domain validations.
src/main/java/cat/udl/eps/softarch/fll/domain/Award.java Adds Award.create(...) with domain validations.

Copilot AI review requested due to automatic review settings March 3, 2026 16:28
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 34 out of 34 changed files in this pull request and generated 7 comments.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

♻️ Duplicate comments (1)
src/main/java/cat/udl/eps/softarch/fll/domain/Venue.java (1)

34-42: ⚠️ Potential issue | 🟠 Major

Factory validation is bypassable while implicit public construction remains.

Venue.create(...) validates correctly, but invalid instances can still be created directly via implicit new Venue() (no explicit protected constructor). This still violates strict creation-time invariants.

🔧 Proposed fix
 public class Venue {
+	protected Venue() {
+	}
 
 	`@Id`
 	`@GeneratedValue`(strategy = GenerationType.IDENTITY)

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e3a058 and 39cdd65.

📒 Files selected for processing (20)
  • src/main/java/cat/udl/eps/softarch/fll/domain/Award.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Coach.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/CompetitionTable.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/DomainValidation.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Floater.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Record.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Referee.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/ScientificProject.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Team.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/TeamMember.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/User.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Venue.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/AwardValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/MatchResultValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/MediaContentValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/TeamMemberValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/TeamValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/VenueValidationTest.java
  • src/test/java/cat/udl/eps/softarch/fll/steps/TeamSteps.java
  • src/test/java/cat/udl/eps/softarch/fll/steps/VolunteerStepDefs.java

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (6)
src/main/java/cat/udl/eps/softarch/fll/domain/ScientificProject.java (1)

27-33: ⚠️ Potential issue | 🔴 Critical

Factory validation can still be bypassed by direct instantiation.

create(...) validates correctly, but the class still has an implicit public no-arg constructor, so invalid objects can be created with new ScientificProject().

🔒 Proposed fix
 public class ScientificProject extends UriEntity<Long> {
+	protected ScientificProject() {
+	}
 
 	`@Id`
src/main/java/cat/udl/eps/softarch/fll/domain/CompetitionTable.java (1)

27-32: ⚠️ Potential issue | 🔴 Critical

Creation invariants are bypassable due implicit public constructor.

create(id) enforces non-blank id, but callers can still bypass it using new CompetitionTable().

🔒 Proposed fix
 public class CompetitionTable extends UriEntity<String> {
+	protected CompetitionTable() {
+	}
 
 	`@Id`
src/main/java/cat/udl/eps/softarch/fll/domain/Coach.java (1)

41-49: ⚠️ Potential issue | 🔴 Critical

Factory guarantees are bypassable because constructor remains public by default.

Coach.create(...) validates correctly, but new Coach() still allows invalid instances before persistence-time validation.

🔒 Proposed fix
 public class Coach extends UriEntity<Integer> {
+	protected Coach() {
+	}
 
 	`@Id`
src/main/java/cat/udl/eps/softarch/fll/domain/Floater.java (1)

30-37: ⚠️ Potential issue | 🔴 Critical

Factory validations can still be bypassed through direct construction.

Floater.create(...) is validated, but an implicit public no-arg constructor still permits unvalidated new Floater() instances.

🔒 Proposed fix
 public class Floater extends Volunteer {
+	protected Floater() {
+	}
 
 	`@NotBlank`(message = "Student code is mandatory")
src/main/java/cat/udl/eps/softarch/fll/domain/User.java (1)

41-51: ⚠️ Potential issue | 🔴 Critical

Validated factory remains bypassable due implicit public constructor.

Even with create(...), callers can still instantiate User directly with new User(), violating creation-time invariant enforcement.

🔒 Proposed fix
 public class User extends UriEntity<String> implements UserDetails {
+	protected User() {
+	}
 
 	public static final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
src/main/java/cat/udl/eps/softarch/fll/domain/Record.java (1)

39-45: ⚠️ Potential issue | 🔴 Critical

Protected constructor required to enforce validated object creation.

Record.create(...) validates name, but Lombok's @Data annotation generates a public no-arg constructor that bypasses validation. Direct instantiation like new Record() is currently possible (as evidenced in DBInitialization.java), allowing entities to violate the @NotBlank constraint on the name field.

🔒 Proposed fix
 public class Record extends UriEntity<Long> {
+	protected Record() {
+	}
 
 	`@Id`

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39cdd65 and c0d85bf.

📒 Files selected for processing (10)
  • src/main/java/cat/udl/eps/softarch/fll/domain/Coach.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/CompetitionTable.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/DomainValidation.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Floater.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Record.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/ScientificProject.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/Team.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/TeamMember.java
  • src/main/java/cat/udl/eps/softarch/fll/domain/User.java
  • src/test/java/cat/udl/eps/softarch/fll/domain/TeamMemberValidationTest.java

Copilot AI review requested due to automatic review settings March 3, 2026 17:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 54 out of 54 changed files in this pull request and generated 2 comments.

Comment on lines +1 to +8
package cat.udl.eps.softarch.fll.domain;

public class DomainValidationException extends RuntimeException {

public DomainValidationException(String message) {
super(message);
}
}
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already a cat.udl.eps.softarch.fll.exception.DomainValidationException (with an error code) and a DomainValidationExceptionHandler that catches that type. Introducing a second DomainValidationException in the domain package means exceptions thrown by DomainValidation won’t be handled consistently (and may surface as 500s) and creates ambiguous imports. Prefer using a single exception type (reuse the existing one, or update the handler + callers to use this one) and keep a structured error code as required by #35.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +80
private DomainValidation() {
}

public static void requireNonBlank(String value, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
}
if (value.isBlank()) {
throw new DomainValidationException(fieldName + " must not be blank");
}
}

public static void requireValidEmail(String email, String fieldName) {
requireNonBlank(email, fieldName);
if (!EMAIL_PATTERN.matcher(email).matches()) {
throw new DomainValidationException(fieldName + " must be a valid email address");
}
}

public static void requireNonNegative(Integer value, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
}
if (value < 0) {
throw new DomainValidationException(fieldName + " must not be negative");
}
}

public static void requireMin(Integer value, Integer minValue, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
}
if (value < minValue) {
throw new DomainValidationException(fieldName + " must not be less than " + minValue);
}
}

public static void requirePast(LocalDate value, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
}
if (!value.isBefore(LocalDate.now())) {
throw new DomainValidationException(fieldName + " must be in the past");
}
}

public static void requireLengthBetween(String value, Integer minLength, Integer maxLength, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
}
if (value.length() < minLength) {
throw new DomainValidationException(fieldName + " length must not be less than " + minLength);
}
if (value.length() > maxLength) {
throw new DomainValidationException(fieldName + " length must not be more than " + maxLength);
}
}

public static void requireMaxLength(String value, Integer maxLength, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
}
if (value.length() > maxLength) {
throw new DomainValidationException(fieldName + " length must not be more than " + maxLength);
}
}

public static void requireNonNull(Object value, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DomainValidation currently throws a message-only DomainValidationException (e.g., "name must not be blank"). Issue #35 asks for specific validation error categories (e.g., EMPTY_NAME, NULL_ID, NEGATIVE_SCORE), and the codebase already has a coded cat.udl.eps.softarch.fll.exception.DomainValidationException. Consider throwing a coded exception (or adding a code field here) so callers/controllers can reliably distinguish validation failures without parsing messages.

Suggested change
private DomainValidation() {
}
public static void requireNonBlank(String value, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
}
if (value.isBlank()) {
throw new DomainValidationException(fieldName + " must not be blank");
}
}
public static void requireValidEmail(String email, String fieldName) {
requireNonBlank(email, fieldName);
if (!EMAIL_PATTERN.matcher(email).matches()) {
throw new DomainValidationException(fieldName + " must be a valid email address");
}
}
public static void requireNonNegative(Integer value, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
}
if (value < 0) {
throw new DomainValidationException(fieldName + " must not be negative");
}
}
public static void requireMin(Integer value, Integer minValue, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
}
if (value < minValue) {
throw new DomainValidationException(fieldName + " must not be less than " + minValue);
}
}
public static void requirePast(LocalDate value, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
}
if (!value.isBefore(LocalDate.now())) {
throw new DomainValidationException(fieldName + " must be in the past");
}
}
public static void requireLengthBetween(String value, Integer minLength, Integer maxLength, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
}
if (value.length() < minLength) {
throw new DomainValidationException(fieldName + " length must not be less than " + minLength);
}
if (value.length() > maxLength) {
throw new DomainValidationException(fieldName + " length must not be more than " + maxLength);
}
}
public static void requireMaxLength(String value, Integer maxLength, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
}
if (value.length() > maxLength) {
throw new DomainValidationException(fieldName + " length must not be more than " + maxLength);
}
}
public static void requireNonNull(Object value, String fieldName) {
if (value == null) {
throw new DomainValidationException(fieldName + " must not be null");
private static final String ERROR_CODE_NULL_VALUE = "NULL_VALUE";
private static final String ERROR_CODE_INVALID_EMAIL = "INVALID_EMAIL";
private static final String ERROR_CODE_NEGATIVE_VALUE = "NEGATIVE_VALUE";
private static final String ERROR_CODE_BELOW_MIN_VALUE = "BELOW_MIN_VALUE";
private static final String ERROR_CODE_VALUE_NOT_IN_PAST = "VALUE_NOT_IN_PAST";
private static final String ERROR_CODE_STRING_TOO_SHORT = "STRING_TOO_SHORT";
private static final String ERROR_CODE_STRING_TOO_LONG = "STRING_TOO_LONG";
private static final String ERROR_CODE_EXCEEDS_MAX_LENGTH = "EXCEEDS_MAX_LENGTH";
private static final String ERROR_CODE_NULL_OBJECT = "NULL_OBJECT";
private DomainValidation() {
}
private static void throwValidationException(String errorCode, String message) {
throw new DomainValidationException("[" + errorCode + "] " + message);
}
public static void requireNonBlank(String value, String fieldName) {
if (value == null) {
throwValidationException(ERROR_CODE_NULL_VALUE, fieldName + " must not be null");
}
if (value.isBlank()) {
throwValidationException(ERROR_CODE_STRING_TOO_SHORT, fieldName + " must not be blank");
}
}
public static void requireValidEmail(String email, String fieldName) {
requireNonBlank(email, fieldName);
if (!EMAIL_PATTERN.matcher(email).matches()) {
throwValidationException(ERROR_CODE_INVALID_EMAIL, fieldName + " must be a valid email address");
}
}
public static void requireNonNegative(Integer value, String fieldName) {
if (value == null) {
throwValidationException(ERROR_CODE_NULL_VALUE, fieldName + " must not be null");
}
if (value < 0) {
throwValidationException(ERROR_CODE_NEGATIVE_VALUE, fieldName + " must not be negative");
}
}
public static void requireMin(Integer value, Integer minValue, String fieldName) {
if (value == null) {
throwValidationException(ERROR_CODE_NULL_VALUE, fieldName + " must not be null");
}
if (value < minValue) {
throwValidationException(ERROR_CODE_BELOW_MIN_VALUE, fieldName + " must not be less than " + minValue);
}
}
public static void requirePast(LocalDate value, String fieldName) {
if (value == null) {
throwValidationException(ERROR_CODE_NULL_VALUE, fieldName + " must not be null");
}
if (!value.isBefore(LocalDate.now())) {
throwValidationException(ERROR_CODE_VALUE_NOT_IN_PAST, fieldName + " must be in the past");
}
}
public static void requireLengthBetween(String value, Integer minLength, Integer maxLength, String fieldName) {
if (value == null) {
throwValidationException(ERROR_CODE_NULL_VALUE, fieldName + " must not be null");
}
if (value.length() < minLength) {
throwValidationException(ERROR_CODE_STRING_TOO_SHORT, fieldName + " length must not be less than " + minLength);
}
if (value.length() > maxLength) {
throwValidationException(ERROR_CODE_STRING_TOO_LONG, fieldName + " length must not be more than " + maxLength);
}
}
public static void requireMaxLength(String value, Integer maxLength, String fieldName) {
if (value == null) {
throwValidationException(ERROR_CODE_NULL_VALUE, fieldName + " must not be null");
}
if (value.length() > maxLength) {
throwValidationException(ERROR_CODE_EXCEEDS_MAX_LENGTH, fieldName + " length must not be more than " + maxLength);
}
}
public static void requireNonNull(Object value, String fieldName) {
if (value == null) {
throwValidationException(ERROR_CODE_NULL_OBJECT, fieldName + " must not be null");

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings March 9, 2026 16:57
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 54 out of 54 changed files in this pull request and generated 3 comments.

Comment on lines +65 to +72
@ManyToMany
@JoinTable(
name = "team_coach",
joinColumns = @JoinColumn(name = "team_name"),
inverseJoinColumns = @JoinColumn(name = "coach_id"))
@ToString.Exclude
@Setter(AccessLevel.NONE)
private Set<Coach> trainedBy = new HashSet<>();
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trainedBy is declared twice in Team (also appears again later in the file), which will cause a compilation error and duplicate JPA mappings. Remove one of the declarations and keep a single @ManyToMany mapping for coaches (including the intended @Setter(AccessLevel.NONE) if you want to prevent external mutation).

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +80
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinTable(
name = "team_floaters",
joinColumns = @JoinColumn(name = "team_name"),
inverseJoinColumns = @JoinColumn(name = "floater_id"))
@ToString.Exclude
@Setter(AccessLevel.NONE)
private Set<Floater> floaters = new HashSet<>();
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

floaters is also declared twice in Team (it’s redefined again later with the same @JoinTable), which will not compile and creates ambiguous ORM mappings. Keep a single floaters field/mapping and delete the duplicate definition.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +42
public static ScientificProject create(Integer score) {
DomainValidation.requireNonNegative(score, "score");

ScientificProject project = new ScientificProject();
project.score = score;
return project;
}
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ScientificProject.create(Integer score) returns an instance with team and edition left null, even though both associations are annotated @NotNull/nullable=false. This allows creation of an invalid domain entity and contradicts the invariant-at-creation goal; consider requiring Team team and Edition edition in the factory (or otherwise ensuring these required references are set/validated before returning).

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings March 9, 2026 17:11
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 58 out of 58 changed files in this pull request and generated 5 comments.

Comment on lines +41 to +49
public static Coach create(String name, String emailAddress) {
DomainValidation.requireNonBlank(name, "name");
DomainValidation.requireValidEmail(emailAddress, "emailAddress");

Coach coach = new Coach();
coach.name = name;
coach.emailAddress = emailAddress;
return coach;
}
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coach.create(...) adds validation, but the class still has an implicit public no-arg constructor plus Lombok-generated setters (@Data), so callers can still instantiate new Coach() with null/blank required fields and bypass domain invariants. If the goal is to prevent invalid entities from being created, consider making the no-arg constructor protected (for JPA only) and validating required setters (or removing the setters for required fields).

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +62
public static Award create(String name, Edition edition, Team winner) {
DomainValidation.requireNonBlank(name, "name");
DomainValidation.requireNonNull(edition, "edition");
DomainValidation.requireNonNull(winner, "winner");

Award award = new Award();
award.name = name;
award.edition = edition;
award.winner = winner;
return award;
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Award.create(...) validates inputs, but Award still has a public no-arg constructor and Lombok-generated setters via @Data, so invalid awards (blank name, null edition/winner) can still be instantiated and mutated directly, bypassing the new validation. To fully enforce the invariant at creation time, consider making the no-arg constructor protected and validating/removing setters for mandatory fields.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +31
public static CompetitionTable create(String id) {
DomainValidation.requireNonBlank(id, "id");
CompetitionTable table = new CompetitionTable();
table.id = id;
return table;
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding CompetitionTable.create(...) introduces validation, but CompetitionTable still has a public no-arg constructor and an unvalidated setId(...) method, so new CompetitionTable() / setId(null|blank) can still create an invalid entity. To align with the domain-invariant goal, consider making the constructor protected and validating/removing setId (or routing it through DomainValidation.requireNonBlank).

Copilot uses AI. Check for mistakes.
Comment on lines +44 to 45
Judge judge = Judge.create("Test Judge " + judgeAlias, "judge_" + Random.from(RandomGenerator.getDefault()).nextFloat() + "@test.com", "123456789");
judge = judgeRepository.save(judge);
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The email generation using Random.from(RandomGenerator.getDefault()).nextFloat() is relatively low-entropy and can collide across runs (and makes failures hard to reproduce), which can lead to flaky tests when emailAddress has a uniqueness constraint. Prefer using a UUID (as before) or System.nanoTime() to guarantee uniqueness.

Copilot uses AI. Check for mistakes.
@xertrec
Copy link
Contributor Author

xertrec commented Mar 9, 2026

ready

@udl-softarch udl-softarch bot removed the pr-not-ready This PR cannot be merged until you have reviewed the code analysis results. label Mar 9, 2026
@udl-softarch udl-softarch bot requested a review from pol-rivero March 9, 2026 17:33
@udl-softarch
Copy link

udl-softarch bot commented Mar 9, 2026

This PR is now marked as ready to be merged.

@pol-rivero
Copy link
Member

@xertrec Please fix the merge conflict

# Conflicts:
#	src/main/java/cat/udl/eps/softarch/fll/domain/CompetitionTable.java
#	src/main/java/cat/udl/eps/softarch/fll/domain/Edition.java
#	src/main/java/cat/udl/eps/softarch/fll/domain/Match.java
#	src/main/java/cat/udl/eps/softarch/fll/domain/Referee.java
#	src/main/java/cat/udl/eps/softarch/fll/domain/Round.java
#	src/main/java/cat/udl/eps/softarch/fll/service/MatchScoreRegistrationService.java
#	src/test/java/cat/udl/eps/softarch/fll/controller/MatchAssignmentControllerTest.java
#	src/test/java/cat/udl/eps/softarch/fll/service/EditionVolunteerServiceTest.java
#	src/test/java/cat/udl/eps/softarch/fll/steps/ManageVenueStepDefs.java
#	src/test/java/cat/udl/eps/softarch/fll/steps/MatchScoreRegistrationStepDefs.java
#	src/test/java/cat/udl/eps/softarch/fll/steps/ProjectRoomSteps.java
#	src/test/java/cat/udl/eps/softarch/fll/steps/TestCompetitionTable.java
#	src/test/java/cat/udl/eps/softarch/fll/steps/TestReferee.java
Copilot AI review requested due to automatic review settings March 10, 2026 22:07
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 61 out of 61 changed files in this pull request and generated 5 comments.

@sonarqubecloud
Copy link

@pol-rivero
Copy link
Member

LGTM

@pol-rivero pol-rivero merged commit bacdf43 into UdL-EPS-SoftArch-Igualada:main Mar 11, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat]: Add basic field validations to domain entities

3 participants