blob: 49757baa8fb62ff3ff617e1a8698491e7a8361d0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
#include <errno.h>
#include <avro/platform.h>
#include <stdlib.h>
#include <string.h>
#include "avro/allocation.h"
#include "avro/errors.h"
#include "avro/legacy.h"
#include "avro/schema.h"
#include "avro/value.h"
#include "avro_private.h"
#include "jansson.h"
/*
* Converts a binary buffer into a NUL-terminated JSON UTF-8 string.
* Avro bytes and fixed values are encoded in JSON as a string, and JSON
* strings must be in UTF-8. For these Avro types, the JSON string is
* restricted to the characters U+0000..U+00FF, which corresponds to the
* ISO-8859-1 character set. This function performs this conversion.
* The resulting string must be freed using avro_free when you're done
* with it.
*/
static int
encode_utf8_bytes(const void *src, size_t src_len,
void **dest, size_t *dest_len)
{
check_param(EINVAL, src, "source");
check_param(EINVAL, dest, "dest");
check_param(EINVAL, dest_len, "dest_len");
// First, determine the size of the resulting UTF-8 buffer.
// Bytes in the range 0x00..0x7f will take up one byte; bytes in
// the range 0x80..0xff will take up two.
const uint8_t *src8 = (const uint8_t *) src;
size_t utf8_len = src_len + 1; // +1 for NUL terminator
size_t i;
for (i = 0; i < src_len; i++) {
if (src8[i] & 0x80) {
utf8_len++;
}
}
// Allocate a new buffer for the UTF-8 string and fill it in.
uint8_t *dest8 = (uint8_t *) avro_malloc(utf8_len);
if (dest8 == NULL) {
avro_set_error("Cannot allocate JSON bytes buffer");
return ENOMEM;
}
uint8_t *curr = dest8;
for (i = 0; i < src_len; i++) {
if (src8[i] & 0x80) {
*curr++ = (0xc0 | (src8[i] >> 6));
*curr++ = (0x80 | (src8[i] & 0x3f));
} else {
*curr++ = src8[i];
}
}
*curr = '\0';
// And we're good.
*dest = dest8;
*dest_len = utf8_len;
return 0;
}
#define return_json(type, exp) \
{ \
json_t *result = exp; \
if (result == NULL) { \
avro_set_error("Cannot allocate JSON " type); \
} \
return result; \
}
#define check_return(retval, call) \
do { \
int __rc; \
__rc = call; \
if (__rc != 0) { \
return retval; \
} \
} while (0)
static json_t *
avro_value_to_json_t(const avro_value_t *value)
{
switch (avro_value_get_type(value)) {
case AVRO_BOOLEAN:
{
int val;
check_return(NULL, avro_value_get_boolean(value, &val));
return_json("boolean",
val? json_true(): json_false());
}
case AVRO_BYTES:
{
const void *val;
size_t size;
void *encoded = NULL;
size_t encoded_size = 0;
check_return(NULL, avro_value_get_bytes(value, &val, &size));
if (encode_utf8_bytes(val, size, &encoded, &encoded_size)) {
return NULL;
}
json_t *result = json_string_nocheck((const char *) encoded);
avro_free(encoded, encoded_size);
if (result == NULL) {
avro_set_error("Cannot allocate JSON bytes");
}
return result;
}
case AVRO_DOUBLE:
{
double val;
check_return(NULL, avro_value_get_double(value, &val));
return_json("double", json_real(val));
}
case AVRO_FLOAT:
{
float val;
check_return(NULL, avro_value_get_float(value, &val));
return_json("float", json_real(val));
}
case AVRO_INT32:
{
int32_t val;
check_return(NULL, avro_value_get_int(value, &val));
return_json("int", json_integer(val));
}
case AVRO_INT64:
{
int64_t val;
check_return(NULL, avro_value_get_long(value, &val));
return_json("long", json_integer(val));
}
case AVRO_NULL:
{
check_return(NULL, avro_value_get_null(value));
return_json("null", json_null());
}
case AVRO_STRING:
{
const char *val;
size_t size;
check_return(NULL, avro_value_get_string(value, &val, &size));
return_json("string", json_string(val));
}
case AVRO_ARRAY:
{
int rc;
size_t element_count, i;
json_t *result = json_array();
if (result == NULL) {
avro_set_error("Cannot allocate JSON array");
return NULL;
}
rc = avro_value_get_size(value, &element_count);
if (rc != 0) {
json_decref(result);
return NULL;
}
for (i = 0; i < element_count; i++) {
avro_value_t element;
rc = avro_value_get_by_index(value, i, &element, NULL);
if (rc != 0) {
json_decref(result);
return NULL;
}
json_t *element_json = avro_value_to_json_t(&element);
if (element_json == NULL) {
json_decref(result);
return NULL;
}
if (json_array_append_new(result, element_json)) {
avro_set_error("Cannot append element to array");
json_decref(result);
return NULL;
}
}
return result;
}
case AVRO_ENUM:
{
avro_schema_t enum_schema;
int symbol_value;
const char *symbol_name;
check_return(NULL, avro_value_get_enum(value, &symbol_value));
enum_schema = avro_value_get_schema(value);
symbol_name = avro_schema_enum_get(enum_schema, symbol_value);
return_json("enum", json_string(symbol_name));
}
case AVRO_FIXED:
{
const void *val;
size_t size;
void *encoded = NULL;
size_t encoded_size = 0;
check_return(NULL, avro_value_get_fixed(value, &val, &size));
if (encode_utf8_bytes(val, size, &encoded, &encoded_size)) {
return NULL;
}
json_t *result = json_string_nocheck((const char *) encoded);
avro_free(encoded, encoded_size);
if (result == NULL) {
avro_set_error("Cannot allocate JSON fixed");
}
return result;
}
case AVRO_MAP:
{
int rc;
size_t element_count, i;
json_t *result = json_object();
if (result == NULL) {
avro_set_error("Cannot allocate JSON map");
return NULL;
}
rc = avro_value_get_size(value, &element_count);
if (rc != 0) {
json_decref(result);
return NULL;
}
for (i = 0; i < element_count; i++) {
const char *key;
avro_value_t element;
rc = avro_value_get_by_index(value, i, &element, &key);
if (rc != 0) {
json_decref(result);
return NULL;
}
json_t *element_json = avro_value_to_json_t(&element);
if (element_json == NULL) {
json_decref(result);
return NULL;
}
if (json_object_set_new(result, key, element_json)) {
avro_set_error("Cannot append element to map");
json_decref(result);
return NULL;
}
}
return result;
}
case AVRO_RECORD:
{
int rc;
size_t field_count, i;
json_t *result = json_object();
if (result == NULL) {
avro_set_error("Cannot allocate new JSON record");
return NULL;
}
rc = avro_value_get_size(value, &field_count);
if (rc != 0) {
json_decref(result);
return NULL;
}
for (i = 0; i < field_count; i++) {
const char *field_name;
avro_value_t field;
rc = avro_value_get_by_index(value, i, &field, &field_name);
if (rc != 0) {
json_decref(result);
return NULL;
}
json_t *field_json = avro_value_to_json_t(&field);
if (field_json == NULL) {
json_decref(result);
return NULL;
}
if (json_object_set_new(result, field_name, field_json)) {
avro_set_error("Cannot append field to record");
json_decref(result);
return NULL;
}
}
return result;
}
case AVRO_UNION:
{
int disc;
avro_value_t branch;
avro_schema_t union_schema;
avro_schema_t branch_schema;
const char *branch_name;
check_return(NULL, avro_value_get_current_branch(value, &branch));
if (avro_value_get_type(&branch) == AVRO_NULL) {
return_json("null", json_null());
}
check_return(NULL, avro_value_get_discriminant(value, &disc));
union_schema = avro_value_get_schema(value);
branch_schema =
avro_schema_union_branch(union_schema, disc);
branch_name = avro_schema_type_name(branch_schema);
json_t *result = json_object();
if (result == NULL) {
avro_set_error("Cannot allocate JSON union");
return NULL;
}
json_t *branch_json = avro_value_to_json_t(&branch);
if (branch_json == NULL) {
json_decref(result);
return NULL;
}
if (json_object_set_new(result, branch_name, branch_json)) {
avro_set_error("Cannot append branch to union");
json_decref(result);
return NULL;
}
return result;
}
default:
return NULL;
}
}
int
avro_value_to_json(const avro_value_t *value,
int one_line, char **json_str)
{
check_param(EINVAL, value, "value");
check_param(EINVAL, json_str, "string buffer");
json_t *json = avro_value_to_json_t(value);
if (json == NULL) {
return ENOMEM;
}
/*
* Jansson will only encode an object or array as the root
* element.
*/
*json_str = json_dumps
(json,
JSON_ENCODE_ANY |
JSON_INDENT(one_line? 0: 2) |
JSON_ENSURE_ASCII |
JSON_PRESERVE_ORDER);
json_decref(json);
return 0;
}
int
avro_datum_to_json(const avro_datum_t datum,
int one_line, char **json_str)
{
avro_value_t value;
avro_datum_as_value(&value, datum);
return avro_value_to_json(&value, one_line, json_str);
}