Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/dev/sorn/fmp4j/json/FmpJsonModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.databind.module.SimpleModule;
import dev.sorn.fmp4j.types.FmpIsin;
import dev.sorn.fmp4j.types.FmpSymbol;

public final class FmpJsonModule extends SimpleModule {

Expand All @@ -10,5 +11,6 @@ public final class FmpJsonModule extends SimpleModule {
public FmpJsonModule() {
super(FMP_JSON_MODULE_NAME);
this.addDeserializer(FmpIsin.class, new FmpJsonBlankAsNullStringDeserializer<>(FmpIsin::isin));
this.addDeserializer(FmpSymbol.class, new FmpSymbolDeserializer());
}
}
26 changes: 26 additions & 0 deletions src/main/java/dev/sorn/fmp4j/json/FmpSymbolDeserializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dev.sorn.fmp4j.json;

import static dev.sorn.fmp4j.types.FmpSymbol.symbol;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import dev.sorn.fmp4j.exceptions.FmpInvalidSymbolException;
import dev.sorn.fmp4j.types.FmpSymbol;
import java.io.IOException;

public class FmpSymbolDeserializer extends JsonDeserializer<FmpSymbol> {
@Override
public FmpSymbol deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
String value = p.getText();
if (value == null || value.trim().isEmpty()) {
return null; // or skip
}
try {
return symbol(value);
} catch (FmpInvalidSymbolException e) {
System.err.printf("Invalid symbol skipped: %s%n", value);
return null;
}
}
}
6 changes: 5 additions & 1 deletion src/main/java/dev/sorn/fmp4j/services/FmpService.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ protected final URI url() {

protected abstract Set<String> optionalParams();

protected R filter(R r) {
return r;
}

public final void param(String key, Object value) {
if (!requiredParams().contains(key) && !optionalParams().contains(key)) {
throw new FmpServiceException("'%s' is not a recognized query param for endpoint [%s]", key, url());
Expand All @@ -49,6 +53,6 @@ public final R download() {
throw new FmpServiceException("'%s' is a required query param for endpoint [%s]", req, url());
}
}
return http.get(typeRef, url(), headers(), params);
return filter(http.get(typeRef, url(), headers(), params));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.sorn.fmp4j.services;

import static dev.sorn.fmp4j.json.FmpJsonUtils.typeRef;
import static java.util.Arrays.stream;
import static java.util.Collections.emptySet;

import dev.sorn.fmp4j.cfg.FmpConfig;
Expand All @@ -27,4 +28,9 @@ protected Set<String> requiredParams() {
protected Set<String> optionalParams() {
return emptySet();
}

@Override
protected FmpStock[] filter(FmpStock[] stocks) {
return stream(stocks).filter(s -> s.symbol() != null).toArray(FmpStock[]::new);
}
}
70 changes: 70 additions & 0 deletions src/test/java/dev/sorn/fmp4j/json/FmpSymbolDeserializerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package dev.sorn.fmp4j.json;

import static dev.sorn.fmp4j.types.FmpSymbol.symbol;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import java.io.IOException;
import org.junit.jupiter.api.Test;

class FmpSymbolDeserializerTest {
private final JsonParser p = mock(JsonParser.class);
private final DeserializationContext ctx = mock(DeserializationContext.class);
private final FmpSymbolDeserializer deserializer = new FmpSymbolDeserializer();

@Test
void deserialize_null_symbol_returns_null() throws IOException {
// given
var nullStr = (String) null;

// when
when(p.getText()).thenReturn(nullStr);
var result = deserializer.deserialize(p, ctx);

// then
assertNull(result);
}

@Test
void deserialize_blank_symbol_returns_null() throws IOException {
// given
var blank = "";

// when
when(p.getText()).thenReturn(blank);
var result = deserializer.deserialize(p, ctx);

// then
assertNull(result);
}

@Test
void deserialize_invalid_symbol_returns_null() throws IOException {
// given
var invalid = "$INVALID";

// when
when(p.getText()).thenReturn(invalid);
var result = deserializer.deserialize(p, ctx);

// then
assertNull(result);
}

@Test
void deserialize_valid_symbol_returns_symbol() throws IOException {
// given
var symbol = "AAPL";

// when
when(p.getText()).thenReturn(symbol);
var result = deserializer.deserialize(p, ctx);

// then
assertEquals(symbol("AAPL"), result);
}
}
17 changes: 17 additions & 0 deletions src/test/java/dev/sorn/fmp4j/services/FmpStockListServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,21 @@ void successful_download() {
range(0, 2).forEach(i -> assertInstanceOf(FmpStock.class, result[i]));
range(0, 2).forEach(i -> assertAllFieldsNonNull(result[i]));
}

@Test
void partial_successful_download() {
// given
httpStub.configureResponse()
.body(jsonTestResource("stable/stock-list/contains_invalid.json"))
.statusCode(200)
.apply();

// when
var result = service.download();

// then
assertEquals(2, result.length);
range(0, 2).forEach(i -> assertInstanceOf(FmpStock.class, result[i]));
range(0, 2).forEach(i -> assertAllFieldsNonNull(result[i]));
}
}
14 changes: 14 additions & 0 deletions src/testFixtures/resources/stable/stock-list/contains_invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"symbol": "6898.HK",
"companyName": "China Aluminum Cans Holdings Limited"
},
{
"symbol": "0700.HK",
"companyName": "Tencent Holdings Limited"
},
{
"symbol": "$INVALID",
"companyName": "Some Invalid Company Limited"
}
]
Loading