Skip to content

Commit eef72e0

Browse files
Add schema description endpoint (#281)
Signed-off-by: Hugo Marcellin <[email protected]> Co-authored-by: Tristan Chuine <[email protected]>
1 parent b404c8b commit eef72e0

File tree

9 files changed

+227
-8
lines changed

9 files changed

+227
-8
lines changed

pom.xml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
</developers>
4343

4444
<properties>
45-
45+
<jackson-module-jsonSchema.version>2.15.2</jackson-module-jsonSchema.version>
4646
<gridsuite-dependencies.version>43.0.0</gridsuite-dependencies.version>
4747
<sonar.organization>gridsuite</sonar.organization>
4848
<sonar.projectKey>org.gridsuite:network-map-server</sonar.projectKey>
@@ -80,6 +80,12 @@
8080

8181
<dependencyManagement>
8282
<dependencies>
83+
<dependency>
84+
<groupId>com.fasterxml.jackson.module</groupId>
85+
<artifactId>jackson-module-jsonSchema</artifactId>
86+
<version>${jackson-module-jsonSchema.version}</version>
87+
</dependency>
88+
8389
<!-- project specific dependencies (must be before to have priority during resolution) -->
8490
<dependency><!--TODO remove when springboot version will be updated-->
8591
<groupId>org.junit</groupId>
@@ -110,6 +116,10 @@
110116

111117
<!-- Compilation dependencies -->
112118
<!-- overrides of imports -->
119+
<dependency>
120+
<groupId>com.fasterxml.jackson.module</groupId>
121+
<artifactId>jackson-module-jsonSchema</artifactId>
122+
</dependency>
113123
<dependency>
114124
<groupId>com.powsybl</groupId>
115125
<artifactId>powsybl-network-store-client</artifactId>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
package org.gridsuite.network.map;
8+
9+
/**
10+
* @author Hugo Marcellin <hugo.marcelin at rte-france.com>
11+
*/
12+
13+
public final class NetworkMapApi {
14+
15+
private NetworkMapApi() {
16+
}
17+
18+
public static final String API_VERSION = "v1";
19+
}

src/main/java/org/gridsuite/network/map/NetworkMapController.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import lombok.AllArgsConstructor;
1717
import org.gridsuite.network.map.dto.*;
1818
import org.gridsuite.network.map.dto.definition.hvdc.HvdcShuntCompensatorsInfos;
19+
import org.gridsuite.network.map.services.NetworkMapService;
1920
import org.springframework.context.annotation.ComponentScan;
2021
import org.springframework.web.bind.annotation.*;
2122

@@ -28,14 +29,11 @@
2829
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
2930
*/
3031
@RestController
31-
@RequestMapping(value = "/" + NetworkMapController.API_VERSION + "/")
32-
@Tag(name = "network-map-server")
32+
@RequestMapping(value = "/" + NetworkMapApi.API_VERSION + "/")
33+
@Tag(name = "Network map server")
3334
@ComponentScan(basePackageClasses = NetworkMapService.class)
3435
@AllArgsConstructor
3536
public class NetworkMapController {
36-
37-
public static final String API_VERSION = "v1";
38-
3937
private final NetworkMapService networkMapService;
4038

4139
@PostMapping(value = "/networks/{networkUuid}/elements-ids", produces = APPLICATION_JSON_VALUE)

src/main/java/org/gridsuite/network/map/NetworkMapSwaggerConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ public OpenAPI createOpenApi() {
2222
.info(new Info()
2323
.title("Network map API")
2424
.description("This is the documentation of network map REST API")
25-
.version(NetworkMapController.API_VERSION));
25+
.version(NetworkMapApi.API_VERSION));
2626
}
2727
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
package org.gridsuite.network.map;
8+
9+
import com.fasterxml.jackson.databind.JsonMappingException;
10+
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
11+
import io.swagger.v3.oas.annotations.Operation;
12+
import io.swagger.v3.oas.annotations.Parameter;
13+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
14+
import io.swagger.v3.oas.annotations.tags.Tag;
15+
import lombok.AllArgsConstructor;
16+
import org.gridsuite.network.map.dto.ElementInfos.InfoType;
17+
import org.gridsuite.network.map.dto.ElementType;
18+
import org.gridsuite.network.map.services.SchemaService;
19+
import org.springframework.http.HttpStatus;
20+
import org.springframework.web.bind.annotation.GetMapping;
21+
import org.springframework.web.bind.annotation.PathVariable;
22+
import org.springframework.web.bind.annotation.RequestMapping;
23+
import org.springframework.web.bind.annotation.RestController;
24+
import org.springframework.web.server.ResponseStatusException;
25+
26+
@RestController
27+
@RequestMapping(value = "/" + NetworkMapApi.API_VERSION + "/schemas")
28+
@Tag(name = "Network map server - Schemas")
29+
@AllArgsConstructor
30+
public class SchemaController {
31+
public static final String APPLICATION_JSON_SCHEMA_VALUE = "application/schema+json";
32+
private final SchemaService schemaService;
33+
34+
@GetMapping(value = "/{elementType}/{infoType}", produces = APPLICATION_JSON_SCHEMA_VALUE)
35+
@Operation(summary = "Get element schema description")
36+
@ApiResponse(responseCode = "200", description = "Element schema")
37+
public JsonSchema getElementSchema(@Parameter(description = "Element type") @PathVariable(name = "elementType") ElementType elementType,
38+
@Parameter(description = "Info type") @PathVariable(name = "infoType") InfoType infoType) {
39+
try {
40+
return schemaService.getSchema(elementType, infoType);
41+
} catch (final UnsupportedOperationException | JsonMappingException ex) {
42+
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "The view " + infoType + " for " + elementType + " type is not available yet.");
43+
}
44+
}
45+
}

src/main/java/org/gridsuite/network/map/NetworkMapService.java renamed to src/main/java/org/gridsuite/network/map/services/NetworkMapService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* License, v. 2.0. If a copy of the MPL was not distributed with this
55
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
66
*/
7-
package org.gridsuite.network.map;
7+
package org.gridsuite.network.map.services;
88

99
import com.powsybl.commons.PowsyblException;
1010
import com.powsybl.iidm.network.*;
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
package org.gridsuite.network.map.services;
8+
9+
import com.fasterxml.jackson.databind.JsonMappingException;
10+
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
12+
import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator;
13+
import lombok.AllArgsConstructor;
14+
import lombok.NonNull;
15+
import org.gridsuite.network.map.dto.ElementInfos.InfoType;
16+
import org.gridsuite.network.map.dto.ElementType;
17+
import org.gridsuite.network.map.dto.definition.battery.BatteryTabInfos;
18+
import org.gridsuite.network.map.dto.definition.branch.BranchTabInfos;
19+
import org.gridsuite.network.map.dto.definition.branch.line.LineTabInfos;
20+
import org.gridsuite.network.map.dto.definition.branch.twowindingstransformer.TwoWindingsTransformerTabInfos;
21+
import org.gridsuite.network.map.dto.definition.bus.BusTabInfos;
22+
import org.gridsuite.network.map.dto.definition.busbarsection.BusBarSectionTabInfos;
23+
import org.gridsuite.network.map.dto.definition.danglingline.DanglingLineTabInfos;
24+
import org.gridsuite.network.map.dto.definition.generator.GeneratorTabInfos;
25+
import org.gridsuite.network.map.dto.definition.hvdc.HvdcTabInfos;
26+
import org.gridsuite.network.map.dto.definition.lccconverterstation.LccConverterStationTabInfos;
27+
import org.gridsuite.network.map.dto.definition.load.LoadTabInfos;
28+
import org.gridsuite.network.map.dto.definition.shuntcompensator.ShuntCompensatorTabInfos;
29+
import org.gridsuite.network.map.dto.definition.staticvarcompensator.StaticVarCompensatorTabInfos;
30+
import org.gridsuite.network.map.dto.definition.substation.SubstationTabInfos;
31+
import org.gridsuite.network.map.dto.definition.threewindingstransformer.ThreeWindingsTransformerTabInfos;
32+
import org.gridsuite.network.map.dto.definition.tieline.TieLineTabInfos;
33+
import org.gridsuite.network.map.dto.definition.voltagelevel.VoltageLevelTabInfos;
34+
import org.gridsuite.network.map.dto.definition.vscconverterstation.VscConverterStationTabInfos;
35+
import org.springframework.stereotype.Service;
36+
37+
@Service
38+
@AllArgsConstructor
39+
public class SchemaService {
40+
41+
private final ObjectMapper objectMapper;
42+
43+
/**
44+
* @apiNote use class instance to be more secure with enum and classes rename/moving/etc with IDE
45+
*/
46+
private static Class<?> getTabInfosClass(final ElementType elementType) {
47+
return switch (elementType) {
48+
case BATTERY -> BatteryTabInfos.class;
49+
case BUS -> BusTabInfos.class;
50+
case BUSBAR_SECTION -> BusBarSectionTabInfos.class;
51+
case DANGLING_LINE -> DanglingLineTabInfos.class;
52+
case GENERATOR -> GeneratorTabInfos.class;
53+
case HVDC_LINE, HVDC_LINE_LCC, HVDC_LINE_VSC -> HvdcTabInfos.class;
54+
case LCC_CONVERTER_STATION -> LccConverterStationTabInfos.class;
55+
case LINE -> LineTabInfos.class;
56+
case LOAD -> LoadTabInfos.class;
57+
case SHUNT_COMPENSATOR -> ShuntCompensatorTabInfos.class;
58+
case STATIC_VAR_COMPENSATOR -> StaticVarCompensatorTabInfos.class;
59+
case SUBSTATION -> SubstationTabInfos.class;
60+
case THREE_WINDINGS_TRANSFORMER -> ThreeWindingsTransformerTabInfos.class;
61+
case TIE_LINE -> TieLineTabInfos.class;
62+
case TWO_WINDINGS_TRANSFORMER -> TwoWindingsTransformerTabInfos.class;
63+
case VOLTAGE_LEVEL -> VoltageLevelTabInfos.class;
64+
case VSC_CONVERTER_STATION -> VscConverterStationTabInfos.class;
65+
case BRANCH -> BranchTabInfos.class;
66+
};
67+
}
68+
69+
public JsonSchema getSchema(@NonNull final ElementType elementType, @NonNull final InfoType infoType) throws JsonMappingException {
70+
if (infoType != InfoType.TAB) {
71+
throw new UnsupportedOperationException("This info type is not currently supported.");
72+
}
73+
JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(objectMapper);
74+
return schemaGen.generateSchema(getTabInfosClass(elementType));
75+
}
76+
}

src/test/java/org/gridsuite/network/map/ListHandlingControllerTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import org.gridsuite.network.map.dto.ElementType;
99
import org.gridsuite.network.map.dto.InfoTypeParameters;
10+
import org.gridsuite.network.map.services.NetworkMapService;
1011
import org.junit.jupiter.api.AfterAll;
1112
import org.junit.jupiter.api.AfterEach;
1213
import org.junit.jupiter.api.BeforeEach;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
package org.gridsuite.network.map;
8+
9+
import com.fasterxml.jackson.databind.ObjectMapper;
10+
import com.fasterxml.jackson.databind.ObjectWriter;
11+
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
12+
import org.apache.commons.lang3.tuple.Pair;
13+
import org.gridsuite.network.map.dto.ElementInfos.InfoType;
14+
import org.gridsuite.network.map.dto.ElementType;
15+
import org.gridsuite.network.map.services.SchemaService;
16+
import org.junit.jupiter.params.ParameterizedTest;
17+
import org.junit.jupiter.params.provider.Arguments;
18+
import org.junit.jupiter.params.provider.MethodSource;
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
21+
import org.springframework.boot.test.mock.mockito.SpyBean;
22+
import org.springframework.lang.NonNull;
23+
import org.springframework.test.web.servlet.MockMvc;
24+
import org.springframework.test.web.servlet.ResultActions;
25+
26+
import java.util.ArrayList;
27+
import java.util.List;
28+
import java.util.stream.Stream;
29+
30+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
31+
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.log;
32+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
33+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
34+
35+
@WebMvcTest(SchemaController.class)
36+
class SchemaControllerTest {
37+
@Autowired
38+
private MockMvc mockMvc;
39+
40+
@SpyBean
41+
private SchemaService schemaService;
42+
43+
private static Stream<Arguments> schemaRequestValues() {
44+
final List<Pair<ElementType, InfoType>> cases = new ArrayList<>();
45+
for (ElementType elementType : ElementType.values()) {
46+
for (InfoType infoType : InfoType.values()) {
47+
cases.add(Pair.of(elementType, infoType));
48+
}
49+
}
50+
return cases.stream().map(e1 -> Arguments.of(e1.getKey(), e1.getValue()));
51+
}
52+
53+
@ParameterizedTest(name = "{0} (view {1})")
54+
@MethodSource("schemaRequestValues")
55+
void schemaRequest(@NonNull final ElementType eType, @NonNull final InfoType iType) throws Exception {
56+
ResultActions result = this.mockMvc.perform(get("/v1/schemas/{eType}/{iType}", eType, iType)).andDo(log());
57+
if (iType.equals(InfoType.TAB)) {
58+
JsonSchema schema = schemaService.getSchema(eType, iType);
59+
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
60+
String json = ow.writeValueAsString(schema);
61+
result.andExpectAll(
62+
status().isOk(),
63+
content().contentType(SchemaController.APPLICATION_JSON_SCHEMA_VALUE),
64+
content().json(json)
65+
);
66+
} else {
67+
result.andExpect(status().isNotImplemented());
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)