Skip to content

Add NoExcessive type and confidential http helper#251

Open
ernest-nowacki wants to merge 3 commits intomainfrom
feat/no-excess
Open

Add NoExcessive type and confidential http helper#251
ernest-nowacki wants to merge 3 commits intomainfrom
feat/no-excess

Conversation

@ernest-nowacki
Copy link
Copy Markdown
Collaborator

@ernest-nowacki ernest-nowacki commented May 4, 2026

Two related improvements to how callers pass requests into capability methods:

  1. httpRequest helper for ConfidentialHTTPClient — supports ability to just use body field with json and make allow helper to put into the right proto oneof (bodyString / bodyBytes). Having body in http request is super popular and well expected within TypeScript community so I consider it worthy addon. The drawback is that we would need to manually maintain this helper if there are proto changes to the capability itself.

  2. NoExcess / CapabilityInput input gating — every generated capability method now rejects unknown keys on the JSON input shape, even when the request is lifted into a variable. Previously TypeScript's structural typing only triggered excess-property checks on inline object literals; once a request was bound to a const, using fields like body (instead of bodyString) silently compiled.

  3. Added a new workflow example demonstrating usage of the helper.

Native protobuf messages are still accepted unchanged — CapabilityInput branches on the $typeName brand and only applies NoExcess to JSON shapes.

Before / After

Before — manual proto-aware payload, plus stale keys silently allowed once bound to a variable:

const client = new ConfidentialHTTPClient()

// Has to know the proto oneof keys (bodyString/bodyBytes), and base64 the bytes path manually.
const req = {
  request: {
    url: runtime.config.url,
    method: 'POST',
    bodyString: JSON.stringify({ hello: 'world' }),
    multiHeaders: {
      'content-type': { values: ['application/json'] },
    },
    body: 'hello world', // silently compiles — excess-property check skipped on bound objects
  },
}

client.sendRequest(runtime, req).result()

After — single ergonomic body, and unknown keys fail at the call boundary:

import { ConfidentialHTTPClient, httpRequest } from '@chainlink/cre-sdk'

const client = new ConfidentialHTTPClient()

const req = {
  request: httpRequest({
    url: runtime.config.url,
    method: 'POST',
    body: { hello: 'world' },             // object  -> JSON.stringify -> bodyString
    // body: 'raw',                       // string  -> bodyString , value passed as is
    // body: new Uint8Array([0xde, 0xad]),// bytes   -> base64 -> bodyBytes
  }),
}

client.sendRequest(runtime, req).result() // typo'd `body` field on the outer shape now fails to typecheck

The raw form still works for callers who want it — httpRequest is opt-in:

client.sendRequest(runtime, {
  request: {
    url: runtime.config.url,
    method: 'POST',
    bodyString: JSON.stringify({ hello: 'world' }),
    multiHeaders: { 'content-type': { values: ['application/json'] } },
  },
}).result()

Notes

  • body, bodyString, and bodyBytes are mutually exclusive (proto oneof); supplying more than one throws.
  • headers and multiHeaders may both be supplied — multiHeaders is applied first, headers entries replace per-name.
  • NoExcess recursion stops at primitives, Uint8Array, and index-signature maps (e.g. multiHeaders[string]), so dynamic header names remain unrestricted. Depth bound is 6 to keep tsc work bounded.

(opts.bodyBytes !== undefined ? 1 : 0)
if (bodyFieldsSet > 1) {
throw new Error(
'httpRequest: body, bodyString and bodyBytes are mutually exclusive (proto oneof)',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is what the developer sees we should give them a bit more clarity. They probably don't need to know we use proto under the hood so something like `'httpRequest: specify the request body using only one of: body, bodyString, or bodyBytes'

out.bodyString = JSON.stringify(opts.body, (_k, v) =>
typeof v === 'bigint' ? v.toString() : v,
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should wrap this in a try/catch in case JSON.stringify errors from bad input we can give a better error message than the generic error it will return

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants