Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial commit - incorporating changes from prev PR #6

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package main

import (
"context"
)

type GetTodosRequest struct {
ID int
}
type GetTodosResponse struct {
Todos []Todo
}
type Todo struct {
Title string
Description string
}

func getTodos(ctx context.Context, input GetTodosRequest) (GetTodosResponse, error) {
return GetTodosResponse{
Todos: []Todo{},
}, nil
}
func addTodos(ctx context.Context, input Todo) error {
return nil
}

func main() {
s := Create("myServer")

router := s.Router()

// 1. Generic usage for most use cases
// todo : error handling while creating these routes

router.Query("/myQuery1", addTodos)
// router.Mutation("myQuery2", addTodos)

// 2. Configuring HTTP Parameters
// router.Query("myQuery3", getTodos).setMethod("POST").setURL("/v1/some-weird-api")

// 3. Starting the server
s.Start()

// /************************************************************/
// /* Don't implement the following now. Keep it for reference */
// /************************************************************/

// // 1. Configuring middlewares on the router level
// // Note. I can call `Use` multiple times. Each middleware needs to be stored in order
// router.Use(client.Middlewares.PolicyIsAdmin()) // Note: client is autogenerated sdk we will create
// router.Use(middleware1, middleware2) // Function parameter is spreaded

// // 2. Apply middleware for specific route only
// router.With(middleware1, middleware2).Query("myQuery4", getTodos) // Note: With creates a new subrouter with `Use` called internally.

// // 3. Configuring subrouters (for simplified middleware management)
// router.Use(middleware2, middleware3)
// router.Use(middleware1)
// router.Route(func(subrouter *server.Router){
// subrouter.Query("query1")
// subrouter.With(middleware5).Mutation("query2")

// // Use can be called after Query, Mutation
// subrouter.Use(middleware4)
// })

// router.Query("query3")

// // In this example the following middlewares should be applied to each query in specified order
// // query1 -> 2, 3, 1, 4
// // query2 -> 2, 3, 1, 4, 5
// // query3 -> 2, 3, 1

// /************************************************************/
// /* Config Management */
// /************************************************************/
// // Automatically create flags and env variables based on the fields in the struct
// s.Configure(func (config *MyConfigStruct) {
// // Configure your application here
// })

// /************************************************************/
// /* Task Management */
// /************************************************************/
// // 1. Register a function for async tasks
// router.Task("myTask1", func(ctx context.Context, p MyTaskParams)(Result, error) {
// // Perform async task here
// })

// // 2. Register a function to receive a group of async tasks
// // Note: Second param has to be an array when group option is provided
// router
// .Task("myTask2", func(ctx context.Context, p []*MyTaskParams)(Result, error) {
// // Perform async task here
// })
// .On(client.Hooks.MyTask1Completed(), server.OptionHookFilter("'task2' in payload.types"))
// .Group(/* Max # of tasks to group together */ 10)

// // 3. Configure task management config on the server level
// server.TaskConfig(&server.TaskConfig{
// Concurrency: 40, // Max number of tasks that can be processed concurrently
// Queues: map[string]string{
// "Premium": 60 // 60% of all tasks processed will be from premium queue
// "Basic": 30 // 30% of all tasks processed will be from basic queue
// "Free": 10 // 10% of all tasks processed will be from basic queue
// },
// Timeout: 2*time.Second, // Default timeout for each individual task
// RetryStrategy: &server.RetryStrategy{
// MaxRetries: 10, // Maximum number of retries to perfrom - Default -1 (infinite)
// MaxTimeInQueue: 10 * 24 * time.Hour // Maximum time the task can be in queue - Default 1 day
// BaseRetryInterval: 2*time.Second, // Base retry interval
// MaxRetryInterval: 10*time.Minutes //
// }
// })

// // 4. Configuring config on the task level
// router
// .Task("myTask3", myTask)
// .Group(/* Max # of tasks to group together */ 10)
// .Retries(5)
// .Timeout(1*time.Second)
}
122 changes: 122 additions & 0 deletions route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package main

import (
"context"
"errors"
"fmt"
"reflect"

"github.com/invopop/jsonschema"
)

type Route struct {
handler *Handler
routeConfig *RouteConfig
}

// (opType: "query" or opType: "mutation")
type RouteConfig struct {
opId string
opType string
method string
url string
}
type JSONSchemaForInputAndOutput struct {
input *jsonschema.Schema
output *jsonschema.Schema
}

type Handler struct {
inpType reflect.Type
outType reflect.Type
function interface{}
jsonSchema *JSONSchemaForInputAndOutput
}

func NewRoute(routeConfig *RouteConfig) (r *Route) {
return &Route{
handler: &Handler{},
routeConfig: routeConfig,
}
}

func (r *Route) Method(method string) *Route {
// todo - check if the method is valid string
r.routeConfig.method = method
return r
}
func (r *Route) URL(url string) *Route {
// todo - check for any invalid URL
r.routeConfig.url = url
return r
}

func (r *Route) Fn(function interface{}) *Route {
inpType, outType, err := validateAndRetrieveHandlerParamType(function)
if err != nil {
panic(err)
}
inpJSONSchema := jsonschema.ReflectFromType(inpType)
outJSONSchema := jsonschema.ReflectFromType(outType)
handler := &Handler{inpType, outType, function, &JSONSchemaForInputAndOutput{inpJSONSchema, outJSONSchema}}
r.handler = handler
return r
}

func validateAndRetrieveHandlerParamType(fn interface{}) (reflect.Type, reflect.Type, error) {

fnValue := reflect.ValueOf(fn)

// check if the type if Func
if fnValue.Kind() != reflect.Func {
return nil, nil, errors.New("Provided handler is not a function")
}

// get the function's signature
fnType := fnValue.Type()

inpType, err := validateAndRetrieveInputParamType(fnType)
if err != nil {
fmt.Println(err)
return nil, nil, err
}

outType, err := validateAndRetrieveOutputParamType(fnType)
if err != nil {
return nil, nil, err
}
return inpType, outType, nil
}
func validateAndRetrieveInputParamType(fnType reflect.Type) (reflect.Type, error) {

noOfInputParam := fnType.NumIn()

if noOfInputParam == 2 {
if reflect.TypeOf((*context.Context)(nil)).Elem() == fnType.In(0) {
return fnType.In(1), nil
} else {
return nil, errors.New("First parameter of handler function should be of type Context.context")
}
} else {
return nil, errors.New(fmt.Sprintf("Invalid number of input arguments - Expected : 2, Actual : %v", noOfInputParam))
}

}

func validateAndRetrieveOutputParamType(fnType reflect.Type) (reflect.Type, error) {

noOfOutputParam := fnType.NumOut()

if noOfOutputParam == 1 {
if reflect.TypeOf((*error)(nil)).Elem() == fnType.Out(0) {
return fnType.Out(0), nil
}
return nil, errors.New("If the output of the function has 1 paramteres, it should of type error")
} else if noOfOutputParam == 2 {
if reflect.TypeOf((*error)(nil)).Elem() == fnType.Out(1) {
return fnType.Out(0), nil
}
return nil, errors.New("Second output parameter of handler function should be of type error")
}
return nil, errors.New(fmt.Sprintf("Invalid number of output arguments of handler function - Expected : 2, Actual : %v", noOfOutputParam))
}
130 changes: 130 additions & 0 deletions router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package main

import (
"net/http"
)

type Router struct {
name string
routes []*Route
}

// func (r *Router) Query(opId string, fn interface{}) *Route {
// // todo: should I return error?
// inpType, outType, err := validateAndRetrieveHandlerParamType(fn)
// if err != nil {
// panic(err)
// }

// handler := &Handler{inpType, outType, fn}
// route := &Route{
// handler: handler,
// routeConfig: &RouteConfig{
// opId: opId,
// opType: "query",
// method: http.MethodGet,
// url: "/v1/" + opId,
// jsonSchema: &JSONSchemaForInputAndOutput{
// input: jsonschema.ReflectFromType(inpType),
// output: jsonschema.ReflectFromType(outType),
// },
// }}
// r.routes = append(r.routes, route)
// return route
// }

func (r *Router) Query(items ...interface{}) *Route {
var operationId string
var function interface{}
var route *Route
// if the Query function is called with operationID
if len(items) >= 1 {
operationId = items[0].(string)

// prepare the config.
// todo: can be refactored into a prepareConfig func
var config RouteConfig
config.opId = operationId
config.opType = "query"
config.method = http.MethodGet
config.url = "/v1/" + operationId
route = NewRoute(&config)
}

// if the function is called alongwith the Handler fn
if len(items) == 2 {
function = items[1]
route = route.Fn(function)

} else if len(items) < 1 || len(items) > 2 {
panic("Illegal number of arguments provided to Query")
}
r.routes = append(r.routes, route)
return route
}

func (r *Router) Mutation(items ...interface{}) *Route {
var operationId string
var function interface{}
var route *Route
// if the Mutation function is called with operationID
if len(items) >= 1 {
operationId = items[0].(string)
var config RouteConfig
config.opId = operationId
config.opType = "mutation"
config.method = http.MethodPost
config.url = "/v1/" + operationId
route = NewRoute(&config)
}

// if the function is called alongwith the Handler fn
if len(items) == 2 {
function = items[1]
route = route.Fn(function)
} else if len(items) < 1 || len(items) > 2 {
panic("Illegal number of arguments provided to Mutation")
}
r.routes = append(r.routes, route)
return route
}

// For now, this wrapper is not needed
// func wrapper(fn interface{}) func(context.Context, interface{}) []interface{} {
// fnValue := reflect.ValueOf(fn)

// return func(ctx context.Context, i interface{}) []interface{} {
// args := []reflect.Value{
// reflect.ValueOf(ctx),
// reflect.ValueOf(i),
// }

// result := fnValue.Call(args)

// if len(result) == 1 {
// err := result[0].Interface()
// return []interface{}{
// err,
// }
// } else if len(result) == 2 {
// res := result[0].Interface()
// err := result[1].Interface()

// if err != nil {
// return []interface{}{
// nil,
// err,
// }
// }

// return []interface{}{
// res,
// nil,
// }
// }

// return []interface{}{
// fmt.Errorf("Invalid function signature: expected (context.Context, struct) (struct, error)"),
// }
// }
// }
Loading