package rpcv2 import ( "bytes" "context" "fmt" "io" "net/http" "github.com/aws/smithy-go" internales "github.com/aws/smithy-go/internal/eventstream" internalerrors "github.com/aws/smithy-go/internal/errors" smithyio "github.com/aws/smithy-go/io" "github.com/aws/smithy-go/middleware" internalcbor "github.com/aws/smithy-go/transport/http/protocol/internal/cbor" "github.com/aws/smithy-go/traits" smithyhttp "github.com/aws/smithy-go/transport/http" ) // Protocol implements an RPC v2 protocol. // // RPCv2 protocol family: // - CBOR: https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html type Protocol struct { queryCompatible bool serviceName string eventstream *internales.Codec } var _ smithyhttp.ClientProtocol = (*Protocol)(nil) // ProtocolOptions configures smithy.protocols#rpcv2Cbor. type ProtocolOptions struct{} // NewCBOR returns an instance of the smithy.protocols#rpcv2Cbor protocol. func NewCBOR(service *smithy.ServiceSchema, opts ...func(*ProtocolOptions)) *Protocol { var o ProtocolOptions for _, fn := range opts { fn(&o) } _, qc := smithy.SchemaTrait[*traits.AWSQueryCompatible](service.Schema) return &Protocol{ queryCompatible: qc, serviceName: service.Schema.ID().Name, eventstream: &internales.Codec{ Serializer: func() smithy.ShapeSerializer { return internalcbor.NewShapeSerializer() }, Deserializer: func(p []byte) smithy.ShapeDeserializer { return internalcbor.NewShapeDeserializer(p) }, ContentType: "application/cbor", }, } } // ID identifies the protocol. func (p *Protocol) ID() smithy.ShapeID { return smithy.ShapeID{Namespace: "smithy.protocols", Name: "rpcv2Cbor"} } // SerializeRequest serializes a request for rpcv2Cbor. func (p *Protocol) SerializeRequest( ctx context.Context, schema *smithy.OperationSchema, in smithy.Serializable, req *smithyhttp.Request, ) error { req.Method = http.MethodPost req.URL.Path = fmt.Sprintf("/service/%s/operation/%s", p.serviceName, middleware.GetOperationName(ctx)) req.Header.Set("Smithy-Protocol", "rpc-v2-cbor") req.Header.Set("Accept", "application/cbor") if p.queryCompatible { req.Header.Set("X-Amzn-Query-Mode", "true") } if schema.IsInputEventStream() { req.Header.Set("Content-Type", "application/vnd.amazon.eventstream") return nil } if schema.Input == nil { return nil } ss := internalcbor.NewShapeSerializer() in.Serialize(ss) payload := ss.Bytes() if len(payload) == 0 { return nil } if ss.IsUnitShape() { return nil } req.Header.Set("Content-Type", "application/cbor") sreq, err := req.SetStream(bytes.NewReader(payload)) if err != nil { return fmt.Errorf("set stream: %w", err) } *req = *sreq return nil } // DeserializeResponse deserializes a response for rpcv2Cbor. func (p *Protocol) DeserializeResponse( ctx context.Context, schema *smithy.OperationSchema, types *smithy.TypeRegistry, resp *smithyhttp.Response, out smithy.Deserializable, ) error { if resp.StatusCode < 200 || resp.StatusCode >= 300 { return p.deserializeError(types, resp) } if schema.IsOutputEventStream() { if schema.IsInputEventStream() { if err := p.eventstream.RequireBidiHTTP2(resp.Proto, resp.ProtoMajor); err != nil { return err } } return nil } payload, err := io.ReadAll(resp.Body) if err != nil { return &smithy.DeserializationError{Err: err} } if len(payload) == 0 { return nil } sd := internalcbor.NewShapeDeserializer(payload) if err := out.Deserialize(sd); err != nil { return &smithy.DeserializationError{Err: err} } return nil } // HasInitialEventMessage is true because this is an RPC protocol. func (*Protocol) HasInitialEventMessage() bool { return true } // SerializeEventMessage implements [smithyhttp.ClientProtocol]. func (p *Protocol) SerializeEventMessage(schema, variant *smithy.Schema, v smithy.Serializable, w io.Writer) error { return p.eventstream.SerializeEventMessage(schema, variant, v, w) } // DeserializeEventMessage implements [smithyhttp.ClientProtocol]. func (p *Protocol) DeserializeEventMessage(schema *smithy.Schema, types *smithy.TypeRegistry, r io.Reader) (smithy.Deserializable, error) { return p.eventstream.DeserializeEventMessage(schema, types, r) } // SerializeInitialRequest implements [smithyhttp.ClientProtocol]. func (p *Protocol) SerializeInitialRequest(schema *smithy.Schema, v smithy.Serializable, w io.Writer) error { return p.eventstream.SerializeInitialRequest(schema, v, w) } // DeserializeInitialResponse implements [smithyhttp.ClientProtocol]. func (p *Protocol) DeserializeInitialResponse(schema *smithy.Schema, r io.Reader, out smithy.Deserializable) error { return p.eventstream.DeserializeInitialResponse(schema, r, out) } func (p *Protocol) deserializeError(types *smithy.TypeRegistry, response *smithyhttp.Response) error { var errorBuffer bytes.Buffer if _, err := io.Copy(&errorBuffer, response.Body); err != nil { return &smithy.DeserializationError{Err: fmt.Errorf("failed to copy error response body, %w", err)} } errorBody := errorBuffer.Bytes() errorCode := "UnknownError" errorMessage := errorCode var buff [1024]byte ringBuffer := smithyio.NewRingBuffer(buff[:]) body := io.TeeReader(bytes.NewReader(errorBody), ringBuffer) bodyBytes, err := io.ReadAll(body) if err != nil { var snapshot bytes.Buffer io.Copy(&snapshot, ringBuffer) return &smithy.DeserializationError{ Err: fmt.Errorf("failed to read response body, %w", err), Snapshot: snapshot.Bytes(), } } bodyType, bodyMessage, err := internalcbor.GetProtocolErrorInfo(bodyBytes) if err != nil { var snapshot bytes.Buffer io.Copy(&snapshot, ringBuffer) return &smithy.DeserializationError{ Err: fmt.Errorf("failed to decode response body, %w", err), Snapshot: snapshot.Bytes(), } } if bodyType != "" { errorCode = bodyType } if bodyMessage != "" { errorMessage = bodyMessage } errorCode = internalerrors.SanitizeErrorCode(errorCode) var queryCode string var queryFault smithy.ErrorFault if p.queryCompatible { queryHeader := response.Header.Get("X-Amzn-Query-Error") queryCode, queryFault = internalerrors.ParseQueryError(queryHeader) } perr, ok := types.DeserializableError(errorCode) if !ok { code := errorCode if queryCode != "" { code = queryCode } return &smithy.GenericAPIError{ Code: code, Message: errorMessage, Fault: queryFault, } } if len(bodyBytes) > 0 { deser := internalcbor.NewShapeDeserializer(bodyBytes) if err := perr.Deserialize(deser); err != nil { return &smithy.DeserializationError{Err: err} } } if queryCode != "" { internalerrors.SetErrorCodeOverride(perr, queryCode) } return perr }