Share day-style generated text validation
This commit is contained in:
@@ -1,10 +1,5 @@
|
|||||||
package generatedtext
|
package generatedtext
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Daily struct {
|
type Daily struct {
|
||||||
Summary string `json:"summary"`
|
Summary string `json:"summary"`
|
||||||
ForecastDiscussion []string `json:"forecast_discussion"`
|
ForecastDiscussion []string `json:"forecast_discussion"`
|
||||||
@@ -13,25 +8,21 @@ type Daily struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ValidateDaily(data []byte) (Daily, []byte, error) {
|
func ValidateDaily(data []byte) (Daily, []byte, error) {
|
||||||
value, err := decodeGeneratedText[Daily](data, "daily")
|
return validateDayStyleGeneratedText[Daily, *Daily](data, "daily")
|
||||||
if err != nil {
|
}
|
||||||
return Daily{}, nil, err
|
|
||||||
}
|
func (d *Daily) dayStyleFields() dayStyleFields {
|
||||||
|
return dayStyleFields{
|
||||||
value.Summary = strings.TrimSpace(value.Summary)
|
Summary: d.Summary,
|
||||||
value.PrecipitationTiming = strings.TrimSpace(value.PrecipitationTiming)
|
ForecastDiscussion: d.ForecastDiscussion,
|
||||||
value.Confidence = strings.TrimSpace(value.Confidence)
|
PrecipitationTiming: d.PrecipitationTiming,
|
||||||
value.ForecastDiscussion = trimNonEmpty(value.ForecastDiscussion)
|
Confidence: d.Confidence,
|
||||||
if value.Summary == "" {
|
}
|
||||||
return Daily{}, nil, fmt.Errorf("daily generated text summary is required")
|
}
|
||||||
}
|
|
||||||
if len(value.ForecastDiscussion) == 0 {
|
func (d *Daily) setDayStyleFields(fields dayStyleFields) {
|
||||||
return Daily{}, nil, fmt.Errorf("daily generated text forecast discussion is required")
|
d.Summary = fields.Summary
|
||||||
}
|
d.ForecastDiscussion = fields.ForecastDiscussion
|
||||||
|
d.PrecipitationTiming = fields.PrecipitationTiming
|
||||||
normalized, err := normalizeGeneratedText(value, "daily")
|
d.Confidence = fields.Confidence
|
||||||
if err != nil {
|
|
||||||
return Daily{}, nil, err
|
|
||||||
}
|
|
||||||
return value, normalized, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
63
internal/generatedtext/day_style.go
Normal file
63
internal/generatedtext/day_style.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package generatedtext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dayStyleFields struct {
|
||||||
|
Summary string
|
||||||
|
ForecastDiscussion []string
|
||||||
|
PrecipitationTiming string
|
||||||
|
Confidence string
|
||||||
|
}
|
||||||
|
|
||||||
|
type dayStyleGeneratedText interface {
|
||||||
|
dayStyleFields() dayStyleFields
|
||||||
|
setDayStyleFields(dayStyleFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDayStyleGeneratedText[T any, PT interface {
|
||||||
|
*T
|
||||||
|
dayStyleGeneratedText
|
||||||
|
}](data []byte, name string) (T, []byte, error) {
|
||||||
|
value, err := decodeGeneratedText[T](data, name)
|
||||||
|
if err != nil {
|
||||||
|
var zero T
|
||||||
|
return zero, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer := PT(&value)
|
||||||
|
fields := pointer.dayStyleFields()
|
||||||
|
fields.Summary = strings.TrimSpace(fields.Summary)
|
||||||
|
fields.PrecipitationTiming = strings.TrimSpace(fields.PrecipitationTiming)
|
||||||
|
fields.Confidence = strings.TrimSpace(fields.Confidence)
|
||||||
|
fields.ForecastDiscussion = trimNonEmpty(fields.ForecastDiscussion)
|
||||||
|
if fields.Summary == "" {
|
||||||
|
var zero T
|
||||||
|
return zero, nil, fmt.Errorf("%s generated text summary is required", name)
|
||||||
|
}
|
||||||
|
if len(fields.ForecastDiscussion) == 0 {
|
||||||
|
var zero T
|
||||||
|
return zero, nil, fmt.Errorf("%s generated text forecast discussion is required", name)
|
||||||
|
}
|
||||||
|
pointer.setDayStyleFields(fields)
|
||||||
|
|
||||||
|
normalized, err := normalizeGeneratedText(value, name)
|
||||||
|
if err != nil {
|
||||||
|
var zero T
|
||||||
|
return zero, nil, err
|
||||||
|
}
|
||||||
|
return value, normalized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimNonEmpty(values []string) []string {
|
||||||
|
out := make([]string, 0, len(values))
|
||||||
|
for _, value := range values {
|
||||||
|
trimmed := strings.TrimSpace(value)
|
||||||
|
if trimmed != "" {
|
||||||
|
out = append(out, trimmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
131
internal/generatedtext/day_style_test.go
Normal file
131
internal/generatedtext/day_style_test.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package generatedtext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateDayStyleGeneratedTextSharedBehavior(t *testing.T) {
|
||||||
|
reports := []struct {
|
||||||
|
name string
|
||||||
|
validate func([]byte) (any, []byte, error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "daily",
|
||||||
|
validate: func(data []byte) (any, []byte, error) {
|
||||||
|
value, normalized, err := ValidateDaily(data)
|
||||||
|
return value, normalized, err
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "today",
|
||||||
|
validate: func(data []byte) (any, []byte, error) {
|
||||||
|
value, normalized, err := ValidateToday(data)
|
||||||
|
return value, normalized, err
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tomorrow",
|
||||||
|
validate: func(data []byte) (any, []byte, error) {
|
||||||
|
value, normalized, err := ValidateTomorrow(data)
|
||||||
|
return value, normalized, err
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, report := range reports {
|
||||||
|
t.Run(report.name, func(t *testing.T) {
|
||||||
|
t.Run("requires summary", func(t *testing.T) {
|
||||||
|
_, _, err := report.validate([]byte(`{"forecast_discussion":["First paragraph."]}`))
|
||||||
|
want := fmt.Sprintf("%s generated text summary is required", report.name)
|
||||||
|
if err == nil || err.Error() != want {
|
||||||
|
t.Fatalf("validate() error = %v, want %q", err, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("requires non-empty forecast discussion", func(t *testing.T) {
|
||||||
|
_, _, err := report.validate([]byte(`{"summary":"Shared summary.","forecast_discussion":[" ",""]}`))
|
||||||
|
want := fmt.Sprintf("%s generated text forecast discussion is required", report.name)
|
||||||
|
if err == nil || err.Error() != want {
|
||||||
|
t.Fatalf("validate() error = %v, want %q", err, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("trims and normalizes", func(t *testing.T) {
|
||||||
|
value, normalized, err := report.validate([]byte(`{
|
||||||
|
"summary": " Shared summary. ",
|
||||||
|
"forecast_discussion": [
|
||||||
|
" First paragraph. ",
|
||||||
|
"",
|
||||||
|
" Second paragraph. "
|
||||||
|
],
|
||||||
|
"precipitation_timing": " Afternoon. ",
|
||||||
|
"confidence": " Medium "
|
||||||
|
}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("validate() error = %v", err)
|
||||||
|
}
|
||||||
|
fields := dayStyleFieldsForTest(t, value)
|
||||||
|
if fields.Summary != "Shared summary." {
|
||||||
|
t.Fatalf("Summary = %q, want trimmed summary", fields.Summary)
|
||||||
|
}
|
||||||
|
if strings.Join(fields.ForecastDiscussion, "|") != "First paragraph.|Second paragraph." {
|
||||||
|
t.Fatalf("ForecastDiscussion = %#v, want trimmed non-empty paragraphs", fields.ForecastDiscussion)
|
||||||
|
}
|
||||||
|
if fields.PrecipitationTiming != "Afternoon." {
|
||||||
|
t.Fatalf("PrecipitationTiming = %q, want trimmed precipitation timing", fields.PrecipitationTiming)
|
||||||
|
}
|
||||||
|
if fields.Confidence != "Medium" {
|
||||||
|
t.Fatalf("Confidence = %q, want trimmed confidence", fields.Confidence)
|
||||||
|
}
|
||||||
|
want := `{"summary":"Shared summary.","forecast_discussion":["First paragraph.","Second paragraph."],"precipitation_timing":"Afternoon.","confidence":"Medium"}`
|
||||||
|
if string(normalized) != want {
|
||||||
|
t.Fatalf("normalized = %s, want %s", normalized, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("omits empty optional fields", func(t *testing.T) {
|
||||||
|
_, normalized, err := report.validate([]byte(`{
|
||||||
|
"summary": "Shared summary.",
|
||||||
|
"forecast_discussion": ["First paragraph."],
|
||||||
|
"precipitation_timing": " ",
|
||||||
|
"confidence": " "
|
||||||
|
}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("validate() error = %v", err)
|
||||||
|
}
|
||||||
|
want := `{"summary":"Shared summary.","forecast_discussion":["First paragraph."]}`
|
||||||
|
if string(normalized) != want {
|
||||||
|
t.Fatalf("normalized = %s, want %s", normalized, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("rejects unknown fields", func(t *testing.T) {
|
||||||
|
_, _, err := report.validate([]byte(`{"summary":"Shared summary.","forecast_discussion":["First paragraph."],"extra":"value"}`))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("validate() error = nil, want error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), `unknown field "extra"`) {
|
||||||
|
t.Fatalf("validate() error = %v, want unknown field error", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dayStyleFieldsForTest(t *testing.T, value any) dayStyleFields {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
switch value := value.(type) {
|
||||||
|
case Daily:
|
||||||
|
return (&value).dayStyleFields()
|
||||||
|
case Today:
|
||||||
|
return (&value).dayStyleFields()
|
||||||
|
case Tomorrow:
|
||||||
|
return (&value).dayStyleFields()
|
||||||
|
default:
|
||||||
|
t.Fatalf("value type = %T, want day-style generated text", value)
|
||||||
|
return dayStyleFields{}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,5 @@
|
|||||||
package generatedtext
|
package generatedtext
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Today struct {
|
type Today struct {
|
||||||
Summary string `json:"summary"`
|
Summary string `json:"summary"`
|
||||||
ForecastDiscussion []string `json:"forecast_discussion"`
|
ForecastDiscussion []string `json:"forecast_discussion"`
|
||||||
@@ -13,25 +8,21 @@ type Today struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ValidateToday(data []byte) (Today, []byte, error) {
|
func ValidateToday(data []byte) (Today, []byte, error) {
|
||||||
value, err := decodeGeneratedText[Today](data, "today")
|
return validateDayStyleGeneratedText[Today, *Today](data, "today")
|
||||||
if err != nil {
|
}
|
||||||
return Today{}, nil, err
|
|
||||||
}
|
func (t *Today) dayStyleFields() dayStyleFields {
|
||||||
|
return dayStyleFields{
|
||||||
value.Summary = strings.TrimSpace(value.Summary)
|
Summary: t.Summary,
|
||||||
value.PrecipitationTiming = strings.TrimSpace(value.PrecipitationTiming)
|
ForecastDiscussion: t.ForecastDiscussion,
|
||||||
value.Confidence = strings.TrimSpace(value.Confidence)
|
PrecipitationTiming: t.PrecipitationTiming,
|
||||||
value.ForecastDiscussion = trimNonEmpty(value.ForecastDiscussion)
|
Confidence: t.Confidence,
|
||||||
if value.Summary == "" {
|
}
|
||||||
return Today{}, nil, fmt.Errorf("today generated text summary is required")
|
}
|
||||||
}
|
|
||||||
if len(value.ForecastDiscussion) == 0 {
|
func (t *Today) setDayStyleFields(fields dayStyleFields) {
|
||||||
return Today{}, nil, fmt.Errorf("today generated text forecast discussion is required")
|
t.Summary = fields.Summary
|
||||||
}
|
t.ForecastDiscussion = fields.ForecastDiscussion
|
||||||
|
t.PrecipitationTiming = fields.PrecipitationTiming
|
||||||
normalized, err := normalizeGeneratedText(value, "today")
|
t.Confidence = fields.Confidence
|
||||||
if err != nil {
|
|
||||||
return Today{}, nil, err
|
|
||||||
}
|
|
||||||
return value, normalized, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
package generatedtext
|
package generatedtext
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Tomorrow struct {
|
type Tomorrow struct {
|
||||||
Summary string `json:"summary"`
|
Summary string `json:"summary"`
|
||||||
ForecastDiscussion []string `json:"forecast_discussion"`
|
ForecastDiscussion []string `json:"forecast_discussion"`
|
||||||
@@ -13,36 +8,21 @@ type Tomorrow struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ValidateTomorrow(data []byte) (Tomorrow, []byte, error) {
|
func ValidateTomorrow(data []byte) (Tomorrow, []byte, error) {
|
||||||
value, err := decodeGeneratedText[Tomorrow](data, "tomorrow")
|
return validateDayStyleGeneratedText[Tomorrow, *Tomorrow](data, "tomorrow")
|
||||||
if err != nil {
|
|
||||||
return Tomorrow{}, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
value.Summary = strings.TrimSpace(value.Summary)
|
|
||||||
value.PrecipitationTiming = strings.TrimSpace(value.PrecipitationTiming)
|
|
||||||
value.Confidence = strings.TrimSpace(value.Confidence)
|
|
||||||
value.ForecastDiscussion = trimNonEmpty(value.ForecastDiscussion)
|
|
||||||
if value.Summary == "" {
|
|
||||||
return Tomorrow{}, nil, fmt.Errorf("tomorrow generated text summary is required")
|
|
||||||
}
|
|
||||||
if len(value.ForecastDiscussion) == 0 {
|
|
||||||
return Tomorrow{}, nil, fmt.Errorf("tomorrow generated text forecast discussion is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
normalized, err := normalizeGeneratedText(value, "tomorrow")
|
|
||||||
if err != nil {
|
|
||||||
return Tomorrow{}, nil, err
|
|
||||||
}
|
|
||||||
return value, normalized, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimNonEmpty(values []string) []string {
|
func (t *Tomorrow) dayStyleFields() dayStyleFields {
|
||||||
out := make([]string, 0, len(values))
|
return dayStyleFields{
|
||||||
for _, value := range values {
|
Summary: t.Summary,
|
||||||
trimmed := strings.TrimSpace(value)
|
ForecastDiscussion: t.ForecastDiscussion,
|
||||||
if trimmed != "" {
|
PrecipitationTiming: t.PrecipitationTiming,
|
||||||
out = append(out, trimmed)
|
Confidence: t.Confidence,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return out
|
}
|
||||||
|
|
||||||
|
func (t *Tomorrow) setDayStyleFields(fields dayStyleFields) {
|
||||||
|
t.Summary = fields.Summary
|
||||||
|
t.ForecastDiscussion = fields.ForecastDiscussion
|
||||||
|
t.PrecipitationTiming = fields.PrecipitationTiming
|
||||||
|
t.Confidence = fields.Confidence
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user