Skip to content

Commit

Permalink
more work on errors
Browse files Browse the repository at this point in the history
  • Loading branch information
markbates committed Feb 19, 2024
1 parent 7862568 commit 0aea91d
Show file tree
Hide file tree
Showing 37 changed files with 735 additions and 354 deletions.
35 changes: 12 additions & 23 deletions cmd_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,22 @@ import (
)

type CmdError struct {
clam.RunError `json:"clam_error,omitempty"`
Filename string `json:"filename,omitempty"`
clam.RunError
Filename string
}

func (ce CmdError) MarshalJSON() ([]byte, error) {
mm := map[string]any{
"args": ce.Args,
"env": ce.Env,
"error": ce.Err,
"error": errForJSON(ce.Err),
"exit": ce.Exit,
"filename": ce.Filename,
"output": string(ce.Output),
"root": ce.Dir,
"type": fmt.Sprintf("%T", ce),
}

if _, ok := ce.Err.(json.Marshaler); !ok && ce.Err != nil {
mm["err"] = ce.Err.Error()
}

p := hepa.Deep()

env := make([]string, 0, len(ce.Env))
Expand All @@ -44,30 +40,23 @@ func (ce CmdError) MarshalJSON() ([]byte, error) {
}

func (ce CmdError) Error() string {
b, _ := json.MarshalIndent(ce, "", " ")
return string(b)
return toError(ce)
}

func (ce CmdError) Unwrap() error {
type Unwrapper interface {
Unwrap() error
}

if _, ok := ce.Err.(Unwrapper); ok {
return errors.Unwrap(ce.Err)
func (ce CmdError) As(target any) bool {
ex, ok := target.(*CmdError)
if !ok {
return errors.As(ce.Err, target)
}

return ce.Err
(*ex) = ce
return true
}

func (ce CmdError) Is(target error) bool {
if ce.Err == nil {
return false
if _, ok := target.(CmdError); ok {
return true
}

return errors.Is(ce.Err, target)
}

func (ce CmdError) As(target any) bool {
return errors.As(ce.Err, target)
}
37 changes: 37 additions & 0 deletions cmd_error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package hype

import (
"errors"
"fmt"
"io"
"testing"

"github.com/markbates/clam"
"github.com/stretchr/testify/require"
)

func Test_CmdError(t *testing.T) {
t.Parallel()
r := require.New(t)

oce := CmdError{
RunError: clam.RunError{
Err: io.EOF,
},
}

wrapped := fmt.Errorf("error: %w", oce)

r.True(oce.As(&CmdError{}), oce)
r.True(oce.Is(oce), oce)
r.True(oce.Unwrap() == io.EOF, oce)

var ce CmdError
r.True(errors.As(wrapped, &ce), wrapped)

ce = CmdError{}
r.True(errors.Is(wrapped, ce), wrapped)

err := errors.Unwrap(oce)
r.Equal(io.EOF, err)
}
3 changes: 3 additions & 0 deletions cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ func Test_Cmd_Execute_UnexpectedExit(t *testing.T) {
err := c.Execute(ctx, doc)
r.Error(err)

r.True(errors.Is(err, CmdError{}))

c.ExpectedExit = 1

err = c.Execute(ctx, doc)
Expand Down Expand Up @@ -151,4 +153,5 @@ func Test_Cmd_Execute_Timeout(t *testing.T) {
err := c.Execute(ctx, doc)
r.Error(err)

r.True(errors.Is(err, CmdError{}))
}
85 changes: 25 additions & 60 deletions document_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,91 +2,56 @@ package hype

import (
"context"
"errors"
"io/fs"
"strings"
"testing"
"testing/fstest"
"time"

"github.com/stretchr/testify/require"
)

func Test_Document_Execute(t *testing.T) {
t.Parallel()
r := require.New(t)

mod := `# Page 1
<foo></foo>
<include src="second/second.md"></include>`

second := `# Second Page
<foo></foo>`

cab := fstest.MapFS{
"module.md": &fstest.MapFile{
Data: []byte(mod),
},
"second/second.md": &fstest.MapFile{
Data: []byte(second),
},
}

p := NewParser(cab)
p.NodeParsers["foo"] = func(p *Parser, el *Element) (Nodes, error) {
x := executeNode{
Element: el,
}
x.ExecuteFn = func(ctx context.Context, d *Document) error {
time.Sleep(time.Millisecond * 10)

x.Lock()
x.Nodes = append(x.Nodes, Text("baz"))
x.Unlock()
return nil
}

x.Nodes = append(x.Nodes, Text("bar"))

return Nodes{x}, nil
}
t.Run("success", func(t *testing.T) {
r := require.New(t)
p := testParser(t, "testdata/doc/execution/success")

doc, err := p.ParseFile("module.md")
r.NoError(err)

ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)
defer cancel()
doc, err := p.ParseFile("module.md")
r.NoError(err)

go func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()

err = doc.Execute(ctx)
r.NoError(err)
}()

<-ctx.Done()
act := doc.String()
act = strings.TrimSpace(act)

r.NotEqual(context.DeadlineExceeded, ctx.Err())
exp := "<html><head></head><body><page>\n<h1>Command</h1>\n\n<cmd exec=\"echo 'Hello World'\"><pre><code class=\"language-shell\" language=\"shell\">$ echo Hello World\n\nHello World</code></pre></cmd>\n</page>\n</body></html>"

act := doc.String()
// fmt.Println(act)
r.Equal(exp, act)
})

exp := `<html><head></head><body><page>
<h1>Page 1</h1>
t.Run("failure", func(t *testing.T) {
r := require.New(t)
p := testParser(t, "testdata/doc/execution/failure")

<foo>barbaz</foo>
</page>
<page>
<h1>Second Page</h1>
doc, err := p.ParseFile("module.md")
r.NoError(err)

<foo>barbaz</foo>
</page>
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()

</body></html>`
err = doc.Execute(ctx)
r.Error(err)

r.Equal(exp, act)
_, ok := err.(ExecuteError)
r.True(ok, err)
r.True(errors.Is(err, ExecuteError{}), err)
})

}

Expand Down
29 changes: 29 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package hype

import (
"encoding/json"
"fmt"
)

Expand All @@ -25,3 +26,31 @@ func WrapNodeErr(n Node, err error) error {

return fmt.Errorf("%T: %w", n, err)
}

func errForJSON(err error) any {
if err == nil {
return nil
}

if _, ok := err.(json.Marshaler); ok {
return err
}

return err.Error()
}

func toError(err error) string {
if err == nil {
return ""
}

if _, ok := err.(json.Marshaler); ok {
b, err := json.MarshalIndent(err, "", " ")
if err != nil {
return "error marshalling to json: " + err.Error()
}
return string(b)
}

return err.Error()
}
43 changes: 43 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package hype

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"
)

func saveErrorJSON(t testing.TB, name string, err error) {
t.Helper()

if err == nil {
t.Fatal("error is nil")
return
}

fp := filepath.Join("testdata", "errors", "json")
// os.RemoveAll(fp)
if err := os.MkdirAll(fp, 0755); err != nil {
t.Fatal(err)
}
fp = filepath.Join(fp, name+".json")

f, ex := os.Create(fp)
if ex != nil {
t.Fatal(ex)
}
defer f.Close()

val, ok := err.(json.Marshaler)
if !ok {
fmt.Fprintf(f, "%+v", err)
return
}

enc := json.NewEncoder(f)
enc.SetIndent("", " ")
if err := enc.Encode(val); err != nil {
t.Fatal(err)
}
}
21 changes: 6 additions & 15 deletions execute_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,28 @@ import (
)

type ExecuteError struct {
Err error `json:"error,omitempty"`
Filename string `json:"filename,omitempty"`
Root string `json:"root,omitempty"`
Err error
Filename string
Root string
}

func (pe ExecuteError) MarshalJSON() ([]byte, error) {
mm := map[string]any{
"type": fmt.Sprintf("%T", pe),
"error": pe.Err,
"error": errForJSON(pe.Err),
"root": pe.Root,
"filename": pe.Filename,
}

if _, ok := pe.Err.(json.Marshaler); !ok && pe.Err != nil {
mm["error"] = pe.Err.Error()
}

return json.MarshalIndent(mm, "", " ")
}

func (pe ExecuteError) Error() string {
b, _ := json.MarshalIndent(pe, "", " ")
return string(b)
return toError(pe)
}

func (pe ExecuteError) Unwrap() error {
type Unwrapper interface {
Unwrap() error
}

if _, ok := pe.Err.(Unwrapper); ok {
if _, ok := pe.Err.(unwrapper); ok {
return errors.Unwrap(pe.Err)
}

Expand Down
Loading

0 comments on commit 0aea91d

Please sign in to comment.