Add omitempty to JSON fields in alert, forecast, and observation services
All checks were successful
ci/woodpecker/push/build-image Pipeline was successful
All checks were successful
ci/woodpecker/push/build-image Pipeline was successful
This commit is contained in:
@@ -232,3 +232,70 @@ func TestForecastReturnsUSUnits(t *testing.T) {
|
|||||||
t.Fatalf("expected forecast timestamp passed to repo")
|
t.Fatalf("expected forecast timestamp passed to repo")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNullFieldsAreOmittedFromJSON(t *testing.T) {
|
||||||
|
obsRepo := &fakeObservationRepo{
|
||||||
|
summary: ports.ObservationSummaryMetric{},
|
||||||
|
conditions: []ports.ObservationConditionMetric{
|
||||||
|
{
|
||||||
|
ObservedAt: time.Date(2026, 3, 17, 12, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fcRepo := &fakeForecastRepo{
|
||||||
|
periods: []ports.ForecastPeriodMetric{
|
||||||
|
{
|
||||||
|
PeriodIndex: 1,
|
||||||
|
StartTime: time.Date(2026, 3, 17, 12, 0, 0, 0, time.UTC),
|
||||||
|
EndTime: time.Date(2026, 3, 17, 13, 0, 0, 0, time.UTC),
|
||||||
|
ConditionCode: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
server := NewServer(
|
||||||
|
observations.NewService(obsRepo, units.USConverter{}, constants.ObservationWindow),
|
||||||
|
forecasts.NewService(fcRepo, units.USConverter{}, constants.ForecastQueryLimit),
|
||||||
|
alerts.NewService(fakeAlertRepo{}),
|
||||||
|
).Handler()
|
||||||
|
|
||||||
|
wObs := httptest.NewRecorder()
|
||||||
|
server.ServeHTTP(wObs, httptest.NewRequest(http.MethodGet, "/observations/current", nil))
|
||||||
|
if wObs.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected observations 200, got %d", wObs.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
var obsPayload map[string]any
|
||||||
|
if err := json.Unmarshal(wObs.Body.Bytes(), &obsPayload); err != nil {
|
||||||
|
t.Fatalf("decode observations response: %v", err)
|
||||||
|
}
|
||||||
|
summary := obsPayload["summary"].(map[string]any)
|
||||||
|
if _, exists := summary["temperatureF"]; exists {
|
||||||
|
t.Fatalf("expected summary.temperatureF to be omitted when nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
conditions := obsPayload["conditions"].([]any)
|
||||||
|
firstCond := conditions[0].(map[string]any)
|
||||||
|
if _, exists := firstCond["temperatureF"]; exists {
|
||||||
|
t.Fatalf("expected conditions[0].temperatureF to be omitted when nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
wFc := httptest.NewRecorder()
|
||||||
|
server.ServeHTTP(wFc, httptest.NewRequest(http.MethodGet, "/forecast?timestamp=2026-03-17T12:30:00Z", nil))
|
||||||
|
if wFc.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected forecast 200, got %d", wFc.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fcPayload map[string]any
|
||||||
|
if err := json.Unmarshal(wFc.Body.Bytes(), &fcPayload); err != nil {
|
||||||
|
t.Fatalf("decode forecast response: %v", err)
|
||||||
|
}
|
||||||
|
periods := fcPayload["periods"].([]any)
|
||||||
|
firstPeriod := periods[0].(map[string]any)
|
||||||
|
if _, exists := firstPeriod["temperatureF"]; exists {
|
||||||
|
t.Fatalf("expected periods[0].temperatureF to be omitted when nil")
|
||||||
|
}
|
||||||
|
if _, exists := firstPeriod["windSpeedMph"]; exists {
|
||||||
|
t.Fatalf("expected periods[0].windSpeedMph to be omitted when nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ type Response struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AlertResponse struct {
|
type AlertResponse struct {
|
||||||
Effective *time.Time `json:"effective"`
|
Effective *time.Time `json:"effective,omitempty"`
|
||||||
Expires *time.Time `json:"expires"`
|
Expires *time.Time `json:"expires,omitempty"`
|
||||||
Severity *string `json:"severity"`
|
Severity *string `json:"severity,omitempty"`
|
||||||
Event *string `json:"event"`
|
Event *string `json:"event,omitempty"`
|
||||||
Headline *string `json:"headline"`
|
Headline *string `json:"headline,omitempty"`
|
||||||
Instruction *string `json:"instruction"`
|
Instruction *string `json:"instruction,omitempty"`
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetCurrent(ctx context.Context) (Response, error) {
|
func (s *Service) GetCurrent(ctx context.Context) (Response, error) {
|
||||||
|
|||||||
@@ -28,30 +28,30 @@ type PeriodResponse struct {
|
|||||||
PeriodIndex int `json:"periodIndex"`
|
PeriodIndex int `json:"periodIndex"`
|
||||||
StartTime time.Time `json:"startTime"`
|
StartTime time.Time `json:"startTime"`
|
||||||
EndTime time.Time `json:"endTime"`
|
EndTime time.Time `json:"endTime"`
|
||||||
Name *string `json:"name"`
|
Name *string `json:"name,omitempty"`
|
||||||
IsDay *bool `json:"isDay"`
|
IsDay *bool `json:"isDay,omitempty"`
|
||||||
ConditionCode int `json:"conditionCode"`
|
ConditionCode int `json:"conditionCode"`
|
||||||
ConditionText *string `json:"conditionText"`
|
ConditionText *string `json:"conditionText,omitempty"`
|
||||||
ProviderRawDescription *string `json:"providerRawDescription"`
|
ProviderRawDescription *string `json:"providerRawDescription,omitempty"`
|
||||||
TextDescription *string `json:"textDescription"`
|
TextDescription *string `json:"textDescription,omitempty"`
|
||||||
DetailedText *string `json:"detailedText"`
|
DetailedText *string `json:"detailedText,omitempty"`
|
||||||
IconURL *string `json:"iconUrl"`
|
IconURL *string `json:"iconUrl,omitempty"`
|
||||||
TemperatureF *float64 `json:"temperatureF"`
|
TemperatureF *float64 `json:"temperatureF,omitempty"`
|
||||||
TemperatureFMin *float64 `json:"temperatureFMin"`
|
TemperatureFMin *float64 `json:"temperatureFMin,omitempty"`
|
||||||
TemperatureFMax *float64 `json:"temperatureFMax"`
|
TemperatureFMax *float64 `json:"temperatureFMax,omitempty"`
|
||||||
DewpointF *float64 `json:"dewpointF"`
|
DewpointF *float64 `json:"dewpointF,omitempty"`
|
||||||
RelativeHumidityPercent *float64 `json:"relativeHumidityPercent"`
|
RelativeHumidityPercent *float64 `json:"relativeHumidityPercent,omitempty"`
|
||||||
WindDirectionDegrees *float64 `json:"windDirectionDegrees"`
|
WindDirectionDegrees *float64 `json:"windDirectionDegrees,omitempty"`
|
||||||
WindSpeedMph *float64 `json:"windSpeedMph"`
|
WindSpeedMph *float64 `json:"windSpeedMph,omitempty"`
|
||||||
WindGustMph *float64 `json:"windGustMph"`
|
WindGustMph *float64 `json:"windGustMph,omitempty"`
|
||||||
BarometricPressurePa *float64 `json:"barometricPressurePa"`
|
BarometricPressurePa *float64 `json:"barometricPressurePa,omitempty"`
|
||||||
VisibilityMeters *float64 `json:"visibilityMeters"`
|
VisibilityMeters *float64 `json:"visibilityMeters,omitempty"`
|
||||||
ApparentTemperatureF *float64 `json:"apparentTemperatureF"`
|
ApparentTemperatureF *float64 `json:"apparentTemperatureF,omitempty"`
|
||||||
CloudCoverPercent *float64 `json:"cloudCoverPercent"`
|
CloudCoverPercent *float64 `json:"cloudCoverPercent,omitempty"`
|
||||||
ProbabilityOfPrecipitationPercent *float64 `json:"probabilityOfPrecipitationPercent"`
|
ProbabilityOfPrecipitationPercent *float64 `json:"probabilityOfPrecipitationPercent,omitempty"`
|
||||||
PrecipitationAmountMm *float64 `json:"precipitationAmountMm"`
|
PrecipitationAmountMm *float64 `json:"precipitationAmountMm,omitempty"`
|
||||||
SnowfallDepthMm *float64 `json:"snowfallDepthMm"`
|
SnowfallDepthMm *float64 `json:"snowfallDepthMm,omitempty"`
|
||||||
UVIndex *float64 `json:"uvIndex"`
|
UVIndex *float64 `json:"uvIndex,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetByTimestamp(ctx context.Context, ts time.Time) (Response, error) {
|
func (s *Service) GetByTimestamp(ctx context.Context, ts time.Time) (Response, error) {
|
||||||
|
|||||||
@@ -26,18 +26,18 @@ type CurrentResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SummaryResponse struct {
|
type SummaryResponse struct {
|
||||||
TemperatureF *float64 `json:"temperatureF"`
|
TemperatureF *float64 `json:"temperatureF,omitempty"`
|
||||||
ApparentTemperatureF *float64 `json:"apparentTemperatureF"`
|
ApparentTemperatureF *float64 `json:"apparentTemperatureF,omitempty"`
|
||||||
WindowMinutes int `json:"windowMinutes"`
|
WindowMinutes int `json:"windowMinutes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConditionResponse struct {
|
type ConditionResponse struct {
|
||||||
StationID *string `json:"stationId"`
|
StationID *string `json:"stationId,omitempty"`
|
||||||
ObservedAt time.Time `json:"observedAt"`
|
ObservedAt time.Time `json:"observedAt"`
|
||||||
TemperatureF *float64 `json:"temperatureF"`
|
TemperatureF *float64 `json:"temperatureF,omitempty"`
|
||||||
TextDescription *string `json:"textDescription"`
|
TextDescription *string `json:"textDescription,omitempty"`
|
||||||
ProviderRawDescription *string `json:"providerRawDescription"`
|
ProviderRawDescription *string `json:"providerRawDescription,omitempty"`
|
||||||
ConditionText *string `json:"conditionText"`
|
ConditionText *string `json:"conditionText,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetCurrent(ctx context.Context) (CurrentResponse, error) {
|
func (s *Service) GetCurrent(ctx context.Context) (CurrentResponse, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user