subrepo: subdir: "deps/nats.c" merged: "66cec7f" upstream: origin: "https://github.com/nats-io/nats.c.git" branch: "v3.6.1" commit: "66cec7f" git-subrepo: version: "0.4.6" commit: "b8b46501e"
3662 lines
113 KiB
C
3662 lines
113 KiB
C
// Copyright 2021-2022 The NATS Authors
|
|
// Licensed 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 "natsp.h"
|
|
#include "mem.h"
|
|
#include "util.h"
|
|
#include "js.h"
|
|
|
|
typedef enum
|
|
{
|
|
jsStreamActionCreate = 1,
|
|
jsStreamActionUpdate,
|
|
jsStreamActionGet,
|
|
|
|
} jsStreamAction;
|
|
|
|
typedef struct apiPaged
|
|
{
|
|
int64_t total;
|
|
int64_t offset;
|
|
int64_t limit;
|
|
|
|
} apiPaged;
|
|
|
|
static natsStatus
|
|
_marshalTimeUTC(natsBuffer *buf, const char *fieldName, int64_t timeUTC)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
char dbuf[36] = {'\0'};
|
|
|
|
s = nats_EncodeTimeUTC(dbuf, sizeof(dbuf), timeUTC);
|
|
if (s != NATS_OK)
|
|
return nats_setError(NATS_ERR, "unable to encode data for field '%s' value %" PRId64, fieldName, timeUTC);
|
|
|
|
s = natsBuf_Append(buf, ",\"", -1);
|
|
IFOK(s, natsBuf_Append(buf, fieldName, -1));
|
|
IFOK(s, natsBuf_Append(buf, "\":\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, dbuf, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
//
|
|
// Stream related functions
|
|
//
|
|
|
|
static natsStatus
|
|
_checkStreamName(const char *stream)
|
|
{
|
|
if (nats_IsStringEmpty(stream))
|
|
return nats_setError(NATS_INVALID_ARG, "%s", jsErrStreamNameRequired);
|
|
|
|
if (strchr(stream, '.') != NULL)
|
|
return nats_setError(NATS_INVALID_ARG, "%s '%s' (cannot contain '.')", jsErrInvalidStreamName, stream);
|
|
|
|
return NATS_OK;
|
|
}
|
|
|
|
static void
|
|
_destroyPlacement(jsPlacement *placement)
|
|
{
|
|
int i;
|
|
|
|
if (placement == NULL)
|
|
return;
|
|
|
|
NATS_FREE((char*)placement->Cluster);
|
|
for (i=0; i<placement->TagsLen; i++)
|
|
NATS_FREE((char*) placement->Tags[i]);
|
|
NATS_FREE((char**) placement->Tags);
|
|
NATS_FREE(placement);
|
|
}
|
|
|
|
static void
|
|
_destroyExternalStream(jsExternalStream *external)
|
|
{
|
|
if (external == NULL)
|
|
return;
|
|
|
|
NATS_FREE((char*) external->APIPrefix);
|
|
NATS_FREE((char*) external->DeliverPrefix);
|
|
NATS_FREE(external);
|
|
}
|
|
|
|
static void
|
|
_destroyStreamSource(jsStreamSource *source)
|
|
{
|
|
if (source == NULL)
|
|
return;
|
|
|
|
NATS_FREE((char*) source->Name);
|
|
NATS_FREE((char*) source->FilterSubject);
|
|
_destroyExternalStream(source->External);
|
|
NATS_FREE(source);
|
|
}
|
|
|
|
static void
|
|
_destroyRePublish(jsRePublish *rp)
|
|
{
|
|
if (rp == NULL)
|
|
return;
|
|
|
|
NATS_FREE((char*) rp->Source);
|
|
NATS_FREE((char*) rp->Destination);
|
|
NATS_FREE(rp);
|
|
}
|
|
|
|
void
|
|
js_destroyStreamConfig(jsStreamConfig *cfg)
|
|
{
|
|
int i;
|
|
|
|
if (cfg == NULL)
|
|
return;
|
|
|
|
NATS_FREE((char*) cfg->Name);
|
|
NATS_FREE((char*) cfg->Description);
|
|
for (i=0; i<cfg->SubjectsLen; i++)
|
|
NATS_FREE((char*) cfg->Subjects[i]);
|
|
NATS_FREE((char**) cfg->Subjects);
|
|
NATS_FREE((char*) cfg->Template);
|
|
_destroyPlacement(cfg->Placement);
|
|
_destroyStreamSource(cfg->Mirror);
|
|
for (i=0; i<cfg->SourcesLen; i++)
|
|
_destroyStreamSource(cfg->Sources[i]);
|
|
NATS_FREE(cfg->Sources);
|
|
_destroyRePublish(cfg->RePublish);
|
|
NATS_FREE(cfg);
|
|
}
|
|
|
|
static void
|
|
_destroyPeerInfo(jsPeerInfo *peer)
|
|
{
|
|
if (peer == NULL)
|
|
return;
|
|
|
|
NATS_FREE(peer->Name);
|
|
NATS_FREE(peer);
|
|
}
|
|
|
|
static void
|
|
_destroyClusterInfo(jsClusterInfo *cluster)
|
|
{
|
|
int i;
|
|
|
|
if (cluster == NULL)
|
|
return;
|
|
|
|
NATS_FREE(cluster->Name);
|
|
NATS_FREE(cluster->Leader);
|
|
for (i=0; i<cluster->ReplicasLen; i++)
|
|
_destroyPeerInfo(cluster->Replicas[i]);
|
|
NATS_FREE(cluster->Replicas);
|
|
NATS_FREE(cluster);
|
|
}
|
|
|
|
static void
|
|
_destroyStreamSourceInfo(jsStreamSourceInfo *info)
|
|
{
|
|
if (info == NULL)
|
|
return;
|
|
|
|
NATS_FREE(info->Name);
|
|
_destroyExternalStream(info->External);
|
|
NATS_FREE(info);
|
|
}
|
|
|
|
static void
|
|
_destroyLostStreamData(jsLostStreamData *lost)
|
|
{
|
|
if (lost == NULL)
|
|
return;
|
|
|
|
NATS_FREE(lost->Msgs);
|
|
NATS_FREE(lost);
|
|
}
|
|
|
|
static void
|
|
_destroyStreamStateSubjects(jsStreamStateSubjects *subjects)
|
|
{
|
|
int i;
|
|
|
|
if (subjects == NULL)
|
|
return;
|
|
|
|
for (i=0; i<subjects->Count; i++)
|
|
NATS_FREE((char*) subjects->List[i].Subject);
|
|
NATS_FREE(subjects->List);
|
|
NATS_FREE(subjects);
|
|
}
|
|
|
|
void
|
|
js_cleanStreamState(jsStreamState *state)
|
|
{
|
|
if (state == NULL)
|
|
return;
|
|
|
|
NATS_FREE(state->Deleted);
|
|
_destroyLostStreamData(state->Lost);
|
|
_destroyStreamStateSubjects(state->Subjects);
|
|
}
|
|
|
|
static void
|
|
_destroyStreamAlternate(jsStreamAlternate *sa)
|
|
{
|
|
if (sa == NULL)
|
|
return;
|
|
|
|
NATS_FREE((char*) sa->Name);
|
|
NATS_FREE((char*) sa->Domain);
|
|
NATS_FREE((char*) sa->Cluster);
|
|
NATS_FREE(sa);
|
|
}
|
|
|
|
void
|
|
jsStreamInfo_Destroy(jsStreamInfo *si)
|
|
{
|
|
int i;
|
|
|
|
if (si == NULL)
|
|
return;
|
|
|
|
js_destroyStreamConfig(si->Config);
|
|
_destroyClusterInfo(si->Cluster);
|
|
js_cleanStreamState(&(si->State));
|
|
_destroyStreamSourceInfo(si->Mirror);
|
|
for (i=0; i<si->SourcesLen; i++)
|
|
_destroyStreamSourceInfo(si->Sources[i]);
|
|
NATS_FREE(si->Sources);
|
|
for (i=0; i<si->AlternatesLen; i++)
|
|
_destroyStreamAlternate(si->Alternates[i]);
|
|
NATS_FREE(si->Alternates);
|
|
NATS_FREE(si);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalExternalStream(nats_JSON *json, const char *fieldName, jsExternalStream **new_external)
|
|
{
|
|
jsExternalStream *external = NULL;
|
|
nats_JSON *obj = NULL;
|
|
natsStatus s;
|
|
|
|
s = nats_JSONGetObject(json, fieldName, &obj);
|
|
if (obj == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
external = (jsExternalStream*) NATS_CALLOC(1, sizeof(jsExternalStream));
|
|
if (external == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
s = nats_JSONGetStr(obj, "api", (char**) &(external->APIPrefix));
|
|
IFOK(s, nats_JSONGetStr(obj, "deliver", (char**) &(external->DeliverPrefix)));
|
|
|
|
if (s == NATS_OK)
|
|
*new_external = external;
|
|
else
|
|
_destroyExternalStream(external);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_marshalExternalStream(jsExternalStream *external, const char *fieldName, natsBuffer *buf)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
|
|
IFOK(s, natsBuf_Append(buf, ",\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, fieldName, -1));
|
|
IFOK(s, natsBuf_Append(buf, "\":{\"api\":\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, external->APIPrefix, -1));
|
|
if ((s == NATS_OK) && !nats_IsStringEmpty(external->DeliverPrefix))
|
|
{
|
|
IFOK(s, natsBuf_Append(buf, "\",\"deliver\":\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, external->DeliverPrefix, -1));
|
|
}
|
|
IFOK(s, natsBuf_Append(buf, "\"}", -1));
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalStreamSource(nats_JSON *json, const char *fieldName, jsStreamSource **new_source)
|
|
{
|
|
jsStreamSource *source = NULL;
|
|
nats_JSON *obj = NULL;
|
|
natsStatus s;
|
|
|
|
if (fieldName != NULL)
|
|
{
|
|
s = nats_JSONGetObject(json, fieldName, &obj);
|
|
if (obj == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
else
|
|
{
|
|
obj = json;
|
|
}
|
|
|
|
source = (jsStreamSource*) NATS_CALLOC(1, sizeof(jsStreamSource));
|
|
if (source == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
s = nats_JSONGetStr(obj, "name", (char**) &(source->Name));
|
|
IFOK(s, nats_JSONGetULong(obj, "opt_start_seq", &(source->OptStartSeq)));
|
|
IFOK(s, nats_JSONGetTime(obj, "opt_start_time", &(source->OptStartTime)));
|
|
IFOK(s, nats_JSONGetStr(obj, "filter_subject", (char**) &(source->FilterSubject)));
|
|
IFOK(s, _unmarshalExternalStream(obj, "external", &(source->External)));
|
|
|
|
if (s == NATS_OK)
|
|
*new_source = source;
|
|
else
|
|
_destroyStreamSource(source);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_marshalStreamSource(jsStreamSource *source, const char *fieldName, natsBuffer *buf)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
|
|
if (fieldName != NULL)
|
|
{
|
|
IFOK(s, natsBuf_Append(buf, ",\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, fieldName, -1));
|
|
IFOK(s, natsBuf_Append(buf, "\":", -1));
|
|
}
|
|
IFOK(s, natsBuf_Append(buf, "{\"name\":\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, source->Name, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
if ((s == NATS_OK) && (source->OptStartSeq > 0))
|
|
s = nats_marshalLong(buf, true, "opt_start_seq", source->OptStartSeq);
|
|
if ((s == NATS_OK) && (source->OptStartTime > 0))
|
|
IFOK(s, _marshalTimeUTC(buf, "opt_start_time", source->OptStartTime));
|
|
if (source->FilterSubject != NULL)
|
|
{
|
|
IFOK(s, natsBuf_Append(buf, ",\"filter_subject\":\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, source->FilterSubject, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
}
|
|
if (source->External != NULL)
|
|
IFOK(s, _marshalExternalStream(source->External, "external", buf));
|
|
|
|
IFOK(s, natsBuf_AppendByte(buf, '}'));
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalPlacement(nats_JSON *json, const char *fieldName, jsPlacement **new_placement)
|
|
{
|
|
jsPlacement *placement = NULL;
|
|
nats_JSON *jpl = NULL;
|
|
natsStatus s;
|
|
|
|
s = nats_JSONGetObject(json, fieldName, &jpl);
|
|
if (jpl == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
placement = (jsPlacement*) NATS_CALLOC(1, sizeof(jsPlacement));
|
|
if (placement == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
s = nats_JSONGetStr(jpl, "cluster", (char**) &(placement->Cluster));
|
|
IFOK(s, nats_JSONGetArrayStr(jpl, "tags", (char***) &(placement->Tags), &(placement->TagsLen)));
|
|
|
|
if (s == NATS_OK)
|
|
*new_placement = placement;
|
|
else
|
|
_destroyPlacement(placement);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_marshalPlacement(jsPlacement *placement, natsBuffer *buf)
|
|
{
|
|
natsStatus s;
|
|
|
|
s = natsBuf_Append(buf, ",\"placement\":{\"cluster\":\"", -1);
|
|
IFOK(s, natsBuf_Append(buf, placement->Cluster, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
if (placement->TagsLen > 0)
|
|
{
|
|
int i;
|
|
|
|
IFOK(s, natsBuf_Append(buf, ",\"tags\":[\"", -1));
|
|
for (i=0; (s == NATS_OK) && (i<placement->TagsLen); i++)
|
|
{
|
|
IFOK(s, natsBuf_Append(buf, placement->Tags[i], -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
if (i < placement->TagsLen-1)
|
|
IFOK(s, natsBuf_Append(buf, ",\"", -1));
|
|
}
|
|
IFOK(s, natsBuf_AppendByte(buf, ']'));
|
|
}
|
|
IFOK(s, natsBuf_AppendByte(buf, '}'));
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalRetentionPolicy(nats_JSON *json, const char *fieldName, jsRetentionPolicy *policy)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
char *str = NULL;
|
|
|
|
s = nats_JSONGetStr(json, fieldName, &str);
|
|
if (str == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (strcmp(str, jsRetPolicyLimitsStr) == 0)
|
|
*policy = js_LimitsPolicy;
|
|
else if (strcmp(str, jsRetPolicyInterestStr) == 0)
|
|
*policy = js_InterestPolicy;
|
|
else if (strcmp(str, jsRetPolicyWorkQueueStr) == 0)
|
|
*policy = js_WorkQueuePolicy;
|
|
else
|
|
s = nats_setError(NATS_ERR, "unable to unmarshal retention policy '%s'", str);
|
|
|
|
NATS_FREE(str);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_marshalRetentionPolicy(jsRetentionPolicy policy, natsBuffer *buf)
|
|
{
|
|
natsStatus s;
|
|
const char *rp = NULL;
|
|
|
|
s = natsBuf_Append(buf, ",\"retention\":\"", -1);
|
|
switch (policy)
|
|
{
|
|
case js_LimitsPolicy: rp = jsRetPolicyLimitsStr; break;
|
|
case js_InterestPolicy: rp = jsRetPolicyInterestStr; break;
|
|
case js_WorkQueuePolicy: rp = jsRetPolicyWorkQueueStr; break;
|
|
default:
|
|
return nats_setError(NATS_INVALID_ARG, "invalid retention policy %d", (int) policy);
|
|
}
|
|
IFOK(s, natsBuf_Append(buf, rp, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalDiscardPolicy(nats_JSON *json, const char *fieldName, jsDiscardPolicy *policy)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
char *str = NULL;
|
|
|
|
s = nats_JSONGetStr(json, fieldName, &str);
|
|
if (str == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (strcmp(str, jsDiscardPolicyOldStr) == 0)
|
|
*policy = js_DiscardOld;
|
|
else if (strcmp(str, jsDiscardPolicyNewStr) == 0)
|
|
*policy = js_DiscardNew;
|
|
else
|
|
s = nats_setError(NATS_ERR, "unable to unmarshal discard policy '%s'", str);
|
|
|
|
NATS_FREE(str);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_marshalDiscardPolicy(jsDiscardPolicy policy, natsBuffer *buf)
|
|
{
|
|
natsStatus s;
|
|
const char *dp = NULL;
|
|
|
|
s = natsBuf_Append(buf, ",\"discard\":\"", -1);
|
|
switch (policy)
|
|
{
|
|
case js_DiscardOld: dp = jsDiscardPolicyOldStr; break;
|
|
case js_DiscardNew: dp = jsDiscardPolicyNewStr; break;
|
|
default:
|
|
return nats_setError(NATS_INVALID_ARG, "invalid discard policy %d", (int) policy);
|
|
}
|
|
IFOK(s, natsBuf_Append(buf, dp, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalStorageType(nats_JSON *json, const char *fieldName, jsStorageType *storage)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
char *str = NULL;
|
|
|
|
s = nats_JSONGetStr(json, "storage", &str);
|
|
if (str == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (strcmp(str, jsStorageTypeFileStr) == 0)
|
|
*storage = js_FileStorage;
|
|
else if (strcmp(str, jsStorageTypeMemStr) == 0)
|
|
*storage = js_MemoryStorage;
|
|
else
|
|
s = nats_setError(NATS_ERR, "unable to unmarshal storage type '%s'", str);
|
|
|
|
NATS_FREE(str);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_marshalStorageType(jsStorageType storage, natsBuffer *buf)
|
|
{
|
|
natsStatus s;
|
|
const char *st = NULL;
|
|
|
|
s = natsBuf_Append(buf, ",\"storage\":\"", -1);
|
|
switch (storage)
|
|
{
|
|
case js_FileStorage: st = jsStorageTypeFileStr; break;
|
|
case js_MemoryStorage: st = jsStorageTypeMemStr; break;
|
|
default:
|
|
return nats_setError(NATS_INVALID_ARG, "invalid storage type %d", (int) storage);
|
|
}
|
|
IFOK(s, natsBuf_Append(buf, st, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalRePublish(nats_JSON *json, const char *fieldName, jsRePublish **new_republish)
|
|
{
|
|
jsRePublish *rp = NULL;
|
|
nats_JSON *jsm = NULL;
|
|
natsStatus s;
|
|
|
|
s = nats_JSONGetObject(json, fieldName, &jsm);
|
|
if (jsm == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
rp = (jsRePublish*) NATS_CALLOC(1, sizeof(jsRePublish));
|
|
if (rp == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
s = nats_JSONGetStr(jsm, "src", (char**) &(rp->Source));
|
|
IFOK(s, nats_JSONGetStr(jsm, "dest", (char**) &(rp->Destination)));
|
|
IFOK(s, nats_JSONGetBool(jsm, "headers_only", &(rp->HeadersOnly)));
|
|
|
|
if (s == NATS_OK)
|
|
*new_republish = rp;
|
|
else
|
|
_destroyRePublish(rp);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
}
|
|
|
|
natsStatus
|
|
js_unmarshalStreamConfig(nats_JSON *json, const char *fieldName, jsStreamConfig **new_cfg)
|
|
{
|
|
nats_JSON *jcfg = NULL;
|
|
jsStreamConfig *cfg = NULL;
|
|
nats_JSON **sources = NULL;
|
|
int sourcesLen = 0;
|
|
natsStatus s;
|
|
|
|
if (fieldName != NULL)
|
|
{
|
|
s = nats_JSONGetObject(json, fieldName, &jcfg);
|
|
if (jcfg == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
else
|
|
{
|
|
jcfg = json;
|
|
}
|
|
|
|
cfg = (jsStreamConfig*) NATS_CALLOC(1, sizeof(jsStreamConfig));
|
|
if (cfg == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
s = nats_JSONGetStr(jcfg, "name", (char**) &(cfg->Name));
|
|
IFOK(s, nats_JSONGetStr(jcfg, "description", (char**) &(cfg->Description)));
|
|
IFOK(s, nats_JSONGetArrayStr(jcfg, "subjects", (char***) &(cfg->Subjects), &(cfg->SubjectsLen)));
|
|
IFOK(s, _unmarshalRetentionPolicy(jcfg, "retention", &(cfg->Retention)));
|
|
IFOK(s, nats_JSONGetLong(jcfg, "max_consumers", &(cfg->MaxConsumers)));
|
|
IFOK(s, nats_JSONGetLong(jcfg, "max_msgs", &(cfg->MaxMsgs)));
|
|
IFOK(s, nats_JSONGetLong(jcfg, "max_bytes", &(cfg->MaxBytes)));
|
|
IFOK(s, nats_JSONGetLong(jcfg, "max_age", &(cfg->MaxAge)));
|
|
IFOK(s, nats_JSONGetLong(jcfg, "max_msgs_per_subject", &(cfg->MaxMsgsPerSubject)));
|
|
IFOK(s, nats_JSONGetInt32(jcfg, "max_msg_size", &(cfg->MaxMsgSize)));
|
|
IFOK(s, _unmarshalDiscardPolicy(jcfg, "discard", &(cfg->Discard)));
|
|
IFOK(s, _unmarshalStorageType(jcfg, "storage", &(cfg->Storage)));
|
|
IFOK(s, nats_JSONGetLong(jcfg, "num_replicas", &(cfg->Replicas)));
|
|
IFOK(s, nats_JSONGetBool(jcfg, "no_ack", &(cfg->NoAck)));
|
|
IFOK(s, nats_JSONGetStr(jcfg, "template_owner", (char**) &(cfg->Template)));
|
|
IFOK(s, nats_JSONGetLong(jcfg, "duplicate_window", &(cfg->Duplicates)));
|
|
IFOK(s, _unmarshalPlacement(jcfg, "placement", &(cfg->Placement)));
|
|
IFOK(s, _unmarshalStreamSource(jcfg, "mirror", &(cfg->Mirror)));
|
|
// Get the sources and unmarshal if present
|
|
IFOK(s, nats_JSONGetArrayObject(jcfg, "sources", &sources, &sourcesLen));
|
|
if ((s == NATS_OK) && (sources != NULL))
|
|
{
|
|
int i;
|
|
|
|
cfg->Sources = (jsStreamSource**) NATS_CALLOC(sourcesLen, sizeof(jsStreamSource*));
|
|
if (cfg->Sources == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
for (i=0; (s == NATS_OK) && (i<sourcesLen); i++)
|
|
{
|
|
s = _unmarshalStreamSource(sources[i], NULL, &(cfg->Sources[i]));
|
|
if (s == NATS_OK)
|
|
cfg->SourcesLen++;
|
|
}
|
|
// Free the array of JSON objects that was allocated by nats_JSONGetArrayObject.
|
|
NATS_FREE(sources);
|
|
}
|
|
IFOK(s, nats_JSONGetBool(jcfg, "sealed", &(cfg->Sealed)));
|
|
IFOK(s, nats_JSONGetBool(jcfg, "deny_delete", &(cfg->DenyDelete)));
|
|
IFOK(s, nats_JSONGetBool(jcfg, "deny_purge", &(cfg->DenyPurge)));
|
|
IFOK(s, nats_JSONGetBool(jcfg, "allow_rollup_hdrs", &(cfg->AllowRollup)));
|
|
IFOK(s, _unmarshalRePublish(jcfg, "republish", &(cfg->RePublish)));
|
|
IFOK(s, nats_JSONGetBool(jcfg, "allow_direct", &(cfg->AllowDirect)));
|
|
IFOK(s, nats_JSONGetBool(jcfg, "mirror_direct", &(cfg->MirrorDirect)));
|
|
IFOK(s, nats_JSONGetBool(jcfg, "discard_new_per_subject", &(cfg->DiscardNewPerSubject)));
|
|
|
|
if (s == NATS_OK)
|
|
*new_cfg = cfg;
|
|
else
|
|
js_destroyStreamConfig(cfg);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_marshalStreamConfig(natsBuffer **new_buf, jsStreamConfig *cfg)
|
|
{
|
|
natsBuffer *buf = NULL;
|
|
natsStatus s;
|
|
|
|
s = natsBuf_Create(&buf, 256);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
s = natsBuf_Append(buf, "{\"name\":\"", -1);
|
|
IFOK(s, natsBuf_Append(buf, cfg->Name, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
if ((s == NATS_OK) && !nats_IsStringEmpty(cfg->Description))
|
|
{
|
|
IFOK(s, natsBuf_Append(buf, ",\"description\":\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, cfg->Description, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
}
|
|
if ((s == NATS_OK) && (cfg->SubjectsLen > 0))
|
|
{
|
|
int i;
|
|
|
|
IFOK(s, natsBuf_Append(buf, ",\"subjects\":[\"", -1));
|
|
for (i=0; (s == NATS_OK) && (i<cfg->SubjectsLen); i++)
|
|
{
|
|
IFOK(s, natsBuf_Append(buf, cfg->Subjects[i], -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
if (i < cfg->SubjectsLen - 1)
|
|
IFOK(s, natsBuf_Append(buf, ",\"", -1));
|
|
}
|
|
IFOK(s, natsBuf_AppendByte(buf, ']'));
|
|
}
|
|
IFOK(s, _marshalRetentionPolicy(cfg->Retention, buf));
|
|
|
|
IFOK(s, nats_marshalLong(buf, true, "max_consumers", cfg->MaxConsumers));
|
|
IFOK(s, nats_marshalLong(buf, true, "max_msgs", cfg->MaxMsgs));
|
|
IFOK(s, nats_marshalLong(buf, true, "max_bytes", cfg->MaxBytes));
|
|
IFOK(s, nats_marshalLong(buf, true, "max_age", cfg->MaxAge));
|
|
IFOK(s, nats_marshalLong(buf, true, "max_msg_size", (int64_t) cfg->MaxMsgSize));
|
|
IFOK(s, nats_marshalLong(buf, true, "max_msgs_per_subject", cfg->MaxMsgsPerSubject));
|
|
|
|
IFOK(s, _marshalDiscardPolicy(cfg->Discard, buf));
|
|
|
|
IFOK(s, _marshalStorageType(cfg->Storage, buf));
|
|
|
|
IFOK(s, nats_marshalLong(buf, true, "num_replicas", cfg->Replicas));
|
|
|
|
if ((s == NATS_OK) && cfg->NoAck)
|
|
IFOK(s, natsBuf_Append(buf, ",\"no_ack\":true", -1));
|
|
|
|
if ((s == NATS_OK) && (cfg->Template != NULL))
|
|
{
|
|
IFOK(s, natsBuf_Append(buf, ",\"template_owner\":\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, cfg->Template, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
}
|
|
|
|
if ((s == NATS_OK) && (cfg->Duplicates != 0))
|
|
IFOK(s, nats_marshalLong(buf, true, "duplicate_window", cfg->Duplicates));
|
|
|
|
if ((s == NATS_OK) && (cfg->Placement != NULL))
|
|
IFOK(s, _marshalPlacement(cfg->Placement, buf));
|
|
|
|
if ((s == NATS_OK) && (cfg->Mirror != NULL))
|
|
IFOK(s, _marshalStreamSource(cfg->Mirror, "mirror", buf));
|
|
|
|
if ((s == NATS_OK) && (cfg->SourcesLen > 0))
|
|
{
|
|
int i;
|
|
|
|
IFOK(s, natsBuf_Append(buf, ",\"sources\":[", -1));
|
|
for (i=0; (s == NATS_OK) && (i < cfg->SourcesLen); i++)
|
|
{
|
|
IFOK(s, _marshalStreamSource(cfg->Sources[i], NULL, buf));
|
|
if ((s == NATS_OK) && (i < cfg->SourcesLen-1))
|
|
IFOK(s, natsBuf_AppendByte(buf, ','));
|
|
}
|
|
IFOK(s, natsBuf_AppendByte(buf, ']'));
|
|
}
|
|
|
|
if ((s == NATS_OK) && cfg->Sealed)
|
|
IFOK(s, natsBuf_Append(buf, ",\"sealed\":true", -1));
|
|
if ((s == NATS_OK) && cfg->DenyDelete)
|
|
IFOK(s, natsBuf_Append(buf, ",\"deny_delete\":true", -1));
|
|
if ((s == NATS_OK) && cfg->DenyPurge)
|
|
IFOK(s, natsBuf_Append(buf, ",\"deny_purge\":true", -1));
|
|
if ((s == NATS_OK) && cfg->AllowRollup)
|
|
IFOK(s, natsBuf_Append(buf, ",\"allow_rollup_hdrs\":true", -1));
|
|
if ((s == NATS_OK) && (cfg->RePublish != NULL) && !nats_IsStringEmpty(cfg->RePublish->Destination))
|
|
{
|
|
// "dest" is not omitempty, in that the field will always be present.
|
|
IFOK(s, natsBuf_Append(buf, ",\"republish\":{\"dest\":\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, cfg->RePublish->Destination, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
// Now the source...
|
|
if (!nats_IsStringEmpty(cfg->RePublish->Source))
|
|
{
|
|
IFOK(s, natsBuf_Append(buf, ",\"src\":\"", -1))
|
|
IFOK(s, natsBuf_Append(buf, cfg->RePublish->Source, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
}
|
|
if (cfg->RePublish->HeadersOnly)
|
|
IFOK(s, natsBuf_Append(buf, ",\"headers_only\":true", -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '}'));
|
|
}
|
|
if ((s == NATS_OK) && cfg->AllowDirect)
|
|
IFOK(s, natsBuf_Append(buf, ",\"allow_direct\":true", -1));
|
|
if ((s == NATS_OK) && cfg->MirrorDirect)
|
|
IFOK(s, natsBuf_Append(buf, ",\"mirror_direct\":true", -1));
|
|
if ((s == NATS_OK) && cfg->DiscardNewPerSubject)
|
|
IFOK(s, natsBuf_Append(buf, ",\"discard_new_per_subject\":true", -1));
|
|
|
|
IFOK(s, natsBuf_AppendByte(buf, '}'));
|
|
|
|
if (s == NATS_OK)
|
|
*new_buf = buf;
|
|
else
|
|
natsBuf_Destroy(buf);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalLostStreamData(nats_JSON *pjson, const char *fieldName, jsLostStreamData **new_lost)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
jsLostStreamData *lost = NULL;
|
|
nats_JSON *json = NULL;
|
|
|
|
s = nats_JSONGetObject(pjson, fieldName, &json);
|
|
if (json == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
lost = (jsLostStreamData*) NATS_CALLOC(1, sizeof(jsLostStreamData));
|
|
if (lost == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
IFOK(s, nats_JSONGetArrayULong(json, "msgs", &(lost->Msgs), &(lost->MsgsLen)));
|
|
IFOK(s, nats_JSONGetULong(json, "bytes", &(lost->Bytes)));
|
|
|
|
if (s == NATS_OK)
|
|
*new_lost = lost;
|
|
else
|
|
_destroyLostStreamData(lost);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_fillSubjectsList(void *userInfo, const char *subject, nats_JSONField *f)
|
|
{
|
|
jsStreamStateSubjects *subjs = (jsStreamStateSubjects*) userInfo;
|
|
natsStatus s = NATS_OK;
|
|
int i = subjs->Count;
|
|
|
|
DUP_STRING(s, subjs->List[i].Subject, subject);
|
|
if (s == NATS_OK)
|
|
{
|
|
subjs->List[i].Msgs = f->value.vuint;
|
|
subjs->Count = i+1;
|
|
}
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalStreamStateSubjects(nats_JSON *pjson, const char *fieldName, jsStreamStateSubjects **subjects)
|
|
{
|
|
nats_JSON *json = NULL;
|
|
natsStatus s = NATS_OK;
|
|
jsStreamStateSubjects *subjs= NULL;
|
|
int n;
|
|
|
|
s = nats_JSONGetObject(pjson, fieldName, &json);
|
|
if (json == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if ((n = natsStrHash_Count(json->fields)) > 0)
|
|
{
|
|
subjs = NATS_CALLOC(1, sizeof(jsStreamStateSubjects));
|
|
if (subjs == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
if (s == NATS_OK)
|
|
{
|
|
subjs->List = NATS_CALLOC(n, sizeof(jsStreamStateSubject));
|
|
if (subjs->List == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
}
|
|
IFOK(s, nats_JSONRange(json, TYPE_NUM, TYPE_UINT, _fillSubjectsList, (void*) subjs));
|
|
|
|
if (s == NATS_OK)
|
|
*subjects = subjs;
|
|
else
|
|
_destroyStreamStateSubjects(subjs);
|
|
}
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_unmarshalStreamState(nats_JSON *pjson, const char *fieldName, jsStreamState *state)
|
|
{
|
|
nats_JSON *json = NULL;
|
|
natsStatus s;
|
|
|
|
s = nats_JSONGetObject(pjson, fieldName, &json);
|
|
if (json == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
s = nats_JSONGetULong(json, "messages", &(state->Msgs));
|
|
IFOK(s, nats_JSONGetULong(json, "bytes", &(state->Bytes)));
|
|
IFOK(s, nats_JSONGetULong(json, "first_seq", &(state->FirstSeq)));
|
|
IFOK(s, nats_JSONGetTime(json, "first_ts", &(state->FirstTime)));
|
|
IFOK(s, nats_JSONGetULong(json, "last_seq", &(state->LastSeq)));
|
|
IFOK(s, nats_JSONGetTime(json, "last_ts", &(state->LastTime)));
|
|
IFOK(s, nats_JSONGetULong(json, "num_deleted", &(state->NumDeleted)));
|
|
IFOK(s, nats_JSONGetArrayULong(json, "deleted", &(state->Deleted), &(state->DeletedLen)));
|
|
IFOK(s, _unmarshalLostStreamData(json, "lost", &(state->Lost)));
|
|
IFOK(s, nats_JSONGetLong(json, "consumer_count", &(state->Consumers)));
|
|
IFOK(s, nats_JSONGetLong(json, "num_subjects", &(state->NumSubjects)));
|
|
IFOK(s, _unmarshalStreamStateSubjects(json, "subjects", &(state->Subjects)));
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalPeerInfo(nats_JSON *json, jsPeerInfo **new_pi)
|
|
{
|
|
jsPeerInfo *pi = NULL;
|
|
natsStatus s;
|
|
|
|
pi = (jsPeerInfo*) NATS_CALLOC(1, sizeof(jsPeerInfo));
|
|
if (pi == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
s = nats_JSONGetStr(json, "name", &(pi->Name));
|
|
IFOK(s, nats_JSONGetBool(json, "current", &(pi->Current)));
|
|
IFOK(s, nats_JSONGetBool(json, "offline", &(pi->Offline)));
|
|
IFOK(s, nats_JSONGetLong(json, "active", &(pi->Active)));
|
|
IFOK(s, nats_JSONGetULong(json, "lag", &(pi->Lag)));
|
|
|
|
if (s == NATS_OK)
|
|
*new_pi = pi;
|
|
else
|
|
_destroyPeerInfo(pi);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalClusterInfo(nats_JSON *pjson, const char *fieldName, jsClusterInfo **new_ci)
|
|
{
|
|
jsClusterInfo *ci = NULL;
|
|
nats_JSON *json = NULL;
|
|
nats_JSON **replicas = NULL;
|
|
int replicasLen = 0;
|
|
natsStatus s;
|
|
|
|
s = nats_JSONGetObject(pjson, fieldName, &json);
|
|
if (json == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
ci = (jsClusterInfo*) NATS_CALLOC(1, sizeof(jsClusterInfo));
|
|
if (ci == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
s = nats_JSONGetStr(json, "name", &(ci->Name));
|
|
IFOK(s, nats_JSONGetStr(json, "leader", &(ci->Leader)));
|
|
IFOK(s, nats_JSONGetArrayObject(json, "replicas", &replicas, &replicasLen));
|
|
if ((s == NATS_OK) && (replicas != NULL))
|
|
{
|
|
int i;
|
|
|
|
ci->Replicas = (jsPeerInfo**) NATS_CALLOC(replicasLen, sizeof(jsPeerInfo*));
|
|
if (ci->Replicas == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
for (i=0; (s == NATS_OK) && (i<replicasLen); i++)
|
|
{
|
|
s = _unmarshalPeerInfo(replicas[i], &(ci->Replicas[i]));
|
|
if (s == NATS_OK)
|
|
ci->ReplicasLen++;
|
|
}
|
|
// Free the array of JSON objects that was allocated by nats_JSONGetArrayObject.
|
|
NATS_FREE(replicas);
|
|
}
|
|
if (s == NATS_OK)
|
|
*new_ci = ci;
|
|
else
|
|
_destroyClusterInfo(ci);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalStreamSourceInfo(nats_JSON *pjson, const char *fieldName, jsStreamSourceInfo **new_src)
|
|
{
|
|
nats_JSON *json = NULL;
|
|
jsStreamSourceInfo *ssi = NULL;
|
|
natsStatus s;
|
|
|
|
if (fieldName != NULL)
|
|
{
|
|
s = nats_JSONGetObject(pjson, fieldName, &json);
|
|
if (json == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
else
|
|
{
|
|
json = pjson;
|
|
}
|
|
|
|
ssi = (jsStreamSourceInfo*) NATS_CALLOC(1, sizeof(jsStreamSourceInfo));
|
|
if (ssi == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
s = nats_JSONGetStr(json, "name", &(ssi->Name));
|
|
IFOK(s, _unmarshalExternalStream(json, "external", &(ssi->External)));
|
|
IFOK(s, nats_JSONGetULong(json, "lag", &(ssi->Lag)));
|
|
IFOK(s, nats_JSONGetLong(json, "active", &(ssi->Active)));
|
|
|
|
if (s == NATS_OK)
|
|
*new_src = ssi;
|
|
else
|
|
_destroyStreamSourceInfo(ssi);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalStreamAlternate(nats_JSON *json, jsStreamAlternate **new_alt)
|
|
{
|
|
jsStreamAlternate *sa = NULL;
|
|
natsStatus s = NATS_OK;
|
|
|
|
sa = (jsStreamAlternate*) NATS_CALLOC(1, sizeof(jsStreamAlternate));
|
|
if (sa == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
s = nats_JSONGetStr(json, "name", (char**) &(sa->Name));
|
|
IFOK(s, nats_JSONGetStr(json, "domain", (char**) &(sa->Domain)));
|
|
IFOK(s, nats_JSONGetStr(json, "cluster", (char**) &(sa->Cluster)));
|
|
|
|
if (s == NATS_OK)
|
|
*new_alt = sa;
|
|
else
|
|
_destroyStreamAlternate(sa);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalStreamInfoPaged(nats_JSON *json, jsStreamInfo **new_si, apiPaged *page)
|
|
{
|
|
jsStreamInfo *si = NULL;
|
|
nats_JSON **sources = NULL;
|
|
int sourcesLen = 0;
|
|
nats_JSON **alts = NULL;
|
|
int altsLen = 0;
|
|
natsStatus s;
|
|
|
|
si = (jsStreamInfo*) NATS_CALLOC(1, sizeof(jsStreamInfo));
|
|
if (si == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
// Get the config object
|
|
s = js_unmarshalStreamConfig(json, "config", &(si->Config));
|
|
IFOK(s, nats_JSONGetTime(json, "created", &(si->Created)));
|
|
IFOK(s, js_unmarshalStreamState(json, "state", &(si->State)));
|
|
IFOK(s, _unmarshalClusterInfo(json, "cluster", &(si->Cluster)));
|
|
IFOK(s, _unmarshalStreamSourceInfo(json, "mirror", &(si->Mirror)));
|
|
IFOK(s, nats_JSONGetArrayObject(json, "sources", &sources, &sourcesLen));
|
|
if ((s == NATS_OK) && (sources != NULL))
|
|
{
|
|
int i;
|
|
|
|
si->Sources = (jsStreamSourceInfo**) NATS_CALLOC(sourcesLen, sizeof(jsStreamSourceInfo*));
|
|
if (si->Sources == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
for (i=0; (s == NATS_OK) && (i<sourcesLen); i++)
|
|
{
|
|
s = _unmarshalStreamSourceInfo(sources[i], NULL, &(si->Sources[i]));
|
|
if (s == NATS_OK)
|
|
si->SourcesLen++;
|
|
}
|
|
// Free the array of JSON objects that was allocated by nats_JSONGetArrayObject.
|
|
NATS_FREE(sources);
|
|
}
|
|
IFOK(s, nats_JSONGetArrayObject(json, "alternates", &alts, &altsLen));
|
|
if ((s == NATS_OK) && (alts != NULL))
|
|
{
|
|
int i;
|
|
|
|
si->Alternates = (jsStreamAlternate**) NATS_CALLOC(altsLen, sizeof(jsStreamAlternate*));
|
|
if (si->Alternates == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
for (i=0; (s == NATS_OK) && (i<altsLen); i++)
|
|
{
|
|
s = _unmarshalStreamAlternate(alts[i], &(si->Alternates[i]));
|
|
if (s == NATS_OK)
|
|
si->AlternatesLen++;
|
|
}
|
|
// Free the array of JSON objects that was allocated by nats_JSONGetArrayObject.
|
|
NATS_FREE(alts);
|
|
}
|
|
if ((s == NATS_OK) && (page != NULL))
|
|
{
|
|
IFOK(s, nats_JSONGetLong(json, "total", &page->total));
|
|
IFOK(s, nats_JSONGetLong(json, "offset", &page->offset));
|
|
IFOK(s, nats_JSONGetLong(json, "limit", &page->limit));
|
|
}
|
|
|
|
if (s == NATS_OK)
|
|
*new_si = si;
|
|
else
|
|
jsStreamInfo_Destroy(si);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_unmarshalStreamInfo(nats_JSON *json, jsStreamInfo **new_si)
|
|
{
|
|
natsStatus s = _unmarshalStreamInfoPaged(json, new_si, NULL);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalStreamCreateResp(jsStreamInfo **new_si, apiPaged *page, natsMsg *resp, jsErrCode *errCode)
|
|
{
|
|
nats_JSON *json = NULL;
|
|
jsApiResponse ar;
|
|
natsStatus s;
|
|
|
|
s = js_unmarshalResponse(&ar, &json, resp);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (js_apiResponseIsErr(&ar))
|
|
{
|
|
if (errCode != NULL)
|
|
*errCode = (int) ar.Error.ErrCode;
|
|
|
|
// If the error code is JSStreamNotFoundErr then pick NATS_NOT_FOUND.
|
|
if (ar.Error.ErrCode == JSStreamNotFoundErr)
|
|
s = NATS_NOT_FOUND;
|
|
else
|
|
s = nats_setError(NATS_ERR, "%s", ar.Error.Description);
|
|
}
|
|
else if (new_si != NULL)
|
|
{
|
|
// At this point we need to unmarshal the stream info itself.
|
|
s = _unmarshalStreamInfoPaged(json, new_si, page);
|
|
}
|
|
|
|
js_freeApiRespContent(&ar);
|
|
nats_JSONDestroy(json);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
jsStreamConfig_Init(jsStreamConfig *cfg)
|
|
{
|
|
if (cfg == NULL)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
memset(cfg, 0, sizeof(jsStreamConfig));
|
|
cfg->Retention = js_LimitsPolicy;
|
|
cfg->MaxConsumers = -1;
|
|
cfg->MaxMsgs = -1;
|
|
cfg->MaxBytes = -1;
|
|
cfg->MaxMsgSize = -1;
|
|
cfg->Storage = js_FileStorage;
|
|
cfg->Discard = js_DiscardOld;
|
|
cfg->Replicas = 1;
|
|
return NATS_OK;
|
|
}
|
|
|
|
static void
|
|
_restoreMirrorAndSourcesExternal(jsStreamConfig *cfg)
|
|
{
|
|
int i;
|
|
|
|
// We are guaranteed that if a source's Domain is set, there was originally
|
|
// no External value. So free any External value and reset to NULL to
|
|
// restore the original setting.
|
|
if ((cfg->Mirror != NULL) && !nats_IsStringEmpty(cfg->Mirror->Domain))
|
|
{
|
|
_destroyExternalStream(cfg->Mirror->External);
|
|
cfg->Mirror->External = NULL;
|
|
}
|
|
for (i=0; i<cfg->SourcesLen; i++)
|
|
{
|
|
jsStreamSource *src = cfg->Sources[i];
|
|
if ((src != NULL) && !nats_IsStringEmpty(src->Domain))
|
|
{
|
|
_destroyExternalStream(src->External);
|
|
src->External = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static natsStatus
|
|
_convertDomain(jsStreamSource *src)
|
|
{
|
|
jsExternalStream *e = NULL;
|
|
|
|
e = (jsExternalStream*) NATS_CALLOC(1, sizeof(jsExternalStream));
|
|
if (e == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
if (nats_asprintf((char**) &(e->APIPrefix), jsExtDomainT, src->Domain) < 0)
|
|
{
|
|
NATS_FREE(e);
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
}
|
|
src->External = e;
|
|
return NATS_OK;
|
|
}
|
|
|
|
static natsStatus
|
|
_convertMirrorAndSourcesDomain(bool *converted, jsStreamConfig *cfg)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
bool cm = false;
|
|
bool cs = false;
|
|
int i;
|
|
|
|
*converted = false;
|
|
|
|
if ((cfg->Mirror != NULL) && !nats_IsStringEmpty(cfg->Mirror->Domain))
|
|
{
|
|
if (cfg->Mirror->External != NULL)
|
|
return nats_setError(NATS_INVALID_ARG, "%s", "mirror's domain and external are both set");
|
|
cm = true;
|
|
}
|
|
for (i=0; i<cfg->SourcesLen; i++)
|
|
{
|
|
jsStreamSource *src = cfg->Sources[i];
|
|
if ((src != NULL) && !nats_IsStringEmpty(src->Domain))
|
|
{
|
|
if (src->External != NULL)
|
|
return nats_setError(NATS_INVALID_ARG, "%s", "source's domain and external are both set");
|
|
cs = true;
|
|
}
|
|
}
|
|
if (!cm && !cs)
|
|
return NATS_OK;
|
|
|
|
if (cm)
|
|
s = _convertDomain(cfg->Mirror);
|
|
if ((s == NATS_OK) && cs)
|
|
{
|
|
for (i=0; (s == NATS_OK) && (i<cfg->SourcesLen); i++)
|
|
{
|
|
jsStreamSource *src = cfg->Sources[i];
|
|
if ((src != NULL) && !nats_IsStringEmpty(src->Domain))
|
|
s = _convertDomain(src);
|
|
}
|
|
}
|
|
if (s == NATS_OK)
|
|
*converted = true;
|
|
else
|
|
_restoreMirrorAndSourcesExternal(cfg);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_addOrUpdate(jsStreamInfo **new_si, jsStreamAction action, jsCtx *js, jsStreamConfig *cfg, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
natsBuffer *buf = NULL;
|
|
char *subj = NULL;
|
|
natsMsg *resp = NULL;
|
|
natsConnection *nc = NULL;
|
|
const char *apiT = NULL;
|
|
bool freePfx = false;
|
|
bool msc = false;
|
|
jsOptions o;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if (js == NULL)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
if (cfg == NULL)
|
|
return nats_setError(NATS_INVALID_ARG, "%s", jsErrStreamConfigRequired);
|
|
|
|
s = _checkStreamName(cfg->Name);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
switch (action)
|
|
{
|
|
case jsStreamActionCreate: apiT = jsApiStreamCreateT; break;
|
|
case jsStreamActionUpdate: apiT = jsApiStreamUpdateT; break;
|
|
default: abort();
|
|
}
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
if (nats_asprintf(&subj, apiT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, cfg->Name) < 0)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
if ((s == NATS_OK) && (action == jsStreamActionCreate))
|
|
s = _convertMirrorAndSourcesDomain(&msc, cfg);
|
|
|
|
// Marshal the stream create/update request
|
|
IFOK(s, js_marshalStreamConfig(&buf, cfg));
|
|
|
|
// Send the request
|
|
IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, natsBuf_Data(buf), natsBuf_Len(buf), o.Wait));
|
|
|
|
// If we got a response, check for error or return the stream info result.
|
|
IFOK(s, _unmarshalStreamCreateResp(new_si, NULL, resp, errCode));
|
|
|
|
// If mirror and/or sources were converted for the domain, then we need
|
|
// to restore the original values (which will free the memory that was
|
|
// allocated for the conversion).
|
|
if (msc)
|
|
_restoreMirrorAndSourcesExternal(cfg);
|
|
|
|
natsBuf_Destroy(buf);
|
|
natsMsg_Destroy(resp);
|
|
NATS_FREE(subj);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_AddStream(jsStreamInfo **new_si, jsCtx *js, jsStreamConfig *cfg, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = _addOrUpdate(new_si, jsStreamActionCreate, js, cfg, opts, errCode);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_UpdateStream(jsStreamInfo **new_si, jsCtx *js, jsStreamConfig *cfg, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = _addOrUpdate(new_si, jsStreamActionUpdate, js, cfg, opts, errCode);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_marshalStreamInfoReq(natsBuffer **new_buf, struct jsOptionsStreamInfo *o, int64_t offset)
|
|
{
|
|
natsBuffer *buf = *new_buf;
|
|
natsStatus s = NATS_OK;
|
|
|
|
if (!o->DeletedDetails && nats_IsStringEmpty(o->SubjectsFilter))
|
|
return NATS_OK;
|
|
|
|
if (buf == NULL)
|
|
s = natsBuf_Create(&buf, 30);
|
|
|
|
IFOK(s, natsBuf_AppendByte(buf, '{'));
|
|
if ((s == NATS_OK) && o->DeletedDetails)
|
|
s = natsBuf_Append(buf, "\"deleted_details\":true", -1);
|
|
if ((s == NATS_OK) && !nats_IsStringEmpty(o->SubjectsFilter))
|
|
{
|
|
if (o->DeletedDetails)
|
|
s = natsBuf_AppendByte(buf, ',');
|
|
IFOK(s, natsBuf_Append(buf, "\"subjects_filter\":\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, o->SubjectsFilter, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '\"'));
|
|
if ((s == NATS_OK) && (offset > 0))
|
|
IFOK(s, nats_marshalLong(buf, true, "offset", offset));
|
|
}
|
|
IFOK(s, natsBuf_AppendByte(buf, '}'));
|
|
|
|
if (*new_buf == NULL)
|
|
*new_buf = buf;
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_GetStreamInfo(jsStreamInfo **new_si, jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s;
|
|
natsBuffer *buf = NULL;
|
|
char *subj = NULL;
|
|
char *req = NULL;
|
|
int reqLen = 0;
|
|
natsMsg *resp = NULL;
|
|
natsConnection *nc = NULL;
|
|
bool freePfx = false;
|
|
jsOptions o;
|
|
// For subjects pagination
|
|
int64_t offset = 0;
|
|
bool doPage = false;
|
|
bool done = false;
|
|
jsStreamInfo *si = NULL;
|
|
jsStreamStateSubjects *subjects = NULL;
|
|
apiPaged page;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if ((js == NULL) || (new_si == NULL))
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
s = _checkStreamName(stream);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
if (nats_asprintf(&subj, jsApiStreamInfoT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
|
|
if (!nats_IsStringEmpty(o.Stream.Info.SubjectsFilter))
|
|
{
|
|
doPage = true;
|
|
memset(&page, 0, sizeof(apiPaged));
|
|
}
|
|
|
|
do
|
|
{
|
|
// This will return a buffer if the request was marshal'ed
|
|
// (due to presence of options)
|
|
IFOK(s, _marshalStreamInfoReq(&buf, &(o.Stream.Info), offset));
|
|
if ((s == NATS_OK) && (buf != NULL) && (natsBuf_Len(buf) > 0))
|
|
{
|
|
req = natsBuf_Data(buf);
|
|
reqLen = natsBuf_Len(buf);
|
|
}
|
|
|
|
// Send the request
|
|
IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, req, reqLen, o.Wait));
|
|
|
|
// If we got a response, check for error or return the stream info result.
|
|
IFOK(s, _unmarshalStreamCreateResp(&si, &page, resp, errCode));
|
|
|
|
// If there was paging, we need to collect subjects until we get them all.
|
|
if ((s == NATS_OK) && doPage)
|
|
{
|
|
if (si->State.Subjects != NULL)
|
|
{
|
|
int sc = si->State.Subjects->Count;
|
|
offset += (int64_t) sc;
|
|
if (subjects == NULL)
|
|
subjects = si->State.Subjects;
|
|
else
|
|
{
|
|
jsStreamStateSubject *list = subjects->List;
|
|
int prev = subjects->Count;
|
|
|
|
list = (jsStreamStateSubject*) NATS_REALLOC(list, (prev + sc) * sizeof(jsStreamStateSubject));
|
|
if (list == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
else
|
|
{
|
|
int i;
|
|
for (i=0; i<sc; i++)
|
|
{
|
|
list[prev+i].Subject = si->State.Subjects->List[i].Subject;
|
|
list[prev+i].Msgs = si->State.Subjects->List[i].Msgs;
|
|
}
|
|
NATS_FREE(si->State.Subjects->List);
|
|
NATS_FREE(si->State.Subjects);
|
|
subjects->List = list;
|
|
subjects->Count += sc;
|
|
}
|
|
}
|
|
if (s == NATS_OK)
|
|
si->State.Subjects = NULL;
|
|
}
|
|
done = offset >= page.total;
|
|
if (!done)
|
|
{
|
|
jsStreamInfo_Destroy(si);
|
|
si = NULL;
|
|
// Reset the request buffer, we may be able to reuse.
|
|
natsBuf_Reset(buf);
|
|
}
|
|
}
|
|
natsMsg_Destroy(resp);
|
|
resp = NULL;
|
|
}
|
|
while ((s == NATS_OK) && doPage && !done);
|
|
|
|
natsBuf_Destroy(buf);
|
|
NATS_FREE(subj);
|
|
|
|
if (s == NATS_OK)
|
|
{
|
|
if (doPage && (subjects != NULL))
|
|
si->State.Subjects = subjects;
|
|
|
|
*new_si = si;
|
|
}
|
|
else
|
|
{
|
|
if (subjects != NULL)
|
|
_destroyStreamStateSubjects(subjects);
|
|
|
|
if (s == NATS_NOT_FOUND)
|
|
{
|
|
nats_clearLastError();
|
|
return s;
|
|
}
|
|
}
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalSuccessResp(bool *success, natsMsg *resp, jsErrCode *errCode)
|
|
{
|
|
nats_JSON *json = NULL;
|
|
jsApiResponse ar;
|
|
natsStatus s;
|
|
|
|
*success = false;
|
|
|
|
s = js_unmarshalResponse(&ar, &json, resp);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (js_apiResponseIsErr(&ar))
|
|
{
|
|
if (errCode != NULL)
|
|
*errCode = (int) ar.Error.ErrCode;
|
|
|
|
// For stream or consumer not found, return NATS_NOT_FOUND instead of NATS_ERR.
|
|
if ((ar.Error.ErrCode == JSStreamNotFoundErr)
|
|
|| (ar.Error.ErrCode == JSConsumerNotFoundErr))
|
|
{
|
|
s = NATS_NOT_FOUND;
|
|
}
|
|
else
|
|
s = nats_setError(NATS_ERR, "%s", ar.Error.Description);
|
|
}
|
|
else
|
|
{
|
|
s = nats_JSONGetBool(json, "success", success);
|
|
}
|
|
|
|
js_freeApiRespContent(&ar);
|
|
nats_JSONDestroy(json);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_marshalPurgeRequest(natsBuffer **new_buf, struct jsOptionsStreamPurge *opts)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
natsBuffer *buf = NULL;
|
|
bool comma = false;
|
|
|
|
if (nats_IsStringEmpty(opts->Subject) && (opts->Sequence == 0) && (opts->Keep == 0))
|
|
return NATS_OK;
|
|
|
|
if ((opts->Sequence > 0) && (opts->Keep > 0))
|
|
return nats_setError(NATS_INVALID_ARG,
|
|
"Sequence (%" PRIu64 ") and Keep (%" PRIu64 " are mutually exclusive",
|
|
opts->Sequence, opts->Keep);
|
|
|
|
s = natsBuf_Create(&buf, 128);
|
|
IFOK(s, natsBuf_AppendByte(buf, '{'));
|
|
if ((s == NATS_OK) && !nats_IsStringEmpty(opts->Subject))
|
|
{
|
|
s = natsBuf_Append(buf, "\"filter\":\"", -1);
|
|
IFOK(s, natsBuf_Append(buf, opts->Subject, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
comma = true;
|
|
}
|
|
if ((s == NATS_OK) && (opts->Sequence > 0))
|
|
s = nats_marshalULong(buf, comma, "seq", opts->Sequence);
|
|
|
|
if ((s == NATS_OK) && (opts->Keep > 0))
|
|
s = nats_marshalULong(buf, comma, "keep", opts->Keep);
|
|
|
|
IFOK(s, natsBuf_AppendByte(buf, '}'));
|
|
|
|
if (s == NATS_OK)
|
|
*new_buf = buf;
|
|
else
|
|
natsBuf_Destroy(buf);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_purgeOrDelete(bool purge, jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
char *subj = NULL;
|
|
natsMsg *resp = NULL;
|
|
natsConnection *nc = NULL;
|
|
const char *apiT = (purge ? jsApiStreamPurgeT : jsApiStreamDeleteT);
|
|
bool freePfx = false;
|
|
natsBuffer *buf = NULL;
|
|
const void *data = NULL;
|
|
int dlen = 0;
|
|
bool success = false;
|
|
jsOptions o;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if (js == NULL)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
s = _checkStreamName(stream);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
if (nats_asprintf(&subj, apiT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
if ((s == NATS_OK) && purge)
|
|
{
|
|
s = _marshalPurgeRequest(&buf, &(o.Stream.Purge));
|
|
if ((s == NATS_OK) && (buf != NULL))
|
|
{
|
|
data = (const void*) natsBuf_Data(buf);
|
|
dlen = natsBuf_Len(buf);
|
|
}
|
|
}
|
|
|
|
// Send the request
|
|
IFOK_JSR(s, natsConnection_Request(&resp, js->nc, subj, data, dlen, o.Wait));
|
|
|
|
IFOK(s, _unmarshalSuccessResp(&success, resp, errCode));
|
|
if ((s == NATS_OK) && !success)
|
|
s = nats_setError(NATS_ERR, "failed to %s stream '%s'", (purge ? "purge" : "delete"), stream);
|
|
|
|
natsBuf_Destroy(buf);
|
|
natsMsg_Destroy(resp);
|
|
NATS_FREE(subj);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_PurgeStream(jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = _purgeOrDelete(true, js, stream, opts, errCode);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_DeleteStream(jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = _purgeOrDelete(false, js, stream, opts, errCode);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_decodeBytesLen(nats_JSON *json, const char *field, const char **str, int *strLen, int *decodedLen)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
|
|
s = nats_JSONGetStrPtr(json, field, str);
|
|
if ((s == NATS_OK) && (*str != NULL))
|
|
s = nats_Base64_DecodeLen(*str, strLen, decodedLen);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalStoredMsg(nats_JSON *json, natsMsg **new_msg)
|
|
{
|
|
natsStatus s;
|
|
natsMsg *msg = NULL;
|
|
const char *subject= NULL;
|
|
const char *hdrs = NULL;
|
|
const char *data = NULL;
|
|
int hdrsl = 0;
|
|
int dhdrsl = 0;
|
|
int datal = 0;
|
|
int ddatal = 0;
|
|
|
|
s = nats_JSONGetStrPtr(json, "subject", &subject);
|
|
IFOK(s, _decodeBytesLen(json, "hdrs", &hdrs, &hdrsl, &dhdrsl));
|
|
IFOK(s, _decodeBytesLen(json, "data", &data, &datal, &ddatal));
|
|
if ((s == NATS_OK) && (subject != NULL))
|
|
{
|
|
s = natsMsg_create(&msg, subject, (int) strlen(subject),
|
|
NULL, 0, NULL, dhdrsl+ddatal, dhdrsl);
|
|
if (s == NATS_OK)
|
|
{
|
|
if ((hdrs != NULL) && (dhdrsl > 0))
|
|
nats_Base64_DecodeInPlace(hdrs, hdrsl, (unsigned char*) msg->hdr);
|
|
if ((data != NULL) && (ddatal > 0))
|
|
nats_Base64_DecodeInPlace(data, datal, (unsigned char*) msg->data);
|
|
}
|
|
IFOK(s, nats_JSONGetULong(json, "seq", &(msg->seq)));
|
|
IFOK(s, nats_JSONGetTime(json, "time", &(msg->time)));
|
|
}
|
|
if (s == NATS_OK)
|
|
*new_msg = msg;
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalGetMsgResp(natsMsg **msg, natsMsg *resp, jsErrCode *errCode)
|
|
{
|
|
nats_JSON *json = NULL;
|
|
jsApiResponse ar;
|
|
natsStatus s;
|
|
|
|
s = js_unmarshalResponse(&ar, &json, resp);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (js_apiResponseIsErr(&ar))
|
|
{
|
|
if (errCode != NULL)
|
|
*errCode = (int) ar.Error.ErrCode;
|
|
|
|
// If the error code is JSNoMessageFoundErr then pick NATS_NOT_FOUND.
|
|
if (ar.Error.ErrCode == JSNoMessageFoundErr)
|
|
s = NATS_NOT_FOUND;
|
|
else
|
|
s = nats_setError(NATS_ERR, "%s", ar.Error.Description);
|
|
}
|
|
else
|
|
{
|
|
nats_JSON *mjson = NULL;
|
|
|
|
s = nats_JSONGetObject(json, "message", &mjson);
|
|
if ((s == NATS_OK) && (mjson == NULL))
|
|
s = nats_setError(NATS_NOT_FOUND, "%s", "message content not found");
|
|
else
|
|
s = _unmarshalStoredMsg(mjson, msg);
|
|
}
|
|
|
|
js_freeApiRespContent(&ar);
|
|
nats_JSONDestroy(json);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_getMsg(natsMsg **msg, jsCtx *js, const char *stream, uint64_t seq, const char *subject, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
char *subj = NULL;
|
|
natsMsg *resp = NULL;
|
|
natsConnection *nc = NULL;
|
|
bool freePfx = false;
|
|
jsOptions o;
|
|
char buffer[64];
|
|
natsBuffer buf;
|
|
|
|
if ((msg == NULL) || (js == NULL))
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
if (nats_IsStringEmpty(stream))
|
|
return nats_setError(NATS_INVALID_ARG, "%s", jsErrStreamNameRequired);
|
|
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
if (nats_asprintf(&subj, jsApiMsgGetT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
IFOK(s, natsBuf_InitWithBackend(&buf, buffer, 0, sizeof(buffer)));
|
|
IFOK(s, natsBuf_AppendByte(&buf, '{'));
|
|
if ((s == NATS_OK) && (seq > 0))
|
|
{
|
|
s = nats_marshalULong(&buf, false, "seq", seq);
|
|
}
|
|
else
|
|
{
|
|
IFOK(s, natsBuf_Append(&buf, "\"last_by_subj\":\"", -1));
|
|
IFOK(s, natsBuf_Append(&buf, subject, -1));
|
|
IFOK(s, natsBuf_AppendByte(&buf, '"'));
|
|
}
|
|
IFOK(s, natsBuf_AppendByte(&buf, '}'));
|
|
|
|
// Send the request
|
|
IFOK_JSR(s, natsConnection_Request(&resp, js->nc, subj, natsBuf_Data(&buf), natsBuf_Len(&buf), o.Wait));
|
|
// Unmarshal response
|
|
IFOK(s, _unmarshalGetMsgResp(msg, resp, errCode));
|
|
|
|
natsBuf_Cleanup(&buf);
|
|
natsMsg_Destroy(resp);
|
|
NATS_FREE(subj);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_GetMsg(natsMsg **msg, jsCtx *js, const char *stream, uint64_t seq, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if (seq < 1)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
s = _getMsg(msg, js, stream, seq, NULL, opts, errCode);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_GetLastMsg(natsMsg **msg, jsCtx *js, const char *stream, const char *subject, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if (nats_IsStringEmpty(subject))
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
s = _getMsg(msg, js, stream, 0, subject, opts, errCode);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
jsDirectGetMsgOptions_Init(jsDirectGetMsgOptions *opts)
|
|
{
|
|
if (opts == NULL)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
memset(opts, 0, sizeof(jsDirectGetMsgOptions));
|
|
return NATS_OK;
|
|
}
|
|
|
|
natsStatus
|
|
js_directGetMsgToJSMsg(const char *stream, natsMsg *msg)
|
|
{
|
|
natsStatus s;
|
|
const char *val = NULL;
|
|
int64_t seq = 0;
|
|
int64_t tm = 0;
|
|
|
|
if ((msg->hdrLen == 0) && (msg->headers == NULL))
|
|
return nats_setError(NATS_ERR, "%s", "direct get message response should have headers");
|
|
|
|
// If the server returns an error (not found/bad request), we would receive
|
|
// an empty body message with the Status header. Check for that.
|
|
if ((natsMsg_GetDataLength(msg) == 0)
|
|
&& (natsMsgHeader_Get(msg, STATUS_HDR, &val) == NATS_OK))
|
|
{
|
|
if (strcmp(val, NOT_FOUND_STATUS) == 0)
|
|
return nats_setDefaultError(NATS_NOT_FOUND);
|
|
else
|
|
{
|
|
natsMsgHeader_Get(msg, DESCRIPTION_HDR, &val);
|
|
return nats_setError(NATS_ERR, "error getting message: %s", val);
|
|
}
|
|
}
|
|
|
|
s = natsMsgHeader_Get(msg, JSStream, &val);
|
|
if ((s != NATS_OK) || nats_IsStringEmpty(val))
|
|
return nats_setError(NATS_ERR, "missing stream name '%s'", val);
|
|
|
|
val = NULL;
|
|
s = natsMsgHeader_Get(msg, JSSequence, &val);
|
|
if ((s != NATS_OK) || ((seq = nats_ParseInt64(val, (int) strlen(val))) == -1))
|
|
return nats_setError(NATS_ERR, "missing or invalid sequence '%s'", val);
|
|
|
|
val = NULL;
|
|
s = natsMsgHeader_Get(msg, JSTimeStamp, &val);
|
|
if ((s == NATS_OK) && !nats_IsStringEmpty(val))
|
|
s = nats_parseTime((char*) val, &tm);
|
|
if ((s != NATS_OK) || (tm == 0))
|
|
return nats_setError(NATS_ERR, "missing or invalid timestamp '%s'", val);
|
|
|
|
val = NULL;
|
|
s = natsMsgHeader_Get(msg, JSSubject, &val);
|
|
if ((s != NATS_OK) || nats_IsStringEmpty(val))
|
|
return nats_setError(NATS_ERR, "missing or invalid subject '%s'", val);
|
|
|
|
// Will point the message subject to the JSSubject header value.
|
|
// This will remain in the message memory allocated block, even
|
|
// if later the user changes the JSSubject header.
|
|
msg->subject = val;
|
|
msg->seq = seq;
|
|
msg->time = tm;
|
|
return NATS_OK;
|
|
}
|
|
|
|
natsStatus
|
|
js_DirectGetMsg(natsMsg **msg, jsCtx *js, const char *stream, jsOptions *opts, jsDirectGetMsgOptions *dgOpts)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
char *subj = NULL;
|
|
natsMsg *resp = NULL;
|
|
natsConnection *nc = NULL;
|
|
bool freePfx = false;
|
|
bool comma = false;
|
|
bool doLBS = false;
|
|
jsOptions o;
|
|
char buffer[64];
|
|
natsBuffer buf;
|
|
|
|
if ((msg == NULL) || (js == NULL) || (dgOpts == NULL))
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
if (nats_IsStringEmpty(stream))
|
|
return nats_setError(NATS_INVALID_ARG, "%s", jsErrStreamNameRequired);
|
|
|
|
doLBS = !nats_IsStringEmpty(dgOpts->LastBySubject);
|
|
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
if (doLBS)
|
|
{
|
|
if (nats_asprintf(&subj, jsApiDirectMsgGetLastBySubjectT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream, dgOpts->LastBySubject) < 0)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
}
|
|
else
|
|
{
|
|
if (nats_asprintf(&subj, jsApiDirectMsgGetT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
}
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
if ((s == NATS_OK) && doLBS)
|
|
{
|
|
IFOK(s, natsConnection_Request(&resp, js->nc, subj, NULL, 0, o.Wait));
|
|
}
|
|
else if (s == NATS_OK)
|
|
{
|
|
IFOK(s, natsBuf_InitWithBackend(&buf, buffer, 0, sizeof(buffer)));
|
|
IFOK(s, natsBuf_AppendByte(&buf, '{'));
|
|
if ((s == NATS_OK) && (dgOpts->Sequence > 0))
|
|
{
|
|
comma = true;
|
|
s = nats_marshalULong(&buf, false, "seq", dgOpts->Sequence);
|
|
}
|
|
if ((s == NATS_OK) && !nats_IsStringEmpty(dgOpts->NextBySubject))
|
|
{
|
|
if (comma)
|
|
s = natsBuf_AppendByte(&buf, ',');
|
|
|
|
comma = true;
|
|
IFOK(s, natsBuf_Append(&buf, "\"next_by_subj\":\"", -1));
|
|
IFOK(s, natsBuf_Append(&buf, dgOpts->NextBySubject, -1));
|
|
IFOK(s, natsBuf_AppendByte(&buf, '"'));
|
|
}
|
|
IFOK(s, natsBuf_AppendByte(&buf, '}'));
|
|
// Send the request
|
|
IFOK(s, natsConnection_Request(&resp, js->nc, subj, natsBuf_Data(&buf), natsBuf_Len(&buf), o.Wait));
|
|
|
|
natsBuf_Cleanup(&buf);
|
|
}
|
|
// Convert the response to a JS message returned to the user.
|
|
IFOK(s, js_directGetMsgToJSMsg(stream, resp));
|
|
|
|
NATS_FREE(subj);
|
|
|
|
if (s == NATS_OK)
|
|
*msg = resp;
|
|
else
|
|
natsMsg_Destroy(resp);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_deleteMsg(jsCtx *js, bool noErase, const char *stream, uint64_t seq, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
char *subj = NULL;
|
|
natsMsg *resp = NULL;
|
|
natsConnection *nc = NULL;
|
|
bool freePfx = false;
|
|
bool success = false;
|
|
jsOptions o;
|
|
char buffer[64];
|
|
natsBuffer buf;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if (js == NULL)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
if (nats_IsStringEmpty(stream))
|
|
return nats_setError(NATS_INVALID_ARG, "%s", jsErrStreamNameRequired);
|
|
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
if (nats_asprintf(&subj, jsApiMsgDeleteT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
IFOK(s, natsBuf_InitWithBackend(&buf, buffer, 0, sizeof(buffer)));
|
|
IFOK(s, natsBuf_AppendByte(&buf, '{'));
|
|
IFOK(s, nats_marshalULong(&buf, false, "seq", seq));
|
|
if ((s == NATS_OK) && noErase)
|
|
s = natsBuf_Append(&buf, ",\"no_erase\":true", -1);
|
|
IFOK(s, natsBuf_AppendByte(&buf, '}'));
|
|
|
|
// Send the request
|
|
IFOK_JSR(s, natsConnection_Request(&resp, js->nc, subj, natsBuf_Data(&buf), natsBuf_Len(&buf), o.Wait));
|
|
|
|
IFOK(s, _unmarshalSuccessResp(&success, resp, errCode));
|
|
if ((s == NATS_OK) && !success)
|
|
s = nats_setError(NATS_ERR, "failed to delete message %" PRIu64, seq);
|
|
|
|
natsBuf_Cleanup(&buf);
|
|
natsMsg_Destroy(resp);
|
|
NATS_FREE(subj);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_DeleteMsg(jsCtx *js, const char *stream, uint64_t seq, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s;
|
|
|
|
s = _deleteMsg(js, true, stream, seq, opts, errCode);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_EraseMsg(jsCtx *js, const char *stream, uint64_t seq, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s;
|
|
|
|
s = _deleteMsg(js, false, stream, seq, opts, errCode);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalStreamInfoListResp(natsStrHash *m, apiPaged *page, natsMsg *resp, jsErrCode *errCode)
|
|
{
|
|
nats_JSON *json = NULL;
|
|
jsApiResponse ar;
|
|
natsStatus s;
|
|
|
|
s = js_unmarshalResponse(&ar, &json, resp);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (js_apiResponseIsErr(&ar))
|
|
{
|
|
if (errCode != NULL)
|
|
*errCode = (int) ar.Error.ErrCode;
|
|
|
|
// If the error code is JSStreamNotFoundErr then pick NATS_NOT_FOUND.
|
|
if (ar.Error.ErrCode == JSStreamNotFoundErr)
|
|
s = NATS_NOT_FOUND;
|
|
else
|
|
s = nats_setError(NATS_ERR, "%s", ar.Error.Description);
|
|
}
|
|
else
|
|
{
|
|
nats_JSON **streams = NULL;
|
|
int streamsLen = 0;
|
|
|
|
IFOK(s, nats_JSONGetLong(json, "total", &page->total));
|
|
IFOK(s, nats_JSONGetLong(json, "offset", &page->offset));
|
|
IFOK(s, nats_JSONGetLong(json, "limit", &page->limit));
|
|
IFOK(s, nats_JSONGetArrayObject(json, "streams", &streams, &streamsLen));
|
|
if ((s == NATS_OK) && (streams != NULL))
|
|
{
|
|
int i;
|
|
|
|
for (i=0; (s == NATS_OK) && (i<streamsLen); i++)
|
|
{
|
|
jsStreamInfo *si = NULL;
|
|
jsStreamInfo *osi = NULL;
|
|
|
|
s = js_unmarshalStreamInfo(streams[i], &si);
|
|
if ((s == NATS_OK) && ((si->Config == NULL) || nats_IsStringEmpty(si->Config->Name)))
|
|
s = nats_setError(NATS_ERR, "%s", "stream name missing from configuration");
|
|
IFOK(s, natsStrHash_SetEx(m, (char*) si->Config->Name, true, true, si, (void**) &osi));
|
|
if (osi != NULL)
|
|
jsStreamInfo_Destroy(osi);
|
|
}
|
|
// Free the array of JSON objects that was allocated by nats_JSONGetArrayObject.
|
|
NATS_FREE(streams);
|
|
}
|
|
}
|
|
js_freeApiRespContent(&ar);
|
|
nats_JSONDestroy(json);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_Streams(jsStreamInfoList **new_list, jsCtx *js, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
natsBuffer *buf = NULL;
|
|
char *subj = NULL;
|
|
natsMsg *resp = NULL;
|
|
natsConnection *nc = NULL;
|
|
bool freePfx = false;
|
|
bool done = false;
|
|
int64_t offset = 0;
|
|
int64_t start = 0;
|
|
int64_t timeout = 0;
|
|
natsStrHash *streams= NULL;
|
|
jsStreamInfoList *list = NULL;
|
|
jsOptions o;
|
|
apiPaged page;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if ((new_list == NULL) || (js == NULL))
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
if (nats_asprintf(&subj, jsApiStreamListT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix) < 0)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
IFOK(s, natsBuf_Create(&buf, 64));
|
|
IFOK(s, natsStrHash_Create(&streams, 16));
|
|
|
|
if (s == NATS_OK)
|
|
{
|
|
memset(&page, 0, sizeof(apiPaged));
|
|
start = nats_Now();
|
|
}
|
|
|
|
do
|
|
{
|
|
IFOK(s, natsBuf_AppendByte(buf, '{'));
|
|
IFOK(s, nats_marshalLong(buf, false, "offset", offset));
|
|
if (!nats_IsStringEmpty(o.Stream.Info.SubjectsFilter))
|
|
{
|
|
IFOK(s, natsBuf_Append(buf, ",\"subject\":\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, o.Stream.Info.SubjectsFilter, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '\"'));
|
|
}
|
|
IFOK(s, natsBuf_AppendByte(buf, '}'));
|
|
|
|
timeout = o.Wait - (nats_Now() - start);
|
|
if (timeout <= 0)
|
|
s = NATS_TIMEOUT;
|
|
|
|
// Send the request
|
|
IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, natsBuf_Data(buf), natsBuf_Len(buf), timeout));
|
|
|
|
IFOK(s, _unmarshalStreamInfoListResp(streams, &page, resp, errCode));
|
|
if (s == NATS_OK)
|
|
{
|
|
offset += page.limit;
|
|
done = offset >= page.total;
|
|
if (!done)
|
|
{
|
|
// Reset the request buffer, we may be able to reuse.
|
|
natsBuf_Reset(buf);
|
|
}
|
|
}
|
|
natsMsg_Destroy(resp);
|
|
resp = NULL;
|
|
}
|
|
while ((s == NATS_OK) && !done);
|
|
|
|
natsBuf_Destroy(buf);
|
|
NATS_FREE(subj);
|
|
|
|
if (s == NATS_OK)
|
|
{
|
|
if (natsStrHash_Count(streams) == 0)
|
|
{
|
|
natsStrHash_Destroy(streams);
|
|
return NATS_NOT_FOUND;
|
|
}
|
|
list = (jsStreamInfoList*) NATS_CALLOC(1, sizeof(jsStreamInfoList));
|
|
if (list == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
else
|
|
{
|
|
list->List = (jsStreamInfo**) NATS_CALLOC(natsStrHash_Count(streams), sizeof(jsStreamInfo*));
|
|
if (list->List == NULL)
|
|
{
|
|
NATS_FREE(list);
|
|
list = NULL;
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
}
|
|
else
|
|
{
|
|
natsStrHashIter iter;
|
|
void *val = NULL;
|
|
|
|
natsStrHashIter_Init(&iter, streams);
|
|
while (natsStrHashIter_Next(&iter, NULL, (void**) &val))
|
|
{
|
|
jsStreamInfo *si = (jsStreamInfo*) val;
|
|
|
|
list->List[list->Count++] = si;
|
|
natsStrHashIter_RemoveCurrent(&iter);
|
|
}
|
|
natsStrHashIter_Done(&iter);
|
|
|
|
*new_list = list;
|
|
}
|
|
}
|
|
}
|
|
if (s != NATS_OK)
|
|
{
|
|
natsStrHashIter iter;
|
|
void *val = NULL;
|
|
|
|
natsStrHashIter_Init(&iter, streams);
|
|
while (natsStrHashIter_Next(&iter, NULL, (void**) &val))
|
|
{
|
|
jsStreamInfo *si = (jsStreamInfo*) val;
|
|
|
|
natsStrHashIter_RemoveCurrent(&iter);
|
|
jsStreamInfo_Destroy(si);
|
|
}
|
|
natsStrHashIter_Done(&iter);
|
|
}
|
|
natsStrHash_Destroy(streams);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
void
|
|
jsStreamInfoList_Destroy(jsStreamInfoList *list)
|
|
{
|
|
int i;
|
|
|
|
if (list == NULL)
|
|
return;
|
|
|
|
for (i=0; i<list->Count; i++)
|
|
jsStreamInfo_Destroy(list->List[i]);
|
|
|
|
NATS_FREE(list->List);
|
|
NATS_FREE(list);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalNamesListResp(const char *fieldName, natsStrHash *m, apiPaged *page, natsMsg *resp, jsErrCode *errCode)
|
|
{
|
|
nats_JSON *json = NULL;
|
|
jsApiResponse ar;
|
|
natsStatus s;
|
|
|
|
s = js_unmarshalResponse(&ar, &json, resp);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (js_apiResponseIsErr(&ar))
|
|
{
|
|
if (errCode != NULL)
|
|
*errCode = (int) ar.Error.ErrCode;
|
|
|
|
// If the error code is JSStreamNotFoundErr then pick NATS_NOT_FOUND.
|
|
if (ar.Error.ErrCode == JSStreamNotFoundErr)
|
|
s = NATS_NOT_FOUND;
|
|
else
|
|
s = nats_setError(NATS_ERR, "%s", ar.Error.Description);
|
|
}
|
|
else
|
|
{
|
|
char **names = NULL;
|
|
int namesLen = 0;
|
|
|
|
IFOK(s, nats_JSONGetLong(json, "total", &page->total));
|
|
IFOK(s, nats_JSONGetLong(json, "offset", &page->offset));
|
|
IFOK(s, nats_JSONGetLong(json, "limit", &page->limit));
|
|
IFOK(s, nats_JSONGetArrayStr(json, fieldName, &names, &namesLen));
|
|
if ((s == NATS_OK) && (names != NULL))
|
|
{
|
|
int i;
|
|
|
|
for (i=0; (s == NATS_OK) && (i<namesLen); i++)
|
|
s = natsStrHash_Set(m, names[i], true, NULL, NULL);
|
|
|
|
// Free the array of JSON objects that was allocated by nats_JSONGetArrayStr.
|
|
for (i=0; i<namesLen; i++)
|
|
NATS_FREE(names[i]);
|
|
NATS_FREE(names);
|
|
}
|
|
}
|
|
js_freeApiRespContent(&ar);
|
|
nats_JSONDestroy(json);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_StreamNames(jsStreamNamesList **new_list, jsCtx *js, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
natsBuffer *buf = NULL;
|
|
char *subj = NULL;
|
|
natsMsg *resp = NULL;
|
|
natsConnection *nc = NULL;
|
|
bool freePfx = false;
|
|
bool done = false;
|
|
int64_t offset = 0;
|
|
int64_t start = 0;
|
|
int64_t timeout = 0;
|
|
natsStrHash *names = NULL;
|
|
jsStreamNamesList *list = NULL;
|
|
jsOptions o;
|
|
apiPaged page;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if ((new_list == NULL) || (js == NULL))
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
if (nats_asprintf(&subj, jsApiStreamNamesT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix) < 0)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
IFOK(s, natsBuf_Create(&buf, 64));
|
|
IFOK(s, natsStrHash_Create(&names, 16));
|
|
|
|
if (s == NATS_OK)
|
|
{
|
|
memset(&page, 0, sizeof(apiPaged));
|
|
start = nats_Now();
|
|
}
|
|
|
|
do
|
|
{
|
|
IFOK(s, natsBuf_AppendByte(buf, '{'));
|
|
IFOK(s, nats_marshalLong(buf, false, "offset", offset));
|
|
if (!nats_IsStringEmpty(o.Stream.Info.SubjectsFilter))
|
|
{
|
|
IFOK(s, natsBuf_Append(buf, ",\"subject\":\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, o.Stream.Info.SubjectsFilter, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '\"'));
|
|
}
|
|
IFOK(s, natsBuf_AppendByte(buf, '}'));
|
|
|
|
timeout = o.Wait - (nats_Now() - start);
|
|
if (timeout <= 0)
|
|
s = NATS_TIMEOUT;
|
|
|
|
// Send the request
|
|
IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, natsBuf_Data(buf), natsBuf_Len(buf), timeout));
|
|
|
|
IFOK(s, _unmarshalNamesListResp("streams", names, &page, resp, errCode));
|
|
if (s == NATS_OK)
|
|
{
|
|
offset += page.limit;
|
|
done = offset >= page.total;
|
|
if (!done)
|
|
{
|
|
// Reset the request buffer, we may be able to reuse.
|
|
natsBuf_Reset(buf);
|
|
}
|
|
}
|
|
natsMsg_Destroy(resp);
|
|
resp = NULL;
|
|
}
|
|
while ((s == NATS_OK) && !done);
|
|
|
|
natsBuf_Destroy(buf);
|
|
NATS_FREE(subj);
|
|
|
|
if (s == NATS_OK)
|
|
{
|
|
if (natsStrHash_Count(names) == 0)
|
|
{
|
|
natsStrHash_Destroy(names);
|
|
return NATS_NOT_FOUND;
|
|
}
|
|
list = (jsStreamNamesList*) NATS_CALLOC(1, sizeof(jsStreamNamesList));
|
|
if (list == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
else
|
|
{
|
|
list->List = (char**) NATS_CALLOC(natsStrHash_Count(names), sizeof(char*));
|
|
if (list->List == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
else
|
|
{
|
|
natsStrHashIter iter;
|
|
char *sname = NULL;
|
|
|
|
natsStrHashIter_Init(&iter, names);
|
|
while ((s == NATS_OK) && natsStrHashIter_Next(&iter, &sname, NULL))
|
|
{
|
|
char *copyName = NULL;
|
|
|
|
DUP_STRING(s, copyName, sname);
|
|
if (s == NATS_OK)
|
|
{
|
|
list->List[list->Count++] = copyName;
|
|
natsStrHashIter_RemoveCurrent(&iter);
|
|
}
|
|
}
|
|
natsStrHashIter_Done(&iter);
|
|
}
|
|
if (s == NATS_OK)
|
|
*new_list = list;
|
|
else
|
|
jsStreamNamesList_Destroy(list);
|
|
}
|
|
}
|
|
natsStrHash_Destroy(names);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
void
|
|
jsStreamNamesList_Destroy(jsStreamNamesList *list)
|
|
{
|
|
int i;
|
|
|
|
if (list == NULL)
|
|
return;
|
|
|
|
for (i=0; i<list->Count; i++)
|
|
NATS_FREE(list->List[i]);
|
|
|
|
NATS_FREE(list->List);
|
|
NATS_FREE(list);
|
|
}
|
|
|
|
//
|
|
// Account related functions
|
|
//
|
|
|
|
static natsStatus
|
|
_unmarshalAccLimits(nats_JSON *json, jsAccountLimits *limits)
|
|
{
|
|
natsStatus s;
|
|
nats_JSON *obj = NULL;
|
|
|
|
s = nats_JSONGetObject(json, "limits", &obj);
|
|
if (obj == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
IFOK(s, nats_JSONGetLong(obj, "max_memory", &(limits->MaxMemory)));
|
|
IFOK(s, nats_JSONGetLong(obj, "max_storage", &(limits->MaxStore)));
|
|
IFOK(s, nats_JSONGetLong(obj, "max_streams", &(limits->MaxStreams)));
|
|
IFOK(s, nats_JSONGetLong(obj, "max_consumers", &(limits->MaxConsumers)));
|
|
IFOK(s, nats_JSONGetLong(obj, "max_ack_pending", &(limits->MaxAckPending)));
|
|
IFOK(s, nats_JSONGetLong(obj, "memory_max_stream_bytes", &(limits->MemoryMaxStreamBytes)));
|
|
IFOK(s, nats_JSONGetLong(obj, "storage_max_stream_bytes", &(limits->StoreMaxStreamBytes)));
|
|
IFOK(s, nats_JSONGetBool(obj, "max_bytes_required", &(limits->MaxBytesRequired)));
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_fillTier(void *userInfo, const char *subject, nats_JSONField *f)
|
|
{
|
|
jsAccountInfo *ai = (jsAccountInfo*) userInfo;
|
|
natsStatus s = NATS_OK;
|
|
jsTier *t = NULL;
|
|
nats_JSON *json = f->value.vobj;
|
|
|
|
t = (jsTier*) NATS_CALLOC(1, sizeof(jsTier));
|
|
if (t == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
ai->Tiers[ai->TiersLen++] = t;
|
|
|
|
DUP_STRING(s, t->Name, subject);
|
|
IFOK(s, nats_JSONGetULong(json, "memory", &(t->Memory)));
|
|
IFOK(s, nats_JSONGetULong(json, "storage", &(t->Store)));
|
|
IFOK(s, nats_JSONGetLong(json, "streams", &(t->Streams)));
|
|
IFOK(s, nats_JSONGetLong(json, "consumers", &(t->Consumers)));
|
|
IFOK(s, _unmarshalAccLimits(json, &(t->Limits)));
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalAccTiers(nats_JSON *json, jsAccountInfo *ai)
|
|
{
|
|
nats_JSON *obj = NULL;
|
|
natsStatus s = NATS_OK;
|
|
int n;
|
|
|
|
s = nats_JSONGetObject(json, "tier", &obj);
|
|
if (obj == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
n = natsStrHash_Count(obj->fields);
|
|
if (n == 0)
|
|
return NATS_OK;
|
|
|
|
ai->Tiers = (jsTier**) NATS_CALLOC(n, sizeof(jsTier*));
|
|
if (ai->Tiers == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
s = nats_JSONRange(obj, TYPE_OBJECT, 0, _fillTier, (void*) ai);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_unmarshalAccountInfo(nats_JSON *json, jsAccountInfo **new_ai)
|
|
{
|
|
natsStatus s;
|
|
nats_JSON *obj = NULL;
|
|
jsAccountInfo *ai = NULL;
|
|
|
|
ai = (jsAccountInfo*) NATS_CALLOC(1, sizeof(jsAccountInfo));
|
|
if (ai == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
s = nats_JSONGetULong(json, "memory", &(ai->Memory));
|
|
IFOK(s, nats_JSONGetULong(json, "storage", &(ai->Store)));
|
|
IFOK(s, nats_JSONGetLong(json, "streams", &(ai->Streams)));
|
|
IFOK(s, nats_JSONGetLong(json, "consumers", &(ai->Consumers)));
|
|
IFOK(s, nats_JSONGetStr(json, "domain", &(ai->Domain)));
|
|
IFOK(s, nats_JSONGetObject(json, "api", &obj));
|
|
if ((s == NATS_OK) && (obj != NULL))
|
|
{
|
|
IFOK(s, nats_JSONGetULong(obj, "total", &(ai->API.Total)));
|
|
IFOK(s, nats_JSONGetULong(obj, "errors", &(ai->API.Errors)));
|
|
obj = NULL;
|
|
}
|
|
IFOK(s, _unmarshalAccLimits(json, &(ai->Limits)));
|
|
IFOK(s, _unmarshalAccTiers(json, ai));
|
|
|
|
if (s == NATS_OK)
|
|
*new_ai = ai;
|
|
else
|
|
jsAccountInfo_Destroy(ai);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalAccountInfoResp(jsAccountInfo **new_ai, natsMsg *resp, jsErrCode *errCode)
|
|
{
|
|
nats_JSON *json = NULL;
|
|
jsApiResponse ar;
|
|
natsStatus s;
|
|
|
|
s = js_unmarshalResponse(&ar, &json, resp);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (js_apiResponseIsErr(&ar))
|
|
{
|
|
if (errCode != NULL)
|
|
*errCode = (int) ar.Error.ErrCode;
|
|
s = nats_setError(NATS_ERR, "%s", ar.Error.Description);
|
|
}
|
|
else
|
|
s = js_unmarshalAccountInfo(json, new_ai);
|
|
|
|
js_freeApiRespContent(&ar);
|
|
nats_JSONDestroy(json);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_GetAccountInfo(jsAccountInfo **new_ai, jsCtx *js, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsMsg *resp = NULL;
|
|
char *subj = NULL;
|
|
natsConnection *nc = NULL;
|
|
natsStatus s = NATS_OK;
|
|
bool freePfx = false;
|
|
jsOptions o;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if (new_ai == NULL || js == NULL)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
if (nats_asprintf(&subj, jsApiAccountInfo, js_lenWithoutTrailingDot(o.Prefix), o.Prefix) < 0)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
// Send the request
|
|
IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, NULL, 0, o.Wait));
|
|
|
|
// If we get a response, unmarshal the response
|
|
IFOK(s, _unmarshalAccountInfoResp(new_ai, resp, errCode));
|
|
|
|
// Common cleanup that is done regardless of success or failure.
|
|
NATS_FREE(subj);
|
|
natsMsg_Destroy(resp);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
void
|
|
jsAccountInfo_Destroy(jsAccountInfo *ai)
|
|
{
|
|
if (ai == NULL)
|
|
return;
|
|
|
|
if (ai->Tiers != NULL)
|
|
{
|
|
int i;
|
|
for (i=0; i<ai->TiersLen; i++)
|
|
{
|
|
jsTier *t = ai->Tiers[i];
|
|
|
|
NATS_FREE((char*) t->Name);
|
|
NATS_FREE(t);
|
|
}
|
|
NATS_FREE(ai->Tiers);
|
|
}
|
|
NATS_FREE(ai->Domain);
|
|
NATS_FREE(ai);
|
|
}
|
|
|
|
natsStatus
|
|
jsPlacement_Init(jsPlacement *placement)
|
|
{
|
|
if (placement == NULL)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
memset(placement, 0, sizeof(jsPlacement));
|
|
return NATS_OK;
|
|
}
|
|
|
|
natsStatus
|
|
jsStreamSource_Init(jsStreamSource *source)
|
|
{
|
|
if (source == NULL)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
memset(source, 0, sizeof(jsStreamSource));
|
|
return NATS_OK;
|
|
|
|
}
|
|
|
|
natsStatus
|
|
jsExternalStream_Init(jsExternalStream *external)
|
|
{
|
|
if (external == NULL)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
memset(external, 0, sizeof(jsExternalStream));
|
|
return NATS_OK;
|
|
}
|
|
|
|
natsStatus
|
|
jsRePublish_Init(jsRePublish *rp)
|
|
{
|
|
if (rp == NULL)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
memset(rp, 0, sizeof(jsRePublish));
|
|
return NATS_OK;
|
|
}
|
|
|
|
//
|
|
// Consumer related functions
|
|
//
|
|
|
|
static natsStatus
|
|
_marshalDeliverPolicy(natsBuffer *buf, jsDeliverPolicy p)
|
|
{
|
|
natsStatus s;
|
|
const char *dp = NULL;
|
|
|
|
s = natsBuf_Append(buf, "\"deliver_policy\":\"", -1);
|
|
switch (p)
|
|
{
|
|
case js_DeliverAll: dp = jsDeliverAllStr; break;
|
|
case js_DeliverLast: dp = jsDeliverLastStr; break;
|
|
case js_DeliverNew: dp = jsDeliverNewStr; break;
|
|
case js_DeliverByStartSequence: dp = jsDeliverBySeqStr; break;
|
|
case js_DeliverByStartTime: dp = jsDeliverByTimeStr; break;
|
|
case js_DeliverLastPerSubject: dp = jsDeliverLastPerSubjectStr;break;
|
|
default:
|
|
return nats_setError(NATS_INVALID_ARG, "invalid deliver policy %d", (int) p);
|
|
}
|
|
IFOK(s, natsBuf_Append(buf, dp, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_marshalAckPolicy(natsBuffer *buf, jsAckPolicy p)
|
|
{
|
|
natsStatus s;
|
|
const char *ap;
|
|
|
|
s = natsBuf_Append(buf, ",\"ack_policy\":\"", -1);
|
|
switch (p)
|
|
{
|
|
case js_AckNone: ap = jsAckNoneStr; break;
|
|
case js_AckAll: ap = jsAckAllStr; break;
|
|
case js_AckExplicit: ap = jsAckExplictStr; break;
|
|
default:
|
|
return nats_setError(NATS_INVALID_ARG, "invalid ack policy %d", (int)p);
|
|
}
|
|
IFOK(s, natsBuf_Append(buf, ap, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_marshalReplayPolicy(natsBuffer *buf, jsReplayPolicy p)
|
|
{
|
|
natsStatus s;
|
|
const char *rp = NULL;
|
|
|
|
s = natsBuf_Append(buf, ",\"replay_policy\":\"", -1);
|
|
switch (p)
|
|
{
|
|
case js_ReplayOriginal: rp = jsReplayOriginalStr; break;
|
|
case js_ReplayInstant: rp = jsReplayInstantStr; break;
|
|
default:
|
|
return nats_setError(NATS_INVALID_ARG, "invalid replay policy %d", (int)p);
|
|
}
|
|
IFOK(s, natsBuf_Append(buf, rp, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_marshalConsumerCreateReq(natsBuffer **new_buf, const char *stream, jsConsumerConfig *cfg)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
natsBuffer *buf = NULL;
|
|
|
|
// If not set, set some defaults
|
|
if ((int) cfg->DeliverPolicy < 0)
|
|
cfg->DeliverPolicy = js_DeliverAll;
|
|
if ((int) cfg->AckPolicy < 0)
|
|
cfg->AckPolicy = js_AckExplicit;
|
|
if ((int) cfg->ReplayPolicy < 0)
|
|
cfg->ReplayPolicy = js_ReplayInstant;
|
|
|
|
s = natsBuf_Create(&buf, 256);
|
|
IFOK(s, natsBuf_Append(buf, "{\"stream_name\":\"", -1));
|
|
IFOK(s, natsBuf_Append(buf, stream, -1));
|
|
IFOK(s, natsBuf_Append(buf, "\",\"config\":{", -1));
|
|
// Marshal something that is always present first, so that the optionals
|
|
// will always start with a "," and we know that there will be a field before that.
|
|
IFOK(s, _marshalDeliverPolicy(buf, cfg->DeliverPolicy));
|
|
if ((s == NATS_OK) && !nats_IsStringEmpty(cfg->Name))
|
|
{
|
|
s = natsBuf_Append(buf, ",\"name\":\"", -1);
|
|
IFOK(s, natsBuf_Append(buf, cfg->Name, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
}
|
|
if ((s == NATS_OK) && (!nats_IsStringEmpty(cfg->Description)))
|
|
{
|
|
s = natsBuf_Append(buf, ",\"description\":\"", -1);
|
|
IFOK(s, natsBuf_Append(buf, cfg->Description, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
}
|
|
if ((s == NATS_OK) && (!nats_IsStringEmpty(cfg->Durable)))
|
|
{
|
|
s = natsBuf_Append(buf, ",\"durable_name\":\"", -1);
|
|
IFOK(s, natsBuf_Append(buf, cfg->Durable, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
}
|
|
if ((s == NATS_OK) && (!nats_IsStringEmpty(cfg->DeliverSubject)))
|
|
{
|
|
s = natsBuf_Append(buf, ",\"deliver_subject\":\"", -1);
|
|
IFOK(s, natsBuf_Append(buf, cfg->DeliverSubject, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
}
|
|
if ((s == NATS_OK) && (!nats_IsStringEmpty(cfg->DeliverGroup)))
|
|
{
|
|
s = natsBuf_Append(buf, ",\"deliver_group\":\"", -1);
|
|
IFOK(s, natsBuf_Append(buf, cfg->DeliverGroup, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
}
|
|
if ((s == NATS_OK) && (cfg->OptStartSeq > 0))
|
|
s = nats_marshalLong(buf, true, "opt_start_seq", cfg->OptStartSeq);
|
|
if ((s == NATS_OK) && (cfg->OptStartTime > 0))
|
|
s = _marshalTimeUTC(buf, "opt_start_time", cfg->OptStartTime);
|
|
IFOK(s, _marshalAckPolicy(buf, cfg->AckPolicy));
|
|
if ((s == NATS_OK) && (cfg->AckWait > 0))
|
|
s = nats_marshalLong(buf, true, "ack_wait", cfg->AckWait);
|
|
if ((s == NATS_OK) && (cfg->MaxDeliver > 0))
|
|
s = nats_marshalLong(buf, true, "max_deliver", cfg->MaxDeliver);
|
|
if ((s == NATS_OK) && !nats_IsStringEmpty(cfg->FilterSubject))
|
|
{
|
|
s = natsBuf_Append(buf, ",\"filter_subject\":\"", -1);
|
|
IFOK(s, natsBuf_Append(buf, cfg->FilterSubject, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
}
|
|
IFOK(s, _marshalReplayPolicy(buf, cfg->ReplayPolicy))
|
|
if ((s == NATS_OK) && (cfg->RateLimit > 0))
|
|
s = nats_marshalULong(buf, true, "rate_limit_bps", cfg->RateLimit);
|
|
if ((s == NATS_OK) && !nats_IsStringEmpty(cfg->SampleFrequency))
|
|
{
|
|
s = natsBuf_Append(buf, ",\"sample_freq\":\"", -1);
|
|
IFOK(s, natsBuf_Append(buf, cfg->SampleFrequency, -1));
|
|
IFOK(s, natsBuf_AppendByte(buf, '"'));
|
|
}
|
|
if ((s == NATS_OK) && (cfg->MaxWaiting > 0))
|
|
s = nats_marshalLong(buf, true, "max_waiting", cfg->MaxWaiting);
|
|
if ((s == NATS_OK) && (cfg->MaxAckPending > 0))
|
|
s = nats_marshalLong(buf, true, "max_ack_pending", cfg->MaxAckPending);
|
|
if ((s == NATS_OK) && cfg->FlowControl)
|
|
s = natsBuf_Append(buf, ",\"flow_control\":true", -1);
|
|
if ((s == NATS_OK) && (cfg->Heartbeat > 0))
|
|
s = nats_marshalLong(buf, true, "idle_heartbeat", cfg->Heartbeat);
|
|
if ((s == NATS_OK) && cfg->HeadersOnly)
|
|
s = natsBuf_Append(buf, ",\"headers_only\":true", -1);
|
|
if ((s == NATS_OK) && (cfg->MaxRequestBatch > 0))
|
|
s = nats_marshalLong(buf, true, "max_batch", cfg->MaxRequestBatch);
|
|
if ((s == NATS_OK) && (cfg->MaxRequestExpires > 0))
|
|
s = nats_marshalLong(buf, true, "max_expires", cfg->MaxRequestExpires);
|
|
if ((s == NATS_OK) && (cfg->MaxRequestMaxBytes > 0))
|
|
s = nats_marshalLong(buf, true, "max_bytes", cfg->MaxRequestMaxBytes);
|
|
if ((s == NATS_OK) && (cfg->InactiveThreshold > 0))
|
|
s = nats_marshalLong(buf, true, "inactive_threshold", cfg->InactiveThreshold);
|
|
if ((s == NATS_OK) && (cfg->BackOff != NULL) && (cfg->BackOffLen > 0))
|
|
{
|
|
char tmp[32];
|
|
int i;
|
|
|
|
s = natsBuf_Append(buf, ",\"backoff\":[", -1);
|
|
for (i=0; (s == NATS_OK) && (i<cfg->BackOffLen); i++)
|
|
{
|
|
snprintf(tmp, sizeof(tmp), "%" PRId64, cfg->BackOff[i]);
|
|
if (i > 0)
|
|
s = natsBuf_AppendByte(buf, ',');
|
|
IFOK(s, natsBuf_Append(buf, tmp, -1));
|
|
}
|
|
IFOK(s, natsBuf_AppendByte(buf, ']'));
|
|
}
|
|
if ((s == NATS_OK) && (cfg->Replicas > 0))
|
|
s = nats_marshalLong(buf, true, "num_replicas", cfg->Replicas);
|
|
if ((s == NATS_OK) && cfg->MemoryStorage)
|
|
s = natsBuf_Append(buf, ",\"mem_storage\":true", -1);
|
|
IFOK(s, natsBuf_Append(buf, "}}", -1));
|
|
|
|
if (s == NATS_OK)
|
|
*new_buf = buf;
|
|
else
|
|
natsBuf_Destroy(buf);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
void
|
|
js_destroyConsumerConfig(jsConsumerConfig *cc)
|
|
{
|
|
if (cc == NULL)
|
|
return;
|
|
|
|
NATS_FREE((char*) cc->Name);
|
|
NATS_FREE((char*) cc->Durable);
|
|
NATS_FREE((char*) cc->Description);
|
|
NATS_FREE((char*) cc->DeliverSubject);
|
|
NATS_FREE((char*) cc->DeliverGroup);
|
|
NATS_FREE((char*) cc->FilterSubject);
|
|
NATS_FREE((char*) cc->SampleFrequency);
|
|
NATS_FREE(cc->BackOff);
|
|
NATS_FREE(cc);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalDeliverPolicy(nats_JSON *json, const char *fieldName, jsDeliverPolicy *dp)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
char *str = NULL;
|
|
|
|
s = nats_JSONGetStr(json, fieldName, &str);
|
|
if (str == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (strcmp(str, jsDeliverAllStr) == 0)
|
|
*dp = js_DeliverAll;
|
|
else if (strcmp(str, jsDeliverLastStr) == 0)
|
|
*dp = js_DeliverLast;
|
|
else if (strcmp(str, jsDeliverNewStr) == 0)
|
|
*dp = js_DeliverNew;
|
|
else if (strcmp(str, jsDeliverBySeqStr) == 0)
|
|
*dp = js_DeliverByStartSequence;
|
|
else if (strcmp(str, jsDeliverByTimeStr) == 0)
|
|
*dp = js_DeliverByStartTime;
|
|
else if (strcmp(str, jsDeliverLastPerSubjectStr) == 0)
|
|
*dp = js_DeliverLastPerSubject;
|
|
else
|
|
s = nats_setError(NATS_ERR, "unable to unmarshal delivery policy '%s'", str);
|
|
|
|
NATS_FREE(str);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalAckPolicy(nats_JSON *json, const char *fieldName, jsAckPolicy *ap)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
char *str = NULL;
|
|
|
|
s = nats_JSONGetStr(json, fieldName, &str);
|
|
if (str == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (strcmp(str, jsAckNoneStr) == 0)
|
|
*ap = js_AckNone;
|
|
else if (strcmp(str, jsAckAllStr) == 0)
|
|
*ap = js_AckAll;
|
|
else if (strcmp(str, jsAckExplictStr) == 0)
|
|
*ap = js_AckExplicit;
|
|
else
|
|
s = nats_setError(NATS_ERR, "unable to unmarshal ack policy '%s'", str);
|
|
|
|
NATS_FREE(str);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalReplayPolicy(nats_JSON *json, const char *fieldName, jsReplayPolicy *rp)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
char *str = NULL;
|
|
|
|
s = nats_JSONGetStr(json, fieldName, &str);
|
|
if (str == NULL)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (strcmp(str, jsReplayOriginalStr) == 0)
|
|
*rp = js_ReplayOriginal;
|
|
else if (strcmp(str, jsReplayInstantStr) == 0)
|
|
*rp = js_ReplayInstant;
|
|
else
|
|
s = nats_setError(NATS_ERR, "unable to unmarshal replay policy '%s'", str);
|
|
|
|
NATS_FREE(str);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalConsumerConfig(nats_JSON *json, const char *fieldName, jsConsumerConfig **new_cc)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
jsConsumerConfig *cc = NULL;
|
|
nats_JSON *cjson = NULL;
|
|
|
|
cc = (jsConsumerConfig*) NATS_CALLOC(1, sizeof(jsConsumerConfig));
|
|
if (cc == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
s = nats_JSONGetObject(json, fieldName, &cjson);
|
|
if ((s == NATS_OK) && (cjson != NULL))
|
|
{
|
|
s = nats_JSONGetStr(cjson, "durable_name", (char**) &(cc->Durable));
|
|
IFOK(s, nats_JSONGetStr(cjson, "name", (char**) &(cc->Name)));
|
|
IFOK(s, nats_JSONGetStr(cjson, "description", (char**) &(cc->Description)));
|
|
IFOK(s, nats_JSONGetStr(cjson, "deliver_subject", (char**) &(cc->DeliverSubject)));
|
|
IFOK(s, nats_JSONGetStr(cjson, "deliver_group", (char**) &(cc->DeliverGroup)));
|
|
IFOK(s, _unmarshalDeliverPolicy(cjson, "deliver_policy", &(cc->DeliverPolicy)));
|
|
IFOK(s, nats_JSONGetULong(cjson, "opt_start_seq", &(cc->OptStartSeq)));
|
|
IFOK(s, nats_JSONGetTime(cjson, "opt_start_time", &(cc->OptStartTime)));
|
|
IFOK(s, _unmarshalAckPolicy(cjson, "ack_policy", &(cc->AckPolicy)));
|
|
IFOK(s, nats_JSONGetLong(cjson, "ack_wait", &(cc->AckWait)));
|
|
IFOK(s, nats_JSONGetLong(cjson, "max_deliver", &(cc->MaxDeliver)));
|
|
IFOK(s, nats_JSONGetStr(cjson, "filter_subject", (char**) &(cc->FilterSubject)));
|
|
IFOK(s, _unmarshalReplayPolicy(cjson, "replay_policy", &(cc->ReplayPolicy)));
|
|
IFOK(s, nats_JSONGetULong(cjson, "rate_limit_bps", &(cc->RateLimit)));
|
|
IFOK(s, nats_JSONGetStr(cjson, "sample_freq", (char**) &(cc->SampleFrequency)));
|
|
IFOK(s, nats_JSONGetLong(cjson, "max_waiting", &(cc->MaxWaiting)));
|
|
IFOK(s, nats_JSONGetLong(cjson, "max_ack_pending", &(cc->MaxAckPending)));
|
|
IFOK(s, nats_JSONGetBool(cjson, "flow_control", &(cc->FlowControl)));
|
|
IFOK(s, nats_JSONGetLong(cjson, "idle_heartbeat", &(cc->Heartbeat)));
|
|
IFOK(s, nats_JSONGetBool(cjson, "headers_only", &(cc->HeadersOnly)));
|
|
IFOK(s, nats_JSONGetLong(cjson, "max_batch", &(cc->MaxRequestBatch)));
|
|
IFOK(s, nats_JSONGetLong(cjson, "max_expires", &(cc->MaxRequestExpires)));
|
|
IFOK(s, nats_JSONGetLong(cjson, "max_bytes", &(cc->MaxRequestMaxBytes)));
|
|
IFOK(s, nats_JSONGetLong(cjson, "inactive_threshold", &(cc->InactiveThreshold)));
|
|
IFOK(s, nats_JSONGetArrayLong(cjson, "backoff", &(cc->BackOff), &(cc->BackOffLen)));
|
|
IFOK(s, nats_JSONGetLong(cjson, "num_replicas", &(cc->Replicas)));
|
|
IFOK(s, nats_JSONGetBool(cjson, "mem_storage", &(cc->MemoryStorage)));
|
|
}
|
|
|
|
if (s == NATS_OK)
|
|
*new_cc = cc;
|
|
else
|
|
js_destroyConsumerConfig(cc);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalSeqInfo(nats_JSON *json, const char *fieldName, jsSequenceInfo *si)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
nats_JSON *sij = NULL;
|
|
|
|
s = nats_JSONGetObject(json, fieldName, &sij);
|
|
if ((s == NATS_OK) && (sij != NULL))
|
|
{
|
|
IFOK(s, nats_JSONGetULong(sij, "consumer_seq", &(si->Consumer)));
|
|
IFOK(s, nats_JSONGetULong(sij, "stream_seq", &(si->Stream)));
|
|
IFOK(s, nats_JSONGetTime(sij, "last_active", &(si->Last)));
|
|
}
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_unmarshalConsumerInfo(nats_JSON *json, jsConsumerInfo **new_ci)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
jsConsumerInfo *ci = NULL;
|
|
|
|
ci = (jsConsumerInfo*) NATS_CALLOC(1, sizeof(jsConsumerInfo));
|
|
if (ci == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
s = nats_JSONGetStr(json, "stream_name", &(ci->Stream));
|
|
IFOK(s, nats_JSONGetStr(json, "name", &(ci->Name)));
|
|
IFOK(s, nats_JSONGetTime(json, "created", &(ci->Created)));
|
|
IFOK(s, _unmarshalConsumerConfig(json, "config", &(ci->Config)));
|
|
IFOK(s, _unmarshalSeqInfo(json, "delivered", &(ci->Delivered)));
|
|
IFOK(s, _unmarshalSeqInfo(json, "ack_floor", &(ci->AckFloor)));
|
|
IFOK(s, nats_JSONGetLong(json, "num_ack_pending", &(ci->NumAckPending)));
|
|
IFOK(s, nats_JSONGetLong(json, "num_redelivered", &(ci->NumRedelivered)));
|
|
IFOK(s, nats_JSONGetLong(json, "num_waiting", &(ci->NumWaiting)));
|
|
IFOK(s, nats_JSONGetULong(json, "num_pending", &(ci->NumPending)));
|
|
IFOK(s, _unmarshalClusterInfo(json, "cluster", &(ci->Cluster)));
|
|
IFOK(s, nats_JSONGetBool(json, "push_bound", &(ci->PushBound)));
|
|
|
|
if (s == NATS_OK)
|
|
*new_ci = ci;
|
|
else
|
|
jsConsumerInfo_Destroy(ci);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalConsumerCreateOrGetResp(jsConsumerInfo **new_ci, natsMsg *resp, jsErrCode *errCode)
|
|
{
|
|
nats_JSON *json = NULL;
|
|
jsApiResponse ar;
|
|
natsStatus s;
|
|
|
|
s = js_unmarshalResponse(&ar, &json, resp);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (js_apiResponseIsErr(&ar))
|
|
{
|
|
if (errCode != NULL)
|
|
*errCode = (int) ar.Error.ErrCode;
|
|
|
|
if (ar.Error.ErrCode == JSConsumerNotFoundErr)
|
|
s = NATS_NOT_FOUND;
|
|
else
|
|
s = nats_setError(NATS_ERR, "%s", ar.Error.Description);
|
|
}
|
|
else if (new_ci != NULL)
|
|
{
|
|
// At this point we need to unmarshal the consumer info itself.
|
|
s = js_unmarshalConsumerInfo(json, new_ci);
|
|
}
|
|
|
|
js_freeApiRespContent(&ar);
|
|
nats_JSONDestroy(json);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_AddConsumer(jsConsumerInfo **new_ci, jsCtx *js,
|
|
const char *stream, jsConsumerConfig *cfg,
|
|
jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
natsBuffer *buf = NULL;
|
|
natsConnection *nc = NULL;
|
|
char *subj = NULL;
|
|
bool freePfx = false;
|
|
natsMsg *resp = NULL;
|
|
jsOptions o;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if (js == NULL)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
if (cfg == NULL)
|
|
return nats_setError(NATS_INVALID_ARG, "%s", jsErrConsumerConfigRequired);
|
|
|
|
s = _checkStreamName(stream);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (!nats_IsStringEmpty(cfg->Name))
|
|
{
|
|
if ((s = js_checkConsName(cfg->Name, false)) != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
if (!nats_IsStringEmpty(cfg->Durable))
|
|
{
|
|
if ((s = js_checkConsName(cfg->Durable, true)) != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
int res;
|
|
|
|
// If there is a Name in the config, this takes precedence.
|
|
if (!nats_IsStringEmpty(cfg->Name))
|
|
{
|
|
// No subject filter, use <stream>.<consumer name>
|
|
// otherwise, the filter subject goes at the end.
|
|
if (nats_IsStringEmpty(cfg->FilterSubject))
|
|
res = nats_asprintf(&subj, jsApiConsumerCreateExT,
|
|
js_lenWithoutTrailingDot(o.Prefix), o.Prefix,
|
|
stream, cfg->Name);
|
|
else
|
|
res = nats_asprintf(&subj, jsApiConsumerCreateExWithFilterT,
|
|
js_lenWithoutTrailingDot(o.Prefix), o.Prefix,
|
|
stream, cfg->Name, cfg->FilterSubject);
|
|
}
|
|
else if (nats_IsStringEmpty(cfg->Durable))
|
|
res = nats_asprintf(&subj, jsApiConsumerCreateT,
|
|
js_lenWithoutTrailingDot(o.Prefix), o.Prefix,
|
|
stream);
|
|
else
|
|
res = nats_asprintf(&subj, jsApiDurableCreateT,
|
|
js_lenWithoutTrailingDot(o.Prefix), o.Prefix,
|
|
stream, cfg->Durable);
|
|
if (res < 0)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
IFOK(s, _marshalConsumerCreateReq(&buf, stream, cfg));
|
|
|
|
// Send the request
|
|
IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, natsBuf_Data(buf), natsBuf_Len(buf), o.Wait));
|
|
|
|
// If we got a response, check for error or return the consumer info result.
|
|
IFOK(s, _unmarshalConsumerCreateOrGetResp(new_ci, resp, errCode));
|
|
|
|
NATS_FREE(subj);
|
|
natsMsg_Destroy(resp);
|
|
natsBuf_Destroy(buf);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_UpdateConsumer(jsConsumerInfo **ci, jsCtx *js,
|
|
const char *stream, jsConsumerConfig *cfg,
|
|
jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s;
|
|
|
|
if ((cfg != NULL) && nats_IsStringEmpty(cfg->Durable))
|
|
return nats_setError(NATS_INVALID_ARG, "%s", jsErrDurRequired);
|
|
|
|
s = js_AddConsumer(ci, js, stream, cfg, opts, errCode);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_GetConsumerInfo(jsConsumerInfo **new_ci, jsCtx *js,
|
|
const char *stream, const char *consumer,
|
|
jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
char *subj = NULL;
|
|
bool freePfx = false;
|
|
natsConnection *nc = NULL;
|
|
natsMsg *resp = NULL;
|
|
jsOptions o;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if ((js == NULL) || (new_ci == NULL))
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
s = _checkStreamName(stream);
|
|
IFOK(s, js_checkConsName(consumer, false))
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
if (nats_asprintf(&subj, jsApiConsumerInfoT,
|
|
js_lenWithoutTrailingDot(o.Prefix), o.Prefix,
|
|
stream, consumer) < 0 )
|
|
{
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
}
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
|
|
// Send the request
|
|
IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, NULL, 0, o.Wait));
|
|
|
|
// If we got a response, check for error or return the consumer info result.
|
|
IFOK(s, _unmarshalConsumerCreateOrGetResp(new_ci, resp, errCode));
|
|
|
|
NATS_FREE(subj);
|
|
natsMsg_Destroy(resp);
|
|
|
|
if (s == NATS_NOT_FOUND)
|
|
{
|
|
nats_clearLastError();
|
|
return s;
|
|
}
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_DeleteConsumer(jsCtx *js, const char *stream, const char *consumer,
|
|
jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
char *subj = NULL;
|
|
bool freePfx = false;
|
|
natsConnection *nc = NULL;
|
|
natsMsg *resp = NULL;
|
|
bool success = false;
|
|
jsOptions o;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if (js == NULL)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
s = _checkStreamName(stream);
|
|
IFOK(s, js_checkConsName(consumer, false))
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
if (nats_asprintf(&subj, jsApiConsumerDeleteT,
|
|
js_lenWithoutTrailingDot(o.Prefix), o.Prefix,
|
|
stream, consumer) < 0 )
|
|
{
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
}
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
|
|
// Send the request
|
|
IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, NULL, 0, o.Wait));
|
|
|
|
// If we got a response, check for error and success result.
|
|
IFOK(s, _unmarshalSuccessResp(&success, resp, errCode));
|
|
if ((s == NATS_OK) && !success)
|
|
s = nats_setError(s, "failed to delete consumer '%s'", consumer);
|
|
|
|
NATS_FREE(subj);
|
|
natsMsg_Destroy(resp);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
jsConsumerConfig_Init(jsConsumerConfig *cc)
|
|
{
|
|
if (cc == NULL)
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
memset(cc, 0, sizeof(jsConsumerConfig));
|
|
cc->AckPolicy = -1;
|
|
cc->DeliverPolicy = -1;
|
|
cc->ReplayPolicy = -1;
|
|
return NATS_OK;
|
|
}
|
|
|
|
void
|
|
jsConsumerInfo_Destroy(jsConsumerInfo *ci)
|
|
{
|
|
if (ci == NULL)
|
|
return;
|
|
|
|
NATS_FREE(ci->Stream);
|
|
NATS_FREE(ci->Name);
|
|
js_destroyConsumerConfig(ci->Config);
|
|
_destroyClusterInfo(ci->Cluster);
|
|
NATS_FREE(ci);
|
|
}
|
|
|
|
static natsStatus
|
|
_unmarshalConsumerInfoListResp(natsStrHash *m, apiPaged *page, natsMsg *resp, jsErrCode *errCode)
|
|
{
|
|
nats_JSON *json = NULL;
|
|
jsApiResponse ar;
|
|
natsStatus s;
|
|
|
|
s = js_unmarshalResponse(&ar, &json, resp);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
if (js_apiResponseIsErr(&ar))
|
|
{
|
|
if (errCode != NULL)
|
|
*errCode = (int) ar.Error.ErrCode;
|
|
|
|
// If the error code is JSStreamNotFoundErr then pick NATS_NOT_FOUND.
|
|
if (ar.Error.ErrCode == JSStreamNotFoundErr)
|
|
s = NATS_NOT_FOUND;
|
|
else
|
|
s = nats_setError(NATS_ERR, "%s", ar.Error.Description);
|
|
}
|
|
else
|
|
{
|
|
nats_JSON **consumers = NULL;
|
|
int consumersLen = 0;
|
|
|
|
IFOK(s, nats_JSONGetLong(json, "total", &page->total));
|
|
IFOK(s, nats_JSONGetLong(json, "offset", &page->offset));
|
|
IFOK(s, nats_JSONGetLong(json, "limit", &page->limit));
|
|
IFOK(s, nats_JSONGetArrayObject(json, "consumers", &consumers, &consumersLen));
|
|
if ((s == NATS_OK) && (consumers != NULL))
|
|
{
|
|
int i;
|
|
|
|
for (i=0; (s == NATS_OK) && (i<consumersLen); i++)
|
|
{
|
|
jsConsumerInfo *ci = NULL;
|
|
jsConsumerInfo *oci = NULL;
|
|
|
|
s = js_unmarshalConsumerInfo(consumers[i], &ci);
|
|
if ((s == NATS_OK) && ((ci->Config == NULL) || nats_IsStringEmpty(ci->Name)))
|
|
s = nats_setError(NATS_ERR, "%s", "consumer name missing");
|
|
IFOK(s, natsStrHash_SetEx(m, (char*) ci->Name, true, true, ci, (void**) &oci));
|
|
if (oci != NULL)
|
|
jsConsumerInfo_Destroy(oci);
|
|
}
|
|
// Free the array of JSON objects that was allocated by nats_JSONGetArrayObject.
|
|
NATS_FREE(consumers);
|
|
}
|
|
}
|
|
js_freeApiRespContent(&ar);
|
|
nats_JSONDestroy(json);
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
natsStatus
|
|
js_Consumers(jsConsumerInfoList **new_list, jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
natsBuffer *buf = NULL;
|
|
char *subj = NULL;
|
|
natsMsg *resp = NULL;
|
|
natsConnection *nc = NULL;
|
|
bool freePfx = false;
|
|
bool done = false;
|
|
int64_t offset = 0;
|
|
int64_t start = 0;
|
|
int64_t timeout = 0;
|
|
natsStrHash *cons = NULL;
|
|
jsConsumerInfoList *list = NULL;
|
|
jsOptions o;
|
|
apiPaged page;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if ((new_list == NULL) || (js == NULL))
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
s = _checkStreamName(stream);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
if (nats_asprintf(&subj, jsApiConsumerListT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
IFOK(s, natsBuf_Create(&buf, 16));
|
|
IFOK(s, natsStrHash_Create(&cons, 16));
|
|
|
|
if (s == NATS_OK)
|
|
{
|
|
memset(&page, 0, sizeof(apiPaged));
|
|
start = nats_Now();
|
|
}
|
|
|
|
do
|
|
{
|
|
IFOK(s, natsBuf_AppendByte(buf, '{'));
|
|
IFOK(s, nats_marshalLong(buf, false, "offset", offset));
|
|
IFOK(s, natsBuf_AppendByte(buf, '}'));
|
|
|
|
timeout = o.Wait - (nats_Now() - start);
|
|
if (timeout <= 0)
|
|
s = NATS_TIMEOUT;
|
|
|
|
// Send the request
|
|
IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, natsBuf_Data(buf), natsBuf_Len(buf), timeout));
|
|
|
|
IFOK(s, _unmarshalConsumerInfoListResp(cons, &page, resp, errCode));
|
|
if (s == NATS_OK)
|
|
{
|
|
offset += page.limit;
|
|
done = offset >= page.total;
|
|
if (!done)
|
|
{
|
|
// Reset the request buffer, we may be able to reuse.
|
|
natsBuf_Reset(buf);
|
|
}
|
|
}
|
|
natsMsg_Destroy(resp);
|
|
resp = NULL;
|
|
}
|
|
while ((s == NATS_OK) && !done);
|
|
|
|
natsBuf_Destroy(buf);
|
|
NATS_FREE(subj);
|
|
|
|
if (s == NATS_OK)
|
|
{
|
|
if (natsStrHash_Count(cons) == 0)
|
|
{
|
|
natsStrHash_Destroy(cons);
|
|
return NATS_NOT_FOUND;
|
|
}
|
|
list = (jsConsumerInfoList*) NATS_CALLOC(1, sizeof(jsConsumerInfoList));
|
|
if (list == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
else
|
|
{
|
|
list->List = (jsConsumerInfo**) NATS_CALLOC(natsStrHash_Count(cons), sizeof(jsConsumerInfo*));
|
|
if (list->List == NULL)
|
|
{
|
|
NATS_FREE(list);
|
|
list = NULL;
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
}
|
|
else
|
|
{
|
|
natsStrHashIter iter;
|
|
void *val = NULL;
|
|
|
|
natsStrHashIter_Init(&iter, cons);
|
|
while (natsStrHashIter_Next(&iter, NULL, (void**) &val))
|
|
{
|
|
jsConsumerInfo *ci = (jsConsumerInfo*) val;
|
|
|
|
list->List[list->Count++] = ci;
|
|
natsStrHashIter_RemoveCurrent(&iter);
|
|
}
|
|
natsStrHashIter_Done(&iter);
|
|
|
|
*new_list = list;
|
|
}
|
|
}
|
|
}
|
|
if (s != NATS_OK)
|
|
{
|
|
natsStrHashIter iter;
|
|
void *val = NULL;
|
|
|
|
natsStrHashIter_Init(&iter, cons);
|
|
while (natsStrHashIter_Next(&iter, NULL, (void**) &val))
|
|
{
|
|
jsStreamInfo *si = (jsStreamInfo*) val;
|
|
|
|
natsStrHashIter_RemoveCurrent(&iter);
|
|
jsStreamInfo_Destroy(si);
|
|
}
|
|
natsStrHashIter_Done(&iter);
|
|
}
|
|
natsStrHash_Destroy(cons);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
void
|
|
jsConsumerInfoList_Destroy(jsConsumerInfoList *list)
|
|
{
|
|
int i;
|
|
|
|
if (list == NULL)
|
|
return;
|
|
|
|
for (i=0; i<list->Count; i++)
|
|
jsConsumerInfo_Destroy(list->List[i]);
|
|
|
|
NATS_FREE(list->List);
|
|
NATS_FREE(list);
|
|
}
|
|
|
|
natsStatus
|
|
js_ConsumerNames(jsConsumerNamesList **new_list, jsCtx *js, const char *stream, jsOptions *opts, jsErrCode *errCode)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
natsBuffer *buf = NULL;
|
|
char *subj = NULL;
|
|
natsMsg *resp = NULL;
|
|
natsConnection *nc = NULL;
|
|
bool freePfx = false;
|
|
bool done = false;
|
|
int64_t offset = 0;
|
|
int64_t start = 0;
|
|
int64_t timeout = 0;
|
|
natsStrHash *names = NULL;
|
|
jsConsumerNamesList *list = NULL;
|
|
jsOptions o;
|
|
apiPaged page;
|
|
|
|
if (errCode != NULL)
|
|
*errCode = 0;
|
|
|
|
if ((new_list == NULL) || (js == NULL))
|
|
return nats_setDefaultError(NATS_INVALID_ARG);
|
|
|
|
s = _checkStreamName(stream);
|
|
if (s != NATS_OK)
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
|
|
s = js_setOpts(&nc, &freePfx, js, opts, &o);
|
|
if (s == NATS_OK)
|
|
{
|
|
if (nats_asprintf(&subj, jsApiConsumerNamesT, js_lenWithoutTrailingDot(o.Prefix), o.Prefix, stream) < 0)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
if (freePfx)
|
|
NATS_FREE((char*) o.Prefix);
|
|
}
|
|
IFOK(s, natsBuf_Create(&buf, 16));
|
|
IFOK(s, natsStrHash_Create(&names, 16));
|
|
|
|
if (s == NATS_OK)
|
|
{
|
|
memset(&page, 0, sizeof(apiPaged));
|
|
start = nats_Now();
|
|
}
|
|
|
|
do
|
|
{
|
|
IFOK(s, natsBuf_AppendByte(buf, '{'));
|
|
IFOK(s, nats_marshalLong(buf, false, "offset", offset));
|
|
IFOK(s, natsBuf_AppendByte(buf, '}'));
|
|
|
|
timeout = o.Wait - (nats_Now() - start);
|
|
if (timeout <= 0)
|
|
s = NATS_TIMEOUT;
|
|
|
|
// Send the request
|
|
IFOK_JSR(s, natsConnection_Request(&resp, nc, subj, natsBuf_Data(buf), natsBuf_Len(buf), timeout));
|
|
|
|
IFOK(s, _unmarshalNamesListResp("consumers", names, &page, resp, errCode));
|
|
if (s == NATS_OK)
|
|
{
|
|
offset += page.limit;
|
|
done = offset >= page.total;
|
|
if (!done)
|
|
{
|
|
// Reset the request buffer, we may be able to reuse.
|
|
natsBuf_Reset(buf);
|
|
}
|
|
}
|
|
natsMsg_Destroy(resp);
|
|
resp = NULL;
|
|
}
|
|
while ((s == NATS_OK) && !done);
|
|
|
|
natsBuf_Destroy(buf);
|
|
NATS_FREE(subj);
|
|
|
|
if (s == NATS_OK)
|
|
{
|
|
if (natsStrHash_Count(names) == 0)
|
|
{
|
|
natsStrHash_Destroy(names);
|
|
return NATS_NOT_FOUND;
|
|
}
|
|
list = (jsConsumerNamesList*) NATS_CALLOC(1, sizeof(jsConsumerNamesList));
|
|
if (list == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
else
|
|
{
|
|
list->List = (char**) NATS_CALLOC(natsStrHash_Count(names), sizeof(char*));
|
|
if (list->List == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
else
|
|
{
|
|
natsStrHashIter iter;
|
|
char *sname = NULL;
|
|
|
|
natsStrHashIter_Init(&iter, names);
|
|
while ((s == NATS_OK) && natsStrHashIter_Next(&iter, &sname, NULL))
|
|
{
|
|
char *copyName = NULL;
|
|
|
|
DUP_STRING(s, copyName, sname);
|
|
if (s == NATS_OK)
|
|
{
|
|
list->List[list->Count++] = copyName;
|
|
natsStrHashIter_RemoveCurrent(&iter);
|
|
}
|
|
}
|
|
natsStrHashIter_Done(&iter);
|
|
}
|
|
if (s == NATS_OK)
|
|
*new_list = list;
|
|
else
|
|
jsConsumerNamesList_Destroy(list);
|
|
}
|
|
}
|
|
natsStrHash_Destroy(names);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|
|
|
|
void
|
|
jsConsumerNamesList_Destroy(jsConsumerNamesList *list)
|
|
{
|
|
int i;
|
|
|
|
if (list == NULL)
|
|
return;
|
|
|
|
for (i=0; i<list->Count; i++)
|
|
NATS_FREE(list->List[i]);
|
|
|
|
NATS_FREE(list->List);
|
|
NATS_FREE(list);
|
|
}
|
|
|
|
natsStatus
|
|
js_cloneConsumerConfig(jsConsumerConfig *org, jsConsumerConfig **clone)
|
|
{
|
|
natsStatus s = NATS_OK;
|
|
jsConsumerConfig *c = NULL;
|
|
|
|
*clone = NULL;
|
|
if (org == NULL)
|
|
return NATS_OK;
|
|
|
|
c = (jsConsumerConfig*) NATS_CALLOC(1, sizeof(jsConsumerConfig));
|
|
if (c == NULL)
|
|
return nats_setDefaultError(NATS_NO_MEMORY);
|
|
|
|
memcpy(c, org, sizeof(jsConsumerConfig));
|
|
|
|
// Need to first set all pointers to NULL in case we fail to dup and then
|
|
// do the cleanup.
|
|
c->Name = NULL;
|
|
c->Durable = NULL;
|
|
c->Description = NULL;
|
|
c->BackOff = NULL;
|
|
c->FilterSubject = NULL;
|
|
c->SampleFrequency = NULL;
|
|
c->DeliverSubject = NULL;
|
|
c->DeliverGroup = NULL;
|
|
// Now dup all strings, etc...
|
|
IF_OK_DUP_STRING(s, c->Name, org->Name);
|
|
IF_OK_DUP_STRING(s, c->Durable, org->Durable);
|
|
IF_OK_DUP_STRING(s, c->Description, org->Description);
|
|
IF_OK_DUP_STRING(s, c->FilterSubject, org->FilterSubject);
|
|
IF_OK_DUP_STRING(s, c->SampleFrequency, org->SampleFrequency);
|
|
IF_OK_DUP_STRING(s, c->DeliverSubject, org->DeliverSubject);
|
|
IF_OK_DUP_STRING(s, c->DeliverGroup, org->DeliverGroup);
|
|
if ((s == NATS_OK) && (org->BackOff != NULL) && (org->BackOffLen > 0))
|
|
{
|
|
c->BackOff = (int64_t*) NATS_CALLOC(org->BackOffLen, sizeof(int64_t));
|
|
if (c->BackOff == NULL)
|
|
s = nats_setDefaultError(NATS_NO_MEMORY);
|
|
else
|
|
memcpy(c->BackOff, org->BackOff, org->BackOffLen*sizeof(int64_t));
|
|
}
|
|
if (s == NATS_OK)
|
|
*clone = c;
|
|
else
|
|
js_destroyConsumerConfig(c);
|
|
|
|
return NATS_UPDATE_ERR_STACK(s);
|
|
}
|