Merge branch 'v4' into 'master'

Finnishing V4 as Preview

See merge request restcountries/restcountries!105
This commit is contained in:
Alejandro Matos
2026-02-20 19:12:08 +00:00
28 changed files with 79744 additions and 584 deletions
@@ -0,0 +1,128 @@
package com.restcountries.controller;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.restcountries.domain.ICountryRestSymbols;
import com.restcountries.domain.v4.Country;
import java.util.*;
public class ControllerV4Helper {
protected Object checkFieldsAndParseCountries(Optional<String> fields, Set<Country> countries) {
if (fields.isPresent()) {
return parsedCountries(countries, fields.get());
} else {
return parsedCountries(countries, null);
}
}
protected Object checkFieldsAndParseCountry(Set<Country> countries, Optional<String> fields) {
if (fields.isPresent()) {
return parsedCountry(countries, fields.get());
} else {
return parsedCountry(countries, null);
}
}
protected Object parsedCountries(Set<Country> countries, String excludedFields) {
if (excludedFields == null || excludedFields.isEmpty()) {
return countries;
} else {
return getCountriesJson(countries,
Arrays.asList(excludedFields.split(ICountryRestSymbols.COLON)));
}
}
protected static Object parsedCountry(Set<Country> countries, String fields) {
if (fields == null || fields.isEmpty()) {
return countries;
} else {
StringBuilder result = new StringBuilder();
countries.forEach(country -> result.append(
getCountryJson(country, Arrays.asList(fields.split(ICountryRestSymbols.COLON)))));
return result;
}
}
private static String getCountryJson(Country country, List<String> fields) {
var gson = new Gson();
var jsonObject = JsonParser.parseString(gson.toJson(country)).getAsJsonObject();
List<String> excludedFields = new ArrayList<>(Arrays.asList(V4_COUNTRY_FIELDS));
excludedFields.removeAll(fields);
excludedFields.forEach(jsonObject::remove);
return jsonObject.toString();
}
private String getCountriesJson(Set<Country> countries, List<String> fields) {
var gson = new Gson();
var jsonArray = JsonParser.parseString(gson.toJson(countries)).getAsJsonArray();
var resultArray = new JsonArray();
jsonArray.forEach(element -> {
var jsonObject = (JsonObject) element;
var excludedFields = getExcludedFields(fields);
excludedFields.forEach(jsonObject::remove);
resultArray.add(jsonObject);
});
return resultArray.toString();
}
private List<String> getExcludedFields(List<String> fields) {
List<String> excludedFields = new ArrayList<>(Arrays.asList(V4_COUNTRY_FIELDS));
excludedFields.removeAll(fields);
return excludedFields;
}
boolean isEmpty(String value) {
return value == null || value.isEmpty();
}
protected static final String[] V4_COUNTRY_FIELDS = new String[]{
"name",
"tld",
"cca2",
"ccn3",
"cca3",
"cioc",
"independent",
"status",
"unMember",
"currencies",
"idd",
"region",
"subregion",
"languages",
"translations",
"landlocked",
"borders",
"area",
"flags",
"demonyms",
"population",
"flag",
"maps",
"gini",
"fifa",
"car",
"timezones",
"continents",
"coatOfArms",
"startOfWeek",
"capitalInfo",
"postalCode",
"capital",
"altSpellings",
"geolocation",
"religion",
"ethnicity",
"government",
"density",
"gdp",
"nationalHoliday",
"anthem",
"regionalBlocs",
"callingCodes"
};
}
@@ -0,0 +1,208 @@
package com.restcountries.controller;
import com.restcountries.service.v4.CountryServiceV4;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.PathVariable;
import io.micronaut.http.annotation.QueryValue;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import java.util.Optional;
import static com.restcountries.controller.ControllerHelper.hasInvalidFields;
@Controller("/v4/")
public class CountryControllerV4 extends ControllerV4Helper {
@Get(uri = "all", produces = MediaType.APPLICATION_JSON)
@Schema(name = "RestCountries")
public Object getAllCountries(@QueryValue("fields") Optional<String> fields) {
if (hasInvalidFields(fields)) {
return ControllerHelper.badAllRequest();
}
var countries = CountryServiceV4.getInstance().getAll();
return ControllerHelper.ok(checkFieldsAndParseCountries(fields, countries));
}
@Get("alpha/{alphacode}")
@Schema(name = "RestCountries")
public Object getByAlpha(@PathVariable("alphacode") String alpha,
@QueryValue("fields") Optional<String> fields) {
if (alpha.contains("codes")) {
alpha = alpha.replace("codes=", "");
}
if (isEmpty(alpha) || alpha.length() < 2 || alpha.length() > 3) {
return ControllerHelper.badRequest();
}
var country = CountryServiceV4.getInstance().getByAlpha(alpha);
if (country != null && !country.isEmpty()) {
return ControllerHelper.ok(checkFieldsAndParseCountry(country, fields));
}
return ControllerHelper.notFound();
}
@Get("alpha/")
@Schema(name = "RestCountries")
public Object getByAlphaList(@QueryParam("codes") String codes,
@QueryParam("fields") Optional<String> fields) {
if (isEmpty(codes) || codes.length() < 2 || (codes.length() > 3 && !codes.contains(","))) {
return ControllerHelper.badRequest();
}
try {
var countries = CountryServiceV4.getInstance().getByCodeList(codes);
if (!countries.isEmpty()) {
return ControllerHelper.ok(checkFieldsAndParseCountries(fields, countries));
}
return ControllerHelper.notFound();
} catch (Exception e) {
return ControllerHelper.internalError();
}
}
@Get("currency/{currency}")
@Schema(name = "RestCountries")
public Object getByCurrency(@PathVariable("currency") String currency,
@QueryParam("fields") Optional<String> fields) {
if (isEmpty(currency)) {
return ControllerHelper.badRequest();
}
try {
var countries = CountryServiceV4.getInstance().getByCurrency(currency);
if (!countries.isEmpty()) {
return ControllerHelper.ok(checkFieldsAndParseCountries(fields, countries));
}
return ControllerHelper.notFound();
} catch (Exception e) {
return ControllerHelper.internalError();
}
}
@Get("name/{name}")
@Schema(name = "RestCountries")
public Object getByName(@PathVariable("name") String name,
@QueryParam("fullText") Optional<Boolean> fullText,
@QueryParam("fields") Optional<String> fields) {
try {
var countries = CountryServiceV4.getInstance().getByName(name, fullText.orElse(false));
if (!countries.isEmpty()) {
return ControllerHelper.ok(checkFieldsAndParseCountries(fields, countries));
}
return ControllerHelper.notFound();
} catch (Exception e) {
return HttpResponse.serverError(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@Get("capital/{capital}")
@Schema(name = "RestCountries")
public Object getByCapital(@PathVariable("capital") String capital,
@QueryParam("fields") Optional<String> fields) {
try {
var countries = CountryServiceV4.getInstance().getByCapital(capital);
if (!countries.isEmpty()) {
return ControllerHelper.ok(checkFieldsAndParseCountries(fields, countries));
}
return ControllerHelper.notFound();
} catch (Exception e) {
return HttpResponse.serverError(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@Get("region/{region}")
@Schema(name = "RestCountries")
public Object getByContinent(@PathVariable("region") String region,
@QueryParam("fields") Optional<String> fields) {
try {
var countries = CountryServiceV4.getInstance().getByRegion(region);
if (!countries.isEmpty()) {
return ControllerHelper.ok(checkFieldsAndParseCountries(fields, countries));
}
return ControllerHelper.notFound();
} catch (Exception e) {
return HttpResponse.serverError(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@Get("subregion/{subregion}")
@Schema(name = "RestCountries")
public Object getBySubRegion(@PathVariable("subregion") String subregion,
@QueryParam("fields") Optional<String> fields) {
try {
var countries = CountryServiceV4.getInstance().getBySubregion(subregion);
if (!countries.isEmpty()) {
return ControllerHelper.ok(checkFieldsAndParseCountries(fields, countries));
}
return ControllerHelper.notFound();
} catch (Exception e) {
return HttpResponse.serverError(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@Get("lang/{lang}")
@Schema(name = "RestCountries")
public Object getByLanguage(@PathVariable("lang") String language,
@QueryParam("fields") Optional<String> fields) {
try {
var countries = CountryServiceV4.getInstance().getByLanguage(language);
if (!countries.isEmpty()) {
return ControllerHelper.ok(checkFieldsAndParseCountries(fields, countries));
}
return ControllerHelper.notFound();
} catch (Exception e) {
return HttpResponse.serverError(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@Get("demonym/{demonym}")
@Schema(name = "RestCountries")
public Object getByDemonym(@PathVariable("demonym") String demonym,
@QueryParam("fields") Optional<String> fields) {
try {
var countries = CountryServiceV4.getInstance().getByDemonym(demonym);
if (!countries.isEmpty()) {
return ControllerHelper.ok(checkFieldsAndParseCountries(fields, countries));
}
return ControllerHelper.notFound();
} catch (Exception e) {
return HttpResponse.serverError(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@Get("translation/{translation}")
@Schema(name = "RestCountries")
public Object getByTranslation(@PathVariable("translation") String translation,
@QueryParam("fields") Optional<String> fields) {
try {
var countries = CountryServiceV4.getInstance().getByTranslation(translation);
if (!countries.isEmpty()) {
return ControllerHelper.ok(checkFieldsAndParseCountries(fields, countries));
}
return ControllerHelper.notFound();
} catch (Exception e) {
return HttpResponse.serverError(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@Get("independent")
@Schema(name = "RestCountries")
public Object getIndependentCountries(@QueryParam("status") Optional<Boolean> status,
@QueryParam("fields") Optional<String> fields) {
try {
var result = true;
if (status.isPresent()) {
result = Boolean.TRUE.equals(status.get());
}
var countries = CountryServiceV4.getInstance().getIndependent(result);
if (!countries.isEmpty()) {
return ControllerHelper.ok(checkFieldsAndParseCountries(fields, countries));
}
return ControllerHelper.notFound();
} catch (Exception e) {
return HttpResponse.serverError(Response.Status.INTERNAL_SERVER_ERROR);
}
}
}
@@ -6,39 +6,15 @@ import java.util.List;
import java.util.Map;
@Serdeable.Serializable
public class BaseCountry {
public class BaseCountry extends BaseCountryCore {
private Name name;
private List<String> tld;
private String cca2;
private String ccn3;
private String cca3;
private String cioc;
private Boolean independent;
private String status;
private Boolean unMember;
private Map<String, Currency> currencies;
private Idd idd;
private List<String> capital;
private List<String> altSpellings;
private String region;
private String subregion;
private Map<String, String> languages;
private Map<String, Map<String, String>> translations;
private List<Double> latlng;
private Boolean landlocked;
private List<String> borders;
private Double area;
private Map<String, Map<String, String>> demonyms;
private List<String> callingCodes;
private String flag;
private Map<String, String> maps;
private Integer population;
private Map<String, Double> gini;
private String fifa;
private Car car;
private List<String> timezones;
private List<String> continents;
public Name getName() {
return name;
@@ -48,62 +24,6 @@ public class BaseCountry {
this.name = name;
}
public List<String> getTld() {
return tld;
}
public void setTld(List<String> tld) {
this.tld = tld;
}
public String getCca2() {
return cca2;
}
public void setCca2(String cca2) {
this.cca2 = cca2;
}
public String getCcn3() {
return ccn3;
}
public void setCcn3(String ccn3) {
this.ccn3 = ccn3;
}
public String getCioc() {
return cioc;
}
public void setCioc(String cioc) {
this.cioc = cioc;
}
public Boolean getIndependent() {
return independent;
}
public void setIndependent(Boolean independent) {
this.independent = independent;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Boolean getUnMember() {
return unMember;
}
public void setUnMember(Boolean unMember) {
this.unMember = unMember;
}
public Map<String, Currency> getCurrencies() {
return currencies;
}
@@ -113,46 +33,6 @@ public class BaseCountry {
this.currencies = currencies;
}
public Idd getIdd() {
return idd;
}
public void setIdd(Idd idd) {
this.idd = idd;
}
public List<String> getCapital() {
return capital;
}
public void setCapital(List<String> capital) {
this.capital = capital;
}
public List<String> getAltSpellings() {
return altSpellings;
}
public void setAltSpellings(List<String> altSpellings) {
this.altSpellings = altSpellings;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getSubregion() {
return subregion;
}
public void setSubregion(String subregion) {
this.subregion = subregion;
}
public Map<String, String> getLanguages() {
return languages;
}
@@ -169,30 +49,6 @@ public class BaseCountry {
this.latlng = latlng;
}
public Boolean getLandlocked() {
return landlocked;
}
public void setLandlocked(Boolean landlocked) {
this.landlocked = landlocked;
}
public List<String> getBorders() {
return borders;
}
public void setBorders(List<String> borders) {
this.borders = borders;
}
public Double getArea() {
return area;
}
public void setArea(Double area) {
this.area = area;
}
public Map<String, Map<String, String>> getDemonyms() {
return demonyms;
}
@@ -208,36 +64,28 @@ public class BaseCountry {
"NativeName=" + name.getNativeName() + "\n" +
"Common=" + name.getCommon() + "\n" +
"Official=" + name.getOfficial() + "\n" +
", tld=" + tld + "\n" +
", cca2='" + cca2 + '\'' + "\n" +
", ccn3='" + ccn3 + '\'' + "\n" +
", cioc='" + cioc + '\'' + "\n" +
", independent=" + independent + "\n" +
", status='" + status + '\'' + "\n" +
", unMember=" + unMember + "\n" +
", tld=" + getTld() + "\n" +
", cca2='" + getCca2() + '\'' + "\n" +
", ccn3='" + getCcn3() + '\'' + "\n" +
", cioc='" + getCioc() + '\'' + "\n" +
", independent=" + getIndependent() + "\n" +
", status='" + getStatus() + '\'' + "\n" +
", unMember=" + getUnMember() + "\n" +
", currencies=" + currencies + "\n" +
", idd=" + idd + "\n" +
", capital=" + capital + "\n" +
", altSpelling=" + altSpellings + "\n" +
", region='" + region + '\'' + "\n" +
", subregion='" + subregion + '\'' + "\n" +
", idd=" + getIdd() + "\n" +
", capital=" + getCapital() + "\n" +
", altSpelling=" + getAltSpellings() + "\n" +
", region='" + getRegion() + '\'' + "\n" +
", subregion='" + getSubregion() + '\'' + "\n" +
", language=" + languages + "\n" +
", latlng=" + latlng + "\n" +
", landlocked=" + landlocked + "\n" +
", borders=" + borders + "\n" +
", area=" + area + "\n" +
", landlocked=" + getLandlocked() + "\n" +
", borders=" + getBorders() + "\n" +
", area=" + getArea() + "\n" +
", demonyms=" + demonyms + "\n" +
'}';
}
public String getCca3() {
return cca3;
}
public void setCca3(String cca3) {
this.cca3 = cca3;
}
public Map<String, Map<String, String>> getTranslations() {
return translations;
}
@@ -246,38 +94,6 @@ public class BaseCountry {
this.translations = translations;
}
public List<String> getCallingCodes() {
return callingCodes;
}
public void setCallingCodes(List<String> callingCodes) {
this.callingCodes = callingCodes;
}
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
public Map<String, String> getMaps() {
return maps;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
public Integer getPopulation() {
return population;
}
public void setPopulation(Integer population) {
this.population = population;
}
public Map<String, Double> getGini() {
return gini;
}
@@ -285,36 +101,4 @@ public class BaseCountry {
public void setGini(Map<String, Double> gini) {
this.gini = gini;
}
public String getFifa() {
return fifa;
}
public void setFifa(String fifa) {
this.fifa = fifa;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public List<String> getTimezones() {
return timezones;
}
public void setTimezones(List<String> timezones) {
this.timezones = timezones;
}
public List<String> getContinents() {
return continents;
}
public void setContinents(List<String> continents) {
this.continents = continents;
}
}
@@ -0,0 +1,227 @@
package com.restcountries.domain.base;
import io.micronaut.serde.annotation.Serdeable;
import java.util.List;
import java.util.Map;
@Serdeable.Serializable
public abstract class BaseCountryCore {
private List<String> tld;
private String cca2;
private String ccn3;
private String cca3;
private String cioc;
private Boolean independent;
private String status;
private Boolean unMember;
private Idd idd;
private List<String> capital;
private List<String> altSpellings;
private String region;
private String subregion;
private Boolean landlocked;
private List<String> borders;
private Double area;
private List<String> callingCodes;
private String flag;
private Map<String, String> maps;
private Integer population;
private String fifa;
private Car car;
private List<String> timezones;
private List<String> continents;
public List<String> getTld() {
return tld;
}
public void setTld(List<String> tld) {
this.tld = tld;
}
public String getCca2() {
return cca2;
}
public void setCca2(String cca2) {
this.cca2 = cca2;
}
public String getCcn3() {
return ccn3;
}
public void setCcn3(String ccn3) {
this.ccn3 = ccn3;
}
public String getCca3() {
return cca3;
}
public void setCca3(String cca3) {
this.cca3 = cca3;
}
public String getCioc() {
return cioc;
}
public void setCioc(String cioc) {
this.cioc = cioc;
}
public Boolean getIndependent() {
return independent;
}
public void setIndependent(Boolean independent) {
this.independent = independent;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Boolean getUnMember() {
return unMember;
}
public void setUnMember(Boolean unMember) {
this.unMember = unMember;
}
public Idd getIdd() {
return idd;
}
public void setIdd(Idd idd) {
this.idd = idd;
}
public List<String> getCapital() {
return capital;
}
public void setCapital(List<String> capital) {
this.capital = capital;
}
public List<String> getAltSpellings() {
return altSpellings;
}
public void setAltSpellings(List<String> altSpellings) {
this.altSpellings = altSpellings;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getSubregion() {
return subregion;
}
public void setSubregion(String subregion) {
this.subregion = subregion;
}
public Boolean getLandlocked() {
return landlocked;
}
public void setLandlocked(Boolean landlocked) {
this.landlocked = landlocked;
}
public List<String> getBorders() {
return borders;
}
public void setBorders(List<String> borders) {
this.borders = borders;
}
public Double getArea() {
return area;
}
public void setArea(Double area) {
this.area = area;
}
public List<String> getCallingCodes() {
return callingCodes;
}
public void setCallingCodes(List<String> callingCodes) {
this.callingCodes = callingCodes;
}
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
public Map<String, String> getMaps() {
return maps;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
public Integer getPopulation() {
return population;
}
public void setPopulation(Integer population) {
this.population = population;
}
public String getFifa() {
return fifa;
}
public void setFifa(String fifa) {
this.fifa = fifa;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public List<String> getTimezones() {
return timezones;
}
public void setTimezones(List<String> timezones) {
this.timezones = timezones;
}
public List<String> getContinents() {
return continents;
}
public void setContinents(List<String> continents) {
this.continents = continents;
}
}
@@ -0,0 +1,203 @@
package com.restcountries.domain.v4;
import com.restcountries.domain.base.BaseCountryCore;
import com.restcountries.domain.v3.v31.CapitalInformation;
import com.restcountries.domain.v3.v31.Flag;
import io.micronaut.serde.annotation.Serdeable;
import java.util.List;
import java.util.Map;
@Serdeable.Serializable
public class Country extends BaseCountryCore {
private Name name;
private List<Currency> currencies;
private List<Language> languages;
private List<Translation> translations;
private List<Demonym> demonyms;
private List<Gini> gini;
private Flag flags;
private Flag coatOfArms;
private String startOfWeek;
private CapitalInformation capitalInfo;
private Map<String, String> postalCode;
private Geolocation geolocation;
private List<Religion> religion;
private List<Ethnicity> ethnicity;
private Government government;
private Double density;
private GDP gdp;
private String nationalHoliday;
private String anthem;
private List<RegionalBloc> regionalBlocs;
private Double hdi;
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
public List<Currency> getCurrencies() {
return currencies;
}
public void setCurrencies(List<Currency> currencies) {
this.currencies = currencies;
}
public List<Language> getLanguages() {
return languages;
}
public void setLanguages(List<Language> languages) {
this.languages = languages;
}
public List<Translation> getTranslations() {
return translations;
}
public void setTranslations(List<Translation> translations) {
this.translations = translations;
}
public List<Demonym> getDemonyms() {
return demonyms;
}
public void setDemonyms(List<Demonym> demonyms) {
this.demonyms = demonyms;
}
public List<Gini> getGini() {
return gini;
}
public void setGini(List<Gini> gini) {
this.gini = gini;
}
public Flag getFlags() {
return flags;
}
public void setFlags(Flag flags) {
this.flags = flags;
}
public Flag getCoatOfArms() {
return coatOfArms;
}
public void setCoatOfArms(Flag coatOfArms) {
this.coatOfArms = coatOfArms;
}
public String getStartOfWeek() {
return startOfWeek;
}
public void setStartOfWeek(String startOfWeek) {
this.startOfWeek = startOfWeek;
}
public CapitalInformation getCapitalInfo() {
return capitalInfo;
}
public void setCapitalInfo(CapitalInformation capitalInfo) {
this.capitalInfo = capitalInfo;
}
public Map<String, String> getPostalCode() {
return postalCode;
}
public void setPostalCode(Map<String, String> postalCode) {
this.postalCode = postalCode;
}
public Geolocation getGeolocation() {
return geolocation;
}
public void setGeolocation(Geolocation geolocation) {
this.geolocation = geolocation;
}
public List<Religion> getReligion() {
return religion;
}
public void setReligion(List<Religion> religion) {
this.religion = religion;
}
public List<Ethnicity> getEthnicity() {
return ethnicity;
}
public void setEthnicity(List<Ethnicity> ethnicity) {
this.ethnicity = ethnicity;
}
public Government getGovernment() {
return government;
}
public void setGovernment(Government government) {
this.government = government;
}
public Double getDensity() {
return density;
}
public void setDensity(Double density) {
this.density = density;
}
public GDP getGdp() {
return gdp;
}
public void setGdp(GDP gdp) {
this.gdp = gdp;
}
public String getNationalHoliday() {
return nationalHoliday;
}
public void setNationalHoliday(String nationalHoliday) {
this.nationalHoliday = nationalHoliday;
}
public String getAnthem() {
return anthem;
}
public void setAnthem(String anthem) {
this.anthem = anthem;
}
public List<RegionalBloc> getRegionalBlocs() {
return regionalBlocs;
}
public void setRegionalBlocs(List<RegionalBloc> regionalBlocs) {
this.regionalBlocs = regionalBlocs;
}
public Double getHdi() {
return hdi;
}
public void setHdi(Double hdi) {
this.hdi = hdi;
}
}
@@ -0,0 +1,34 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable.Serializable
public class Currency {
private String code;
private String name;
private String symbol;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
}
@@ -0,0 +1,34 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable.Serializable
public class Demonym {
private String lang;
private String male;
private String female;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getMale() {
return male;
}
public void setMale(String male) {
this.male = male;
}
public String getFemale() {
return female;
}
public void setFemale(String female) {
this.female = female;
}
}
@@ -0,0 +1,25 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable.Serializable
public class Ethnicity {
private String name;
private Double percentage;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPercentage() {
return percentage;
}
public void setPercentage(Double percentage) {
this.percentage = percentage;
}
}
@@ -0,0 +1,25 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable.Serializable
public class GDP {
private Long total;
private Long perCapita;
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
public Long getPerCapita() {
return perCapita;
}
public void setPerCapita(Long perCapita) {
this.perCapita = perCapita;
}
}
@@ -0,0 +1,25 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable.Serializable
public class Geolocation {
private Double latitude;
private Double longitude;
public Double getLatitude() {
return latitude;
}
public void setLatitude(Double latitude) {
this.latitude = latitude;
}
public Double getLongitude() {
return longitude;
}
public void setLongitude(Double longitude) {
this.longitude = longitude;
}
}
@@ -0,0 +1,25 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable.Serializable
public class Gini {
private String year;
private Double value;
public String getYear() {
return year;
}
public void setYear(String year) {
this.year = year;
}
public Double getValue() {
return value;
}
public void setValue(Double value) {
this.value = value;
}
}
@@ -0,0 +1,27 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
import java.util.List;
@Serdeable.Serializable
public class Government {
private String type;
private List<Leader> leaders;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public List<Leader> getLeaders() {
return leaders;
}
public void setLeaders(List<Leader> leaders) {
this.leaders = leaders;
}
}
@@ -0,0 +1,43 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable.Serializable
public class Language {
private String iso639_1;
private String iso639_2;
private String name;
private String nativeName;
public String getIso639_1() {
return iso639_1;
}
public void setIso639_1(String iso639_1) {
this.iso639_1 = iso639_1;
}
public String getIso639_2() {
return iso639_2;
}
public void setIso639_2(String iso639_2) {
this.iso639_2 = iso639_2;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNativeName() {
return nativeName;
}
public void setNativeName(String nativeName) {
this.nativeName = nativeName;
}
}
@@ -0,0 +1,25 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable.Serializable
public class Leader {
private String title;
private String name;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -0,0 +1,36 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
import java.util.List;
@Serdeable.Serializable
public class Name {
private String common;
private String official;
private List<NativeName> nativeName;
public String getCommon() {
return common;
}
public void setCommon(String common) {
this.common = common;
}
public String getOfficial() {
return official;
}
public void setOfficial(String official) {
this.official = official;
}
public List<NativeName> getNativeName() {
return nativeName;
}
public void setNativeName(List<NativeName> nativeName) {
this.nativeName = nativeName;
}
}
@@ -0,0 +1,34 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable.Serializable
public class NativeName {
private String lang;
private String official;
private String common;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getOfficial() {
return official;
}
public void setOfficial(String official) {
this.official = official;
}
public String getCommon() {
return common;
}
public void setCommon(String common) {
this.common = common;
}
}
@@ -0,0 +1,40 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
import java.util.ArrayList;
import java.util.List;
@Serdeable.Serializable
public class RegionalBloc {
private String acronym;
private String name;
private List<String> otherNames;
public String getAcronym() {
return acronym;
}
public void setAcronym(String acronym) {
this.acronym = acronym;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getOtherNames() {
if (otherNames == null) {
otherNames = new ArrayList<>();
}
return otherNames;
}
public void setOtherNames(List<String> otherNames) {
this.otherNames = otherNames;
}
}
@@ -0,0 +1,34 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable.Serializable
public class Religion {
private String name;
private Long population;
private Double percentage;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getPopulation() {
return population;
}
public void setPopulation(Long population) {
this.population = population;
}
public Double getPercentage() {
return percentage;
}
public void setPercentage(Double percentage) {
this.percentage = percentage;
}
}
@@ -0,0 +1,34 @@
package com.restcountries.domain.v4;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable.Serializable
public class Translation {
private String lang;
private String official;
private String common;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getOfficial() {
return official;
}
public void setOfficial(String official) {
this.official = official;
}
public String getCommon() {
return common;
}
public void setCommon(String common) {
this.common = common;
}
}
@@ -0,0 +1,198 @@
package com.restcountries.service.v4;
import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
import com.restcountries.domain.ICountryRestSymbols;
import com.restcountries.domain.v4.Country;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.text.Normalizer;
import java.util.HashSet;
import java.util.Set;
public class CountryServiceBaseV4 {
protected Set<Country> getByAlpha(String alpha, Set<Country> countries) {
var result = new HashSet<Country>();
for (var country : countries) {
if ((country.getCca2() != null && country.getCca2().equalsIgnoreCase(alpha)) ||
(country.getCcn3() != null && country.getCcn3().equalsIgnoreCase(alpha)) ||
(country.getCca3() != null && country.getCca3().equalsIgnoreCase(alpha)) ||
(country.getCioc() != null && country.getCioc().equalsIgnoreCase(alpha))
) {
result.add(country);
}
}
return result;
}
protected Set<Country> getByCodeList(String codeList, Set<Country> countries) {
Set<Country> result = new HashSet<>();
if (codeList == null) {
return result;
}
String[] codes = codeList.split(ICountryRestSymbols.COLON);
for (String code : codes) {
result.addAll(getByAlpha(code, countries));
}
return result;
}
protected Set<Country> getByName(String name, boolean fullText, Set<Country> countries) {
if (fullText) {
return fulltextSearch(name, countries);
} else {
return substringSearch(name, countries);
}
}
private Set<Country> fulltextSearch(String name, Set<Country> countries) {
Set<Country> result = new HashSet<>();
for (var country : countries) {
if (name.equalsIgnoreCase(country.getName().getCommon()) ||
name.equalsIgnoreCase(country.getName().getOfficial())) {
result.add(country);
return result;
}
}
return result;
}
private Set<Country> substringSearch(String name, Set<Country> countries) {
Set<Country> result = new HashSet<>();
for (var country : countries) {
if (country.getName().getCommon().toLowerCase().contains(name.toLowerCase()) ||
country.getName().getOfficial().toLowerCase().contains(name.toLowerCase())) {
result.add(country);
}
for (String alternative : country.getAltSpellings()) {
if (alternative.toLowerCase().contains(name.toLowerCase())) {
result.add(country);
}
}
}
return result;
}
protected Set<Country> getByCurrency(String currency, Set<Country> countries) {
Set<Country> result = new HashSet<>();
for (var country : countries) {
if (country.getCurrencies() != null) {
for (var c : country.getCurrencies()) {
if ((c.getCode() != null && c.getCode().equalsIgnoreCase(currency)) ||
(c.getName() != null && c.getName().toLowerCase().contains(currency.toLowerCase()))) {
result.add(country);
}
}
}
}
return result;
}
protected Set<Country> getByCapital(String capital, Set<Country> countries) {
Set<Country> result = new HashSet<>();
for (var country : countries) {
if (country.getCapital() != null) {
for (String countryCapital : country.getCapital()) {
if (normalize(countryCapital.toLowerCase()).contains(normalize(capital.toLowerCase()))) {
result.add(country);
}
}
}
}
return result;
}
protected Set<Country> getByRegion(String region, Set<Country> countries) {
Set<Country> result = new HashSet<>();
for (var country : countries) {
if ((country.getRegion() != null && country.getRegion().toLowerCase().contains(region.toLowerCase())) ||
(country.getSubregion() != null && country.getSubregion().equalsIgnoreCase(region))) {
result.add(country);
}
}
return result;
}
protected Set<Country> getBySubregion(String subregion, Set<Country> countries) {
Set<Country> result = new HashSet<>();
for (var country : countries) {
if (country.getSubregion() != null &&
(country.getSubregion().toLowerCase().contains(subregion.toLowerCase()) ||
country.getSubregion().equalsIgnoreCase(subregion))) {
result.add(country);
}
}
return result;
}
protected Set<Country> getByLanguage(String language, Set<Country> countries) {
Set<Country> result = new HashSet<>();
for (var country : countries) {
if (country.getLanguages() != null) {
for (var lang : country.getLanguages()) {
if ((lang.getName() != null && lang.getName().equalsIgnoreCase(language)) ||
(lang.getIso639_1() != null && lang.getIso639_1().equalsIgnoreCase(language)) ||
(lang.getIso639_2() != null && lang.getIso639_2().equalsIgnoreCase(language))) {
result.add(country);
}
}
}
}
return result;
}
protected Set<Country> getByDemonym(String demonym, Set<Country> countries) {
Set<Country> result = new HashSet<>();
for (var country : countries) {
if (country.getDemonyms() != null) {
for (var d : country.getDemonyms()) {
if ((d.getMale() != null && d.getMale().toLowerCase().contains(demonym.toLowerCase())) ||
(d.getFemale() != null && d.getFemale().toLowerCase().contains(demonym.toLowerCase()))) {
result.add(country);
}
}
}
}
return result;
}
protected Set<Country> getByTranslation(String translation, Set<Country> countries) {
Set<Country> result = new HashSet<>();
for (var country : countries) {
if (country.getTranslations() != null) {
for (var t : country.getTranslations()) {
if ((t.getOfficial() != null && t.getOfficial().toLowerCase().contains(translation.toLowerCase())) ||
(t.getCommon() != null && t.getCommon().toLowerCase().contains(translation.toLowerCase()))) {
result.add(country);
}
}
}
}
return result;
}
protected String normalize(String string) {
return Normalizer.normalize(string, Normalizer.Form.NFD)
.replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
}
protected Set<Country> loadJson(String filename) {
Set<Country> countries = new HashSet<>();
InputStream is = CountryServiceBaseV4.class.getClassLoader().getResourceAsStream(filename);
Gson gson = new Gson();
try {
JsonReader reader = new JsonReader(new InputStreamReader(is, StandardCharsets.UTF_8));
reader.beginArray();
while (reader.hasNext()) {
Country country = gson.fromJson(reader, Country.class);
countries.add(country);
}
} catch (Exception e) {
e.printStackTrace();
}
return countries;
}
}
@@ -0,0 +1,86 @@
package com.restcountries.service.v4;
import com.restcountries.domain.v4.Country;
import java.text.Normalizer;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
public class CountryServiceV4 extends CountryServiceBaseV4 {
private static Set<Country> countries;
private CountryServiceV4() {
initialize();
}
private static class InstanceHolder {
private static final CountryServiceV4 INSTANCE = new CountryServiceV4();
}
public static CountryServiceV4 getInstance() {
return InstanceHolder.INSTANCE;
}
public Set<Country> getAll() {
return countries;
}
public Set<Country> getByAlpha(String alpha) {
return super.getByAlpha(alpha, countries);
}
public Set<Country> getByName(String name, boolean isFullText) {
return super.getByName(name, isFullText, countries);
}
public Set<Country> getByCurrency(String currency) {
return super.getByCurrency(currency, countries);
}
public Set<Country> getByCodeList(String codeList) {
return super.getByCodeList(codeList, countries);
}
public Set<Country> getByCapital(String capital) {
return super.getByCapital(capital, countries);
}
public Set<Country> getByRegion(String region) {
return super.getByRegion(region, countries);
}
public Set<Country> getBySubregion(String subregion) {
return super.getBySubregion(subregion, countries);
}
public Set<Country> getByLanguage(String language) {
return super.getByLanguage(language, countries);
}
public Set<Country> getByDemonym(String demonym) {
return super.getByDemonym(demonym, countries);
}
public Set<Country> getByTranslation(String translation) {
return super.getByTranslation(translation, countries);
}
public Set<Country> getIndependent(boolean status) {
return countries.stream().filter(country -> {
var independent = Boolean.TRUE.equals(country.getIndependent());
return independent == status;
}).collect(Collectors.toSet());
}
@Override
protected String normalize(String string) {
return Normalizer.normalize(string, Normalizer.Form.NFD)
.replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
}
private void initialize() {
countries = super.loadJson("countriesV4.json");
}
}
File diff suppressed because it is too large Load Diff
-303
View File
@@ -1,303 +0,0 @@
REST Countries 🇵🇪
=======
Get information about countries via a RESTful API
*Current version: 3.1*
# About this Project
This project is inspired on restcountries.eu by Fayder Florez. Although the original project has now
moved to
a subscription base API, this project is still Open Source and Free to use.
## Important Information
* The structure of V2 has been reverted to its original form from the [Original Project] to maintain
compatibility.
* ***Only the latest version will receive updates and improvements.***
# REST Countries
You can access API through https://restcountries.com/v3.1/all but in order to get a faster response,
you should filter the results by the fields you need.
Like
``` html
https://restcountries.com/v3.1/all?fields=name,flags`
```
# Contributing
Any help is always welcome! Just edit the relevant file and create a new Merge Request or you can
also
donate using [Patreon](https://www.patreon.com/amatos)
or [PayPal](https://www.paypal.me/amatosg/15).
# Donations
This are getting out of control (in a positive way).
I'm getting about 4 million hits **each day** and that means CPU ussage (sometimes at 99%) and also
bandwidth consumption (120 GB **per day!**) so costs have obviously increased. **Please**, consider
making a donation on [Patreon](https://www.patreon.com/amatos)
or [PayPal](https://www.paypal.me/amatosg/15). This will help me pay the server's bills
# Fields (mandatory)
You can check the [FIELDS.md](https://gitlab.com/restcountries/restcountries/-/blob/master/FIELDS.md) file to get a description for each field (thanks to
@ePascalC!). You **must** specify the fields you need (up to 10 fields) when calling the `all` endpoints.
# API Endpoints
## Using this Project
- [Famosos](https://famosos.com)
- [Cultural Care](https://www.culturalcare.world/)
- [Covidata](https://worldcovidata.com/)
- [Asendia](https://tracking.asendia.com)
- [Picker](https://mwb.pickerexpress.com/#/login)
# Endpoints
Below are described the REST endpoints available that you can use to search for countries
## Latest added Enpoint
### Independent
Now you can get all independent (or not independent) countries by calling this endpoint:
``` html
https://restcountries.com/v3.1/independent?status=true
```
If you don't specify the status, true will be taken as default. You can mix it with the `fields`
filter like this:
``` html
https://restcountries.com/v3.1/independent?status=true&fields=languages,capital
```
## All
You **must** specify the fields you need (up to 10 fields) when calling the `all` endpoints,
otherwise you'll get a `bad request` response. Please see [this issue](https://gitlab.com/restcountries/restcountries/-/issues/265)
for more information. This applies to all versions.
``` html
https://restcountries.com/v3.1/all
```
## Name
**Search** by country name. If you want to get an exact match, use the next endpoint. It can be the
common or official value
``` html
https://restcountries.com/v3.1/name/{name}
```
``` html
https://restcountries.com/v3.1/name/eesti
```
``` html
https://restcountries.com/v3.1/name/deutschland
```
## Full Name
Search by country's full name. It can be the common or official value
``` html
https://restcountries.com/v3.1/name/{name}?fullText=true
```
``` html
https://restcountries.com/v3.1/name/aruba?fullText=true
```
## Code
Search by cca2, ccn3, cca3 or cioc country code (yes, any!)
``` html
https://restcountries.com/v3.1/alpha/{code}
```
``` html
https://restcountries.com/v3.1/alpha/co
```
``` html
https://restcountries.com/v3.1/alpha/col
```
``` html
https://restcountries.com/v3.1/alpha/170
```
## List of codes
Search by cca2, ccn3, cca3 or cioc country code (yes, any!)
``` html
https://restcountries.com/v3.1/alpha?codes={code},{code},{code}
```
``` html
https://restcountries.com/v3.1/alpha?codes=170,no,est,pe
```
## Currency
Search by currency code or name
``` html
https://restcountries.com/v3.1/currency/{currency}
```
``` html
https://restcountries.com/v3.1/currency/cop
```
## Demonym
Now you can search by how a citizen is called.
``` html
https://restcountries.com/v3.1/demonym/{demonym}
```
``` html
https://restcountries.com/v3.1/demonym/peruvian
```
## Language
Search by language code or name
``` html
https://restcountries.com/v3.1/lang/{language}
```
``` html
https://restcountries.com/v3.1/lang/cop
```
``` html
https://restcountries.com/v3.1/lang/spanish
```
## Capital city
Search by capital city
``` html
https://restcountries.com/v3.1/capital/{capital}
```
``` html
https://restcountries.com/v3.1/capital/tallinn
```
## Calling code
In version 3, calling codes are in the _idd_ object. There is no implementation
to search by calling codes in V3.
## Region
Search by region (replace X with the version you want to use)
``` html
https://restcountries.com/v3.1/region/{region}
```
``` html
https://restcountries.com/v3.1/region/europe
```
## Subregions
You can search by subregions (replace X with the version you want to use)
``` html
https://restcountries.com/v3.1/subregion/{subregion}
```
``` html
https://restcountries.com/v3.1/subregion/Northern Europe
```
## Translation
You can search by any translation name
``` html
https://restcountries.com/v3.1/translation/{translation}
```
``` html
https://restcountries.com/v3.1/translation/germany
```
``` html
https://restcountries.com/v3.1/translation/alemania
```
``` html
https://restcountries.com/v3.1/translation/Saksamaa
```
## Filter Response
You can filter the output of your request to include only the specified fields.
``` html
https://restcountries.com/v3.1/{service}?fields={field},{field},{field}
```
``` html
https://restcountries.com/v3.1/all?fields=name,capital,currencies
```
## REST Countries Typed API Package
Yusif Aliyev from Azerbaijan created [an npm package](https://www.npmjs.com/package/@yusifaliyevpro/countries)
which provides TypeScript support for the REST Countries API. Everyone can use
the package for their own purpose.
This package offers full type and autocomplete support for anyone using
JavaScript or TypeScript. Users no longer need to spend time reading
documentation or manually writing API URLs and types. You can easily use
all the package's functionalities by calling its functions.
He is also open to contributing further improvements.
You can find the code [here](https://github.com/yusifaliyevpro/countries)
## Similar projects
* [REST Countries] (original project)
* [Countries of the world]
* [REST Countries Node.js]
* [REST Countries Ruby]
* [REST Countries Go]
* [REST Countries Python]
* [world-currencies]
[world-currencies]: https://github.com/wiredmax/world-currencies
[REST Countries Node.js]: https://github.com/aredo/restcountries
[REST Countries Ruby]: https://github.com/davidesantangelo/restcountry
[REST Countries Go]: https://github.com/alediaferia/gocountries
[REST Countries Python]: https://github.com/SteinRobert/python-restcountries
[Countries of the world]: http://countries.petethompson.net
[REST Countries]: https://github.com/apilayer/restcountries
[Original Project]: https://github.com/apilayer/restcountries/
[donation]: https://www.paypal.me/amatosg/15
[donate]: https://www.paypal.me/amatosg/15
+601 -49
View File
@@ -1,63 +1,615 @@
<!doctype html>
<html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>REST Countries</title>
<!-- Flatdoc -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src='https://cdn.rawgit.com/rstacruz/flatdoc/v0.9.0/legacy.js'></script>
<script src='https://cdn.rawgit.com/rstacruz/flatdoc/v0.9.0/flatdoc.js'></script>
<!-- Flatdoc theme -->
<link href='https://cdn.rawgit.com/rstacruz/flatdoc/v0.9.0/theme-white/style.css' rel='stylesheet'>
<script src='https://cdn.rawgit.com/rstacruz/flatdoc/v0.9.0/theme-white/script.js'></script>
<!-- Meta -->
<meta content="REST Countries" property="og:title">
<meta content="Get information about countries via a RESTful API" name="description">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>REST Countries - Get information about countries via a RESTful API</title>
<meta name="description"
content="Get information about countries via a RESTful API. Access information about 250+ countries including flags, languages, currencies, and more.">
<meta name="keywords" content="rest,api,countries,world,json">
<meta name="author" content="Fayder Florez">
<link rel="shortcut icon" href="img/favicon.ico"/>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Libre+Baskerville:wght@400;700&display=swap"
rel="stylesheet">
<style>
:root {
--bg: #fafaf8;
--text: #1a1a1a;
--accent: #2563eb;
--accent-dark: #1e40af;
--warning: #d97706;
--border: #e0e0e0;
--subtle: #666;
--card-bg: #fff;
}
<!-- Custom -->
<link rel="shortcut icon" href="img/favicon.ico" />
<script src="https://checkout.stripe.com/checkout.js"></script>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
<!-- Initializer -->
<script>
Flatdoc.run({
fetcher: Flatdoc.file('flatdoc.md')
});
</script>
<style type="text/css" media="screen">
body {
font-family: Verdana, sans-serif;
font-family: 'IBM Plex Mono', monospace;
background: var(--bg);
color: var(--text);
line-height: 1.7;
font-size: 15px;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
height: 2px;
background: var(--accent);
z-index: 100;
}
.container {
max-width: 1000px;
margin: 0 auto;
padding: 0 2rem;
}
/* Announcement Banner */
.announcement-banner {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
border-bottom: 2px solid var(--warning);
padding: 1.5rem 0;
animation: slideDown 0.6s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.announcement-content {
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
}
.announcement-badge {
background: var(--warning);
color: white;
padding: 0.35rem 0.8rem;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.1em;
font-weight: 600;
flex-shrink: 0;
}
.announcement-text {
flex: 1;
min-width: 250px;
}
.announcement-text strong {
font-weight: 600;
}
/* Header */
.header {
padding: 4rem 0 3rem;
animation: fadeIn 0.8s ease-out 0.2s backwards;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.logo {
font-family: 'Libre Baskerville', serif;
font-size: 3rem;
font-weight: 700;
letter-spacing: -0.02em;
margin-bottom: 1rem;
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-dark) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.tagline {
font-size: 1.25rem;
color: var(--subtle);
margin-bottom: 2rem;
}
.stats {
display: flex;
gap: 3rem;
flex-wrap: wrap;
margin-top: 2rem;
}
.stat {
flex: 1;
min-width: 150px;
}
.stat-number {
font-family: 'Libre Baskerville', serif;
font-size: 2rem;
font-weight: 700;
color: var(--accent);
display: block;
margin-bottom: 0.25rem;
}
.stat-label {
font-size: 0.875rem;
color: var(--subtle);
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Sections */
.section {
padding: 4rem 0;
animation: fadeIn 1s ease-out 0.4s backwards;
}
.section-title {
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--subtle);
margin-bottom: 2rem;
font-weight: 500;
}
.code-block {
background: var(--card-bg);
border: 1px solid var(--border);
padding: 1.5rem;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.9rem;
overflow-x: auto;
margin-bottom: 1rem;
position: relative;
}
.code-block code {
color: var(--text);
display: block;
white-space: pre;
}
.code-comment {
color: var(--subtle);
}
/* Documentation Cards */
.doc-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.doc-card {
background: var(--card-bg);
border: 1px solid var(--border);
padding: 1.5rem;
transition: all 0.3s ease;
}
.doc-card:hover {
border-color: var(--accent);
}
.doc-title {
font-weight: 600;
margin-bottom: 0.75rem;
font-size: 1rem;
}
.doc-description {
font-size: 0.9rem;
color: var(--subtle);
line-height: 1.6;
}
.endpoint {
background: #f3f4f6;
padding: 0.4rem 0.6rem;
font-size: 0.85rem;
display: inline-block;
margin-top: 0.75rem;
color: var(--accent-dark);
}
.endpoint-example {
font-size: 0.8rem;
color: var(--subtle);
margin-top: 0.5rem;
}
/* Info box */
.info-box {
background: #eff6ff;
border-left: 3px solid var(--accent);
padding: 1rem;
margin-bottom: 2rem;
font-size: 0.9rem;
}
.info-box a {
color: var(--accent);
text-decoration: underline;
}
/* Similar projects */
.similar-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.similar-grid a {
color: var(--accent);
text-decoration: none;
font-size: 0.9rem;
padding: 0.5rem 0;
transition: color 0.2s ease;
}
.similar-grid a:hover {
color: var(--accent-dark);
}
/* Footer */
.footer {
padding: 3rem 0;
border-top: 1px solid var(--border);
margin-top: 4rem;
}
.footer-content {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 2rem;
}
.footer-section {
flex: 1;
min-width: 200px;
}
.footer-title {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.08em;
margin-bottom: 1rem;
font-weight: 600;
}
.footer-links {
list-style: none;
}
.footer-links li {
margin-bottom: 0.5rem;
}
.footer-links a {
color: var(--subtle);
text-decoration: none;
font-size: 0.9rem;
transition: color 0.2s ease;
}
.footer-links a:hover {
color: var(--accent);
}
.footer-bottom {
margin-top: 3rem;
padding-top: 2rem;
border-top: 1px solid var(--border);
text-align: center;
color: var(--subtle);
font-size: 0.875rem;
}
@media (max-width: 768px) {
.logo {
font-size: 2.25rem;
}
.stats {
gap: 2rem;
}
.stat {
min-width: 120px;
}
.announcement-content {
flex-direction: column;
align-items: flex-start;
}
}
</style>
</head>
<body role='flatdoc'>
<div class='header'>
<div class='left'>
<h1>REST Countries</h1>
<ul>
<li><a href='https://gitlab.com/amatos/rest-countries'>View on GitLab</a></li>
<li><a href='https://gitlab.com/amatos/rest-countries/-/issues'>Issues</a></li>
</ul>
<body>
<!-- Announcement Banner -->
<div class="announcement-banner">
<div class="container">
<div class="announcement-content">
<span class="announcement-badge">v4 Preview</span>
<div class="announcement-text">
<strong>RestCountries v4 has been released.</strong> This version is currently in preview and should not
be used in production yet. Please report any issues or feature requests through the <a
href="https://gitlab.com/restcountries/restcountries/-/issues"
style="color: var(--accent); text-decoration: underline;">GitLab issues board</a>.
</div>
</div>
</div>
</div>
<div class='content-root'>
<div class='menubar'>
<div class='menu section' role='flatdoc-menu'></div>
</div>
<div role='flatdoc-content' class='content'></div>
</div>
<!-- Header -->
<header class="header">
<div class="container">
<h1 class="logo">REST Countries</h1>
<p class="tagline">Get information about countries via a RESTful API</p>
<p style="color: var(--subtle); font-size: 0.9rem;">Current version: v3.1 &mdash; v4 in preview</p>
<script src="js/restc.min.js" type="text/javascript" charset="utf-8" async defer></script>
<div class="stats">
<div class="stat">
<span class="stat-number">4M+</span>
<span class="stat-label">Daily Requests</span>
</div>
<div class="stat">
<span class="stat-number">250+</span>
<span class="stat-label">Countries</span>
</div>
<div class="stat">
<span class="stat-number">Open Source</span>
<span class="stat-label">Free to Use</span>
</div>
</div>
</div>
</header>
<!-- Quick Start -->
<section class="section">
<div class="container">
<h2 class="section-title">Quick Start</h2>
<div class="info-box">
<strong>Fields filter:</strong> You <strong>must</strong> specify the fields you need (up to 10 fields) when
calling the <code>all</code> endpoint, otherwise you'll get a <code>bad request</code> response. See <a
href="https://gitlab.com/restcountries/restcountries/-/issues/265">this issue</a> for more information.
</div>
<div class="code-block">
<code><span class="code-comment"># Get all countries (filtered by fields)</span>
https://restcountries.com/v3.1/all?fields=name,capital,currencies
<span class="code-comment"># Get country by name</span>
https://restcountries.com/v3.1/name/peru
<span class="code-comment"># Get country by code</span>
https://restcountries.com/v3.1/alpha/co
<span class="code-comment"># Filter response fields</span>
https://restcountries.com/v3.1/{service}?fields={field},{field},{field}</code>
</div>
<p style="margin-top: 1rem; font-size: 0.9rem; color: var(--subtle);">
Check the <a href="https://gitlab.com/restcountries/restcountries/-/blob/master/FIELDS.md"
style="color: var(--accent);">FIELDS.md</a> file for a description of each field.
</p>
</div>
</section>
<!-- API Endpoints -->
<section class="section">
<div class="container">
<h2 class="section-title">API Endpoints</h2>
<div class="doc-grid">
<div class="doc-card">
<div class="doc-title">All Countries</div>
<div class="doc-description">Retrieve information about all countries. Must specify fields.</div>
<code class="endpoint">/v3.1/all?fields=name,flags</code>
</div>
<div class="doc-card">
<div class="doc-title">By Name</div>
<div class="doc-description">Search by country name (common or official value)</div>
<code class="endpoint">/v3.1/name/{name}</code>
<div class="endpoint-example">e.g. /v3.1/name/eesti</div>
</div>
<div class="doc-card">
<div class="doc-title">Full Name</div>
<div class="doc-description">Search by country's full name (common or official)</div>
<code class="endpoint">/v3.1/name/{name}?fullText=true</code>
<div class="endpoint-example">e.g. /v3.1/name/aruba?fullText=true</div>
</div>
<div class="doc-card">
<div class="doc-title">By Code</div>
<div class="doc-description">Search by cca2, ccn3, cca3 or cioc country code</div>
<code class="endpoint">/v3.1/alpha/{code}</code>
<div class="endpoint-example">e.g. /v3.1/alpha/co, /v3.1/alpha/col, /v3.1/alpha/170</div>
</div>
<div class="doc-card">
<div class="doc-title">List of Codes</div>
<div class="doc-description">Search by multiple country codes at once</div>
<code class="endpoint">/v3.1/alpha?codes={code},{code}</code>
<div class="endpoint-example">e.g. /v3.1/alpha?codes=170,no,est,pe</div>
</div>
<div class="doc-card">
<div class="doc-title">By Currency</div>
<div class="doc-description">Search by currency code or name</div>
<code class="endpoint">/v3.1/currency/{currency}</code>
<div class="endpoint-example">e.g. /v3.1/currency/cop</div>
</div>
<div class="doc-card">
<div class="doc-title">By Language</div>
<div class="doc-description">Search by language code or name</div>
<code class="endpoint">/v3.1/lang/{language}</code>
<div class="endpoint-example">e.g. /v3.1/lang/spanish</div>
</div>
<div class="doc-card">
<div class="doc-title">By Capital City</div>
<div class="doc-description">Search by capital city</div>
<code class="endpoint">/v3.1/capital/{capital}</code>
<div class="endpoint-example">e.g. /v3.1/capital/tallinn</div>
</div>
<div class="doc-card">
<div class="doc-title">By Region</div>
<div class="doc-description">Filter countries by region</div>
<code class="endpoint">/v3.1/region/{region}</code>
<div class="endpoint-example">e.g. /v3.1/region/europe</div>
</div>
<div class="doc-card">
<div class="doc-title">By Subregion</div>
<div class="doc-description">Filter countries by subregion</div>
<code class="endpoint">/v3.1/subregion/{subregion}</code>
<div class="endpoint-example">e.g. /v3.1/subregion/Northern Europe</div>
</div>
<div class="doc-card">
<div class="doc-title">By Demonym</div>
<div class="doc-description">Search by how a citizen is called</div>
<code class="endpoint">/v3.1/demonym/{demonym}</code>
<div class="endpoint-example">e.g. /v3.1/demonym/peruvian</div>
</div>
<div class="doc-card">
<div class="doc-title">By Translation</div>
<div class="doc-description">Search by any translation name</div>
<code class="endpoint">/v3.1/translation/{translation}</code>
<div class="endpoint-example">e.g. /v3.1/translation/alemania</div>
</div>
<div class="doc-card">
<div class="doc-title">Independent</div>
<div class="doc-description">Get all independent (or non-independent) countries</div>
<code class="endpoint">/v3.1/independent?status=true</code>
<div class="endpoint-example">Combine with fields: ?status=true&amp;fields=languages,capital</div>
</div>
<div class="doc-card">
<div class="doc-title">Filter Response</div>
<div class="doc-description">Filter the output to include only specified fields</div>
<code class="endpoint">/v3.1/{service}?fields={field},{field}</code>
<div class="endpoint-example">e.g. /v3.1/all?fields=name,capital,currencies</div>
</div>
</div>
</div>
</section>
<!-- Contributing -->
<section class="section">
<div class="container">
<h2 class="section-title">Contributing &amp; Donations</h2>
<p style="font-size: 0.95rem; margin-bottom: 1.5rem;">
Any help is always welcome! Just edit the relevant file and create a new Merge Request or you can also
donate using
<a href="https://www.patreon.com/amatos" style="color: var(--accent); text-decoration: none;">Patreon</a> or
<a href="https://www.paypal.me/amatosg/15" style="color: var(--accent); text-decoration: none;">PayPal</a>.
</p>
<div class="info-box">
Requests are growing fast &mdash; about 4 million hits <strong>each day</strong> and 120 GB of bandwidth
<strong>per day</strong>. Please consider making a
<a href="https://www.patreon.com/amatos">donation</a> to help cover server costs.
</div>
<h2 class="section-title" style="margin-top: 3rem;">Similar Projects</h2>
<div class="similar-grid">
<a href="https://github.com/apilayer/restcountries">REST Countries (original)</a>
<a href="http://countries.petethompson.net">Countries of the World</a>
<a href="https://github.com/aredo/restcountries">REST Countries Node.js</a>
<a href="https://github.com/davidesantangelo/restcountry">REST Countries Ruby</a>
<a href="https://github.com/alediaferia/gocountries">REST Countries Go</a>
<a href="https://github.com/SteinRobert/python-restcountries">REST Countries Python</a>
<a href="https://github.com/wiredmax/world-currencies">world-currencies</a>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-section">
<div class="footer-title">Product</div>
<ul class="footer-links">
<li><a href="https://gitlab.com/restcountries/restcountries/-/blob/master/FIELDS.md">Fields
Documentation</a></li>
<li>
<a href="https://gitlab.com/restcountries/restcountries/-/blob/master/README.md">Documentation</a>
</li>
<li><a href="https://gitlab.com/restcountries/restcountries/-/issues">Support</a></li>
</ul>
</div>
<div class="footer-section">
<div class="footer-title">Resources</div>
<ul class="footer-links">
<li><a href="https://gitlab.com/restcountries/restcountries">GitLab Repository</a></li>
<li><a href="https://gitlab.com/restcountries/restcountries/-/issues">Report Issues</a></li>
<li><a href="https://www.npmjs.com/package/@yusifaliyevpro/countries">TypeScript Package</a></li>
</ul>
</div>
<div class="footer-section">
<div class="footer-title">Support the Project</div>
<ul class="footer-links">
<li><a href="https://www.patreon.com/amatos">Donate via Patreon</a></li>
<li><a href="https://www.paypal.me/amatosg/15">Donate via PayPal</a></li>
<li><a href="https://gitlab.com/restcountries/restcountries/contribute">Contribute</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>REST Countries is an open-source project inspired by restcountries.eu by Fayder Florez.</p>
<p style="margin-top: 1rem; font-size: 0.85rem;">
<a href="/terms.html" style="color: var(--subtle); text-decoration: none;">Terms and Conditions</a>
&nbsp;&middot;&nbsp;
<a href="/privacy.html" style="color: var(--subtle); text-decoration: none;">Privacy Policy</a>
</p>
</div>
</div>
</footer>
</body>
</html>
</html>
+149
View File
@@ -0,0 +1,149 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Privacy Policy - RestCountries</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Libre+Baskerville:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg: #fafaf8;
--text: #1a1a1a;
--accent: #2563eb;
--border: #e0e0e0;
--subtle: #666;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'IBM Plex Mono', monospace;
background: var(--bg);
color: var(--text);
line-height: 1.8;
font-size: 14px;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
height: 2px;
background: var(--accent);
z-index: 100;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 3rem 2rem;
}
.header {
margin-bottom: 3rem;
}
.logo {
font-family: 'Libre Baskerville', serif;
font-size: 1.75rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.logo a {
color: var(--text);
text-decoration: none;
}
h1 {
font-family: 'Libre Baskerville', serif;
font-size: 2rem;
margin-bottom: 1rem;
}
.last-updated {
color: var(--subtle);
font-size: 0.9rem;
margin-bottom: 2rem;
}
h2 {
font-size: 1.25rem;
margin-top: 2.5rem;
margin-bottom: 1rem;
}
p {
margin-bottom: 1rem;
}
ul {
margin-left: 1.5rem;
margin-bottom: 1rem;
}
li {
margin-bottom: 0.5rem;
}
a {
color: var(--accent);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.highlight {
background: #f0fdf4;
border-left: 3px solid #16a34a;
padding: 1rem;
margin: 1rem 0;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo"><a href="/">RestCountries</a></div>
</div>
<h1>Privacy Policy</h1>
<p class="last-updated">Last updated: February 3, 2026</p>
<p>RestCountries respects your privacy. This policy explains how we collect, use, and protect your information.</p>
<h2>1. Information We Collect</h2>
<p>As of right now, there is no information that we collect other than the logs from the web server and they are removed every 15 minutes</p>
<h2>2. Children's Privacy</h2>
<p>Our service is not intended for users under 13 years of age. We do not knowingly collect information from children.</p>
<h2>3. International Users</h2>
<p>By using our service, you consent to the processing and storage of your data as described in this policy.</p>
<h2>4. Changes to This Policy</h2>
<p>We may update this privacy policy from time to time.</p>
<h2>5. Open Source</h2>
<p>RestCountries is an open-source project. Our code is publicly available on <a href="https://gitlab.com/restcountries/restcountries">GitLab</a>, allowing you to verify our security and privacy practices.</p>
<h2>6. Contact</h2>
<p>For privacy-related questions or concerns, please contact us through our <a href="https://gitlab.com/restcountries/restcountries/-/issues">GitLab repository</a>.</p>
<p style="margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--border); color: var(--subtle);">
<a href="/">← Back to Home</a>
</p>
</div>
</body>
</html>
+151
View File
@@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Terms of Service - RestCountries</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Libre+Baskerville:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg: #fafaf8;
--text: #1a1a1a;
--accent: #2563eb;
--border: #e0e0e0;
--subtle: #666;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'IBM Plex Mono', monospace;
background: var(--bg);
color: var(--text);
line-height: 1.8;
font-size: 14px;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
height: 2px;
background: var(--accent);
z-index: 100;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 3rem 2rem;
}
.header {
margin-bottom: 3rem;
}
.logo {
font-family: 'Libre Baskerville', serif;
font-size: 1.75rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.logo a {
color: var(--text);
text-decoration: none;
}
h1 {
font-family: 'Libre Baskerville', serif;
font-size: 2rem;
margin-bottom: 1rem;
}
.last-updated {
color: var(--subtle);
font-size: 0.9rem;
margin-bottom: 2rem;
}
h2 {
font-size: 1.25rem;
margin-top: 2.5rem;
margin-bottom: 1rem;
}
p {
margin-bottom: 1rem;
}
ul {
margin-left: 1.5rem;
margin-bottom: 1rem;
}
li {
margin-bottom: 0.5rem;
}
a {
color: var(--accent);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo"><a href="/">RestCountries</a></div>
</div>
<h1>Terms of Service</h1>
<p class="last-updated">Last updated: 18/02/2026</p>
<p>By accessing and using the RestCountries API, you agree to be bound by these Terms of Service.</p>
<h2>1. Acceptance of Terms</h2>
<p>By accessing and using the RestCountries API, you accept these terms in full. If you disagree with any part of these terms, you must not use our services.</p>
<h2>2. API Usage</h2>
<p>When using the RestCountries API, you agree to:</p>
<ul>
<li>Comply with all applicable laws and regulations</li>
</ul>
<h2>3. Prohibited Uses</h2>
<p>You may not use the API to:</p>
<ul>
<li>Violate any laws or regulations</li>
<li>Infringe on intellectual property rights</li>
<li>Distribute malware or harmful code</li>
<li>Scrape or harvest data for resale</li>
<li>Impersonate RestCountries or misrepresent your relationship with us</li>
</ul>
<h2>4. Changes to Terms</h2>
<p>We reserve the right to modify these terms at any time. Continued use of the API after changes constitutes acceptance of the new terms.</p>
<h2>5. Limitation of Liability</h2>
<p>RestCountries and its operators shall not be liable for any indirect, incidental, special, consequential, or punitive damages resulting from your use of the API.</p>
<h2>6. Contact</h2>
<p>For questions about these terms, please contact us through our <a href="https://gitlab.com/restcountries/restcountries/-/issues">GitLab repository</a>.</p>
<p style="margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--border); color: var(--subtle);">
<a href="/">← Back to Home</a>
</p>
</div>
</body>
</html>
@@ -0,0 +1,188 @@
package com.restcountries;
import com.restcountries.service.v4.CountryServiceV4;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RestCountriesV4Test {
@Test
void getAll() {
var countries = CountryServiceV4.getInstance().getAll();
assertFalse(countries.isEmpty());
Assertions.assertEquals(250, countries.size());
}
@Test
void getByAlpha() {
var countries = CountryServiceV4.getInstance().getByAlpha("CO");
assertFalse(countries.isEmpty());
Assertions.assertEquals("Colombia",
countries.stream().findFirst().map(country -> country.getName().getCommon()).orElseThrow());
}
@Test
void getByAlphaList() {
var countries = CountryServiceV4.getInstance().getByCodeList("de,co");
Assertions.assertEquals(2, countries.size());
}
@Test
void getCountryByName() {
var countries = CountryServiceV4.getInstance().getByName("Peru", false);
assertFalse(countries.isEmpty());
Assertions.assertEquals("Peru",
countries.stream().findFirst().map(country -> country.getName().getCommon()).orElseThrow());
}
@Test
void getByCodeList() {
var countries = CountryServiceV4.getInstance().getByCodeList("PE,NL,DE");
Assertions.assertNotNull(countries);
assertFalse(countries.isEmpty());
Assertions.assertEquals(3, countries.size());
var result = countries.stream().allMatch(country ->
country.getName().getCommon().contains("Peru") ||
country.getName().getCommon().contains("Netherlands") ||
country.getName().getCommon().contains("German")
);
assertTrue(result);
}
@Test
void getByCurrency() {
var countries = CountryServiceV4.getInstance().getByCurrency("EUR");
Assertions.assertNotNull(countries);
assertFalse(countries.isEmpty());
countries.forEach(country -> {
var hasEur = country.getCurrencies().stream()
.anyMatch(c -> "EUR".equals(c.getCode()));
assertTrue(hasEur);
});
}
@Test
void getByCapital() {
var countries = CountryServiceV4.getInstance().getByCapital("Helsinki");
var result = countries.stream()
.anyMatch(country -> country.getName().getCommon().equalsIgnoreCase("Finland"));
assertTrue(result);
Assertions.assertEquals(1, countries.size());
Assertions.assertEquals("Finland",
countries.stream().findFirst().map(country -> country.getName().getCommon()).orElseThrow());
}
@Test
void getByRegion() {
var countries = CountryServiceV4.getInstance().getByRegion("Asia");
assertFalse(countries.isEmpty());
var result = countries.stream()
.anyMatch(country -> country.getName().getCommon().equalsIgnoreCase("Bangladesh"));
assertTrue(result);
}
@Test
void getBySubregion() {
var countries = CountryServiceV4.getInstance().getBySubregion("Middle Africa");
assertFalse(countries.isEmpty());
var result = countries.stream()
.anyMatch(country -> country.getName().getCommon().equalsIgnoreCase("Gabon"));
assertTrue(result);
}
@Test
void getByLanguage() {
var countries = CountryServiceV4.getInstance().getByLanguage("german");
assertFalse(countries.isEmpty());
var result = countries.stream()
.anyMatch(country -> country.getName().getCommon().equalsIgnoreCase("Liechtenstein"));
assertTrue(result);
}
@Test
void getByDemonym() {
var countries = CountryServiceV4.getInstance().getByDemonym("chilean");
assertFalse(countries.isEmpty());
var result = countries.stream()
.anyMatch(country -> country.getName().getCommon().equalsIgnoreCase("Chile"));
assertTrue(result);
}
@Test
void getByTranslation() {
var countries = CountryServiceV4.getInstance().getByTranslation("Běloruská");
assertFalse(countries.isEmpty());
Assertions.assertEquals(1, countries.size());
Assertions.assertEquals("Belarus", countries.stream().findFirst().get().getName().getCommon());
}
@Test
void testFlags() {
var countries = CountryServiceV4.getInstance().getAll();
try {
var result = countries.stream().noneMatch(
country -> null == country.getFlags().getPng() || null == country.getFlags().getSvg()
|| null == country.getFlags().getAlt());
assertTrue(result);
} catch (Exception ex) {
Assertions.fail();
}
}
@Test
void testGeolocation() {
var countries = CountryServiceV4.getInstance().getAll();
var countriesWithGeo = countries.stream()
.filter(c -> c.getGeolocation() != null &&
c.getGeolocation().getLatitude() != null &&
c.getGeolocation().getLongitude() != null)
.count();
assertTrue(countriesWithGeo > 200);
}
@Test
void testCurrenciesAsArray() {
var countries = CountryServiceV4.getInstance().getByAlpha("US");
assertFalse(countries.isEmpty());
var us = countries.stream().findFirst().get();
assertFalse(us.getCurrencies().isEmpty());
var usd = us.getCurrencies().stream()
.filter(c -> "USD".equals(c.getCode()))
.findFirst();
assertTrue(usd.isPresent());
Assertions.assertEquals("United States dollar", usd.get().getName());
}
@Test
void testLanguagesAsArray() {
var countries = CountryServiceV4.getInstance().getByAlpha("CH");
assertFalse(countries.isEmpty());
var ch = countries.stream().findFirst().get();
assertFalse(ch.getLanguages().isEmpty());
var hasSwissGerman = ch.getLanguages().stream()
.anyMatch(l -> "Swiss German".equals(l.getName()));
assertTrue(hasSwissGerman);
}
@Test
void testNativeNameAsArray() {
var countries = CountryServiceV4.getInstance().getByAlpha("BE");
assertFalse(countries.isEmpty());
var be = countries.stream().findFirst().get();
assertFalse(be.getName().getNativeName().isEmpty());
var hasFrench = be.getName().getNativeName().stream()
.anyMatch(nn -> "fra".equals(nn.getLang()));
assertTrue(hasFrench);
}
@Test
void testIndependent() {
var countries = CountryServiceV4.getInstance().getIndependent(true);
assertFalse(countries.isEmpty());
var nonIndependent = CountryServiceV4.getInstance().getIndependent(false);
assertFalse(nonIndependent.isEmpty());
}
}
@@ -0,0 +1,323 @@
package com.restcountries;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
class V4JsonStructureTest {
private static final Logger log = LoggerFactory.getLogger(V4JsonStructureTest.class);
private static JsonArray countries;
@BeforeAll
static void loadJson() {
var is = V4JsonStructureTest.class.getClassLoader().getResourceAsStream("countriesV4.json");
assertNotNull(is, "countriesV4.json not found");
countries = JsonParser.parseReader(new InputStreamReader(is, StandardCharsets.UTF_8)).getAsJsonArray();
}
@Test
void testAllCountriesLoaded() {
assertTrue(countries.size() >= 200, "Should have at least 200 countries");
}
@Test
void testRequiredFields() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
assertTrue(c.has("name"), "Missing name field");
assertTrue(c.has("cca2"), "Missing cca2 field");
assertTrue(c.has("cca3"), "Missing cca3 field");
assertTrue(c.has("region"), "Missing region field");
}
}
@Test
void testNameStructure() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
JsonObject name = c.getAsJsonObject("name");
assertNotNull(name, "name should be an object");
assertTrue(name.has("common") && !name.get("common").isJsonNull(),
"name.common should not be null for " + c.get("cca2"));
assertTrue(name.has("official") && !name.get("official").isJsonNull(),
"name.official should not be null for " + c.get("cca2"));
String common = name.get("common").getAsString();
String official = name.get("official").getAsString();
assertFalse(common.isEmpty(), "name.common should not be empty");
assertFalse(official.isEmpty(), "name.official should not be empty");
assertTrue(name.has("nativeName"), "name.nativeName should exist");
assertTrue(name.get("nativeName").isJsonArray(),
"name.nativeName should be an array for " + c.get("cca2"));
for (JsonElement nn : name.getAsJsonArray("nativeName")) {
assertTrue(nn.isJsonObject(), "nativeName entry should be object");
JsonObject nnObj = nn.getAsJsonObject();
assertTrue(nnObj.has("lang"), "nativeName entry should have lang");
assertTrue(nnObj.has("official"), "nativeName entry should have official");
assertTrue(nnObj.has("common"), "nativeName entry should have common");
}
}
}
@Test
void testCurrenciesStructure() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
assertTrue(c.has("currencies"), "Missing currencies for " + c.get("cca2"));
assertTrue(c.get("currencies").isJsonArray(),
"currencies should be an array for " + c.get("cca2"));
for (JsonElement curr : c.getAsJsonArray("currencies")) {
assertTrue(curr.isJsonObject(), "currency entry should be object");
JsonObject currObj = curr.getAsJsonObject();
assertTrue(currObj.has("code"), "currency entry should have code");
assertTrue(currObj.has("name"), "currency entry should have name");
assertTrue(currObj.has("symbol"), "currency entry should have symbol");
}
}
}
@Test
void testLanguagesStructure() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
assertTrue(c.has("languages"), "Missing languages for " + c.get("cca2"));
assertTrue(c.get("languages").isJsonArray(),
"languages should be an array for " + c.get("cca2"));
for (JsonElement lang : c.getAsJsonArray("languages")) {
assertTrue(lang.isJsonObject(), "language entry should be object");
JsonObject langObj = lang.getAsJsonObject();
assertTrue(langObj.has("iso639_1"), "language entry should have iso639_1");
assertTrue(langObj.has("iso639_2"), "language entry should have iso639_2");
assertTrue(langObj.has("name"), "language entry should have name");
assertTrue(langObj.has("nativeName"), "language entry should have nativeName");
}
}
}
@Test
void testGeolocationStructure() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
assertTrue(c.has("geolocation"), "Missing geolocation for " + c.get("cca2"));
if (!c.get("geolocation").isJsonNull()) {
JsonObject geo = c.getAsJsonObject("geolocation");
assertTrue(geo.has("latitude"), "geolocation should have latitude");
assertTrue(geo.has("longitude"), "geolocation should have longitude");
}
}
}
@Test
void testReligionStructure() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
assertTrue(c.has("religion"), "Missing religion for " + c.get("cca2"));
assertTrue(c.get("religion").isJsonArray(),
"religion should be an array for " + c.get("cca2"));
for (JsonElement r : c.getAsJsonArray("religion")) {
assertTrue(r.isJsonObject(), "religion entry should be object");
JsonObject rObj = r.getAsJsonObject();
assertTrue(rObj.has("name"), "religion entry should have name");
assertTrue(rObj.has("population"), "religion entry should have population");
assertTrue(rObj.has("percentage"), "religion entry should have percentage");
}
}
}
@Test
void testEthnicityStructure() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
assertTrue(c.has("ethnicity"), "Missing ethnicity for " + c.get("cca2"));
assertTrue(c.get("ethnicity").isJsonArray(),
"ethnicity should be an array for " + c.get("cca2"));
for (JsonElement e : c.getAsJsonArray("ethnicity")) {
assertTrue(e.isJsonObject(), "ethnicity entry should be object");
JsonObject eObj = e.getAsJsonObject();
if(!eObj.has("percentage")) {
log.error("name: {}", e.toString());
}
assertTrue(eObj.has("name"), "ethnicity entry should have name");
assertTrue(eObj.has("percentage"), "ethnicity entry should have percentage");
}
}
}
@Test
void testGovernmentStructure() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
assertTrue(c.has("government"), "Missing government for " + c.get("cca2"));
if (!c.get("government").isJsonNull()) {
JsonObject gov = c.getAsJsonObject("government");
assertTrue(gov.has("type"), "government should have type");
assertTrue(gov.has("leaders"), "government should have leaders");
assertTrue(gov.get("leaders").isJsonArray(), "leaders should be an array");
for (JsonElement l : gov.getAsJsonArray("leaders")) {
assertTrue(l.isJsonObject(), "leader entry should be an object");
JsonObject leader = l.getAsJsonObject();
assertTrue(leader.has("title"), "leader should have title");
assertTrue(leader.has("name"), "leader should have name");
}
}
}
}
@Test
void testGdpStructure() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
assertTrue(c.has("gdp"), "Missing gdp for " + c.get("cca2"));
if (!c.get("gdp").isJsonNull()) {
JsonObject gdp = c.getAsJsonObject("gdp");
assertTrue(gdp.has("total"), "gdp should have total");
assertTrue(gdp.has("perCapita"), "gdp should have perCapita");
}
}
}
@Test
void testGiniStructure() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
assertTrue(c.has("gini"), "Missing gini for " + c.get("cca2"));
assertTrue(c.get("gini").isJsonArray(),
"gini should be an array for " + c.get("cca2"));
for (JsonElement g : c.getAsJsonArray("gini")) {
assertTrue(g.isJsonObject(), "gini entry should be object");
JsonObject gObj = g.getAsJsonObject();
assertTrue(gObj.has("year"), "gini entry should have year");
assertTrue(gObj.has("value"), "gini entry should have value");
}
}
}
@Test
void testTranslationsStructure() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
assertTrue(c.has("translations"), "Missing translations for " + c.get("cca2"));
assertTrue(c.get("translations").isJsonArray(),
"translations should be an array for " + c.get("cca2"));
for (JsonElement t : c.getAsJsonArray("translations")) {
assertTrue(t.isJsonObject(), "translation entry should be object");
JsonObject tObj = t.getAsJsonObject();
assertTrue(tObj.has("lang"), "translation entry should have lang");
assertTrue(tObj.has("official"), "translation entry should have official");
assertTrue(tObj.has("common"), "translation entry should have common");
}
}
}
@Test
void testDemonymsStructure() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
assertTrue(c.has("demonyms"), "Missing demonyms for " + c.get("cca2"));
assertTrue(c.get("demonyms").isJsonArray(),
"demonyms should be an array for " + c.get("cca2"));
for (JsonElement d : c.getAsJsonArray("demonyms")) {
assertTrue(d.isJsonObject(), "demonym entry should be object");
JsonObject dObj = d.getAsJsonObject();
assertTrue(dObj.has("lang"), "demonym entry should have lang");
assertTrue(dObj.has("male"), "demonym entry should have male");
assertTrue(dObj.has("female"), "demonym entry should have female");
}
}
}
@Test
void testArrayFieldsAreArrays() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
assertIsArrayIfPresent(c, "currencies");
assertIsArrayIfPresent(c, "languages");
assertIsArrayIfPresent(c, "gini");
assertIsArrayIfPresent(c, "translations");
assertIsArrayIfPresent(c, "demonyms");
assertIsArrayIfPresent(c, "religion");
assertIsArrayIfPresent(c, "ethnicity");
assertIsArrayIfPresent(c, "regionalBlocs");
assertIsArrayIfPresent(c, "callingCodes");
assertIsArrayIfPresent(c, "timezones");
assertIsArrayIfPresent(c, "borders");
assertIsArrayIfPresent(c, "continents");
}
}
@Test
void testCca2CodeLength() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
if (c.has("cca2") && !c.get("cca2").isJsonNull()) {
String cca2 = c.get("cca2").getAsString();
assertEquals(2, cca2.length(), "cca2 should be 2 chars: " + cca2);
}
}
}
@Test
void testCca3CodeLength() {
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
if (c.has("cca3") && !c.get("cca3").isJsonNull()) {
String cca3 = c.get("cca3").getAsString();
assertEquals(3, cca3.length(), "cca3 should be 3 chars: " + cca3);
}
}
}
@Test
void testNoDuplicateCca2() {
Set<String> seen = new HashSet<>();
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
if (c.has("cca2") && !c.get("cca2").isJsonNull()) {
String cca2 = c.get("cca2").getAsString();
assertTrue(seen.add(cca2), "Duplicate cca2: " + cca2);
}
}
}
@Test
void testNoDuplicateCca3() {
Set<String> seen = new HashSet<>();
for (JsonElement el : countries) {
JsonObject c = el.getAsJsonObject();
if (c.has("cca3") && !c.get("cca3").isJsonNull()) {
String cca3 = c.get("cca3").getAsString();
assertTrue(seen.add(cca3), "Duplicate cca3: " + cca3);
}
}
}
private void assertIsArrayIfPresent(JsonObject obj, String field) {
if (obj.has(field) && !obj.get(field).isJsonNull()) {
assertTrue(obj.get(field).isJsonArray(),
field + " should be an array for " + obj.get("cca2"));
}
}
}