From 60f3e4b73763c5029d77a07a90f5b0a13f476f42 Mon Sep 17 00:00:00 2001 From: Sorn Date: Mon, 8 Sep 2025 20:29:35 +0400 Subject: [PATCH] #107 Create FmpSector Value Object --- .../exceptions/FmpInvalidSectorException.java | 7 +++ .../dev/sorn/fmp4j/models/FmpCompany.java | 5 +- .../fmp4j/models/FmpEtfSectorWeighting.java | 5 +- .../java/dev/sorn/fmp4j/types/FmpSector.java | 45 +++++++++++++++++ .../dev/sorn/fmp4j/types/FmpSectorTest.java | 46 ++++++++++++++++++ .../java/dev/sorn/fmp4j/CompanyTestData.java | 3 +- .../fmp4j/EtfSectorWeightingTestData.java | 3 +- .../models/serialization/FmpCompany.snapshot | Bin 2299 -> 2405 bytes .../models/serialization/FmpCompany.version | Bin 8 -> 8 bytes .../FmpEtfSectorWeighting.snapshot | Bin 336 -> 442 bytes .../FmpEtfSectorWeighting.version | Bin 8 -> 8 bytes 11 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 src/main/java/dev/sorn/fmp4j/exceptions/FmpInvalidSectorException.java create mode 100644 src/main/java/dev/sorn/fmp4j/types/FmpSector.java create mode 100644 src/test/java/dev/sorn/fmp4j/types/FmpSectorTest.java diff --git a/src/main/java/dev/sorn/fmp4j/exceptions/FmpInvalidSectorException.java b/src/main/java/dev/sorn/fmp4j/exceptions/FmpInvalidSectorException.java new file mode 100644 index 00000000..06b21e39 --- /dev/null +++ b/src/main/java/dev/sorn/fmp4j/exceptions/FmpInvalidSectorException.java @@ -0,0 +1,7 @@ +package dev.sorn.fmp4j.exceptions; + +public class FmpInvalidSectorException extends FmpException { + public FmpInvalidSectorException(String message, Object... args) { + super(message, args); + } +} diff --git a/src/main/java/dev/sorn/fmp4j/models/FmpCompany.java b/src/main/java/dev/sorn/fmp4j/models/FmpCompany.java index 3813b1e0..95c90fcb 100644 --- a/src/main/java/dev/sorn/fmp4j/models/FmpCompany.java +++ b/src/main/java/dev/sorn/fmp4j/models/FmpCompany.java @@ -4,6 +4,7 @@ import dev.sorn.fmp4j.types.FmpCurrency; import dev.sorn.fmp4j.types.FmpCusip; import dev.sorn.fmp4j.types.FmpIsin; +import dev.sorn.fmp4j.types.FmpSector; import dev.sorn.fmp4j.types.FmpSymbol; import java.io.Serial; import java.time.LocalDate; @@ -30,7 +31,7 @@ public record FmpCompany( String website, String description, String ceo, - String sector, + FmpSector sector, String country, String fullTimeEmployees, String phone, @@ -47,5 +48,5 @@ public record FmpCompany( Boolean isFund) implements FmpModel { @Serial - private static final long serialVersionUID = 5L; + private static final long serialVersionUID = 6L; } diff --git a/src/main/java/dev/sorn/fmp4j/models/FmpEtfSectorWeighting.java b/src/main/java/dev/sorn/fmp4j/models/FmpEtfSectorWeighting.java index 23bbdff4..4323e581 100644 --- a/src/main/java/dev/sorn/fmp4j/models/FmpEtfSectorWeighting.java +++ b/src/main/java/dev/sorn/fmp4j/models/FmpEtfSectorWeighting.java @@ -1,9 +1,10 @@ package dev.sorn.fmp4j.models; +import dev.sorn.fmp4j.types.FmpSector; import dev.sorn.fmp4j.types.FmpSymbol; import java.io.Serial; -public record FmpEtfSectorWeighting(FmpSymbol symbol, String sector, Double weightPercentage) implements FmpModel { +public record FmpEtfSectorWeighting(FmpSymbol symbol, FmpSector sector, Double weightPercentage) implements FmpModel { @Serial - private static final long serialVersionUID = 2L; + private static final long serialVersionUID = 3L; } diff --git a/src/main/java/dev/sorn/fmp4j/types/FmpSector.java b/src/main/java/dev/sorn/fmp4j/types/FmpSector.java new file mode 100644 index 00000000..3e61ee03 --- /dev/null +++ b/src/main/java/dev/sorn/fmp4j/types/FmpSector.java @@ -0,0 +1,45 @@ +package dev.sorn.fmp4j.types; + +import static java.util.Arrays.stream; + +import com.fasterxml.jackson.annotation.JsonCreator; +import dev.sorn.fmp4j.exceptions.FmpInvalidSectorException; +import java.util.Objects; + +public enum FmpSector implements FmpValueObject { + BASIC_MATERIALS("Basic Materials"), + COMMUNICATION_SERVICES("Communication Services"), + CONSUMER_CYCLICAL("Consumer Cyclical"), + CONSUMER_DEFENSIVE("Consumer Defensive"), + ENERGY("Energy"), + FINANCIAL_SERVICES("Financial Services"), + HEALTHCARE("Healthcare"), + INDUSTRIALS("Industrials"), + REAL_ESTATE("Real Estate"), + TECHNOLOGY("Technology"), + UTILITIES("Utilities"); + + private final String value; + + FmpSector(String value) { + this.value = value; + } + + @JsonCreator + public static FmpSector sector(String value) { + return stream(values()) + .filter(sector -> Objects.equals(sector.value, value)) + .findFirst() + .orElseThrow(() -> new FmpInvalidSectorException("[%s] is not a valid sector", value)); + } + + @Override + public String value() { + return value; + } + + @Override + public String toString() { + return value(); + } +} diff --git a/src/test/java/dev/sorn/fmp4j/types/FmpSectorTest.java b/src/test/java/dev/sorn/fmp4j/types/FmpSectorTest.java new file mode 100644 index 00000000..7c9a6224 --- /dev/null +++ b/src/test/java/dev/sorn/fmp4j/types/FmpSectorTest.java @@ -0,0 +1,46 @@ +package dev.sorn.fmp4j.types; + +import static dev.sorn.fmp4j.types.FmpSector.sector; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import dev.sorn.fmp4j.exceptions.FmpInvalidSectorException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class FmpSectorTest { + @ParameterizedTest + @ValueSource( + strings = { + "Basic Materials", + "Communication Services", + "Consumer Cyclical", + "Consumer Defensive", + "Energy", + "Financial Services", + "Healthcare", + "Industrials", + "Real Estate", + "Technology", + "Utilities", + }) + void valid_sectors(String readable) { + // given // when + var sector = sector(readable); + + // then + assertEquals(readable, sector.value()); + assertEquals(readable, sector.toString()); + } + + @Test + void invalid_sector() { + // given + var invalid = "Invalid Sector"; + + // when // then + var e = assertThrows(FmpInvalidSectorException.class, () -> sector(invalid)); + assertEquals("[Invalid Sector] is not a valid sector", e.getMessage()); + } +} diff --git a/src/testFixtures/java/dev/sorn/fmp4j/CompanyTestData.java b/src/testFixtures/java/dev/sorn/fmp4j/CompanyTestData.java index ea114bf8..e17b2d4c 100644 --- a/src/testFixtures/java/dev/sorn/fmp4j/CompanyTestData.java +++ b/src/testFixtures/java/dev/sorn/fmp4j/CompanyTestData.java @@ -4,6 +4,7 @@ import static dev.sorn.fmp4j.types.FmpCurrency.USD; import static dev.sorn.fmp4j.types.FmpCusip.cusip; import static dev.sorn.fmp4j.types.FmpIsin.isin; +import static dev.sorn.fmp4j.types.FmpSector.sector; import static dev.sorn.fmp4j.types.FmpSymbol.symbol; import dev.sorn.fmp4j.models.FmpCompany; @@ -33,7 +34,7 @@ default FmpCompany aCompany() { "https://www.apple.com", "Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide. The company offers iPhone, a line of smartphones; Mac, a line of personal computers; iPad, a line of multi-purpose tablets; and wearables, home, and accessories comprising AirPods, Apple TV, Apple Watch, Beats products, and HomePod. It also provides AppleCare support and cloud services; and operates various platforms, including the App Store that allow customers to discov...", "Mr. Timothy D. Cook", - "Technology", + sector("Technology"), "US", "164000", "(408) 996-1010", diff --git a/src/testFixtures/java/dev/sorn/fmp4j/EtfSectorWeightingTestData.java b/src/testFixtures/java/dev/sorn/fmp4j/EtfSectorWeightingTestData.java index 7b7bb05b..dd488733 100644 --- a/src/testFixtures/java/dev/sorn/fmp4j/EtfSectorWeightingTestData.java +++ b/src/testFixtures/java/dev/sorn/fmp4j/EtfSectorWeightingTestData.java @@ -1,11 +1,12 @@ package dev.sorn.fmp4j; +import static dev.sorn.fmp4j.types.FmpSector.sector; import static dev.sorn.fmp4j.types.FmpSymbol.symbol; import dev.sorn.fmp4j.models.FmpEtfSectorWeighting; public interface EtfSectorWeightingTestData { default FmpEtfSectorWeighting anEtfSectorWeighting() { - return new FmpEtfSectorWeighting(symbol("SPY"), "Utilities", 2.45); + return new FmpEtfSectorWeighting(symbol("SPY"), sector("Utilities"), 2.45); } } diff --git a/src/testFixtures/resources/models/serialization/FmpCompany.snapshot b/src/testFixtures/resources/models/serialization/FmpCompany.snapshot index 1ac1f37c48b86d90af3c20d343eb23c286931511..b65d355619c49a5df90bab3a8ae5c691b469f88c 100644 GIT binary patch delta 229 zcmew@_*7_uJ|o*kgKbQc8JT6-Qj<&ai>xQVX8Od)KlvWBGQXgMu*TOz{V|%wg$#8J zf|~_cjxdWVF@iYC2%@-%L1uCTi^OC`4kJB5`}gZasvf$p18d=9U|`ml4D4tqVc^p+ zF|g27u(UMOH8e0ZfS5QrkwcuNu82Vnq~9$V>;MKR5Mp4cC}QBtN-Rs%%Sp^j*K^G) q%|(_hC}H3Vadr0a^Y`(0k1Szea&`nd1#HyhWgI$;e3LJ8XaE3^zB`Bj delta 129 zcmaDV^jmO(J|pW!gKbQ#g$#8JjFUN;KQZ!6{==-yFW?}o@%2!DjAk)NMqslZ%MoT# zMMe-u2|*MWG004w$09LVp2LV=!2bO@k*bI8>%dxgCns>oF$zqs=Md*DVc-f$P0q;6 T&&f}(ocx}{myvg}EvE(mkI*HF diff --git a/src/testFixtures/resources/models/serialization/FmpCompany.version b/src/testFixtures/resources/models/serialization/FmpCompany.version index 62f3e6fff7af10a35059124bcb1c4f486e18abed..fa902fa8b4ec3b0b75d9e4917b34547e68e2913d 100644 GIT binary patch literal 8 LcmZQz00TAv01f~L literal 8 LcmZQz00UM401W^J diff --git a/src/testFixtures/resources/models/serialization/FmpEtfSectorWeighting.snapshot b/src/testFixtures/resources/models/serialization/FmpEtfSectorWeighting.snapshot index c846e989eafe94777fead227d71cf26a2ed7ffd1..88e5a4a9adde012c4f25dc8caed1abc669d694ad 100644 GIT binary patch delta 150 zcmcb>w2OIyl|C~Q1G5hUTXAY~Nq$iYgMv>=YMFjx&T5f?!mVQZPL29wSTW&!x zSjEKXaK^fc`(>=bJO(HbVqmB!V&Kb4EKAhONz6;vbImKwMV2fmVc-l6@$~Tw@pKKI cxX)Cygh9v$q+B1QTtB#^C^IkJdg6}=0Q%%CQ2+n{ delta 74 zcmdnRe1U0#l_C=p1G5hUTXAY~Nq$iYgOE>FVp*boPGVlVesD=qW?uTl%y8Zk2F}ou e%$&@U%+%uIi3epSn=(qU7BbW^FiuWli~s;o=N9V# diff --git a/src/testFixtures/resources/models/serialization/FmpEtfSectorWeighting.version b/src/testFixtures/resources/models/serialization/FmpEtfSectorWeighting.version index ccfcbf4136d7788a93b96bb14565bcc4ddf6c4d7..29ce11cc9c2acfbfd357e04485500314b13afbb5 100644 GIT binary patch literal 8 LcmZQz00U+K01E&F literal 8 LcmZQz00Slf015yD