Skip to content

Commit

Permalink
feat: Support re-use of notification payloads in Simulator API
Browse files Browse the repository at this point in the history
fix: Fixes unmarshal for GetNotification
  • Loading branch information
mikeymike committed Oct 12, 2024
1 parent 4fea022 commit 38c8b18
Show file tree
Hide file tree
Showing 24 changed files with 750 additions and 165 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx&utm_medium=paddle-go-sdk) for information about changes to the Paddle Billing platform, the Paddle API, and other developer tools.

## 2.1.0 - 2024-10-08
## 2.1.0 - 2024-10-15

### Added

Expand All @@ -19,6 +19,7 @@ Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx
- Subscription resume union funcs now accept the required path param
- `NewResumeSubscriptionRequestResumeOnASpecificDate`
- `NewResumeSubscriptionRequestResumeImmediately`
- Response unmarshal for GetNotifications to the Notification struct

## 2.0.0 - 2024-09-18

Expand Down
10 changes: 7 additions & 3 deletions collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,12 @@ func (c *Collection[T]) Next(ctx context.Context) *Res[T] {
return &ptrCopy
}

// Ensure that Collection[T] implements the json.Unmarshaler and the
// Ensure that Collection[T] implements the response.Unmarshaler and the
// client.Wanter interfaces.
var (
_ json.Unmarshaler = &Collection[any]{}
_ client.Wanter = &Collection[any]{}
_ response.UnmarshalsResponses = &Collection[any]{}
_ json.Unmarshaler = &Collection[any]{}
_ client.Wanter = &Collection[any]{}
)

// Wants sets the client to be used for making requests.
Expand Down Expand Up @@ -157,6 +158,9 @@ func (c *Collection[T]) IterErr(ctx context.Context, fn func(v T) error) error {
}
}

// UnmarshalsResponses acts as a marker to identify this struct must Unmarshal the entire response.
func (c *Collection[T]) UnmarshalsResponses() {}

// UnmarshalJSON unmarshals the collection from a JSON response.
func (c *Collection[T]) UnmarshalJSON(b []byte) error {
if len(b) == 0 {
Expand Down
2 changes: 1 addition & 1 deletion events.go

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

298 changes: 298 additions & 0 deletions example_simulations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
package paddle_test

import (
"context"
"fmt"
"os"
"time"

paddle "github.com/PaddleHQ/paddle-go-sdk"
"github.com/PaddleHQ/paddle-go-sdk/pkg/paddlenotification"
)

// Demonstrates how to create a Simulation with Payload and read the Payload back out of the response
func Example_simulation_create() {
// Create a mock HTTP server for this example - skip over this bit!
s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{simulation}}})

// Create a new Paddle client.
client, err := paddle.New(
os.Getenv("PADDLE_API_KEY"),
paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration.
)

if err != nil {
fmt.Println(err)
return
}

ctx := context.Background()

// Optionally set a transit ID on the context. This is useful to link your
// own request IDs to Paddle API requests.
ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1")

simulation, err := client.CreateSimulation(ctx, paddle.NewCreateSimulationRequestSimulationSingleEventCreate(&paddle.SimulationSingleEventCreate{
NotificationSettingID: "ntfset_01j84xydheq48n3btebwf6ndn6",
Name: "Go SDK Test with Payload",
Type: "customer.created",
Payload: paddlenotification.CustomerNotification{
ID: "ctm_01j870snka0xdp6szgyxze6d6d",
Name: paddle.PtrTo("John Doe"),
Email: "[email protected]",
MarketingConsent: false,
Status: paddlenotification.StatusActive,
CustomData: nil,
Locale: "en",
CreatedAt: time.Date(2024, 4, 12, 0, 0, 0, 0, time.UTC).Format(time.RFC3339),
UpdatedAt: time.Date(2024, 4, 12, 0, 0, 0, 0, time.UTC).Format(time.RFC3339),
ImportMeta: nil,
},
}))
if err != nil {
return
}

// Use type assertion on the response for payload
payload, ok := (simulation.Payload).(*paddlenotification.CustomerNotification)
if !ok {
fmt.Println("Payload is unexpected type")
return
}

fmt.Println(simulation.ID)
fmt.Println(payload.ID)
// Output:
//ntfsim_01j9y0jwekrcyezscgkehvdmd6
//ctm_01j870snka0xdp6szgyxze6d6d
}

// Demonstrates how to update a Simulation with Payload and read the Payload back out of the response
func Example_simulation_update() {
// Create a mock HTTP server for this example - skip over this bit!
s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{simulation}}})

// Create a new Paddle client.
client, err := paddle.New(
os.Getenv("PADDLE_API_KEY"),
paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration.
)

if err != nil {
fmt.Println(err)
return
}

ctx := context.Background()

// Optionally set a transit ID on the context. This is useful to link your
// own request IDs to Paddle API requests.
ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1")

simulation, err := client.UpdateSimulation(
ctx,
paddle.NewUpdateSimulationRequestSimulationSingleEventUpdate(
"ntfsim_01j9y0jwekrcyezscgkehvdmd6",
&paddle.SimulationSingleEventUpdate{
NotificationSettingID: "ntfset_01j84xydheq48n3btebwf6ndn6",
Name: "Go SDK Test with Payload",
Type: "customer.created",
Payload: paddlenotification.CustomerNotification{
ID: "ctm_01j870snka0xdp6szgyxze6d6d",
Name: paddle.PtrTo("John Doe"),
Email: "[email protected]",
MarketingConsent: false,
Status: paddlenotification.StatusActive,
CustomData: nil,
Locale: "en",
CreatedAt: time.Date(2024, 4, 12, 0, 0, 0, 0, time.UTC).Format(time.RFC3339),
UpdatedAt: time.Date(2024, 4, 12, 0, 0, 0, 0, time.UTC).Format(time.RFC3339),
ImportMeta: nil,
},
},
),
)
if err != nil {
return
}

// Use type assertion on the response for payload
payload, ok := (simulation.Payload).(*paddlenotification.CustomerNotification)
if !ok {
fmt.Println("Payload is unexpected type")
return
}

fmt.Println(simulation.ID)
fmt.Println(payload.ID)
// Output:
//ntfsim_01j9y0jwekrcyezscgkehvdmd6
//ctm_01j870snka0xdp6szgyxze6d6d
}

// // Demonstrates how to list Simulations with Payload and read the Payload back out of the response
func Example_simulation_list() {
// Create a mock HTTP server for this example - skip over this bit!
s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{simulations}}})

// Create a new Paddle client.
client, err := paddle.New(
os.Getenv("PADDLE_API_KEY"),
paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration.
)

if err != nil {
fmt.Println(err)
return
}

ctx := context.Background()

// Optionally set a transit ID on the context. This is useful to link your
// own request IDs to Paddle API requests.
ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1")

simulations, err := client.ListSimulations(ctx, &paddle.ListSimulationsRequest{})
if err != nil {
}

simulations.Iter(ctx, func(s *paddle.Simulation) (bool, error) {
switch p := s.Payload.(type) {
case *paddlenotification.AddressNotification:
// here v could be used as concrete type AddressNotification
fmt.Println(p.CustomerID)
case *paddlenotification.CustomerNotification:
// here v could be used as concrete type CustomerNotification
fmt.Println(p.Email)
}

return true, nil
})
// Output:
//[email protected]
//ctm_01hv6y1jedq4p1n0yqn5ba3ky4
}

// Demonstrates how to get a Simulation with Payload and read the Payload back out of the response
func Example_simulation_get() {
// Create a mock HTTP server for this example - skip over this bit!
s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{simulation}}})

// Create a new Paddle client.
client, err := paddle.New(
os.Getenv("PADDLE_API_KEY"),
paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration.
)

if err != nil {
fmt.Println(err)
return
}

ctx := context.Background()

// Optionally set a transit ID on the context. This is useful to link your
// own request IDs to Paddle API requests.
ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1")

simulation, err := client.GetSimulation(ctx, &paddle.GetSimulationRequest{SimulationID: "ntfsim_01j9y0jwekrcyezscgkehvdmd6"})
if err != nil {
return
}

// Use type assertion on the response for payload
payload, ok := (simulation.Payload).(*paddlenotification.CustomerNotification)
if !ok {
fmt.Println("Payload is unexpected type")
return
}

fmt.Println(simulation.ID)
fmt.Println(payload.ID)
// Output:
//ntfsim_01j9y0jwekrcyezscgkehvdmd6
//ctm_01j870snka0xdp6szgyxze6d6d
}

// Demonstrates how to run a Simulation
func Example_simulation_run() {
// Create a mock HTTP server for this example - skip over this bit!
s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{simulationRun}}})

// Create a new Paddle client.
client, err := paddle.New(
os.Getenv("PADDLE_API_KEY"),
paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration.
)

if err != nil {
fmt.Println(err)
return
}

ctx := context.Background()

// Optionally set a transit ID on the context. This is useful to link your
// own request IDs to Paddle API requests.
ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1")

simulationRun, err := client.CreateSimulationRun(ctx, &paddle.CreateSimulationRunRequest{
SimulationID: "ntfsim_01j9y0jwekrcyezscgkehvdmd6",
})
if err != nil {
return
}

fmt.Println(simulationRun.ID)
// Output:
//ntfsimrun_01j9yq3yspewy5r8zr05vkeekd
}

// Demonstrates how to get a SimulationRun with included SimulationRunEvents
func Example_simulation_run_get() {
// Create a mock HTTP server for this example - skip over this bit!
s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{simulationRunWithEvents}}})

// Create a new Paddle client.
client, err := paddle.New(
os.Getenv("PADDLE_API_KEY"),
paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration.
)

if err != nil {
fmt.Println(err)
return
}

ctx := context.Background()

// Optionally set a transit ID on the context. This is useful to link your
// own request IDs to Paddle API requests.
ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1")

simulationRun, err := client.GetSimulationRun(ctx, &paddle.GetSimulationRunRequest{
SimulationID: "ntfsim_01j9y0jwekrcyezscgkehvdmd6",
SimulationRunID: "ntfsimrun_01j9yq3yspewy5r8zr05vkeekd",
IncludeEvents: true,
})
if err != nil {
return
}

// Use type assertion on the response for payload
for _, event := range simulationRun.Events {
payload, ok := (event.Payload).(*paddlenotification.CustomerNotification)
if !ok {
fmt.Println("Payload is unexpected type")
return
}
fmt.Println(payload.ID)
fmt.Println(event.Response.StatusCode)
}

fmt.Println(simulationRun.ID)
// Output:
//ctm_01j870snka0xdp6szgyxze6d6d
//200
//ntfsimrun_01j9yq3yspewy5r8zr05vkeekd
}
4 changes: 4 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const (
transactionsPaginatedPg1 stubPath = "testdata/transactions_paginated_pg1.json"
transactionsPaginatedPg2 stubPath = "testdata/transactions_paginated_pg2.json"
priceCreatedEvent stubPath = "testdata/price_created.json"
simulation stubPath = "testdata/simulation.json"
simulations stubPath = "testdata/simulations.json"
simulationRun stubPath = "testdata/simulation_run.json"
simulationRunWithEvents stubPath = "testdata/simulation_run_with_events.json"
)

//go:embed testdata
Expand Down
7 changes: 6 additions & 1 deletion internal/response/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import (
"github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr"
)

// UnmarshalsResponses is an interface implemented by type that must unmarshal responses for themselves.
type UnmarshalsResponses interface {
UnmarshalsResponses()
}

// Response is the wrapper response type returned by the Paddle API.
type Response[T any] struct {
Data T `json:"data"`
Expand Down Expand Up @@ -58,7 +63,7 @@ func Handle(req *http.Request, res *http.Response, dst any) (err error) {
teedBytes := bytes.NewBuffer([]byte{})
tee := io.TeeReader(res.Body, teedBytes)

if dst != nil && reflect.TypeOf(dst).Elem().Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) {
if dst != nil && reflect.TypeOf(dst).Elem().Implements(reflect.TypeOf((*UnmarshalsResponses)(nil)).Elem()) {
err = json.NewDecoder(tee).Decode(dst)
} else {
err = json.NewDecoder(tee).Decode(r)
Expand Down
Loading

0 comments on commit 38c8b18

Please sign in to comment.