func (ar *AbstractResource) writeStatusCodeAndErrorReason(responseWriter http.ResponseWriter,
	err error,
	defaultStatusCode int) bool {
	statusCode := common.ResolveErrorStatusCodeOrDefault(err, defaultStatusCode)
	if ar.statusCodeIsError(statusCode) {
		responseWriter.Header().Set("Content-Type", "application/json")
		responseWriter.WriteHeader(statusCode)
		ar.writeErrorReason(responseWriter, err)
		return true
	}
	responseWriter.WriteHeader(statusCode)
	return false
}
write error and status code if applicable
package restful
import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"strconv"
	"time"
	"github.com/nuclio/nuclio/pkg/common"
	"github.com/nuclio/nuclio/pkg/registry"
	"github.com/go-chi/chi/v5"
	"github.com/nuclio/errors"
	"github.com/nuclio/logger"
	"github.com/nuclio/nuclio-sdk-go"
)
type Attributes map[string]interface{}
type CustomRouteFuncResponse struct {
	ResourceType string
	Resources    map[string]Attributes
	Headers      map[string]string
	Single     bool
	StatusCode int
}
type CustomRouteFuncStreamResponse struct {
	Headers       map[string]string
	StatusCode    int
	ForceFlush    bool
	FlushInternal time.Duration
	ReadCloser    io.ReadCloser
}
type CustomRouteFunc func(*http.Request) (*CustomRouteFuncResponse, error)
type CustomRouteFuncStream func(*http.Request) (*CustomRouteFuncStreamResponse, error)
type CustomRoute struct {
	Stream          bool
	Pattern         string
	Method          string
	RouteFunc       CustomRouteFunc
	StreamRouteFunc CustomRouteFuncStream
}
type Resource interface {
	Initialize(logger.Logger, Server) (chi.Router, error)
	ExtendMiddlewares() error
	OnAfterInitialize() error
	GetCustomRoutes() ([]CustomRoute, error)
	GetAll(request *http.Request) (map[string]Attributes, error)
	GetByID(request *http.Request, id string) (Attributes, error)
	Create(request *http.Request) (string, Attributes, error)
	Update(request *http.Request, id string) (Attributes, error)
	Delete(request *http.Request, id string) error
}
type ResourceMethod int
const (
	ResourceMethodGetList ResourceMethod = iota
	ResourceMethodGetDetail
	ResourceMethodCreate
	ResourceMethodUpdate
	ResourceMethodDelete
)
const (
	ParamImport = "import"
	ParamExport = "export"
)
type AbstractResource struct {
	Logger   logger.Logger
	Resource Resource
	name            string
	router          chi.Router
	resourceMethods []ResourceMethod
	server          Server
	encoderFactory  EncoderFactory
}
func NewAbstractResource(name string, resourceMethods []ResourceMethod) *AbstractResource {
	return &AbstractResource{
		name:            name,
		resourceMethods: resourceMethods,
		encoderFactory:  &JSONEncoderFactory{},
	}
}
func (ar *AbstractResource) Initialize(parentLogger logger.Logger, server Server) (chi.Router, error) {
	ar.Logger = parentLogger.GetChild(ar.name)
	ar.server = server
	ar.router = chi.NewRouter()
	if err := ar.Resource.ExtendMiddlewares(); err != nil {
		return nil, errors.Wrap(err, "ExtendMiddlewares returned error")
	}
	if err := ar.registerRoutes(); err != nil {
		return nil, errors.Wrap(err, "Failed to register routes")
	}
	if err := ar.Resource.OnAfterInitialize(); err != nil {
		return nil, errors.Wrap(err, "OnAfterInitialize returned error")
	}
	return ar.router, nil
}
func (ar *AbstractResource) Register(registry *registry.Registry) {
	registry.Register(ar.name, ar)
}
func (ar *AbstractResource) GetName() string {
	return ar.name
}
func (ar *AbstractResource) GetServer() Server {
	return ar.server
}
func (ar *AbstractResource) OnAfterInitialize() error {
	return nil
}
func (ar *AbstractResource) GetAll(request *http.Request) (map[string]Attributes, error) {
	return nil, nil
}
func (ar *AbstractResource) GetByID(request *http.Request, id string) (Attributes, error) {
	return nil, nil
}
func (ar *AbstractResource) Create(request *http.Request) (string, Attributes, error) {
	return "", nil, nuclio.ErrNotImplemented
}
func (ar *AbstractResource) Update(request *http.Request, id string) (Attributes, error) {
	return nil, nuclio.ErrNotImplemented
}
func (ar *AbstractResource) Delete(request *http.Request, id string) error {
	return nuclio.ErrNotImplemented
}
func (ar *AbstractResource) GetCustomRoutes() ([]CustomRoute, error) {
	return []CustomRoute{}, nil
}
func (ar *AbstractResource) GetRouter() chi.Router {
	return ar.router
}
func (ar *AbstractResource) ExtendMiddlewares() error {
	return nil
}
func (ar *AbstractResource) parseURLParamValue(paramValue string) interface{} {
	parsedBool, err := strconv.ParseBool(paramValue)
	if err == nil {
		return parsedBool
	}
	parsedInt, err := strconv.ParseInt(paramValue, 10, 64)
	if err == nil {
		return parsedInt
	}
	parsedUint, err := strconv.ParseUint(paramValue, 10, 64)
	if err == nil {
		return parsedUint
	}
	parsedFloat, err := strconv.ParseFloat(paramValue, 64)
	if err == nil {
		return parsedFloat
	}
	return paramValue
}
func (ar *AbstractResource) GetURLParamValues(paramKey string, request *http.Request) []interface{} {
	paramValues, ok := request.URL.Query()[paramKey]
	if !ok || len(paramValues) == 0 {
		return nil
	}
	var values []interface{}
	for _, value := range paramValues {
		values = append(values, ar.parseURLParamValue(value))
	}
	return values
}
func (ar *AbstractResource) GetURLParamValue(paramKey string, request *http.Request) interface{} {
	paramValues, ok := request.URL.Query()[paramKey]
	if !ok || len(paramValues) == 0 {
		return nil
	}
	return ar.parseURLParamValue(paramValues[0])
}
func (ar *AbstractResource) GetURLParamBoolOrDefault(request *http.Request, paramKey string, defaultValue bool) bool {
	booleanParam, ok := ar.GetURLParamValue(paramKey, request).(bool)
	if !ok {
		return defaultValue
	}
	return booleanParam
}
func (ar *AbstractResource) GetURLParamInt64OrDefault(request *http.Request, paramKey string, defaultValue int64) int64 {
	int64Param, ok := ar.GetURLParamValue(paramKey, request).(int64)
	if !ok {
		return defaultValue
	}
	return int64Param
}
func (ar *AbstractResource) GetURLParamUint64OrDefault(request *http.Request, paramKey string, defaultValue uint64) uint64 {
	uint64Param, ok := ar.GetURLParamValue(paramKey, request).(uint64)
	if !ok {
		return defaultValue
	}
	return uint64Param
}
func (ar *AbstractResource) GetURLParamFloatOrDefault(request *http.Request, paramKey string, defaultValue float64) float64 {
	float64Param, ok := ar.GetURLParamValue(paramKey, request).(float64)
	if !ok {
		return defaultValue
	}
	return float64Param
}
func (ar *AbstractResource) GetURLParamStringOrDefault(request *http.Request, paramKey string, defaultValue string) string {
	stringParam, ok := ar.GetURLParamValue(paramKey, request).(string)
	if !ok {
		return defaultValue
	}
	return stringParam
}
func (ar *AbstractResource) GetRouterURLParam(request *http.Request, paramKey string) string {
	return chi.URLParam(request, paramKey)
}
func (ar *AbstractResource) registerRoutes() error {
	for _, resourceMethod := range ar.resourceMethods {
		switch resourceMethod {
		case ResourceMethodGetList:
			ar.router.Get("/", ar.handleGetList)
		case ResourceMethodGetDetail:
			ar.router.Get("/{id}", ar.handleGetDetails)
		case ResourceMethodCreate:
			ar.router.Post("/", ar.handleCreate)
		case ResourceMethodUpdate:
			ar.router.Put("/{id}", ar.handleUpdate)
		case ResourceMethodDelete:
			ar.router.Delete("/{id}", ar.handleDelete)
		}
	}
	return ar.registerCustomRoutes()
}
func (ar *AbstractResource) registerCustomRoutes() error {
	CustomRoutes, _ := ar.Resource.GetCustomRoutes()
	if CustomRoutes == nil {
		return nil
	}
	for _, customRoute := range CustomRoutes {
		var routerFunc func(string, http.HandlerFunc)
		switch customRoute.Method {
		case http.MethodGet:
			routerFunc = ar.router.Get
		case http.MethodPost:
			routerFunc = ar.router.Post
		case http.MethodPut:
			routerFunc = ar.router.Put
		case http.MethodDelete:
			routerFunc = ar.router.Delete
		default:
			return errors.Errorf("Invalid method %s used in custom route", customRoute.Method)
		}
		customRouteCopy := customRoute
		ar.Logger.DebugWith("Registered custom route",
			"routeName", ar.name,
			"stream", customRoute.Stream,
			"pattern", customRoute.Pattern,
			"method", customRoute.Method)
		if customRoute.Stream {
			routerFunc(customRoute.Pattern, func(responseWriter http.ResponseWriter, request *http.Request) {
				ar.callCustomStreamRouteFunc(responseWriter, request, customRouteCopy.StreamRouteFunc)
			})
		} else {
			routerFunc(customRoute.Pattern, func(responseWriter http.ResponseWriter, request *http.Request) {
				ar.callCustomRouteFunc(responseWriter, request, customRouteCopy.RouteFunc)
			})
		}
	}
	return nil
}
func (ar *AbstractResource) handleGetList(responseWriter http.ResponseWriter, request *http.Request) {
	encoder := ar.encoderFactory.NewEncoder(responseWriter, ar.name)
	allResources, err := ar.Resource.GetAll(request)
	if ar.writeStatusCodeAndErrorReason(responseWriter, err, http.StatusOK) {
		return
	}
	if allResources == nil {
		allResources = map[string]Attributes{}
	}
	encoder.EncodeResources(allResources)
}
func (ar *AbstractResource) handleGetDetails(responseWriter http.ResponseWriter, request *http.Request) {
	encoder := ar.encoderFactory.NewEncoder(responseWriter, ar.name)
	resourceID := ar.GetRouterURLParam(request, "id")
	attributes, err := ar.Resource.GetByID(request, resourceID)
	if err == nil && attributes == nil {
		responseWriter.WriteHeader(http.StatusNotFound)
		return
	}
	if ar.writeStatusCodeAndErrorReason(responseWriter, err, http.StatusOK) {
		return
	}
	if attributes == nil {
		attributes = Attributes{}
	}
	encoder.EncodeResource(resourceID, attributes)
}
func (ar *AbstractResource) handleCreate(responseWriter http.ResponseWriter, request *http.Request) {
	encoder := ar.encoderFactory.NewEncoder(responseWriter, ar.name)
	resourceID, attributes, err := ar.Resource.Create(request)
	defaultStatusCode := http.StatusCreated
	if attributes == nil {
		defaultStatusCode = http.StatusNoContent
	}
	if ar.writeStatusCodeAndErrorReason(responseWriter, err, defaultStatusCode) || attributes == nil {
		return
	}
	encoder.EncodeResource(resourceID, attributes)
}
func (ar *AbstractResource) handleUpdate(responseWriter http.ResponseWriter, request *http.Request) {
	encoder := ar.encoderFactory.NewEncoder(responseWriter, ar.name)
	resourceID := ar.GetRouterURLParam(request, "id")
	attributes, err := ar.Resource.Update(request, resourceID)
	defaultStatusCode := http.StatusOK
	if attributes == nil {
		defaultStatusCode = http.StatusNoContent
	}
	if ar.writeStatusCodeAndErrorReason(responseWriter, err, defaultStatusCode) || attributes == nil {
		return
	}
	encoder.EncodeResource(resourceID, attributes)
}
func (ar *AbstractResource) handleDelete(responseWriter http.ResponseWriter, request *http.Request) {
	resourceID := ar.GetRouterURLParam(request, "id")
	err := ar.Resource.Delete(request, resourceID)
	ar.writeStatusCodeAndErrorReason(responseWriter, err, http.StatusNoContent)
}
func (ar *AbstractResource) callCustomStreamRouteFunc(responseWriter http.ResponseWriter,
	request *http.Request,
	routeFunc CustomRouteFuncStream) {
	response, err := routeFunc(request)
	defer func() {
		if response != nil && response.ReadCloser != nil {
			response.ReadCloser.Close()
		}
	}()
	if err != nil || response == nil {
		ar.Logger.WarnWithCtx(request.Context(),
			"Custom routed handler failed",
			"response", response)
		ar.writeStatusCodeAndErrorReason(responseWriter, err, http.StatusInternalServerError)
		return
	}
	for headerKey, headerValue := range response.Headers {
		responseWriter.Header().Set(headerKey, headerValue)
	}
	responseWriter.WriteHeader(response.StatusCode)
	if response.ForceFlush {
		if response.FlushInternal == 0 {
			response.FlushInternal = time.Second
		}
		go func() {
			defer common.CatchAndLogPanic(request.Context(),  
				ar.Logger,
				"flush-custom-stream-func")
			for {
				select {
				case <-time.After(response.FlushInternal):
					if flusher, ok := responseWriter.(http.Flusher); ok {
						flusher.Flush()
					}
				case <-request.Context().Done():
					response.ReadCloser.Close()  
					return
				}
			}
		}()
	}
	if _, err := io.Copy(responseWriter, response.ReadCloser); err != nil && err != io.EOF {
		ar.Logger.WarnWithCtx(request.Context(), "Failed to stream", "err", err.Error())
		responseWriter.Write([]byte(err.Error()))  
	}
}
func (ar *AbstractResource) callCustomRouteFunc(responseWriter http.ResponseWriter,
	request *http.Request,
	routeFunc CustomRouteFunc) {
	response, err := routeFunc(request)
	if err != nil {
		ar.Logger.WarnWith("Custom routed handler failed",
			"err", err,
			"routeFunc", routeFunc,
			"request", request)
	}
	if response == nil {
		response = &CustomRouteFuncResponse{
			Single:     true,
			StatusCode: http.StatusInternalServerError,
			Headers:    map[string]string{"Content-Type": "application/json"},
		}
		ar.Logger.WarnWith("Response object not filled by handler, using placeholder",
			"routeFunc", routeFunc,
			"request", request,
			"response", response)
	}
	for headerKey, headerValue := range response.Headers {
		responseWriter.Header().Set(headerKey, headerValue)
	}
	if ar.writeStatusCodeAndErrorReason(responseWriter, err, response.StatusCode) {
		return
	}
	if response.Resources == nil {
		switch response.StatusCode {
		case http.StatusNoContent:
			break
		default:
			if _, err := responseWriter.Write([]byte("{}")); err != nil {
				ar.Logger.ErrorWith("Response writer failed writing empty resources",
					"err", err,
					"routeFunc", routeFunc,
					"request", request)
			}
		}
		return
	}
	encoder := ar.encoderFactory.NewEncoder(responseWriter, response.ResourceType)
	if response.Single {
		for resourceKey, resourceAttributes := range response.Resources {
			if resourceAttributes != nil {
				encoder.EncodeResource(resourceKey, resourceAttributes)
			}
			break
		}
	} else {
		encoder.EncodeResources(response.Resources)
	}
}
func (ar *AbstractResource) writeErrorReason(responseWriter io.Writer, err error) {
	if err == nil {
		return
	}
	buffer := bytes.Buffer{}
	switch typedErr := err.(type) {
	case nuclio.ErrorWithStatusCode:
		err = typedErr.GetError()
	case *nuclio.ErrorWithStatusCode:
		err = typedErr.GetError()
	}
	errorCause := ""
	if errors.RootCause(err) != nil {
		errorCause = errors.RootCause(err).Error()
	}
	errors.PrintErrorStack(&buffer, err, 10)
	serializedError, _ := json.Marshal(struct {
		Error           string `json:"error"`
		ErrorStackTrace string `json:"errorStackTrace"`
	}{
		errorCause,
		buffer.String(),
	})
	responseWriter.Write(serializedError)  
}
func (ar *AbstractResource) statusCodeIsError(statusCode int) bool {
	return statusCode >= 400
}