view lib/apic.c @ 20:f98ea578b1ef

misc: revamp database
author David Demelier <markand@malikania.fr>
date Tue, 19 Jul 2022 21:52:42 +0200
parents de4bf839b565
children dd078aea5d02
line wrap: on
line source

#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include <curl/curl.h>

#include "apic.h"
#include "types.h"
#include "util.h"

struct curlpack {
	CURL *curl;
	CURLcode code;
	struct curl_slist *headers;
};

struct converter {
	void *data;
	size_t datasz;
	int (*unpack)(void *, size_t, json_t *);
	json_t *(*pack)(const void *, size_t);
};

struct apiconf apiconf = {
	.baseurl = "http://127.0.0.1"
};

static size_t
writer(char *in, size_t n, size_t w, FILE *fp)
{
	if (fwrite(in, n, w, fp) != w)
		return 0;

	return w;
}

static inline char *
create_url(const char *fmt, va_list args)
{
	static _Thread_local char ret[256];
	char page[128];
	va_list ap;

	ret[0] = 0;
	va_copy(ap, args);
	vsnprintf(page, sizeof (page), fmt, ap);
	va_end(ap);

	snprintf(ret, sizeof (ret), "%s/%s", apiconf.baseurl, page);

	return ret;
}

static inline FILE *
create_file(char **buf, size_t *bufsz)
{
	FILE *fp;

	*buf = NULL;
	*bufsz = 0;

	if (!(fp = open_memstream(buf, bufsz)))
		util_die("open_memstream: %s\n", strerror(errno));

	return fp;
}

static struct curlpack
create_curl(FILE *fp, const char *body, const char *url)
{
	struct curlpack pack = {0};

	pack.headers = curl_slist_append(pack.headers, "Content-Type: application/json");
	pack.curl = curl_easy_init();
	curl_easy_setopt(pack.curl, CURLOPT_HTTPHEADER, pack.headers);
	curl_easy_setopt(pack.curl, CURLOPT_URL, url);
	curl_easy_setopt(pack.curl, CURLOPT_FOLLOWLOCATION, 1L);
	curl_easy_setopt(pack.curl, CURLOPT_TIMEOUT, 3L);
	curl_easy_setopt(pack.curl, CURLOPT_WRITEFUNCTION, writer);
	curl_easy_setopt(pack.curl, CURLOPT_WRITEDATA, fp);
	curl_easy_setopt(pack.curl, CURLOPT_NOSIGNAL, 1L);

	/* Assume POST request if there is a body. */
	if (body) {
		curl_easy_setopt(pack.curl, CURLOPT_POSTFIELDS, body);
		curl_easy_setopt(pack.curl, CURLOPT_POSTFIELDSIZE, strlen(body));
	}

	pack.code = curl_easy_perform(pack.curl);

	return pack;
}

static int
perform(struct apic *req, const char *body, const char *fmt, va_list ap)
{
	FILE *fp;
	char *response, *url;
	size_t responsesz;
	json_error_t error;
	int ret = -1;
	struct curlpack curl;

	memset(req, 0, sizeof (*req));

	url  = create_url(fmt, ap);
	fp   = create_file(&response, &responsesz);
	curl = create_curl(fp, body, url);

	/* Perform that request now. */
	fclose(fp);

	if (curl.code != CURLE_OK)
		snprintf(req->error, sizeof (req->error), "%s", curl_easy_strerror(curl.code));
	else {
		curl_easy_getinfo(curl.curl, CURLINFO_RESPONSE_CODE, &req->status);

		if (req->status != 200)
			snprintf(req->error, sizeof (req->error), "HTTP returned %ld", req->status);
		if (response[0] && !(req->doc = json_loads(response, 0, &error)))
			snprintf(req->error, sizeof (req->error), "JSON parse error: %s", error.text);
		else
			ret = 0;
	}

	curl_easy_cleanup(curl.curl);
	curl_slist_free_all(curl.headers);

	free(response);

	return ret;
}

int
apic_get(struct apic *req, const char *fmt, ...)
{
	assert(req);
	assert(fmt);

	va_list ap;
	int ret;

	va_start(ap, fmt);
	ret = perform(req, NULL, fmt, ap);
	va_end(ap);

	return ret;
}

int
apic_post(struct apic *req, const json_t *doc, const char *fmt, ...)
{
	assert(req);
	assert(fmt);

	va_list ap;
	int ret;
	char *body;

	if (!(body = json_dumps(doc, JSON_COMPACT)))
		util_die("%s", strerror(ENOMEM));

	va_start(ap, fmt);
	ret = perform(req, body, fmt, ap);
	va_end(ap);

	free(body);

	return ret;
}

static int
get(struct apic *req, const struct converter *cv, const char *fmt, ...)
{
	va_list ap;
	int ret;

	va_start(ap, fmt);
	ret = perform(req, NULL, fmt, ap);
	va_end(ap);

	memset(req, 0, sizeof (*req));

	if (ret < 0)
		return -1;
	if (!req->doc || !json_is_object(req->doc))
		return snprintf(req->error, sizeof (req->error), "invalid JSON object received"), -1;
	if (cv->unpack(cv->data, cv->datasz, req->doc) < 0)
		return snprintf(req->error, sizeof (req->error), "%s", strerror(errno));

	return 0;
}

static int
create(struct apic *req, const struct converter *cv, const char *fmt, ...)
{
	va_list ap;
	int ret;
	json_t *doc;
	char *body;

	memset(req, 0, sizeof (*req));

	if (!(doc = cv->pack(cv->data, cv->datasz)))
		return snprintf(req->error, sizeof (req->error), "%s", strerror(errno));
	if (!(body = json_dumps(doc, JSON_COMPACT))) {
		json_decref(doc);
		return snprintf(req->error, sizeof (req->error), "%s", strerror(errno));
	}

	va_start(ap, fmt);
	ret = perform(req, body, fmt, ap);
	va_end(ap);
	json_decref(doc);
	free(body);

	if (cv->unpack(cv->data, cv->datasz, req->doc) < 0)
		return snprintf(req->error, sizeof (req->error), "%s", strerror(errno));

	return 0;
}

static json_t *
wrap_job_to(const void *data, size_t datasz)
{
	return job_to(data, datasz);
}

static int
wrap_job_from(void *data, size_t datasz, json_t *doc)
{
	return job_from(data, datasz, doc);
}

static json_t *
wrap_jobresult_to(const void *data, size_t datasz)
{
	return jobresult_to(data, datasz);
}

static int
wrap_project_from(void *data, size_t datasz, json_t *doc)
{
	return project_from(data, datasz, doc);
}

static json_t *
wrap_project_to(const void *data, size_t datasz)
{
	return project_to(data, datasz);
}

static int
wrap_worker_from(void *data, size_t datasz, json_t *doc)
{
	return worker_from(data, datasz, doc);
}

static json_t *
wrap_worker_to(const void *data, size_t datasz)
{
	return worker_to(data, datasz);
}

static int
wrap_jobresult_from(void *data, size_t datasz, json_t *doc)
{
	return jobresult_from(data, datasz, doc);
}

int
apic_job_add(struct apic *req, struct job *job)
{
	assert(req);
	assert(job);

	const struct converter cv = {
		.data = job,
		.datasz = 1,
		.pack = wrap_job_to,
		.unpack = wrap_job_from
	};

	return create(req, &cv, "api/v1/jobs");
}

ssize_t
apic_job_todo(struct apic *req, struct job *jobs, size_t jobsz, int worker_id)
{
	assert(req);
	assert(jobs);

	struct converter cv = {
		.data = jobs,
		.datasz = 1,
		.unpack = wrap_job_from
	};

	return get(req, &cv, "api/v1/jobs/%d", worker_id);
}

int
apic_jobresult_add(struct apic *req, struct jobresult *result)
{
	assert(req);
	assert(result);

	struct converter cv = {
		.data = result,
		.datasz = 1,
		.pack = wrap_jobresult_to,
		.unpack = wrap_jobresult_from
	};

	return create(req, &cv, "api/v1/jobresults");
}

int
apic_project_add(struct apic *req, struct project *project)
{
	assert(req);
	assert(project);

	struct converter cv = {
		.data = project,
		.datasz = 1,
		.pack = wrap_project_to,
		.unpack = wrap_project_from
	};

	return create(req, &cv, "api/v1/projects");
}

int
apic_project_update(struct apic *req, struct project *project)
{
	assert(req);
	assert(project);

	struct converter cv = {
		.data = project,
		.datasz = 1,
		.pack = wrap_project_to,
		.unpack = wrap_project_from
	};

	return create(req, &cv, "api/v1/projects");
}

ssize_t
apic_project_list(struct apic *req, struct project *projects, size_t projectsz)
{
	assert(req);
	assert(projects);

	struct converter cv = {
		.data = projects,
		.datasz = projectsz,
		.unpack = wrap_project_from
	};

	return get(req, &cv, "api/v1/projects");
}

int
apic_project_find(struct apic *req, struct project *project)
{
	assert(req);
	assert(project);

	struct converter cv = {
		.data = project,
		.datasz = 1,
		.unpack = wrap_project_from
	};

	return get(req, &cv, "api/v1/projects/%s", project->name);
}

int
apic_project_find_id(struct apic *req, struct project *project)
{
	assert(req);
	assert(project);

	struct converter cv = {
		.data = project,
		.datasz = 1,
		.unpack = wrap_project_from
	};

	return get(req, &cv, "api/v1/projects/%jd", project->id);
}

int
apic_worker_add(struct apic *req, struct worker *wk)
{
	assert(req);
	assert(wk);

	struct converter cv = {
		.data = wk,
		.datasz = 1,
		.pack = wrap_worker_to
	};

	return create(req, &cv, "api/v1/workers");
}

ssize_t
apic_worker_list(struct apic *req, struct worker *wk, size_t wksz)
{
	assert(req);
	assert(wk);
	assert(wksz);

	struct converter cv = {
		.data = wk,
		.datasz = wksz,
		.unpack = wrap_worker_from
	};

	return get(req, &cv, "api/v1/workers");
}

int
apic_worker_find(struct apic *req, struct worker *wk)
{
	assert(req);
	assert(wk);

	struct converter cv = {
		.data = wk,
		.datasz = 1,
		.unpack = wrap_worker_from
	};

	return get(req, &cv, "api/v1/workers/%s", wk->name);
}

int
apic_worker_find_id(struct apic *req, struct worker *wk)
{
	assert(req);
	assert(wk);

	struct converter cv = {
		.data = wk,
		.datasz = 1,
		.unpack = wrap_worker_from
	};

	return get(req, &cv, "api/v1/workers/%jd", wk->id);
}