Skip to content

Commit

Permalink
Merge 30426f4 into 76dd1eb
Browse files Browse the repository at this point in the history
  • Loading branch information
fogfish committed Mar 29, 2020
2 parents 76dd1eb + 30426f4 commit a6ba891
Show file tree
Hide file tree
Showing 20 changed files with 193 additions and 168 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

---


The library is a thin layer of purely functional abstractions on top
of AWS Gateway API. It resolves a challenge of building simple and
declarative api implementations in the absence of pattern matching.
Expand Down Expand Up @@ -59,15 +60,14 @@ package main
import (
"github.com/aws/aws-lambda-go/lambda"
µ "github.com/fogfish/gouldian"
"github.com/fogfish/gouldian/core"
"github.com/fogfish/gouldian/path"
)

func main() {
lambda.Start(µ.Serve(hello()))
}

func hello() core.Endpoint {
func hello() µ.Endpoint {
return µ.GET(
µ.Path(path.Is("hello")),
µ.FMap(
Expand All @@ -83,9 +83,9 @@ See [example](example) folder for advanced use-case. The library [api specifica

## Next steps

* Study [Endpoint](core/endpoint.go) type and its composition
* Study [User Guide](doc/user-guide.md).

* Check build-in [collection of endpoints](request.go) to deal with HTTP request. See types: [HTTP](http://godoc.org/github.com/fogfish/gouldian/#HTTP), [APIGateway](http://godoc.org/github.com/fogfish/gouldian/#APIGateway)
* Check build-in collection of endpoints to deal with HTTP request: [path](path/path.go), [query param](param/param.go), [http header](header/header.go), [body and other](request.go)

* Endpoint always returns some `Output` that defines HTTP response. There are three cases of output: HTTP Success, HTTP Failure and general error. See [Output](http://godoc.org/github.com/fogfish/gouldian/#Output), [Issue](http://godoc.org/github.com/fogfish/gouldian/#Issue) types.

Expand Down Expand Up @@ -115,4 +115,4 @@ go test

## License

[![See LICENSE](https://img.shields.io/github/license/fogfish/gouldian.svg?style=for-the-badge)](LICENSE)
[![See LICENSE](https://img.shields.io/github/license/fogfish/gouldian.svg?style=for-the-badge)](LICENSE)
2 changes: 1 addition & 1 deletion core/auth.go → auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// limitations under the License.
//

package core
package gouldian

// AccessToken is a container for user identity
type AccessToken struct {
Expand Down
40 changes: 20 additions & 20 deletions doc/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

## Overview

A `core.Endpoint` is a key abstraction in the framework. It is a *pure function* that takes HTTP request as Input and return Output (result of request evaluation).
A `µ.Endpoint` is a key abstraction in the framework. It is a *pure function* that takes HTTP request as Input and return Output (result of request evaluation).

```go
/*
Expand All @@ -38,36 +38,36 @@ Golang is missing generics and type variance. Therefore, the Output is always an

**and-then**

Use `and-then` to build product Endpoint: `A × B ⟼ C`. The product type matches Input if each composed function successfully matches it. Compose them with `Then` function or variadic alternative `core.Join`.
Use `and-then` to build product Endpoint: `A × B ⟼ C`. The product type matches Input if each composed function successfully matches it. Compose them with `Then` function or variadic alternative `µ.Join`.

```go
var a: core.Endpoint = /* ... */
var b: core.Endpoint = /* ... */
var a: µ.Endpoint = /* ... */
var b: µ.Endpoint = /* ... */

//
// You can use `Then` method declared for the Endpoint type
c := a.Then(b)

//
// Alternatively, variadic function `Join` does same for sequence of Endpoints
c := core.Join(a, b)
c := µ.Join(a, b)
```

**or-else**

Use `or-else` to build co-product Endpoint: `A ⨁ B ⟼ C`. The co-product is also known as sum-type that matches the first successful function.

```go
var a: core.Endpoint = /* ... */
var b: core.Endpoint = /* ... */
var a: µ.Endpoint = /* ... */
var b: µ.Endpoint = /* ... */

//
// You can use `Or` method declared for the Endpoint type
c := a.Or(b)

//
// Alternatively, variadic `Or` variant does same for sequence of Endpoints
c := core.Or(a, b)
c := µ.Or(a, b)
```

These rules of Endpoint composition allow developers to build any complex HTTP request handling from small re-usable block.
Expand Down Expand Up @@ -99,10 +99,10 @@ Gouldian library delivers set of built-in endpoints to deal with HTTP request pr

**Match HTTP Verb/Method**

`func Method(verb string) core.Endpoint` builds the `Endpoint` that matches HTTP Verb. You supplies either a valid HTTP Verb or wildcard to match anything.
`func Method(verb string) µ.Endpoint` builds the `Endpoint` that matches HTTP Verb. You supplies either a valid HTTP Verb or wildcard to match anything.

```go
e := core.Join(µ.Method("GET"), /* ... */)
e := µ.Join(µ.Method("GET"), /* ... */)
e(mock.Input())

// The library implements a syntax sugar for mostly used HTTP Verbs
Expand All @@ -112,7 +112,7 @@ e(mock.Input())

**Match Path**

`func Path(arrows ...path.Arrow) core.Endpoint` builds the `Endpoint` that matches URL path from HTTP request. The endpoint considers the path as an ordered sequence of segments, it takes a corresponding product of segment pattern matchers/extractors (they are defined in [`path`](../path/path.go) package).
`func Path(arrows ...path.Arrow) µ.Endpoint` builds the `Endpoint` that matches URL path from HTTP request. The endpoint considers the path as an ordered sequence of segments, it takes a corresponding product of segment pattern matchers/extractors (they are defined in [`path`](../path/path.go) package).

```go
e := µ.Path(path.Is("foo"), path.Is("bar"))
Expand Down Expand Up @@ -144,7 +144,7 @@ e(mock.Input(mock.URL("/foo/bar")))

A handing of query string params for HTTP request is consistent with matching/extracting path segments.

`func Param(arrows ...param.Arrow) core.Endpoint` builds the `Endpoint` that matches URL query string from HTTP request. The endpoint considers a query params as a hashmap, it takes a product of params matchers/extractors (they are defined in [`param`](../param/param.go) package). Functions `param.Is` and `param.Any` matches query params; `param.String`, `param.MaybeString`, `param.Int` and `param.MaybeInt` extracts values.
`func Param(arrows ...param.Arrow) µ.Endpoint` builds the `Endpoint` that matches URL query string from HTTP request. The endpoint considers a query params as a hashmap, it takes a product of params matchers/extractors (they are defined in [`param`](../param/param.go) package). Functions `param.Is` and `param.Any` matches query params; `param.String`, `param.MaybeString`, `param.Int` and `param.MaybeInt` extracts values.

```go
var text string
Expand All @@ -159,7 +159,7 @@ e(mock.Input(mock.URL("/?foo=bar&q=text")))

A handing of HTTP headers is consistent with matching/extracting path segments.

`func Header(arrows ...header.Arrow) core.Endpoint` builds the `Endpoint` that matches HTTP request headers. The endpoint considers headers as a hashmap, it takes a product of header matchers/extractors (they are defined in [`header`](../header/header.go) package). Functions `header.Is` and `header.Any` matches headers; `header.String`, `header.MaybeString`, `header.Int` and `header.MaybeInt` extracts values.
`func Header(arrows ...header.Arrow) µ.Endpoint` builds the `Endpoint` that matches HTTP request headers. The endpoint considers headers as a hashmap, it takes a product of header matchers/extractors (they are defined in [`header`](../header/header.go) package). Functions `header.Is` and `header.Any` matches headers; `header.String`, `header.MaybeString`, `header.Int` and `header.MaybeInt` extracts values.

```go
var length int
Expand Down Expand Up @@ -189,7 +189,7 @@ e(mock.Input(mock.Text("{\"username\":\"Joe Doe\"}")))

**Authentication with AWS Cognito**

The library defines a type `core.AccessToken` and `func AccessToken(val *core.AccessToken) core.Endpoint` to extract JWT access token, which is provided by AWS Cognito service.
The library defines a type `µ.AccessToken` and `func JWT(val *µ.AccessToken) µ.Endpoint` to extract JWT access token, which is provided by AWS Cognito service.


## High-order Endpoints
Expand All @@ -203,8 +203,8 @@ Use the product combinator to declare *conjunctive conditions*.
```go
// High Order Product Endpoint
// /search?q=:text
func search(q *string) core.Endpoint {
return core.Join(
func search(q *string) µ.Endpoint {
return µ.Join(
µ.Path(path.Is("search")),
µ.Param(param.String("q", q))
)
Expand All @@ -223,10 +223,10 @@ A co-product represents either-or endpoint evaluation.
// High Order CoProduct Endpoint
// /search?q=:text
// /search/:text
func search(q *string) core.Endpoint {
return core.Or(
func search(q *string) µ.Endpoint {
return µ.Or(
µ.Path(path.Is("search"), path.String(q)),
core.Join(
µ.Join(
µ.Path(path.Is("search")),
µ.Param(param.String("q", q)),
),
Expand All @@ -240,7 +240,7 @@ var q string

## Mapping Endpoints

A business logic is defined as Endpoint mapper with help of closure functions `Ø ⟼ Output`. The library provides `func FMap(f func() error) core.Endpoint` function. It lifts a transformer into Endpoint so that it is composable with other Endpoints.
A business logic is defined as Endpoint mapper with help of closure functions `Ø ⟼ Output`. The library provides `func FMap(f func() error) µ.Endpoint` function. It lifts a transformer into Endpoint so that it is composable with other Endpoints.

```go
µ.GET(
Expand Down
2 changes: 1 addition & 1 deletion core/endpoint.go → endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// limitations under the License.
//

package core
package gouldian

import (
"errors"
Expand Down
16 changes: 8 additions & 8 deletions core/endpoint_test.go → endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,32 @@
// limitations under the License.
//

package core_test
package gouldian_test

import (
"errors"
"testing"

"github.com/fogfish/gouldian/core"
µ "github.com/fogfish/gouldian"
"github.com/fogfish/gouldian/mock"
"github.com/fogfish/it"
)

func TestEndpointThen(t *testing.T) {
var ok = errors.New("b")
var a core.Endpoint = func(x *core.Input) error { return nil }
var b core.Endpoint = func(x *core.Input) error { return ok }
var c core.Endpoint = a.Then(b)
var a µ.Endpoint = func(x *µ.Input) error { return nil }
var b µ.Endpoint = func(x *µ.Input) error { return ok }
var c µ.Endpoint = a.Then(b)

it.Ok(t).
If(c(mock.Input())).Should().Equal(ok)
}

func TestEndpointOr(t *testing.T) {
var ok = errors.New("a")
var a core.Endpoint = func(x *core.Input) error { return ok }
var b core.Endpoint = func(x *core.Input) error { return nil }
var c core.Endpoint = a.Or(b)
var a µ.Endpoint = func(x *µ.Input) error { return ok }
var b µ.Endpoint = func(x *µ.Input) error { return nil }
var c µ.Endpoint = a.Or(b)

it.Ok(t).
If(c(mock.Input())).Should().Equal(ok)
Expand Down
2 changes: 1 addition & 1 deletion core/errors.go → errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
package core
package gouldian

import "fmt"

Expand Down
3 changes: 1 addition & 2 deletions example/hello-world/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@ package main
import (
"github.com/aws/aws-lambda-go/lambda"
µ "github.com/fogfish/gouldian"
"github.com/fogfish/gouldian/core"
"github.com/fogfish/gouldian/path"
)

func main() {
lambda.Start(µ.Serve(hello()))
}

func hello() core.Endpoint {
func hello() µ.Endpoint {
return µ.GET(
µ.Path(path.Is("hello")),
µ.FMap(
Expand Down
31 changes: 15 additions & 16 deletions example/httpbin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (

"github.com/fogfish/gouldian"
µ "github.com/fogfish/gouldian"
"github.com/fogfish/gouldian/core"
"github.com/fogfish/gouldian/header"
"github.com/fogfish/gouldian/param"
"github.com/fogfish/gouldian/path"
Expand All @@ -49,10 +48,10 @@ type response struct {
//
//-----------------------------------------------------------------------------

func anyMethod(subpath string) core.Endpoint {
func anyMethod(subpath string) µ.Endpoint {
var h headers

return core.Join(
return µ.Join(
µ.Path(path.Is(subpath)),
µ.Header(
header.MaybeString("Accept", &h.Accept),
Expand All @@ -70,23 +69,23 @@ func anyMethod(subpath string) core.Endpoint {
)
}

func delete() core.Endpoint {
func delete() µ.Endpoint {
return µ.DELETE(anyMethod("delete"))
}

func get() core.Endpoint {
func get() µ.Endpoint {
return µ.GET(anyMethod("get"))
}

func patch() core.Endpoint {
func patch() µ.Endpoint {
return µ.PATCH(anyMethod("patch"))
}

func post() core.Endpoint {
func post() µ.Endpoint {
return µ.POST(anyMethod("post"))
}

func put() core.Endpoint {
func put() µ.Endpoint {
return µ.PUT(anyMethod("put"))
}

Expand All @@ -96,7 +95,7 @@ func put() core.Endpoint {
//
//-----------------------------------------------------------------------------

func bearer() core.Endpoint {
func bearer() µ.Endpoint {
var token string
return µ.GET(
µ.Path(path.Is("bearer")),
Expand All @@ -115,7 +114,7 @@ func bearer() core.Endpoint {
//
//-----------------------------------------------------------------------------

func status() core.Endpoint {
func status() µ.Endpoint {
var code int
return µ.GET(
µ.Path(path.Is("status"), path.Int(&code)),
Expand All @@ -133,7 +132,7 @@ func status() core.Endpoint {
//
//-----------------------------------------------------------------------------

func head() core.Endpoint {
func head() µ.Endpoint {
var h headers

return µ.GET(
Expand All @@ -153,7 +152,7 @@ func head() core.Endpoint {
)
}

func ip() core.Endpoint {
func ip() µ.Endpoint {
var ip string
return µ.GET(
µ.Path(path.Is("ip")),
Expand All @@ -166,7 +165,7 @@ func ip() core.Endpoint {
)
}

func ua() core.Endpoint {
func ua() µ.Endpoint {
var ua string
return µ.GET(
µ.Path(path.Is("user-agent")),
Expand All @@ -185,7 +184,7 @@ func ua() core.Endpoint {
//
//-----------------------------------------------------------------------------

func redirect1() core.Endpoint {
func redirect1() µ.Endpoint {
return µ.GET(
µ.Path(path.Is("redirect"), path.Is("1")),
µ.FMap(
Expand All @@ -196,7 +195,7 @@ func redirect1() core.Endpoint {
)
}

func redirectN() core.Endpoint {
func redirectN() µ.Endpoint {
var host string
var n int
return µ.GET(
Expand All @@ -210,7 +209,7 @@ func redirectN() core.Endpoint {
)
}

func redirectTo() core.Endpoint {
func redirectTo() µ.Endpoint {
var to string
return µ.GET(
µ.Path(path.Is("redirect-to")),
Expand Down

0 comments on commit a6ba891

Please sign in to comment.