

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of CMSDK version 2.1.1.260726 (26 July 2026)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2026
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  and is released under the following
//_/_/  
//_/_/  EXTENDED BSD License
//_/_/
//_/_/  Redistribution and use in source and binary forms, with or without
//_/_/  modification, is permitted provided that the following conditions 
//_/_/  are met:
//_/_/
//_/_/  - Redistributions of source code must retain the above copyright 
//_/_/    and collaboration notice, this list of conditions and the following 
//_/_/    disclaimer.
//_/_/
//_/_/  - Redistributions in binary form must reproduce the above copyright 
//_/_/    notice, this list of conditions and the following disclaimer in the 
//_/_/    documentation and/or other materials provided with the distribution.
//_/_/
//_/_/  - Neither the name of its copyright holders nor the names of its 
//_/_/    contributors may be used to endorse or promote products derived from 
//_/_/    this software without specific prior written permission.
//_/_/
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/
//_/_/  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//_/_/  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//_/_/  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//_/_/  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
//_/_/  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
//_/_/  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
//_/_/  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
//_/_/  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
//_/_/  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//_/_/  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
//_/_/  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
/*! @file RESTAPI.h
	@brief XML-defined REST API parsing: turn HTTP requests (or DataMessages) into validated RESTRequest objects.

	A RESTParser is initialised with an XML API definition (`<restapi>...`)
	describing named requests and their typed parameter sets. Incoming HTTPRequest
	objects (from NetworkProtocols.h) or internal DataMessages are then parsed and
	validated against that definition, producing RESTRequest instances whose typed
	getters expose the extracted parameters. RESTHTTPCall describes outbound HTTP
	calls generated from a request (templated URL, headers and body).
*/
#if !defined(_RESTAPI_H_)
#define _RESTAPI_H_

#include "NetworkProtocols.h"
#include "xml_parser.h"
#include "jsmn.h"

namespace cmlabs {

/*! @brief Definition of one parameter of a REST request, as declared in the API XML. */
struct RESTParam {
	enum Type { INTEGER, FLOAT, TEXT, ARRAY, IMAGE, VIDEO, BINARY } type; //!< Expected data type of the parameter.
	bool optional;   //!< true if the parameter may be omitted.
	bool allowEmpty; //!< true if an empty value is acceptable.
	std::string name;    //!< Parameter name.
	std::string objects; //!< For ARRAY parameters: the nested object/param-set name.
};


class RESTParser;
class RESTHTTPCall;

/*! @brief A parsed and validated REST request with typed access to its parameters.

	Produced by RESTParser::parseRequest(). Parameter values are stored in an
	internal DataMessage (@c storage, owned by the request); the getString()/
	getInt()/getData() family reads them back by name. Check getStatus() (or the
	public @c status member) before using the values; on failure getErrorString()
	explains why. Filters (getFilterString() etc.) carry optional query-filter
	key/values. Not thread-safe; use from one thread at a time.
*/
class RESTRequest {
	friend class RESTParser;
public:
	/*! Parse/validation outcome of the request. SUCCESS and SUCCESS_OPTIONS indicate a usable request. */
	enum Status {FAILED_EMPTY = 0, SUCCESS = 1, FAILED_DESCRIPTION = 2, FAILED_UNKNOWN_REQUEST = 3, FAILED_FORMAT = 4, FAILED_PARAM = 5, FAILED_AUTH = 6, SUCCESS_OPTIONS = 7} status;

protected:
	std::string origin;
	std::string signature;
	uint64 createTime;
	DataMessage* storage;
	bool requireAuthentication;
	std::map<std::string, std::string> filterMap;
	std::string errorString;
	RESTHTTPCall* httpCall;

	bool setStatus(Status status) {this->status = status; return true;}
	bool setOrigin(const char* origin) {this->origin = origin; return true;}
	bool setCreateTime(uint64 created) {this->createTime = created; return true;}
	bool setRequestSignature(const char* signature) {this->signature = signature; return true; }
	bool setRequireAuthentication(bool auth) {this->requireAuthentication = auth; return true; }

	bool setTime(const char* key, uint64 val) {return storage->setTime(key, val);}
	bool setInt(const char* key, int64 val) {return storage->setInt(key, val);}
	bool setDouble(const char* key, double val) {return storage->setDouble(key, val);}
	bool setString(const char* key, const char* val) {return storage->setString(key, val);}
	bool setBinary(const char* key, const char* data, uint32 size, const char* contentType) {
		if (!storage->setData(key, data, size))
			return false;
		return storage->setString(utils::StringFormat("%s_content_type_", key).c_str(), contentType);
	}

	bool addToErrorString(const char* errorLine) {errorString.length() ? errorString = utils::StringFormat("%s - %s", errorString.c_str(), errorLine) : errorString = errorLine; return true;}
	bool clearErrorState() { errorString.clear(); status=FAILED_EMPTY; return true; }

public:
	/*! Reconstruct a request from a DataMessage previously produced by getStorageMessageCopy().
		@param msg Source message; contents are copied/adopted into internal storage.
	*/
	RESTRequest(DataMessage* msg);
	/*! Create an empty request (status FAILED_EMPTY) with fresh internal storage. */
	RESTRequest() { createTime = 0; status = FAILED_EMPTY; storage = new DataMessage(); requireAuthentication = false; httpCall = NULL; }
	/*! Destructor; deletes the internal storage message. */
	virtual ~RESTRequest();

	/*! @return true if this request maps to an outbound HTTP call (RESTHTTPCall) rather than a local operation. */
	bool isHTTPCall() { return (httpCall != NULL); };
	/*! Generate the outbound HTTPRequest for an HTTP-call request.
		@return New HTTPRequest (caller takes ownership), or NULL if not an HTTP call.
	*/
	HTTPRequest* generateHTTPCallRequest();
	/*! @return The templated URL of the associated HTTP call, with parameters substituted; empty if none. */
	std::string getHTTPCallURL();

	/*! @return The parse/validation status of this request. */
	Status getStatus() {return status;}
	/*! Get the request signature (canonical "name(paramtypes)" string from the API definition).
		@param success Set to true if a signature is present.
		@return Signature string (valid while the request lives).
	*/
	const char* getSignature(bool& success) {success = (bool)signature.length(); return signature.c_str(); }

	/*! Get the request origin (e.g. remote address / source identifier).
		@param success Set to true if an origin was recorded. */
	const char* getOrigin(bool& success) {success = (bool)origin.length(); return origin.c_str(); }
	/*! Get the creation timestamp (PsyTime µs). @param success Set to true if recorded. */
	uint64 getCreateTime(bool& success) {success = (bool)createTime; return createTime; }

	/*! @name Typed parameter getters
		Read a named parameter extracted from the request. @a success is set to false
		if the key is absent or of the wrong type. Returned pointers reference the
		internal storage and remain valid for the life of the request (getDataCopy()
		returns a malloc'ed copy the caller must free).
		@{ */
	uint64 getTime(const char* key, bool& success) {return storage->getTime(key, success);}
	const char* getString(const char* key, bool& success) {return storage->getString(key, success);}
	int64 getInt(const char* key, bool& success) {return storage->getInt(key, success);}
	double getDouble(const char* key, bool& success) {return storage->getDouble(key, success);}
	const char* getData(const char* key, uint32& size, bool& success) {return storage->getData(key, size, success);}
	char* getDataCopy(const char* key, uint32& size, bool& success) {return storage->getDataCopy(key, size, success);}
	/*! @} */
	/*! @return true if the API definition requires authentication for this request. */
	bool getRequireAuthentication() {return this->requireAuthentication;}

	/*! @return Accumulated parse/validation error text (empty when status is SUCCESS). */
	const char* getErrorString() {return errorString.c_str();}

	/*! @name Filters
		Optional query-filter key/values attached to the request (e.g. from URL query
		parameters). Getters set @a success false when the key is absent.
		@{ */
	const char* getFilterString(const char* key, bool& success) { if (filterMap.find(key) == filterMap.end()) {success = false; return NULL; } else {success = true; return filterMap[key].c_str();} }
	int64 getFilterInt(const char* key, bool& success) { if (filterMap.find(key) == filterMap.end()) {success = false; return 0; } else {success = true; return utils::Ascii2Int64(filterMap[key].c_str());} }
	float64 getFilterFloat(const char* key, bool& success) { if (filterMap.find(key) == filterMap.end()) {success = false; return 0; } else {success = true; return utils::Ascii2Float64(filterMap[key].c_str());} }
	bool setFilter(const char* key, const char* val) {filterMap[key] = val; return true;}
	bool setFilter(const char* key, int64 val) {filterMap[key] = utils::StringFormat("%d", val); return true;}
	bool setFilter(const char* key, float64 val) {filterMap[key] = utils::StringFormat("%f", val); return true;}
	/*! @} */

	/*! @return A new copy of the internal storage message (caller takes ownership); can later rebuild the request via RESTRequest(DataMessage*). */
	DataMessage* getStorageMessageCopy();
};

/*! @brief Parses and validates incoming requests against an XML REST API definition.

	Initialise once with init() (the `<restapi>` XML), then call parseRequest() per
	incoming HTTPRequest or DataMessage. Returns a new RESTRequest per call (caller
	takes ownership); check RESTRequest::getStatus(). Owns the RESTHTTPCall objects
	defined in the API. init()/parseRequest() are not synchronised — initialise
	before serving and treat the parser as read-only afterwards.
*/
class RESTParser {
public:
	/*! Run the RESTParser unit tests. @return true if all tests pass. */
	static bool UnitTest();

	/*! Create an uninitialised parser; call init() before use. */
	RESTParser();
	/*! Destructor; deletes the owned RESTHTTPCall objects. */
	virtual ~RESTParser();

	/*! Load and validate the XML REST API definition.
		@param restDefinition XML text with `<restapi>` as the main tag.
		@return true on success; on failure see getLastErrorString().
	*/
	bool init(const char* restDefinition);

	/*! Parse an incoming HTTP request against the API definition.
		@param req The HTTP request to parse (not consumed).
		@return New RESTRequest (caller takes ownership; check its status), or NULL on internal error.
	*/
	RESTRequest* parseRequest(HTTPRequest* req);
	/*! Parse a request carried in a DataMessage against the API definition.
		@param msg The message to parse (not consumed).
		@return New RESTRequest (caller takes ownership; check its status), or NULL on internal error.
	*/
	RESTRequest* parseRequest(DataMessage* msg);

	/*! @return Description of the most recent init()/parse error. */
	const char* getLastErrorString() {return lastErrorString.c_str(); }

protected:
	XMLNode mainNode;
	std::string lastErrorString;
	std::map<std::string, std::map<std::string, RESTParam> > paramSets;
	std::map<std::string, RESTHTTPCall*> httpCalls;

	XMLNode findTopNodeForRequest(const char* requestString);
	std::map<std::string, RESTParam> parseParamSet(XMLNode objectNode);

	XMLNode parseRequest(const char* requestString, std::map<std::string, RESTParam> &paramSet, RESTRequest* restReq, std::string &signature);

	bool extractParameters(HTTPRequest* req, std::map<std::string, RESTParam> &paramSet, RESTRequest* restReq);
	bool extractParameters(DataMessage* msg, std::map<std::string, RESTParam> &paramSet, RESTRequest* restReq);

	bool extractArrayParameters(HTTPRequest* req, std::map<std::string, RESTParam> &paramSet, RESTRequest* restReq, int entryNum, const char* content, uint32 contentSize, const char* contentType);
	bool extractArrayParameters(DataMessage* msg, std::map<std::string, RESTParam> &paramSet, RESTRequest* restReq, int entryNum, const char* content, uint32 contentSize, const char* contentType);

	bool setParameterValue(HTTPRequest* req, RESTParam &param, std::string value, RESTRequest* restReq, uint32 entryNum = 0);
	bool setParameterValue(DataMessage* msg, RESTParam &param, std::string value, RESTRequest* restReq, uint32 entryNum = 0);
};


/*! @brief Definition of an outbound HTTP call template from the REST API XML.

	Describes how to turn a validated RESTRequest into an HTTPRequest: URL and
	header/body templates with parameter substitution. Instances are created and
	owned by RESTParser during init().
*/
class RESTHTTPCall {
public:
	/*! Create an empty call definition; populate with init(). */
	RESTHTTPCall();
	/*! Destructor. */
	~RESTHTTPCall();

	/*! Populate this call definition from its XML node in the API definition.
		@param objectNode XML node describing the HTTP call.
		@return true if the definition is valid.
	*/
	bool init(XMLNode objectNode);

	/*! Build the outbound HTTP request for a parsed REST request, substituting parameters into URL, headers and body.
		@param restReq The validated request supplying parameter values.
		@return New HTTPRequest (caller takes ownership), or NULL on failure.
	*/
	HTTPRequest* generateHTTPRequest(RESTRequest* restReq);

	std::string name;        //!< Call name from the API definition.
	std::string url;         //!< URL template (with parameter placeholders).
	uint8 ops;               //!< Allowed HTTP operations bitmask (GET/POST/...).
	std::string contentType;  //!< Content-Type for the request body.
	std::string cacheControl; //!< Cache-Control header value, if any.

	std::map<std::string, std::string> headerEntries; //!< Fixed header key/values.
	std::map<std::string, std::string> headerParams;  //!< Headers filled from request parameters.
	std::map<std::string, std::string> bodyEntries;   //!< Fixed body key/values.
	std::map<std::string, std::string> bodyParams;    //!< Body fields filled from request parameters.
	std::map<std::string, std::string> bodyContentTypes; //!< Per-body-part content types (e.g. multipart).
};


} // namespace cmlabs

#endif //_RESTAPI_H_

