Skip to content

Commit

Permalink
add shopsping/decimal to jsonapi
Browse files Browse the repository at this point in the history
  • Loading branch information
Vincent Landgraf committed May 19, 2020
1 parent 33c81a6 commit fd899b1
Show file tree
Hide file tree
Showing 12 changed files with 124 additions and 109 deletions.
1 change: 1 addition & 0 deletions http/jsonapi/generator/generate_handler.go
Expand Up @@ -26,6 +26,7 @@ const (
pkgOAuth2 = "github.com/pace/bricks/http/oauth2"
pkgOIDC = "github.com/pace/bricks/http/oidc"
pkgApiKey = "github.com/pace/bricks/http/security/apikey"
pkgDecimal = "github.com/shopspring/decimal"
)

const serviceInterface = "Service"
Expand Down
2 changes: 1 addition & 1 deletion http/jsonapi/generator/generate_helper.go
Expand Up @@ -46,7 +46,7 @@ func (g *Generator) goType(stmt *jen.Statement, schema *openapi3.Schema, tags ma
stmt.String()
case "decimal":
addValidator(tags, "matches(^(\\d*\\.)?\\d+$)")
stmt.Qual(pkgRuntime, "Decimal")
stmt.Qual(pkgDecimal, "Decimal")
default:
stmt.String()
}
Expand Down
15 changes: 8 additions & 7 deletions http/jsonapi/generator/internal/pay/open-api_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 12 additions & 12 deletions http/jsonapi/generator/internal/pay/pay_test.go
Expand Up @@ -59,19 +59,19 @@ func (s *testService) GetPaymentMethodsIncludingPaymentToken(context.Context, Ge
}

func (s *testService) ProcessPayment(ctx context.Context, w ProcessPaymentResponseWriter, r *ProcessPaymentRequest) error {
if !r.ParamPathDecimal.Decode().Equals(decimal.RequireFromString("1337.42")) {
s.t.Errorf(`expected pathDecimal "1337.42", got %q`, r.ParamPathDecimal.Decode())
if r.ParamPathDecimal.String() != "1337.42" {
s.t.Errorf(`expected pathDecimal "1337.42", got %q`, r.ParamPathDecimal)
}

if !r.ParamQueryDecimal.Decode().Equals(decimal.RequireFromString("123.456")) {
s.t.Errorf(`expected queryDecimal "123.456", got %q`, r.ParamPathDecimal.Decode())
if r.ParamQueryDecimal.String() != "123.456" {
s.t.Errorf(`expected queryDecimal "123.456", got %q`, r.ParamPathDecimal)
}

w.Created(&ProcessPaymentCreated{
ID: "42",
VAT: ProcessPaymentCreatedVAT{
Amount: runtime.DecimalFrom(decimal.RequireFromString("11.07")),
Rate: runtime.DecimalFrom(decimal.RequireFromString("19.0")),
Amount: decimal.RequireFromString("11.07"),
Rate: decimal.RequireFromString("19.0"),
},
Currency: "EUR",
Fueling: ProcessPaymentCreatedFueling{
Expand All @@ -81,8 +81,8 @@ func (s *testService) ProcessPayment(ctx context.Context, w ProcessPaymentRespon
Mileage: 66435,
},
PaymentToken: "f106ac99-213c-4cf7-8c1b-1e841516026b",
PriceIncludingVAT: runtime.DecimalFrom(decimal.RequireFromString("69.34")),
PriceWithoutVAT: runtime.DecimalFrom(decimal.RequireFromString("58.27")),
PriceIncludingVAT: decimal.RequireFromString("69.34"),
PriceWithoutVAT: decimal.RequireFromString("58.27"),
})

return nil
Expand Down Expand Up @@ -215,10 +215,10 @@ func TestHandlerDecimal(t *testing.T) {
t.Fatal(err)
}

assertDecimal(t, pc.VAT.Amount.Decode(), decimal.RequireFromString("11.07"))
assertDecimal(t, pc.VAT.Rate.Decode(), decimal.RequireFromString("19.0"))
assertDecimal(t, pc.PriceIncludingVAT.Decode(), decimal.RequireFromString("69.34"))
assertDecimal(t, pc.PriceWithoutVAT.Decode(), decimal.RequireFromString("58.27"))
assertDecimal(t, pc.VAT.Amount, decimal.RequireFromString("11.07"))
assertDecimal(t, pc.VAT.Rate, decimal.RequireFromString("19.0"))
assertDecimal(t, pc.PriceIncludingVAT, decimal.RequireFromString("69.34"))
assertDecimal(t, pc.PriceWithoutVAT, decimal.RequireFromString("58.27"))
}

func assertDecimal(t *testing.T, got, want decimal.Decimal) {
Expand Down
9 changes: 6 additions & 3 deletions http/jsonapi/models_test.go
Expand Up @@ -6,6 +6,8 @@ package jsonapi
import (
"fmt"
"time"

"github.com/shopspring/decimal"
)

type BadModel struct {
Expand Down Expand Up @@ -67,9 +69,10 @@ type Book struct {
Description *string `jsonapi:"attr,description"`
Pages *uint `jsonapi:"attr,pages,omitempty"`
PublishedAt time.Time
Tags []string `jsonapi:"attr,tags"`
// Decimal1 decimal.Decimal `jsonapi:"attr,dec1"`
// Decimal2 decimal.Decimal `jsonapi:"attr,dec2"`
Tags []string `jsonapi:"attr,tags"`
Decimal1 decimal.Decimal `jsonapi:"attr,dec1,omitempty"`
Decimal2 decimal.Decimal `jsonapi:"attr,dec2,omitempty"`
Decimal3 decimal.Decimal `jsonapi:"attr,dec3,omitempty"`
}

type Blog struct {
Expand Down
53 changes: 46 additions & 7 deletions http/jsonapi/request.go
Expand Up @@ -13,6 +13,8 @@ import (
"strconv"
"strings"
"time"

"github.com/shopspring/decimal"
)

const (
Expand Down Expand Up @@ -390,12 +392,11 @@ func unmarshalAttribute(
value = reflect.ValueOf(attribute)
fieldType := structField.Type

// Handle field of type json.RawMessage
// if fieldValue.Type() == reflect.TypeOf(decimal.Decimal{}) {
// panic(attribute)
// value, err = handleDecimal(attribute)
// return
// }
// decimal.Decimal
if fieldValue.Type() == reflect.TypeOf(decimal.Decimal{}) {
value, err = handleDecimal(attribute)
return
}

// Handle field of type []string
if fieldValue.Type() == reflect.TypeOf([]string{}) {
Expand Down Expand Up @@ -445,7 +446,45 @@ func unmarshalAttribute(
}

func handleDecimal(attribute interface{}) (reflect.Value, error) {
return reflect.ValueOf(attribute), nil
var dec decimal.Decimal
var err error

switch v := attribute.(type) {
case string:
dec, err = decimal.NewFromString(v)
if err != nil {
return reflect.Value{}, err
}
case int:
dec = decimal.NewFromInt(int64(v))
case int8:
dec = decimal.NewFromInt(int64(v))
case int16:
dec = decimal.NewFromInt(int64(v))
case int32:
dec = decimal.NewFromInt(int64(v))
case int64:
dec = decimal.NewFromInt(int64(v))
case uint:
dec = decimal.NewFromInt(int64(v))
case uint8:
dec = decimal.NewFromInt(int64(v))
case uint16:
dec = decimal.NewFromInt(int64(v))
case uint32:
dec = decimal.NewFromInt(int64(v))
case uint64:
dec = decimal.NewFromInt(int64(v))
case float32:
dec = decimal.NewFromFloat32(v)
case float64:
dec = decimal.NewFromFloat(v)
default:
return reflect.Value{}, fmt.Errorf("can't decode decimal from value: %#v (%s)",
attribute, reflect.TypeOf(attribute))
}

return reflect.ValueOf(dec), nil
}

func handleStringSlice(attribute interface{}) (reflect.Value, error) {
Expand Down
25 changes: 15 additions & 10 deletions http/jsonapi/request_test.go
Expand Up @@ -19,16 +19,18 @@ import (
func TestUnmarshall_attrStringSlice(t *testing.T) {
out := &Book{}
tags := []string{"fiction", "sale"}
// dec1 := "10.63"
// dec2 := 10.63
dec1 := "10.63"
dec2 := 10.63
dec3 := 10
data := map[string]interface{}{
"data": map[string]interface{}{
"type": "books",
"id": "1",
"attributes": map[string]interface{}{
"tags": tags,
// "dec1": dec1,
// "dec2": dec2,
"dec1": dec1,
"dec2": dec2,
"dec3": dec3,
},
},
}
Expand All @@ -48,12 +50,15 @@ func TestUnmarshall_attrStringSlice(t *testing.T) {
sort.Strings(tags)
sort.Strings(out.Tags)

// if out.Decimal1.String() == "10.63" {
// t.Fatalf("Expected json dec1 data to be %#v got: %#v", dec1, out.Decimal1)
// }
// if out.Decimal2.String() == "10.63" {
// t.Fatalf("Expected json dec2 data to be %#v got: %#v", dec2, out.Decimal2)
// }
if out.Decimal1.String() != "10.63" {
t.Fatalf("Expected json dec1 data to be %#v got: %#v", dec1, out.Decimal1.String())
}
if out.Decimal2.String() != "10.63" {
t.Fatalf("Expected json dec2 data to be %#v got: %#v", dec2, out.Decimal2.String())
}
if out.Decimal3.String() != "10" {
t.Fatalf("Expected json dec2 data to be %#v got: %#v", dec3, out.Decimal3.String())
}

for i, tag := range tags {
if e, a := tag, out.Tags[i]; e != a {
Expand Down
12 changes: 11 additions & 1 deletion http/jsonapi/response.go
Expand Up @@ -12,6 +12,8 @@ import (
"strconv"
"strings"
"time"

"github.com/shopspring/decimal"
)

var (
Expand Down Expand Up @@ -303,7 +305,15 @@ func visitModelNode(model interface{}, included *map[string]*Node,
node.Attributes = make(map[string]interface{})
}

if fieldValue.Type() == reflect.TypeOf(time.Time{}) {
if fieldValue.Type() == reflect.TypeOf(decimal.Decimal{}) {
d := fieldValue.Interface().(decimal.Decimal)

if d.IsZero() {
continue
}

node.Attributes[args[1]] = json.RawMessage(d.String())
} else if fieldValue.Type() == reflect.TypeOf(time.Time{}) {
t := fieldValue.Interface().(time.Time)

if t.IsZero() {
Expand Down
11 changes: 10 additions & 1 deletion http/jsonapi/response_test.go
Expand Up @@ -6,27 +6,36 @@ package jsonapi
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"sort"
"strings"
"testing"
"time"

"github.com/shopspring/decimal"
)

func TestMarshalPayload(t *testing.T) {
book := &Book{ID: 1}
book := &Book{ID: 1, Decimal1: decimal.NewFromFloat(10.63)}
books := []*Book{book, {ID: 2}}
var jsonData map[string]interface{}

// One
out1 := bytes.NewBuffer(nil)
MarshalPayload(out1, book)

if strings.Contains(out1.String(), `"10.63"`) {
t.Fatalf("decimals should be encoded as number, got: %q", out1.String())
}

if err := json.Unmarshal(out1.Bytes(), &jsonData); err != nil {
t.Fatal(err)
}
if _, ok := jsonData["data"].(map[string]interface{}); !ok {
t.Fatalf("data key did not contain an Hash/Dict/Map")
}
fmt.Println(string(out1.Bytes()))

// Many
out2 := bytes.NewBuffer(nil)
Expand Down
17 changes: 0 additions & 17 deletions http/jsonapi/runtime/decimal.go

This file was deleted.

48 changes: 0 additions & 48 deletions http/jsonapi/runtime/decimal_test.go

This file was deleted.

0 comments on commit fd899b1

Please sign in to comment.