├── LICENSE ├── README.md ├── api_test.go ├── aux.go ├── circular_dependency.go ├── context.go ├── dependency.go ├── doc.go ├── id.go ├── init_test.go ├── interface_test.go ├── method.go ├── new_test.go ├── resource.go ├── route.go ├── router.go └── router_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Rafael Moreira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Image of Yaktocat](http://resoursea.com/img/resoursea-logo-header.png) 2 | 3 | ## What is Resoursea? 4 | 5 | A high productivity web framework for quickly writing resource based services fully implementing the REST architectural style. 6 | 7 | This framework allows you to really focus on the Resources and how it behaves, and let the tool for routing the requests and inject the required dependencies. 8 | 9 | This framework is written in [Golang](http://golang.org/) and uses the power of its implicit Interface and decentralized package manager. 10 | 11 | ## Features 12 | 13 | - Describes the service API as a Go *struct* structure. 14 | - Method dependencies are constructed and injected when requested. 15 | - Resources becomes accessible simply defining the HTTP methods it is listening to. 16 | 17 | ## Getting Started 18 | 19 | First [install Go](https://golang.org/doc/install) and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH). 20 | 21 | Install the Resoursea package: 22 | 23 | ~~~ 24 | go get github.com/resoursea/api 25 | ~~~ 26 | 27 | To create your service all you have to do is create ordinary Go *structs* and call the `api.newRouter` to route them for you. Then, just call the standard Go server to provide the resources on the network. 28 | 29 | ## By Example 30 | 31 | Save the code below in a file named `main.go`. 32 | 33 | ~~~ go 34 | package main 35 | 36 | import ( 37 | "log" 38 | "net/http" 39 | 40 | "github.com/resoursea/api" 41 | ) 42 | 43 | type Gopher struct { 44 | Message string 45 | } 46 | 47 | func (r *Gopher) GET() *Gopher { 48 | return r 49 | } 50 | 51 | func main() { 52 | router, err := api.NewRouter(Gopher{ 53 | Message: "Hello Gophers!", 54 | }) 55 | if err != nil { 56 | log.Fatalln(err) 57 | } 58 | 59 | // Starting de HTTP server 60 | log.Println("Starting the service on http://localhost:8080/") 61 | if err := http.ListenAndServe(":8080", router); err != nil { 62 | log.Fatalln(err) 63 | } 64 | } 65 | ~~~ 66 | 67 | Then run your new service: 68 | 69 | ~~~ 70 | go run main.go 71 | ~~~ 72 | 73 | Now you have a new REST service runnig, to **GET** your new `Gopher` Resource, open any browser and type `http://localhost:8080/gopher`. 74 | 75 | Another more complete example shows how to build and testing a [simple library service](https://github.com/resoursea/example) with database access, dependency injection and the use of `api.ID`. 76 | 77 | ## Basis 78 | 79 | - Create a hierarchy of ordinary Go *structs* and it will be mapped and routed, each *struct* will turn into a new Resource. 80 | 81 | - Define HTTP methods for Resources these methods will be cached and routed. 82 | 83 | - Define the dependencies of each method and these dependencies will be constructed and injected whenever necessary. 84 | 85 | - You can define the initial state of some Resource and it will be injected in the initializer and constructor methods. 86 | 87 | - Resources can define an initializer `Init` method and it will be used to change the initial state of this Resource. It runs just one time when resources are being mapped. 88 | 89 | - Resources can define a constructor `New` method, it will be used to construct the Resource every time it needs to be injected. It runs every time one method depends on this resource. 90 | 91 | - The URI address of the Resource will be the identifier of the field that receives this Resource. 92 | 93 | - The root of the Resource tree isn't attached to any field, so you can pass 2 optional parameters when creating the router: the field identifier and the field tag. 94 | 95 | ### More Info 96 | 97 | * Initial state of Resources are optional, if not defined a new empty instance will be used. 98 | 99 | * The initialzer method `Init` is optional, if not declared the initial state will remains the same. 100 | 101 | * The constructor method `New` is optional, if not declared the initial state will be injected. 102 | 103 | * The first argument of a Go *struct* method is the *struct* itself, it means that for mapped methods the instance of the Resource will be always injected as the first argument. 104 | 105 | * One of the constraints for a REST services is to don't keep states in the server component, it means that the Resources shouldn't keep states over the connection. For this rason, every request will receive a new constructed Resource of each dependency. 106 | 107 | * Initializers cant have dependencies, and you can return just the resource itself and/or an error. 108 | 109 | * Constructors can have dependencies, but **you can't design a circular dependency**, and you can return just the resource itself and/or an error. 110 | 111 | * Obs: If you change the state of some dependency somewhere that isn't it's method constructor, when it receives pointer Dependency value for instance, it can cause unexpected behavior. 112 | 113 | ### The Resource Tree 114 | 115 | Resources is declared using ordinary Go *structs* and *slices* of *struts*. 116 | 117 | When declaring the service you create a tree of *structs* that will be mapped in routes. 118 | 119 | If you declare a list of Resources `type Gophers []Gopher` its behavior will be:: 120 | 121 | - Requests for the route `/gophers` will be answered by the `Gophers` type. 122 | 123 | - Requests for the route `/gophers/:ID` will be answered by the `Gopher` type. 124 | 125 | - Methods in `Gopher` could request for `*api.ID`. This dependency keeps the requested ID for this Resource present in the URI. 126 | 127 | ### Initializer `Init` Method 128 | 129 | This method is used to insert/modify the initial value of some method. If you defined the initial state of this resource on the API creation, this state will always be injected as the first argument of this method. This method just can return the resource itself and/or an error. If this method returns an error, this value will be returned by the `api.NewRouter` method. 130 | 131 | ### Constructor `New` Method 132 | 133 | This method is used to construct the value of the Resource before it is injected. The initial value of this method will always be injected as the first argument of this method. This method just can return the resource itself and/or an error. If this method returns an error, this value can be caught by any subsequent method. 134 | 135 | 136 | ### ID Dependency 137 | 138 | This dependency is used to identify one Resource in a list. The `api.ID` dependency will be injected in the Resource's methods that it's parent is a slice of the Resource itself. 139 | 140 | ### Interface Dependency 141 | 142 | Interfaces can be used to decouple the service of the Resource's implementation. When an method is requiring an Interface, the framework will search in the Resource tree which Resource satisfies this Interface. It searches in the siblings and uncles until reaches the root of the tree. If no Resource were found to satisfy thi Interface, an error is returned on the mapping time. 143 | 144 | So, if some method requires one Interface, you should specify at least one implementation of this interface in the Resource tree. You can add all the interfaces implementation in the root of your service, so it will be easy to change the implementation if it's necessary. 145 | 146 | ## A More Complete Example 147 | 148 | ~~~ go 149 | package main 150 | 151 | import ( 152 | "log" 153 | "net/http" 154 | 155 | "github.com/resoursea/api" 156 | ) 157 | 158 | type Gopher struct { 159 | ID int 160 | Message string 161 | Initialized bool 162 | } 163 | 164 | func (r *Gopher) Init() *Gopher { 165 | r.Initialized = true 166 | return r 167 | } 168 | 169 | func (r *Gopher) New(id api.ID) (*Gopher, error) { 170 | idInt, err := id.Int() 171 | if err != nil { 172 | return nil, err 173 | } 174 | r.ID = idInt 175 | return r, nil 176 | } 177 | 178 | func (r *Gopher) GET(err error) (*Gopher, error) { 179 | return r, err 180 | } 181 | 182 | type Gophers []Gopher 183 | 184 | type API struct { 185 | Gophers Gophers 186 | } 187 | 188 | func main() { 189 | router, err := api.NewRouter(API{ 190 | Gophers: Gophers{ 191 | Gopher{ 192 | Message: "Hello Gophers!", 193 | Initialized: false, 194 | }, 195 | }, 196 | }) 197 | if err != nil { 198 | log.Fatalln(err) 199 | } 200 | 201 | // Starting de HTTP server 202 | log.Println("Starting the service on http://localhost:8080/") 203 | if err := http.ListenAndServe(":8080", router); err != nil { 204 | log.Fatalln(err) 205 | } 206 | } 207 | 208 | ~~~ 209 | 210 | When you run de service above and try to **GET** one specific `Gopher`, accessing `http://localhost:8080/api/gophers/123` in a browser, the server will return: 211 | 212 | ~~~ javascript 213 | { 214 | "Gopher": { 215 | "ID": 123, 216 | "Message": "Hello Gophers!", 217 | "Initialized": true, 218 | } 219 | } 220 | ~~~ 221 | 222 | Here we can see that the declared initial state was injected in the `Gopher` initializer, and this method updates the initial state. The initial state of `Gopher` and the `api.ID`, sent by the URI, was injected in the constructor method. The `GET` method of `Gopher` just listen for an **HTTP GET** action and return the injected values to the client. 223 | 224 | 225 | ### Mapped Methods 226 | 227 | In the REST arquitecture HTTP methods should be used explicitly in a way that's consistent with the protocol definition. This basic REST design principle establishes a one-to-one mapping between create, read, update, and delete (CRUD) operations and HTTP methods. According to this mapping: 228 | 229 | 230 | - GET = Retrieve a representation of a Resource. 231 | - POST = Create a new Resource subordinate of the specified resource collection. 232 | - PUT = Update the specified Resource. 233 | - DELETE = Delete the specified Resource. 234 | - HEAD = Get metadata about the specified Resource. 235 | 236 | This thing scans and route all Resource's methods that has some of those prefix. Methods also can be used to create the Actions some Resource can perform, you can declare it this way: `POSTLike()`. It will be mapped to the route `[POST] /resource/like`. If you declare just `POST()`, it will be mapped to the route `[POST] /resource`. 237 | 238 | 239 | ### Dependency Injection 240 | 241 | When this framework is creating the Routes for mapped methods, it creates a tree with the dependencies of each method and ensures that there is no circular dependency. This tree is used to answer the request using a depth-first pos-order scanning to construct the dependencies, which ensures that every dependency will be present in the context before it is was requested. 242 | 243 | When injecting the required dependency, first the framework search for the initial value of the Resource in the Resource tree, if it wasn't declared, it creates a new empty value for the `struct`. If this dependency has a creator method (New), it is called using this value, and its returned values is injected on the subsequent dependencies until arrive to the root of the dependency tree, the mapped HTTP method itself. 244 | 245 | If the method is requesting for an Interface, the framework need find in the Resource tree which one implements it, the framework will search in the siblings, parents or uncles. The same search is done when requiring Structs too, but it is not necessary to be in the Resource tree, if it is not present just a new empty value is used. All this process is done in the route creation time, it guarantee that everything is cached before start to receive the client requests. 246 | 247 | 248 | ### Resoursea Ecosystem 249 | 250 | You also has a high software reuse through the sharing of Resources already created by the community. It’s the resource sea! 251 | 252 | Think about a Resource used by virtually all web services, like an instance of the database. Most web services require this Resource to process the request. This Resource and its behavior not need to be implemented by all the developers. It is much better if everyone uses and contribute with just one package that contains this Resource. Thus we’ll have stable and secure packages with Resources to suit most of the needs. Allowing the exclusive focus on particular business rule, of your service. 253 | 254 | In Go the explicit declaration of implementation of an Interface is not required, which provide a decoupling of the Interface with the struct which satisfies this interface. This added to the fact tat Go provides a decentralized package manager provides the ideal environment for the sustainable growth of an ecosystem with interfaces and features that can be reused. 255 | 256 | Think of a scenario with a list of interfaces, each with a list of Resources that implements it. His work as a developer of services is choosing the interfaces and Resources to attend the requirements of the service and implements only the specific features of your nincho. 257 | 258 | ### Router Printer 259 | 260 | A [printer package](https://github.com/resoursea/printer) was created just for debug reasons. If you want to see the tree of mapped routes and methods, you can import the `https://github.com/resoursea/printer` package and use the `printer.Router` method, passing the *Router* interface returned by a `api.newRouter` call. 261 | 262 | ## Larn More 263 | 264 | [The concept, Samples, Documentation, Interfaces and Resources to use...](http://resoursea.com) 265 | 266 | ## Join The Community 267 | 268 | * [Google Groups](https://groups.google.com/d/forum/resoursea) via [resoursea@googlegroups.com](mailto:resoursea@googlegroups.com) 269 | * [GitHub Issues](https://github.com/resoursea/api/issues) 270 | * [Leave us a comment](https://docs.google.com/forms/d/1GCKn7yN4UYsS4Pv7p2cwHPRfdrURbvB0ajQbaTJrtig/viewform) 271 | * [Twitter](https://twitter.com/resoursea) 272 | -------------------------------------------------------------------------------- /api_test.go: -------------------------------------------------------------------------------- 1 | // This package implements an API structure 2 | // used to test many concepts of this framework 3 | package api 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "net/http/httptest" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | // The initial states of my Resources 14 | var api = API{ 15 | Version: Version{ 16 | Version: 1, 17 | Message: "API version: ", 18 | }, 19 | Gophers: Gophers{ 20 | Gopher{ 21 | Id: 1, 22 | Message: "I love you", 23 | }, 24 | Gopher{ 25 | Id: 2, 26 | Message: "I still love programming", 27 | }, 28 | Gopher{ 29 | Id: 3, 30 | Message: "You so cute", 31 | }, 32 | }, 33 | Maltese: Maltese{}, // My Dogger implementation 34 | 35 | } 36 | 37 | type API struct { 38 | Gophers Gophers 39 | Version Version 40 | Maltese Maltese 41 | Date Date 42 | } 43 | 44 | func (a *API) GETDogBark(dog Doger) string { 45 | return dog.Bark() 46 | } 47 | 48 | type Date struct { 49 | Time time.Time 50 | } 51 | 52 | func (d *Date) New() *Date { 53 | d.Time = time.Now().Local() 54 | return d 55 | } 56 | 57 | func (d *Date) GET() *Date { 58 | return d 59 | } 60 | 61 | type Version struct { 62 | Version int 63 | Message string 64 | } 65 | 66 | func (v *Version) Init() (*Version, error) { 67 | if v.Version != 1 { 68 | return nil, fmt.Errorf("Initial value of version not received") 69 | } 70 | v.Message = fmt.Sprintf("%s %d", v.Message, v.Version) 71 | return v, nil 72 | } 73 | 74 | func (v *Version) GET() *Version { 75 | return v 76 | } 77 | 78 | type Gophers []Gopher 79 | 80 | // Testing the Init method, returning the new value to be used 81 | func (gs Gophers) Init() (Gophers, error) { 82 | if len(gs) != len(api.Gophers) { 83 | return nil, fmt.Errorf("Gophers Init received a different initial value") 84 | } 85 | gs = append(gs, Gopher{ 86 | Id: 4, 87 | Message: "Intruder", 88 | }) 89 | return gs, nil 90 | } 91 | 92 | func (gs *Gophers) GET(err error) (*Gophers, error) { 93 | return gs, err 94 | } 95 | 96 | type Gopher struct { 97 | Id int 98 | Message string 99 | } 100 | 101 | // A constructor for Gopher dependency 102 | // Receives a Gophers dependency, and an ID passed on the URI 103 | // Gophers has no constructor, then is injected the raw initial state for Gophers 104 | func (_ *Gopher) New(gs Gophers, id ID) (*Gopher, error) { 105 | // Getting the ID in the URI 106 | i, err := id.Int() 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | gopher := &Gopher{} 112 | for _, g := range gs { 113 | if g.Id == i { 114 | *gopher = g 115 | } 116 | } 117 | if gopher == nil { 118 | return nil, fmt.Errorf("Id %d not found in Gophers list", i) 119 | } 120 | return gopher, nil 121 | } 122 | 123 | func (g *Gopher) GET(err error) (*Gopher, error) { 124 | return g, err 125 | } 126 | func (g *Gopher) GETMessage(err error) (string, error) { 127 | if err != nil { 128 | return "", err 129 | } 130 | return g.Message, err 131 | } 132 | 133 | type Doger interface { 134 | Bark() string 135 | } 136 | 137 | type Maltese struct{} 138 | 139 | func (m *Maltese) Bark() string { 140 | return "yap-yap" 141 | } 142 | 143 | // 144 | // Reponse data 145 | // 146 | 147 | type ErrorResp struct { 148 | Error *string 149 | } 150 | 151 | type StringResp struct { 152 | String string 153 | } 154 | 155 | type VersionResp struct { 156 | Version Version 157 | Error string 158 | } 159 | 160 | type DateResp struct { 161 | Date Date 162 | } 163 | 164 | type GopherResp struct { 165 | Gopher Gopher 166 | } 167 | 168 | type GophersResp struct { 169 | Gophers Gophers 170 | } 171 | 172 | // Try to get any Error from the response 173 | // If it returned an error, it fails the Test 174 | func errorTest(w *httptest.ResponseRecorder, t *testing.T) { 175 | var errResp ErrorResp 176 | err := json.Unmarshal(w.Body.Bytes(), &errResp) 177 | if err != nil { 178 | t.Fatal(errResp) 179 | } 180 | if errResp.Error != nil { 181 | t.Fatal("Error returned!") 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /aux.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "reflect" 9 | "strings" 10 | ) 11 | 12 | var httpMethods = [...]string{ 13 | "GET", 14 | "PUT", 15 | "POST", 16 | "DELETE", 17 | "HEAD", 18 | } 19 | 20 | // Constants to test type equality 21 | var ( 22 | responseWriterPtrType = reflect.TypeOf((*http.ResponseWriter)(nil)) 23 | responseWriterType = responseWriterPtrType.Elem() 24 | requestPtrType = reflect.TypeOf((*http.Request)(nil)) 25 | requestType = requestPtrType.Elem() 26 | errorSliceType = reflect.TypeOf(([]error)(nil)) 27 | errorType = errorSliceType.Elem() 28 | errorNilValue = reflect.New(errorType).Elem() 29 | ) 30 | 31 | // This method return true if the received type is an context type 32 | // It means that it doesn't need to be mapped and will be present in the context 33 | // It also return an error message if user used *http.ResponseWriter or used http.Request 34 | // Context types include error and []error types 35 | func isContextType(resourceType reflect.Type) bool { 36 | // Test if user used *http.ResponseWriter insted of http.ResponseWriter 37 | if resourceType.AssignableTo(responseWriterPtrType) { 38 | log.Fatalf("You asked for %s when you should used %s", resourceType, responseWriterType) 39 | } 40 | // Test if user used http.Request insted of *http.Request 41 | if resourceType.AssignableTo(requestType) { 42 | log.Fatalf("You asked for %s when you should used %s", resourceType, requestPtrType) 43 | } 44 | 45 | return resourceType.AssignableTo(responseWriterType) || 46 | resourceType.AssignableTo(requestPtrType) || 47 | resourceType.AssignableTo(errorType) || 48 | resourceType.AssignableTo(errorSliceType) || 49 | resourceType.Implements(idInterfaceType) 50 | } 51 | 52 | // Return one Ptr to the given Value... 53 | // - If receive Struct or *Struct, resturn *Struct 54 | // - If receive []Struct, return *[]Struct 55 | // - If receive []*Struct, return *[]*Struct 56 | func ptrOfValue(value reflect.Value) reflect.Value { 57 | ptr := reflect.New(elemOfType(value.Type())) 58 | if value.Kind() == reflect.Ptr && !value.IsNil() { 59 | ptr.Elem().Set(value.Elem()) 60 | } 61 | if value.Kind() == reflect.Struct || value.Kind() == reflect.Slice { 62 | ptr.Elem().Set(value) 63 | } 64 | return ptr 65 | } 66 | 67 | // Return the Value of the Ptr to Elem of the inside the given Slice 68 | // []struct, *[]struct, *[]*struct -> return struct Value 69 | func elemOfSliceValue(value reflect.Value) reflect.Value { 70 | 71 | // If Value is a Ptr, get the Elem it points to 72 | sliceValue := elemOfValue(value) 73 | 74 | // Type of the Elem inside the given Slice 75 | // []struct or []*struct -> return struct type 76 | elemType := elemOfType(sliceValue.Type().Elem()) 77 | 78 | // Creates a new Ptr to Elem of the Type this Slice stores 79 | ptrToElem := reflect.New(elemType) 80 | 81 | if sliceValue.IsNil() || sliceValue.Len() == 0 { 82 | // If given Slice is null or it has nothing inside it 83 | // return the new empty Value of the Elem inside this slice 84 | return ptrToElem 85 | } 86 | 87 | // If this slice has an Elem inside it 88 | // and set the value of the first Elem inside this Slice 89 | ptrToElem.Elem().Set(elemOfValue(sliceValue.Index(0))) 90 | 91 | return ptrToElem 92 | } 93 | 94 | // If Value is a Ptr, return the Elem it points to 95 | func elemOfValue(value reflect.Value) reflect.Value { 96 | if value.Kind() == reflect.Ptr { 97 | // It occours if an Struct has an Field that points itself 98 | // so it should never occours, and will be cauch by the check for Circular Dependency 99 | if !value.Elem().IsValid() { 100 | value = reflect.New(elemOfType(value.Type())) 101 | } 102 | return value.Elem() 103 | } 104 | return value 105 | } 106 | 107 | // If Type is a Ptr, return the Type of the Elem it points to 108 | func elemOfType(t reflect.Type) reflect.Type { 109 | if t.Kind() == reflect.Ptr { 110 | return t.Elem() 111 | } 112 | return t 113 | } 114 | 115 | // If Type is a Slice, return the Type of the Elem it stores 116 | func elemOfSliceType(t reflect.Type) reflect.Type { 117 | if t.Kind() == reflect.Slice { 118 | return t.Elem() 119 | } 120 | return t 121 | } 122 | 123 | // If Type is a Ptr, return the Type of the Elem it points to 124 | // If Type is a Slice, return the Type of the Elem it stores 125 | func mainElemOfType(t reflect.Type) reflect.Type { 126 | t = elemOfType(t) 127 | t = elemOfSliceType(t) 128 | t = elemOfType(t) 129 | return t 130 | 131 | } 132 | 133 | // If Type is not a Ptr, return the one Type that points to it 134 | func ptrOfType(t reflect.Type) reflect.Type { 135 | if t.Kind() != reflect.Ptr { 136 | return reflect.PtrTo(t) 137 | } 138 | return t 139 | } 140 | 141 | // Return the true if is an exported field of those of types 142 | // Struct, *Struct, []Struct or []*Struct 143 | // Check if this field is exported, begin with UpperCase: v.CanInterface() 144 | // and if this field is valid fo create Resources: Structs or Slices of Structs 145 | func isValidValue(v reflect.Value) bool { 146 | if !v.CanInterface() { 147 | return false 148 | } 149 | if mainElemOfType(v.Type()).Kind() == reflect.Struct { 150 | return true 151 | } 152 | return false 153 | } 154 | 155 | // Return the true if the dependency is one of those types 156 | // Interface, Struct, *Struct, []Struct or []*Struct 157 | func isValidDependencyType(t reflect.Type) error { 158 | t = elemOfType(t) 159 | 160 | if t.Kind() == reflect.Interface || t.Kind() == reflect.Struct || 161 | t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Struct { 162 | return nil 163 | } 164 | 165 | return fmt.Errorf("Type %s is not allowed as dependency", t) 166 | } 167 | 168 | // Return true if this Type is a Slice or Ptr to Slice 169 | func isSliceType(t reflect.Type) bool { 170 | if t.Kind() == reflect.Ptr { 171 | t = t.Elem() 172 | } 173 | return t.Kind() == reflect.Slice 174 | } 175 | 176 | // 'Init' methods should have no Inputs, 177 | // and it can just output an error 178 | func isValidInit(m reflect.Method) error { 179 | 180 | if m.Type.NumIn() > 1 { 181 | return fmt.Errorf("Resource %s has an invalid Init method %s. "+ 182 | " It should have no inputs\n", 183 | m.Type.In(0), m.Type) 184 | } 185 | 186 | if m.Type.NumOut() > 2 { 187 | return fmt.Errorf("Resource %s has an invalid Init method %s. "+ 188 | " It can outputs just itself and one error\n", 189 | m.Type.In(0), m.Type) 190 | } 191 | 192 | // Method Struct owner Type 193 | owner := elemOfType(m.Type.In(0)) 194 | for i := 0; i < m.Type.NumOut(); i++ { 195 | t := elemOfType(m.Type.Out(i)) 196 | // Ignore the Resource itself and the error types 197 | if elemOfType(t) == owner || t == errorType { 198 | continue 199 | } 200 | 201 | return fmt.Errorf("Resource %s has an invalid New method %s. "+ 202 | "It can't outputs %s\n", m.Type.In(0), m.Type, t) 203 | } 204 | 205 | return nil 206 | } 207 | 208 | // 'New' methods should have no Output, 209 | // it should alter the first argument as a pointer 210 | // Or, at least, return itself 211 | func isValidConstructor(method reflect.Method) error { 212 | 213 | // Test if New method return itself and/or error types 214 | // just one of each type is accepted 215 | itself := false 216 | err := false 217 | 218 | //errorType := reflect.TypeOf(errors.New("432")) 219 | 220 | // Method Struct owner Type 221 | owner := mainElemOfType(method.Type.In(0)) 222 | for i := 0; i < method.Type.NumOut(); i++ { 223 | t := mainElemOfType(method.Type.Out(i)) 224 | if t == owner && !itself { 225 | itself = true 226 | continue 227 | } 228 | if t == errorType && !err { 229 | err = true 230 | continue 231 | } 232 | 233 | return fmt.Errorf("Resource %s has an invalid New method %s. "+ 234 | "It can't outputs %s \n", method.Type.In(0), method.Type, t) 235 | } 236 | 237 | return nil 238 | } 239 | 240 | // Return true if given StructField is an exported Field 241 | // return false if is an unexported Field 242 | func isExportedField(field reflect.StructField) bool { 243 | firstChar := string([]rune(field.Name)[0]) 244 | return firstChar == strings.ToUpper(firstChar) 245 | } 246 | 247 | // Return a new empty Value for one of these Types 248 | // Struct, *Struct, Slice, *Slice 249 | func newEmptyValue(t reflect.Type) (reflect.Value, error) { 250 | t = elemOfType(t) 251 | if t.Kind() == reflect.Struct || t.Kind() == reflect.Slice { 252 | return reflect.New(t), nil // A new Ptr to Struct of this type 253 | } 254 | return reflect.Value{}, fmt.Errorf("Can't create an empty Value for type %s", t) 255 | } 256 | 257 | // Return if this method should be mapped or not 258 | // Methods starting with GET, POST, PUT, DELETE or HEAD should be mapped 259 | func isMappedMethod(m reflect.Method) bool { 260 | for _, httpMethod := range httpMethods { 261 | if strings.HasPrefix(m.Name, httpMethod) { 262 | return true 263 | } 264 | } 265 | return false 266 | } 267 | 268 | // Splits the method name and returns 269 | // the name of the HTTP method 270 | // and the address of the method 271 | // it is because Actions are addressable methods 272 | func splitsMethodName(m *method) (string, string) { 273 | var httpMethod, addr string 274 | for _, httpMethod = range httpMethods { 275 | if strings.HasPrefix(m.method.Name, httpMethod) { 276 | slice := strings.Split(m.method.Name, httpMethod) 277 | if len(slice) > 1 { 278 | addr = slice[1] 279 | } 280 | return httpMethod, strings.ToLower(addr) 281 | } 282 | } 283 | // If cant split it, return an error 284 | log.Fatalf("Can't split the method %s name", m.method.Type) 285 | return httpMethod, addr 286 | } 287 | 288 | // Write an error and Status Code in the ResponseWriter encoded as JSON, 289 | func writeError(w http.ResponseWriter, err error, status int) { 290 | // Encode the output in JSON 291 | jsonResponse, err := json.MarshalIndent(map[string]string{"error": err.Error()}, "", "\t") 292 | if err != nil { 293 | http.Error(w, "{error: \"Error encoding the error message to Json: "+err.Error()+"\"}", http.StatusInternalServerError) 294 | return 295 | } 296 | 297 | w.Header().Set("Content-Type", "application/json") 298 | w.Write(jsonResponse) 299 | } 300 | -------------------------------------------------------------------------------- /circular_dependency.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | type circularDependency struct { 10 | checked []*dependency 11 | dependents []reflect.Type 12 | } 13 | 14 | // Check the existence of Circular Dependency on the route 15 | func checkCircularDependency(ro *route) error { 16 | cd := &circularDependency{ 17 | checked: []*dependency{}, 18 | dependents: []reflect.Type{}, 19 | } 20 | return cd.checkRoute(ro) 21 | } 22 | 23 | func (cd *circularDependency) checkRoute(ro *route) error { 24 | for _, m := range ro.methods { 25 | //log.Println("Check CD for Method", m.Method) 26 | for _, d := range m.dependencies { 27 | err := cd.checkDependency(d, m) 28 | if err != nil { 29 | return err 30 | } 31 | } 32 | } 33 | 34 | for _, child := range ro.children { 35 | err := cd.checkRoute(child) 36 | if err != nil { 37 | return err 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | 44 | // This method add de Dependency to the Dependents list testing if it conflicts 45 | // and moves recursively on each Dependency of this Dependency... 46 | // at the end of the method the Dependency is removed from the Dependents list 47 | func (cd *circularDependency) checkDependency(d *dependency, m *method) error { 48 | // If this Dependency is already checked, 49 | // we don't need to check it again 50 | if cd.isChecked(d) { 51 | return nil 52 | } 53 | 54 | //log.Println("CD for Dependency", d.Value.Type()) 55 | 56 | // Add this dependency type to the dependency list 57 | // and check if this type desn't already exist 58 | err := cd.addAndCheck(d.value.Type()) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | // Check if this Dependency has New Method 64 | if d.constructor != nil { 65 | for i := 0; i < d.constructor.Type.NumIn(); i++ { 66 | 67 | t := d.constructor.Type.In(i) 68 | //log.Println("CD for Dependency New Dependency", i, t, dependency.isType(t)) 69 | 70 | // The first element will always be the dependency itself 71 | if d.isType(t) { 72 | continue 73 | } 74 | 75 | // All context types doesn't need to be checked 76 | // it will always be present in the context 77 | if isContextType(t) { 78 | continue 79 | } 80 | 81 | d, exist := m.dependencies.vaueOf(t) 82 | if !exist { // It should never occurs! 83 | return fmt.Errorf("Danger! No dependency %s found! Something very wrong happened!", t) 84 | } 85 | 86 | // Go ahead recursively on each Dependency 87 | err := cd.checkDependency(d, m) 88 | if err != nil { 89 | return err 90 | } 91 | } 92 | } 93 | 94 | // Remove itself from the list 95 | cd.pop() 96 | 97 | // Add this dependency to the checked list 98 | cd.checked = append(cd.checked, d) 99 | 100 | return nil 101 | } 102 | 103 | // Check if this dependency Type doesn't exist in the Dependents list 104 | // If it already exist, it indicates a circular dependency! 105 | // Throws an error showing the list of dependencies that caused it 106 | func (cd *circularDependency) addAndCheck(t reflect.Type) error { 107 | 108 | // Check for circular dependency 109 | ok := true 110 | errMsg := "" 111 | 112 | for _, t2 := range cd.dependents { 113 | if !ok { 114 | errMsg += fmt.Sprintf("%s that depends on ", t2) 115 | } 116 | if t == t2 { 117 | ok = false 118 | errMsg += fmt.Sprintf("%s depends on ", t) 119 | } 120 | } 121 | 122 | if !ok { 123 | errMsg += fmt.Sprintf("%s\n", t) 124 | return errors.New(errMsg) 125 | } 126 | 127 | //log.Println("Adding:", t) 128 | 129 | // Everything ok, add this new type dependency 130 | cd.dependents = append(cd.dependents, t) 131 | return nil 132 | } 133 | 134 | // Remove the last element from the Dependents list 135 | func (cd *circularDependency) pop() { 136 | //log.Println("Removing:", cd.Dependents[len(cd.Dependents)-1]) 137 | cd.dependents = cd.dependents[:len(cd.dependents)-1] 138 | } 139 | 140 | func (cd *circularDependency) isChecked(dependency *dependency) bool { 141 | for _, d := range cd.checked { 142 | if dependency == d { 143 | return true 144 | } 145 | } 146 | return false 147 | } 148 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "reflect" 7 | ) 8 | 9 | type context struct { 10 | method *method 11 | values []reflect.Value 12 | idMap idMap 13 | errors []reflect.Value // To append the errors outputed 14 | } 15 | 16 | // Creates a new context 17 | // It creates the initial state used to answer the request 18 | // Since states are not allowed to be stored on te server, 19 | // this initial state is all the service has to answer a request 20 | func newContext(m *method, w http.ResponseWriter, req *http.Request, ids idMap) *context { 21 | return &context{ 22 | method: m, 23 | values: []reflect.Value{ 24 | reflect.ValueOf(w), 25 | reflect.ValueOf(req), 26 | }, 27 | idMap: ids, 28 | errors: []reflect.Value{}, 29 | } 30 | } 31 | 32 | func (c *context) run() []reflect.Value { 33 | 34 | //log.Println("Running Context method Method:", c.method.Method.Method.Type) 35 | 36 | // Then run the main method 37 | inputs := c.getInputs(&c.method.method) 38 | 39 | return c.method.method.Func.Call(inputs) 40 | } 41 | 42 | // Return the inputs Values from a Method 43 | // For the especial case of the ID input, we should know the requester Type 44 | func (c *context) getInputs(m *reflect.Method) []reflect.Value { 45 | 46 | requester := m.Type.In(0) // Get the requester Type 47 | 48 | values := make([]reflect.Value, m.Type.NumIn()) 49 | 50 | //log.Println("Getting inputs:", inputs) 51 | for i := 0; i < m.Type.NumIn(); i++ { 52 | t := m.Type.In(i) 53 | 54 | //log.Println("Getting input", t) 55 | values[i] = c.valueOf(t, requester) 56 | //log.Println("Getted", values[i], "for", t) 57 | 58 | } 59 | 60 | //log.Println("Returning values:", values, "for", inputs) 61 | 62 | return values 63 | } 64 | 65 | // Get the reflect.Value for the required type 66 | func (c *context) valueOf(t reflect.Type, requester reflect.Type) reflect.Value { 67 | 68 | //log.Println("Searching for", t) 69 | 70 | // If it is requesting the first error in the list 71 | if t == errorType { 72 | return c.errorValue() 73 | } 74 | 75 | // If it is requesting the whole error list 76 | if t == errorSliceType { 77 | return c.errorSliceValue() 78 | } 79 | 80 | // If it is requesting the *ID type 81 | if t == idInterfaceType { 82 | return c.idValue(requester) 83 | } 84 | 85 | // So it can only be a Resource Value 86 | // Or Request or Writer 87 | v := c.resourceValue(t) 88 | 89 | // If it is requiring the Elem itself and it returned a Ptr to Elem 90 | // Or if it is requiring the Slice itself and it returned a Ptr to Slice 91 | if t.Kind() == reflect.Struct && v.Kind() == reflect.Ptr || 92 | t.Kind() == reflect.Slice && v.Kind() == reflect.Ptr { 93 | // It is requiring the Elem of a nil Ptr? 94 | // Ok, give it an empty Elem of that Type 95 | if v.IsNil() { 96 | return reflect.New(t).Elem() 97 | } 98 | 99 | return v.Elem() 100 | //log.Println("Transformed", v, "for", t) 101 | } 102 | 103 | return v 104 | } 105 | 106 | // Get the Resource Value of the required Resource Type 107 | // It could be http.ResponseWriter or *http.Request too 108 | func (c *context) resourceValue(t reflect.Type) reflect.Value { 109 | for _, v := range c.values { 110 | switch t.Kind() { 111 | case reflect.Interface: 112 | if v.Type().Implements(t) { 113 | return v 114 | } 115 | case reflect.Struct, reflect.Slice: // non-pointer 116 | if v.Type().Elem() == t { 117 | return v 118 | } 119 | case reflect.Ptr: 120 | if v.Type() == t { 121 | return v 122 | } 123 | } 124 | 125 | } 126 | // It is not present yet, so we need to construct it 127 | return c.newDependencie(t) 128 | } 129 | 130 | // Return the first error of the list, or an nil error 131 | func (c *context) errorValue() reflect.Value { 132 | if len(c.errors) > 0 { 133 | return c.errors[0] 134 | } 135 | return errorNilValue 136 | } 137 | 138 | // Return a whole error list 139 | func (c *context) errorSliceValue() reflect.Value { 140 | errs := make([]error, len(c.errors)) 141 | for i, err := range c.errors { 142 | errs[i] = err.Interface().(error) 143 | } 144 | return reflect.ValueOf(errs) 145 | } 146 | 147 | // Get the reflect.Value for the ID list caught in the URI 148 | // It returns an nil *ID if ID were not passed in the URI 149 | func (c *context) idValue(t reflect.Type) reflect.Value { 150 | 151 | id, exist := c.idMap[t] 152 | if exist { 153 | return id // its an reflect.Value from the type of ID 154 | } 155 | 156 | // Doesn't exist, returning an empty default ID 157 | return nilIDValue 158 | } 159 | 160 | // Construct all the dependencies level by level 161 | // Garants that every dependencie exists before be requisited 162 | func (c *context) newDependencie(t reflect.Type) reflect.Value { 163 | 164 | dependencie, exist := c.method.dependencies[t] 165 | if !exist { // It should never occours 166 | log.Printf("%v", c.method.dependencies) 167 | log.Panicf("Dependencie %s not mapped!!!", t) 168 | } 169 | 170 | //log.Println("Constructing dependency", dependencie.Value.Type()) 171 | 172 | // This Value will be mapped in the index index 173 | index := len(c.values) 174 | 175 | // Instanciate a new dependency and add it to the list 176 | c.values = append(c.values, dependencie.new()) 177 | 178 | if dependencie.constructor != nil { 179 | 180 | inputs := c.getInputs(dependencie.constructor) //dependencie.Input, dependencie.Value.Type()) 181 | 182 | out := make([]reflect.Value, dependencie.constructor.Type.NumOut()) 183 | 184 | //log.Printf("Calling %s with %q \n", dependencie.Method.Method.Type, inputs) 185 | 186 | out = dependencie.constructor.Func.Call(inputs) 187 | 188 | // If the New method return something, 189 | // it will be the resource itself with 190 | // its values updated 191 | if dependencie.constructor.Type.NumOut() > 0 { 192 | 193 | for i := 0; i < dependencie.constructor.Type.NumOut(); i++ { 194 | 195 | out[i].Type() 196 | 197 | //log.Println("### Threating output:", dependencie.Method.Outputs[i]) 198 | 199 | if out[i].Type() == errorType { 200 | //log.Println("### Fucking shit error!!!!", out[i].IsNil(), out[i].IsValid(), out[i].CanSet(), out[i].CanInterface()) 201 | if !out[i].IsNil() { 202 | c.errors = append(c.errors, out[i]) 203 | //log.Println("### Appending the error!!!!") 204 | } 205 | continue 206 | } 207 | // Check if this output is the dependency itself 208 | if dependencie.isType(out[i].Type()) { 209 | //log.Println("### Its just me...", out[i].Type(), out[i].IsValid(), out[i].CanSet(), out[i].CanInterface(), out[i].Type()) 210 | 211 | // If this method outputs an Elem insted an Ptr to the Elem 212 | if out[i].Type().Kind() != reflect.Ptr { 213 | value := reflect.New(out[i].Type()) 214 | value.Elem().Set(out[i]) 215 | c.values[index] = value 216 | } else { 217 | c.values[index] = out[i] 218 | } 219 | } 220 | 221 | } 222 | } 223 | } 224 | 225 | //log.Println("Constructed", c.Values[index], "for", t, "value", c.Values[index].Interface()) 226 | 227 | return c.values[index] 228 | } 229 | -------------------------------------------------------------------------------- /dependency.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | type dependency struct { 8 | // The initial dependency state 9 | // Type Ptr to Struct, or Ptr to Slice of Struct 10 | value reflect.Value 11 | 12 | // Constructor method New() 13 | constructor *reflect.Method 14 | } 15 | 16 | type dependencies map[reflect.Type]*dependency 17 | 18 | // Create a new Dependencies map 19 | func newDependencies(m reflect.Method, r *resource) (dependencies, error) { 20 | //log.Println("Creating dependencies for method", m.Type) 21 | ds := dependencies{} //make(map[reflect.Type]*dependency) 22 | err := ds.scanMethodInputs(m, r) 23 | if err != nil { 24 | return nil, err 25 | } 26 | //log.Printf("Dependencies created: %v\n", ds) 27 | return ds, nil 28 | } 29 | 30 | // Scan the dependencies of a Method 31 | func (ds dependencies) scanMethodInputs(m reflect.Method, r *resource) error { 32 | //log.Println("Trying to scan method", m.Type) 33 | // So we scan all dependencies to create a tree 34 | 35 | for i := 0; i < m.Type.NumIn(); i++ { 36 | input := m.Type.In(i) 37 | 38 | //log.Println("Scanning for dependency", input, "on method", m.Type) 39 | 40 | // Check if this type already exists in the dependencies 41 | // If it was indexed by another type, this method 42 | // ensures that it will be indexed for this type too 43 | if ds.exists(input) { 44 | //log.Printf("Found dependency to use as %s\n", input) 45 | continue 46 | } 47 | 48 | // If the required resource is http.ResponseWriter or *http.Request or ID 49 | // it will be added to context on each request and don't need to be mapped 50 | if isContextType(input) { 51 | continue // Not need to be mapped as a dependency 52 | } 53 | 54 | // Scan this dependency and its dependencies recursively 55 | // and add it to Dependencies list 56 | d, err := newDependency(input, r) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | //log.Printf("Dependency created [%s]%s", input, d.value) 62 | 63 | // We should add this dependency before scan its constructor Dependencies 64 | // cause the constructor's first argument will requires the Resource itself 65 | 66 | ds.add(input, d) 67 | 68 | // Check if Dependency constructor exists 69 | constructor, exists := d.value.Type().MethodByName("New") 70 | if !exists { 71 | //log.Printf("Type %s doesn't have New method\n", d.Value.Type()) 72 | continue 73 | } 74 | 75 | //log.Println("Scanning New for ", constructor.Type) 76 | 77 | // 'New' method should have no return, 78 | // or return just the resource itself and/or error 79 | err = isValidConstructor(constructor) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | //log.Println("Scan Dependencies for 'New' method", d.Method.Method.Type) 85 | 86 | err = ds.scanMethodInputs(constructor, r) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | // And attach it into the Dependency Method 92 | d.constructor = &constructor 93 | } 94 | return nil 95 | } 96 | 97 | // Scan the dependencies recursively and add it to the Method dependencies list 98 | // This method ensures that all dependencies will be present 99 | // when the dependents methods want them 100 | func newDependency(t reflect.Type, r *resource) (*dependency, error) { 101 | 102 | //log.Println("Trying to create a new dependency", t) 103 | 104 | err := isValidDependencyType(t) 105 | if err != nil { 106 | return nil, err 107 | } 108 | 109 | // If this dependency is an Interface, 110 | // we should search which resource satisfies this Interface in the Resource Tree 111 | // If this is a Struct, just find for the initial value, 112 | // if the Struct doesn't exist, create one and return it 113 | v, err := r.valueOf(t) 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | d := &dependency{ 119 | value: v, 120 | constructor: nil, 121 | } 122 | 123 | //log.Printf("Created dependency %s to use as %s\n", v, t) 124 | 125 | return d, nil 126 | } 127 | 128 | // Add a new dependency to the Dependencies list 129 | // Receives an type, to use as index, and the dependency itself 130 | // This type could be both Interface or Struct type 131 | func (ds dependencies) add(t reflect.Type, d *dependency) { 132 | //log.Println("Adding dependency", d.Value.Type()) 133 | ds[t] = d 134 | } 135 | 136 | // This method checks if exist an value for the received type 137 | // If it already exist, but its indexed by another type 138 | // it will index for the new type too 139 | func (ds dependencies) vaueOf(t reflect.Type) (*dependency, bool) { 140 | 141 | //log.Println("Dependency: Searching for dependency", t) 142 | 143 | d, exist := ds[t] 144 | if exist { 145 | //log.Println("Dependency: Found:", d.value.Type(), d.value.Interface()) 146 | return d, true 147 | } 148 | 149 | // Check if one of the dependencies is of this type 150 | for _, d := range ds { 151 | if d.isType(t) { 152 | //log.Println("Dependency: Found out of index", d.value.Interface()) 153 | 154 | // Index this dependency with this new type it implements 155 | ds[t] = d 156 | return d, true 157 | } 158 | } 159 | 160 | //log.Println("Dependency: Not Exist") 161 | 162 | // Not found 163 | return nil, false 164 | } 165 | 166 | // Return true if required type already exists in the Dependencies map 167 | // Check if this type already exists in the dependencies 168 | // If it was indexed by another type, this method 169 | // ensures that it will be indexed for this type too 170 | func (ds dependencies) exists(t reflect.Type) bool { 171 | _, exist := ds.vaueOf(t) 172 | return exist 173 | } 174 | 175 | // Return true if this Resrouce is from by this Type 176 | func (d *dependency) isType(t reflect.Type) bool { 177 | 178 | if t.Kind() == reflect.Interface { 179 | return d.value.Type().Implements(t) 180 | } 181 | 182 | // The Value stored in Dependency 183 | // is from Type Ptr to Struct, or Ptr to Slice of Struct 184 | return d.value.Type() == ptrOfType(t) 185 | } 186 | 187 | // Cosntruct a new dependency in a new memory space with the initial dependency value 188 | func (d *dependency) new() reflect.Value { 189 | v := reflect.New(d.value.Type().Elem()) 190 | v.Elem().Set(d.value.Elem()) 191 | return v 192 | } 193 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | A high productivity web framework for quickly writing resource based services fully implementing the REST architectural style. 3 | 4 | This framework allows you to really focus on the Resources and how it behaves, and let the tool for routing the requests and inject the required dependencies. 5 | 6 | For a full guide visit https://github.com/resoursea/api 7 | 8 | Example of usage: 9 | 10 | package main 11 | 12 | import ( 13 | "log" 14 | "net/http" 15 | 16 | "github.com/resoursea/api" 17 | ) 18 | 19 | type Gopher struct { 20 | Message string 21 | } 22 | 23 | func (r *Gopher) GET() *Gopher { 24 | return r 25 | } 26 | 27 | func main() { 28 | router, err := api.NewRouter(Gopher{ 29 | Message: "Hello Gophers!", 30 | }) 31 | if err != nil { 32 | log.Fatalln(err) 33 | } 34 | 35 | // Starting de HTTP server 36 | log.Println("Starting the service on http://localhost:8080/") 37 | if err := http.ListenAndServe(":8080", router); err != nil { 38 | log.Fatalln(err) 39 | } 40 | } 41 | */ 42 | package api 43 | -------------------------------------------------------------------------------- /id.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "reflect" 5 | "strconv" 6 | ) 7 | 8 | // IDs to be passed to resources when resceived in the URL 9 | // Ex: resource/123/child/321 10 | // Resource will receive the ID 123 in its arguments, 11 | // ans its child will receive the ID 321 when asked for it 12 | 13 | type ID interface { 14 | String() string 15 | Int() (int, error) 16 | } 17 | 18 | type id struct { 19 | id string 20 | } 21 | 22 | func (i idMap) extend(ids idMap) { 23 | for t, v := range ids { 24 | i[t] = v 25 | } 26 | } 27 | 28 | func (i id) String() string { 29 | return i.id 30 | } 31 | 32 | func (i id) Int() (int, error) { 33 | return strconv.Atoi(i.String()) 34 | } 35 | 36 | type idMap map[reflect.Type]reflect.Value 37 | 38 | var nilIDValue = reflect.ValueOf((*id)(nil)) 39 | 40 | // TODO, refactor this code 41 | // Dunno another way to do it 42 | var idInterfaceType = reflect.TypeOf(([]ID)(nil)).Elem() 43 | -------------------------------------------------------------------------------- /init_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func TestVersionInit(t *testing.T) { 12 | rt, err := NewRouter(api) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | w := httptest.NewRecorder() 18 | req, err := http.NewRequest("GET", "/api/version", nil) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | rt.ServeHTTP(w, req) 24 | 25 | // Try to get the gopher from the response 26 | var resp VersionResp 27 | err = json.Unmarshal(w.Body.Bytes(), &resp) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | // Testing if the initial value of version was set 33 | if resp.Version.Version != api.Version.Version { 34 | t.Fatal("The initial value of Version wasn't set") 35 | } 36 | 37 | // Testing if Version was Initialized 38 | if resp.Version.Message != fmt.Sprintf("%s %d", api.Version.Message, api.Version.Version) { 39 | t.Fatal("Version wasn't initialized correctly") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /interface_test.go: -------------------------------------------------------------------------------- 1 | // This package tests the Interface injection 2 | package api 3 | 4 | import ( 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | // Testing the Interface Injection 12 | // The API resource has an Action called GETDogBark 13 | // It depends on an Interface Dogger 14 | // The interface is implemented by the Maltese struct attached to the API 15 | func TestInterfaceInjection(t *testing.T) { 16 | rt, err := NewRouter(api) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | w := httptest.NewRecorder() 22 | req, err := http.NewRequest("GET", "/api/dogbark", nil) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | 27 | rt.ServeHTTP(w, req) 28 | 29 | // Try to get the gopher from the response 30 | var resp StringResp 31 | err = json.Unmarshal(w.Body.Bytes(), &resp) 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | 36 | maltese := Maltese{} 37 | 38 | // Testing if the interface implementation injection 39 | if resp.String != maltese.Bark() { 40 | t.Fatal("Interface not injected correctly") 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /method.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type method struct { 9 | method reflect.Method 10 | // Many Types could point to the same dependencie 11 | // It could occour couse could have any number of Interfaces 12 | // that could be satisfied by a single dependency 13 | dependencies dependencies 14 | outName []string 15 | } 16 | 17 | func newMethod(m reflect.Method, r *resource) (*method, error) { 18 | 19 | //log.Println("Creating Method", m.Name, m.Type) 20 | 21 | ds, err := newDependencies(m, r) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | h := &method{ 27 | method: m, 28 | dependencies: ds, 29 | outName: make([]string, m.Type.NumOut()), 30 | } 31 | 32 | // Caching the Output Resources name 33 | for i := 0; i < m.Type.NumOut(); i++ { 34 | h.outName[i] = elemOfType(m.Type.Out(i)).Name() 35 | } 36 | 37 | return h, nil 38 | } 39 | 40 | func (h *method) String() string { 41 | return fmt.Sprintf("[%s] %s", h.method.Name, h.method.Type) 42 | } 43 | -------------------------------------------------------------------------------- /new_test.go: -------------------------------------------------------------------------------- 1 | // This package tests the Resource Creation method New() 2 | package api 3 | 4 | import ( 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | // Testing the Resource Creation method New() 12 | func TestNew(t *testing.T) { 13 | rt, err := NewRouter(api) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | 18 | w := httptest.NewRecorder() 19 | req, err := http.NewRequest("GET", "/api/date", nil) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | rt.ServeHTTP(w, req) 25 | 26 | var resp DateResp 27 | err = json.Unmarshal(w.Body.Bytes(), &resp) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | if resp.Date.Time.IsZero() { 33 | t.Fatal("Date not Created correctly") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /resource.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | // We are storing the Pointer to Struct value and Pointer to Slice as Value 10 | type resource struct { 11 | name string 12 | value reflect.Value 13 | parent *resource 14 | children []*resource 15 | extends []*resource // Spot for Anonymous fields 16 | anonymous bool // Is Anonymous field? 17 | tag reflect.StructTag 18 | isSlice bool 19 | init *reflect.Method 20 | } 21 | 22 | // Create a new Resource tree based on given Struct, its Struct Field and its Resource parent 23 | func newResource(value reflect.Value, field reflect.StructField, parent *resource) (*resource, error) { 24 | // Check if the value is valid, valid values are: 25 | // struct, *struct, []struct, *[]struct, *[]*struct 26 | if !isValidValue(value) { 27 | return nil, fmt.Errorf("Can't create a Resource with type %s", value.Type()) 28 | } 29 | 30 | // Garants we are working with a Ptr to Struct or Slice 31 | value = ptrOfValue(value) 32 | 33 | //log.Println("Scanning Struct:", value.Type(), "name:", strings.ToLower(field.Name), value.Interface()) 34 | 35 | r := &resource{ 36 | name: strings.ToLower(field.Name), 37 | value: value, 38 | parent: parent, 39 | children: []*resource{}, 40 | extends: []*resource{}, 41 | anonymous: field.Anonymous, 42 | tag: field.Tag, 43 | isSlice: isSliceType(value.Type()), 44 | init: nil, // Appended above 45 | } 46 | 47 | // Check for circular dependency !!! 48 | exist, p := r.existParentOfType(r) 49 | if exist { 50 | return nil, fmt.Errorf("The resource %s as '%s' have an circular dependency in %s as '%s'", 51 | r.value.Type(), r.name, p.value.Type(), p.name) 52 | } 53 | 54 | // Check if this resource has a Init method 55 | // If it has, validate it and initialize the resource 56 | init, exists := r.value.Type().MethodByName("Init") 57 | if exists { 58 | err := isValidInit(init) 59 | if err != nil { 60 | return nil, err 61 | } 62 | r.init = &init 63 | 64 | // Running Init method 65 | 66 | out := make([]reflect.Value, init.Type.NumOut()) 67 | // Call the init method passing the initial value of the resource 68 | out = init.Func.Call([]reflect.Value{r.value}) 69 | for _, v := range out { 70 | // Test is Init returned an error 71 | if v.Type() == errorType && !v.IsNil() { 72 | return nil, v.Interface().(error) 73 | } 74 | if ptrOfType(v.Type()) == r.value.Type() { 75 | r.value = ptrOfValue(v) 76 | } 77 | } 78 | } 79 | 80 | // If it is slice, scan the Elem of this slice 81 | if r.isSlice { 82 | 83 | elemValue := elemOfSliceValue(value) 84 | 85 | elem, err := newResource(elemValue, field, r) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | //r.Elem = elem 91 | r.addChild(elem) 92 | 93 | return r, nil 94 | } 95 | 96 | for i := 0; i < value.Elem().Type().NumField(); i++ { 97 | 98 | field := value.Elem().Type().Field(i) 99 | fieldValue := value.Elem().Field(i) 100 | 101 | //log.Println("Field:", field.Name, field.Type, "of", value.Elem().Type(), "is valid", isValidValue(fieldValue)) 102 | 103 | // Check if this field is exported: fieldValue.CanInterface() 104 | // and if this field is valid fo create Resources: Structs or Slices of Structs 105 | if isValidValue(fieldValue) { 106 | child, err := newResource(fieldValue, field, r) 107 | if err != nil { 108 | return nil, err 109 | } 110 | err = r.addChild(child) 111 | if err != nil { 112 | return nil, err 113 | } 114 | } 115 | } 116 | 117 | return r, nil 118 | } 119 | 120 | // The child should be added to the first non anonymous parent 121 | // An anonymous field indicates that the containing non anonymous parent Struct 122 | // should have all the fields and methos this anonymous field has 123 | func (parent *resource) addChild(child *resource) error { 124 | //log.Printf("%s Anonymous: %v adding Child %s", 125 | // parent.Value.Type(), parent.Anonymous, child.Value.Type()) 126 | 127 | // Just add the child to the first non anonymous parent 128 | if parent.anonymous { 129 | parent.parent.addChild(child) 130 | return nil 131 | } 132 | 133 | // If this child is Anonymous, its father will extends its behavior 134 | if child.anonymous { 135 | parent.extends = append(parent.extends, child) 136 | return nil 137 | } 138 | 139 | // Two children can't have the same name, check it before insert them 140 | for _, sibling := range parent.children { 141 | if child.name == sibling.name { 142 | return fmt.Errorf("Two resources have the same name '%s' \nR1: %s, R2: %s, Parent: %s", 143 | child.name, sibling.value.Type(), child.value.Type(), parent.value.Type()) 144 | } 145 | } 146 | 147 | parent.children = append(parent.children, child) 148 | return nil 149 | } 150 | 151 | // Return Value of the implementation of some Interface, 152 | // this Resource that satisfies this interface 153 | // should be present in this Resource children or in its parents children recursively 154 | // If requested type is an Struct return the initial Value of this Type, if exists, 155 | // if Struct type not contained on the resource tree, create a new empty Value for this Type 156 | func (r *resource) valueOf(t reflect.Type) (reflect.Value, error) { 157 | 158 | for _, child := range r.children { 159 | if child.isType(t) { 160 | return child.value, nil 161 | } 162 | } 163 | 164 | // Go recursively until reaching the root 165 | if r.parent != nil { 166 | return r.parent.valueOf(t) 167 | } 168 | 169 | // Testing the root of the Resource Tree 170 | ok := r.isType(t) 171 | if ok { 172 | return r.value, nil 173 | } 174 | 175 | // At this point we tested all Resources in the tree 176 | // If we are searching for an Interface, and noone implements it 177 | // so we shall throws an error informing user to satisfy this Interface in the Resource Tree 178 | if t.Kind() == reflect.Interface { 179 | return reflect.Value{}, fmt.Errorf( 180 | "Not found any Resource that implements the Interface "+ 181 | "type %s in the Resource tree %s", t, r) 182 | } 183 | 184 | // If it isn't present in the Resource tree 185 | // and this type we are searching isn't an interface 186 | // So we will use an empty new value for it! 187 | return newEmptyValue(t) 188 | } 189 | 190 | // Return true if this Resrouce is from by this Type 191 | func (r *resource) isType(t reflect.Type) bool { 192 | 193 | if t.Kind() == reflect.Interface { 194 | if r.value.Type().Implements(t) { 195 | return true 196 | } 197 | } 198 | 199 | // If its not an Ptr to Struct or to Slice 200 | // so thest the type of this Ptr 201 | if r.value.Type() == ptrOfType(t) { 202 | return true 203 | } 204 | 205 | return false 206 | } 207 | 208 | // Return true any of its father have the same type of this resrouce 209 | // This method prevents for Circular Dependency 210 | func (r *resource) existParentOfType(re *resource) (bool, *resource) { 211 | if r.parent != nil { 212 | if r.parent.value.Type() == re.value.Type() { 213 | return true, r.parent 214 | } 215 | return r.parent.existParentOfType(re) 216 | } 217 | return false, nil 218 | } 219 | 220 | func (r *resource) String() string { 221 | 222 | name := "[" + r.name + "] " 223 | 224 | response := fmt.Sprintf("%-20s ", name+r.value.Type().String()) 225 | 226 | return response 227 | } 228 | -------------------------------------------------------------------------------- /route.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "strings" 8 | ) 9 | 10 | // This struct stores a tree of routed methods 11 | // It implements the Router interface 12 | // and implements the net/http.Handler interface 13 | type route struct { 14 | // The Route URI 15 | // The name of the Resource in lowercase 16 | name string 17 | 18 | // The Resource value 19 | // that created this route 20 | value reflect.Value 21 | 22 | // Mapped Methods attached in this Route 23 | // Indexed by the route identifier in lowercase 24 | // ex: get, postlike 25 | methods map[string]*method 26 | 27 | // Children Route that builds a tree 28 | // Indexed by the child name 29 | children map[string]*route 30 | 31 | // True if this is a Route for a set of Resources 32 | isSlice bool 33 | } 34 | 35 | // It maps the Resource's mapped methods and creates a new Route tree 36 | func newRoute(r *resource) (*route, error) { 37 | 38 | //log.Printf("Building Routes for %s\n", r) 39 | 40 | ro := &route{ 41 | name: r.name, 42 | value: r.value, 43 | methods: make(map[string]*method), 44 | children: make(map[string]*route), 45 | isSlice: r.isSlice, 46 | } 47 | 48 | // Maps the Resource's mapped Methods 49 | // and also the Resources it extends recursively 50 | err := ro.scanRoutesFrom(r) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | // Check for Circular Dependency 56 | // on the Dependencies of each mapped Method 57 | err = checkCircularDependency(ro) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | // Go down to the Resource tree 63 | // and create Routes recursivelly for each Resource child 64 | for _, child := range r.children { 65 | c, err := newRoute(child) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | // Add this new routed child 71 | // and ensures that there is no URI conflict 72 | err = ro.addChild(c) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | } 78 | 79 | return ro, nil 80 | } 81 | 82 | // Scan the methods of some Resourse 83 | // Resources are actually saved as a pointer to the Resource 84 | // We need to scan the methods of the Ptr to the Struct, 85 | // cause some methods could be attached to the pointer, 86 | // like func (r *Resource) GET() {} will not be visible to non pointer 87 | func (ro *route) scanRoutesFrom(r *resource) error { 88 | 89 | err := ro.mapsMethods(r) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | // 95 | // TODO 96 | // 97 | // This Resource Type already extends all the 98 | // extended methods, and don't need to map 99 | // this extended resource methods 100 | 101 | // All the resources it Exstends 102 | // should be mapped to this Route too 103 | /* 104 | for _, extend := range r.extends { 105 | err := ro.scanRoutesFrom(extend) 106 | if err != nil { 107 | return err 108 | } 109 | } 110 | */ 111 | 112 | return nil 113 | } 114 | 115 | // Maps the methods from one Resource type and attach it to the Route 116 | func (ro *route) mapsMethods(r *resource) error { 117 | 118 | t := r.value.Type() 119 | 120 | //log.Println("### Scanning methods from type", t, "is slice:", isSliceType(t)) 121 | 122 | for i := 0; i < t.NumMethod(); i++ { 123 | 124 | m := t.Method(i) 125 | 126 | // We will accept all methods that 127 | // has GET, POST, PUT, DELETE, HEAD 128 | // in the prefix of the method name 129 | if isMappedMethod(m) { 130 | 131 | m, err := newMethod(m, r) 132 | if err != nil { 133 | return err 134 | } 135 | 136 | //log.Printf("Adding Method %s for route %s\n", m, ro) 137 | 138 | // Check if this new Method will conflict with some address of Method that already exist 139 | // Action Handlers Names could conflict with Children Names... 140 | err = ro.addMethod(m) 141 | if err != nil { 142 | return err 143 | } 144 | } 145 | } 146 | 147 | return nil 148 | } 149 | 150 | // Return true if this route, or children/grandchildren... 151 | // have mapped methods attached 152 | func (ro *route) hasMethod() bool { 153 | if len(ro.methods) > 0 { 154 | return true 155 | } 156 | for _, child := range ro.children { 157 | if child.hasMethod() { 158 | return true 159 | } 160 | } 161 | return false 162 | } 163 | 164 | // Add a new Route child 165 | func (ro *route) addChild(child *route) error { 166 | //log.Printf("addChild %s %v\n", child, child.hasMethod()) 167 | 168 | // Add this Route to the tree only if it has methods 169 | if child.hasMethod() { 170 | 171 | // Test if this Name wasn't in use yet by one child 172 | _, exist := ro.children[child.name] 173 | if exist { 174 | return errors.New("Route " + ro.name + " already has child " + child.name) 175 | } 176 | 177 | // Test if this Name isn't used by one Method 178 | // Remember for Action Handlers 179 | for _, m := range ro.methods { 180 | _, addr := splitsMethodName(m) 181 | if addr == child.name { 182 | return fmt.Errorf("The address %s used by the resource %s"+ 183 | " is already in use by an action in the route %s", addr, child, ro) 184 | } 185 | } 186 | 187 | ro.children[child.name] = child 188 | 189 | //log.Printf("Child name %s added %s\n", child.Name, child) 190 | } 191 | return nil 192 | } 193 | 194 | // Check if this new Method will conflict with some Method already created 195 | // Action Handlers Names could conflict with Children Names... 196 | func (ro *route) addMethod(m *method) error { 197 | 198 | _, address := splitsMethodName(m) 199 | 200 | // If this Method is an Action with address, 201 | // we should ensure that there is no other child with this address 202 | if len(address) > 0 { 203 | for addr, child := range ro.children { 204 | if addr == address { 205 | return fmt.Errorf("The address %s already used by the child %s in the route %s", addr, child, ro) 206 | } 207 | } 208 | } 209 | 210 | _, exist := ro.methods[strings.ToLower(m.method.Name)] 211 | if exist { 212 | return fmt.Errorf("%s already has method %s", ro, m) 213 | } 214 | 215 | // Index: GETLogin, POST, or POSTMessage... 216 | ro.methods[strings.ToLower(m.method.Name)] = m 217 | 218 | return nil 219 | } 220 | -------------------------------------------------------------------------------- /router.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "reflect" 9 | "strings" 10 | ) 11 | 12 | // This is the main interface returned to user 13 | // It routes the Resources and Methods 14 | // and can be used with the Go's net/http standard library 15 | type Router interface { 16 | ServeHTTP(http.ResponseWriter, *http.Request) 17 | 18 | Methods() []Method 19 | Children() []Router 20 | IsSlice() bool 21 | String() string 22 | } 23 | 24 | type router struct { 25 | route 26 | } 27 | 28 | // This interface is returned when 29 | // user asks for an Route Method 30 | type Method interface { 31 | String() string 32 | } 33 | 34 | // Creates a new Resource tree based on given Struct 35 | // Receives the Struct to be mapped in a new Resource Tree, 36 | // it also receive the Field name and Field tag as optional arguments 37 | func NewRouter(object interface{}, args ...string) (*route, error) { 38 | 39 | value := reflect.ValueOf(object) 40 | 41 | name := value.Type().Name() 42 | tag := "" 43 | 44 | // Defining a name as an opitional secound argument 45 | if len(args) >= 1 { 46 | name = args[0] 47 | } 48 | 49 | // Defining a tag as an opitional thrid argument 50 | if len(args) >= 2 { 51 | tag = args[1] 52 | } 53 | 54 | field := reflect.StructField{ 55 | Name: name, 56 | Tag: reflect.StructTag(tag), 57 | Anonymous: false, 58 | } 59 | 60 | r, err := newResource(value, field, nil) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | return newRoute(r) 66 | } 67 | 68 | /////////////////////////////////////////////////// 69 | // Router methods attached to the Route struct // 70 | /////////////////////////////////////////////////// 71 | 72 | // 73 | // This methods are attached to the Route struct 74 | // to garants it implments the Router interface 75 | // These are saved here to reduce de size of the route.go file 76 | // 77 | 78 | // Return the the method pointed by the URI and httpMethod 79 | // Fulfill the IDMap with IDs present in the requested URI 80 | func (ro *route) method(uri []string, httpMethod string, ids idMap) (*method, error) { 81 | 82 | //log.Println("Route Handling", uri, "in the", ro) 83 | 84 | // Check if is trying to request some Method of this Route 85 | if len(uri) == 0 { 86 | m, exist := ro.methods[httpMethod] 87 | if !exist { 88 | return nil, fmt.Errorf("Method %s not found in the %s", httpMethod, ro) 89 | } 90 | return m, nil 91 | } 92 | 93 | // Check if is trying to request some Action Method of this Route 94 | if len(uri) == 1 { 95 | m, exist := ro.methods[httpMethod+uri[0]] 96 | if exist { 97 | return m, nil 98 | } 99 | } 100 | 101 | // If we are in a Slice Route, get its ID and search in the Child Route 102 | if ro.isSlice { 103 | // Get the only child this route has, the slice Element 104 | for _, child := range ro.children { 105 | // Add its ID to the Map 106 | ids[child.value.Type()] = reflect.ValueOf(&id{id: uri[0]}) 107 | // Continue searching in the Route child 108 | return child.method(uri[1:], httpMethod, ids) 109 | } 110 | // It should never occurs, because a slice Resource always has a Elem Resource 111 | return nil, fmt.Errorf("Route %s is an slice and has no child!") 112 | } 113 | 114 | // If we are in an Elem Route, the only possibility is to have a Child with this Name 115 | child, exist := ro.children[uri[0]] 116 | if exist { 117 | return child.method(uri[1:], httpMethod, ids) 118 | } 119 | 120 | return nil, fmt.Errorf("Not exist any Child '%s' or Action '%s' in the %s", uri[0], httpMethod+strings.Title(uri[0]), ro) 121 | } 122 | 123 | // Implementing the http.Handler Interface 124 | // TODO: Error messages should be sent in JSON 125 | func (ro *route) ServeHTTP(w http.ResponseWriter, req *http.Request) { 126 | //log.Println("### Serving the resource", req.URL.RequestURI()) 127 | 128 | // Get the resource identfiers from the URL 129 | // Remember to descart the query string: ?q=sfxt&x=132... 130 | // Remember to descart the first empty element of the list, before the first / 131 | uri := strings.Split(strings.Split(req.URL.RequestURI(), "?")[0], "/")[1:] 132 | 133 | // Check if the requested URI maches with this main Route 134 | if ro.name != uri[0] { 135 | writeError(w, errors.New("Route "+ro.name+" not match with "+uri[0]), http.StatusNotFound) 136 | return 137 | } 138 | 139 | // Store the IDs of the resources in the URI 140 | ids := idMap{} 141 | httpMethod := strings.ToLower(req.Method) 142 | 143 | // Get the method this URI and HTTP method is pointing to 144 | method, err := ro.method(uri[1:], httpMethod, ids) 145 | if err != nil { 146 | writeError(w, err, http.StatusNotFound) 147 | return 148 | } 149 | 150 | //log.Printf("Route found: %s = %s ids: %q\n", req.URL.RequestURI(), method, ids) 151 | 152 | // Process the request with the found Method 153 | output := newContext(method, w, req, ids).run() 154 | 155 | // If there is no output to sent back 156 | if method.method.Type.NumOut() == 0 { 157 | w.Header().Set("Content-Type", "application/json") 158 | w.WriteHeader(http.StatusNoContent) 159 | return 160 | } 161 | 162 | // Trans form the method output into an slice of the values 163 | // * Needed to generate a JSON response 164 | response := make(map[string]interface{}, method.method.Type.NumOut()) 165 | for i, v := range output { 166 | if !v.CanInterface() || v.Kind() == reflect.Ptr && v.IsNil() { 167 | continue 168 | } 169 | if v.Type() == errorType { 170 | value, ok := v.Interface().(error) 171 | if !ok || value == nil { 172 | continue 173 | } 174 | response["error"] = value.Error() 175 | continue 176 | } 177 | if v.Type() == errorSliceType { 178 | 179 | errs := make([]string, v.Len()) 180 | for i := 0; i < v.Len(); i++ { 181 | value, err := v.Index(i).Interface().(error) 182 | if err || value == nil { 183 | continue 184 | } 185 | errs[i] = value.Error() 186 | } 187 | response["errors"] = errs 188 | continue 189 | } 190 | 191 | response[method.outName[i]] = v.Interface() 192 | } 193 | 194 | // Encode the output in JSON 195 | var jsonResponse []byte 196 | jsonResponse, err = json.MarshalIndent(response, "", "\t") 197 | if err != nil { 198 | writeError(w, errors.New("Error encoding to Json: "+err.Error()), http.StatusInternalServerError) 199 | return 200 | } 201 | 202 | w.Header().Set("Content-Type", "application/json") 203 | w.Write(jsonResponse) 204 | } 205 | 206 | // Return all accessible Methods in a specific Route 207 | func (ro *route) Methods() []Method { 208 | methods := make([]Method, len(ro.methods)) 209 | i := 0 210 | for _, m := range ro.methods { 211 | methods[i] = m 212 | i += 1 213 | } 214 | return methods 215 | } 216 | 217 | // Return all children Routes of a specific Route 218 | func (ro *route) Children() []Router { 219 | children := make([]Router, len(ro.children)) 220 | i := 0 221 | for _, c := range ro.children { 222 | children[i] = c 223 | i += 1 224 | } 225 | return children 226 | } 227 | 228 | // Return true if this Route wraps a list of Resources 229 | func (ro *route) IsSlice() bool { 230 | return ro.isSlice 231 | } 232 | 233 | // Return a text with the name and the type of a specific Route 234 | func (ro *route) String() string { 235 | return fmt.Sprintf("[%s] %s", ro.name, ro.value.Type()) 236 | } 237 | -------------------------------------------------------------------------------- /router_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "net/http/httptest" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func TestGopherGet(t *testing.T) { 12 | rt, err := NewRouter(api) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | w := httptest.NewRecorder() 18 | req, err := http.NewRequest("GET", "/api/gophers/1", nil) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | rt.ServeHTTP(w, req) 24 | 25 | // Test if returned some error 26 | errorTest(w, t) 27 | 28 | // Try to get the gopher from the response 29 | var resp GopherResp 30 | err = json.Unmarshal(w.Body.Bytes(), &resp) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | if !reflect.DeepEqual(resp.Gopher, api.Gophers[0]) { 36 | t.Fatal("The service returned the gopher wrong!") 37 | } 38 | 39 | } 40 | 41 | func TestGophersInit(t *testing.T) { 42 | rt, err := NewRouter(api) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | 47 | w := httptest.NewRecorder() 48 | req, err := http.NewRequest("GET", "/api/gophers", nil) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | rt.ServeHTTP(w, req) 54 | 55 | // Test if returned some error 56 | errorTest(w, t) 57 | 58 | // Try to get the gopher from the response 59 | var resp GophersResp 60 | err = json.Unmarshal(w.Body.Bytes(), &resp) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | if len(resp.Gophers) < 4 { 66 | t.Fatal("The service not all gophers wrong!") 67 | } 68 | 69 | } 70 | 71 | func TestGetAction(t *testing.T) { 72 | 73 | rt, err := NewRouter(api) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | 78 | w := httptest.NewRecorder() 79 | req, err := http.NewRequest("GET", "/api/gophers/2/message", nil) 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | 84 | rt.ServeHTTP(w, req) 85 | 86 | // Test if returned some error 87 | errorTest(w, t) 88 | 89 | // Try to get the string returned 90 | var resp StringResp 91 | err = json.Unmarshal(w.Body.Bytes(), &resp) 92 | if err != nil { 93 | t.Fatal(err) 94 | } 95 | 96 | if resp.String != "I still love programming" { 97 | t.Fatal("The service returned something wrong!") 98 | } 99 | 100 | } 101 | --------------------------------------------------------------------------------