<commit_before>// Verify data

package main

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"

	pip "github.com/JamesMilnerUK/pip-go"
	"github.com/printmaps/printmaps/pd"
)

/*
verifyContentType verifies the media type for header field "Content-Type"
*/
func verifyContentType(request *http.Request, pmErrorList *pd.PrintmapsErrorList) {
	mediaType := request.Header.Get("Content-Type")
	if mediaType != pd.JSONAPIMediaType {
		appendError(pmErrorList, "1001", "expected http header field = Content-Type: "+pd.JSONAPIMediaType, "")
	}
}

/*
verifyAccept verifies the media type for header field "Accept"
*/
func verifyAccept(request *http.Request, pmErrorList *pd.PrintmapsErrorList) {

	mediaType := request.Header.Get("Accept")
	if mediaType != pd.JSONAPIMediaType {
		appendError(pmErrorList, "1002", "expected http header field = Accept: "+pd.JSONAPIMediaType, "")
	}
}

/*
verifyMetadata verifies the map meta data
*/
func verifyMetadata(pmData pd.PrintmapsData, pmErrorList *pd.PrintmapsErrorList) {
	var message string
	var found bool

	if pmData.Data.Type != "maps" {
		appendError(pmErrorList, "3001", "valid value: maps", pmData.Data.ID)
	}

	// try to find the style
	if pmData.Data.Attributes.Style != "" {
		found = false
		for _, style := range pmFeature.ConfigStyles {
			if pmData.Data.Attributes.Style == style.Name {
				found = true
				break
			}
		}
		if found == false {
			var validStyles []string
			for _, style := range pmFeature.ConfigStyles {
				validStyles = append(validStyles, style.Name)
			}
			message = fmt.Sprintf("valid values: %s", strings.Join(validStyles, ", "))
			appendError(pmErrorList, "3008", message, pmData.Data.ID)
		}
	}

	// try to find the format
	var inputMapformat ConfigMapformat
	if pmData.Data.Attributes.Fileformat != "" {
		found = false
		for _, mapformat := range pmFeature.ConfigMapformats {
			if pmData.Data.Attributes.Fileformat == mapformat.Type {
				inputMapformat = mapformat
				found = true
				break
			}
		}
		if found == false {
			var validFormats []string
			for _, mapformat := range pmFeature.ConfigMapformats {
				validFormats = append(validFormats, mapformat.Type)
			}
			message = fmt.Sprintf("valid values: %s", strings.Join(validFormats, ", "))
			appendError(pmErrorList, "3002", message, pmData.Data.ID)
		}
	}

	if pmData.Data.Attributes.Scale != 0 {
		if pmData.Data.Attributes.Scale < pmFeature.ConfigMapscale.MinScale || pmData.Data.Attributes.Scale > pmFeature.ConfigMapscale.MaxScale {
			message = fmt.Sprintf("valid values: %d ... %d", pmFeature.ConfigMapscale.MinScale, pmFeature.ConfigMapscale.MaxScale)
			appendError(pmErrorList, "3003", message, pmData.Data.ID)
		}
	}

	if pmData.Data.Attributes.PrintWidth != 0 && inputMapformat.Type != "" {
		if pmData.Data.Attributes.PrintWidth < inputMapformat.MinPrintWidth || pmData.Data.Attributes.PrintWidth > inputMapformat.MaxPrintWidth {
			message = fmt.Sprintf("valid values: %.2f ... %.2f", inputMapformat.MinPrintWidth, inputMapformat.MaxPrintWidth)
			appendError(pmErrorList, "3004", message, pmData.Data.ID)
		}
	}

	if pmData.Data.Attributes.PrintHeight != 0 && inputMapformat.Type != "" {
		if pmData.Data.Attributes.PrintHeight < inputMapformat.MinPrintHeigth || pmData.Data.Attributes.PrintHeight > inputMapformat.MaxPrintHeigth {
			message = fmt.Sprintf("valid values: %.2f ... %.2f", inputMapformat.MinPrintHeigth, inputMapformat.MaxPrintHeigth)
			appendError(pmErrorList, "3005", message, pmData.Data.ID)
		}
	}

	if pmData.Data.Attributes.Latitude != 0.0 {
		// latMin := pPolygonBoundingBox.BottomLeft.Y
		// latMax := pPolygonBoundingBox.TopRight.Y
		latMin := pmFeature.ConfigMapdata.MinLatitude
		latMax := pmFeature.ConfigMapdata.MaxLatitude
		if pmData.Data.Attributes.Latitude < latMin || pmData.Data.Attributes.Latitude > latMax {
			message = fmt.Sprintf("valid values: %.2f ... %.2f", latMin, latMax)
			appendError(pmErrorList, "3006", message, pmData.Data.ID)
		}
	}

	if pmData.Data.Attributes.Longitude != 0.0 {
		// lonMin := pPolygonBoundingBox.BottomLeft.X
		// lonMax := pPolygonBoundingBox.TopRight.X
		lonMin := pmFeature.ConfigMapdata.MinLongitude
		lonMax := pmFeature.ConfigMapdata.MaxLongitude
		if pmData.Data.Attributes.Longitude < lonMin || pmData.Data.Attributes.Longitude > lonMax {
			message = fmt.Sprintf("valid values: %.2f ... %.2f", lonMin, lonMax)
			appendError(pmErrorList, "3007", message, pmData.Data.ID)
		}
	}

	// projection must be an integer
	if pmData.Data.Attributes.Projection != "" {
		_, err := strconv.Atoi(pmData.Data.Attributes.Projection)
		if err != nil {
			appendError(pmErrorList, "3014", "projection must be an integer", pmData.Data.ID)
		}
	}

	// full planet osm data (world) : config.Polyfile empty
	if config.Polyfile != "" {
		if pmData.Data.Attributes.Latitude != 0.0 || pmData.Data.Attributes.Longitude != 0.0 {
			var pP pip.Point
			pP.X = pmData.Data.Attributes.Longitude
			pP.Y = pmData.Data.Attributes.Latitude
			found = pip.PointInPolygon(pP, pPolygon)
			if found == false {
				appendError(pmErrorList, "3013", "no data available for the center position of the map", pmData.Data.ID)
			}
		}
	}
}

/*
verifyRequiredMetadata verifies (only) the existence of the required map meta data
*/
func verifyRequiredMetadata(pmData pd.PrintmapsData, pmErrorList *pd.PrintmapsErrorList) {
	var missingAttributes []string

	// required map meta data (content already validated)

	if pmData.Data.Attributes.Style == "" {
		missingAttributes = append(missingAttributes, "style")
	}
	if pmData.Data.Attributes.Fileformat == "" {
		missingAttributes = append(missingAttributes, "fileformat")
	}
	if pmData.Data.Attributes.Scale == 0 {
		missingAttributes = append(missingAttributes, "scale")
	}
	if pmData.Data.Attributes.PrintWidth == 0 {
		missingAttributes = append(missingAttributes, "printWidth")
	}
	if pmData.Data.Attributes.PrintHeight == 0 {
		missingAttributes = append(missingAttributes, "printHeigth")
	}
	if pmData.Data.Attributes.Latitude == 0.0 {
		missingAttributes = append(missingAttributes, "latitude")
	}
	if pmData.Data.Attributes.Longitude == 0.0 {
		missingAttributes = append(missingAttributes, "longitude")
	}
	if pmData.Data.Attributes.Projection == "" {
		missingAttributes = append(missingAttributes, "projection")
	}

	if len(missingAttributes) > 0 {
		detail := fmt.Sprintf("missing attribute(s): %s", strings.Join(missingAttributes, ", "))
		appendError(pmErrorList, "5001", detail, pmData.Data.ID)
	}
}

/*
appendError append an error entry to the error list
*/
func appendError(pmErrorList *pd.PrintmapsErrorList, code string, detail string, mapID string) {
	var jaError pd.PrintmapsError

	switch code {
	case "1001":
		jaError.Status = strconv.Itoa(http.StatusUnsupportedMediaType) + " " + http.StatusText(http.StatusUnsupportedMediaType)
		jaError.Source.Pointer = "Content-Type"
		jaError.Title = "missing or unexpected http header field Content-Type"
	case "1002":
		jaError.Status = strconv.Itoa(http.StatusUnsupportedMediaType) + " " + http.StatusText(http.StatusUnsupportedMediaType)
		jaError.Source.Pointer = "Accept"
		jaError.Title = "missing or unexpected http header field Accept"
	case "2001":
		jaError.Status = strconv.Itoa(http.StatusBadRequest) + " " + http.StatusText(http.StatusBadRequest)
		jaError.Source.Pointer = "body"
		jaError.Title = "missing or undecodable http body (json)"
	case "3001":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.type"
		jaError.Title = "invalid type"
	case "3002":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.fileformat"
		jaError.Title = "invalid attribute fileformat"
	case "3003":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.scale"
		jaError.Title = "invalid attribute scale"
	case "3004":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.printWidth"
		jaError.Title = "invalid attribute printWidth"
	case "3005":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.printHeight"
		jaError.Title = "invalid attribute printHeight"
	case "3006":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.latitude"
		jaError.Title = "invalid attribute latitude"
	case "3007":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.longitude"
		jaError.Title = "invalid attribute longitude"
	case "3008":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.style"
		jaError.Title = "invalid attribute style"
	case "3013":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.latitude and/or data.attributes.longitude"
		jaError.Title = "no map data available"
	case "3014":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.projection"
		jaError.Title = "invalid attribute projection"
	case "4001":
		jaError.Status = strconv.Itoa(http.StatusNotFound) + " " + http.StatusText(http.StatusNotFound)
		jaError.Source.Pointer = "id"
		jaError.Title = "id invalid (not an uuid)"
	case "4002":
		jaError.Status = strconv.Itoa(http.StatusNotFound) + " " + http.StatusText(http.StatusNotFound)
		jaError.Source.Pointer = "id"
		jaError.Title = "id not found"
	case "5001":
		jaError.Status = strconv.Itoa(http.StatusPreconditionFailed) + " " + http.StatusText(http.StatusPreconditionFailed)
		jaError.Source.Pointer = "data.attributes"
		jaError.Title = "map build rejected, required attributes missing"
	case "6001":
		jaError.Status = strconv.Itoa(http.StatusPreconditionFailed) + " " + http.StatusText(http.StatusPreconditionFailed)
		jaError.Source.Pointer = "POST: api/beta2/maps/mapfile"
		jaError.Title = "map build order missing"
	case "6002":
		jaError.Status = strconv.Itoa(http.StatusPreconditionFailed) + " " + http.StatusText(http.StatusPreconditionFailed)
		jaError.Source.Pointer = "SERVER: asynchronous build process"
		jaError.Title = "map build not started yet"
	case "6003":
		jaError.Status = strconv.Itoa(http.StatusPreconditionFailed) + " " + http.StatusText(http.StatusPreconditionFailed)
		jaError.Source.Pointer = "SERVER: asynchronous build process"
		jaError.Title = "map build not completed yet"
	case "6004":
		jaError.Status = strconv.Itoa(http.StatusPreconditionFailed) + " " + http.StatusText(http.StatusPreconditionFailed)
		jaError.Source.Pointer = "SERVER: map build process"
		jaError.Title = "map build process not successful"
	case "7001":
		jaError.Status = strconv.Itoa(http.StatusRequestEntityTooLarge) + " " + http.StatusText(http.StatusRequestEntityTooLarge)
		jaError.Source.Pointer = "POST: api/beta2/maps/upload"
		jaError.Title = "size of uploaded file exceeds upload limit"
	case "7002":
		jaError.Status = strconv.Itoa(http.StatusUnsupportedMediaType) + " " + http.StatusText(http.StatusUnsupportedMediaType)
		jaError.Source.Pointer = "POST: api/beta2/maps/upload"
		jaError.Title = "insecure file rejected"
	default:
		jaError.Status = strconv.Itoa(http.StatusInternalServerError) + " " + http.StatusText(http.StatusInternalServerError)
		jaError.Source.Pointer = "unknown error code"
		jaError.Title = "unexpected program error"
	}

	jaError.ID = mapID
	jaError.Code = code
	jaError.Detail = detail
	pmErrorList.Errors = append(pmErrorList.Errors, jaError)
}
<commit_msg>fix: case insensitive verification of MIME types<commit_after>// Verify data

package main

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"

	pip "github.com/JamesMilnerUK/pip-go"
	"github.com/printmaps/printmaps/pd"
)

/*
verifyContentType verifies the media type for header field "Content-Type"
*/
func verifyContentType(request *http.Request, pmErrorList *pd.PrintmapsErrorList) {
	mediaType := request.Header.Get("Content-Type")
	if !strings.EqualsFold(pd.JSONAPIMediaType, mediaType) {
		appendError(pmErrorList, "1001", "expected http header field = Content-Type: "+pd.JSONAPIMediaType+" but received: "+mediaType, "")
	}
}

/*
verifyAccept verifies the media type for header field "Accept"
*/
func verifyAccept(request *http.Request, pmErrorList *pd.PrintmapsErrorList) {

	mediaType := request.Header.Get("Accept")
	if !strings.EqualsFold(pd.JSONAPIMediaType, mediaType) {
		appendError(pmErrorList, "1002", "expected http header field = Accept: "+pd.JSONAPIMediaType+" but received: "+mediaType, "")
	}
}

/*
verifyMetadata verifies the map meta data
*/
func verifyMetadata(pmData pd.PrintmapsData, pmErrorList *pd.PrintmapsErrorList) {
	var message string
	var found bool

	if pmData.Data.Type != "maps" {
		appendError(pmErrorList, "3001", "valid value: maps", pmData.Data.ID)
	}

	// try to find the style
	if pmData.Data.Attributes.Style != "" {
		found = false
		for _, style := range pmFeature.ConfigStyles {
			if pmData.Data.Attributes.Style == style.Name {
				found = true
				break
			}
		}
		if found == false {
			var validStyles []string
			for _, style := range pmFeature.ConfigStyles {
				validStyles = append(validStyles, style.Name)
			}
			message = fmt.Sprintf("valid values: %s", strings.Join(validStyles, ", "))
			appendError(pmErrorList, "3008", message, pmData.Data.ID)
		}
	}

	// try to find the format
	var inputMapformat ConfigMapformat
	if pmData.Data.Attributes.Fileformat != "" {
		found = false
		for _, mapformat := range pmFeature.ConfigMapformats {
			if pmData.Data.Attributes.Fileformat == mapformat.Type {
				inputMapformat = mapformat
				found = true
				break
			}
		}
		if found == false {
			var validFormats []string
			for _, mapformat := range pmFeature.ConfigMapformats {
				validFormats = append(validFormats, mapformat.Type)
			}
			message = fmt.Sprintf("valid values: %s", strings.Join(validFormats, ", "))
			appendError(pmErrorList, "3002", message, pmData.Data.ID)
		}
	}

	if pmData.Data.Attributes.Scale != 0 {
		if pmData.Data.Attributes.Scale < pmFeature.ConfigMapscale.MinScale || pmData.Data.Attributes.Scale > pmFeature.ConfigMapscale.MaxScale {
			message = fmt.Sprintf("valid values: %d ... %d", pmFeature.ConfigMapscale.MinScale, pmFeature.ConfigMapscale.MaxScale)
			appendError(pmErrorList, "3003", message, pmData.Data.ID)
		}
	}

	if pmData.Data.Attributes.PrintWidth != 0 && inputMapformat.Type != "" {
		if pmData.Data.Attributes.PrintWidth < inputMapformat.MinPrintWidth || pmData.Data.Attributes.PrintWidth > inputMapformat.MaxPrintWidth {
			message = fmt.Sprintf("valid values: %.2f ... %.2f", inputMapformat.MinPrintWidth, inputMapformat.MaxPrintWidth)
			appendError(pmErrorList, "3004", message, pmData.Data.ID)
		}
	}

	if pmData.Data.Attributes.PrintHeight != 0 && inputMapformat.Type != "" {
		if pmData.Data.Attributes.PrintHeight < inputMapformat.MinPrintHeigth || pmData.Data.Attributes.PrintHeight > inputMapformat.MaxPrintHeigth {
			message = fmt.Sprintf("valid values: %.2f ... %.2f", inputMapformat.MinPrintHeigth, inputMapformat.MaxPrintHeigth)
			appendError(pmErrorList, "3005", message, pmData.Data.ID)
		}
	}

	if pmData.Data.Attributes.Latitude != 0.0 {
		// latMin := pPolygonBoundingBox.BottomLeft.Y
		// latMax := pPolygonBoundingBox.TopRight.Y
		latMin := pmFeature.ConfigMapdata.MinLatitude
		latMax := pmFeature.ConfigMapdata.MaxLatitude
		if pmData.Data.Attributes.Latitude < latMin || pmData.Data.Attributes.Latitude > latMax {
			message = fmt.Sprintf("valid values: %.2f ... %.2f", latMin, latMax)
			appendError(pmErrorList, "3006", message, pmData.Data.ID)
		}
	}

	if pmData.Data.Attributes.Longitude != 0.0 {
		// lonMin := pPolygonBoundingBox.BottomLeft.X
		// lonMax := pPolygonBoundingBox.TopRight.X
		lonMin := pmFeature.ConfigMapdata.MinLongitude
		lonMax := pmFeature.ConfigMapdata.MaxLongitude
		if pmData.Data.Attributes.Longitude < lonMin || pmData.Data.Attributes.Longitude > lonMax {
			message = fmt.Sprintf("valid values: %.2f ... %.2f", lonMin, lonMax)
			appendError(pmErrorList, "3007", message, pmData.Data.ID)
		}
	}

	// projection must be an integer
	if pmData.Data.Attributes.Projection != "" {
		_, err := strconv.Atoi(pmData.Data.Attributes.Projection)
		if err != nil {
			appendError(pmErrorList, "3014", "projection must be an integer", pmData.Data.ID)
		}
	}

	// full planet osm data (world) : config.Polyfile empty
	if config.Polyfile != "" {
		if pmData.Data.Attributes.Latitude != 0.0 || pmData.Data.Attributes.Longitude != 0.0 {
			var pP pip.Point
			pP.X = pmData.Data.Attributes.Longitude
			pP.Y = pmData.Data.Attributes.Latitude
			found = pip.PointInPolygon(pP, pPolygon)
			if found == false {
				appendError(pmErrorList, "3013", "no data available for the center position of the map", pmData.Data.ID)
			}
		}
	}
}

/*
verifyRequiredMetadata verifies (only) the existence of the required map meta data
*/
func verifyRequiredMetadata(pmData pd.PrintmapsData, pmErrorList *pd.PrintmapsErrorList) {
	var missingAttributes []string

	// required map meta data (content already validated)

	if pmData.Data.Attributes.Style == "" {
		missingAttributes = append(missingAttributes, "style")
	}
	if pmData.Data.Attributes.Fileformat == "" {
		missingAttributes = append(missingAttributes, "fileformat")
	}
	if pmData.Data.Attributes.Scale == 0 {
		missingAttributes = append(missingAttributes, "scale")
	}
	if pmData.Data.Attributes.PrintWidth == 0 {
		missingAttributes = append(missingAttributes, "printWidth")
	}
	if pmData.Data.Attributes.PrintHeight == 0 {
		missingAttributes = append(missingAttributes, "printHeigth")
	}
	if pmData.Data.Attributes.Latitude == 0.0 {
		missingAttributes = append(missingAttributes, "latitude")
	}
	if pmData.Data.Attributes.Longitude == 0.0 {
		missingAttributes = append(missingAttributes, "longitude")
	}
	if pmData.Data.Attributes.Projection == "" {
		missingAttributes = append(missingAttributes, "projection")
	}

	if len(missingAttributes) > 0 {
		detail := fmt.Sprintf("missing attribute(s): %s", strings.Join(missingAttributes, ", "))
		appendError(pmErrorList, "5001", detail, pmData.Data.ID)
	}
}

/*
appendError append an error entry to the error list
*/
func appendError(pmErrorList *pd.PrintmapsErrorList, code string, detail string, mapID string) {
	var jaError pd.PrintmapsError

	switch code {
	case "1001":
		jaError.Status = strconv.Itoa(http.StatusUnsupportedMediaType) + " " + http.StatusText(http.StatusUnsupportedMediaType)
		jaError.Source.Pointer = "Content-Type"
		jaError.Title = "missing or unexpected http header field Content-Type"
	case "1002":
		jaError.Status = strconv.Itoa(http.StatusUnsupportedMediaType) + " " + http.StatusText(http.StatusUnsupportedMediaType)
		jaError.Source.Pointer = "Accept"
		jaError.Title = "missing or unexpected http header field Accept"
	case "2001":
		jaError.Status = strconv.Itoa(http.StatusBadRequest) + " " + http.StatusText(http.StatusBadRequest)
		jaError.Source.Pointer = "body"
		jaError.Title = "missing or undecodable http body (json)"
	case "3001":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.type"
		jaError.Title = "invalid type"
	case "3002":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.fileformat"
		jaError.Title = "invalid attribute fileformat"
	case "3003":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.scale"
		jaError.Title = "invalid attribute scale"
	case "3004":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.printWidth"
		jaError.Title = "invalid attribute printWidth"
	case "3005":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.printHeight"
		jaError.Title = "invalid attribute printHeight"
	case "3006":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.latitude"
		jaError.Title = "invalid attribute latitude"
	case "3007":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.longitude"
		jaError.Title = "invalid attribute longitude"
	case "3008":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.style"
		jaError.Title = "invalid attribute style"
	case "3013":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.latitude and/or data.attributes.longitude"
		jaError.Title = "no map data available"
	case "3014":
		jaError.Status = strconv.Itoa(http.StatusUnprocessableEntity) + " " + http.StatusText(http.StatusUnprocessableEntity)
		jaError.Source.Pointer = "data.attributes.projection"
		jaError.Title = "invalid attribute projection"
	case "4001":
		jaError.Status = strconv.Itoa(http.StatusNotFound) + " " + http.StatusText(http.StatusNotFound)
		jaError.Source.Pointer = "id"
		jaError.Title = "id invalid (not an uuid)"
	case "4002":
		jaError.Status = strconv.Itoa(http.StatusNotFound) + " " + http.StatusText(http.StatusNotFound)
		jaError.Source.Pointer = "id"
		jaError.Title = "id not found"
	case "5001":
		jaError.Status = strconv.Itoa(http.StatusPreconditionFailed) + " " + http.StatusText(http.StatusPreconditionFailed)
		jaError.Source.Pointer = "data.attributes"
		jaError.Title = "map build rejected, required attributes missing"
	case "6001":
		jaError.Status = strconv.Itoa(http.StatusPreconditionFailed) + " " + http.StatusText(http.StatusPreconditionFailed)
		jaError.Source.Pointer = "POST: api/beta2/maps/mapfile"
		jaError.Title = "map build order missing"
	case "6002":
		jaError.Status = strconv.Itoa(http.StatusPreconditionFailed) + " " + http.StatusText(http.StatusPreconditionFailed)
		jaError.Source.Pointer = "SERVER: asynchronous build process"
		jaError.Title = "map build not started yet"
	case "6003":
		jaError.Status = strconv.Itoa(http.StatusPreconditionFailed) + " " + http.StatusText(http.StatusPreconditionFailed)
		jaError.Source.Pointer = "SERVER: asynchronous build process"
		jaError.Title = "map build not completed yet"
	case "6004":
		jaError.Status = strconv.Itoa(http.StatusPreconditionFailed) + " " + http.StatusText(http.StatusPreconditionFailed)
		jaError.Source.Pointer = "SERVER: map build process"
		jaError.Title = "map build process not successful"
	case "7001":
		jaError.Status = strconv.Itoa(http.StatusRequestEntityTooLarge) + " " + http.StatusText(http.StatusRequestEntityTooLarge)
		jaError.Source.Pointer = "POST: api/beta2/maps/upload"
		jaError.Title = "size of uploaded file exceeds upload limit"
	case "7002":
		jaError.Status = strconv.Itoa(http.StatusUnsupportedMediaType) + " " + http.StatusText(http.StatusUnsupportedMediaType)
		jaError.Source.Pointer = "POST: api/beta2/maps/upload"
		jaError.Title = "insecure file rejected"
	default:
		jaError.Status = strconv.Itoa(http.StatusInternalServerError) + " " + http.StatusText(http.StatusInternalServerError)
		jaError.Source.Pointer = "unknown error code"
		jaError.Title = "unexpected program error"
	}

	jaError.ID = mapID
	jaError.Code = code
	jaError.Detail = detail
	pmErrorList.Errors = append(pmErrorList.Errors, jaError)
}
<|endoftext|>


<commit_before>package archive

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

// VCS is a struct that explains how to get the file list for a given
// VCS.
type VCS struct {
	Name string

	// Detect is a list of files/folders that if they exist, signal that
	// this VCS is the VCS in use.
	Detect []string

	// Files returns the files that are under version control for the
	// given path.
	Files func(path string) ([]string, error)
}

// VCSList is the list of VCS we recognize.
var VCSList = []*VCS{
	&VCS{
		Name:   "git",
		Detect: []string{".git/"},
		Files:  vcsFilesCmd("git", "ls-files"),
	},
	&VCS{
		Name:   "svn",
		Detect: []string{".svn/"},
		Files:  vcsFilesCmd("svn", "ls"),
	},
}

// vcsDetect detects the VCS that is used for path.
func vcsDetect(path string) (*VCS, error) {
	for _, v := range VCSList {
		for _, f := range v.Detect {
			check := filepath.Join(path, f)
			if _, err := os.Stat(check); err == nil {
				return v, nil
			}
		}
	}

	return nil, nil
}

// vcsFiles returns the files for the VCS directory path.
func vcsFiles(path string) ([]string, error) {
	vcs, err := vcsDetect(path)
	if err != nil {
		return nil, fmt.Errorf("Error detecting VCS: %s", err)
	}
	if vcs == nil {
		return nil, fmt.Errorf("No VCS found for path: %s", path)
	}

	return vcs.Files(path)
}

// vcsFilesCmd creates a Files-compatible function that reads the files
// by executing the command in the repository path and returning each
// line in stdout.
func vcsFilesCmd(args ...string) func(string) ([]string, error) {
	return func(path string) ([]string, error) {
		var stderr, stdout bytes.Buffer

		cmd := exec.Command(args[0], args[1:]...)
		cmd.Dir = path
		cmd.Stdout = &stdout
		cmd.Stderr = &stderr
		if err := cmd.Run(); err != nil {
			return nil, fmt.Errorf(
				"Error executing %s: %s",
				strings.Join(args, " "),
				err)
		}

		// Read each line of output as a path
		result := make([]string, 0, 100)
		scanner := bufio.NewScanner(&stdout)
		for scanner.Scan() {
			result = append(result, scanner.Text())
		}

		return result, nil
	}
}
<commit_msg>archive: look up directories for a git repo<commit_after>package archive

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

// VCS is a struct that explains how to get the file list for a given
// VCS.
type VCS struct {
	Name string

	// Detect is a list of files/folders that if they exist, signal that
	// this VCS is the VCS in use.
	Detect []string

	// Files returns the files that are under version control for the
	// given path.
	Files func(path string) ([]string, error)
}

// VCSList is the list of VCS we recognize.
var VCSList = []*VCS{
	&VCS{
		Name:   "git",
		Detect: []string{".git/"},
		Files:  vcsFilesCmd("git", "ls-files"),
	},
	&VCS{
		Name:   "svn",
		Detect: []string{".svn/"},
		Files:  vcsFilesCmd("svn", "ls"),
	},
}

// vcsDetect detects the VCS that is used for path.
func vcsDetect(path string) (*VCS, error) {
	for _, v := range VCSList {
		dir := path
		for {
			for _, f := range v.Detect {
				check := filepath.Join(dir, f)
				if _, err := os.Stat(check); err == nil {
					return v, nil
				}
			}

			lastDir := dir
			dir = filepath.Dir(dir)
			if dir == lastDir {
				break
			}
		}
	}

	return nil, nil
}

// vcsFiles returns the files for the VCS directory path.
func vcsFiles(path string) ([]string, error) {
	vcs, err := vcsDetect(path)
	if err != nil {
		return nil, fmt.Errorf("Error detecting VCS: %s", err)
	}
	if vcs == nil {
		return nil, fmt.Errorf("No VCS found for path: %s", path)
	}

	return vcs.Files(path)
}

// vcsFilesCmd creates a Files-compatible function that reads the files
// by executing the command in the repository path and returning each
// line in stdout.
func vcsFilesCmd(args ...string) func(string) ([]string, error) {
	return func(path string) ([]string, error) {
		var stderr, stdout bytes.Buffer

		cmd := exec.Command(args[0], args[1:]...)
		cmd.Dir = path
		cmd.Stdout = &stdout
		cmd.Stderr = &stderr
		if err := cmd.Run(); err != nil {
			return nil, fmt.Errorf(
				"Error executing %s: %s",
				strings.Join(args, " "),
				err)
		}

		// Read each line of output as a path
		result := make([]string, 0, 100)
		scanner := bufio.NewScanner(&stdout)
		for scanner.Scan() {
			result = append(result, scanner.Text())
		}

		return result, nil
	}
}
<|endoftext|>


<commit_before>// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package vgo

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"cmd/go/internal/base"
	"cmd/go/internal/dirhash"
	"cmd/go/internal/module"
)

var CmdVerify = &base.Command{
	UsageLine: "verify",
	Run:       runVerify,
	Short:     "verify downloaded modules against expected hashes",
	Long: `
Verify checks that the depdendencies of the current module,
which are stored in a local downloaded source cache,
have not been modified since being downloaded.

If all the modules are unmodified, verify prints

	all modules verified

and exits successfully (status 0). Otherwise, verify reports
which modules have been changed and exits with a non-zero status.
	`,
}

func runVerify(cmd *base.Command, args []string) {
	if Init(); !Enabled() {
		base.Fatalf("vgo verify: cannot use outside module")
	}
	if len(args) != 0 {
		// TODO: take arguments
		base.Fatalf("vgo verify: verify takes no arguments")
	}
	InitMod()
	ImportPaths([]string{"all"})

	ok := true
	for _, mod := range buildList[1:] {
		ok = verifyMod(mod) && ok
	}
	if ok {
		fmt.Printf("all modules verified\n")
	}
}

func verifyMod(mod module.Version) bool {
	ok := true
	zip := filepath.Join(srcV, "cache", mod.Path+"@"+mod.Version, "zip")
	_, zipErr := os.Stat(zip)
	dir := filepath.Join(srcV, mod.Path+"@"+mod.Version)
	_, dirErr := os.Stat(dir)
	data, err := ioutil.ReadFile(zip + "hash")
	if err != nil {
		if zipErr != nil && os.IsNotExist(zipErr) && dirErr != nil && os.IsNotExist(dirErr) {
			// Nothing downloaded yet. Nothing to verify.
			return true
		}
		base.Errorf("%s %s: missing ziphash: %v", mod.Path, mod.Version, err)
		return false
	}
	h := string(bytes.TrimSpace(data))

	if zipErr != nil && os.IsNotExist(zipErr) {
		// ok
	} else {
		hZ, err := dirhash.HashZip(zip, dirhash.DefaultHash)
		if err != nil {
			base.Errorf("%s %s: %v", mod.Path, mod.Version, err)
			return false
		} else if hZ != h {
			base.Errorf("%s %s: zip has been modified (%v)", mod.Path, mod.Version, zip)
			ok = false
		}
	}
	if dirErr != nil && os.IsNotExist(dirErr) {
		// ok
	} else {
		hD, err := dirhash.HashDir(dir, mod.Path+"@"+mod.Version, dirhash.DefaultHash)
		if err != nil {

			base.Errorf("%s %s: %v", mod.Path, mod.Version, err)
			return false
		}
		if hD != h {
			base.Errorf("%s %s: dir has been modified (%v)", mod.Path, mod.Version, dir)
			ok = false
		}
	}
	return ok
}
<commit_msg>cmd/go/internal/vgo: fix verify paths<commit_after>// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package vgo

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"cmd/go/internal/base"
	"cmd/go/internal/dirhash"
	"cmd/go/internal/module"
)

var CmdVerify = &base.Command{
	UsageLine: "verify",
	Run:       runVerify,
	Short:     "verify downloaded modules against expected hashes",
	Long: `
Verify checks that the depdendencies of the current module,
which are stored in a local downloaded source cache,
have not been modified since being downloaded.

If all the modules are unmodified, verify prints

	all modules verified

and exits successfully (status 0). Otherwise, verify reports
which modules have been changed and exits with a non-zero status.
	`,
}

func runVerify(cmd *base.Command, args []string) {
	if Init(); !Enabled() {
		base.Fatalf("vgo verify: cannot use outside module")
	}
	if len(args) != 0 {
		// TODO: take arguments
		base.Fatalf("vgo verify: verify takes no arguments")
	}
	InitMod()
	ImportPaths([]string{"all"})

	ok := true
	for _, mod := range buildList[1:] {
		ok = verifyMod(mod) && ok
	}
	if ok {
		fmt.Printf("all modules verified\n")
	}
}

func verifyMod(mod module.Version) bool {
	ok := true
	zip := filepath.Join(srcV, "cache", mod.Path, "/@v/", mod.Version+".zip")
	_, zipErr := os.Stat(zip)
	dir := filepath.Join(srcV, mod.Path+"@"+mod.Version)
	_, dirErr := os.Stat(dir)
	data, err := ioutil.ReadFile(zip + "hash")
	if err != nil {
		if zipErr != nil && os.IsNotExist(zipErr) && dirErr != nil && os.IsNotExist(dirErr) {
			// Nothing downloaded yet. Nothing to verify.
			return true
		}
		base.Errorf("%s %s: missing ziphash: %v", mod.Path, mod.Version, err)
		return false
	}
	h := string(bytes.TrimSpace(data))

	if zipErr != nil && os.IsNotExist(zipErr) {
		// ok
	} else {
		hZ, err := dirhash.HashZip(zip, dirhash.DefaultHash)
		if err != nil {
			base.Errorf("%s %s: %v", mod.Path, mod.Version, err)
			return false
		} else if hZ != h {
			base.Errorf("%s %s: zip has been modified (%v)", mod.Path, mod.Version, zip)
			ok = false
		}
	}
	if dirErr != nil && os.IsNotExist(dirErr) {
		// ok
	} else {
		hD, err := dirhash.HashDir(dir, mod.Path+"@"+mod.Version, dirhash.DefaultHash)
		if err != nil {

			base.Errorf("%s %s: %v", mod.Path, mod.Version, err)
			return false
		}
		if hD != h {
			base.Errorf("%s %s: dir has been modified (%v)", mod.Path, mod.Version, dir)
			ok = false
		}
	}
	return ok
}
<|endoftext|>


<commit_before>/*
Copyright 2019 Gravitational, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package events

// Event describes an audit log event.
type Event struct {
	// Name is the event name.
	Name string
	// Code is the unique event code.
	Code string
}

var (
	// UserLocalLogin is emitted when a local user successfully logs in.
	UserLocalLogin = Event{
		Name: UserLoginEvent,
		Code: UserLocalLoginCode,
	}
	// UserLocalLoginFailure is emitted when a local user login attempt fails.
	UserLocalLoginFailure = Event{
		Name: UserLoginEvent,
		Code: UserLocalLoginFailureCode,
	}
	// UserSSOLogin is emitted when an SSO user successfully logs in.
	UserSSOLogin = Event{
		Name: UserLoginEvent,
		Code: UserSSOLoginCode,
	}
	// UserSSOLoginFailure is emitted when an SSO user login attempt fails.
	UserSSOLoginFailure = Event{
		Name: UserLoginEvent,
		Code: UserSSOLoginFailureCode,
	}
	// UserUpdate is emitted when a user is updated.
	UserUpdate = Event{
		Name: UserUpdatedEvent,
		Code: UserUpdateCode,
	}
	// UserDelete is emitted when a user is deleted.
	UserDelete = Event{
		Name: UserDeleteEvent,
		Code: UserDeleteCode,
	}
	// UserCreate is emitted when a user is created.
	UserCreate = Event{
		Name: UserCreateEvent,
		Code: UserCreateCode,
	}
	// UserPasswordChange is emitted when a user changes their own password.
	UserPasswordChange = Event{
		Name: UserPasswordChangeEvent,
		Code: UserPasswordChangeCode,
	}
	// SessionStart is emitted when a user starts a new session.
	SessionStart = Event{
		Name: SessionStartEvent,
		Code: SessionStartCode,
	}
	// SessionJoin is emitted when a user joins the session.
	SessionJoin = Event{
		Name: SessionJoinEvent,
		Code: SessionJoinCode,
	}
	// TerminalResize is emitted when a user resizes the terminal.
	TerminalResize = Event{
		Name: ResizeEvent,
		Code: TerminalResizeCode,
	}
	// SessionLeave is emitted when a user leaves the session.
	SessionLeave = Event{
		Name: SessionLeaveEvent,
		Code: SessionLeaveCode,
	}
	// SessionEnd is emitted when a user ends the session.
	SessionEnd = Event{
		Name: SessionEndEvent,
		Code: SessionEndCode,
	}
	// SessionUpload is emitted after a session recording has been uploaded.
	SessionUpload = Event{
		Name: SessionUploadEvent,
		Code: SessionUploadCode,
	}
	// SessionData is emitted to report session data usage.
	SessionData = Event{
		Name: SessionDataEvent,
		Code: SessionDataCode,
	}
	// Subsystem is emitted when a user requests a new subsystem.
	Subsystem = Event{
		Name: SubsystemEvent,
		Code: SubsystemCode,
	}
	// SubsystemFailure is emitted when a user subsystem request fails.
	SubsystemFailure = Event{
		Name: SubsystemEvent,
		Code: SubsystemFailureCode,
	}
	// Exec is emitted when a user executes a command on a node.
	Exec = Event{
		Name: ExecEvent,
		Code: ExecCode,
	}
	// ExecFailure is emitted when a user command execution fails.
	ExecFailure = Event{
		Name: ExecEvent,
		Code: ExecFailureCode,
	}
	// PortForward is emitted when a user requests port forwarding.
	PortForward = Event{
		Name: PortForwardEvent,
		Code: PortForwardCode,
	}
	// PortForwardFailure is emitted when a port forward request fails.
	PortForwardFailure = Event{
		Name: PortForwardEvent,
		Code: PortForwardFailureCode,
	}
	// SCPDownload is emitted when a user downloads a file.
	SCPDownload = Event{
		Name: SCPEvent,
		Code: SCPDownloadCode,
	}
	// SCPDownloadFailure is emitted when a file download fails.
	SCPDownloadFailure = Event{
		Name: SCPEvent,
		Code: SCPDownloadFailureCode,
	}
	// SCPUpload is emitted when a user uploads a file.
	SCPUpload = Event{
		Name: SCPEvent,
		Code: SCPUploadCode,
	}
	// SCPUploadFailure is emitted when a file upload fails.
	SCPUploadFailure = Event{
		Name: SCPEvent,
		Code: SCPUploadFailureCode,
	}
	// ClientDisconnect is emitted when a user session is disconnected.
	ClientDisconnect = Event{
		Name: ClientDisconnectEvent,
		Code: ClientDisconnectCode,
	}
	// AuthAttemptFailure is emitted upon a failed authentication attempt.
	AuthAttemptFailure = Event{
		Name: AuthAttemptEvent,
		Code: AuthAttemptFailureCode,
	}
	// AccessRequestCreated is emitted when an access request is created.
	AccessRequestCreated = Event{
		Name: AccessRequestCreateEvent,
		Code: AccessRequestCreateCode,
	}
	AccessRequestUpdated = Event{
		Name: AccessRequestUpdateEvent,
		Code: AccessRequestUpdateCode,
	}
	// SessionCommand is emitted upon execution of a command when using enhanced
	// session recording.
	SessionCommand = Event{
		Name: SessionCommandEvent,
		Code: SessionCommandCode,
	}
	// SessionDisk is emitted upon open of a file when using enhanced session recording.
	SessionDisk = Event{
		Name: SessionDiskEvent,
		Code: SessionDiskCode,
	}
	// SessionNetwork is emitted when a network requests is is issued when
	// using enhanced session recording.
	SessionNetwork = Event{
		Name: SessionNetworkEvent,
		Code: SessionNetworkCode,
	}
	// ResetPasswordTokenCreated is emitted when token is created.
	ResetPasswordTokenCreated = Event{
		Name: ResetPasswordTokenCreateEvent,
		Code: ResetPasswordTokenCreateCode,
	}
	// RoleCreated is emitted when a role is created/updated.
	RoleCreated = Event{
		Name: RoleCreatedEvent,
		Code: RoleCreatedCode,
	}
	// RoleDeleted is emitted when a role is deleted.
	RoleDeleted = Event{
		Name: RoleDeletedEvent,
		Code: RoleDeletedCode,
	}
	// TrustedClusterCreate is emitted when a trusted cluster relationship is created.
	TrustedClusterCreate = Event{
		Name: TrustedClusterCreateEvent,
		Code: TrustedClusterCreateCode,
	}
	// TrustedClusterDelete is emitted when a trusted cluster is removed from the root cluster.
	TrustedClusterDelete = Event{
		Name: TrustedClusterDeleteEvent,
		Code: TrustedClusterDeleteCode,
	}
	// TrustedClusterTokenCreate is emitted when a new join
	// token for trusted cluster is created.
	TrustedClusterTokenCreate = Event{
		Name: TrustedClusterTokenCreateEvent,
		Code: TrustedClusterTokenCreateCode,
	}
	// GithubConnectorCreated is emitted when a Github connector is created/updated.
	GithubConnectorCreated = Event{
		Name: GithubConnectorCreatedEvent,
		Code: GithubConnectorCreatedCode,
	}
	// GithubConnectorDeleted is emitted when a Github connector is deleted.
	GithubConnectorDeleted = Event{
		Name: GithubConnectorDeletedEvent,
		Code: GithubConnectorDeletedCode,
	}
	// OIDCConnectorCreated is emitted when an OIDC connector is created/updated.
	OIDCConnectorCreated = Event{
		Name: OIDCConnectorCreatedEvent,
		Code: OIDCConnectorCreatedCode,
	}
	// OIDCConnectorDeleted is emitted when an OIDC connector is deleted.
	OIDCConnectorDeleted = Event{
		Name: OIDCConnectorDeletedEvent,
		Code: OIDCConnectorDeletedCode,
	}
	// SAMLConnectorCreated is emitted when a SAML connector is created/updated.
	SAMLConnectorCreated = Event{
		Name: SAMLConnectorCreatedEvent,
		Code: SAMLConnectorCreatedCode,
	}
	// SAMLConnectorDeleted is emitted when a SAML connector is deleted.
	SAMLConnectorDeleted = Event{
		Name: SAMLConnectorDeletedEvent,
		Code: SAMLConnectorDeletedCode,
	}
)

// There is no strict algorithm for picking an event code, however existing
// event codes are currently loosely categorized as follows:
//
//  * OSS event codes start with "T" and belong in this const block.
//
//  * Enterprise event codes start with "TE", and are in another const block.
//
//  * Related events are grouped starting with the same number.
//		eg: All user related events are grouped under 1xxx.
//
//  * Suffix code with one of these letters: I (info), W (warn), E (error).
const (
	// UserLocalLoginCode is the successful local user login event code.
	UserLocalLoginCode = "T1000I"
	// UserLocalLoginFailureCode is the unsuccessful local user login event code.
	UserLocalLoginFailureCode = "T1000W"
	// UserSSOLoginCode is the successful SSO user login event code.
	UserSSOLoginCode = "T1001I"
	// UserSSOLoginFailureCode is the unsuccessful SSO user login event code.
	UserSSOLoginFailureCode = "T1001W"
	// UserCreateCode is the user create event code.
	UserCreateCode = "T1002I"
	// UserUpdateCode is the user update event code.
	UserUpdateCode = "T1003I"
	// UserDeleteCode is the user delete event code.
	UserDeleteCode = "T1004I"
	// UserPasswordChangeCode is an event code for when user changes their own password.
	UserPasswordChangeCode = "T1005I"

	// SessionStartCode is the session start event code.
	SessionStartCode = "T2000I"
	// SessionJoinCode is the session join event code.
	SessionJoinCode = "T2001I"
	// TerminalResizeCode is the terminal resize event code.
	TerminalResizeCode = "T2002I"
	// SessionLeaveCode is the session leave event code.
	SessionLeaveCode = "T2003I"
	// SessionEndCode is the session end event code.
	SessionEndCode = "T2004I"
	// SessionUploadCode is the session upload event code.
	SessionUploadCode = "T2005I"
	// SessionDataCode is the session data event code.
	SessionDataCode = "T2006I"

	// SubsystemCode is the subsystem event code.
	SubsystemCode = "T3001I"
	// SubsystemFailureCode is the subsystem failure event code.
	SubsystemFailureCode = "T3001E"
	// ExecCode is the exec event code.
	ExecCode = "T3002I"
	// ExecFailureCode is the exec failure event code.
	ExecFailureCode = "T3002E"
	// PortForwardCode is the port forward event code.
	PortForwardCode = "T3003I"
	// PortForwardFailureCode is the port forward failure event code.
	PortForwardFailureCode = "T3003E"
	// SCPDownloadCode is the file download event code.
	SCPDownloadCode = "T3004I"
	// SCPDownloadFailureCode is the file download event failure code.
	SCPDownloadFailureCode = "T3004E"
	// SCPUploadCode is the file upload event code.
	SCPUploadCode = "T3005I"
	// SCPUploadFailureCode is the file upload failure event code.
	SCPUploadFailureCode = "T3005E"
	// ClientDisconnectCode is the client disconnect event code.
	ClientDisconnectCode = "T3006I"
	// AuthAttemptFailureCode is the auth attempt failure event code.
	AuthAttemptFailureCode = "T3007W"

	// SessionCommandCode is a session command code.
	SessionCommandCode = "T4000I"
	// SessionDiskCode is a session disk code.
	SessionDiskCode = "T4001I"
	// SessionNetworkCode is a session network code.
	SessionNetworkCode = "T4002I"

	// AccessRequestCreateCode is the the access request creation code.
	AccessRequestCreateCode = "T5000I"
	// AccessRequestUpdateCode is the access request state update code.
	AccessRequestUpdateCode = "T5001I"

	// ResetPasswordTokenCreateCode is the token create event code.
	ResetPasswordTokenCreateCode = "T6000I"

	// TrustedClusterCreateCode is the event code for creating a trusted cluster.
	TrustedClusterCreateCode = "T7000I"
	// TrustedClusterDeleteCode is the event code for removing a trusted cluster.
	TrustedClusterDeleteCode = "T7001I"
	// TrustedClusterTokenCreateCode is the event code for
	// creating new join token for a trusted cluster.
	TrustedClusterTokenCreateCode = "T7002I"

	// GithubConnectorCreatedCode is the Github connector created event code.
	GithubConnectorCreatedCode = "T8000I"
	// GithubConnectorDeletedCode is the Github connector deleted event code.
	GithubConnectorDeletedCode = "T8001I"
)

// Enterprise event codes.
const (
	// RoleCreatedCode is the role created event code.
	RoleCreatedCode = "TE1000I"
	// RoleDeletedCode is the role deleted event code.
	RoleDeletedCode = "TE1001I"

	// OIDCConnectorCreatedCode is the OIDC connector created event code.
	OIDCConnectorCreatedCode = "TE2000I"
	// OIDCConnectorDeletedCode is the OIDC connector deleted event code.
	OIDCConnectorDeletedCode = "TE2001I"

	// SAMLConnectorCreatedCode is the SAML connector created event code.
	SAMLConnectorCreatedCode = "TE3000I"
	// SAMLConnectorDeletedCode is the SAML connector deleted event code.
	SAMLConnectorDeletedCode = "TE3001I"
)
<commit_msg>Remove "E" suffixes from event codes (#3804)<commit_after>/*
Copyright 2019 Gravitational, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package events

// Event describes an audit log event.
type Event struct {
	// Name is the event name.
	Name string
	// Code is the unique event code.
	Code string
}

var (
	// UserLocalLogin is emitted when a local user successfully logs in.
	UserLocalLogin = Event{
		Name: UserLoginEvent,
		Code: UserLocalLoginCode,
	}
	// UserLocalLoginFailure is emitted when a local user login attempt fails.
	UserLocalLoginFailure = Event{
		Name: UserLoginEvent,
		Code: UserLocalLoginFailureCode,
	}
	// UserSSOLogin is emitted when an SSO user successfully logs in.
	UserSSOLogin = Event{
		Name: UserLoginEvent,
		Code: UserSSOLoginCode,
	}
	// UserSSOLoginFailure is emitted when an SSO user login attempt fails.
	UserSSOLoginFailure = Event{
		Name: UserLoginEvent,
		Code: UserSSOLoginFailureCode,
	}
	// UserUpdate is emitted when a user is updated.
	UserUpdate = Event{
		Name: UserUpdatedEvent,
		Code: UserUpdateCode,
	}
	// UserDelete is emitted when a user is deleted.
	UserDelete = Event{
		Name: UserDeleteEvent,
		Code: UserDeleteCode,
	}
	// UserCreate is emitted when a user is created.
	UserCreate = Event{
		Name: UserCreateEvent,
		Code: UserCreateCode,
	}
	// UserPasswordChange is emitted when a user changes their own password.
	UserPasswordChange = Event{
		Name: UserPasswordChangeEvent,
		Code: UserPasswordChangeCode,
	}
	// SessionStart is emitted when a user starts a new session.
	SessionStart = Event{
		Name: SessionStartEvent,
		Code: SessionStartCode,
	}
	// SessionJoin is emitted when a user joins the session.
	SessionJoin = Event{
		Name: SessionJoinEvent,
		Code: SessionJoinCode,
	}
	// TerminalResize is emitted when a user resizes the terminal.
	TerminalResize = Event{
		Name: ResizeEvent,
		Code: TerminalResizeCode,
	}
	// SessionLeave is emitted when a user leaves the session.
	SessionLeave = Event{
		Name: SessionLeaveEvent,
		Code: SessionLeaveCode,
	}
	// SessionEnd is emitted when a user ends the session.
	SessionEnd = Event{
		Name: SessionEndEvent,
		Code: SessionEndCode,
	}
	// SessionUpload is emitted after a session recording has been uploaded.
	SessionUpload = Event{
		Name: SessionUploadEvent,
		Code: SessionUploadCode,
	}
	// SessionData is emitted to report session data usage.
	SessionData = Event{
		Name: SessionDataEvent,
		Code: SessionDataCode,
	}
	// Subsystem is emitted when a user requests a new subsystem.
	Subsystem = Event{
		Name: SubsystemEvent,
		Code: SubsystemCode,
	}
	// SubsystemFailure is emitted when a user subsystem request fails.
	SubsystemFailure = Event{
		Name: SubsystemEvent,
		Code: SubsystemFailureCode,
	}
	// Exec is emitted when a user executes a command on a node.
	Exec = Event{
		Name: ExecEvent,
		Code: ExecCode,
	}
	// ExecFailure is emitted when a user command execution fails.
	ExecFailure = Event{
		Name: ExecEvent,
		Code: ExecFailureCode,
	}
	// PortForward is emitted when a user requests port forwarding.
	PortForward = Event{
		Name: PortForwardEvent,
		Code: PortForwardCode,
	}
	// PortForwardFailure is emitted when a port forward request fails.
	PortForwardFailure = Event{
		Name: PortForwardEvent,
		Code: PortForwardFailureCode,
	}
	// SCPDownload is emitted when a user downloads a file.
	SCPDownload = Event{
		Name: SCPEvent,
		Code: SCPDownloadCode,
	}
	// SCPDownloadFailure is emitted when a file download fails.
	SCPDownloadFailure = Event{
		Name: SCPEvent,
		Code: SCPDownloadFailureCode,
	}
	// SCPUpload is emitted when a user uploads a file.
	SCPUpload = Event{
		Name: SCPEvent,
		Code: SCPUploadCode,
	}
	// SCPUploadFailure is emitted when a file upload fails.
	SCPUploadFailure = Event{
		Name: SCPEvent,
		Code: SCPUploadFailureCode,
	}
	// ClientDisconnect is emitted when a user session is disconnected.
	ClientDisconnect = Event{
		Name: ClientDisconnectEvent,
		Code: ClientDisconnectCode,
	}
	// AuthAttemptFailure is emitted upon a failed authentication attempt.
	AuthAttemptFailure = Event{
		Name: AuthAttemptEvent,
		Code: AuthAttemptFailureCode,
	}
	// AccessRequestCreated is emitted when an access request is created.
	AccessRequestCreated = Event{
		Name: AccessRequestCreateEvent,
		Code: AccessRequestCreateCode,
	}
	AccessRequestUpdated = Event{
		Name: AccessRequestUpdateEvent,
		Code: AccessRequestUpdateCode,
	}
	// SessionCommand is emitted upon execution of a command when using enhanced
	// session recording.
	SessionCommand = Event{
		Name: SessionCommandEvent,
		Code: SessionCommandCode,
	}
	// SessionDisk is emitted upon open of a file when using enhanced session recording.
	SessionDisk = Event{
		Name: SessionDiskEvent,
		Code: SessionDiskCode,
	}
	// SessionNetwork is emitted when a network requests is is issued when
	// using enhanced session recording.
	SessionNetwork = Event{
		Name: SessionNetworkEvent,
		Code: SessionNetworkCode,
	}
	// ResetPasswordTokenCreated is emitted when token is created.
	ResetPasswordTokenCreated = Event{
		Name: ResetPasswordTokenCreateEvent,
		Code: ResetPasswordTokenCreateCode,
	}
	// RoleCreated is emitted when a role is created/updated.
	RoleCreated = Event{
		Name: RoleCreatedEvent,
		Code: RoleCreatedCode,
	}
	// RoleDeleted is emitted when a role is deleted.
	RoleDeleted = Event{
		Name: RoleDeletedEvent,
		Code: RoleDeletedCode,
	}
	// TrustedClusterCreate is emitted when a trusted cluster relationship is created.
	TrustedClusterCreate = Event{
		Name: TrustedClusterCreateEvent,
		Code: TrustedClusterCreateCode,
	}
	// TrustedClusterDelete is emitted when a trusted cluster is removed from the root cluster.
	TrustedClusterDelete = Event{
		Name: TrustedClusterDeleteEvent,
		Code: TrustedClusterDeleteCode,
	}
	// TrustedClusterTokenCreate is emitted when a new join
	// token for trusted cluster is created.
	TrustedClusterTokenCreate = Event{
		Name: TrustedClusterTokenCreateEvent,
		Code: TrustedClusterTokenCreateCode,
	}
	// GithubConnectorCreated is emitted when a Github connector is created/updated.
	GithubConnectorCreated = Event{
		Name: GithubConnectorCreatedEvent,
		Code: GithubConnectorCreatedCode,
	}
	// GithubConnectorDeleted is emitted when a Github connector is deleted.
	GithubConnectorDeleted = Event{
		Name: GithubConnectorDeletedEvent,
		Code: GithubConnectorDeletedCode,
	}
	// OIDCConnectorCreated is emitted when an OIDC connector is created/updated.
	OIDCConnectorCreated = Event{
		Name: OIDCConnectorCreatedEvent,
		Code: OIDCConnectorCreatedCode,
	}
	// OIDCConnectorDeleted is emitted when an OIDC connector is deleted.
	OIDCConnectorDeleted = Event{
		Name: OIDCConnectorDeletedEvent,
		Code: OIDCConnectorDeletedCode,
	}
	// SAMLConnectorCreated is emitted when a SAML connector is created/updated.
	SAMLConnectorCreated = Event{
		Name: SAMLConnectorCreatedEvent,
		Code: SAMLConnectorCreatedCode,
	}
	// SAMLConnectorDeleted is emitted when a SAML connector is deleted.
	SAMLConnectorDeleted = Event{
		Name: SAMLConnectorDeletedEvent,
		Code: SAMLConnectorDeletedCode,
	}
)

// There is no strict algorithm for picking an event code, however existing
// event codes are currently loosely categorized as follows:
//
//  * Teleport event codes start with "T" and belong in this const block.
//
//  * Related events are grouped starting with the same number.
//		eg: All user related events are grouped under 1xxx.
//
//  * Suffix code with one of these letters: I (info), W (warn), E (error).
const (
	// UserLocalLoginCode is the successful local user login event code.
	UserLocalLoginCode = "T1000I"
	// UserLocalLoginFailureCode is the unsuccessful local user login event code.
	UserLocalLoginFailureCode = "T1000W"
	// UserSSOLoginCode is the successful SSO user login event code.
	UserSSOLoginCode = "T1001I"
	// UserSSOLoginFailureCode is the unsuccessful SSO user login event code.
	UserSSOLoginFailureCode = "T1001W"
	// UserCreateCode is the user create event code.
	UserCreateCode = "T1002I"
	// UserUpdateCode is the user update event code.
	UserUpdateCode = "T1003I"
	// UserDeleteCode is the user delete event code.
	UserDeleteCode = "T1004I"
	// UserPasswordChangeCode is an event code for when user changes their own password.
	UserPasswordChangeCode = "T1005I"

	// SessionStartCode is the session start event code.
	SessionStartCode = "T2000I"
	// SessionJoinCode is the session join event code.
	SessionJoinCode = "T2001I"
	// TerminalResizeCode is the terminal resize event code.
	TerminalResizeCode = "T2002I"
	// SessionLeaveCode is the session leave event code.
	SessionLeaveCode = "T2003I"
	// SessionEndCode is the session end event code.
	SessionEndCode = "T2004I"
	// SessionUploadCode is the session upload event code.
	SessionUploadCode = "T2005I"
	// SessionDataCode is the session data event code.
	SessionDataCode = "T2006I"

	// SubsystemCode is the subsystem event code.
	SubsystemCode = "T3001I"
	// SubsystemFailureCode is the subsystem failure event code.
	SubsystemFailureCode = "T3001E"
	// ExecCode is the exec event code.
	ExecCode = "T3002I"
	// ExecFailureCode is the exec failure event code.
	ExecFailureCode = "T3002E"
	// PortForwardCode is the port forward event code.
	PortForwardCode = "T3003I"
	// PortForwardFailureCode is the port forward failure event code.
	PortForwardFailureCode = "T3003E"
	// SCPDownloadCode is the file download event code.
	SCPDownloadCode = "T3004I"
	// SCPDownloadFailureCode is the file download event failure code.
	SCPDownloadFailureCode = "T3004E"
	// SCPUploadCode is the file upload event code.
	SCPUploadCode = "T3005I"
	// SCPUploadFailureCode is the file upload failure event code.
	SCPUploadFailureCode = "T3005E"
	// ClientDisconnectCode is the client disconnect event code.
	ClientDisconnectCode = "T3006I"
	// AuthAttemptFailureCode is the auth attempt failure event code.
	AuthAttemptFailureCode = "T3007W"

	// SessionCommandCode is a session command code.
	SessionCommandCode = "T4000I"
	// SessionDiskCode is a session disk code.
	SessionDiskCode = "T4001I"
	// SessionNetworkCode is a session network code.
	SessionNetworkCode = "T4002I"

	// AccessRequestCreateCode is the the access request creation code.
	AccessRequestCreateCode = "T5000I"
	// AccessRequestUpdateCode is the access request state update code.
	AccessRequestUpdateCode = "T5001I"

	// ResetPasswordTokenCreateCode is the token create event code.
	ResetPasswordTokenCreateCode = "T6000I"

	// TrustedClusterCreateCode is the event code for creating a trusted cluster.
	TrustedClusterCreateCode = "T7000I"
	// TrustedClusterDeleteCode is the event code for removing a trusted cluster.
	TrustedClusterDeleteCode = "T7001I"
	// TrustedClusterTokenCreateCode is the event code for
	// creating new join token for a trusted cluster.
	TrustedClusterTokenCreateCode = "T7002I"

	// GithubConnectorCreatedCode is the Github connector created event code.
	GithubConnectorCreatedCode = "T8000I"
	// GithubConnectorDeletedCode is the Github connector deleted event code.
	GithubConnectorDeletedCode = "T8001I"

	// OIDCConnectorCreatedCode is the OIDC connector created event code.
	OIDCConnectorCreatedCode = "T8100I"
	// OIDCConnectorDeletedCode is the OIDC connector deleted event code.
	OIDCConnectorDeletedCode = "T8101I"

	// SAMLConnectorCreatedCode is the SAML connector created event code.
	SAMLConnectorCreatedCode = "T8200I"
	// SAMLConnectorDeletedCode is the SAML connector deleted event code.
	SAMLConnectorDeletedCode = "T8201I"

	// RoleCreatedCode is the role created event code.
	RoleCreatedCode = "T9000I"
	// RoleDeletedCode is the role deleted event code.
	RoleDeletedCode = "T9001I"
)
<|endoftext|>


<commit_before>// Copyright (c) Alex Ellis 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

package proxy

import (
	"net/http"

	"testing"

	"regexp"

	"github.com/openfaas/faas-cli/test"
)

func Test_DeployFunction(t *testing.T) {
	s := test.MockHttpServerStatus(
		t,
		http.StatusOK, // DeleteFunction
		http.StatusOK, // DeployFunction
	)
	defer s.Close()

	stdout := test.CaptureStdout(func() {
		DeployFunction(
			"fproces",
			s.URL,
			"function",
			"image",
			"language",
			true,
			nil,
			"network",
			[]string{},
			false,
			[]string{},
		)
	})

	r := regexp.MustCompile(`(?m:Deployed.)`)
	if !r.MatchString(stdout) {
		t.Fatalf("Output not matched: %s", stdout)
	}
}

func Test_DeployFunction_Not2xx(t *testing.T) {
	s := test.MockHttpServerStatus(
		t,
		http.StatusOK,       // DeleteFunction
		http.StatusNotFound, // DeployFunction
	)
	defer s.Close()

	stdout := test.CaptureStdout(func() {
		DeployFunction(
			"fproces",
			s.URL,
			"function",
			"image",
			"language",
			true,
			nil,
			"network",
			[]string{},
			false,
			[]string{},
		)
	})

	r := regexp.MustCompile(`(?m:Unexpected status: 404)`)
	if !r.MatchString(stdout) {
		t.Fatalf("Output not matched: %s", stdout)
	}
}
<commit_msg>Add label test fixup<commit_after>// Copyright (c) Alex Ellis 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

package proxy

import (
	"net/http"

	"testing"

	"regexp"

	"github.com/openfaas/faas-cli/test"
)

func Test_DeployFunction(t *testing.T) {
	s := test.MockHttpServerStatus(
		t,
		http.StatusOK, // DeleteFunction
		http.StatusOK, // DeployFunction
	)
	defer s.Close()

	stdout := test.CaptureStdout(func() {
		DeployFunction(
			"fproces",
			s.URL,
			"function",
			"image",
			"language",
			true,
			nil,
			"network",
			[]string{},
			false,
			[]string{},
			map[string]string{},
		)
	})

	r := regexp.MustCompile(`(?m:Deployed.)`)
	if !r.MatchString(stdout) {
		t.Fatalf("Output not matched: %s", stdout)
	}
}

func Test_DeployFunction_Not2xx(t *testing.T) {
	s := test.MockHttpServerStatus(
		t,
		http.StatusOK,       // DeleteFunction
		http.StatusNotFound, // DeployFunction
	)
	defer s.Close()

	stdout := test.CaptureStdout(func() {
		DeployFunction(
			"fproces",
			s.URL,
			"function",
			"image",
			"language",
			true,
			nil,
			"network",
			[]string{},
			false,
			[]string{},
			map[string]string{},
		)
	})

	r := regexp.MustCompile(`(?m:Unexpected status: 404)`)
	if !r.MatchString(stdout) {
		t.Fatalf("Output not matched: %s", stdout)
	}
}
<|endoftext|>


<commit_before>package bots

import (
	"fmt"
	//"net/http"
	"strings"
	"bitbucket.com/debtstracker/gae_app/debtstracker/emoji"
	"github.com/strongo/measurement-protocol"
	"net/url"
	"github.com/pkg/errors"
)

type TypeCommands struct {
	all    []Command
	byCode map[string]Command
}

type WebhooksRouter struct {
	commandsByType map[WebhookInputType]TypeCommands
}

func NewWebhookRouter(commandsByType map[WebhookInputType][]Command) *WebhooksRouter {
	r := &WebhooksRouter{commandsByType: make(map[WebhookInputType]TypeCommands, len(commandsByType))}
	for commandType, commands := range commandsByType {
		typeCommands := TypeCommands{byCode: make(map[string]Command, len(commands))}
		r.commandsByType[commandType] = typeCommands
		for _, command := range commands {
			if command.Code == "" {
				panic(fmt.Sprintf("Command %v is missing required property Code", command))
			}
			if _, ok := typeCommands.byCode[command.Code]; ok {
				panic(fmt.Sprintf("Command with code '%v' defined multiple times", command.Code))
			}
			typeCommands.all = append(typeCommands.all, command)
			typeCommands.byCode[command.Code] = command
		}
	}
	return r
}

func matchCallbackCommands (whc WebhookContext, typeCommands TypeCommands) (matchedCommand *Command, callbackUrl *url.URL, err error) {
	if len(typeCommands.all) > 0 {
		callbackData := whc.InputCallbackQuery().GetData()
		callbackUrl, err = url.Parse(callbackData)
		if err != nil {
			whc.Logger().Errorf("Failed to parse callback data to URL: %v", err.Error())
		} else {
			callbackPath := callbackUrl.Path
			if command, ok := typeCommands.byCode[callbackPath]; ok {
				return &command, callbackUrl, nil
			}
		}
	}
	return nil, callbackUrl, err
}

func (r *WebhooksRouter) matchCommands(whc WebhookContext, parentPath string, commands []Command) (matchedCommand *Command) {
	inputType := whc.InputType()

	var awaitingReplyCommand Command

	logger := whc.Logger()

	if inputType != WebhookInputMessage && inputType != WebhookInputUnknown && len(commands) == 1 {
		command := commands[0]
		if command.InputType == inputType { //TODO: it should refactored - we match 1st by type for now
			logger.Debugf("%v matched my command.InputType", command.Code)
			matchedCommand = &command
			return
		} else {
			logger.Warningf("inputType: %v, commandInputType: %v, commandCode: %v", WebhookInputTypeNames[inputType], WebhookInputTypeNames[command.InputType], command.Code)
		}
	}

	messageText := whc.MessageText()
	messageTextLowerCase := strings.ToLower(messageText)

	awaitingReplyTo := whc.ChatEntity().GetAwaitingReplyTo()
	//logger.Debugf("awaitingReplyTo: %v", awaitingReplyTo)

	var awaitingReplyCommandFound bool

	for _, command := range commands {
		for _, commandName := range command.Commands {
			if messageTextLowerCase == commandName || strings.HasPrefix(messageTextLowerCase, commandName+" ") {
				logger.Debugf("command(code=%v) matched my command.commands", command.Code)
				matchedCommand = &command
				return
			}
		}
	}

	for _, command := range commands {

		if !awaitingReplyCommandFound && awaitingReplyTo != "" {
			awaitingReplyPrefix := strings.TrimLeft(parentPath + AWAITING_REPLY_TO_PATH_SEPARATOR + command.Code, AWAITING_REPLY_TO_PATH_SEPARATOR)

			if strings.HasPrefix(awaitingReplyTo, awaitingReplyPrefix) {
				//logger.Debugf("[%v] is a prefix for [%v]", awaitingReplyPrefix, awaitingReplyTo)
				//logger.Debugf("awaitingReplyCommand: %v", command.Code)
				if matchedCommand = r.matchCommands(whc, awaitingReplyPrefix, command.Replies); matchedCommand != nil {
					logger.Debugf("%v matched my command.replies", command.Code)
					awaitingReplyCommand = *matchedCommand
					awaitingReplyCommandFound = true
					continue
				}
			} else {
				logger.Debugf("[%v] is NOT a prefix for [%v]", awaitingReplyPrefix, awaitingReplyTo)
			}
		}

		if command.ExactMatch != "" && (command.ExactMatch == messageText || whc.TranslateNoWarning(command.ExactMatch) == messageText) {
			logger.Debugf("%v matched my command.exactMatch", command.Code)
			matchedCommand = &command
			return
		}

		if command.DefaultTitle(whc) == messageText {
			logger.Debugf("%v matched my command.FullName()", command.Code)
			matchedCommand = &command
			return
		} else {
			logger.Debugf("command(code=%v).Title(whc): %v", command.Code, command.DefaultTitle(whc))
		}
		if command.Matcher != nil && command.Matcher(command, whc) {
			logger.Debugf("%v matched my command.matcher()", command.Code)
			matchedCommand = &command
			return
		}

		if !awaitingReplyCommandFound {
			awaitingReplyToPath := AwaitingReplyToPath(awaitingReplyTo)
			if awaitingReplyToPath == command.Code || strings.HasSuffix(awaitingReplyToPath, AWAITING_REPLY_TO_PATH_SEPARATOR + command.Code) {
				awaitingReplyCommand = command
				switch {
				case awaitingReplyToPath == command.Code:
					logger.Debugf("%v matched by: awaitingReplyToPath == command.Code", command.Code)
				case strings.HasSuffix(awaitingReplyToPath, AWAITING_REPLY_TO_PATH_SEPARATOR + command.Code):
					logger.Debugf("%v matched by: strings.HasSuffix(awaitingReplyToPath, AWAITING_REPLY_TO_PATH_SEPARATOR + command.Code)", command.Code)
				}
				awaitingReplyCommandFound = true
				continue
			}
		}
		logger.Debugf("%v - not matched, matchedCommand: %v", command.Code, matchedCommand)
	}
	if awaitingReplyCommandFound {
		matchedCommand = &awaitingReplyCommand
		logger.Debugf("Assign awaitingReplyCommand to matchedCommand: %v", awaitingReplyCommand.Code)
	} else {
		matchedCommand = nil
		logger.Debugf("Cleaning up matchedCommand: %v", matchedCommand)
	}

	logger.Debugf("matchedCommand: %v", matchedCommand)
	return
}

func (r *WebhooksRouter) DispatchInlineQuery(responder WebhookResponder) {

}

func (r *WebhooksRouter) Dispatch(responder WebhookResponder, whc WebhookContext) {
	logger := whc.Logger()
	inputType := whc.InputType()
	switch inputType {
	case WebhookInputMessage:
		logger.Debugf("message text: [%v]", whc.InputMessage().Text())
	case WebhookInputInlineQuery:
		logger.Debugf("inline query: [%v]", whc.InputInlineQuery().GetQuery())
	case WebhookInputCallbackQuery:
		logger.Debugf("callback data: [%v]", whc.InputCallbackQuery().GetData())
	case WebhookInputChosenInlineResult:
		chosenResult := whc.InputChosenInlineResult()
		logger.Debugf("ChosenInlineResult: ResultID=[%v], InlineMessageID=[%v], Query=[%v]", chosenResult.GetResultID(), chosenResult.GetInlineMessageID(), chosenResult.GetQuery())
	}

	if typeCommands, found := r.commandsByType[inputType]; found {
		var matchedCommand *Command
		var commandAction CommandAction
		var err error
		var m MessageFromBot
		switch inputType {
		case WebhookInputCallbackQuery:
			var callbackUrl *url.URL
			matchedCommand, callbackUrl, err = matchCallbackCommands(whc, typeCommands)
			if matchedCommand.Code == "" {
				err = errors.New(fmt.Sprintf("matchedCommand(%T: %v).Code is empty string", matchedCommand, matchedCommand))
			}
			if matchedCommand.CallbackAction == nil {
				err = errors.New(fmt.Sprintf("matchedCommand(%v).CallbackAction == nil", matchedCommand.Code))
			}
			commandAction = func(whc WebhookContext) (MessageFromBot, error) {
				return matchedCommand.CallbackAction(whc, callbackUrl)
			}
		default:
			matchedCommand = r.matchCommands(whc, "", typeCommands.all)
			if matchedCommand != nil {
				commandAction = matchedCommand.Action
			}
		}
		if err != nil {
			processCommandResponse(matchedCommand, responder, whc, m, err)
			return
		}

		if matchedCommand == nil {
			m = MessageFromBot{Text: whc.Translate(MESSAGE_TEXT_I_DID_NOT_UNDERSTAND_THE_COMMAND), Format: MessageFormatHTML}
			chatEntity := whc.ChatEntity()
			if chatEntity != nil && chatEntity.GetAwaitingReplyTo() != "" {
				m.Text += fmt.Sprintf("\n\nAwaitingReplyTo: %v", chatEntity.GetAwaitingReplyTo())
			}
			logger.Infof("No command found for the message: %v", whc.MessageText())
			processCommandResponse(matchedCommand, responder, whc, m, nil)
		} else {
			logger.Infof("Matched to: %v", matchedCommand.Code) //runtime.FuncForPC(reflect.ValueOf(command.Action).Pointer()).Name()
			m, err := commandAction(whc)
			processCommandResponse(matchedCommand, responder, whc, m, err)
		}
	} else {
		logger.Infof("No commands found byt input type %v=%v", inputType, WebhookInputTypeNames[inputType])
	}
}

func processCommandResponse(matchedCommand *Command, responder WebhookResponder, whc WebhookContext, m MessageFromBot, err error) {
	logger := whc.Logger()
	gaMeasurement := whc.GaMeasurement()
	//gam.GeographicalOverride()

	if err == nil {
		logger.Infof("processCommandResponse(): Bot response message: %v", m)
		if _, err = responder.SendMessage(m, BotApiSendMessageOverResponse); err != nil {
			logger.Errorf("Failed to send message to Telegram\n\tError: %v\n\tMessage text: %v", err, m.Text) //TODO: Decide how do we handle it
		}
		if matchedCommand != nil {
			if gaMeasurement != nil {
				chatEntity := whc.ChatEntity()
				gaHostName := fmt.Sprintf("%v.debtstracker.io", strings.ToLower(whc.BotPlatform().Id()))
				pathPrefix := "bot/"
				var pageview measurement.Pageview
				if chatEntity != nil {
					path := chatEntity.GetAwaitingReplyTo()
					if path == "" {
						path = matchedCommand.Code
					}
					pageview = measurement.NewPageviewWithDocumentHost(gaHostName, pathPrefix + path, matchedCommand.Title)
				} else {
					pageview = measurement.NewPageviewWithDocumentHost(gaHostName, pathPrefix + WebhookInputTypeNames[whc.InputType()], matchedCommand.Title)
				}
				pageview.Common = whc.GaCommon()

				go func() {
					err := gaMeasurement.Queue(pageview)
					if err != nil {
						logger.Warningf("Failed to send page view to GA: %v", err)
					}
				}()
			}
		}
	} else {
		logger.Errorf(err.Error())
		if gaMeasurement != nil {
			exceptionMessage := measurement.NewException(err.Error(), false)
			exceptionMessage.Common = whc.GaCommon()
			go func(){
				err = gaMeasurement.Queue(exceptionMessage)
				if err != nil {
					logger.Warningf("Failed to send page view to GA: %v", err)
				}
			}()
		}
		if whc.InputType() == WebhookInputMessage {
			// Todo: Try to get chat ID from user?
			_, respErr := responder.SendMessage(whc.NewMessage(whc.Translate(MESSAGE_TEXT_OOPS_SOMETHING_WENT_WRONG) + "\n\n" + emoji.ERROR_ICON + fmt.Sprintf(" Server error - failed to process message: %v", err)), BotApiSendMessageOverResponse)
			if respErr != nil {
				logger.Errorf("Failed to report to user a server error: %v", respErr)
			}
		}
	}
}
<commit_msg>Dispatch() refactoring<commit_after>package bots

import (
	"fmt"
	//"net/http"
	"strings"
	"bitbucket.com/debtstracker/gae_app/debtstracker/emoji"
	"github.com/strongo/measurement-protocol"
	"net/url"
	"github.com/pkg/errors"
)

type TypeCommands struct {
	all    []Command
	byCode map[string]Command
}

type WebhooksRouter struct {
	commandsByType map[WebhookInputType]TypeCommands
}

func NewWebhookRouter(commandsByType map[WebhookInputType][]Command) *WebhooksRouter {
	r := &WebhooksRouter{commandsByType: make(map[WebhookInputType]TypeCommands, len(commandsByType))}
	for commandType, commands := range commandsByType {
		commandsCount := len(commands)
		typeCommands := TypeCommands{
			byCode: make(map[string]Command, commandsCount),
			all: make([]Command, commandsCount, commandsCount),
		}
		for _, command := range commands {
			if command.Code == "" {
				panic(fmt.Sprintf("Command %v is missing required property Code", command))
			}
			if _, ok := typeCommands.byCode[command.Code]; ok {
				panic(fmt.Sprintf("Command with code '%v' defined multiple times", command.Code))
			}
			typeCommands.all = append(typeCommands.all, command)
			typeCommands.byCode[command.Code] = command
		}
		r.commandsByType[commandType] = typeCommands
	}
	return r
}

func matchCallbackCommands (whc WebhookContext, typeCommands TypeCommands) (matchedCommand *Command, callbackUrl *url.URL, err error) {
	if len(typeCommands.all) > 0 {
		callbackData := whc.InputCallbackQuery().GetData()
		callbackUrl, err = url.Parse(callbackData)
		if err != nil {
			whc.Logger().Errorf("Failed to parse callback data to URL: %v", err.Error())
		} else {
			callbackPath := callbackUrl.Path
			if command, ok := typeCommands.byCode[callbackPath]; ok {
				return &command, callbackUrl, nil
			}
		}
	}
	return nil, callbackUrl, err
}

func (r *WebhooksRouter) matchFirstCommand(commands []Command) (matchedCommand *Command) {
	matchedCommand = &commands[0]
	return
}

func (r *WebhooksRouter) matchMessageCommands(whc WebhookContext, parentPath string, commands []Command) (matchedCommand *Command) {
	var awaitingReplyCommand Command

	logger := whc.Logger()

	messageText := whc.MessageText()
	messageTextLowerCase := strings.ToLower(messageText)

	awaitingReplyTo := whc.ChatEntity().GetAwaitingReplyTo()
	//logger.Debugf("awaitingReplyTo: %v", awaitingReplyTo)

	var awaitingReplyCommandFound bool

	for _, command := range commands {
		for _, commandName := range command.Commands {
			if messageTextLowerCase == commandName || strings.HasPrefix(messageTextLowerCase, commandName+" ") {
				logger.Debugf("command(code=%v) matched my command.commands", command.Code)
				matchedCommand = &command
				return
			}
		}
	}

	for _, command := range commands {
		if !awaitingReplyCommandFound && awaitingReplyTo != "" {
			awaitingReplyPrefix := strings.TrimLeft(parentPath + AWAITING_REPLY_TO_PATH_SEPARATOR + command.Code, AWAITING_REPLY_TO_PATH_SEPARATOR)

			if strings.HasPrefix(awaitingReplyTo, awaitingReplyPrefix) {
				//logger.Debugf("[%v] is a prefix for [%v]", awaitingReplyPrefix, awaitingReplyTo)
				//logger.Debugf("awaitingReplyCommand: %v", command.Code)
				if matchedCommand = r.matchMessageCommands(whc, awaitingReplyPrefix, command.Replies); matchedCommand != nil {
					logger.Debugf("%v matched my command.replies", command.Code)
					awaitingReplyCommand = *matchedCommand
					awaitingReplyCommandFound = true
					continue
				}
			} else {
				logger.Debugf("[%v] is NOT a prefix for [%v]", awaitingReplyPrefix, awaitingReplyTo)
			}
		}

		if command.ExactMatch != "" && (command.ExactMatch == messageText || whc.TranslateNoWarning(command.ExactMatch) == messageText) {
			logger.Debugf("%v matched my command.exactMatch", command.Code)
			matchedCommand = &command
			return
		}

		if command.DefaultTitle(whc) == messageText {
			logger.Debugf("%v matched my command.FullName()", command.Code)
			matchedCommand = &command
			return
		} else {
			logger.Debugf("command(code=%v).Title(whc): %v", command.Code, command.DefaultTitle(whc))
		}
		if command.Matcher != nil && command.Matcher(command, whc) {
			logger.Debugf("%v matched my command.matcher()", command.Code)
			matchedCommand = &command
			return
		}

		if !awaitingReplyCommandFound {
			awaitingReplyToPath := AwaitingReplyToPath(awaitingReplyTo)
			if awaitingReplyToPath == command.Code || strings.HasSuffix(awaitingReplyToPath, AWAITING_REPLY_TO_PATH_SEPARATOR + command.Code) {
				awaitingReplyCommand = command
				switch {
				case awaitingReplyToPath == command.Code:
					logger.Debugf("%v matched by: awaitingReplyToPath == command.Code", command.Code)
				case strings.HasSuffix(awaitingReplyToPath, AWAITING_REPLY_TO_PATH_SEPARATOR + command.Code):
					logger.Debugf("%v matched by: strings.HasSuffix(awaitingReplyToPath, AWAITING_REPLY_TO_PATH_SEPARATOR + command.Code)", command.Code)
				}
				awaitingReplyCommandFound = true
				continue
			}
		}
		logger.Debugf("%v - not matched, matchedCommand: %v", command.Code, matchedCommand)
	}
	if awaitingReplyCommandFound {
		matchedCommand = &awaitingReplyCommand
		logger.Debugf("Assign awaitingReplyCommand to matchedCommand: %v", awaitingReplyCommand.Code)
	} else {
		matchedCommand = nil
		logger.Debugf("Cleaning up matchedCommand: %v", matchedCommand)
	}

	logger.Debugf("matchedCommand: %v", matchedCommand)
	return
}

func (r *WebhooksRouter) DispatchInlineQuery(responder WebhookResponder) {

}

func (r *WebhooksRouter) Dispatch(responder WebhookResponder, whc WebhookContext) {
	logger := whc.Logger()
	inputType := whc.InputType()

	logMessage := fmt.Sprintf("WebhooksRouter.Dispatch(): inputType: %v=%v, ", inputType, WebhookInputTypeNames[inputType])
	switch inputType {
	case WebhookInputMessage:
		logMessage += fmt.Sprintf("message text: [%v]", whc.InputMessage().Text())
	case WebhookInputInlineQuery:
		logMessage += fmt.Sprintf("inline query: [%v]", whc.InputInlineQuery().GetQuery())
	case WebhookInputCallbackQuery:
		logMessage += fmt.Sprintf("callback data: [%v]", whc.InputCallbackQuery().GetData())
	case WebhookInputChosenInlineResult:
		chosenResult := whc.InputChosenInlineResult()
		logMessage += fmt.Sprintf("ChosenInlineResult: ResultID=[%v], InlineMessageID=[%v], Query=[%v]", chosenResult.GetResultID(), chosenResult.GetInlineMessageID(), chosenResult.GetQuery())
	}

	if typeCommands, found := r.commandsByType[inputType]; !found {
		logMessage += "no commands to match"
		logger.Warningf(logMessage)
		err := errors.New(logMessage)
		var m MessageFromBot
		processCommandResponse(nil, responder, whc, m, err)
	} else {
		logMessage += fmt.Sprintf(", len(commandsToMatch): %v", len(typeCommands.all))
		logger.Debugf(logMessage)

		var matchedCommand *Command
		var commandAction CommandAction
		var err error
		var m MessageFromBot
		switch inputType {
		case WebhookInputCallbackQuery:
			var callbackUrl *url.URL
			matchedCommand, callbackUrl, err = matchCallbackCommands(whc, typeCommands)
			if err == nil {
				if matchedCommand.Code == "" {
					err = errors.New(fmt.Sprintf("matchedCommand(%T: %v).Code is empty string", matchedCommand, matchedCommand))
				} else {
					if matchedCommand.CallbackAction == nil {
						err = errors.New(fmt.Sprintf("matchedCommand(%v).CallbackAction == nil", matchedCommand.Code))
					} else {
						commandAction = func(whc WebhookContext) (MessageFromBot, error) {
							return matchedCommand.CallbackAction(whc, callbackUrl)
						}
					}
				}
			}
		case WebhookInputMessage:
			matchedCommand = r.matchMessageCommands(whc, "", typeCommands.all)
			if matchedCommand != nil {
				commandAction = matchedCommand.Action
			}
		case WebhookInputUnknown:
			panic("Unknown input type")
		default:
			matchedCommand = r.matchFirstCommand(typeCommands.all)
		}
		if err != nil {
			processCommandResponse(matchedCommand, responder, whc, m, err)
			return
		}

		if matchedCommand == nil {
			m = MessageFromBot{Text: whc.Translate(MESSAGE_TEXT_I_DID_NOT_UNDERSTAND_THE_COMMAND), Format: MessageFormatHTML}
			chatEntity := whc.ChatEntity()
			if chatEntity != nil && chatEntity.GetAwaitingReplyTo() != "" {
				m.Text += fmt.Sprintf("\n\nAwaitingReplyTo: %v", chatEntity.GetAwaitingReplyTo())
			}
			logger.Infof("No command found for the message: %v", whc.MessageText())
			processCommandResponse(matchedCommand, responder, whc, m, nil)
		} else {
			logger.Infof("Matched to: %v", matchedCommand.Code) //runtime.FuncForPC(reflect.ValueOf(command.Action).Pointer()).Name()
			m, err := commandAction(whc)
			processCommandResponse(matchedCommand, responder, whc, m, err)
		}
	}
}

func processCommandResponse(matchedCommand *Command, responder WebhookResponder, whc WebhookContext, m MessageFromBot, err error) {
	logger := whc.Logger()
	gaMeasurement := whc.GaMeasurement()
	//gam.GeographicalOverride()

	if err == nil {
		logger.Infof("processCommandResponse(): Bot response message: %v", m)
		if _, err = responder.SendMessage(m, BotApiSendMessageOverResponse); err != nil {
			logger.Errorf("Failed to send message to Telegram\n\tError: %v\n\tMessage text: %v", err, m.Text) //TODO: Decide how do we handle it
		}
		if matchedCommand != nil {
			if gaMeasurement != nil {
				chatEntity := whc.ChatEntity()
				gaHostName := fmt.Sprintf("%v.debtstracker.io", strings.ToLower(whc.BotPlatform().Id()))
				pathPrefix := "bot/"
				var pageview measurement.Pageview
				if chatEntity != nil {
					path := chatEntity.GetAwaitingReplyTo()
					if path == "" {
						path = matchedCommand.Code
					}
					pageview = measurement.NewPageviewWithDocumentHost(gaHostName, pathPrefix + path, matchedCommand.Title)
				} else {
					pageview = measurement.NewPageviewWithDocumentHost(gaHostName, pathPrefix + WebhookInputTypeNames[whc.InputType()], matchedCommand.Title)
				}
				pageview.Common = whc.GaCommon()

				go func() {
					err := gaMeasurement.Queue(pageview)
					if err != nil {
						logger.Warningf("Failed to send page view to GA: %v", err)
					}
				}()
			}
		}
	} else {
		logger.Errorf(err.Error())
		if gaMeasurement != nil {
			exceptionMessage := measurement.NewException(err.Error(), false)
			exceptionMessage.Common = whc.GaCommon()
			go func(){
				err = gaMeasurement.Queue(exceptionMessage)
				if err != nil {
					logger.Warningf("Failed to send page view to GA: %v", err)
				}
			}()
		}
		if whc.InputType() == WebhookInputMessage {
			// Todo: Try to get chat ID from user?
			_, respErr := responder.SendMessage(whc.NewMessage(whc.Translate(MESSAGE_TEXT_OOPS_SOMETHING_WENT_WRONG) + "\n\n" + emoji.ERROR_ICON + fmt.Sprintf(" Server error - failed to process message: %v", err)), BotApiSendMessageOverResponse)
			if respErr != nil {
				logger.Errorf("Failed to report to user a server error: %v", respErr)
			}
		}
	}
}
<|endoftext|>


<commit_before>package server

import (
	"../hashtree"
	"../network"
	"io"
	"sync"
	"time"
)

// Implements WaitFor for Database, a database expect WaitFor
// to happen on different go routines than all other functions
type ListeningDatabase struct {
	Database
	listeners map[string]map[chan FileState]bool
	lock      sync.RWMutex //locks listeners R & W
}

func NewListeningDatabase(d Database) *ListeningDatabase {
	return &ListeningDatabase{d, make(map[string]map[chan FileState]bool), sync.RWMutex{}}
}

func (d *ListeningDatabase) AddListener(id network.StaticId, listener chan FileState) {
	sid := id.CompactId()
	d.lock.Lock()
	defer d.lock.Unlock()
	ls, ok := d.listeners[sid]
	if !ok {
		ls = make(map[chan FileState]bool)
		d.listeners[sid] = ls
	}
	ls[listener] = true
}

func (d *ListeningDatabase) RemoveListener(id network.StaticId, listener chan FileState) {
	sid := id.CompactId()
	d.lock.Lock()
	defer d.lock.Unlock()
	ls, ok := d.listeners[sid]
	if ok {
		delete(ls, listener)
		if len(ls) == 0 {
			delete(d.listeners, sid)
		}
	}
}

func (d *ListeningDatabase) writeHappend(id network.StaticId) {
	sid := id.CompactId()
	_, ok := d.listeners[sid]
	if ok {
		state := d.GetState(id)
		// only get state (which may use disk) if there are listeners,
		// but before lock so it does not block, then we have to get
		// listeners again after lock (which is just one more map lookup)
		d.lock.RLock()
		defer d.lock.RUnlock()
		ls, ok := d.listeners[sid]
		if ok {
			for listener := range ls {
				listener <- state
			}
		}
	}
}

func (d *ListeningDatabase) Close() {
	d.Database.Close()
	// terminate all listners
}

func (d *ListeningDatabase) ImportFromReader(r io.Reader) network.StaticId {
	id := d.Database.ImportFromReader(r)
	d.writeHappend(id)
	return id
}

func (d *ListeningDatabase) WaitFor(id network.StaticId, toState FileState, timeOut time.Duration) (ok bool, curState FileState) {
	listener := make(chan FileState)
	defer close(listener)
	d.AddListener(id, listener)
	defer d.RemoveListener(id, listener)

	startState := d.GetState(id)
	if startState == toState {
		return true, startState
	}
	for true {
		select {
		case state := <-listener:
			if state == toState {
				return true, state
			}
		case <-time.After(timeOut):
			state := d.GetState(id)
			return state == toState, state
		}
	}
	panic("code should not reach here")
}

func (d *ListeningDatabase) StartPart(id network.StaticId) error {
	err := d.Database.StartPart(id)
	d.writeHappend(id)
	return err
}

func (d *ListeningDatabase) PutAt(b []byte, id network.StaticId, off hashtree.Bytes) (has hashtree.Nodes, complete bool, err error) {
	has, complete, err = d.Database.PutAt(b, id, off)
	d.writeHappend(id)
	return
}

func (d *ListeningDatabase) PutInnerHashes(id network.StaticId, set network.InnerHashes) (has hashtree.Nodes, complete bool, err error) {
	has, complete, err = d.Database.PutInnerHashes(id, set)
	d.writeHappend(id)
	return
}
<commit_msg>buffers channel so it does not block<commit_after>package server

import (
	"../hashtree"
	"../network"
	"io"
	"sync"
	"time"
)

// Implements WaitFor for Database, a database expect WaitFor
// to happen on different go routines than all other functions
type ListeningDatabase struct {
	Database
	listeners map[string]map[chan FileState]bool
	lock      sync.RWMutex //locks listeners R & W
}

func NewListeningDatabase(d Database) *ListeningDatabase {
	return &ListeningDatabase{d, make(map[string]map[chan FileState]bool), sync.RWMutex{}}
}

func (d *ListeningDatabase) AddListener(id network.StaticId, listener chan FileState) {
	if cap(listener) < 1 {
		panic("listener must have a buffer")
	}
	sid := id.CompactId()
	d.lock.Lock()
	defer d.lock.Unlock()
	ls, ok := d.listeners[sid]
	if !ok {
		ls = make(map[chan FileState]bool)
		d.listeners[sid] = ls
	}
	ls[listener] = true
}

func (d *ListeningDatabase) RemoveListener(id network.StaticId, listener chan FileState) {
	sid := id.CompactId()
	d.lock.Lock()
	defer d.lock.Unlock()
	ls, ok := d.listeners[sid]
	if ok {
		delete(ls, listener)
		if len(ls) == 0 {
			delete(d.listeners, sid)
		}
	}
}

func (d *ListeningDatabase) writeHappend(id network.StaticId) {
	sid := id.CompactId()
	_, ok := d.listeners[sid]
	if ok {
		state := d.GetState(id)
		// only get state (which may use disk) if there are listeners,
		// but before lock so it does not block, then we have to get
		// listeners again after lock (which is just one more map lookup)
		d.lock.RLock()
		defer d.lock.RUnlock()
		ls, ok := d.listeners[sid]
		if ok {
			for listener := range ls {
				select {
				//remove old stuff in listener, if any
				case <-listener:
					continue
				default:
					listener <- state
				}
			}
		}
	}
}

func (d *ListeningDatabase) Close() {
	d.Database.Close()
	// terminate all listners
}

func (d *ListeningDatabase) ImportFromReader(r io.Reader) network.StaticId {
	id := d.Database.ImportFromReader(r)
	d.writeHappend(id)
	return id
}

func (d *ListeningDatabase) WaitFor(id network.StaticId, toState FileState, timeOut time.Duration) (ok bool, curState FileState) {
	listener := make(chan FileState, 1)
	defer close(listener)
	d.AddListener(id, listener)
	defer d.RemoveListener(id, listener)

	startState := d.GetState(id)
	if startState == toState {
		return true, startState
	}
	for true {
		select {
		case state := <-listener:
			if state == toState {
				return true, state
			}
		case <-time.After(timeOut):
			state := d.GetState(id)
			return state == toState, state
		}
	}
	panic("code should not reach here")
}

func (d *ListeningDatabase) StartPart(id network.StaticId) error {
	err := d.Database.StartPart(id)
	d.writeHappend(id)
	return err
}

func (d *ListeningDatabase) PutAt(b []byte, id network.StaticId, off hashtree.Bytes) (has hashtree.Nodes, complete bool, err error) {
	has, complete, err = d.Database.PutAt(b, id, off)
	d.writeHappend(id)
	return
}

func (d *ListeningDatabase) PutInnerHashes(id network.StaticId, set network.InnerHashes) (has hashtree.Nodes, complete bool, err error) {
	has, complete, err = d.Database.PutInnerHashes(id, set)
	d.writeHappend(id)
	return
}
<|endoftext|>