1// Copyright 2011 Google Inc. All rights reserved.
2// Use of this source code is governed by the Apache 2.0
3// license that can be found in the LICENSE file.
4
5// Package internal provides support for package appengine.
6//
7// Programs should not use this package directly. Its API is not stable.
8// Use packages appengine and appengine/* instead.
9package internal
10
11import (
12	"fmt"
13
14	"github.com/golang/protobuf/proto"
15
16	remotepb "google.golang.org/appengine/internal/remote_api"
17)
18
19// errorCodeMaps is a map of service name to the error code map for the service.
20var errorCodeMaps = make(map[string]map[int32]string)
21
22// RegisterErrorCodeMap is called from API implementations to register their
23// error code map. This should only be called from init functions.
24func RegisterErrorCodeMap(service string, m map[int32]string) {
25	errorCodeMaps[service] = m
26}
27
28type timeoutCodeKey struct {
29	service string
30	code    int32
31}
32
33// timeoutCodes is the set of service+code pairs that represent timeouts.
34var timeoutCodes = make(map[timeoutCodeKey]bool)
35
36func RegisterTimeoutErrorCode(service string, code int32) {
37	timeoutCodes[timeoutCodeKey{service, code}] = true
38}
39
40// APIError is the type returned by appengine.Context's Call method
41// when an API call fails in an API-specific way. This may be, for instance,
42// a taskqueue API call failing with TaskQueueServiceError::UNKNOWN_QUEUE.
43type APIError struct {
44	Service string
45	Detail  string
46	Code    int32 // API-specific error code
47}
48
49func (e *APIError) Error() string {
50	if e.Code == 0 {
51		if e.Detail == "" {
52			return "APIError <empty>"
53		}
54		return e.Detail
55	}
56	s := fmt.Sprintf("API error %d", e.Code)
57	if m, ok := errorCodeMaps[e.Service]; ok {
58		s += " (" + e.Service + ": " + m[e.Code] + ")"
59	} else {
60		// Shouldn't happen, but provide a bit more detail if it does.
61		s = e.Service + " " + s
62	}
63	if e.Detail != "" {
64		s += ": " + e.Detail
65	}
66	return s
67}
68
69func (e *APIError) IsTimeout() bool {
70	return timeoutCodes[timeoutCodeKey{e.Service, e.Code}]
71}
72
73// CallError is the type returned by appengine.Context's Call method when an
74// API call fails in a generic way, such as RpcError::CAPABILITY_DISABLED.
75type CallError struct {
76	Detail string
77	Code   int32
78	// TODO: Remove this if we get a distinguishable error code.
79	Timeout bool
80}
81
82func (e *CallError) Error() string {
83	var msg string
84	switch remotepb.RpcError_ErrorCode(e.Code) {
85	case remotepb.RpcError_UNKNOWN:
86		return e.Detail
87	case remotepb.RpcError_OVER_QUOTA:
88		msg = "Over quota"
89	case remotepb.RpcError_CAPABILITY_DISABLED:
90		msg = "Capability disabled"
91	case remotepb.RpcError_CANCELLED:
92		msg = "Canceled"
93	default:
94		msg = fmt.Sprintf("Call error %d", e.Code)
95	}
96	s := msg + ": " + e.Detail
97	if e.Timeout {
98		s += " (timeout)"
99	}
100	return s
101}
102
103func (e *CallError) IsTimeout() bool {
104	return e.Timeout
105}
106
107// NamespaceMods is a map from API service to a function that will mutate an RPC request to attach a namespace.
108// The function should be prepared to be called on the same message more than once; it should only modify the
109// RPC request the first time.
110var NamespaceMods = make(map[string]func(m proto.Message, namespace string))
111