https://github.com/ehsoc/rest
experimental web resource abstraction for composing REST APIs in Go (Golang).
https://github.com/ehsoc/rest
api go golang openapi2 rest swagger web
Last synced: about 1 month ago
JSON representation
experimental web resource abstraction for composing REST APIs in Go (Golang).
- Host: GitHub
- URL: https://github.com/ehsoc/rest
- Owner: ehsoc
- License: mit
- Created: 2020-08-24T21:57:56.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2020-12-19T23:37:34.000Z (about 5 years ago)
- Last Synced: 2024-06-20T10:22:47.573Z (over 1 year ago)
- Topics: api, go, golang, openapi2, rest, swagger, web
- Language: Go
- Homepage:
- Size: 566 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Security: security.go
Awesome Lists containing this project
README
# rest
Rest is an experimental web resource abstraction for composing REST APIs in Go (Golang).
- **Rapid prototyping**.
- **Web Server generation (http.Handler)**
- **REST API Specification generation (OpenAPI v2)**
## Base components:
- **API**: It's the root that contains resources and server information. Also, it generates the server handler and the API specification.
- **Resource**: Each resource is a node in a URL path, and contains methods and other resources.
**Code example:**
```go
api := rest.NewAPI("/api/v1", "localhost", "My simple car API", "v1")
api.Resource("car", func(r *rest.Resource) {
r.Resource("findMatch", func(r *rest.Resource) {
})
carID := rest.NewURIParameter("carID", reflect.String)
r.ResourceP(carID, func(r *rest.Resource) {
})
})
api.Resource("ping", func(r *rest.Resource) {
})
api.Resource("user", func(r *rest.Resource) {
r.Resource("signOut", func(r *rest.Resource) {
})
})
```
**Diagram of the above code:**
```
+-----------+
| API |
+----------+ "/2" +-----------+
| | | |
| +-----+-----+ |
| | |
+------v-----+ +-----v------+ +-----v------+
| Resource | | Resource | | Resource |
+-----------+ "car" | | "ping" | | "user" |
| | | | | | |
| +-----+------+ +------------+ +-----+------+
| | |
+------v-----+ +-----v------+ +-----v------+
| Resource | | Resource | | Resource |
| "findMatch"| | "{carId}" | | "signOut" |
| | | | | |
+------------+ +------------+ +------------+
```
## API methods:
- GenerateServer(g rest.ServerGenerator) http.Handler
- GenerateSpec(w io.Writer, api rest.API)
## Example:
```go
api := rest.NewAPI("/v1", "localhost", "My simple car API", "v1")
// Generating OpenAPI v2 specification to standard output
api.GenerateSpec(os.Stdout, &oaiv2.OpenAPIV2SpecGenerator{})
// Generating server handler
server := api.GenerateServer(chigenerator.ChiGenerator{})
```
## Resource
Resource main components:
- Methods: A collection of HTTP methods.
- Resources: Collection of child resources.
## Example:
```go
api.Resource("user", func(r *rest.Resource) {
// Method
r.Get(getUser, ct)
// Resource
r.Resource("logout", func(r *rest.Resource) {
// Method
r.Get(logOut, ct)
})
})
```
## ResourceP (URI parameter Resource)
ResourceP creates a new URI parameter resource node.
The first argument must be a Parameter of URIParameter type. Use NewURIParameter to create one.
## Example:
```go
carID := rest.NewURIParameter("carID", reflect.String)
r.ResourceP(carID, func(r *rest.Resource) {
r.Get(getCar, ct).WithParameter(carID)
})
```
In the example the route to get a car will be `/car/{carID}`, where `{carID}` is the variable part.
### Method
A `Method` represents an HTTP method with an HTTP Handler. A default handler will make sense of the method specification and return the appropriate HTTP response. The specification elements to be managed by the default handler are: **Content negotiation, security, validation, and operation.**
- MethodOperation: Describes an `Operation` and responses (`Response` for success and failure).
- ContentTypes: Describes the available content-types and encoder/decoders for request and responses.
- Negotiator: Interface for content negotiation. A default implementation will be set when you create a Method.
- SecurityCollection: Is the security definition.
- Parameters: The parameters expected to be sent by the client. The main purpose of the declaration of parameters is for API specification generation.
- Handler: The http.Handler of the method. The default handler will be set when you create a new Method.
### Operation
Represents a logical operation upon a resource, like delete, list, create, ping, etc. `Operation` is an interface defined by an `Execute` method.
#### Execute method
- Inputs: `Input` type
- Output: `body` interface{}, `success` bool, and `err` error .
1. `body` (interface{}): Is the body that is going to be send to the client.(Optional)
2. `success` (bool): If the value is true, it will trigger the `successResponse` (argument passed in the `NewMethodOperation` function). If the value is false, it will trigger the `failResponse` (set it with `WithFailResponse` method). False means that the most positive operation output didn't happened, but is not an API nor a client error.
3. `err` (error): The `err`(error) is meant to indicate an API error, or any internal server error, like a database failure, i/o error, etc. The `err`!=nil will always trigger a 500 code error.
### Method:
```
+----------------+
| |
| Method |
| |
+--------+-------+
|
+--------+-------+
| |
+---+ MethodOperation+---+
| | | |
Your operation method | +--------+-------+ |
goes here | | |
+ | | |
| +------+----+ +-----+-----+ +---+-------+
| | | | Response | | Response |
+-----------> | Operation | | success | | fail |
| | | | | |
+-----------+ +-----------+ +-----------+
```
## Full code example:
```go
// Responses
successResponse := rest.NewResponse(200).WithOperationResultBody(Car{})
failResponse := rest.NewResponse(404)
getCarOperation := func(i rest.Input) (body interface{}, success bool, err error) {
carID, err := i.GetURIParam("carID")
if err != nil {
log.Println("error getting parameter: ", err)
return Car{}, false, err
}
if carID == "error" {
// Internal error trying to get the car. This will trigger a response code 500
return nil, false, errors.New("Internal error")
}
if carID != "101" {
// Car not found, success is false, no error. This will trigger the `failResponse` (response code 404)
return nil, false, nil
}
// Car found, success true, error nil. This will trigger the `successResponse` (response code 200)
return Car{carID, "Foo"}, true, nil
}
// Method Operation
getCar := rest.NewMethodOperation(rest.OperationFunc(getCarOperation), successResponse).WithFailResponse(failResponse)
// ContentTypes
ct := rest.NewContentTypes()
ct.Add("application/json", encdec.JSONEncoderDecoder{}, true)
api := rest.NewAPI("/v1", "localhost", "My simple car API", "v1")
api.Resource("car", func(r *rest.Resource) {
carID := rest.NewURIParameter("carID", reflect.String)
r.ResourceP(carID, func(r *rest.Resource) {
r.Get(getCar, ct).WithParameter(carID)
})
})
// Generating OpenAPI v2 specification to standard output
api.GenerateSpec(os.Stdout, &oaiv2.OpenAPIV2SpecGenerator{})
// Generating server routes
server := api.GenerateServer(chigenerator.ChiGenerator{})
http.ListenAndServe(":8080", server)
```