rewrite for eet 2.0 api version v4
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
#include "eetv4response.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto SoapNamespace = "http://schemas.xmlsoap.org/soap/envelope/";
|
||||
constexpr auto EetNamespace = "http://fs.gov.cz/eet/schema/v4";
|
||||
|
||||
void setError(QString *error, const QString &message)
|
||||
{
|
||||
if (error != nullptr)
|
||||
{
|
||||
*error = message;
|
||||
}
|
||||
}
|
||||
|
||||
bool isElement(const QXmlStreamReader &reader, const char *name, const char *namespaceUri)
|
||||
{
|
||||
return reader.isStartElement() &&
|
||||
reader.name() == QLatin1String(name) &&
|
||||
reader.namespaceUri() == QLatin1String(namespaceUri);
|
||||
}
|
||||
|
||||
bool parseBoolean(const QString &value, bool *result)
|
||||
{
|
||||
if (value == QLatin1String("true") || value == QLatin1String("1"))
|
||||
{
|
||||
*result = true;
|
||||
return true;
|
||||
}
|
||||
if (value == QLatin1String("false") || value == QLatin1String("0"))
|
||||
{
|
||||
*result = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parseDateTime(const QString &value, QDateTime *result)
|
||||
{
|
||||
const QDateTime parsed = QDateTime::fromString(value, Qt::ISODate);
|
||||
if (!parsed.isValid() || value.size() < 20 ||
|
||||
!(value.endsWith(QLatin1Char('Z')) || value.at(value.size() - 6) == QLatin1Char('+') ||
|
||||
value.at(value.size() - 6) == QLatin1Char('-')))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*result = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseUuid(const QString &value)
|
||||
{
|
||||
static const QRegularExpression expression(
|
||||
QStringLiteral("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-"
|
||||
"[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"));
|
||||
return expression.match(value).hasMatch();
|
||||
}
|
||||
|
||||
bool parsePok(const QString &value)
|
||||
{
|
||||
static const QRegularExpression expression(
|
||||
QStringLiteral("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-"
|
||||
"[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}-[0-9a-fA-F]{2}$"));
|
||||
return expression.match(value).hasMatch();
|
||||
}
|
||||
|
||||
bool parseResponseBody(QXmlStreamReader &reader, EetV4Response *response, QString *error)
|
||||
{
|
||||
bool hasHeader = false;
|
||||
bool hasResult = false;
|
||||
while (reader.readNextStartElement())
|
||||
{
|
||||
if (isElement(reader, "Hlavicka", EetNamespace))
|
||||
{
|
||||
if (hasHeader)
|
||||
{
|
||||
setError(error, QStringLiteral("EET response contains duplicate Hlavicka."));
|
||||
return false;
|
||||
}
|
||||
hasHeader = true;
|
||||
const auto attributes = reader.attributes();
|
||||
const QString uuid = attributes.value(QStringLiteral("uuid_zpravy")).toString();
|
||||
if (!uuid.isEmpty() && !parseUuid(uuid))
|
||||
{
|
||||
setError(error, QStringLiteral("EET response contains an invalid message UUID."));
|
||||
return false;
|
||||
}
|
||||
response->messageUuid = uuid;
|
||||
const QString receivedAt = attributes.value(QStringLiteral("dat_prij")).toString();
|
||||
const QString rejectedAt = attributes.value(QStringLiteral("dat_odmit")).toString();
|
||||
if (!receivedAt.isEmpty() && !parseDateTime(receivedAt, &response->receivedAt))
|
||||
{
|
||||
setError(error, QStringLiteral("EET response contains an invalid receipt timestamp."));
|
||||
return false;
|
||||
}
|
||||
if (!rejectedAt.isEmpty() && !parseDateTime(rejectedAt, &response->rejectedAt))
|
||||
{
|
||||
setError(error, QStringLiteral("EET response contains an invalid rejection timestamp."));
|
||||
return false;
|
||||
}
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
else if (isElement(reader, "Potvrzeni", EetNamespace))
|
||||
{
|
||||
if (hasResult)
|
||||
{
|
||||
setError(error, QStringLiteral("EET response contains multiple results."));
|
||||
return false;
|
||||
}
|
||||
hasResult = true;
|
||||
const auto attributes = reader.attributes();
|
||||
const QString pok = attributes.value(QStringLiteral("pok")).toString();
|
||||
if (!parsePok(pok))
|
||||
{
|
||||
setError(error, QStringLiteral("EET response contains an invalid POK."));
|
||||
return false;
|
||||
}
|
||||
response->pok = pok;
|
||||
response->status = EetV4ResponseStatus::Success;
|
||||
const QString test = attributes.value(QStringLiteral("test")).toString();
|
||||
if (!test.isEmpty() && !parseBoolean(test, &response->test))
|
||||
{
|
||||
setError(error, QStringLiteral("EET response contains an invalid test flag."));
|
||||
return false;
|
||||
}
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
else if (isElement(reader, "Chyba", EetNamespace))
|
||||
{
|
||||
if (hasResult)
|
||||
{
|
||||
setError(error, QStringLiteral("EET response contains multiple results."));
|
||||
return false;
|
||||
}
|
||||
hasResult = true;
|
||||
const auto attributes = reader.attributes();
|
||||
bool ok = false;
|
||||
const int code = attributes.value(QStringLiteral("kod")).toInt(&ok);
|
||||
if (!ok || code < -999 || code > 999)
|
||||
{
|
||||
setError(error, QStringLiteral("EET response contains an invalid error code."));
|
||||
return false;
|
||||
}
|
||||
response->errorCode = code;
|
||||
response->message = reader.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
|
||||
response->status = EetV4ResponseStatus::Error;
|
||||
const QString test = attributes.value(QStringLiteral("test")).toString();
|
||||
if (!test.isEmpty() && !parseBoolean(test, &response->test))
|
||||
{
|
||||
setError(error, QStringLiteral("EET response contains an invalid test flag."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (isElement(reader, "Varovani", EetNamespace))
|
||||
{
|
||||
bool ok = false;
|
||||
const int code = reader.attributes().value(QStringLiteral("kod_varov")).toInt(&ok);
|
||||
if (!ok || code < 1 || code > 999)
|
||||
{
|
||||
setError(error, QStringLiteral("EET response contains an invalid warning code."));
|
||||
return false;
|
||||
}
|
||||
response->warningCodes.append(code);
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
if (!hasHeader || !hasResult)
|
||||
{
|
||||
setError(error, QStringLiteral("EET response is missing Hlavicka or a result."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseFault(QXmlStreamReader &reader, EetV4Response *response, QString *error)
|
||||
{
|
||||
QString code;
|
||||
QString reason;
|
||||
QString detail;
|
||||
while (reader.readNextStartElement())
|
||||
{
|
||||
if (reader.name() == QLatin1String("faultcode") ||
|
||||
reader.name() == QLatin1String("Value"))
|
||||
{
|
||||
code = reader.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
|
||||
}
|
||||
else if (reader.name() == QLatin1String("faultstring") ||
|
||||
reader.name() == QLatin1String("Text"))
|
||||
{
|
||||
reason = reader.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
|
||||
}
|
||||
else if (reader.name() == QLatin1String("Detail"))
|
||||
{
|
||||
detail = reader.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
response->status = EetV4ResponseStatus::SoapFault;
|
||||
response->message = reason.isEmpty() ? detail : reason;
|
||||
if (!code.isEmpty())
|
||||
{
|
||||
response->message.prepend(code + QStringLiteral(": "));
|
||||
}
|
||||
if (response->message.isEmpty())
|
||||
{
|
||||
setError(error, QStringLiteral("SOAP Fault does not contain a reason."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool EetV4ResponseParser::parseSoap(const QByteArray &soap,
|
||||
EetV4Response *response,
|
||||
QString *error)
|
||||
{
|
||||
if (response == nullptr)
|
||||
{
|
||||
setError(error, QStringLiteral("A response output object is required."));
|
||||
return false;
|
||||
}
|
||||
*response = {};
|
||||
QXmlStreamReader reader(soap);
|
||||
bool inBody = false;
|
||||
bool hasPayload = false;
|
||||
while (!reader.atEnd())
|
||||
{
|
||||
reader.readNext();
|
||||
if (!reader.isStartElement())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (reader.name() == QLatin1String("Envelope"))
|
||||
{
|
||||
if (reader.namespaceUri() != QLatin1String(SoapNamespace))
|
||||
{
|
||||
setError(error, QStringLiteral("SOAP Envelope has an invalid namespace."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (isElement(reader, "Body", SoapNamespace))
|
||||
{
|
||||
if (inBody)
|
||||
{
|
||||
setError(error, QStringLiteral("SOAP response contains duplicate Body."));
|
||||
return false;
|
||||
}
|
||||
inBody = true;
|
||||
while (reader.readNextStartElement())
|
||||
{
|
||||
if (hasPayload)
|
||||
{
|
||||
setError(error, QStringLiteral("SOAP Body contains multiple payloads."));
|
||||
return false;
|
||||
}
|
||||
hasPayload = true;
|
||||
if (isElement(reader, "Odpoved", EetNamespace))
|
||||
{
|
||||
if (!parseResponseBody(reader, response, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (reader.name() == QLatin1String("Fault") &&
|
||||
reader.namespaceUri() == QLatin1String(SoapNamespace))
|
||||
{
|
||||
if (!parseFault(reader, response, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setError(error, QStringLiteral("SOAP Body contains an unknown payload."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (reader.name() == QLatin1String("Header") &&
|
||||
reader.namespaceUri() == QLatin1String(SoapNamespace))
|
||||
{
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
if (reader.hasError())
|
||||
{
|
||||
setError(error, reader.errorString());
|
||||
return false;
|
||||
}
|
||||
if (!inBody || !hasPayload)
|
||||
{
|
||||
setError(error, QStringLiteral("SOAP response does not contain a payload."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user