Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This generator, from a webrpc schema/design file, will code-generate:

2. Implementation output
C JSON encode/decode helpers, generated method request / response handling,
and a self-contained `libcurl`-based HTTP transport/runtime.
and an optional self-contained `libcurl`-based HTTP transport/runtime.

The generated client is intended to speak to any webrpc server language
(Go, nodejs, etc.) as long as the schema features used are supported by this target.
Expand All @@ -21,10 +21,11 @@ The generated client is intended to speak to any webrpc server language
Generated `header` output only depends on the C standard library headers included by
the generated file.

Generated `impl` output currently depends on:
Generated `impl` output depends on:

- `cJSON`
- `libcurl`
- `libcurl`, unless the generated implementation is compiled with the
prefix-based no-curl guard

The generated code targets C99.

Expand Down Expand Up @@ -53,6 +54,33 @@ Dependency names can vary slightly by platform or package manager. The important
is that the generated implementation can include `<cjson/cJSON.h>` and link against
`libcurl` and `cJSON`.

To build generated implementation output without the built-in libcurl transport, define
`<PREFIX>_NO_CURL_TRANSPORT`, where `<PREFIX>` is the generated prefix uppercased. For
example, code generated with `-prefix=example` can be compiled with:

```bash
cc -std=c99 \
-DEXAMPLE_NO_CURL_TRANSPORT \
$(pkg-config --cflags libcjson) \
-c example.gen.c

cc -std=c99 \
-DEXAMPLE_NO_CURL_TRANSPORT \
app.c example.gen.c \
$(pkg-config --cflags --libs libcjson) \
-o app
```

No-curl mode removes the generated implementation's libcurl include, types, and link
dependency. It does not remove the `cJSON` dependency because generated JSON
encode/decode and response parsing still use `cJSON`.

The lower-level request/response helpers remain available in no-curl mode:
`example_<service>_<method>_prepare_request(...)` builds a prepared request and
`example_<service>_<method>_parse_response(...)` parses an HTTP response supplied by
your own transport. Runtime and client functions still link; send attempts fail with a
`TransportError` indicating that the built-in curl transport is disabled.

Because the generated implementation uses `cJSON`, exact large 64-bit integer handling
follows `cJSON`'s numeric behavior. If your API needs exact integer round-tripping beyond
normal JSON number precision expectations, prefer `bigint` in the schema instead of
Expand All @@ -75,7 +103,7 @@ The current generator supports:
- prepare request bytes without sending them
- send a prepared request with the generated transport
- parse a raw HTTP response into generated response types
- generated `libcurl` client configuration for bearer auth, custom headers, and timeouts
- generated client configuration for bearer auth, custom headers, timeouts, and bounded response buffering

## Limitations

Expand All @@ -87,6 +115,11 @@ The current generator does not support:
- a shared external transport abstraction; the generated runtime is currently self-contained
- automatic redirect following

Generated client options include `max_response_bytes`, which bounds the response body
buffer used by the built-in curl transport. `*_client_options_init(...)` defaults this
to `1024 * 1024` bytes. Leaving it zero is treated the same as the default, not as an
unlimited response size.

Implementation generation also assumes a companion generated header include via
`-header=<file>`.

Expand Down
91 changes: 86 additions & 5 deletions _examples/smoke/example.gen.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@

#include "example.gen.h"

#ifndef SMOKE_NO_CURL_TRANSPORT
#include <curl/curl.h>
#endif
#include <cjson/cJSON.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>

#ifndef SMOKE_NO_CURL_TRANSPORT
typedef struct {
char *data;
size_t len;
size_t cap;
size_t max_response_bytes;
} smoke_buffer;
#endif

#if defined(__GNUC__) || defined(__clang__)
#define SMOKE_JSON_UNUSED __attribute__((unused))
Expand Down Expand Up @@ -133,8 +138,16 @@ static void smoke_set_error(
error->cause = cause ? smoke_strdup(cause) : NULL;
}

static int smoke_buffer_grow(smoke_buffer *buf, size_t need) {
#ifndef SMOKE_NO_CURL_TRANSPORT
static size_t smoke_default_max_response_bytes(void) {
return (size_t)1024 * 1024;
}

static int smoke_buffer_grow(smoke_buffer *buf, size_t need, size_t max_response_bytes) {
size_t max_cap;
if (!buf) return 0;
max_cap = max_response_bytes == SIZE_MAX ? SIZE_MAX : max_response_bytes + 1;
if (need > max_cap) return 0;
if (buf->cap >= need) return 1;
size_t new_cap = buf->cap ? buf->cap : 1024;
while (new_cap < need) {
Expand All @@ -144,6 +157,9 @@ static int smoke_buffer_grow(smoke_buffer *buf, size_t need) {
}
new_cap *= 2;
}
if (new_cap > max_cap) {
new_cap = max_cap;
}
char *next = (char *)realloc(buf->data, new_cap);
if (!next) return 0;
buf->data = next;
Expand All @@ -154,12 +170,18 @@ static int smoke_buffer_grow(smoke_buffer *buf, size_t need) {
static size_t smoke_write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
smoke_buffer *buf = (smoke_buffer *)userdata;
size_t n;
size_t need;
size_t max_response_bytes;

if (!buf) return 0;
if (size != 0 && nmemb > SIZE_MAX / size) return 0;
n = size * nmemb;
if (n > SIZE_MAX - 1) return 0;
if (buf->len > SIZE_MAX - n - 1) return 0;
if (!smoke_buffer_grow(buf, buf->len + n + 1)) return 0;
need = buf->len + n + 1;
max_response_bytes = buf->max_response_bytes > 0 ? buf->max_response_bytes : smoke_default_max_response_bytes();
if (need - 1 > max_response_bytes) return 0;
if (!smoke_buffer_grow(buf, need, max_response_bytes)) return 0;
memcpy(buf->data + buf->len, ptr, n);
buf->len += n;
buf->data[buf->len] = '\0';
Expand Down Expand Up @@ -378,6 +400,7 @@ static int smoke_http_send_request(
const char *bearer_token,
const struct curl_slist *default_headers,
long timeout_ms,
size_t max_response_bytes,
smoke_http_response *response,
smoke_error *error
) {
Expand All @@ -396,6 +419,7 @@ static int smoke_http_send_request(
}
smoke_http_response_init(&result);
memset(&buf, 0, sizeof(buf));
buf.max_response_bytes = max_response_bytes > 0 ? max_response_bytes : smoke_default_max_response_bytes();

curl = curl_easy_init();
if (!curl) {
Expand Down Expand Up @@ -458,6 +482,15 @@ static int smoke_http_send_request(
}
return rc;
}
#else
int smoke_runtime_init(smoke_error *error) {
(void)error;
return 0;
}

void smoke_runtime_cleanup(void) {
}
#endif

static void smoke_parse_rpc_error(const char *body, long http_status, smoke_error *error) {
cJSON *error_name = NULL;
Expand Down Expand Up @@ -931,11 +964,13 @@ int smoke_smoke_echo_parse_response(
return rc;
}

#ifndef SMOKE_NO_CURL_TRANSPORT
struct smoke_smoke_client {
char *base_url;
char *bearer_token;
struct curl_slist *default_headers;
long timeout_ms;
size_t max_response_bytes;
};

static void smoke_smoke_client_free_config_parts(char **bearer_token, struct curl_slist **default_headers) {
Expand All @@ -953,23 +988,29 @@ static void smoke_smoke_client_reset_config(smoke_smoke_client *client) {
if (!client) return;
smoke_smoke_client_free_config_parts(&client->bearer_token, &client->default_headers);
client->timeout_ms = 10000L;
client->max_response_bytes = (size_t)1024 * 1024;
}

static int smoke_smoke_client_build_config(
const smoke_client_options *options,
char **bearer_token,
struct curl_slist **default_headers,
long *timeout_ms
long *timeout_ms,
size_t *max_response_bytes
) {
if (!bearer_token || !default_headers || !timeout_ms) return 0;
if (!bearer_token || !default_headers || !timeout_ms || !max_response_bytes) return 0;
*bearer_token = NULL;
*default_headers = NULL;
*timeout_ms = 10000L;
*max_response_bytes = (size_t)1024 * 1024;
if (!options) return 1;

if (options->timeout_ms > 0) {
*timeout_ms = options->timeout_ms;
}
if (options->max_response_bytes > 0) {
*max_response_bytes = options->max_response_bytes;
}

if (options->bearer_token) {
*bearer_token = smoke_strdup(options->bearer_token);
Expand All @@ -991,23 +1032,26 @@ static int smoke_smoke_client_build_config(
fail:
smoke_smoke_client_free_config_parts(bearer_token, default_headers);
*timeout_ms = 10000L;
*max_response_bytes = (size_t)1024 * 1024;
return 0;
}

int smoke_smoke_client_configure(smoke_smoke_client *client, const smoke_client_options *options) {
char *next_bearer_token = NULL;
struct curl_slist *next_default_headers = NULL;
long next_timeout_ms = 10000L;
size_t next_max_response_bytes = (size_t)1024 * 1024;

if (!client) return 0;
if (!smoke_smoke_client_build_config(options, &next_bearer_token, &next_default_headers, &next_timeout_ms)) {
if (!smoke_smoke_client_build_config(options, &next_bearer_token, &next_default_headers, &next_timeout_ms, &next_max_response_bytes)) {
return 0;
}

smoke_smoke_client_reset_config(client);
client->bearer_token = next_bearer_token;
client->default_headers = next_default_headers;
client->timeout_ms = next_timeout_ms;
client->max_response_bytes = next_max_response_bytes;
return 1;
}

Expand Down Expand Up @@ -1056,10 +1100,47 @@ int smoke_smoke_client_send_prepared_request(
client->bearer_token,
client->default_headers,
client->timeout_ms,
client->max_response_bytes,
response,
error
);
}
#else
struct smoke_smoke_client {
int placeholder;
};

smoke_smoke_client *smoke_smoke_client_create(const char *base_url, const smoke_client_options *options) {
smoke_smoke_client *client;
(void)base_url;
(void)options;
client = (smoke_smoke_client *)calloc(1, sizeof(*client));
return client;
}

void smoke_smoke_client_destroy(smoke_smoke_client *client) {
free(client);
}

int smoke_smoke_client_configure(smoke_smoke_client *client, const smoke_client_options *options) {
(void)options;
return client ? 1 : 0;
}

int smoke_smoke_client_send_prepared_request(
smoke_smoke_client *client,
const smoke_prepared_request *request,
smoke_http_response *response,
smoke_error *error
) {
if (!client || !request || !response) {
smoke_set_error(error, 0, 0, "ClientError", "client, request, and response must be non-NULL", NULL);
return -1;
}
smoke_set_error(error, 0, 0, "TransportError", "built-in curl transport is disabled", NULL);
return -1;
}
#endif
int smoke_smoke_echo(
smoke_smoke_client *client,
const smoke_smoke_echo_request *request,
Expand Down
6 changes: 4 additions & 2 deletions _examples/smoke/example.gen.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ typedef struct {
const char * const *headers;
size_t headers_count;
long timeout_ms;
size_t max_response_bytes;
} smoke_client_options;

static inline char *smoke_strdup(const char *value) {
Expand Down Expand Up @@ -204,6 +205,7 @@ static inline void smoke_client_options_init(smoke_client_options *options) {
if (!options) return;
memset(options, 0, sizeof(*options));
options->timeout_ms = 10000L;
options->max_response_bytes = (size_t)1024 * 1024;
}


Expand Down Expand Up @@ -340,9 +342,9 @@ static inline void smoke_smoke_echo_response_free(smoke_smoke_echo_response *val
}
memset(value, 0, sizeof(*value));
}
/* Must be called before using the generated libcurl client runtime. */
/* Must be called before using the generated client runtime. */
int smoke_runtime_init(smoke_error *error);
/* Call after the last use of the generated libcurl client runtime. */
/* Call after the last use of the generated client runtime. */
void smoke_runtime_cleanup(void);
typedef struct smoke_smoke_client smoke_smoke_client;

Expand Down
4 changes: 2 additions & 2 deletions client.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
{{- $prefix := .Prefix -}}
{{- $services := .Services -}}

/* Must be called before using the generated libcurl client runtime. */
/* Must be called before using the generated client runtime. */
int {{ printf "%s_runtime_init" $prefix }}({{ printf "%s_error" $prefix }} *error);
/* Call after the last use of the generated libcurl client runtime. */
/* Call after the last use of the generated client runtime. */
void {{ printf "%s_runtime_cleanup" $prefix }}(void);

{{- range $_, $service := $services }}
Expand Down
Loading
Loading