<commit_before>#include "jbr.h"
#include <iowow/iwconv.h>
#include "ejdb2_internal.h"

#define FIO_INCLUDE_STR

#include <fio.h>
#include <fiobj.h>
#include <http/http.h>

// HTTP REST API:
//
// Access to HTTP endpoint can be optionally protected by a token specified in `EJDB_HTTP` options.
// In this case `X-Access-Token` header value must be provided in client request.
// If token is not provided `401` HTTP code will be in response.
// If access token doesn't matched a token provided by client `403` HTTP code will be in response.
//
// In any error case `500` error will be returned.
//
// `X-Hints` request header used to provide comma separated extra hints to ejdb2 database engine
//    Hints:
//      `explain` Show the query execution plan (indexes used).
//                Used in query execution POST `/` requests.
//                In this case instead of query resultset client will get query explanation text.
//
// Endpoints:
//
//    POST    `/{collection}`
//      Add a new document to the `collection`
//      Response:
//                200 with a new document identifier as int64 decimal number
//
//    PUT     `/{collection>/{id}`  Replaces/store document under specific numeric `id`
//      Response:
//                200 with empty body
//
//    DELETE  `/{collection}/{id}`
//      Removes document identified by `id` from a `collection`
//      Response:
//                200 on success
//                404 if document not found
//
//    PATCH   `/{collection}/{id}`
//      Patch a document identified by `id` by rfc7396,rfc6902 data.
//      Response:
//                200 on success
//
//    GET|HEAD `/{collection}/{id}`
//      Retrieve document identified by `id` from a `collection`
//      Response:
//                200 on success, document JSON
//                404 if document not found
//
//    POST `/`  - Query a collection by provided query spec as body
//      Response:
//               200 on success, HTTP chunked transfer, JSON documents separated by `\n`
//
//

static uint64_t x_access_token_hash;

typedef enum {
  JBR_GET = 1,
  JBR_PUT,
  JBR_PATCH,
  JBR_DELETE,
  JBR_POST,
  JBR_HEAD
} jbr_method_t;

struct _JBR {
  volatile bool terminated;
  volatile iwrc rc;
  pthread_t worker_thread;
  pthread_barrier_t start_barrier;
  const EJDB_HTTP *http;
  EJDB db;
};

typedef struct _JBRCTX {
  JBR jbr;
  http_s *req;
  jbr_method_t method;
  const char *collection;
  size_t collection_len;
  int64_t id;
  bool read_anon;
} JBRCTX;

#define JBR_RCREPORT(r_, rc_)                                                      \
  do {                                                                             \
    iwlog_ecode_error3(rc_);                                                       \
    const char *strerr = iwlog_ecode_explained(rc_);                               \
    jbr_http_send(r_, 500, "text/plain", strerr, strerr ? strlen(strerr) : 0);     \
  } while(0)

IW_INLINE void jbr_http_set_content_length(http_s *r, uintptr_t length) {
  static uint64_t cl_hash = 0;
  if (!cl_hash)
    cl_hash = fiobj_hash_string("content-length", 14);
  if (!fiobj_hash_get2(r->private_data.out_headers, cl_hash)) {
    fiobj_hash_set(r->private_data.out_headers, HTTP_HEADER_CONTENT_LENGTH,
                   fiobj_num_new(length));
  }
}
IW_INLINE void jbr_http_set_content_type(http_s *r, const char *ctype) {
  static uint64_t ct_hash = 0;
  if (!ct_hash)
    ct_hash = fiobj_hash_string("content-type", 12);
  if (!fiobj_hash_get2(r->private_data.out_headers, ct_hash)) {
    fiobj_hash_set(r->private_data.out_headers, HTTP_HEADER_CONTENT_TYPE,
                   fiobj_str_new(ctype, strlen(ctype)));
  }
}

static iwrc jbr_http_send(http_s *r, int status, const char *ctype, const char *body, int bodylen)  {
  if (!r || !r->private_data.out_headers) {
    iwlog_ecode_error3(IW_ERROR_INVALID_ARGS);
    return IW_ERROR_INVALID_ARGS;
  }
  r->status = status;
  if (ctype) {
    jbr_http_set_content_type(r, ctype);
  }
  if (http_send_body(r, (char *)body, bodylen)) {
    iwlog_ecode_error3(JBR_ERROR_RESPONSE_SENDING);
    return JBR_ERROR_RESPONSE_SENDING;
  }
  return 0;
}

IW_INLINE iwrc jbr_http_error_send(http_s *r, int status)  {
  return jbr_http_send(r, status, "text/plain", 0, 0);
}

IW_INLINE iwrc jbr_http_error_send2(http_s *r, int status, const char *ctype, const char *body, int bodylen)  {
  return jbr_http_send(r, status, ctype, body, bodylen);
}

static iwrc jbr_query_visitor(EJDB_EXEC *ctx, EJDB_DOC doc, int64_t *step) {
  return 0;
}

static void jbr_on_query(JBRCTX *rctx) {
  // todo
}

static void jbr_on_delete(JBRCTX *rctx) {
  if (rctx->read_anon) {
    jbr_http_error_send(rctx->req, 403);
    return;
  }
}

static void jbr_on_patch(JBRCTX *rctx) {
  if (rctx->read_anon) {
    jbr_http_error_send(rctx->req, 403);
    return;
  }
}

static void jbr_on_put(JBRCTX *rctx) {
  if (rctx->read_anon) {
    jbr_http_error_send(rctx->req, 403);
    return;
  }
}

static void jbr_on_get(JBRCTX *rctx) {
  JBL jbl;
  IWXSTR *xstr;
  EJDB db = rctx->jbr->db;
  http_s *req = rctx->req;

  iwrc rc = ejdb_get(db, rctx->collection, rctx->id, &jbl);
  if (rc == IW_ERROR_NOT_EXISTS) {
    jbr_http_error_send(req, 404);
    return;
  } else if (rc) {
    JBR_RCREPORT(req, rc);
    return;
  }
  xstr = iwxstr_new2(jbl->bn.size * 2);
  if (!xstr) {
    rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
    JBR_RCREPORT(req, rc);
    return;
  }
  rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, 0);
  if (rc) {
    JBR_RCREPORT(req, rc);
    goto finish;
  }
  jbl_destroy(&jbl);
  jbr_http_send(req, 200, "application/json", iwxstr_ptr(xstr), iwxstr_size(xstr));

finish:
  iwxstr_destroy(xstr);
}

static bool jbr_fill_ctx(http_s *req, JBRCTX *r) {
  JBR jbr = req->udata;
  memset(r, 0, sizeof(*r));
  r->req = req;
  r->jbr = jbr;
  fio_str_info_s method = fiobj_obj2cstr(req->method);
  switch (method.len) {
    case 3:
      if (!strncmp("GET", method.data, method.len)) {
        r->method = JBR_GET;
      } else if (!strncmp("PUT", method.data, method.len)) {
        r->method = JBR_PUT;
      }
      break;
    case 4:
      if (!strncmp("POST", method.data, method.len)) {
        r->method = JBR_POST;
      } else if (!strncmp("HEAD", method.data, method.len)) {
        r->method = JBR_HEAD;
      }
      break;
    case 5:
      if (!strncmp("PATCH", method.data, method.len)) {
        r->method = JBR_PATCH;
      }
      break;<commit_msg>* #213<commit_after>#include "jbr.h"
#include <iowow/iwconv.h>
#include "ejdb2_internal.h"

#define FIO_INCLUDE_STR

#include <fio.h>
#include <fiobj.h>
#include <http/http.h>

// HTTP REST API:
//
// Access to HTTP endpoint can be optionally protected by a token specified in `EJDB_HTTP` options.
// In this case `X-Access-Token` header value must be provided in client request.
// If token is not provided `401` HTTP code will be in response.
// If access token doesn't matched a token provided by client `403` HTTP code will be in response.
//
// In any error case `500` error will be returned.
//
// `X-Hints` request header used to provide comma separated extra hints to ejdb2 database engine
//    Hints:
//      `explain` Show the query execution plan (indexes used).
//                Used in query execution POST `/` requests.
//                In this case instead of query resultset client will get query explanation text.
//
// Endpoints:
//
//    POST    `/{collection}`
//      Add a new document to the `collection`
//      Response:
//                200 with a new document identifier as int64 decimal number
//
//    PUT     `/{collection>/{id}`  Replaces/store document under specific numeric `id`
//      Response:
//                200 with empty body
//
//    DELETE  `/{collection}/{id}`
//      Removes document identified by `id` from a `collection`
//      Response:
//                200 on success
//                404 if document not found
//
//    PATCH   `/{collection}/{id}`
//      Patch a document identified by `id` by rfc7396,rfc6902 data.
//      Response:
//                200 on success
//
//    GET|HEAD `/{collection}/{id}`
//      Retrieve document identified by `id` from a `collection`
//      Response:
//                200 on success, document JSON
//                404 if document not found
//
//    POST `/`  - Query a collection by provided query spec as body
//      Response:
//               200 on success, HTTP chunked transfer, JSON documents separated by `\n`
//
//

static uint64_t x_access_token_hash;

typedef enum {
  JBR_GET = 1,
  JBR_PUT,
  JBR_PATCH,
  JBR_DELETE,
  JBR_POST,
  JBR_HEAD
} jbr_method_t;

struct _JBR {
  volatile bool terminated;
  volatile iwrc rc;
  pthread_t worker_thread;
  pthread_barrier_t start_barrier;
  const EJDB_HTTP *http;
  EJDB db;
};

typedef struct _JBRCTX {
  JBR jbr;
  http_s *req;
  jbr_method_t method;
  const char *collection;
  size_t collection_len;
  int64_t id;
  bool read_anon;
} JBRCTX;

#define JBR_RCREPORT(r_, rc_)                                                      \
  do {                                                                             \
    iwlog_ecode_error3(rc_);                                                       \
    const char *strerr = iwlog_ecode_explained(rc_);                               \
    jbr_http_send(r_, 500, "text/plain", strerr, strerr ? strlen(strerr) : 0);     \
  } while(0)

IW_INLINE void jbr_http_set_content_length(http_s *r, uintptr_t length) {
  static uint64_t cl_hash = 0;
  if (!cl_hash)
    cl_hash = fiobj_hash_string("content-length", 14);
  if (!fiobj_hash_get2(r->private_data.out_headers, cl_hash)) {
    fiobj_hash_set(r->private_data.out_headers, HTTP_HEADER_CONTENT_LENGTH,
                   fiobj_num_new(length));
  }
}
IW_INLINE void jbr_http_set_content_type(http_s *r, const char *ctype) {
  static uint64_t ct_hash = 0;
  if (!ct_hash)
    ct_hash = fiobj_hash_string("content-type", 12);
  if (!fiobj_hash_get2(r->private_data.out_headers, ct_hash)) {
    fiobj_hash_set(r->private_data.out_headers, HTTP_HEADER_CONTENT_TYPE,
                   fiobj_str_new(ctype, strlen(ctype)));
  }
}

static iwrc jbr_http_send(http_s *r, int status, const char *ctype, const char *body, int bodylen)  {
  if (!r || !r->private_data.out_headers) {
    iwlog_ecode_error3(IW_ERROR_INVALID_ARGS);
    return IW_ERROR_INVALID_ARGS;
  }
  r->status = status;
  if (ctype) {
    jbr_http_set_content_type(r, ctype);
  }
  if (http_send_body(r, (char *)body, bodylen)) {
    iwlog_ecode_error3(JBR_ERROR_RESPONSE_SENDING);
    return JBR_ERROR_RESPONSE_SENDING;
  }
  return 0;
}

IW_INLINE iwrc jbr_http_error_send(http_s *r, int status)  {
  return jbr_http_send(r, status, "text/plain", 0, 0);
}

IW_INLINE iwrc jbr_http_error_send2(http_s *r, int status, const char *ctype, const char *body, int bodylen)  {
  return jbr_http_send(r, status, ctype, body, bodylen);
}

static iwrc jbr_query_visitor(EJDB_EXEC *ctx, EJDB_DOC doc, int64_t *step) {
  return 0;
}

static void jbr_on_query(JBRCTX *rctx) {
  // todo
}

static void jbr_on_delete(JBRCTX *rctx) {
  if (rctx->read_anon) {
    jbr_http_error_send(rctx->req, 403);
    return;
  }
}

static void jbr_on_patch(JBRCTX *rctx) {
  if (rctx->read_anon) {
    jbr_http_error_send(rctx->req, 403);
    return;
  }
}

static void jbr_on_put(JBRCTX *rctx) {
  if (rctx->read_anon) {
    jbr_http_error_send(rctx->req, 403);
    return;
  }
}

static void jbr_on_get(JBRCTX *rctx) {
  JBL jbl;
  IWXSTR *xstr;
  EJDB db = rctx->jbr->db;
  http_s *req = rctx->req;

  iwrc rc = ejdb_get(db, rctx->collection, rctx->id, &jbl);
  if (rc == IW_ERROR_NOT_EXISTS) {
    jbr_http_error_send(req, 404);
    return;
  } else if (rc) {
    JBR_RCREPORT(req, rc);
    return;
  }
  xstr = iwxstr_new2(jbl->bn.size * 2);
  if (!xstr) {
    rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
    JBR_RCREPORT(req, rc);
    return;
  }
  rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, 0);
  if (rc) {
    JBR_RCREPORT(req, rc);
    goto finish;
  }
  jbl_destroy(&jbl);
  jbr_http_send(req, 200, "application/json",
                req->method == JBR_HEAD ? 0 : iwxstr_ptr(xstr),
                iwxstr_size(xstr));
finish:
  iwxstr_destroy(xstr);
}

static bool jbr_fill_ctx(http_s *req, JBRCTX *r) {
  JBR jbr = req->udata;
  memset(r, 0, sizeof(*r));
  r->req = req;
  r->jbr = jbr;
  fio_str_info_s method = fiobj_obj2cstr(req->method);
  switch (method.len) {
    case 3:
      if (!strncmp("GET", method.data, method.len)) {
        r->method = JBR_GET;
      } else if (!strncmp("PUT", method.data, method.len)) {
        r->method = JBR_PUT;
      }
      break;
    case 4:
      if (!strncmp("POST", method.data, method.len)) {
        r->method = JBR_POST;
      } else if (!strncmp("HEAD", method.data, method.len)) {
        r->method = JBR_HEAD;
      }
      break;
    case 5:
      if (!strncmp("PATCH", method.data, method.len)) {
        r->method = JBR_PATCH;
      }
      break;
{'fraction_non_alphanumeric': 0.12077741786210087, 'fraction_numerical': 0.015733456732993985, 'mean_word_length': 2.5702092511013217, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 68, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2677990', 'n_tokens_mistral': 4960, 'n_tokens_neox': 4739, 'n_words': 1161}