Skip to content

Commit

Permalink
unify hinted content codec for Arrows & Combinators (#58)
Browse files Browse the repository at this point in the history
* unify hinted content codec for Arrows & Combinators
* improve test coverage for http.IO
  • Loading branch information
fogfish authored Nov 6, 2023
1 parent c10f93f commit 6a43aa7
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 54 deletions.
39 changes: 16 additions & 23 deletions examples/http-response-image/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,31 @@ import (
ø "github.com/fogfish/gurl/v2/http/send"
)

type Heap struct {
image image.Image
type api struct {
http.Stack
}

// declares http I/O
func (h *Heap) request() http.Arrow {
return http.GET(
// specify specify the request
ø.URI("https://avatars.githubusercontent.com/u/716093"),
ø.Accept.Set("image/*"),

// specify requirements to the response
ƒ.Status.OK,
ƒ.ContentType.Is("image/jpeg"),
ƒ.Body(&h.image),
func (api api) request(ctx context.Context) (*image.Image, error) {
return http.IO[image.Image](api.WithContext(ctx),
http.GET(
ø.URI("https://avatars.githubusercontent.com/u/716093"),
ø.Accept.Set("image/*"),

ƒ.Status.OK,
ƒ.ContentType.Is("image/jpeg"),
),
)
}

func main() {
// instance of http stack
stack := http.New(http.WithDebugPayload())

// declares http i/o
heap := &Heap{}
lazy := heap.request()
api := api{
Stack: http.New(http.WithDebugPayload()),
}

// executes http I/O
err := stack.IO(context.Background(), lazy)
img, err := api.request(context.Background())
if err != nil {
panic(err)
}

// process image
jpeg.Encode(os.Stdout, heap.image, &jpeg.Options{Quality: 93})
jpeg.Encode(os.Stdout, *img, &jpeg.Options{Quality: 93})
}
30 changes: 3 additions & 27 deletions http/recv/arrows.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ package recv
import (
"encoding/json"
"fmt"
"image"
"io"
"strconv"
"strings"
"time"

"github.com/ajg/form"
"github.com/fogfish/gurl/v2"
"github.com/fogfish/gurl/v2/http"
"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -635,7 +633,7 @@ const (
// Supply the pointer to data target data structure.
func Body[T any](out *T) http.Arrow {
return func(cat *http.Context) error {
err := decode(
err := http.HintedContentCodec(
cat.Response.Header.Get("Content-Type"),
cat.Response.Body,
out,
Expand All @@ -655,7 +653,7 @@ func Recv[T any](out *T) http.Arrow {
func Expect[T any](expect T) http.Arrow {
return func(cat *http.Context) error {
var actual T
err := decode(
err := http.HintedContentCodec(
cat.Response.Header.Get("Content-Type"),
cat.Response.Body,
&actual,
Expand All @@ -678,28 +676,6 @@ func Expect[T any](expect T) http.Arrow {
}
}

func decode[T any](content string, stream io.ReadCloser, data *T) error {
switch {
case strings.Contains(content, "json"):
return json.NewDecoder(stream).Decode(data)
case strings.Contains(content, "www-form"):
return form.NewDecoder(stream).Decode(data)
case strings.HasPrefix(content, "image/"):
img, _, err := image.Decode(stream)
if err == nil {
*data = img.(T)
}
return err
default:
return &gurl.NoMatch{
ID: "http.Recv",
Diff: fmt.Sprintf("- Content-Type: application/{json | www-form}\n+ Content-Type: %s", content),
Protocol: "codec",
Actual: content,
}
}
}

// Bytes receive raw binary from HTTP response
func Bytes(val *[]byte) http.Arrow {
return func(cat *http.Context) (err error) {
Expand All @@ -720,7 +696,7 @@ func Match(val string) http.Arrow {
return func(cat *http.Context) (err error) {
var val any

err = decode(
err = http.HintedContentCodec(
cat.Response.Header.Get("Content-Type"),
cat.Response.Body,
&val,
Expand Down
1 change: 0 additions & 1 deletion http/recv/arrows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,6 @@ func TestBodyImage(t *testing.T) {

it.Then(t).Should(
it.Nil(err),
// it.Equal(site.Site, "example.com"),
)
}

Expand Down
13 changes: 10 additions & 3 deletions http/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ package http
import (
"encoding/json"
"fmt"
"image"
"io"
"net/http"
"strings"
Expand Down Expand Up @@ -116,7 +117,7 @@ func IO[T any](ctx *Context, arrows ...Arrow) (*T, error) {
defer ctx.Response.Body.Close()

var val T
err := decode(
err := HintedContentCodec(
ctx.Response.Header.Get("Content-Type"),
ctx.Response.Body,
&val,
Expand All @@ -128,16 +129,22 @@ func IO[T any](ctx *Context, arrows ...Arrow) (*T, error) {
return &val, nil
}

func decode[T any](content string, stream io.ReadCloser, data *T) error {
func HintedContentCodec[T any](content string, stream io.ReadCloser, data *T) error {
switch {
case strings.Contains(content, "json"):
return json.NewDecoder(stream).Decode(data)
case strings.Contains(content, "www-form"):
return form.NewDecoder(stream).Decode(data)
case strings.HasPrefix(content, "image/"):
img, _, err := image.Decode(stream)
if err == nil {
*data = img.(T)
}
return err
default:
return &gurl.NoMatch{
ID: "http.Recv",
Diff: fmt.Sprintf("- Content-Type: application/{json | www-form}\n+ Content-Type: %s", content),
Diff: fmt.Sprintf("- Content-Type: {json | www-form | image}\n+ Content-Type: %s", content),
Protocol: "codec",
Actual: content,
}
Expand Down
26 changes: 26 additions & 0 deletions http/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ package http_test

import (
"context"
"encoding/base64"
"fmt"
"image"
_ "image/png"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -231,6 +235,21 @@ func TestIO(t *testing.T) {
)
})

t.Run("Image", func(t *testing.T) {
val, err := µ.IO[image.Image](cat.WithContext(context.Background()),
µ.GET(
ø.URI("%s/image", ø.Authority(ts.URL)),
ƒ.Status.OK,
ƒ.ContentType.Is("image/png"),
),
)

it.Then(t).Should(
it.Nil(err),
).ShouldNot(
it.Nil(val),
)
})
}

func mock() *httptest.Server {
Expand All @@ -243,6 +262,13 @@ func mock() *httptest.Server {
case r.URL.Path == "/form":
w.Header().Add("Content-Type", "application/x-www-form-urlencoded")
w.Write([]byte("site=example.com"))
case strings.HasPrefix(r.URL.Path, "/image"):
w.Header().Add("Content-Type", "image/png")
dst, err := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=")
if err != nil {
panic(err)
}
w.Write(dst)
case r.URL.Path == "/ok":
w.WriteHeader(http.StatusOK)
case r.URL.Path == "/opts":
Expand Down

0 comments on commit 6a43aa7

Please sign in to comment.