CountryController.java

package cl.restapi.retrievecountriesapi.v1.controllers;

import cl.restapi.retrievecountriesapi.models.Country;
import cl.restapi.retrievecountriesapi.services.CountryService;
import cl.restapi.retrievecountriesapi.v1.Views;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.CacheControl;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonValue;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Tag(name = "Countries")
@RestController
@RequestMapping("/v1")
public class CountryController {
    private final CountryService service;

    public CountryController(CountryService service) {
        this.service = service;
    }

    @GetMapping("/all")
    @Operation(
            summary = "Get all country data.",
            description = "This operation retrieves data for all countries along with their states. However, you will "
                    + "not be able to obtain a list of cities in this call due to the large number of cities in the "
                    + "world. Including all cities in the response of this API is not recommended for us or for the "
                    + "application consuming this JSON. If you need to obtain cities, you can make a new call to one "
                    + "of our other endpoints or filter by country, region, or other criteria. States are included by "
                    + "default, but you can exclude them using the parameter excludeStates=true. "
                    + "As a commitment to best practices, we ask for your help by caching the API response to avoid "
                    + "excessive usage and help us keep it public and available for everyone."
    )
    @ApiResponse(
            responseCode = "200",
            description = "Successful operation",
            content = @Content(mediaType = "application/json", schema = @Schema(implementation = Country.class))
    )
    @ApiResponse(responseCode = "204", description = "No content", content = @Content)
    public ResponseEntity<MappingJacksonValue> getCountries(
            @RequestParam(required = false) Boolean excludeStates) {
        List<Country> countries = service.findAll();
        return getMappingJacksonValueResponseListEntity(excludeStates, countries);
    }

    @Operation(summary = "Get country data by name.",
            description = "This operation retrieves data for a country by its name and includes its cities. "
                    + "If you wish to include the states from the results, you can use the parameter "
                    + "excludeStates=false; similarly, if you want to exclude the cities, you can use "
                    + "excludeCities=true; "
                    + "As a commitment to best practices, we ask for your help by caching the API response to avoid "
                    + "excessive usage and help us keep it public and available for everyone."
    )
    @ApiResponse(responseCode = "200", description = "Successful operation",
            content = @Content(mediaType = "application/json", schema = @Schema(implementation = Country.class)))
    @ApiResponse(responseCode = "204", description = "No content", content = @Content)
    @GetMapping("/{name}")
    public ResponseEntity<MappingJacksonValue> getCountryByName(
            @PathVariable @Parameter(example = "Chile") String name,
            @RequestParam(required = false) Boolean excludeCities,
            @RequestParam(required = false, defaultValue = "true") Boolean excludeStates) {
        Country country = this.service.findByName(name);
        return getMappingJacksonValueResponseEntity(excludeCities, excludeStates, country);
    }

    @Operation(summary = "Get country data by capital.",
            description = "This operation retrieves data for a country by its capital and includes its cities."
                    + "If you wish to include the states from the results, you can use the parameter "
                    + "excludeStates=false; similarly, if you want to exclude the cities, you can use "
                    + "excludeCities=true; "
                    + "As a commitment to best practices, we ask for your help by caching the API response to avoid "
                    + "excessive usage and help us keep it public and available for everyone."
    )
    @ApiResponse(responseCode = "200", description = "Successful operation",
            content = @Content(mediaType = "application/json", schema = @Schema(implementation = Country.class)))
    @ApiResponse(responseCode = "204", description = "No content", content = @Content)
    @GetMapping("/capital/{name}")
    public ResponseEntity<MappingJacksonValue> getCountryByCapital(
            @PathVariable @Parameter(example = "Santiago") String name,
            @RequestParam(required = false) Boolean excludeCities,
            @RequestParam(required = false, defaultValue = "true") Boolean excludeStates) {
        Country country = this.service.findByCapital(name);
        return getMappingJacksonValueResponseEntity(excludeCities, excludeStates, country);
    }

    @Operation(summary = "Get country data by region.",
            description = "This operation retrieves data for countries by its region and includes its states. "
                    + "If you wish to exclude the states from the results, you can use the parameter "
                    + "excludeStates=true; "
                    + "As a commitment to best practices, we ask for your help by caching the API response to avoid "
                    + "excessive usage and help us keep it public and available for everyone."
    )
    @ApiResponse(responseCode = "200", description = "Successful operation",
            content = @Content(mediaType = "application/json", schema = @Schema(implementation = Country.class)))
    @ApiResponse(responseCode = "204", description = "No content", content = @Content)
    @GetMapping("/region/{region}")
    public ResponseEntity<MappingJacksonValue> getCountriesByRegion(
            @PathVariable @Parameter(example = "Americas") String region,
            @RequestParam(required = false) Boolean excludeStates) {
        List<Country> countries = service.findByRegion(region);
        return getMappingJacksonValueResponseListEntity(excludeStates, countries);
    }

    @Operation(summary = "Get country data by subregion.",
            description = "This operation retrieves data for countries by its subregion and includes its states. "
                    + "If you wish to exclude the states from the results, you can use the parameter "
                    + "excludeStates=true; "
                    + "As a commitment to best practices, we ask for your help by caching the API response to avoid "
                    + "excessive usage and help us keep it public and available for everyone."
    )
    @ApiResponse(responseCode = "200", description = "Successful operation",
            content = @Content(mediaType = "application/json", schema = @Schema(implementation = Country.class)))
    @ApiResponse(responseCode = "204", description = "No content", content = @Content)
    @GetMapping("/subregion/{subregion}")
    public ResponseEntity<MappingJacksonValue> getCountriesBySubregion(
            @PathVariable @Parameter(example = "South America") String subregion,
            @RequestParam(required = false) Boolean excludeStates) {
        List<Country> countries = service.findBySubregion(subregion);
        return getMappingJacksonValueResponseListEntity(excludeStates, countries);
    }

    private ResponseEntity<MappingJacksonValue> getMappingJacksonValueResponseListEntity(
            Boolean excludeStates,
            List<Country> countries
    ) {
        if (countries.isEmpty()) {
            return ResponseEntity.noContent().build();
        } else {
            MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(countries);
            return getViewMappingJacksonValueResponseEntity(true, excludeStates, mappingJacksonValue);
        }
    }

    private ResponseEntity<MappingJacksonValue> getMappingJacksonValueResponseEntity(
            Boolean excludeCities,
            Boolean excludeStates,
            Country country) {
        if (country == null) {
            return ResponseEntity.noContent().build();
        } else {
            MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(country);
            return getViewMappingJacksonValueResponseEntity(excludeCities, excludeStates, mappingJacksonValue);
        }
    }

    private ResponseEntity<MappingJacksonValue> getViewMappingJacksonValueResponseEntity(
            Boolean excludeCities,
            Boolean excludeStates,
            MappingJacksonValue mappingJacksonValue
    ) {
        if (Boolean.TRUE.equals(excludeCities) && Boolean.TRUE.equals(excludeStates)) {
            mappingJacksonValue.setSerializationView(Views.Single.class);
        } else if (Boolean.TRUE.equals(excludeCities)) {
            mappingJacksonValue.setSerializationView(Views.WithStates.class);
        } else if (Boolean.TRUE.equals(excludeStates)) {
            mappingJacksonValue.setSerializationView(Views.WithCities.class);
        } else {
            mappingJacksonValue.setSerializationView(Views.Complete.class);
        }
        return ResponseEntity.ok()
                .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS))
                .body(mappingJacksonValue);
    }
}