1 #include "internal.h"
2 #include "errmap.h"
3 #include "contrib/lcb-jsoncpp/lcb-jsoncpp.h"
4 
5 using namespace lcb::errmap;
6 
ErrorMap()7 ErrorMap::ErrorMap() : revision(0), version(0) {
8 }
9 
getAttribute(const std::string & s)10 static ErrorAttribute getAttribute(const std::string& s) {
11     #define X(c, s_) if (s == s_) { return c; }
12     LCB_XERRMAP_ATTRIBUTES(X)
13     #undef X
14     return INVALID_ATTRIBUTE;
15 }
16 
getRetrySpec() const17 RetrySpec *Error::getRetrySpec() const {
18     return retry.specptr;
19 }
20 
parse(const Json::Value & retryJson,std::string & emsg)21 RetrySpec* RetrySpec::parse(const Json::Value& retryJson, std::string& emsg) {
22 
23     RetrySpec *spec = new RetrySpec();
24     spec->refcount = 1;
25 
26 #define FAIL_RETRY(s) \
27     emsg = s; \
28     delete spec; \
29     return NULL;
30 
31     if (!retryJson.isObject()) {
32         FAIL_RETRY("Missing retry specification");
33     }
34 
35     const Json::Value& strategyJson = retryJson["strategy"];
36     if (!strategyJson.isString()) {
37         FAIL_RETRY("Missing `strategy`");
38     }
39     const char* strategy = strategyJson.asCString();
40     if (!strcasecmp(strategy, "constant")) {
41         spec->strategy = CONSTANT;
42     } else if (!strcasecmp(strategy, "linear")) {
43         spec->strategy = LINEAR;
44     } else if (!strcasecmp(strategy, "exponential")) {
45         spec->strategy = EXPONENTIAL;
46     } else {
47         FAIL_RETRY("Unknown strategy");
48     }
49 
50 #define GET_TIMEFLD(srcname, dstname, required) { \
51     Json::Value dstname##Json = retryJson[srcname]; \
52     if (dstname##Json.isNumeric()) { \
53         spec->dstname = (dstname##Json).asUInt() * 1000; \
54     } else if (required) { \
55         FAIL_RETRY("Missing " # srcname); \
56     } else { \
57         spec->dstname = 0; \
58     } \
59 }
60 
61     GET_TIMEFLD("interval", interval, true);
62     GET_TIMEFLD("after", after, true);
63     GET_TIMEFLD("ceil", ceil, false);
64     GET_TIMEFLD("max-duration", max_duration, false);
65 
66     return spec;
67 
68 #undef FAIL_RETRY
69 #undef GET_TIMEFLD
70 
71 }
72 
73 const uint32_t ErrorMap::MAX_VERSION = 1;
74 
75 ErrorMap::ParseStatus
parse(const char * s,size_t n,std::string & errmsg)76 ErrorMap::parse(const char *s, size_t n, std::string& errmsg) {
77     Json::Value root_nonconst;
78     Json::Reader reader;
79     if (!reader.parse(s, s + n, root_nonconst)) {
80         errmsg = "Invalid JSON";
81         return PARSE_ERROR;
82     }
83 
84     const Json::Value& root = root_nonconst;
85     const Json::Value& verJson = root["version"];
86     if (!verJson.isNumeric()) {
87         errmsg = "'version' is not a number";
88         return PARSE_ERROR;
89     }
90 
91     if (verJson.asUInt() > MAX_VERSION) {
92         errmsg = "'version' is unreasonably high";
93         return UNKNOWN_VERSION;
94     }
95 
96     const Json::Value& revJson = root["revision"];
97     if (!revJson.isNumeric()) {
98         errmsg = "'revision' is not a number";
99         return PARSE_ERROR;
100     }
101 
102     if (revJson.asUInt() <= revision) {
103         return NOT_UPDATED;
104     }
105 
106     const Json::Value& errsJson = root["errors"];
107     if (!errsJson.isObject()) {
108         errmsg = "'errors' is not an object";
109         return PARSE_ERROR;
110     }
111 
112     Json::Value::const_iterator ii = errsJson.begin();
113     for (; ii != errsJson.end(); ++ii) {
114         // Key is the version in hex
115         unsigned ec = 0;
116         if (sscanf(ii.key().asCString(), "%x", &ec) != 1) {
117             errmsg = "key " + ii.key().asString() + " is not a hex number";
118             return PARSE_ERROR;
119         }
120 
121         const Json::Value& errorJson = *ii;
122 
123         // Descend into the error attributes
124         Error error;
125         error.code = static_cast<uint16_t>(ec);
126 
127         error.shortname = errorJson["name"].asString();
128         error.description = errorJson["desc"].asString();
129 
130         const Json::Value& attrs = errorJson["attrs"];
131         if (!attrs.isArray()) {
132             errmsg = "'attrs' is not an array";
133             return PARSE_ERROR;
134         }
135 
136         Json::Value::const_iterator jj = attrs.begin();
137         for (; jj != attrs.end(); ++jj) {
138             ErrorAttribute attr = getAttribute(jj->asString());
139             if (attr == INVALID_ATTRIBUTE) {
140                 errmsg = "unknown attribute received";
141                 return UNKNOWN_VERSION;
142             }
143             error.attributes.insert(attr);
144         }
145         if (error.hasAttribute(AUTO_RETRY)) {
146             const Json::Value& retryJson = errorJson["retry"];
147             if (!retryJson.isObject()) {
148                 errmsg = "Need `retry` specification for `auto-retry` attribute";
149                 return PARSE_ERROR;
150             }
151             if ((error.retry.specptr = RetrySpec::parse(retryJson, errmsg)) == NULL) {
152                 return PARSE_ERROR;
153             }
154         }
155         errors.insert(MapType::value_type(ec, error));
156     }
157 
158     return UPDATED;
159 }
160 
getError(uint16_t code) const161 const Error& ErrorMap::getError(uint16_t code) const {
162     static const Error invalid;
163     MapType::const_iterator it = errors.find(code);
164 
165     if (it != errors.end()) {
166         return it->second;
167     } else {
168         return invalid;
169     }
170 }
171 
lcb_errmap_new()172 ErrorMap *lcb_errmap_new() { return new ErrorMap(); }
lcb_errmap_free(ErrorMap * m)173 void lcb_errmap_free(ErrorMap* m) { delete m; }
174