Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VEGA-2261 Save original LPA as JSON in S3 #minor #111

Merged
merged 29 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
10dc1ff
Clean up some untidy and unused code
Feb 14, 2024
04e7ded
Basic interface and constructor for S3 client
Feb 14, 2024
4f65b60
Expose S3 port from localstack in local dev
Feb 15, 2024
874d932
Add FromJSON() method to populate LPA from S3 JSON
Feb 15, 2024
312673d
Working GET and PUT to S3 bucket
Feb 15, 2024
b537a22
Incorporate s3 client into create lambda
Feb 15, 2024
709d5b3
Update dependencies
Feb 19, 2024
28aae18
Unit test for S3 client
Feb 19, 2024
f0f8dd6
Correct code style
Feb 19, 2024
3d56b45
Refactor S3 client to make it unit-testable
Feb 19, 2024
d1d4903
Add unit test for LPA storage client
Feb 20, 2024
d4b788f
Fix merge error
Feb 22, 2024
8936ef6
Point app at the static LPA S3 bucket
Feb 22, 2024
5adfc1b
Modify S3 endpoint resolution based on env var
Feb 26, 2024
a8b6a49
Add GetObject S3 permission
Feb 26, 2024
35ba81e
Name might be wrong, guessing
Feb 26, 2024
8f13562
Change endpoint resolver to work in CI
Feb 27, 2024
ba24822
PUT static LPA without checking if it exists
Feb 28, 2024
d829cf3
Static analysis fixes
Feb 28, 2024
195c1dd
tabs
Feb 28, 2024
3fc1e04
Get endpoint resolver working locally
Feb 28, 2024
e70a004
Go back to deprecated endpoint resolver
Feb 28, 2024
3ea7f31
Remove deprecated endpoint resolver approach
Feb 28, 2024
cbb0563
PR review and formatting
Mar 1, 2024
bcca6e2
Remove unused funcs and return values
Mar 1, 2024
f5f55ba
Move config out of client into lambda
Mar 1, 2024
0766138
Pass context from lambda into S3 client
Mar 1, 2024
49b4145
Fix broken tests
Mar 1, 2024
c6d7830
Keep S3 config separate from other service config
Mar 1, 2024
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
6 changes: 5 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ services:
- DIR=create
environment:
AWS_REGION: eu-west-1
AWS_ACCESS_KEY_ID: localstack
AWS_DYNAMODB_ENDPOINT: http://localstack:4566
AWS_EVENTBRIDGE_ENDPOINT: http://localstack:4566
AWS_S3_ENDPOINT: http://localstack:4566
AWS_ACCESS_KEY_ID: localstack
AWS_SECRET_ACCESS_KEY: localstack
DDB_TABLE_NAME_DEEDS: deeds
DDB_TABLE_NAME_CHANGES: changes
S3_BUCKET_NAME_ORIGINAL: opg-lpa-store-static-eu-west-1
EVENT_BUS_NAME: local-main
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-secret}
volumes:
Expand Down Expand Up @@ -79,6 +81,8 @@ services:

localstack:
image: localstack/localstack:3.1
ports:
- "4566:4566"
volumes:
- "./localstack/init:/etc/localstack/init/ready.d"
- "./localstack/wait:/scripts/wait"
Expand Down
8 changes: 6 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.2
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.29.0
github.com/aws/aws-sdk-go-v2/service/eventbridge v1.29.3
github.com/aws/aws-sdk-go-v2/service/s3 v1.50.3
github.com/aws/aws-xray-sdk-go v1.8.3
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/google/go-cmp v0.6.0
Expand All @@ -22,15 +23,18 @@ require (

require (
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.0 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.1 // indirect
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.19.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.19.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.22.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.27.0 // indirect
Expand Down
32 changes: 12 additions & 20 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,20 @@ github.com/aws/aws-sdk-go v1.50.20 h1:xfAnSDVf/azIWTVQXQODp89bubvCS85r70O3nuQ4dn
github.com/aws/aws-sdk-go v1.50.20/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go-v2 v1.25.1 h1:P7hU6A5qEdmajGwvae/zDkOq+ULLC9tQBTwqqiwFGpI=
github.com/aws/aws-sdk-go-v2 v1.25.1/go.mod h1:Evoc5AsmtveRt1komDwIsjHFyrP5tDuF1D1U+6z6pNo=
github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o=
github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1/go.mod h1:sxpLb+nZk7tIfCWChfd+h4QwHNUR57d8hA1cleTkjJo=
github.com/aws/aws-sdk-go-v2/config v1.27.0 h1:J5sdGCAHuWKIXLeXiqr8II/adSvetkx0qdZwdbXXpb0=
github.com/aws/aws-sdk-go-v2/config v1.27.0/go.mod h1:cfh8v69nuSUohNFMbIISP2fhmblGmYEOKs5V53HiHnk=
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8=
github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0=
github.com/aws/aws-sdk-go-v2/credentials v1.17.0 h1:lMW2x6sKBsiAJrpi1doOXqWFyEPoE886DTb1X0wb7So=
github.com/aws/aws-sdk-go-v2/credentials v1.17.0/go.mod h1:uT41FIH8cCIxOdUYIL0PYyHlL1NoneDuDSCwg5VE/5o=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.2 h1:ksCAKvVacJbsCJAUWaCk4ZS254NByOKlB8V4dGVWC9c=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.2/go.mod h1:vtaNpWHO0v6kWfS27bLuU9dklVj1YmdY/uSc4FqhBE0=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.0 h1:xWCwjjvVz2ojYTP4kBKUuUh9ZrXfcAXpflhOUUeXg1k=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.0/go.mod h1:j3fACuqXg4oMTQOR2yY7m0NmJY0yBK4L4sLsRXq1Ins=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.1 h1:evvi7FbTAoFxdP/mixmP7LIYzQWAmzBcwNB/es9XPNc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.1/go.mod h1:rH61DT6FDdikhPghymripNUCsf+uVF4Cnk4c4DBKH64=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.1 h1:RAnaIrbxPtlXNVI/OIlh1sidTQ3e1qM6LRjs7N0bE0I=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.1/go.mod h1:nbgAGkH5lk0RZRMh6A4K/oG6Xj11eC/1CyDow+DUAFI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.1 h1:rtYJd3w6IWCTVS8vmMaiXjW198noh2PBm5CiXyJea9o=
Expand All @@ -38,22 +32,20 @@ github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.19.1 h1:Wd1F42HO5ZJ+auc4
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.19.1/go.mod h1:0FgUg08+1knEoYHo0pa8ogm7D9sjH79lHnRzCNGk/6Q=
github.com/aws/aws-sdk-go-v2/service/eventbridge v1.29.3 h1:m/JoWWQI/4Ka9WqTgv9ZupD2zePqVMW8PLmLwr+fiGg=
github.com/aws/aws-sdk-go-v2/service/eventbridge v1.29.3/go.mod h1:efCw7VuDRT7Jzj75Tu4Wfx6Pm5Yh6JR2SPSoL7FI1CM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.0 h1:a33HuFlO0KsveiP90IUJh8Xr/cx9US2PqkSroaLc+o8=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.0/go.mod h1:SxIkWpByiGbhbHYTo9CMTUnx2G4p4ZQMrDPcRRy//1c=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.0 h1:SHN/umDLTmFTmYfI+gkanz6da3vK8Kvj/5wkqnTHbuA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.0/go.mod h1:l8gPU5RYGOFHJqWEpPMoRTP0VoaWQSkJdKo+hwWnnDA=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.1 h1:5Wxh862HkXL9CbQ83BIkWKLIgQapGeuh5zG2G9OZtQk=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.1/go.mod h1:V7GLA01pNUxMCYSQsibdVrqUrNIYIT/9lCOyR8ExNvQ=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.1 h1:cVP8mng1RjDyI3JN/AXFCn5FHNlsBaBH0/MBtG1bg0o=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.1/go.mod h1:C8sQjoyAsdfjC7hpy4+S6B92hnFzx0d0UAyHicaOTIE=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.1 h1:OYmmIcyw19f7x0qLBLQ3XsrCZSSyLhxd9GXng5evsN4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.1/go.mod h1:s5rqdn74Vdg10k61Pwf4ZHEApOSD6CKRe6qpeHDq32I=
github.com/aws/aws-sdk-go-v2/service/s3 v1.50.3 h1:Cv/HH7sLzEdJMYQi4MCNHxZeyubQNOOIdVc0VU0lo3Q=
github.com/aws/aws-sdk-go-v2/service/s3 v1.50.3/go.mod h1:lTW7O4iMAnO2o7H3XJTvqaWFZCH6zIPs+eP7RdG/yp0=
github.com/aws/aws-sdk-go-v2/service/sso v1.19.0 h1:u6OkVDxtBPnxPkZ9/63ynEe+8kHbtS5IfaC4PzVxzWM=
github.com/aws/aws-sdk-go-v2/service/sso v1.19.0/go.mod h1:YqbU3RS/pkDVu+v+Nwxvn0i1WB0HkNWEePWbmODEbbs=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.22.0 h1:6DL0qu5+315wbsAEEmzK+P9leRwNbkp+lGjPC+CEvb8=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.22.0/go.mod h1:olUAyg+FaoFaL/zFaeQQONjOZ9HXoxgvI/c7mQTYz7M=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
github.com/aws/aws-sdk-go-v2/service/sts v1.27.0 h1:cjTRjh700H36MQ8M0LnDn33W3JmwC77mdxIIyPWCdpM=
github.com/aws/aws-sdk-go-v2/service/sts v1.27.0/go.mod h1:nXfOBMWPokIbOY+Gi7a1psWMSvskUCemZzI+SMB7Akc=
github.com/aws/aws-xray-sdk-go v1.8.3 h1:S8GdgVncBRhzbNnNUgTPwhEqhwt2alES/9rLASyhxjU=
Expand Down
12 changes: 6 additions & 6 deletions internal/ddb/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ type Client struct {
}

func (c *Client) PutChanges(ctx context.Context, data any, update shared.Update) error {
changesItem, err := dynamodbattribute.MarshalMap(map[string]interface{}{
"uid": update.Uid,
changesItem, _ := dynamodbattribute.MarshalMap(map[string]interface{}{
"uid": update.Uid,
"applied": update.Applied,
"author": update.Author,
"type": update.Type,
"change": update.Changes,
"author": update.Author,
"type": update.Type,
"change": update.Changes,
})

item, err := dynamodbattribute.MarshalMap(data)
Expand Down Expand Up @@ -94,7 +94,7 @@ func (c *Client) Get(ctx context.Context, uid string) (shared.Lpa, error) {
return lpa, err
}

func New(endpoint, tableName string, changesTableName string) *Client {
func New(endpoint, tableName, changesTableName string) *Client {
sess := session.Must(session.NewSession())
sess.Config.Endpoint = &endpoint

Expand Down
10 changes: 5 additions & 5 deletions internal/event/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type Client struct {

func NewClient(cfg aws.Config, eventBusName string) *Client {
return &Client{
svc: eventbridge.NewFromConfig(cfg, func (o *eventbridge.Options) {
svc: eventbridge.NewFromConfig(cfg, func(o *eventbridge.Options) {
o.BaseEndpoint = aws.String(os.Getenv("AWS_EVENTBRIDGE_ENDPOINT"))
}),
eventBusName: eventBusName,
Expand All @@ -40,13 +40,13 @@ func (c *Client) send(ctx context.Context, eventType string, detail any) error {
if err != nil {
return err
}

_, err = c.svc.PutEvents(ctx, &eventbridge.PutEventsInput{
Entries: []types.PutEventsRequestEntry{{
EventBusName: aws.String(c.eventBusName),
Source: aws.String(source),
DetailType: aws.String(eventType),
Detail: aws.String(string(v)),
Source: aws.String(source),
DetailType: aws.String(eventType),
Detail: aws.String(string(v)),
}},
})

Expand Down
16 changes: 8 additions & 8 deletions internal/event/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ func TestClientSendEvent(t *testing.T) {
ctx := context.Background()
expectedError := errors.New("err")

event := LpaUpdated{ Uid: "M-1234-1234-1234", ChangeType: "CREATED" }
event := LpaUpdated{Uid: "M-1234-1234-1234", ChangeType: "CREATED"}
data, _ := json.Marshal(event)

mockClient := &mockEventBridgeClient{}
mockClient.On("PutEvents", mock.Anything, &eventbridge.PutEventsInput{
Entries: []types.PutEventsRequestEntry{{
EventBusName: aws.String("my-bus"),
Source: aws.String("opg.poas.lpastore"),
DetailType: aws.String("lpa-updated"),
Detail: aws.String(string(data)),
}},
}).
Entries: []types.PutEventsRequestEntry{{
EventBusName: aws.String("my-bus"),
Source: aws.String("opg.poas.lpastore"),
DetailType: aws.String("lpa-updated"),
Detail: aws.String(string(data)),
}},
}).
Return(nil, expectedError)

svc := &Client{svc: mockClient, eventBusName: "my-bus"}
Expand Down
48 changes: 48 additions & 0 deletions internal/objectstore/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package objectstore

import (
"bytes"
"context"
"encoding/json"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
)

type awsS3Client interface {
PutObject(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3.Options)) (*s3.PutObjectOutput, error)
}

type S3Client struct {
bucketName string
awsClient awsS3Client
}

func (c *S3Client) Put(ctx context.Context, objectKey string, obj any) error {
b, err := json.Marshal(obj)
if err != nil {
return err
}

_, err = c.awsClient.PutObject(
ctx,
&s3.PutObjectInput{
townxelliot marked this conversation as resolved.
Show resolved Hide resolved
Bucket: aws.String(c.bucketName),
Key: aws.String(objectKey),
Body: bytes.NewReader(b),
ServerSideEncryption: types.ServerSideEncryptionAwsKms,
},
)

return err
}

func NewS3Client(awsConfig aws.Config, bucketName string) *S3Client {
awsClient := s3.NewFromConfig(awsConfig)

return &S3Client{
bucketName: bucketName,
awsClient: awsClient,
}
}
34 changes: 34 additions & 0 deletions internal/objectstore/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package objectstore

import (
"context"
"testing"

"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

type mockAwsClient struct {
mock.Mock
}

func (m *mockAwsClient) PutObject(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3.Options)) (*s3.PutObjectOutput, error) {
args := m.Called(ctx, input)
return args.Get(0).(*s3.PutObjectOutput), args.Error(1)
}

func TestPut(t *testing.T) {
client := mockAwsClient{}
client.On("PutObject", mock.Anything, mock.Anything, mock.Anything).Return(&s3.PutObjectOutput{}, nil)

c := S3Client{
bucketName: "bucket1",
awsClient: &client,
}

err := c.Put(context.Background(), "anobject", struct{ ID int }{ID: 1})

assert.Equal(t, nil, err)
client.AssertExpectations(t)
}
4 changes: 3 additions & 1 deletion internal/shared/lpa.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package shared

import "time"
import (
"time"
)

type LpaInit struct {
LpaType LpaType `json:"lpaType"`
Expand Down
6 changes: 3 additions & 3 deletions internal/shared/problem.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ type FieldError struct {
}

type Problem struct {
StatusCode int `json:"-"`
Code string `json:"code"`
Detail string `json:"detail"`
StatusCode int `json:"-"`
Code string `json:"code"`
Detail string `json:"detail"`
Errors []FieldError `json:"errors,omitempty"`
}

Expand Down
Loading