package api import ( "fmt" "os" "runtime/debug" "github.com/aws/aws-lambda-go/lambda" "gitlab.com/uafrica/go-utils/audit" "gitlab.com/uafrica/go-utils/errors" queues_mem "gitlab.com/uafrica/go-utils/queues/mem" queues_sqs "gitlab.com/uafrica/go-utils/queues/sqs" "gitlab.com/uafrica/go-utils/service" "gitlab.com/uafrica/go-utils/string_utils" ) // Ctx extends service ctx to include url etc. var Ctx Context // New creates the API with the specified routes keys on [path][method] // value could be any of the handler function signatures supported by the api.Router // requestIDHeaderKey is defined in the response header to match the requestID from the request func New(requestIDHeaderKey string, routes map[string]map[string]interface{}) Api { if requestIDHeaderKey == "" { requestIDHeaderKey = "request-id" } router, err := NewRouter(routes) if err != nil { panic(fmt.Sprintf("cannot create router: %+v", err)) } return Api{ Service: service.New(), router: router, requestIDHeaderKey: requestIDHeaderKey, checks: map[string]ICheck{}, crashReporter: defaultCrashReporter{}, cors: nil, localQueueEventHandlers: nil, } } type Api struct { service.Service router Router requestIDHeaderKey string checks map[string]ICheck crashReporter ICrashReporter cors ICORS localQueueEventHandlers map[string]interface{} //only applies when running locally for local in-memory queues } //wrap Service.WithStarter to return api, else cannot be chained func (api Api) WithStarter(name string, starter service.IStarter) Api { api.Service = api.Service.WithStarter(name, starter) return api } //wrap else cannot be chained func (api Api) WithAuditor(auditor audit.Auditor) Api { api.Service = api.Service.WithAuditor(auditor) return api } //wrap else cannot be chained func (api Api) WithProducer(producer service.Producer) Api { api.Service = api.Service.WithProducer(producer) return api } //add a check to startup of each context //they will be called in the sequence they were added //if check return error, processing stops and err is returned //if check succeed, and return !=nil data, it is stored against the name // so your handler can retieve it with: // checkData := ctx.Value(name).(expectedType) // or // checkData,ok := ctx.Value(name).(expectedType) // if !ok { ... } //you can implement one check that does everything and return a struct or //implement one for your db, one for rate limit, one for auth, one for ... //the name must be snake-case, e.g. "this_is_my_check_data_name" func (api Api) WithCheck(name string, check ICheck) Api { if !string_utils.IsSnakeCase(name) { panic(errors.Errorf("invalid check name=\"%s\", expecting snake_case names only", name)) } if check == nil { panic(errors.Errorf("check(%s) func==nil", name)) } if _, ok := api.checks[name]; ok { panic(errors.Errorf("check(%s) already defined", name)) } api.checks[name] = check return api } func (api Api) WithCORS(cors ICORS) Api { if cors != nil { api.cors = cors } return api } func (api Api) WithCrashReported(crashReporter ICrashReporter) Api { if crashReporter != nil { api.crashReporter = crashReporter } return api } //WithEvents are not used in production, only when env LOG_LEVEL=debug //then the SQS producer is replaced with in-memory producer that uses //go channels to queue and process events, so they can be debugged locally func (api Api) WithEvents(eventHandlers map[string]interface{}) Api { if api.localQueueEventHandlers != nil { panic("local queue event handlers already defined") } api.localQueueEventHandlers = eventHandlers return api } //run and panic on error func (api Api) Run() { //decide local or SQS if (os.Getenv("LOG_LEVEL") == "debug") && api.localQueueEventHandlers != nil { //use in-memory channels for async events api.Debugf("Using in-memory channels for async events ...") api = api.WithProducer(queues_mem.NewProducer(queues_mem.NewConsumer(api.Service, api.localQueueEventHandlers))) } else { //use SQS for async events api.Debugf("Using SQS queue producer for async events ...") api = api.WithProducer(queues_sqs.NewProducer(api.requestIDHeaderKey)) } //run as an AWS Lambda function lambda.Start(api.Handler) } type defaultCrashReporter struct{} func (defaultCrashReporter) Catch(ctx Context) { crashErr := recover() if crashErr != nil { ctx.Errorf("crashed: %v, with stack: %s", crashErr, string(debug.Stack())) } }