@@ -248,6 +248,67 @@ export function createProfileChunkPayload(
248248 } ;
249249}
250250
251+ /**
252+ * Validate a profile chunk against the Sample Format V2 requirements.
253+ * https://develop.sentry.dev/sdk/telemetry/profiles/sample-format-v2/
254+ * - Presence of samples, stacks, frames
255+ * - Required metadata fields
256+ */
257+ export function validateProfileChunk ( chunk : ProfileChunk ) : { valid : boolean ; reason ?: string } {
258+ try {
259+ // Required metadata
260+ if ( ! chunk || typeof chunk !== 'object' ) {
261+ return { valid : false , reason : 'chunk is not an object' } ;
262+ }
263+
264+ // profiler_id and chunk_id must be 32 lowercase hex chars
265+ const isHex32 = ( val : unknown ) : boolean => typeof val === 'string' && / ^ [ a - f 0 - 9 ] { 32 } $ / . test ( val ) ;
266+ if ( ! isHex32 ( chunk . profiler_id ) ) {
267+ return { valid : false , reason : 'missing or invalid profiler_id' } ;
268+ }
269+ if ( ! isHex32 ( chunk . chunk_id ) ) {
270+ return { valid : false , reason : 'missing or invalid chunk_id' } ;
271+ }
272+
273+ // client_sdk name/version are required
274+ if (
275+ ! chunk . client_sdk ||
276+ typeof chunk . client_sdk . name !== 'string' ||
277+ typeof chunk . client_sdk . version !== 'string'
278+ ) {
279+ return { valid : false , reason : 'missing client_sdk metadata' } ;
280+ }
281+
282+ if ( typeof chunk . platform !== 'string' ) {
283+ return { valid : false , reason : 'missing platform' } ;
284+ }
285+
286+ if ( typeof chunk . release !== 'string' ) {
287+ return { valid : false , reason : 'missing release' } ;
288+ }
289+
290+ // Profile data must have frames, stacks, samples
291+ const profile = chunk . profile as { frames ?: unknown [ ] ; stacks ?: unknown [ ] ; samples ?: unknown [ ] } | undefined ;
292+ if ( ! profile ) {
293+ return { valid : false , reason : 'missing profile data' } ;
294+ }
295+
296+ if ( ! Array . isArray ( profile . frames ) || profile . frames . length === 0 ) {
297+ return { valid : false , reason : 'profile has no frames' } ;
298+ }
299+ if ( ! Array . isArray ( profile . stacks ) || profile . stacks . length === 0 ) {
300+ return { valid : false , reason : 'profile has no stacks' } ;
301+ }
302+ if ( ! Array . isArray ( profile . samples ) || profile . samples . length === 0 ) {
303+ return { valid : false , reason : 'profile has no samples' } ;
304+ }
305+
306+ return { valid : true } ;
307+ } catch ( e ) {
308+ return { valid : false , reason : `unknown validation error: ${ e } ` } ;
309+ }
310+ }
311+
251312/**
252313 * Convert from JSSelfProfile format to ContinuousThreadCpuProfile format.
253314 */
0 commit comments