diff --git a/oparl-server/src/main/java/de/twomartens/oparlservice/control/OParlController.java b/oparl-server/src/main/java/de/twomartens/oparlservice/control/OParlController.java index 068f20e..a8a137a 100644 --- a/oparl-server/src/main/java/de/twomartens/oparlservice/control/OParlController.java +++ b/oparl-server/src/main/java/de/twomartens/oparlservice/control/OParlController.java @@ -6,10 +6,9 @@ import de.twomartens.oparlservice.service.OParlService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; import java.util.List; @@ -44,7 +43,9 @@ public class OParlController { @Parameter(description = "body ID", example = "0") String id) { log.info("method invoked /v1.1/body/{}", id); - return service.getBody(id); + return service.getBody(id).orElseThrow(() -> { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Körperschaft mit angefragter ID existiert nicht"); + }); } @GetMapping("/term/{id}") @@ -54,7 +55,9 @@ public class OParlController { @Parameter(description = "legislative term ID", example = "21") String id) { log.info("method invoked /v1.1/term/{}", id); - return service.getLegislativeTerm(id); + return service.getLegislativeTerm(id).orElseThrow(() -> { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Legislaturperiode mit angefragter ID existiert nicht"); + }); } @GetMapping("/body/{id}/organizations") @@ -64,7 +67,9 @@ public class OParlController { @Parameter(description = "body ID", example = "0") String id) { log.info("invoked method /v1.1/body/{}/organizations", id); - return service.getOrganizationsInBody(id); + return service.getOrganizationsInBody(id).orElseThrow(() -> { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Keine Körperschaft mit angefragter ID existiert"); + }); } @GetMapping("/organization/{id}") @@ -74,7 +79,9 @@ public class OParlController { @Parameter(description = "organization ID", example = "0") String id) { log.info("invoked method /v1.1/organization/{}", id); - return service.getOrganization(id); + return service.getOrganization(id).orElseThrow(() -> { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Keine Organisation mit angefragter ID existiert"); + }); } @GetMapping("/body/{id}/persons") @@ -84,7 +91,9 @@ public class OParlController { @Parameter(description = "body ID", example = "0") String id) { log.info("invoked method /v1.1/body/{}/persons", id); - return service.getPersonsInBody(id); + return service.getPersonsInBody(id).orElseThrow(() -> { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Keine Körperschaft mit angefragter ID existiert"); + }); } @GetMapping("/person/{id}") @@ -94,7 +103,9 @@ public class OParlController { @Parameter(description = "person ID", example = "0") String id) { log.info("invoked method /v1.1/person/{}", id); - return service.getPerson(id); + return service.getPerson(id).orElseThrow(() -> { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Keine Person mit angefragter ID existiert"); + }); } @GetMapping("/body/{id}/memberships") @@ -104,7 +115,9 @@ public class OParlController { @Parameter(description = "body ID", example = "0") String id) { log.info("invoked method /v1.1/body/{}/memberships", id); - return service.getMembershipsInBody(id); + return service.getMembershipsInBody(id).orElseThrow(() -> { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Keine Körperschaft mit angefragter ID existiert"); + }); } @GetMapping("/organization/{id}/memberships") @@ -114,7 +127,9 @@ public class OParlController { @Parameter(description = "organization ID", example = "0") String id) { log.info("invoked method /v1.1/organization/{}/memberships", id); - return service.getMembershipsInOrganization(id); + return service.getMembershipsInOrganization(id).orElseThrow(() -> { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Keine Organisation mit angefragter ID existiert"); + }); } @GetMapping("/membership/{id}") @@ -124,7 +139,9 @@ public class OParlController { @Parameter(description = "membership ID", example = "0") String id) { log.info("invoked method /v1.1/membership/{}", id); - return service.getMembership(id); + return service.getMembership(id).orElseThrow(() -> { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Keine Mitgliedschaft mit angefragter ID existiert"); + }); } @GetMapping("/body/{id}/meetings") @@ -134,7 +151,9 @@ public class OParlController { @Parameter(description = "body ID", example = "0") String id) { log.info("invoked method /v1.1/body/{}/meetings", id); - return service.getMeetingsInBody(id); + return service.getMeetingsInBody(id).orElseThrow(() -> { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Keine Körperschaft mit angefragter ID existiert"); + }); } @GetMapping("/organization/{id}/meetings") @@ -144,7 +163,9 @@ public class OParlController { @Parameter(description = "organization ID", example = "0") String id) { log.info("invoked method /v1.1/organization/{}/meetings", id); - return service.getMeetingsInOrganization(id); + return service.getMeetingsInOrganization(id).orElseThrow(() -> { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Keine Organisation mit angefragter ID existiert"); + }); } @GetMapping("/meeting/{id}") @@ -154,7 +175,9 @@ public class OParlController { @Parameter(description = "meeting ID", example = "0") String id) { log.info("invoked method /v1.1/meeting/{}", id); - return service.getMeeting(id); + return service.getMeeting(id).orElseThrow(() -> { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Keine Sitzung mit angefragter ID existiert"); + }); } @GetMapping("/agendaItem/{id}") @@ -164,6 +187,17 @@ public class OParlController { @Parameter(description = "agendaItem ID", example = "0") String id) { log.info("invoked method /v1.1/agendaItem/{}", id); - return service.getAgendaItem(id); + return service.getAgendaItem(id).orElseThrow(() -> { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Kein Tagesordnungspunkt mit angefragter ID existiert"); + }); + } + + @ExceptionHandler({ResponseStatusException.class}) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorObject notFound(ResponseStatusException exception) { + return ErrorObject.builder() + .message(exception.getReason()) + .debug(exception.getLocalizedMessage()) + .build(); } } diff --git a/oparl-server/src/main/java/de/twomartens/oparlservice/entity/ErrorObject.java b/oparl-server/src/main/java/de/twomartens/oparlservice/entity/ErrorObject.java new file mode 100644 index 0000000..4b32a34 --- /dev/null +++ b/oparl-server/src/main/java/de/twomartens/oparlservice/entity/ErrorObject.java @@ -0,0 +1,23 @@ +package de.twomartens.oparlservice.entity; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +@Builder +@Getter +@ToString +@EqualsAndHashCode +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ErrorObject { + @Schema(description = "URL to type specification") + private final String type = "https://schema.oparl.org/1.1/Error"; + + @Schema(description = "message for display to the user") + private final String message; + + @Schema(description = "additional information about the error") + private final String debug; +} diff --git a/oparl-server/src/main/java/de/twomartens/oparlservice/service/OParlService.java b/oparl-server/src/main/java/de/twomartens/oparlservice/service/OParlService.java index d49a5f7..1f58295 100644 --- a/oparl-server/src/main/java/de/twomartens/oparlservice/service/OParlService.java +++ b/oparl-server/src/main/java/de/twomartens/oparlservice/service/OParlService.java @@ -5,130 +5,74 @@ import de.twomartens.oparlservice.entity.System; import de.twomartens.oparlservice.entity.*; import org.springframework.stereotype.Service; -import java.time.LocalDate; -import java.time.ZonedDateTime; -import java.util.List; +import java.util.Optional; @Service public class OParlService { private final OParlServiceProperties properties; - private final Body exampleBody; - private final LegislativeTerm exampleTerm; - private final System system; public OParlService(OParlServiceProperties properties) { this.properties = properties; - - this.exampleTerm = LegislativeTerm.builder() - .id(properties.getUrl() + "/v1.1/term/21") - .type("https://schema.oparl.org/1.1/LegislativeTerm") - .body(properties.getUrl() + "/v1.1/body/0") - .name("21. Wahlperiode") - .startDate(LocalDate.parse("2019-05-27")) - .endDate(LocalDate.parse("2024-05-26")) - .created(ZonedDateTime.now()) - .modified(ZonedDateTime.now()) - .build(); - this.exampleBody = Body.builder() - .id(properties.getUrl() + "/v1.1/body/0") - .type("https://schema.oparl.org/1.1/Body") - .created(ZonedDateTime.now()) - .modified(ZonedDateTime.now()) - .shortName("Hamburg") - .name("Bezirk Eimsbüttel") - .system(properties.getUrl() + "/v1.1") - .legislativeTerm(List.of(exampleTerm)) - .website("https://www.hamburg.de/eimsbuettel") - .ags("02000000") - .rgs("020000000000") - .equivalent(List.of("http://dbpedia.org/page/Eimsb%C3%BCttel", "http://d-nb.info/1208293575")) - .organization(properties.getUrl() + "/v1.1/organizations/0") - .person(properties.getUrl() + "/v1.1/persons/0") - .meeting(properties.getUrl() + "/v1.1/meetings/0") - .paper(properties.getUrl() + "/v1.1/papers/0") - .agendaItem(properties.getUrl() + "/v1.1/agendaItems/0") - .consultation(properties.getUrl() + "/v1.1/consultations/0") - .file(properties.getUrl() + "/v1.1/files/0") - .locationList(properties.getUrl() + "/v1.1/locations/0") - .legislativeTermList(properties.getUrl() + "/v1.1/terms") - .membership(properties.getUrl() + "/v1.1/memberships/0") - .classification("Bezirk") - .build(); - this.system = System.builder() - .id(properties.getUrl() + "/v1.1") - .type("https://schema.oparl.org/1.1/System") - .license("https://www.govdata.de/dl-de/by-2-0") - .created(ZonedDateTime.now()) - .modified(ZonedDateTime.now()) - .oparlVersion("https://schema.oparl.org/1.1/") - .name("OParl interface for ALLRIS of Hamburg Eimsbüttel") - .contactEmail("github@2martens.de") - .contactName("Jim Martens") - .website("https://sitzungsdienst-eimsbuettel.hamburg.de/bi/") - .vendor("https://2martens.de") - .product("https://git.2martens.de/2martens/oparl-service") - .body(properties.getUrl() + "/bodies") - .build(); } - public AgendaItem getAgendaItem(String id) { - return null; + public Optional getAgendaItem(String id) { + return Optional.empty(); } public ObjectList getBodies() { return null; } - public Body getBody(String id) { - return exampleBody; + public Optional getBody(String id) { + return Optional.empty(); } - public LegislativeTerm getLegislativeTerm(String id) { - return exampleTerm; + public Optional getLegislativeTerm(String id) { + return Optional.empty(); } - public Meeting getMeeting(String id) { - return null; + public Optional getMeeting(String id) { + return Optional.empty(); } - public ObjectList getMeetingsInBody(String bodyID) { - return null; + public Optional> getMeetingsInBody(String bodyID) { + return Optional.empty(); } - public ObjectList getMeetingsInOrganization(String organizationID) { - return null; + public Optional> getMeetingsInOrganization(String organizationID) { + return Optional.empty(); } - public Membership getMembership(String id) { - return null; + public Optional getMembership(String id) { + return Optional.empty(); } - public ObjectList getMembershipsInBody(String bodyID) { - return null; + public Optional> getMembershipsInBody(String bodyID) { + return Optional.empty(); } - public ObjectList getMembershipsInOrganization(String organizationID) { - return null; + public Optional> getMembershipsInOrganization(String organizationID) { + return Optional.empty(); } - public ObjectList getOrganizationsInBody(String bodyID) { - return null; + public Optional> getOrganizationsInBody(String bodyID) { + return Optional.empty(); } - public Organization getOrganization(String id) { - return null; + public Optional getOrganization(String id) { + return Optional.empty(); } - public ObjectList getPersonsInBody(String bodyID) { - return null; + public Optional> getPersonsInBody(String bodyID) { + return Optional.empty(); } - public Person getPerson(String id) { - return null; + public Optional getPerson(String id) { + return Optional.empty(); } public System getSystem() { - return system; + return null; } } diff --git a/oparl-server/src/test/java/de/twomartens/oparlservice/control/OParlControllerTest.java b/oparl-server/src/test/java/de/twomartens/oparlservice/control/OParlControllerTest.java index e0b02cf..a0e999b 100644 --- a/oparl-server/src/test/java/de/twomartens/oparlservice/control/OParlControllerTest.java +++ b/oparl-server/src/test/java/de/twomartens/oparlservice/control/OParlControllerTest.java @@ -19,6 +19,7 @@ import java.time.LocalDate; import java.time.ZonedDateTime; import java.util.Collections; import java.util.List; +import java.util.Optional; @WebMvcTest(OParlController.class) class OParlControllerTest { @@ -50,6 +51,29 @@ class OParlControllerTest { initializeTestValues(); } + @Test + void shouldReturnErrorObject_WhenInvalidIDPassed() throws Exception { + BDDMockito.given(service.getBody("2")) + .willReturn(Optional.empty()); + + BDDMockito.given(service.getMembershipsInBody("2")) + .willReturn(Optional.empty()); + + mvc.perform(MockMvcRequestBuilders.get("/v1.1/body/2") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().is4xxClientError()) + .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.aMapWithSize(3))) + .andExpect(MockMvcResultMatchers.jsonPath("$.type", Matchers.equalTo("https://schema.oparl.org/1.1/Error"))) + .andReturn(); + + mvc.perform(MockMvcRequestBuilders.get("/v1.1/body/2/memberships") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().is4xxClientError()) + .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.aMapWithSize(3))) + .andExpect(MockMvcResultMatchers.jsonPath("$.type", Matchers.equalTo("https://schema.oparl.org/1.1/Error"))) + .andReturn(); + } + @Test void shouldReturnSystemInfo() throws Exception { BDDMockito.given(service.getSystem()) @@ -88,7 +112,7 @@ class OParlControllerTest { @Test void shouldReturnBody() throws Exception { BDDMockito.given(service.getBody("0")) - .willReturn(testBody); + .willReturn(Optional.of(testBody)); mvc.perform(MockMvcRequestBuilders.get("/v1.1/body/0") .contentType(MediaType.APPLICATION_JSON)) @@ -102,7 +126,7 @@ class OParlControllerTest { @Test void shouldReturnLegislativeTerm() throws Exception { BDDMockito.given(service.getLegislativeTerm("21")) - .willReturn(testTerm); + .willReturn(Optional.of(testTerm)); mvc.perform(MockMvcRequestBuilders.get("/v1.1/term/21") .contentType(MediaType.APPLICATION_JSON)) @@ -117,7 +141,9 @@ class OParlControllerTest { @Test void shouldReturnOrganizationsInBody() throws Exception { BDDMockito.given(service.getOrganizationsInBody("0")) - .willReturn(ObjectList.builder().data(List.of(testOrganization, testPartyOrganization)).pagination(testPagination).links(testLinks).build()); + .willReturn(Optional.of( + ObjectList.builder().data(List.of(testOrganization, testPartyOrganization)).pagination(testPagination).links(testLinks).build() + )); mvc.perform(MockMvcRequestBuilders.get("/v1.1/body/0/organizations") .contentType(MediaType.APPLICATION_JSON)) @@ -133,7 +159,7 @@ class OParlControllerTest { @Test void shouldReturnOrganization() throws Exception { BDDMockito.given(service.getOrganization("0")) - .willReturn(testOrganization); + .willReturn(Optional.of(testOrganization)); mvc.perform(MockMvcRequestBuilders.get("/v1.1/organization/0") .contentType(MediaType.APPLICATION_JSON)) @@ -147,7 +173,9 @@ class OParlControllerTest { @Test void shouldReturnPersonsInBody() throws Exception { BDDMockito.given(service.getPersonsInBody("0")) - .willReturn(ObjectList.builder().data(List.of(testPerson)).pagination(testPagination).links(testLinks).build()); + .willReturn(Optional.of( + ObjectList.builder().data(List.of(testPerson)).pagination(testPagination).links(testLinks).build() + )); mvc.perform(MockMvcRequestBuilders.get("/v1.1/body/0/persons") .contentType(MediaType.APPLICATION_JSON)) @@ -163,7 +191,7 @@ class OParlControllerTest { @Test void shouldReturnPerson() throws Exception { BDDMockito.given(service.getPerson("0")) - .willReturn(testPerson); + .willReturn(Optional.of(testPerson)); mvc.perform(MockMvcRequestBuilders.get("/v1.1/person/0") .contentType(MediaType.APPLICATION_JSON)) @@ -177,7 +205,9 @@ class OParlControllerTest { @Test void shouldReturnMembershipsInBody() throws Exception { BDDMockito.given(service.getMembershipsInBody("0")) - .willReturn(ObjectList.builder().data(List.of(testMembership)).pagination(testPagination).links(testLinks).build()); + .willReturn(Optional.of( + ObjectList.builder().data(List.of(testMembership)).pagination(testPagination).links(testLinks).build() + )); mvc.perform(MockMvcRequestBuilders.get("/v1.1/body/0/memberships") .contentType(MediaType.APPLICATION_JSON)) @@ -193,7 +223,9 @@ class OParlControllerTest { @Test void shouldReturnMembershipsInOrganization() throws Exception { BDDMockito.given(service.getMembershipsInOrganization("0")) - .willReturn(ObjectList.builder().data(List.of(testMembership)).pagination(testPagination).links(testLinks).build()); + .willReturn(Optional.of( + ObjectList.builder().data(List.of(testMembership)).pagination(testPagination).links(testLinks).build() + )); mvc.perform(MockMvcRequestBuilders.get("/v1.1/organization/0/memberships") .contentType(MediaType.APPLICATION_JSON)) @@ -209,7 +241,7 @@ class OParlControllerTest { @Test void shouldReturnMembership() throws Exception { BDDMockito.given(service.getMembership("0")) - .willReturn(testMembership); + .willReturn(Optional.of(testMembership)); mvc.perform(MockMvcRequestBuilders.get("/v1.1/membership/0") .contentType(MediaType.APPLICATION_JSON)) @@ -224,7 +256,9 @@ class OParlControllerTest { @Test void shouldReturnMeetingsInBody() throws Exception { BDDMockito.given(service.getMeetingsInBody("0")) - .willReturn(ObjectList.builder().data(List.of(testMeeting)).pagination(testPagination).links(testLinks).build()); + .willReturn(Optional.of( + ObjectList.builder().data(List.of(testMeeting)).pagination(testPagination).links(testLinks).build() + )); mvc.perform(MockMvcRequestBuilders.get("/v1.1/body/0/meetings") .contentType(MediaType.APPLICATION_JSON)) @@ -240,7 +274,9 @@ class OParlControllerTest { @Test void shouldReturnMeetingsInOrganization() throws Exception { BDDMockito.given(service.getMeetingsInOrganization("0")) - .willReturn(ObjectList.builder().data(List.of(testMeeting)).pagination(testPagination).links(testLinks).build()); + .willReturn(Optional.of( + ObjectList.builder().data(List.of(testMeeting)).pagination(testPagination).links(testLinks).build() + )); mvc.perform(MockMvcRequestBuilders.get("/v1.1/organization/0/meetings") .contentType(MediaType.APPLICATION_JSON)) @@ -256,7 +292,7 @@ class OParlControllerTest { @Test void shouldReturnMeeting() throws Exception { BDDMockito.given(service.getMeeting("0")) - .willReturn(testMeeting); + .willReturn(Optional.of(testMeeting)); mvc.perform(MockMvcRequestBuilders.get("/v1.1/meeting/0") .contentType(MediaType.APPLICATION_JSON)) @@ -270,7 +306,7 @@ class OParlControllerTest { @Test void shouldReturnAgendaItem() throws Exception { BDDMockito.given(service.getAgendaItem("0")) - .willReturn(testItem); + .willReturn(Optional.of(testItem)); mvc.perform(MockMvcRequestBuilders.get("/v1.1/agendaItem/0") .contentType(MediaType.APPLICATION_JSON))