Skip to content

Commit bcf2e39

Browse files
authored
native aws updates (#107)
1 parent 2ae35f1 commit bcf2e39

14 files changed

Lines changed: 994 additions & 12 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package anysdk_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/stackql/any-sdk/internal/anysdk"
7+
)
8+
9+
func TestParameterNotFoundErrorMessage(t *testing.T) {
10+
err := &ParameterNotFoundError{
11+
Key: "foo_bar",
12+
AvailableWireNames: []string{"VpcId", "EnableDnsHostnames", "DryRun"},
13+
}
14+
want := "field 'foo_bar' not found; available: [VpcId, EnableDnsHostnames, DryRun]"
15+
if got := err.Error(); got != want {
16+
t.Fatalf("Error() = %q, want %q", got, want)
17+
}
18+
}

internal/anysdk/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type StackQLConfig interface {
2525
GetQueryParamPushdown() (QueryParamPushdown, bool)
2626
GetRetryPolicy() (RetryPolicy, bool)
2727
GetMinStackQLVersion() string
28+
IsSnakeCaseAliasesEnabled() bool
2829
//
2930
isObjectSchemaImplicitlyUnioned() bool
3031
setResource(rsc Resource)
@@ -43,6 +44,7 @@ type standardStackQLConfig struct {
4344
QueryParamPushdown *standardQueryParamPushdown `json:"queryParamPushdown,omitempty" yaml:"queryParamPushdown,omitempty"`
4445
Retry *standardRetryPolicy `json:"retry,omitempty" yaml:"retry,omitempty"`
4546
MinStackQLVersion string `json:"minStackQLVersion,omitempty" yaml:"minStackQLVersion,omitempty"`
47+
SnakeCaseAliases bool `json:"snake_case_aliases,omitempty" yaml:"snake_case_aliases,omitempty"`
4648
}
4749

4850
func (qt standardStackQLConfig) JSONLookup(token string) (interface{}, error) {
@@ -70,6 +72,10 @@ func (cfg *standardStackQLConfig) GetMinStackQLVersion() string {
7072
return cfg.MinStackQLVersion
7173
}
7274

75+
func (cfg *standardStackQLConfig) IsSnakeCaseAliasesEnabled() bool {
76+
return cfg.SnakeCaseAliases
77+
}
78+
7379
func (cfg *standardStackQLConfig) GetQueryTranspose() (Transform, bool) {
7480
if cfg.QueryTranspose == nil {
7581
return nil, false

internal/anysdk/const.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ const (
3333
ExtensionKeyResources string = "x-stackQL-resources"
3434
ExtensionKeyStringOnly string = "x-stackQL-stringOnly"
3535
ExtensionKeyAlias string = "x-stackQL-alias"
36+
// ExtensionKeyProtocol is the info-level wire protocol hint (query|ec2|rest-xml)
37+
// consumed by the schema_driven_xml response transform.
38+
ExtensionKeyProtocol string = "x-protocol"
3639
)
3740

3841
const (

internal/anysdk/expectedRequest.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type ExpectedRequest interface {
1313
GetBase() string
1414
GetXMLDeclaration() string
1515
GetXMLTransform() string
16+
GetNativeCasing() string
1617
//
1718
setSchema(Schema)
1819
setBodyMediaType(string)
@@ -33,6 +34,7 @@ type standardExpectedRequest struct {
3334
XMLRootAnnotation string `json:"xmlRootAnnotation,omitempty" yaml:"xmlRootAnnotation,omitempty"`
3435
OverrideSchema *LocalSchemaRef `json:"schema_override,omitempty" yaml:"schema_override,omitempty"`
3536
Transform *standardTransform `json:"transform,omitempty" yaml:"transform,omitempty"`
37+
NativeCasing string `json:"nativeCasing,omitempty" yaml:"nativeCasing,omitempty"`
3638
}
3739

3840
func (er *standardExpectedRequest) setBodyMediaType(s string) {
@@ -73,6 +75,10 @@ func (er *standardExpectedRequest) GetXMLTransform() string {
7375
return er.XMLTransform
7476
}
7577

78+
func (er *standardExpectedRequest) GetNativeCasing() string {
79+
return er.NativeCasing
80+
}
81+
7682
func (er *standardExpectedRequest) GetSchema() Schema {
7783
if er.OverrideSchema != nil && er.OverrideSchema.Value != nil {
7884
return er.OverrideSchema.Value

internal/anysdk/operation_store.go

Lines changed: 129 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/getkin/kin-openapi/openapi3"
1717
"github.com/getkin/kin-openapi/openapi3filter"
18+
"github.com/stackql/any-sdk/pkg/casing"
1819
"github.com/stackql/any-sdk/pkg/dto"
1920
"github.com/stackql/any-sdk/pkg/fuzzymatch"
2021
"github.com/stackql/any-sdk/pkg/media"
@@ -64,6 +65,8 @@ type OperationStore interface {
6465
GetInverse() (OperationInverse, bool)
6566
GetStackQLConfig() StackQLConfig
6667
GetQueryParamPushdown() (QueryParamPushdown, bool)
68+
GetRequestNativeCasing() string
69+
GetParameterOrError(paramKey string) (Addressable, error)
6770
GetRetryPolicy() RetryPolicy
6871
GetParameters() map[string]Addressable
6972
GetPathItem() *openapi3.PathItem
@@ -1050,10 +1053,10 @@ func (m *standardOpenAPIOperationStore) getRequestBodySchemaAttributeMatcher(pat
10501053
return nil, fmt.Errorf("could not find schema at path '%s'", path)
10511054
}
10521055
}
1053-
return getschemaAttributeMatcher(schemaOfInterest)
1056+
return getschemaAttributeMatcher(schemaOfInterest, m.GetRequestNativeCasing())
10541057
}
10551058

1056-
func getschemaAttributeMatcher(schemaOfInterest Schema) (fuzzymatch.FuzzyMatcher[string], error) {
1059+
func getschemaAttributeMatcher(schemaOfInterest Schema, nativeCasing string) (fuzzymatch.FuzzyMatcher[string], error) {
10571060
var matchers []fuzzymatch.StringFuzzyPair
10581061
for k := range schemaOfInterest.getProperties() {
10591062
if k == "" {
@@ -1065,6 +1068,17 @@ func getschemaAttributeMatcher(schemaOfInterest Schema) (fuzzymatch.FuzzyMatcher
10651068
return nil, regexpErr
10661069
}
10671070
matchers = append(matchers, fuzzymatch.NewFuzzyPair(keyRegexp, k))
1071+
// When a native wire casing is declared, also accept the snake_case form of
1072+
// the wire property name and map it back to the wire key.
1073+
if nativeCasing != "" {
1074+
if snakeKey := casing.ToSnake(k); snakeKey != k {
1075+
snakeRegexp, snakeErr := regexp.Compile(fmt.Sprintf("^%s$", regexp.QuoteMeta(snakeKey)))
1076+
if snakeErr != nil {
1077+
return nil, snakeErr
1078+
}
1079+
matchers = append(matchers, fuzzymatch.NewFuzzyPair(snakeRegexp, k))
1080+
}
1081+
}
10681082
}
10691083
return fuzzymatch.NewRegexpStringMetcher(matchers), nil
10701084
}
@@ -1284,10 +1298,44 @@ func (m *standardOpenAPIOperationStore) GetNonBodyParameters() map[string]Addres
12841298
return m.getNonBodyParameters()
12851299
}
12861300

1301+
func (m *standardOpenAPIOperationStore) GetRequestNativeCasing() string {
1302+
if m.Request != nil {
1303+
return m.Request.GetNativeCasing()
1304+
}
1305+
return ""
1306+
}
1307+
12871308
func (m *standardOpenAPIOperationStore) GetParameter(paramKey string) (Addressable, bool) {
12881309
params := m.GetParameters()
1289-
rv, ok := params[paramKey]
1290-
return rv, ok
1310+
if rv, ok := params[paramKey]; ok {
1311+
return rv, true
1312+
}
1313+
// Reverse-casing retry: when the method declares a native wire casing, convert
1314+
// the (snake_case) SQL key to that casing and retry. Absent native casing this
1315+
// is a no-op, preserving existing behaviour.
1316+
if nc := m.GetRequestNativeCasing(); nc != "" {
1317+
if wireKey := casing.FromSnake(paramKey, nc); wireKey != paramKey {
1318+
if rv, ok := params[wireKey]; ok {
1319+
return rv, true
1320+
}
1321+
}
1322+
}
1323+
return nil, false
1324+
}
1325+
1326+
// GetParameterOrError resolves a parameter (including reverse-casing) and, on a
1327+
// final miss, returns a *ParameterNotFoundError listing the wire-format names.
1328+
func (m *standardOpenAPIOperationStore) GetParameterOrError(paramKey string) (Addressable, error) {
1329+
if rv, ok := m.GetParameter(paramKey); ok {
1330+
return rv, nil
1331+
}
1332+
params := m.GetParameters()
1333+
wireNames := make([]string, 0, len(params))
1334+
for k := range params {
1335+
wireNames = append(wireNames, k)
1336+
}
1337+
sort.Strings(wireNames)
1338+
return nil, &ParameterNotFoundError{Key: paramKey, AvailableWireNames: wireNames}
12911339
}
12921340

12931341
func (m *standardOpenAPIOperationStore) GetName() string {
@@ -1819,10 +1867,21 @@ func (op *standardOpenAPIOperationStore) getOverridenResponse(httpResponse *http
18191867
overrideMediaType := expectedResponse.GetOverrrideBodyMediaType()
18201868
if responseTransformExists {
18211869
input := string(bodyBytes)
1822-
streamTransformerFactory := stream_transform.NewStreamTransformerFactory(
1823-
responseTransform.GetType(),
1824-
responseTransform.GetBody(),
1825-
)
1870+
var streamTransformerFactory stream_transform.StreamTransformerFactory
1871+
if responseTransform.GetType() == stream_transform.SchemaDrivenXMLV1 {
1872+
listProperty := strings.TrimPrefix(expectedResponse.GetObjectKey(), "$.")
1873+
streamTransformerFactory = stream_transform.NewSchemaDrivenXMLStreamTransformerFactory(
1874+
responseTransform.GetType(),
1875+
newXMLSchemaAdapter(expectedResponse.GetSchema()),
1876+
op.getXProtocol(),
1877+
listProperty,
1878+
)
1879+
} else {
1880+
streamTransformerFactory = stream_transform.NewStreamTransformerFactory(
1881+
responseTransform.GetType(),
1882+
responseTransform.GetBody(),
1883+
)
1884+
}
18261885
if !streamTransformerFactory.IsTransformable() {
18271886
return nil, fmt.Errorf("unsupported template type: %s", responseTransform.GetType())
18281887
}
@@ -1848,6 +1907,68 @@ func (op *standardOpenAPIOperationStore) getOverridenResponse(httpResponse *http
18481907
return nil, fmt.Errorf("unprocessable response body for operation = %s", op.GetName())
18491908
}
18501909

1910+
// getXProtocol reads the info-level x-protocol hint (query|ec2|rest-xml) used by
1911+
// the schema_driven_xml transform to skip the right response envelope.
1912+
func (op *standardOpenAPIOperationStore) getXProtocol() string {
1913+
if op.OpenAPIService == nil {
1914+
return ""
1915+
}
1916+
t := op.OpenAPIService.getT()
1917+
if t == nil || t.Info == nil {
1918+
return ""
1919+
}
1920+
v, err := extractExtensionValBytes(t.Info.Extensions, ExtensionKeyProtocol)
1921+
if err != nil {
1922+
return ""
1923+
}
1924+
return strings.Trim(strings.TrimSpace(string(v)), `"`)
1925+
}
1926+
1927+
// xmlSchemaAdapter adapts an internal Schema to stream_transform.SchemaTree so the
1928+
// schema_driven_xml walker can navigate it without importing internal/anysdk.
1929+
type xmlSchemaAdapter struct {
1930+
s Schema
1931+
}
1932+
1933+
func newXMLSchemaAdapter(s Schema) stream_transform.SchemaTree {
1934+
if s == nil {
1935+
return nil
1936+
}
1937+
return xmlSchemaAdapter{s: s}
1938+
}
1939+
1940+
func (a xmlSchemaAdapter) Type() string {
1941+
return a.s.GetType()
1942+
}
1943+
1944+
func (a xmlSchemaAdapter) Items() (stream_transform.SchemaTree, bool) {
1945+
items, err := a.s.GetItemsSchema()
1946+
if err != nil || items == nil {
1947+
return nil, false
1948+
}
1949+
return xmlSchemaAdapter{s: items}, true
1950+
}
1951+
1952+
func (a xmlSchemaAdapter) Property(name string) (stream_transform.SchemaTree, bool) {
1953+
p, ok := a.s.GetProperty(name)
1954+
if !ok || p == nil {
1955+
return nil, false
1956+
}
1957+
return xmlSchemaAdapter{s: p}, true
1958+
}
1959+
1960+
func (a xmlSchemaAdapter) Properties() map[string]stream_transform.SchemaTree {
1961+
props, err := a.s.GetProperties()
1962+
if err != nil {
1963+
return nil
1964+
}
1965+
out := make(map[string]stream_transform.SchemaTree, len(props))
1966+
for k, v := range props {
1967+
out[k] = xmlSchemaAdapter{s: v}
1968+
}
1969+
return out
1970+
}
1971+
18511972
func (op *standardOpenAPIOperationStore) ProcessResponse(httpResponse *http.Response) (ProcessedOperationResponse, error) {
18521973
responseSchema, mediaType, err := op.GetResponseBodySchemaAndMediaType()
18531974
if err != nil {

internal/anysdk/params.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package anysdk
22

33
import (
4+
"fmt"
5+
"strings"
6+
47
"github.com/getkin/kin-openapi/openapi3"
58
)
69

@@ -10,6 +13,18 @@ var (
1013
_ Params = &parameters{}
1114
)
1215

16+
// ParameterNotFoundError reports an unresolved clause key together with the
17+
// wire-format names that were available, e.g.
18+
// "field 'foo_bar' not found; available: [VpcId, EnableDnsHostnames, DryRun]".
19+
type ParameterNotFoundError struct {
20+
Key string
21+
AvailableWireNames []string
22+
}
23+
24+
func (e *ParameterNotFoundError) Error() string {
25+
return fmt.Sprintf("field '%s' not found; available: [%s]", e.Key, strings.Join(e.AvailableWireNames, ", "))
26+
}
27+
1328
type standardParameter struct {
1429
openapi3.Parameter
1530
svc OpenAPIService

internal/anysdk/provider.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type Provider interface {
3939
GetRequestTranslateAlgorithm() string
4040
GetResourcesShallow(serviceKey string) (ResourceRegister, error)
4141
GetStackQLConfig() (StackQLConfig, bool)
42+
IsSnakeCaseAliasesEnabled() bool
4243
JSONLookup(token string) (interface{}, error)
4344
MarshalJSON() ([]byte, error)
4445
UnmarshalJSON(data []byte) error
@@ -75,6 +76,13 @@ func (pr *standardProvider) GetMinStackQLVersion() string {
7576
return ""
7677
}
7778

79+
func (pr *standardProvider) IsSnakeCaseAliasesEnabled() bool {
80+
if pr.StackQLConfig != nil {
81+
return pr.StackQLConfig.IsSnakeCaseAliasesEnabled()
82+
}
83+
return false
84+
}
85+
7886
func (pr *standardProvider) GetProviderServices() map[string]ProviderService {
7987
providerServices := make(map[string]ProviderService, len(pr.ProviderServices))
8088
for k, v := range pr.ProviderServices {

internal/anysdk/schema.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/antchfx/xmlquery"
1212
"github.com/getkin/kin-openapi/openapi3"
13+
"github.com/stackql/any-sdk/pkg/casing"
1314
"github.com/stackql/any-sdk/pkg/jsonpath"
1415
"github.com/stackql/any-sdk/pkg/media"
1516
"github.com/stackql/any-sdk/pkg/openapitopath"
@@ -995,13 +996,21 @@ func (s *standardSchema) IsArrayRef() bool {
995996
}
996997

997998
func (s *standardSchema) getPropertiesColumns() []ColumnDescriptor {
999+
snakeAliases := s.isSnakeCaseAliasesEnabled()
9981000
var cols []ColumnDescriptor
9991001
for k, val := range s.Properties {
10001002
valSchema := val.Value
10011003
if valSchema != nil {
1004+
// The column's display/DDL name is snake-aliased when the provider opts
1005+
// in; the wire property name (k) is retained on the Schema for response
1006+
// navigation.
1007+
name := k
1008+
if snakeAliases {
1009+
name = casing.ToSnake(k)
1010+
}
10021011
col := newColumnDescriptor(
10031012
"",
1004-
k,
1013+
name,
10051014
"",
10061015
"",
10071016
nil,
@@ -1019,6 +1028,19 @@ func (s *standardSchema) getPropertiesColumns() []ColumnDescriptor {
10191028
return cols
10201029
}
10211030

1031+
// isSnakeCaseAliasesEnabled reports whether the enclosing provider opts in to
1032+
// snake_case output aliases. Defaults to false (no behaviour change).
1033+
func (s *standardSchema) isSnakeCaseAliasesEnabled() bool {
1034+
if s.svc == nil {
1035+
return false
1036+
}
1037+
prov := s.svc.getProvider()
1038+
if prov == nil {
1039+
return false
1040+
}
1041+
return prov.IsSnakeCaseAliasesEnabled()
1042+
}
1043+
10221044
func (s *standardSchema) getAllOfColumns() []ColumnDescriptor {
10231045
return s.getAllSchemaRefsColumns(s.AllOf)
10241046
}

0 commit comments

Comments
 (0)