Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6b5f16f
harmonise exceptions
benrejebmoh Sep 22, 2025
165632b
simplify exception handling by delegating the work to powsybl-ws-commons
benrejebmoh Oct 10, 2025
74fccc8
fix legacy unit tests and cover new code
benrejebmoh Oct 10, 2025
981e9e9
fix license header
benrejebmoh Oct 13, 2025
1092156
adapt to the replacement in powsybl-ws-commons of unnecessary Records…
benrejebmoh Oct 15, 2025
5d62d66
adapt unit tests after changes in powsybl-ws-commons
benrejebmoh Oct 16, 2025
1732aa2
revert because ignoring exception was done in purpose
benrejebmoh Oct 20, 2025
df42381
remove optional from business code
benrejebmoh Oct 20, 2025
7937da9
refactor with shorter handlers in powsyble-ws-commons
benrejebmoh Oct 21, 2025
0107aa6
regroup error handling classes into a separate package
benrejebmoh Oct 21, 2025
1e44097
use DIRECTORY_SOME_ELEMENTS_ARE_MISSING for strict mode
benrejebmoh Oct 21, 2025
f812889
Update SNAPSHOT version to v2.25.0
gridsuite-actions[bot] Oct 1, 2025
0d74ba1
Homogenize permissions checks (#215)
Meklo Oct 10, 2025
e589b7e
Update SNAPSHOT version to v2.26.0
gridsuite-actions[bot] Oct 21, 2025
10c0356
Merge remote-tracking branch 'origin/main' into poc-harmonise-exceptions
TheMaskedTurtle Oct 21, 2025
018ad47
fix: throw typed exception instead of runtime
TheMaskedTurtle Oct 21, 2025
98df2d4
resolve build issue
benrejebmoh Oct 21, 2025
addc85a
upgrade powsybl-ws-commons to 1.30.0 and use spring.application.name …
benrejebmoh Oct 23, 2025
da9c6f4
Merge remote-tracking branch 'origin/main' into poc-harmonise-exceptions
benrejebmoh Oct 24, 2025
cf07bb2
resolve conflicts with main
benrejebmoh Oct 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@

<dependencyManagement>
<dependencies>
<!-- TODO remove when gridsuite-dependencies is released with this version -->
<dependency>
<groupId>com.powsybl</groupId>
<artifactId>powsybl-ws-commons</artifactId>
<version>1.30.0</version>
</dependency>
<!-- imports -->
<dependency>
<groupId>org.gridsuite</groupId>
Expand Down

This file was deleted.

311 changes: 155 additions & 156 deletions src/main/java/org/gridsuite/directory/server/DirectoryService.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2025, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.directory.server;

import com.powsybl.ws.commons.error.ServerNameProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
* @author Mohamed Ben-rejeb {@literal <mohamed.ben-rejeb at rte-france.com>}
*/
@Component
public class PropertyServerNameProvider implements ServerNameProvider {

private final String name;

public PropertyServerNameProvider(@Value("${spring.application.name:directory-server}") String name) {
this.name = name;
}

@Override
public String serverName() {
return name;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2025, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.directory.server.error;

import com.powsybl.ws.commons.error.BusinessErrorCode;

/**
* @author Mohamed Ben-rejeb {@literal <mohamed.ben-rejeb at rte-france.com>}
*
* Business error codes emitted by the directory service.
*/
public enum DirectoryBusinessErrorCode implements BusinessErrorCode {
DIRECTORY_PERMISSION_DENIED("directory.permissionDenied"),
DIRECTORY_ELEMENT_NAME_BLANK("directory.elementNameBlank"),
DIRECTORY_NOT_DIRECTORY("directory.notDirectory"),
DIRECTORY_ELEMENT_NAME_CONFLICT("directory.elementNameConflict"),
DIRECTORY_MOVE_IN_DESCENDANT_NOT_ALLOWED("directory.moveInDescendantNotAllowed"),
DIRECTORY_SOME_ELEMENTS_ARE_MISSING("directory.someElementsAreMissing"),
DIRECTORY_ELEMENT_NOT_FOUND("directory.elementNotFound");
private final String code;

DirectoryBusinessErrorCode(String code) {
this.code = code;
}

public String value() {
return code;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2021, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.directory.server.error;

import com.powsybl.ws.commons.error.AbstractBusinessException;
import lombok.NonNull;
import org.jetbrains.annotations.NotNull;

import java.util.Objects;
import java.util.UUID;

/**
* @author Abdelsalem Hedhili <abdelsalem.hedhili at rte-france.com>
* @author Mohamed Ben-rejeb {@literal <mohamed.ben-rejeb at rte-france.com>}
*/
public class DirectoryException extends AbstractBusinessException {

private final DirectoryBusinessErrorCode errorCode;

public DirectoryException(DirectoryBusinessErrorCode errorCode, String message) {
super(Objects.requireNonNull(message, "message must not be null"));
this.errorCode = Objects.requireNonNull(errorCode, "errorCode must not be null");
}

public static DirectoryException createElementNotFound(@NonNull String type, @NonNull UUID uuid) {
return new DirectoryException(DirectoryBusinessErrorCode.DIRECTORY_ELEMENT_NOT_FOUND,
String.format("%s '%s' not found !", type, uuid));
}

public static DirectoryException createElementNameAlreadyExists(@NonNull String name) {
return new DirectoryException(DirectoryBusinessErrorCode.DIRECTORY_ELEMENT_NAME_CONFLICT,
String.format("Element with the same name '%s' already exists in the directory !", name));
}

public static DirectoryException of(DirectoryBusinessErrorCode errorCode, String message, Object... args) {
return new DirectoryException(errorCode, args.length == 0 ? message : String.format(message, args));
}

@NotNull
@Override
public DirectoryBusinessErrorCode getBusinessErrorCode() {
return errorCode;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Copyright (c) 2021, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.directory.server.error;

import com.powsybl.ws.commons.error.AbstractBaseRestExceptionHandler;
import com.powsybl.ws.commons.error.ServerNameProvider;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;

/**
* @author Abdelsalem Hedhili <abdelsalem.hedhili at rte-france.com>
* @author Mohamed Ben-rejeb {@literal <mohamed.ben-rejeb at rte-france.com>}
*/
@ControllerAdvice
public class RestResponseEntityExceptionHandler
extends AbstractBaseRestExceptionHandler<DirectoryException, DirectoryBusinessErrorCode> {

public RestResponseEntityExceptionHandler(ServerNameProvider serverNameProvider) {
super(serverNameProvider);
}

@NotNull
@Override
protected DirectoryBusinessErrorCode getBusinessCode(DirectoryException ex) {
return ex.getBusinessErrorCode();
}

@Override
protected HttpStatus mapStatus(DirectoryBusinessErrorCode errorCode) {
return switch (errorCode) {
case DIRECTORY_ELEMENT_NOT_FOUND, DIRECTORY_SOME_ELEMENTS_ARE_MISSING -> HttpStatus.NOT_FOUND;
case DIRECTORY_ELEMENT_NAME_CONFLICT -> HttpStatus.CONFLICT;
case DIRECTORY_PERMISSION_DENIED,
DIRECTORY_ELEMENT_NAME_BLANK,
DIRECTORY_NOT_DIRECTORY,
DIRECTORY_MOVE_IN_DESCENDANT_NOT_ALLOWED -> HttpStatus.FORBIDDEN;
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ public void testElements() throws Exception {
public void testExistence() throws Exception {
// Insert root directory with same name not allowed
UUID rootUuid1 = insertRootDirectory("user1", "root1");
insertRootDirectory("user1", "root1", HttpStatus.FORBIDDEN);
insertRootDirectory("user1", "root1", HttpStatus.CONFLICT);

// Insert elements with same name in a directory not allowed
UUID dirUuid1 = insertSubElement(rootUuid1, toElementAttributes(null, "dir1", DIRECTORY, "user1"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2025, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.directory.server;

import org.gridsuite.directory.server.error.DirectoryBusinessErrorCode;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import static org.assertj.core.api.Assertions.assertThat;

/**
* @author Mohamed Ben-rejeb {@literal <mohamed.ben-rejeb at rte-france.com>}
*/
class DirectoryBusinessErrorCodeTest {

@ParameterizedTest
@EnumSource(DirectoryBusinessErrorCode.class)
void valueMatchesEnumName(DirectoryBusinessErrorCode code) {
assertThat(code.value()).startsWith("directory.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2025, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.directory.server;

import org.gridsuite.directory.server.error.DirectoryBusinessErrorCode;
import org.gridsuite.directory.server.error.DirectoryException;
import org.junit.jupiter.api.Test;

import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

/**
* @author Mohamed Ben-rejeb {@literal <mohamed.ben-rejeb at rte-france.com>}
*/
class DirectoryExceptionTest {

@Test
void staticFactoriesProduceExpectedMessages() {
DirectoryException notFound = DirectoryException.createElementNotFound("Folder", UUID.fromString("123e4567-e89b-12d3-a456-426614174000"));
assertThat(notFound.getMessage()).contains("Folder");
assertThat(notFound.getBusinessErrorCode()).isEqualTo(DirectoryBusinessErrorCode.DIRECTORY_ELEMENT_NOT_FOUND);

DirectoryException conflict = DirectoryException.createElementNameAlreadyExists("report");
assertThat(conflict.getMessage()).contains("report");
assertThat(conflict.getBusinessErrorCode()).isEqualTo(DirectoryBusinessErrorCode.DIRECTORY_ELEMENT_NAME_CONFLICT);

DirectoryException formatted = DirectoryException.of(DirectoryBusinessErrorCode.DIRECTORY_ELEMENT_NAME_BLANK,
"Element '%s' invalid", "x");
assertThat(formatted.getMessage()).isEqualTo("Element 'x' invalid");
assertThat(formatted.getBusinessErrorCode()).isEqualTo(DirectoryBusinessErrorCode.DIRECTORY_ELEMENT_NAME_BLANK);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.gridsuite.directory.server.dto.ElementAttributes;
import org.gridsuite.directory.server.dto.RootDirectoryAttributes;
import org.gridsuite.directory.server.elasticsearch.DirectoryElementInfosRepository;
import org.gridsuite.directory.server.error.DirectoryException;
import org.gridsuite.directory.server.repository.DirectoryElementEntity;
import org.gridsuite.directory.server.repository.DirectoryElementRepository;
import org.gridsuite.directory.server.utils.elasticsearch.DisableElasticsearch;
Expand All @@ -28,6 +29,7 @@
import static org.gridsuite.directory.server.dto.ElementAttributes.toElementAttributes;
import static org.gridsuite.directory.server.utils.DirectoryTestUtils.createElement;
import static org.gridsuite.directory.server.utils.DirectoryTestUtils.createRootElement;
import static org.gridsuite.directory.server.error.DirectoryBusinessErrorCode.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

Expand Down Expand Up @@ -118,7 +120,7 @@ void testDirectoryElementUniqueness() {

// Insert the same element in the same directory throws an exception
DirectoryException directoryException = assertThrows(DirectoryException.class, () -> directoryService.createElement(elementAttributes, rootUuid, "User1", false));
assertEquals(DirectoryException.Type.NAME_ALREADY_EXISTS, directoryException.getType());
assertEquals(DIRECTORY_ELEMENT_NAME_CONFLICT, directoryException.getBusinessErrorCode());
assertEquals(DirectoryException.createElementNameAlreadyExists(elementAttributes.getElementName()).getMessage(), directoryException.getMessage());

// Insert the same element in the same directory with new name generation does not throw an exception
Expand All @@ -137,7 +139,7 @@ void testDirectoryElementUniqueness() {
InOrder inOrder = inOrder(directoryService);
when(directoryService.getDuplicateNameCandidate(root2Uuid, elementAttributes.getElementName(), elementAttributes.getType(), "User1")).thenReturn(elementAttributes.getElementName());
directoryException = assertThrows(DirectoryException.class, () -> directoryService.duplicateElement(element2Uuid, root2Uuid, root2Uuid, "User1"));
assertEquals(DirectoryException.Type.NAME_ALREADY_EXISTS, directoryException.getType());
assertEquals(DIRECTORY_ELEMENT_NAME_CONFLICT, directoryException.getBusinessErrorCode());
assertEquals(DirectoryException.createElementNameAlreadyExists(elementAttributes.getElementName()).getMessage(), directoryException.getMessage());
inOrder.verify(directoryService, calls(MAX_RETRY)).getDuplicateNameCandidate(root2Uuid, elementAttributes.getElementName(), elementAttributes.getType(), "User1");
}
Expand Down Expand Up @@ -203,7 +205,7 @@ void testMoveElement() {
// move directory to it's descendent
List<UUID> list = List.of(dirUuid); // Just for Sonar issue (assertThrows)
DirectoryException exception1 = assertThrows(DirectoryException.class, () -> directoryService.moveElementsDirectory(list, subDirUuid, "user1"));
assertEquals(DirectoryException.Type.MOVE_IN_DESCENDANT_NOT_ALLOWED, exception1.getType());
assertEquals(DIRECTORY_MOVE_IN_DESCENDANT_NOT_ALLOWED, exception1.getBusinessErrorCode());
}

@Test
Expand Down Expand Up @@ -231,6 +233,6 @@ void testMoveInNotDirectory() {

List<UUID> list = List.of(elementUuid1); // Just for Sonar issue (assertThrows)
DirectoryException exception2 = assertThrows(DirectoryException.class, () -> directoryService.moveElementsDirectory(list, elementUuid2, "user1"));
assertEquals(DirectoryException.Type.NOT_DIRECTORY, exception2.getType());
assertEquals(DIRECTORY_NOT_DIRECTORY, exception2.getBusinessErrorCode());
}
}
Loading