Skip to content
This repository has been archived by the owner on Jul 23, 2024. It is now read-only.

Commit

Permalink
Merge pull request #105 from invopop/replicate
Browse files Browse the repository at this point in the history
Adding support for new Replicate command & GOBL 0.75
  • Loading branch information
samlown authored May 6, 2024
2 parents 0559216 + d72e091 commit 061943e
Show file tree
Hide file tree
Showing 10 changed files with 518 additions and 1 deletion.
62 changes: 62 additions & 0 deletions cmd/gobl/replicate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"encoding/json"

"github.com/invopop/gobl.cli/internal"
"github.com/spf13/cobra"
)

type replicateOpts struct {
*rootOpts
}

func replicate(root *rootOpts) *replicateOpts {
return &replicateOpts{
rootOpts: root,
}
}

func (o *replicateOpts) cmd() *cobra.Command {
cmd := &cobra.Command{
Args: cobra.MaximumNArgs(2),
RunE: o.runE,
Use: "replicate [infile] [outfile]",
Short: "Replicate a document from the provided input",
}
return cmd
}

func (o *replicateOpts) runE(cmd *cobra.Command, args []string) error {
ctx := commandContext(cmd)

input, err := openInput(cmd, args)
if err != nil {
return err
}
defer input.Close() // nolint:errcheck

out, err := o.openOutput(cmd, args)
if err != nil {
return err
}
defer out.Close() // nolint:errcheck

rOpts := &internal.ReplicateOptions{
ParseOptions: &internal.ParseOptions{
Input: input,
},
}

obj, err := internal.Replicate(ctx, rOpts)
if err != nil {
return err
}

enc := json.NewEncoder(out)
if o.indent {
enc.SetIndent("", "\t")
}

return enc.Encode(obj)
}
1 change: 1 addition & 0 deletions cmd/gobl/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func (o *rootOpts) cmd() *cobra.Command {
cmd.AddCommand(build(o).cmd())
cmd.AddCommand(sign(o).cmd())
cmd.AddCommand(correct(o).cmd())
cmd.AddCommand(replicate(o).cmd())
cmd.AddCommand(versionCmd())
cmd.AddCommand(serve().cmd())
cmd.AddCommand(keygen(o).cmd())
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.19
require (
github.com/google/go-cmp v0.5.9
github.com/imdario/mergo v0.3.15
github.com/invopop/gobl v0.74.1
github.com/invopop/gobl v0.75.0
github.com/invopop/yaml v0.3.1
github.com/labstack/echo/v4 v4.10.2
github.com/magefile/mage v1.13.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/gobl v0.74.1 h1:2B3CAhFTUIhOGPkAmZq3t9UeyAoj9QOXcNzQ1QoSLKc=
github.com/invopop/gobl v0.74.1/go.mod h1:3ixShxX1jlOKo5Rw22HVQh3jXnK9AZa7Twcw7L92qn0=
github.com/invopop/gobl v0.75.0 h1:QQpFTkraauZoGEGQlYqfHpJmBNayamtjT1+Onw8zVEA=
github.com/invopop/gobl v0.75.0/go.mod h1:3ixShxX1jlOKo5Rw22HVQh3jXnK9AZa7Twcw7L92qn0=
github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/invopop/validation v0.3.0 h1:o260kbjXzoBO/ypXDSSrCLL7SxEFUXBsX09YTE9AxZw=
Expand Down
22 changes: 22 additions & 0 deletions internal/bulk.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ type CorrectRequest struct {
Schema bool `json:"schema"`
}

// ReplicateRequest defines the payload used to generate a replicated document.
type ReplicateRequest struct {
Data []byte `json:"data"`
}

// SchemaRequest defines a body used to request a specific JSON schema
type SchemaRequest struct {
Path string `json:"path"`
Expand Down Expand Up @@ -252,6 +257,23 @@ func processRequest(ctx context.Context, req BulkRequest, seq int64, bulkOpts *B
return res
}
res.Payload, _ = marshal(env)
case "replicate":
rep := &ReplicateRequest{}
if err := json.Unmarshal(req.Payload, rep); err != nil {
res.Error = wrapError(StatusUnprocessableEntity, err)
return res
}
opts := &ReplicateOptions{
ParseOptions: &ParseOptions{
Input: bytes.NewReader(rep.Data),
},
}
env, err := Replicate(ctx, opts)
if err != nil {
res.Error = wrapError(StatusUnprocessableEntity, err)
return res
}
res.Payload, _ = marshal(env)
case "keygen":
key := dsig.NewES256Key()

Expand Down
35 changes: 35 additions & 0 deletions internal/bulk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,41 @@ func TestBulk(t *testing.T) {
},
}
})
tests.Add("replicate, success", func(t *testing.T) interface{} {
payload, err := os.ReadFile("testdata/success.json")
if err != nil {
t.Fatal(err)
}
req, err := json.Marshal(map[string]interface{}{
"action": "replicate",
"req_id": "asdf",
"payload": map[string]interface{}{
"data": base64.StdEncoding.EncodeToString(payload),
},
})
if err != nil {
t.Fatal(err)
}
return tt{
opts: &BulkOptions{
In: bytes.NewReader(req),
},
want: []*BulkResponse{
{
ReqID: "asdf",
SeqID: 1,
Payload: json.RawMessage(`{
"$schema": "https://gobl.org/draft-0/envelope"
}`),
IsFinal: false,
},
{
SeqID: 2,
IsFinal: true,
},
},
}
})
tests.Add("unknown action", func(t *testing.T) interface{} {
req, err := json.Marshal(map[string]interface{}{
"action": "frobnicate",
Expand Down
56 changes: 56 additions & 0 deletions internal/replicate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package internal

import (
"context"
"net/http"

"github.com/invopop/gobl"
"github.com/invopop/gobl/schema"
)

// ReplicateOptions define all the basic options required to build a replicated
// document from the input.
type ReplicateOptions struct {
*ParseOptions
}

// Replicate takes a base document as input and builds a replicated document
// for the output.
func Replicate(ctx context.Context, opts *ReplicateOptions) (interface{}, error) {
res, err := replicate(ctx, opts)
if err != nil {
return nil, wrapError(http.StatusUnprocessableEntity, err)
}
return res, nil
}

func replicate(ctx context.Context, opts *ReplicateOptions) (interface{}, error) {
obj, err := parseGOBLData(ctx, opts.ParseOptions)
if err != nil {
return nil, err
}

if env, ok := obj.(*gobl.Envelope); ok {
e2, err := env.Replicate()
if err != nil {
return nil, err
}
if err = e2.Validate(); err != nil {
return nil, err
}
return e2, nil
}

if doc, ok := obj.(*schema.Object); ok {
// Documents are updated in place
if err := doc.Replicate(); err != nil {
return nil, err
}
if err = doc.Validate(); err != nil {
return nil, err
}
return doc, nil
}

panic("input must be either an envelope or a document")
}
74 changes: 74 additions & 0 deletions internal/replicate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package internal

import (
"context"
"regexp"
"testing"

"github.com/stretchr/testify/assert"
"gitlab.com/flimzy/testy"
)

// These tests depend on the build_test.go for some of the basics.

func TestReplicate(t *testing.T) {
type tt struct {
opts *ReplicateOptions
err string
}

tests := testy.NewTable()

tests.Add("success", func(t *testing.T) interface{} {
return tt{
opts: &ReplicateOptions{
ParseOptions: &ParseOptions{
Input: testFileReader(t, "testdata/success.json"),
},
},
}
})

tests.Add("success just invoice", func(t *testing.T) interface{} {
return tt{
opts: &ReplicateOptions{
ParseOptions: &ParseOptions{
Input: testFileReader(t, "testdata/invoice.json"),
},
},
}
})

tests.Run(t, func(t *testing.T, tt tt) {
t.Parallel()
opts := tt.opts
got, err := Replicate(context.Background(), opts)
if tt.err == "" {
assert.Nil(t, err)
} else {
if assert.Error(t, err) {
assert.Contains(t, err.Error(), tt.err)
}
}
if err != nil {
return
}
replacements := []testy.Replacement{
{
Regexp: regexp.MustCompile(`(?s)"sigs": \[.*\]`),
Replacement: `"sigs": ["signature data"]`,
},
{
Regexp: regexp.MustCompile(`"uuid":.?"[^\"]+"`),
Replacement: `"uuid":"00000000-0000-0000-0000-000000000000"`,
},
{
Regexp: regexp.MustCompile(`"val":.?"[\w\d]{64}"`),
Replacement: `"val":"74ffc799663823235951b43a1324c70555c0ba7e3b545c1f50af34bbcc57033b"`,
},
}
if d := testy.DiffAsJSON(testy.Snapshot(t), got, replacements...); d != nil {
t.Error(d)
}
})
}
Loading

0 comments on commit 061943e

Please sign in to comment.