Skip to content

feat(casing): snake/wire surface parity across params, bodies, presentation and columns (#131); hyphenated wire names alias cleanly (#119) - #140

Merged
jeffreyaven merged 2 commits into
mainfrom
feat/casing-surface-131-119
Sep 5, 2026
Merged

feat(casing): snake/wire surface parity across params, bodies, presentation and columns (#131); hyphenated wire names alias cleanly (#119)#140
jeffreyaven merged 2 commits into
mainfrom
feat/casing-surface-131-119

Conversation

@jeffreyaven

Copy link
Copy Markdown
Member

Closes #131. Closes #119.

Every change is gated on provider doc config (request.nativeCasing on the
method, config.snake_case_aliases on the provider). Resolution is wire-first
on every surface: the key as written matches first, the snake alias second, and
no wire spelling is removed from any resolvable set. Absent both flags every
path is byte-identical.

#119 - hyphenated wire names

casing.ToSnake now treats - as a word boundary, transforming each segment
with the existing botocore rules: openai-organization -> openai_organization,
OpenAI-Organization -> open_ai_organization, X-Amz-Date -> x_amz_date.
Non-hyphenated names are unchanged (VPCId -> vpc_id, training_file ->
identity). FromSnake(_, kebab) was already the correct inverse.

#131

item change
1. column surface Schema.GetProperty and FindByPath accept the snake alias of a wire property when snake_case_aliases is on (wire-first), so snake projections resolve their property schema.
2. acronym round-trip GetParameter, parameterMatch, namespaceParameterMatch and GetOperationParameter resolve a snake key through the declared wire names (ToSnake-derived alias map) before the mechanical FromSnake fallback, so ip_protocol -> IPProtocol, binary_id -> BinaryId. GetParametersIncludingNativeCasing aliases data__ body keys with the prefix kept.
3. default data__ regime revertRequestBodyAttributeRename maps a snake body key to its wire property when the method declares nativeCasing, so data__storage_class and data__storageClass both send {"storageClass": ...}. Unknown keys pass through as today (an error here would break free-form bodies).
4. presentation RenameRequestBodyAttribute (SHOW INSERT) and ToPresentationMap (SHOW METHODS) present snake names only when the method declares nativeCasing and the provider enables snake_case_aliases; nested body contents and server variables keep wire casing. The unexported rename used for resolution is unchanged, so GetParameters() keeps the wire keys.
5. body-less EXEC GetRequestBodySchema returns nil, nil when no body schema is declared, so a metadata-only request block on a body-less method no longer fails before dispatch. The internal body-attribute accessors keep their error contract, which preserves routing exactly (they previously short-circuited before required server variables were added).

Verification

  • go build ./..., go test ./... (21 packages), gofmt on changed hunks, lint
    clean on changed lines. New tests: pkg/casing hyphen cases and
    internal/anysdk/casing_alias_resolution_test.go (one test per item, each
    asserting the wire spelling still resolves and the flag-off path is unchanged).
  • End-to-end: stackql built against this branch vs v0.5.4-alpha01, local file
    registry provider with snake_case_aliases: true and per-method
    nativeCasing, mock logging method/path/query/header/body:
statement v0.5.4-alpha01 this branch
WHERE openai_organization = 'org-x' (kebab header) could not locate symbol header openai-organization: org-x on the wire
INSERT ... (data__name, data__storage_class) body {"storage_class": ...} (silently ignored by an API) body {"storageClass": ...}
INSERT ... (data__name, data__storageClass) {"storageClass": ...} unchanged
EXEC ...stop @machineId (body-less, request: {nativeCasing}) no request body for operation dispatched POST /machines/m1/stop
SHOW METHODS (stop) machineId machine_id
SHOW INSERT data__storageClass data__storage_class
WHERE max_results = 2 / WHERE maxResults = 2 ?maxResults=2 unchanged
SELECT machine_type, creation_timestamp, ip_protocol rows unchanged

Remaining in stackql (wire-through)

Two acceptance points in #131 sit in stackql's own layers and are unchanged by
this PR; both now have the any-sdk primitives they need:

  • Wire-spelled column under the flag (SELECT machineType with
    snake_case_aliases): still no such column: machineType from the SQL
    backend. unary_selection.go builds the column descriptor from the SQL
    identifier; with FindByPath now alias-aware it can rename the descriptor to
    the resolved property's snake display name.
  • Required EXEC argument as snake (EXEC ... @machine_id): the analyzer
    matches required exec args by wire name; resolving them through
    GetParameter / GetParametersIncludingNativeCasing makes required and
    optional exec args uniform.
  • analyzeSchemaVsMap should nil-guard the schema now that a body-less method
    reports nil, nil: an EXEC that supplies a payload to such a method must
    error rather than dereference nil.

🤖 Generated with Claude Code

…tation and columns (#131); hyphenated wire names alias cleanly (#119)

- casing.ToSnake treats '-' as a word boundary, so header names such as
  openai-organization gain a usable snake alias.
- Parameter retry paths (GetParameter, parameterMatch, namespaceParameterMatch,
  GetOperationParameter) resolve a snake key through the declared wire names
  (ToSnake-derived), so acronym-headed names (IPProtocol, BinaryId) round-trip;
  FromSnake stays as a fallback.
- data__ body keys: the revert path maps a snake key to its wire property when
  the method declares nativeCasing, so the default body regime sends wire keys.
- Presentation (SHOW METHODS / SHOW INSERT via RenameRequestBodyAttribute and
  ToPresentationMap) is snake only when the method declares nativeCasing and
  the provider enables snake_case_aliases; resolution keeps every wire spelling.
- A request block without a body schema is no longer an error from
  GetRequestBodySchema, so metadata-only blocks on body-less EXEC methods
  dispatch; the body-attribute accessors keep their error contract.
- Schema.GetProperty / FindByPath accept the snake alias of a wire property
  under snake_case_aliases, wire-first.

All gated on provider config; absent both flags every path is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread internal/anysdk/operation_store.go Outdated
rv := make(map[string]struct{})
requestBodySchema, schemaErr := op.getRequestBodySchema()
if schemaErr != nil {
if schemaErr != nil || requestBodySchema == nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

if a nil requestBodySchema is an error state and echemaErr is prior nil, then should update this to some hardcoded error here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed. It now returns noRequestBodyError() when the schema is nil with no prior error, matching the other body-attribute accessors, and the test asserts the error. Fixed in 07de24e.

…BodyStringifiedPaths

Aligns with the other body-attribute accessors (noRequestBodyError); the test now expects the error.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@general-kroll-4-life general-kroll-4-life left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

just the one thing. The early return in getRequestBodyStringifiedPaths looks to be an error state. If so, then the error should be populated if empty. If not, we can go ahead as is. I prefer minimalism so dont want splitting or commments

@jeffreyaven
jeffreyaven merged commit dc7bad2 into main Sep 5, 2026
13 checks passed
@jeffreyaven
jeffreyaven deleted the feat/casing-surface-131-119 branch September 5, 2026 02:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants