github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/usersession/agent/response.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015-2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package agent
    21  
    22  import (
    23  	"encoding/json"
    24  	"fmt"
    25  	"net/http"
    26  
    27  	"github.com/snapcore/snapd/logger"
    28  )
    29  
    30  // TODO: clean up unused code further after we have progressed enough
    31  // to have a clear sense of what is untested and uneeded here
    32  
    33  // ResponseType is the response type
    34  type ResponseType string
    35  
    36  // "there are three standard return types: Standard return value,
    37  // Background operation, Error", each returning a JSON object with the
    38  // following "type" field:
    39  const (
    40  	ResponseTypeSync  ResponseType = "sync"
    41  	ResponseTypeAsync ResponseType = "async"
    42  	ResponseTypeError ResponseType = "error"
    43  )
    44  
    45  // Response knows how to serve itself, and how to find itself
    46  type Response interface {
    47  	ServeHTTP(w http.ResponseWriter, r *http.Request)
    48  }
    49  
    50  type resp struct {
    51  	Status int // HTTP status code
    52  	Type   ResponseType
    53  	Result interface{}
    54  }
    55  
    56  type respJSON struct {
    57  	Type   ResponseType `json:"type"`
    58  	Result interface{}  `json:"result"`
    59  }
    60  
    61  func (r *resp) MarshalJSON() ([]byte, error) {
    62  	return json.Marshal(respJSON{
    63  		Type:   r.Type,
    64  		Result: r.Result,
    65  	})
    66  }
    67  
    68  func (r *resp) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
    69  	status := r.Status
    70  	bs, err := r.MarshalJSON()
    71  	if err != nil {
    72  		logger.Noticef("cannot marshal %#v to JSON: %v", *r, err)
    73  		bs = nil
    74  		status = 500
    75  	}
    76  
    77  	hdr := w.Header()
    78  	if r.Status == 202 || r.Status == 201 {
    79  		if m, ok := r.Result.(map[string]interface{}); ok {
    80  			if location, ok := m["resource"]; ok {
    81  				if location, ok := location.(string); ok && location != "" {
    82  					hdr.Set("Location", location)
    83  				}
    84  			}
    85  		}
    86  	}
    87  
    88  	hdr.Set("Content-Type", "application/json")
    89  	w.WriteHeader(status)
    90  	w.Write(bs)
    91  }
    92  
    93  type errorKind string
    94  
    95  const (
    96  	errorKindLoginRequired  = errorKind("login-required")
    97  	errorKindServiceControl = errorKind("service-control")
    98  )
    99  
   100  type errorValue interface{}
   101  
   102  type errorResult struct {
   103  	Message string     `json:"message"` // mandatory in error responses
   104  	Kind    errorKind  `json:"kind,omitempty"`
   105  	Value   errorValue `json:"value,omitempty"`
   106  }
   107  
   108  // SyncResponse builds a "sync" response from the given result.
   109  func SyncResponse(result interface{}) Response {
   110  	if err, ok := result.(error); ok {
   111  		return InternalError("internal error: %v", err)
   112  	}
   113  
   114  	if rsp, ok := result.(Response); ok {
   115  		return rsp
   116  	}
   117  
   118  	return &resp{
   119  		Type:   ResponseTypeSync,
   120  		Status: 200,
   121  		Result: result,
   122  	}
   123  }
   124  
   125  // AsyncResponse builds an "async" response from the given *Task
   126  func AsyncResponse(result map[string]interface{}) Response {
   127  	return &resp{
   128  		Type:   ResponseTypeAsync,
   129  		Status: 202,
   130  		Result: result,
   131  	}
   132  }
   133  
   134  // makeErrorResponder builds an errorResponder from the given error status.
   135  func makeErrorResponder(status int) errorResponder {
   136  	return func(format string, v ...interface{}) Response {
   137  		res := &errorResult{}
   138  		if len(v) == 0 {
   139  			res.Message = format
   140  		} else {
   141  			res.Message = fmt.Sprintf(format, v...)
   142  		}
   143  		if status == 401 {
   144  			res.Kind = errorKindLoginRequired
   145  		}
   146  		return &resp{
   147  			Type:   ResponseTypeError,
   148  			Result: res,
   149  			Status: status,
   150  		}
   151  	}
   152  }
   153  
   154  // errorResponder is a callable that produces an error Response.
   155  // e.g., InternalError("something broke: %v", err), etc.
   156  type errorResponder func(string, ...interface{}) Response
   157  
   158  // standard error responses
   159  var (
   160  	Unauthorized     = makeErrorResponder(401)
   161  	NotFound         = makeErrorResponder(404)
   162  	BadRequest       = makeErrorResponder(400)
   163  	MethodNotAllowed = makeErrorResponder(405)
   164  	InternalError    = makeErrorResponder(500)
   165  	NotImplemented   = makeErrorResponder(501)
   166  	Forbidden        = makeErrorResponder(403)
   167  	Conflict         = makeErrorResponder(409)
   168  )