Skip to content

Commit e96b848

Browse files
MapleMaple
authored andcommitted
schema validation logic complete
1 parent 27b35f8 commit e96b848

File tree

4 files changed

+157
-19
lines changed

4 files changed

+157
-19
lines changed

doc/definitions.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
"type": "string"
88
}
99
},
10+
"symbols": {
11+
"description": "JSON schema used in https://api.bitfinex.com/v1/symbols/ endpoint",
12+
"type": "array",
13+
"items": {
14+
"type": "string"
15+
},
16+
"uniqueItems": false
17+
},
1018
"pubticker": {
1119
"description": "JSON schema used in https://api.bitfinex.com/v1/pubticker/ endpoint",
1220
"type": "object",

include/bfx-api-cpp/BitfinexAPI.hpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ namespace BfxAPI
137137
"XRP",
138138
"ZEC"
139139
};
140+
141+
schemaValidator_ = jsonutils::BfxSchemaValidator(symbols_, currencies_);
142+
140143
// As found on
141144
// https://bitfinex.readme.io/v1/reference#rest-auth-deposit
142145
methods_ =
@@ -152,10 +155,12 @@ namespace BfxAPI
152155
"tetheruso",
153156
"zcash",
154157
};
158+
155159
walletNames_ =
156160
{
157161
"trading", "exchange", "deposit"
158162
};
163+
159164
// New order endpoint "type" parameter
160165
types_ =
161166
{
@@ -913,6 +918,8 @@ namespace BfxAPI
913918
CURL *curlGET_;
914919
CURL *curlPOST_;
915920
CURLcode curlStatusCode_;
921+
// internal jsonutils instances
922+
jsonutils::BfxSchemaValidator schemaValidator_;
916923
// BitfinexAPI settings
917924
string WDconfFilePath_;
918925
string APIurl_;
@@ -996,13 +1003,13 @@ namespace BfxAPI
9961003
return noError;
9971004
};
9981005

999-
void doGETrequest(const string &UrlEndPoint, const string &params)
1006+
void doGETrequest(const string &apiEndPoint, const string &params)
10001007
{
10011008
bfxApiStatusCode_ = noError;
10021009

10031010
if(curlGET_)
10041011
{
1005-
string url = APIurl_ + UrlEndPoint + params;
1012+
string url = APIurl_ + apiEndPoint + params;
10061013

10071014
result_.clear();
10081015
curl_easy_setopt(curlGET_, CURLOPT_TIMEOUT, CURL_TIMEOUT);
@@ -1018,10 +1025,20 @@ namespace BfxAPI
10181025
{
10191026
cerr << "libcurl error in doGETrequest():\n";
10201027
cerr << "CURLcode: " << curlStatusCode_ << "\n";
1028+
bfxApiStatusCode_ = curlERR;
1029+
}
1030+
// Check schema
1031+
else
1032+
{
1033+
bfxApiStatusCode_ =
1034+
schemaValidator_.validateSchema(apiEndPoint, result_);
10211035
}
10221036
}
10231037
else
1038+
{
10241039
cerr << "curl not properly initialized curlGET_ = nullptr";
1040+
bfxApiStatusCode_ = curlERR;
1041+
}
10251042
};
10261043

10271044
void doPOSTrequest(const string &UrlEndPoint, const string &params)

include/bfx-api-cpp/error.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,7 @@ enum bfxERR
2020
addressParamsMissing, // 8
2121
badOrderType, // 9
2222
jsonStrToUSetError, // 10
23-
badWDconfFilePath // 11
23+
badWDconfFilePath, // 11
24+
responseParseError, // 12
25+
responseSchemaError // 13
2426
};

include/bfx-api-cpp/jsonutils.hpp

Lines changed: 127 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,26 @@
2121
#include <iostream>
2222
#include <string>
2323
#include <unordered_set>
24+
#include <unordered_map>
2425

2526
// namespaces
2627
using std::cerr;
2728
using std::cout;
2829
using std::endl;
2930
using std::string;
3031
using std::unordered_set;
32+
using std::unordered_map;
3133
namespace rj = rapidjson;
3234

3335

34-
///////////////////////////////////////////////////////////////////////////////
35-
// Routines
36-
///////////////////////////////////////////////////////////////////////////////
37-
3836
namespace jsonutils
3937
{
40-
// Helper class resolving remote schema for schema $ref operator
38+
39+
////////////////////////////////////////////////////////////////////////////
40+
// Classes
41+
////////////////////////////////////////////////////////////////////////////
42+
43+
/// Helper class resolving remote schema for schema $ref operator
4144
class MyRemoteSchemaDocumentProvider: public rj::IRemoteSchemaDocumentProvider
4245
{
4346
public:
@@ -50,27 +53,129 @@ namespace jsonutils
5053
rj::Document d;
5154
d.ParseStream(fReadStream);
5255
fclose(pFileIn);
53-
remoteSchemaDoc = new rj::SchemaDocument(d);
56+
remoteSchemaDoc_ = new rj::SchemaDocument(d);
5457
};
5558

56-
~MyRemoteSchemaDocumentProvider()
57-
{
58-
delete remoteSchemaDoc;
59-
};
59+
// ~MyRemoteSchemaDocumentProvider()
60+
// {
61+
// delete remoteSchemaDoc_;
62+
// };
6063

6164
private:
6265

63-
rj::SchemaDocument *remoteSchemaDoc;
66+
rj::SchemaDocument *remoteSchemaDoc_;
6467

6568
virtual const rj::SchemaDocument*
6669
GetRemoteDocument(const char* uri, rj::SizeType length)
6770
{
6871
// Resolve the URI and return a pointer to that schema
69-
return remoteSchemaDoc;
72+
return remoteSchemaDoc_;
7073
}
7174
};
7275

73-
// SAX events helper struct for jsonStrToUset() routine
76+
class BfxSchemaValidator
77+
{
78+
public:
79+
80+
BfxSchemaValidator() {}
81+
BfxSchemaValidator(unordered_set<string> &symbols,
82+
unordered_set<string> &currencies)
83+
{
84+
// Mapping is needed because rapidjson implementation of $ref
85+
// keyword in json schema doesn't support json keys which contain
86+
// special characters.
87+
// See https://github.com/Tencent/rapidjson/issues/1311
88+
89+
// Create map for endpoints which use symbols in URL
90+
for (const auto &symbol : symbols)
91+
{
92+
// Map /pubticker/[symbol] endpoints to "pubticker" schema
93+
apiEndPointToSchemaMap_.emplace("/pubticker/" + symbol,
94+
"pubticker");
95+
// Map /stats/[symbol] endpoints to "stats" schema
96+
apiEndPointToSchemaMap_.emplace("/stats/" + symbol,
97+
"stats");
98+
// Map /book/[symbol] endpoints to "book" schema
99+
apiEndPointToSchemaMap_.emplace("/book/" + symbol,
100+
"book");
101+
// Map /trades/[symbol] endpoints to "trades" schema
102+
apiEndPointToSchemaMap_.emplace("/trades/" + symbol,
103+
"trades");
104+
}
105+
106+
// Create map for endpoints which use currencies in URL
107+
for (const auto &currency : currencies)
108+
{
109+
// Map /lendbook/[currency] endpoints to "lendbook" schema
110+
apiEndPointToSchemaMap_.emplace("/lendbook/" + currency,
111+
"lendbook");
112+
// Map /lends/[currency] endpoints to "lends" schema
113+
apiEndPointToSchemaMap_.emplace("/lends/" + currency,
114+
"lends");
115+
}
116+
117+
// Create map for static endpoints
118+
apiEndPointToSchemaMap_.emplace("/symbols/", "symbols");
119+
apiEndPointToSchemaMap_.emplace("/symbols_details/",
120+
"symbols_details");
121+
122+
}
123+
124+
auto validateSchema(const string &apiEndPoint, const string &inputJson)
125+
{
126+
const auto schemaName = getApiEndPointSchemaName(apiEndPoint);
127+
128+
// Create rapidjson schema document
129+
rj::Document sd;
130+
string schema =
131+
"{ \"$ref\": \"definitions.json#/" + schemaName + "\" }";
132+
sd.Parse(schema.c_str());
133+
rj::SchemaDocument schemaDocument(sd, 0, 0, &provider_);
134+
135+
// Create rapidjson document and check for parse errors
136+
rj::Document d;
137+
if (d.Parse(inputJson.c_str()).HasParseError())
138+
{
139+
cerr << "Invalid json - response:" << endl;
140+
cerr << inputJson << endl;
141+
cerr << "API endpoint: " << apiEndPoint << endl;
142+
return bfxERR::responseParseError;
143+
}
144+
145+
// Create rapidjson validator and check for schema errors
146+
rj::SchemaValidator validator(schemaDocument);
147+
if (!d.Accept(validator))
148+
{
149+
// Input JSON is invalid according to the schema
150+
// Output diagnostic information
151+
rj::StringBuffer sb;
152+
validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);
153+
cerr << "Invalid schema: " << sb.GetString() << endl;
154+
cerr << "Invalid keyword: " << validator.GetInvalidSchemaKeyword() << endl;
155+
sb.Clear();
156+
validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);
157+
cerr << "Invalid document: " << sb.GetString() << endl;
158+
cerr << "Invalid response: " << inputJson << endl;
159+
cerr << "Invalid API endpoint: " << apiEndPoint << endl;
160+
return bfxERR::responseSchemaError;
161+
}
162+
163+
return bfxERR::noError;
164+
}
165+
166+
private:
167+
168+
MyRemoteSchemaDocumentProvider provider_;
169+
unordered_map<string, string> apiEndPointToSchemaMap_;
170+
171+
const string& getApiEndPointSchemaName(const string& apiEndpoint) noexcept
172+
{
173+
return apiEndPointToSchemaMap_[apiEndpoint];
174+
}
175+
176+
};
177+
178+
/// SAX events helper struct for jsonStrToUset() routine
74179
struct jsonStrToUsetHandler:
75180
public rj::BaseReaderHandler<rj::UTF8<>, jsonStrToUsetHandler>
76181
{
@@ -118,7 +223,11 @@ namespace jsonutils
118223
} state_;
119224
};
120225

121-
bfxERR jsonStrToUset(unordered_set<string> &uSet, const string &jsonStr)
226+
////////////////////////////////////////////////////////////////////////////
227+
// Routines
228+
////////////////////////////////////////////////////////////////////////////
229+
230+
bfxERR jsonStrToUset(unordered_set<string> &uSet, const string &inputJson)
122231
{
123232
// Create schema $ref resolver
124233
rj::Document sd;
@@ -132,7 +241,8 @@ namespace jsonutils
132241
jsonStrToUsetHandler handler;
133242

134243
// Create schema validator
135-
rj::GenericSchemaValidator<rj::SchemaDocument, jsonStrToUsetHandler> validator(schemaDoc, handler);
244+
rj::GenericSchemaValidator<rj::SchemaDocument, jsonStrToUsetHandler>
245+
validator(schemaDoc, handler);
136246

137247
// Create reader
138248
rj::Reader reader;
@@ -142,7 +252,7 @@ namespace jsonutils
142252
/// DEBUG
143253

144254
// Create input JSON StringStream
145-
rj::StringStream ss(jsonStr.c_str());
255+
rj::StringStream ss(inputJson.c_str());
146256
// rj::StringStream ss(mockJson.c_str()); // DEBUG
147257

148258
// Parse and validate
@@ -162,6 +272,7 @@ namespace jsonutils
162272
sb.Clear();
163273
validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);
164274
cerr << "Invalid document: " << sb.GetString() << endl;
275+
cerr << "Invalid response: " << inputJson << endl;
165276
}
166277
return bfxERR::jsonStrToUSetError;
167278
}

0 commit comments

Comments
 (0)