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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/config/tenant-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,19 @@ export function extractTenantFromAttachment(
return null;
}

// Try to parse body as JSON
// Use encoding to determine how to interpret body (same logic as deserializeBody in queries.ts)
// encoding='none' (or unset): body is a native object (already deserialized on read)
// encoding='json': body is intentionally a JSON string — parse it
// encoding='base64url': not a JSON payload, cannot extract tenant
let bodyData: any;
try {
// Handle different encodings
if (attachment.encoding === 'json' || !attachment.encoding) {
if (attachment.encoding === 'base64url') {
return null;
} else if (attachment.encoding === 'json' && typeof attachment.body === 'string') {
bodyData = JSON.parse(attachment.body);
} else {
// For other encodings, try parsing anyway (might be plain JSON)
bodyData = JSON.parse(attachment.body);
// encoding='none' or unset: body was deserialized to an object on read; use directly
bodyData = attachment.body;
}
} catch (e) {
// Not valid JSON, cannot extract tenant
Expand Down
10 changes: 6 additions & 4 deletions src/db/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import { ATTR_CACHE_HIT, ATTR_DB_OPERATION, ATTR_SEARCH_RESULTS_COUNT, ATTR_SEAR
import { logWithContext, recordCounter, withSpan } from '../observability/instrumentation.js';
import { createLogger } from '../observability/logger.js';
import { Analysis, Attachment, Dialog, VCon } from '../types/vcon.js';
import { deserializeBody, serializeBody } from '../utils/body-serialization.js';

const logger = createLogger('queries');


export class VConQueries {
private redis: Redis | null = null;
private cacheEnabled: boolean = false;
Expand Down Expand Up @@ -376,7 +378,7 @@ export class VConQueries {
vendor: analysis.vendor, // ✅ REQUIRED field
product: analysis.product,
schema: analysis.schema, // ✅ CORRECT: 'schema' NOT 'schema_version'
body: analysis.body, // ✅ CORRECT: TEXT type, supports all formats
body: serializeBody(analysis.body, analysis.encoding), // Serialize only for encoding='none'
encoding: analysis.encoding,
url: analysis.url,
content_hash: analysis.content_hash,
Expand Down Expand Up @@ -512,7 +514,7 @@ export class VConQueries {
dialog: attachment.dialog, // ✅ Added per spec Section 4.4.4
mimetype: attachment.mediatype,
filename: attachment.filename,
body: attachment.body,
body: serializeBody(attachment.body, attachment.encoding), // Serialize only for encoding='none'
encoding: attachment.encoding,
url: attachment.url,
content_hash: attachment.content_hash,
Expand Down Expand Up @@ -697,7 +699,7 @@ export class VConQueries {
vendor: a.vendor, // ✅ Required field
product: a.product,
schema: a.schema, // ✅ CORRECT: 'schema' NOT 'schema_version'
body: a.body, // ✅ TEXT type
body: deserializeBody(a.body, a.encoding), // Parse back to object only for encoding='none'
encoding: a.encoding,
url: a.url,
content_hash: a.content_hash,
Expand All @@ -709,7 +711,7 @@ export class VConQueries {
dialog: att.dialog, // ✅ Correct field
mediatype: att.mimetype,
filename: att.filename,
body: att.body,
body: deserializeBody(att.body, att.encoding), // Parse back to object only for encoding='none'
encoding: att.encoding,
url: att.url,
content_hash: att.content_hash,
Expand Down
4 changes: 2 additions & 2 deletions src/resources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

import { VConQueries } from '../db/queries.js';
import { deserializeBody } from '../utils/body-serialization.js';

export interface ResourceDescriptor {
uri: string;
Expand Down Expand Up @@ -238,8 +239,7 @@ export async function resolveCoreResource(queries: VConQueries, uri: string): Pr
}

try {
// Parse the tags body - it should be a JSON array of "key:value" strings
const tagsArray = JSON.parse(tagsAttachment.body);
const tagsArray = deserializeBody(tagsAttachment.body as string, tagsAttachment.encoding) as string[];
const tagsObject: Record<string, string> = {};

for (const tagString of tagsArray) {
Expand Down
4 changes: 2 additions & 2 deletions src/types/vcon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export interface Attachment {
dialog?: number; // ✅ Section 4.4.4 - Dialog reference
mediatype?: string;
filename?: string;
body?: string;
body?: unknown; // Can be string, object, or array depending on encoding
encoding?: Encoding;
url?: string;
content_hash?: string | string[];
Expand All @@ -163,7 +163,7 @@ export interface Analysis {
vendor: string; // ✅ REQUIRED per spec Section 4.5.5 (no ?)
product?: string;
schema?: string; // ✅ CORRECT: 'schema' NOT 'schema_version' (Section 4.5.7)
body?: string; // ✅ CORRECT: string type, supports all formats (Section 4.5.8)
body?: unknown; // Can be string, object, or array depending on content/encoding
encoding?: Encoding;
url?: string;
content_hash?: string | string[];
Expand Down
26 changes: 26 additions & 0 deletions src/utils/body-serialization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Body serialization utilities for vCon TEXT columns.
*
* Encoding semantics:
* 'none' or unset – body is a native JS object/array; stringify on write, parse on read.
* 'json' – body is intentionally a JSON string; leave as-is.
* 'base64url' – body is a base64url string; leave as-is.
*/

export function serializeBody(body: unknown, encoding?: string): unknown {
if ((!encoding || encoding === 'none') && typeof body !== 'string') {
return JSON.stringify(body);
}
return body;
}

export function deserializeBody(body: string, encoding?: string): unknown {
if (!encoding || encoding === 'none') {
try {
return JSON.parse(body);
} catch {
// Not valid JSON — return as-is
}
}
return body;
}
6 changes: 5 additions & 1 deletion src/utils/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,11 @@ export class VConValidator {
// If body and encoding are present, validate they match
if (analysis.body && analysis.encoding === 'json') {
try {
JSON.parse(analysis.body);
// body may already be a parsed object or a JSON string
if (typeof analysis.body === 'string') {
JSON.parse(analysis.body);
}
// If it's already an object, it's valid JSON
} catch (e) {
this.errors.push(
`Analysis ${index} has encoding='json' but body is not valid JSON`
Expand Down
Loading