github.com/hellofresh/janus@v0.0.0-20230925145208-ce8de8183c67/pkg/web/api_handler.go (about)

     1  package web
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	"github.com/hellofresh/janus/pkg/api"
     9  	"github.com/hellofresh/janus/pkg/errors"
    10  	"github.com/hellofresh/janus/pkg/plugin"
    11  	"github.com/hellofresh/janus/pkg/render"
    12  	"github.com/hellofresh/janus/pkg/router"
    13  	"go.opencensus.io/trace"
    14  )
    15  
    16  // APIHandler is the api rest controller
    17  type APIHandler struct {
    18  	configurationChan chan<- api.ConfigurationMessage
    19  	Cfgs              *api.Configuration
    20  }
    21  
    22  // NewAPIHandler creates a new instance of Controller
    23  func NewAPIHandler(cfgChan chan<- api.ConfigurationMessage) *APIHandler {
    24  	return &APIHandler{
    25  		configurationChan: cfgChan,
    26  	}
    27  }
    28  
    29  // Get is the find all handler
    30  func (c *APIHandler) Get() http.HandlerFunc {
    31  	return func(w http.ResponseWriter, r *http.Request) {
    32  		_, span := trace.StartSpan(r.Context(), "definitions.GetAll")
    33  		defer span.End()
    34  
    35  		if c.Cfgs.Definitions == nil {
    36  			// id definitions list is empty - fake it with simple slice to get the empty JSON array in the output
    37  			render.JSON(w, http.StatusOK, []int{})
    38  			return
    39  		}
    40  
    41  		render.JSON(w, http.StatusOK, c.Cfgs.Definitions)
    42  	}
    43  }
    44  
    45  // GetBy is the find by handler
    46  func (c *APIHandler) GetBy() http.HandlerFunc {
    47  	return func(w http.ResponseWriter, r *http.Request) {
    48  		name := router.URLParam(r, "name")
    49  		_, span := trace.StartSpan(r.Context(), "definition.FindByName")
    50  		cfg := c.findByName(name)
    51  		span.End()
    52  
    53  		if cfg == nil {
    54  			errors.Handler(w, r, api.ErrAPIDefinitionNotFound)
    55  			return
    56  		}
    57  
    58  		render.JSON(w, http.StatusOK, cfg)
    59  	}
    60  }
    61  
    62  // PutBy is the update handler
    63  func (c *APIHandler) PutBy() http.HandlerFunc {
    64  	return func(w http.ResponseWriter, r *http.Request) {
    65  		var err error
    66  
    67  		name := router.URLParam(r, "name")
    68  		_, span := trace.StartSpan(r.Context(), "definition.FindByName")
    69  		cfg := c.findByName(name)
    70  		span.End()
    71  
    72  		if cfg == nil {
    73  			errors.Handler(w, r, api.ErrAPIDefinitionNotFound)
    74  			return
    75  		}
    76  
    77  		err = json.NewDecoder(r.Body).Decode(cfg)
    78  		if err != nil {
    79  			errors.Handler(w, r, err)
    80  			return
    81  		}
    82  
    83  		isValid, err := cfg.Validate()
    84  		if false == isValid && err != nil {
    85  			errors.Handler(w, r, errors.New(http.StatusBadRequest, err.Error()))
    86  			return
    87  		}
    88  
    89  		// Additionally validate plugin configuration
    90  		for _, plg := range cfg.Plugins {
    91  			isValid, err := plugin.ValidateConfig(plg.Name, plg.Config)
    92  			if !isValid || err != nil {
    93  				errors.Handler(w, r, errors.New(http.StatusBadRequest, err.Error()))
    94  				return
    95  			}
    96  		}
    97  
    98  		// avoid situation when trying to update existing definition with new path
    99  		// that is already registered with another name
   100  		_, span = trace.StartSpan(r.Context(), "repo.FindByListenPath")
   101  		existingCfg := c.findByListenPath(cfg.Proxy.ListenPath)
   102  		span.End()
   103  
   104  		if existingCfg != nil && existingCfg.Name != cfg.Name {
   105  			errors.Handler(w, r, api.ErrAPIListenPathExists)
   106  			return
   107  		}
   108  
   109  		_, span = trace.StartSpan(r.Context(), "repo.Update")
   110  		c.configurationChan <- api.ConfigurationMessage{
   111  			Operation:     api.UpdatedOperation,
   112  			Configuration: cfg,
   113  		}
   114  		span.End()
   115  
   116  		w.WriteHeader(http.StatusOK)
   117  	}
   118  }
   119  
   120  // Post is the create handler
   121  func (c *APIHandler) Post() http.HandlerFunc {
   122  	return func(w http.ResponseWriter, r *http.Request) {
   123  		cfg := api.NewDefinition()
   124  
   125  		err := json.NewDecoder(r.Body).Decode(cfg)
   126  		if nil != err {
   127  			errors.Handler(w, r, err)
   128  			return
   129  		}
   130  
   131  		isValid, err := cfg.Validate()
   132  		if false == isValid && err != nil {
   133  			errors.Handler(w, r, errors.New(http.StatusBadRequest, err.Error()))
   134  			return
   135  		}
   136  
   137  		// Additionally validate plugin configuration
   138  		for _, plg := range cfg.Plugins {
   139  			isValid, err := plugin.ValidateConfig(plg.Name, plg.Config)
   140  			if !isValid || err != nil {
   141  				errors.Handler(w, r, errors.New(http.StatusBadRequest, err.Error()))
   142  				return
   143  			}
   144  		}
   145  
   146  		_, span := trace.StartSpan(r.Context(), "definition.Exists")
   147  		exists, err := c.exists(cfg)
   148  		span.End()
   149  
   150  		if err != nil || exists {
   151  			errors.Handler(w, r, err)
   152  			return
   153  		}
   154  
   155  		_, span = trace.StartSpan(r.Context(), "repo.Add")
   156  		c.configurationChan <- api.ConfigurationMessage{
   157  			Operation:     api.AddedOperation,
   158  			Configuration: cfg,
   159  		}
   160  		span.End()
   161  
   162  		w.Header().Add("Location", fmt.Sprintf("/apis/%s", cfg.Name))
   163  		w.WriteHeader(http.StatusCreated)
   164  	}
   165  }
   166  
   167  // DeleteBy is the delete handler
   168  func (c *APIHandler) DeleteBy() http.HandlerFunc {
   169  	return func(w http.ResponseWriter, r *http.Request) {
   170  		_, span := trace.StartSpan(r.Context(), "repo.Remove")
   171  		defer span.End()
   172  
   173  		name := router.URLParam(r, "name")
   174  		cfg := c.findByName(name)
   175  		if cfg == nil {
   176  			errors.Handler(w, r, api.ErrAPIDefinitionNotFound)
   177  			return
   178  		}
   179  
   180  		c.configurationChan <- api.ConfigurationMessage{
   181  			Operation:     api.RemovedOperation,
   182  			Configuration: cfg,
   183  		}
   184  
   185  		w.WriteHeader(http.StatusNoContent)
   186  	}
   187  }
   188  
   189  func (c *APIHandler) exists(cfg *api.Definition) (bool, error) {
   190  	for _, storedCfg := range c.Cfgs.Definitions {
   191  		if storedCfg.Name == cfg.Name {
   192  			return true, api.ErrAPINameExists
   193  		}
   194  
   195  		if storedCfg.Proxy.ListenPath == cfg.Proxy.ListenPath {
   196  			return true, api.ErrAPIListenPathExists
   197  		}
   198  	}
   199  
   200  	return false, nil
   201  }
   202  
   203  func (c *APIHandler) findByName(name string) *api.Definition {
   204  	for _, cfg := range c.Cfgs.Definitions {
   205  		if cfg.Name == name {
   206  			return cfg
   207  		}
   208  	}
   209  
   210  	return nil
   211  }
   212  
   213  func (c *APIHandler) findByListenPath(listenPath string) *api.Definition {
   214  	for _, cfg := range c.Cfgs.Definitions {
   215  		if cfg.Proxy.ListenPath == listenPath {
   216  			return cfg
   217  		}
   218  	}
   219  
   220  	return nil
   221  }