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 }