| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | // Copyright The OpenTelemetry Authors | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // 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 internal // import "go.opentelemetry.io/otel/semconv/internal" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"net" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"go.opentelemetry.io/otel/attribute" | 
					
						
							|  |  |  | 	"go.opentelemetry.io/otel/codes" | 
					
						
							|  |  |  | 	"go.opentelemetry.io/otel/trace" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SemanticConventions are the semantic convention values defined for a | 
					
						
							|  |  |  | // version of the OpenTelemetry specification. | 
					
						
							|  |  |  | type SemanticConventions struct { | 
					
						
							|  |  |  | 	EnduserIDKey                attribute.Key | 
					
						
							|  |  |  | 	HTTPClientIPKey             attribute.Key | 
					
						
							|  |  |  | 	HTTPFlavorKey               attribute.Key | 
					
						
							|  |  |  | 	HTTPHostKey                 attribute.Key | 
					
						
							|  |  |  | 	HTTPMethodKey               attribute.Key | 
					
						
							|  |  |  | 	HTTPRequestContentLengthKey attribute.Key | 
					
						
							|  |  |  | 	HTTPRouteKey                attribute.Key | 
					
						
							|  |  |  | 	HTTPSchemeHTTP              attribute.KeyValue | 
					
						
							|  |  |  | 	HTTPSchemeHTTPS             attribute.KeyValue | 
					
						
							|  |  |  | 	HTTPServerNameKey           attribute.Key | 
					
						
							|  |  |  | 	HTTPStatusCodeKey           attribute.Key | 
					
						
							|  |  |  | 	HTTPTargetKey               attribute.Key | 
					
						
							|  |  |  | 	HTTPURLKey                  attribute.Key | 
					
						
							|  |  |  | 	HTTPUserAgentKey            attribute.Key | 
					
						
							|  |  |  | 	NetHostIPKey                attribute.Key | 
					
						
							|  |  |  | 	NetHostNameKey              attribute.Key | 
					
						
							|  |  |  | 	NetHostPortKey              attribute.Key | 
					
						
							|  |  |  | 	NetPeerIPKey                attribute.Key | 
					
						
							|  |  |  | 	NetPeerNameKey              attribute.Key | 
					
						
							|  |  |  | 	NetPeerPortKey              attribute.Key | 
					
						
							|  |  |  | 	NetTransportIP              attribute.KeyValue | 
					
						
							|  |  |  | 	NetTransportOther           attribute.KeyValue | 
					
						
							|  |  |  | 	NetTransportTCP             attribute.KeyValue | 
					
						
							|  |  |  | 	NetTransportUDP             attribute.KeyValue | 
					
						
							|  |  |  | 	NetTransportUnix            attribute.KeyValue | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NetAttributesFromHTTPRequest generates attributes of the net | 
					
						
							|  |  |  | // namespace as specified by the OpenTelemetry specification for a | 
					
						
							|  |  |  | // span.  The network parameter is a string that net.Dial function | 
					
						
							|  |  |  | // from standard library can understand. | 
					
						
							|  |  |  | func (sc *SemanticConventions) NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue { | 
					
						
							|  |  |  | 	attrs := []attribute.KeyValue{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch network { | 
					
						
							|  |  |  | 	case "tcp", "tcp4", "tcp6": | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.NetTransportTCP) | 
					
						
							|  |  |  | 	case "udp", "udp4", "udp6": | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.NetTransportUDP) | 
					
						
							|  |  |  | 	case "ip", "ip4", "ip6": | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.NetTransportIP) | 
					
						
							|  |  |  | 	case "unix", "unixgram", "unixpacket": | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.NetTransportUnix) | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.NetTransportOther) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	peerIP, peerName, peerPort := hostIPNamePort(request.RemoteAddr) | 
					
						
							|  |  |  | 	if peerIP != "" { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.NetPeerIPKey.String(peerIP)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if peerName != "" { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.NetPeerNameKey.String(peerName)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if peerPort != 0 { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.NetPeerPortKey.Int(peerPort)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	hostIP, hostName, hostPort := "", "", 0 | 
					
						
							|  |  |  | 	for _, someHost := range []string{request.Host, request.Header.Get("Host"), request.URL.Host} { | 
					
						
							|  |  |  | 		hostIP, hostName, hostPort = hostIPNamePort(someHost) | 
					
						
							|  |  |  | 		if hostIP != "" || hostName != "" || hostPort != 0 { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if hostIP != "" { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.NetHostIPKey.String(hostIP)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if hostName != "" { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.NetHostNameKey.String(hostName)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if hostPort != 0 { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.NetHostPortKey.Int(hostPort)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return attrs | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // hostIPNamePort extracts the IP address, name and (optional) port from hostWithPort. | 
					
						
							|  |  |  | // It handles both IPv4 and IPv6 addresses. If the host portion is not recognized | 
					
						
							|  |  |  | // as a valid IPv4 or IPv6 address, the `ip` result will be empty and the | 
					
						
							|  |  |  | // host portion will instead be returned in `name`. | 
					
						
							|  |  |  | func hostIPNamePort(hostWithPort string) (ip string, name string, port int) { | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		hostPart, portPart string | 
					
						
							|  |  |  | 		parsedPort         uint64 | 
					
						
							|  |  |  | 		err                error | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if hostPart, portPart, err = net.SplitHostPort(hostWithPort); err != nil { | 
					
						
							|  |  |  | 		hostPart, portPart = hostWithPort, "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if parsedIP := net.ParseIP(hostPart); parsedIP != nil { | 
					
						
							|  |  |  | 		ip = parsedIP.String() | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		name = hostPart | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if parsedPort, err = strconv.ParseUint(portPart, 10, 16); err == nil { | 
					
						
							|  |  |  | 		port = int(parsedPort) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // EndUserAttributesFromHTTPRequest generates attributes of the | 
					
						
							|  |  |  | // enduser namespace as specified by the OpenTelemetry specification | 
					
						
							|  |  |  | // for a span. | 
					
						
							|  |  |  | func (sc *SemanticConventions) EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue { | 
					
						
							|  |  |  | 	if username, _, ok := request.BasicAuth(); ok { | 
					
						
							|  |  |  | 		return []attribute.KeyValue{sc.EnduserIDKey.String(username)} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // HTTPClientAttributesFromHTTPRequest generates attributes of the | 
					
						
							|  |  |  | // http namespace as specified by the OpenTelemetry specification for | 
					
						
							|  |  |  | // a span on the client side. | 
					
						
							|  |  |  | func (sc *SemanticConventions) HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue { | 
					
						
							|  |  |  | 	attrs := []attribute.KeyValue{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// remove any username/password info that may be in the URL | 
					
						
							|  |  |  | 	// before adding it to the attributes | 
					
						
							|  |  |  | 	userinfo := request.URL.User | 
					
						
							|  |  |  | 	request.URL.User = nil | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	attrs = append(attrs, sc.HTTPURLKey.String(request.URL.String())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// restore any username/password info that was removed | 
					
						
							|  |  |  | 	request.URL.User = userinfo | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return append(attrs, sc.httpCommonAttributesFromHTTPRequest(request)...) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (sc *SemanticConventions) httpCommonAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue { | 
					
						
							|  |  |  | 	attrs := []attribute.KeyValue{} | 
					
						
							|  |  |  | 	if ua := request.UserAgent(); ua != "" { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.HTTPUserAgentKey.String(ua)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if request.ContentLength > 0 { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.HTTPRequestContentLengthKey.Int64(request.ContentLength)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return append(attrs, sc.httpBasicAttributesFromHTTPRequest(request)...) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (sc *SemanticConventions) httpBasicAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue { | 
					
						
							|  |  |  | 	// as these attributes are used by HTTPServerMetricAttributesFromHTTPRequest, they should be low-cardinality | 
					
						
							|  |  |  | 	attrs := []attribute.KeyValue{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if request.TLS != nil { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.HTTPSchemeHTTPS) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.HTTPSchemeHTTP) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if request.Host != "" { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.HTTPHostKey.String(request.Host)) | 
					
						
							|  |  |  | 	} else if request.URL != nil && request.URL.Host != "" { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.HTTPHostKey.String(request.URL.Host)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	flavor := "" | 
					
						
							|  |  |  | 	if request.ProtoMajor == 1 { | 
					
						
							|  |  |  | 		flavor = fmt.Sprintf("1.%d", request.ProtoMinor) | 
					
						
							|  |  |  | 	} else if request.ProtoMajor == 2 { | 
					
						
							|  |  |  | 		flavor = "2" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if flavor != "" { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.HTTPFlavorKey.String(flavor)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if request.Method != "" { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.HTTPMethodKey.String(request.Method)) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.HTTPMethodKey.String(http.MethodGet)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return attrs | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes | 
					
						
							|  |  |  | // to be used with server-side HTTP metrics. | 
					
						
							|  |  |  | func (sc *SemanticConventions) HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue { | 
					
						
							|  |  |  | 	attrs := []attribute.KeyValue{} | 
					
						
							|  |  |  | 	if serverName != "" { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.HTTPServerNameKey.String(serverName)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return append(attrs, sc.httpBasicAttributesFromHTTPRequest(request)...) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // HTTPServerAttributesFromHTTPRequest generates attributes of the | 
					
						
							|  |  |  | // http namespace as specified by the OpenTelemetry specification for | 
					
						
							|  |  |  | // a span on the server side. Currently, only basic authentication is | 
					
						
							|  |  |  | // supported. | 
					
						
							|  |  |  | func (sc *SemanticConventions) HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue { | 
					
						
							|  |  |  | 	attrs := []attribute.KeyValue{ | 
					
						
							|  |  |  | 		sc.HTTPTargetKey.String(request.RequestURI), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if serverName != "" { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.HTTPServerNameKey.String(serverName)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if route != "" { | 
					
						
							|  |  |  | 		attrs = append(attrs, sc.HTTPRouteKey.String(route)) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-06-05 10:15:05 +02:00
										 |  |  | 	if values := request.Header["X-Forwarded-For"]; len(values) > 0 { | 
					
						
							|  |  |  | 		addr := values[0] | 
					
						
							|  |  |  | 		if i := strings.Index(addr, ","); i > 0 { | 
					
						
							|  |  |  | 			addr = addr[:i] | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-06-05 10:15:05 +02:00
										 |  |  | 		attrs = append(attrs, sc.HTTPClientIPKey.String(addr)) | 
					
						
							| 
									
										
										
										
											2023-05-09 19:19:48 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return append(attrs, sc.httpCommonAttributesFromHTTPRequest(request)...) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // HTTPAttributesFromHTTPStatusCode generates attributes of the http | 
					
						
							|  |  |  | // namespace as specified by the OpenTelemetry specification for a | 
					
						
							|  |  |  | // span. | 
					
						
							|  |  |  | func (sc *SemanticConventions) HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue { | 
					
						
							|  |  |  | 	attrs := []attribute.KeyValue{ | 
					
						
							|  |  |  | 		sc.HTTPStatusCodeKey.Int(code), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return attrs | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type codeRange struct { | 
					
						
							|  |  |  | 	fromInclusive int | 
					
						
							|  |  |  | 	toInclusive   int | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r codeRange) contains(code int) bool { | 
					
						
							|  |  |  | 	return r.fromInclusive <= code && code <= r.toInclusive | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var validRangesPerCategory = map[int][]codeRange{ | 
					
						
							|  |  |  | 	1: { | 
					
						
							|  |  |  | 		{http.StatusContinue, http.StatusEarlyHints}, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	2: { | 
					
						
							|  |  |  | 		{http.StatusOK, http.StatusAlreadyReported}, | 
					
						
							|  |  |  | 		{http.StatusIMUsed, http.StatusIMUsed}, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	3: { | 
					
						
							|  |  |  | 		{http.StatusMultipleChoices, http.StatusUseProxy}, | 
					
						
							|  |  |  | 		{http.StatusTemporaryRedirect, http.StatusPermanentRedirect}, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	4: { | 
					
						
							|  |  |  | 		{http.StatusBadRequest, http.StatusTeapot}, // yes, teapot is so useful… | 
					
						
							|  |  |  | 		{http.StatusMisdirectedRequest, http.StatusUpgradeRequired}, | 
					
						
							|  |  |  | 		{http.StatusPreconditionRequired, http.StatusTooManyRequests}, | 
					
						
							|  |  |  | 		{http.StatusRequestHeaderFieldsTooLarge, http.StatusRequestHeaderFieldsTooLarge}, | 
					
						
							|  |  |  | 		{http.StatusUnavailableForLegalReasons, http.StatusUnavailableForLegalReasons}, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	5: { | 
					
						
							|  |  |  | 		{http.StatusInternalServerError, http.StatusLoopDetected}, | 
					
						
							|  |  |  | 		{http.StatusNotExtended, http.StatusNetworkAuthenticationRequired}, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SpanStatusFromHTTPStatusCode generates a status code and a message | 
					
						
							|  |  |  | // as specified by the OpenTelemetry specification for a span. | 
					
						
							|  |  |  | func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) { | 
					
						
							|  |  |  | 	spanCode, valid := validateHTTPStatusCode(code) | 
					
						
							|  |  |  | 	if !valid { | 
					
						
							|  |  |  | 		return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return spanCode, "" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message | 
					
						
							|  |  |  | // as specified by the OpenTelemetry specification for a span. | 
					
						
							|  |  |  | // Exclude 4xx for SERVER to set the appropriate status. | 
					
						
							|  |  |  | func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) { | 
					
						
							|  |  |  | 	spanCode, valid := validateHTTPStatusCode(code) | 
					
						
							|  |  |  | 	if !valid { | 
					
						
							|  |  |  | 		return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	category := code / 100 | 
					
						
							|  |  |  | 	if spanKind == trace.SpanKindServer && category == 4 { | 
					
						
							|  |  |  | 		return codes.Unset, "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return spanCode, "" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // validateHTTPStatusCode validates the HTTP status code and returns | 
					
						
							|  |  |  | // corresponding span status code. If the `code` is not a valid HTTP status | 
					
						
							|  |  |  | // code, returns span status Error and false. | 
					
						
							|  |  |  | func validateHTTPStatusCode(code int) (codes.Code, bool) { | 
					
						
							|  |  |  | 	category := code / 100 | 
					
						
							|  |  |  | 	ranges, ok := validRangesPerCategory[category] | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return codes.Error, false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ok = false | 
					
						
							|  |  |  | 	for _, crange := range ranges { | 
					
						
							|  |  |  | 		ok = crange.contains(code) | 
					
						
							|  |  |  | 		if ok { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return codes.Error, false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if category > 0 && category < 4 { | 
					
						
							|  |  |  | 		return codes.Unset, true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return codes.Error, true | 
					
						
							|  |  |  | } |