diff --git a/CHANGELOG.md b/CHANGELOG.md index d4e9c996e..414eac37a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ Thank you! - codegen: Fix `BigInteger`/`BigDecimal` default values and `@range` bounds losing precision when they don't fit in an `Int`/`Double` (e.g. `4294967296` becoming `0`, `9007199254740993` becoming `9007199254740992`), in [#2002](https://github.com/disneystreaming/smithy4s/pull/2002), fixing [#2001](https://github.com/disneystreaming/smithy4s/issues/2001). Values are now carried exactly and rendered as `scala.math.BigDecimal("...")`/`scala.math.BigInt("...")` string literals. Integer `Document` defaults beyond the `Long` range are rendered with `Document.fromBigDecimal` instead of wrapping. As a side effect, `@range` bounds on integer literals now print without a trailing `.0` in validation error messages (e.g. `Input must be >= 1` instead of `>= 1.0`). - Fix `@range` validation losing precision by converting every value through `Double` before comparing it to the bounds, in [#2004](https://github.com/disneystreaming/smithy4s/pull/2004), fixing [#2003](https://github.com/disneystreaming/smithy4s/issues/2003). `Long` values above 2^53, `BigInt` values beyond the double range (which used to be rejected as infinity) and `BigDecimal` values are now compared exactly. The matching codegen fix for `@range` bounds rendered through `Double` is in [#2002](https://github.com/disneystreaming/smithy4s/pull/2002). Validation messages for integral inputs no longer render a trailing `.0` (e.g. `but was 11` instead of `but was 11.0`). - Fix `@http` routes never matching when a greedy label (`{foo+}`) is followed by a static segment (e.g. `/base/{foo+}/end`) in [#1998](https://github.com/disneystreaming/smithy4s/pull/1998). `matchPath` only accumulated greedy segments when the greedy label was the last path segment, so such routes always 404'd. +- aws: Resolve the SigV4 signing name and the endpoint host independently in [#1596](https://github.com/disneystreaming/smithy4s/pull/1596). The signing name now comes from `aws.auth#sigv4`'s `name`, while the host keeps using `endpointPrefix`, falling back to `arnNamespace` when it is absent. Both previously used `endpointPrefix`, so services where the two differ were rejected with "Credential should be scoped to correct service" ([#1568](https://github.com/disneystreaming/smithy4s/issues/1568)), and services without an `endpointPrefix` derived the host from the *operation* name, e.g. `ListRegions.us-east-1.amazonaws.com` ([#1532](https://github.com/disneystreaming/smithy4s/issues/1532)). +- codegen: Skip traits from the `aws.endpoints` namespace when generating hints. They have no published Scala bindings, so services carrying them (e.g. the AWS Account API, via `aws.endpoints#standardPartitionalEndpoints`) generated code that did not compile. This mirrors the existing treatment of `smithy.rules`. # 0.19.11 diff --git a/modules/aws-http4s/src/smithy4s/aws/AwsClient.scala b/modules/aws-http4s/src/smithy4s/aws/AwsClient.scala index f58163fe0..af7bbe205 100644 --- a/modules/aws-http4s/src/smithy4s/aws/AwsClient.scala +++ b/modules/aws-http4s/src/smithy4s/aws/AwsClient.scala @@ -66,22 +66,47 @@ object AwsClient { val service: smithy4s.Service[Alg] ) { + /** + * The host prefix of the standard-partition endpoint, which is not + * necessarily the name used when signing (see `AwsSigning.signingName`): + * SES v2 is hosted at `email..amazonaws.com` but signs as `ses`. + * + * `endpointPrefix` is the field meant for this: it "identifies which + * endpoint in a given region should be used to connect to the service", + * resolving as `{endpointPrefix}.{region}.{dnsSuffix}`. See + * https://smithy.io/2.0/aws/aws-core.html#endpointprefix + * + * It is optional, though, and some services (the Account API, for one) omit + * it. We then fall back to `arnNamespace`, which is constrained to + * `^[a-z0-9.\-]{1,63}$` and defaults to the lowercased service shape name, + * making it a DNS-safe stand-in. See + * https://smithy.io/2.0/aws/aws-core.html#arnnamespace + * + * This only covers the `{prefix}.{region}.amazonaws.com` pattern; FIPS, + * dual-stack, non-standard partitions (`amazonaws.com.cn`, GovCloud) and + * operation-level endpoints all require evaluating + * `smithy.rules#endpointRuleSet`, which we do not do. + */ + private val hostPrefix: String = + awsService.endpointPrefix + .orElse(awsService.arnNamespace.map(_.value)) + .getOrElse(service.id.name) + .toLowerCase() + private def compiler[F[_]: Async: Compression: Hashing]( awsEnv: AwsEnvironment[F] ): service.FunctorEndpointCompiler[F] = { - def baseRequest(endpoint: OperationSchema[_, _, _, _, _]): F[HttpRequest[Blob]] = { + def baseRequest(@annotation.unused endpoint: OperationSchema[_, _, _, _, _]): F[HttpRequest[Blob]] = { awsEnv.region.map { region => - val endpointPrefix = awsService.endpointPrefix.getOrElse(endpoint.id.name) val baseUri = HttpUri.absolute( scheme = HttpUriScheme.Https, - host = s"$endpointPrefix.$region.amazonaws.com", + host = s"$hostPrefix.$region.amazonaws.com", port = None, path = IndexedSeq.empty, queryParams = IndexedSeq.empty, pathParams = None ) - // Uri.unsafeFromString(s"https://$endpointPrefix.$region.amazonaws.com/") HttpRequest(HttpMethod.POST, baseUri, Map.empty, Blob.empty) } } diff --git a/modules/aws-http4s/src/smithy4s/aws/internals/AwsSigning.scala b/modules/aws-http4s/src/smithy4s/aws/internals/AwsSigning.scala index 90179bec8..cbeb22dcc 100644 --- a/modules/aws-http4s/src/smithy4s/aws/internals/AwsSigning.scala +++ b/modules/aws-http4s/src/smithy4s/aws/internals/AwsSigning.scala @@ -67,16 +67,10 @@ private[aws] object AwsSigning { endpointHints: Hints, awsEnvironment: AwsEnvironment[F] ): Client[F] => Client[F] = { - val endpointPrefix = serviceHints - .get(_root_.aws.api.Service) - .flatMap(_.endpointPrefix) - .getOrElse(serviceId.name) - .toLowerCase() - val sign = signingFunction( serviceId.name, endpointId.name, - endpointPrefix, + signingName(serviceId, serviceHints), awsEnvironment.timestamp, awsEnvironment.credentials, awsEnvironment.region @@ -89,10 +83,44 @@ private[aws] object AwsSigning { } } + /** + * The name used in the credential scope when signing requests, which is not + * necessarily the same as the host prefix (see `AwsClient`): SES v2, for + * instance, is hosted at `email..amazonaws.com` but signs as `ses`. + * + * `aws.auth#sigv4`'s `name` is, per its own definition, "the signature + * version 4 service signing name to use in the credential scope when signing + * requests", so it wins. It also states that the value SHOULD match + * `arnNamespace`, which is why that is preferred over `endpointPrefix` -- + * the latter is documented as being unstable and non-unique, and MUST NOT be + * used for anything other than resolving endpoints. See + * https://smithy.io/2.0/aws/aws-auth.html#aws-auth-sigv4-trait and + * https://smithy.io/2.0/aws/aws-core.html#endpointprefix + */ + private[internals] def signingName( + serviceId: ShapeId, + serviceHints: Hints + ): String = { + val fromSigv4 = serviceHints.get(_root_.aws.auth.Sigv4).map(_.name) + + val fromAwsService = serviceHints + .get(_root_.aws.api.Service) + .flatMap { awsService => + awsService.arnNamespace + .map(_.value) + .orElse(awsService.endpointPrefix) + } + + fromSigv4 + .orElse(fromAwsService) + .getOrElse(serviceId.name) + .toLowerCase() + } + private[internals] def signingFunction[F[_]: Concurrent]( serviceName: String, operationName: String, - endpointPrefix: String, + signingName: String, timestamp: F[Timestamp], credentials: F[AwsCredentials], region: F[AwsRegion] @@ -153,7 +181,7 @@ private[aws] object AwsSigning { } .flatMap { case ((payloadHash, preparedRequest), pathString) => val awsHeadersF = (timestamp, credentials, region).mapN { case (timestamp, credentials, region) => - val credentialsScope = s"${timestamp.conciseDate}/$region/$endpointPrefix/aws4_request" + val credentialsScope = s"${timestamp.conciseDate}/$region/$signingName/aws4_request" val queryParams: Vector[(String, String)] = request.uri.query.toVector.sorted.map { case (k, v) => k -> v.getOrElse("") } val canonicalQueryString = @@ -202,7 +230,7 @@ private[aws] object AwsSigning { credentials.secretAccessKey, timestamp.conciseDate, region.value, - endpointPrefix + signingName ) val stringToSign = List[String]( algorithm, diff --git a/modules/aws-http4s/test/src/smithy4s/aws/AwsEndpointAndSigningNameTest.scala b/modules/aws-http4s/test/src/smithy4s/aws/AwsEndpointAndSigningNameTest.scala new file mode 100644 index 000000000..cf9ec996c --- /dev/null +++ b/modules/aws-http4s/test/src/smithy4s/aws/AwsEndpointAndSigningNameTest.scala @@ -0,0 +1,153 @@ +/* + * Copyright 2021-2026 Disney Streaming + * + * Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://disneystreaming.github.io/TOST-1.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smithy4s.aws + +import cats.effect.IO +import cats.effect.Resource +import cats.syntax.all._ +import com.amazonaws.dynamodb.DynamoDB +import org.http4s.Request +import org.http4s.Response +import org.http4s.Status +import org.http4s.client.Client +import smithy4s.aws.kernel.AwsCredentials +import smithy4s.aws.kernel.Timestamp +import weaver._ + +/** + * The host and the sigv4 signing name are resolved from *different* fields and + * are not interchangeable: SES v2 is reached at `email..amazonaws.com` + * but must be signed as `ses`, or AWS rejects the request with "Credential + * should be scoped to correct service". + * + * These tests pin both halves for each combination of `endpointPrefix`, + * `arnNamespace` and `aws.auth#sigv4` we have seen in the wild. + */ +object AwsEndpointAndSigningNameTest extends SimpleIOSuite with Compat { + + private val credentials = AwsCredentials.Default( + accessKeyId = "AKIAIOSFODNN7EXAMPLE", + secretAccessKey = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", + sessionToken = None + ) + + /** 2024-09-27T00:00:00Z -- fixed so the credential scope is deterministic. */ + private val timestamp = Timestamp(1727395200L, 0) + + /** + * Runs one operation against a stub client and returns the request that + * would have gone out to AWS. + */ + private def capture( + run: AwsEnvironment[IO] => Resource[IO, Any] + ): IO[Request[IO]] = + IO.ref(Option.empty[Request[IO]]).flatMap { ref => + val httpClient = Client[IO] { req => + Resource.eval(ref.set(Some(req))).as(Response[IO](Status.Forbidden)) + } + + val awsEnv = AwsEnvironment.make[IO]( + httpClient, + IO.pure(AwsRegion.US_EAST_1), + IO.pure(credentials), + IO.pure(timestamp) + ) + + run(awsEnv).use_.attempt *> + ref.get.flatMap( + _.liftTo[IO](new AssertionError("no request was sent")) + ) + } + + private def host(request: Request[IO]): Option[String] = + request.uri.host.map(_.renderString) + + /** The `` part of `Credential=////aws4_request`. */ + private def signingName(request: Request[IO]): Option[String] = + request.headers + .get(org.typelevel.ci.CIString("Authorization")) + .map(_.head.value) + .flatMap { auth => + "Credential=[^/]+/[^/]+/[^/]+/([^/]+)/aws4_request".r + .findFirstMatchIn(auth) + .map(_.group(1)) + } + + test( + "endpointPrefix and sigv4 name differ: host uses the prefix, signing uses sigv4 (SES v2)" + ) { + capture { awsEnv => + AwsClient(smithy4s.example.aws.PrefixDiffersFromSigningName, awsEnv) + .evalMap(_.doThing()) + }.map { request => + expect.same(host(request), Some("email.us-east-1.amazonaws.com")) && + expect.same(signingName(request), Some("ses")) + } + } + + test( + "dotted endpointPrefix: host keeps the dots, signing uses sigv4 (SageMaker, #1568)" + ) { + capture { awsEnv => + AwsClient(smithy4s.example.aws.DottedPrefix, awsEnv) + .evalMap(_.doThing()) + }.map { request => + expect.same( + host(request), + Some("api.sagemaker.us-east-1.amazonaws.com") + ) && + expect.same(signingName(request), Some("sagemaker")) + } + } + + test( + "no endpointPrefix: both fall back to arnNamespace, never the operation name (Account, #1532)" + ) { + capture { awsEnv => + AwsClient(smithy4s.example.aws.NoEndpointPrefix, awsEnv) + .evalMap(_.doThing()) + }.map { request => + expect.same(host(request), Some("account.us-east-1.amazonaws.com")) && + expect.same(signingName(request), Some("account")) + } + } + + test( + "no sigv4 trait: signing falls back to arnNamespace rather than endpointPrefix" + ) { + capture { awsEnv => + AwsClient(smithy4s.example.aws.NoSigv4, awsEnv) + .evalMap(_.doThing()) + }.map { request => + expect.same( + host(request), + Some("endpointprefix.us-east-1.amazonaws.com") + ) && + expect.same(signingName(request), Some("arnnamespace")) + } + } + + test("all three names agree: nothing changes (DynamoDB)") { + capture { awsEnv => + AwsClient(DynamoDB, awsEnv).evalMap(_.listTables()) + }.map { request => + expect.same(host(request), Some("dynamodb.us-east-1.amazonaws.com")) && + expect.same(signingName(request), Some("dynamodb")) + } + } + +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/aws/DoThingInput.scala b/modules/bootstrapped/src/generated/smithy4s/example/aws/DoThingInput.scala new file mode 100644 index 000000000..6be7aed63 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/aws/DoThingInput.scala @@ -0,0 +1,20 @@ +package smithy4s.example.aws + +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ShapeTag +import smithy4s.schema.Schema.constant + +final case class DoThingInput() + +object DoThingInput extends ShapeTag.Companion[DoThingInput] { + val id: ShapeId = ShapeId("smithy4s.example.aws", "DoThingInput") + + val hints: Hints = Hints( + Hints.dynamic(ShapeId("smithy.api", "input"), smithy4s.Document.obj()), + ) + + + implicit val schema: Schema[DoThingInput] = constant(DoThingInput()).withId(id).addHints(hints) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/aws/DoThingOutput.scala b/modules/bootstrapped/src/generated/smithy4s/example/aws/DoThingOutput.scala new file mode 100644 index 000000000..0fe001add --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/aws/DoThingOutput.scala @@ -0,0 +1,20 @@ +package smithy4s.example.aws + +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ShapeTag +import smithy4s.schema.Schema.constant + +final case class DoThingOutput() + +object DoThingOutput extends ShapeTag.Companion[DoThingOutput] { + val id: ShapeId = ShapeId("smithy4s.example.aws", "DoThingOutput") + + val hints: Hints = Hints( + Hints.dynamic(ShapeId("smithy.api", "output"), smithy4s.Document.obj()), + ) + + + implicit val schema: Schema[DoThingOutput] = constant(DoThingOutput()).withId(id).addHints(hints) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/aws/DottedPrefix.scala b/modules/bootstrapped/src/generated/smithy4s/example/aws/DottedPrefix.scala new file mode 100644 index 000000000..9d111f4d5 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/aws/DottedPrefix.scala @@ -0,0 +1,103 @@ +package smithy4s.example.aws + +import smithy4s.Endpoint +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.Service +import smithy4s.ShapeId +import smithy4s.Transformation +import smithy4s.kinds.BiFunctorAlgebra +import smithy4s.kinds.FunctorAlgebra +import smithy4s.kinds.PolyFunction5 +import smithy4s.kinds.toPolyFunction5.const5 +import smithy4s.schema.OperationSchema + +/** Mirrors the shape of AWS SageMaker (issue #1568): the endpoint prefix is + * dotted and differs from the signing name, which matches the ARN namespace. + */ +trait DottedPrefixGen[F[_, _, _, _, _]] { + self => + + def doThing(): F[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] + +} + +object DottedPrefixGen extends Service.Mixin[DottedPrefixGen, DottedPrefixOperation] { + + val id: ShapeId = ShapeId("smithy4s.example.aws", "DottedPrefix") + val version: String = "" + + val hints: Hints = Hints( + aws.api.Service(sdkId = "DottedPrefix", arnNamespace = Some(aws.api.ArnNamespace("sagemaker")), cloudFormationName = None, cloudTrailEventSource = None, docId = None, endpointPrefix = Some("api.sagemaker"), cloudWatchNamespace = None), + aws.auth.Sigv4(name = "sagemaker"), + aws.protocols.AwsJson1_1(http = None, eventStreamHttp = None), + Hints.dynamic(ShapeId("smithy.api", "documentation"), smithy4s.Document.fromString("Mirrors the shape of AWS SageMaker (issue #1568): the endpoint prefix is\ndotted and differs from the signing name, which matches the ARN namespace.")), + ).lazily + + def apply[F[_]](implicit F: Impl[F]): F.type = F + + object ErrorAware { + def apply[F[_, _]](implicit F: ErrorAware[F]): F.type = F + type Default[F[+_, +_]] = Constant[smithy4s.kinds.stubs.Kind2[F]#toKind5] + } + + val endpoints: Vector[smithy4s.Endpoint[DottedPrefixOperation, _, _, _, _, _]] = Vector( + DottedPrefixOperation.DoThing, + ) + + def input[I, E, O, SI, SO](op: DottedPrefixOperation[I, E, O, SI, SO]): I = op.input + def ordinal[I, E, O, SI, SO](op: DottedPrefixOperation[I, E, O, SI, SO]): Int = op.ordinal + override def endpoint[I, E, O, SI, SO](op: DottedPrefixOperation[I, E, O, SI, SO]) = op.endpoint + class Constant[P[-_, +_, +_, +_, +_]](value: P[Any, Nothing, Nothing, Nothing, Nothing]) extends DottedPrefixOperation.Transformed[DottedPrefixOperation, P](reified, const5(value)) + type Default[F[+_]] = Constant[smithy4s.kinds.stubs.Kind1[F]#toKind5] + def reified: DottedPrefixGen[DottedPrefixOperation] = DottedPrefixOperation.reified + def mapK5[P[_, _, _, _, _], P1[_, _, _, _, _]](alg: DottedPrefixGen[P], f: PolyFunction5[P, P1]): DottedPrefixGen[P1] = new DottedPrefixOperation.Transformed(alg, f) + def fromPolyFunction[P[_, _, _, _, _]](f: PolyFunction5[DottedPrefixOperation, P]): DottedPrefixGen[P] = new DottedPrefixOperation.Transformed(reified, f) + def toPolyFunction[P[_, _, _, _, _]](impl: DottedPrefixGen[P]): PolyFunction5[DottedPrefixOperation, P] = DottedPrefixOperation.toPolyFunction(impl) + + + implicit final class TransformFunctorOps[F[_]](private val alg: FunctorAlgebra[DottedPrefixGen, F]) extends AnyVal { + def transform: Transformation.PartiallyApplied[FunctorAlgebra[DottedPrefixGen, F]] = Transformation.of(alg) + } + + implicit final class TransformBifunctorOps[F[_, _]](private val alg: BiFunctorAlgebra[DottedPrefixGen, F]) extends AnyVal { + def transform: Transformation.PartiallyApplied[BiFunctorAlgebra[DottedPrefixGen, F]] = Transformation.of(alg) + } + + implicit final class TransformOps[F[_, _, _, _, _]](private val alg: DottedPrefixGen[F]) extends AnyVal { + def transform: Transformation.PartiallyApplied[DottedPrefixGen[F]] = Transformation.of(alg) + } +} + +sealed trait DottedPrefixOperation[Input, Err, Output, StreamedInput, StreamedOutput] { + def run[F[_, _, _, _, _]](impl: DottedPrefixGen[F]): F[Input, Err, Output, StreamedInput, StreamedOutput] + def ordinal: Int + def input: Input + def endpoint: Endpoint[DottedPrefixOperation, Input, Err, Output, StreamedInput, StreamedOutput] +} + +object DottedPrefixOperation { + + object reified extends DottedPrefixGen[DottedPrefixOperation] { + def doThing(): DoThing = DoThing(DoThingInput()) + } + class Transformed[P[_, _, _, _, _], P1[_ ,_ ,_ ,_ ,_]](alg: DottedPrefixGen[P], f: PolyFunction5[P, P1]) extends DottedPrefixGen[P1] { + def doThing(): P1[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = f[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing](this.alg.doThing()) + } + + def toPolyFunction[P[_, _, _, _, _]](impl: DottedPrefixGen[P]): PolyFunction5[DottedPrefixOperation, P] = new PolyFunction5[DottedPrefixOperation, P] { + def apply[I, E, O, SI, SO](op: DottedPrefixOperation[I, E, O, SI, SO]): P[I, E, O, SI, SO] = op.run(impl) + } + final case class DoThing(input: DoThingInput) extends DottedPrefixOperation[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] { + def run[F[_, _, _, _, _]](impl: DottedPrefixGen[F]): F[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = impl.doThing() + def ordinal: Int = 0 + def endpoint: smithy4s.Endpoint[DottedPrefixOperation,DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = DoThing + } + object DoThing extends smithy4s.Endpoint[DottedPrefixOperation,DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] { + val schema: OperationSchema[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = Schema.operation(ShapeId("smithy4s.example.aws", "DoThing")) + .withInput(DoThingInput.schema) + .withOutput(DoThingOutput.schema) + def wrap(input: DoThingInput): DoThing = DoThing(input) + } +} + diff --git a/modules/bootstrapped/src/generated/smithy4s/example/aws/NoEndpointPrefix.scala b/modules/bootstrapped/src/generated/smithy4s/example/aws/NoEndpointPrefix.scala new file mode 100644 index 000000000..97224739e --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/aws/NoEndpointPrefix.scala @@ -0,0 +1,104 @@ +package smithy4s.example.aws + +import smithy4s.Endpoint +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.Service +import smithy4s.ShapeId +import smithy4s.Transformation +import smithy4s.kinds.BiFunctorAlgebra +import smithy4s.kinds.FunctorAlgebra +import smithy4s.kinds.PolyFunction5 +import smithy4s.kinds.toPolyFunction5.const5 +import smithy4s.schema.OperationSchema + +/** Mirrors the shape of the AWS Account API (issue #1532): no endpoint prefix + * at all, so both the host and the signing name fall back to the ARN + * namespace. Before the fix, the host was derived from the *operation* name. + */ +trait NoEndpointPrefixGen[F[_, _, _, _, _]] { + self => + + def doThing(): F[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] + +} + +object NoEndpointPrefixGen extends Service.Mixin[NoEndpointPrefixGen, NoEndpointPrefixOperation] { + + val id: ShapeId = ShapeId("smithy4s.example.aws", "NoEndpointPrefix") + val version: String = "" + + val hints: Hints = Hints( + aws.api.Service(sdkId = "NoEndpointPrefix", arnNamespace = Some(aws.api.ArnNamespace("account")), cloudFormationName = None, cloudTrailEventSource = None, docId = None, endpointPrefix = None, cloudWatchNamespace = None), + aws.auth.Sigv4(name = "account"), + aws.protocols.AwsJson1_0(http = None, eventStreamHttp = None), + Hints.dynamic(ShapeId("smithy.api", "documentation"), smithy4s.Document.fromString("Mirrors the shape of the AWS Account API (issue #1532): no endpoint prefix\nat all, so both the host and the signing name fall back to the ARN\nnamespace. Before the fix, the host was derived from the *operation* name.")), + ).lazily + + def apply[F[_]](implicit F: Impl[F]): F.type = F + + object ErrorAware { + def apply[F[_, _]](implicit F: ErrorAware[F]): F.type = F + type Default[F[+_, +_]] = Constant[smithy4s.kinds.stubs.Kind2[F]#toKind5] + } + + val endpoints: Vector[smithy4s.Endpoint[NoEndpointPrefixOperation, _, _, _, _, _]] = Vector( + NoEndpointPrefixOperation.DoThing, + ) + + def input[I, E, O, SI, SO](op: NoEndpointPrefixOperation[I, E, O, SI, SO]): I = op.input + def ordinal[I, E, O, SI, SO](op: NoEndpointPrefixOperation[I, E, O, SI, SO]): Int = op.ordinal + override def endpoint[I, E, O, SI, SO](op: NoEndpointPrefixOperation[I, E, O, SI, SO]) = op.endpoint + class Constant[P[-_, +_, +_, +_, +_]](value: P[Any, Nothing, Nothing, Nothing, Nothing]) extends NoEndpointPrefixOperation.Transformed[NoEndpointPrefixOperation, P](reified, const5(value)) + type Default[F[+_]] = Constant[smithy4s.kinds.stubs.Kind1[F]#toKind5] + def reified: NoEndpointPrefixGen[NoEndpointPrefixOperation] = NoEndpointPrefixOperation.reified + def mapK5[P[_, _, _, _, _], P1[_, _, _, _, _]](alg: NoEndpointPrefixGen[P], f: PolyFunction5[P, P1]): NoEndpointPrefixGen[P1] = new NoEndpointPrefixOperation.Transformed(alg, f) + def fromPolyFunction[P[_, _, _, _, _]](f: PolyFunction5[NoEndpointPrefixOperation, P]): NoEndpointPrefixGen[P] = new NoEndpointPrefixOperation.Transformed(reified, f) + def toPolyFunction[P[_, _, _, _, _]](impl: NoEndpointPrefixGen[P]): PolyFunction5[NoEndpointPrefixOperation, P] = NoEndpointPrefixOperation.toPolyFunction(impl) + + + implicit final class TransformFunctorOps[F[_]](private val alg: FunctorAlgebra[NoEndpointPrefixGen, F]) extends AnyVal { + def transform: Transformation.PartiallyApplied[FunctorAlgebra[NoEndpointPrefixGen, F]] = Transformation.of(alg) + } + + implicit final class TransformBifunctorOps[F[_, _]](private val alg: BiFunctorAlgebra[NoEndpointPrefixGen, F]) extends AnyVal { + def transform: Transformation.PartiallyApplied[BiFunctorAlgebra[NoEndpointPrefixGen, F]] = Transformation.of(alg) + } + + implicit final class TransformOps[F[_, _, _, _, _]](private val alg: NoEndpointPrefixGen[F]) extends AnyVal { + def transform: Transformation.PartiallyApplied[NoEndpointPrefixGen[F]] = Transformation.of(alg) + } +} + +sealed trait NoEndpointPrefixOperation[Input, Err, Output, StreamedInput, StreamedOutput] { + def run[F[_, _, _, _, _]](impl: NoEndpointPrefixGen[F]): F[Input, Err, Output, StreamedInput, StreamedOutput] + def ordinal: Int + def input: Input + def endpoint: Endpoint[NoEndpointPrefixOperation, Input, Err, Output, StreamedInput, StreamedOutput] +} + +object NoEndpointPrefixOperation { + + object reified extends NoEndpointPrefixGen[NoEndpointPrefixOperation] { + def doThing(): DoThing = DoThing(DoThingInput()) + } + class Transformed[P[_, _, _, _, _], P1[_ ,_ ,_ ,_ ,_]](alg: NoEndpointPrefixGen[P], f: PolyFunction5[P, P1]) extends NoEndpointPrefixGen[P1] { + def doThing(): P1[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = f[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing](this.alg.doThing()) + } + + def toPolyFunction[P[_, _, _, _, _]](impl: NoEndpointPrefixGen[P]): PolyFunction5[NoEndpointPrefixOperation, P] = new PolyFunction5[NoEndpointPrefixOperation, P] { + def apply[I, E, O, SI, SO](op: NoEndpointPrefixOperation[I, E, O, SI, SO]): P[I, E, O, SI, SO] = op.run(impl) + } + final case class DoThing(input: DoThingInput) extends NoEndpointPrefixOperation[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] { + def run[F[_, _, _, _, _]](impl: NoEndpointPrefixGen[F]): F[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = impl.doThing() + def ordinal: Int = 0 + def endpoint: smithy4s.Endpoint[NoEndpointPrefixOperation,DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = DoThing + } + object DoThing extends smithy4s.Endpoint[NoEndpointPrefixOperation,DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] { + val schema: OperationSchema[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = Schema.operation(ShapeId("smithy4s.example.aws", "DoThing")) + .withInput(DoThingInput.schema) + .withOutput(DoThingOutput.schema) + def wrap(input: DoThingInput): DoThing = DoThing(input) + } +} + diff --git a/modules/bootstrapped/src/generated/smithy4s/example/aws/NoSigv4.scala b/modules/bootstrapped/src/generated/smithy4s/example/aws/NoSigv4.scala new file mode 100644 index 000000000..155a5c8b3 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/aws/NoSigv4.scala @@ -0,0 +1,102 @@ +package smithy4s.example.aws + +import smithy4s.Endpoint +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.Service +import smithy4s.ShapeId +import smithy4s.Transformation +import smithy4s.kinds.BiFunctorAlgebra +import smithy4s.kinds.FunctorAlgebra +import smithy4s.kinds.PolyFunction5 +import smithy4s.kinds.toPolyFunction5.const5 +import smithy4s.schema.OperationSchema + +/** A service with no sigv4 trait, to check that the signing name falls back to + * the ARN namespace rather than to the endpoint prefix. + */ +trait NoSigv4Gen[F[_, _, _, _, _]] { + self => + + def doThing(): F[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] + +} + +object NoSigv4Gen extends Service.Mixin[NoSigv4Gen, NoSigv4Operation] { + + val id: ShapeId = ShapeId("smithy4s.example.aws", "NoSigv4") + val version: String = "" + + val hints: Hints = Hints( + aws.api.Service(sdkId = "NoSigv4", arnNamespace = Some(aws.api.ArnNamespace("arnnamespace")), cloudFormationName = None, cloudTrailEventSource = None, docId = None, endpointPrefix = Some("endpointprefix"), cloudWatchNamespace = None), + aws.protocols.AwsJson1_0(http = None, eventStreamHttp = None), + Hints.dynamic(ShapeId("smithy.api", "documentation"), smithy4s.Document.fromString("A service with no sigv4 trait, to check that the signing name falls back to\nthe ARN namespace rather than to the endpoint prefix.")), + ).lazily + + def apply[F[_]](implicit F: Impl[F]): F.type = F + + object ErrorAware { + def apply[F[_, _]](implicit F: ErrorAware[F]): F.type = F + type Default[F[+_, +_]] = Constant[smithy4s.kinds.stubs.Kind2[F]#toKind5] + } + + val endpoints: Vector[smithy4s.Endpoint[NoSigv4Operation, _, _, _, _, _]] = Vector( + NoSigv4Operation.DoThing, + ) + + def input[I, E, O, SI, SO](op: NoSigv4Operation[I, E, O, SI, SO]): I = op.input + def ordinal[I, E, O, SI, SO](op: NoSigv4Operation[I, E, O, SI, SO]): Int = op.ordinal + override def endpoint[I, E, O, SI, SO](op: NoSigv4Operation[I, E, O, SI, SO]) = op.endpoint + class Constant[P[-_, +_, +_, +_, +_]](value: P[Any, Nothing, Nothing, Nothing, Nothing]) extends NoSigv4Operation.Transformed[NoSigv4Operation, P](reified, const5(value)) + type Default[F[+_]] = Constant[smithy4s.kinds.stubs.Kind1[F]#toKind5] + def reified: NoSigv4Gen[NoSigv4Operation] = NoSigv4Operation.reified + def mapK5[P[_, _, _, _, _], P1[_, _, _, _, _]](alg: NoSigv4Gen[P], f: PolyFunction5[P, P1]): NoSigv4Gen[P1] = new NoSigv4Operation.Transformed(alg, f) + def fromPolyFunction[P[_, _, _, _, _]](f: PolyFunction5[NoSigv4Operation, P]): NoSigv4Gen[P] = new NoSigv4Operation.Transformed(reified, f) + def toPolyFunction[P[_, _, _, _, _]](impl: NoSigv4Gen[P]): PolyFunction5[NoSigv4Operation, P] = NoSigv4Operation.toPolyFunction(impl) + + + implicit final class TransformFunctorOps[F[_]](private val alg: FunctorAlgebra[NoSigv4Gen, F]) extends AnyVal { + def transform: Transformation.PartiallyApplied[FunctorAlgebra[NoSigv4Gen, F]] = Transformation.of(alg) + } + + implicit final class TransformBifunctorOps[F[_, _]](private val alg: BiFunctorAlgebra[NoSigv4Gen, F]) extends AnyVal { + def transform: Transformation.PartiallyApplied[BiFunctorAlgebra[NoSigv4Gen, F]] = Transformation.of(alg) + } + + implicit final class TransformOps[F[_, _, _, _, _]](private val alg: NoSigv4Gen[F]) extends AnyVal { + def transform: Transformation.PartiallyApplied[NoSigv4Gen[F]] = Transformation.of(alg) + } +} + +sealed trait NoSigv4Operation[Input, Err, Output, StreamedInput, StreamedOutput] { + def run[F[_, _, _, _, _]](impl: NoSigv4Gen[F]): F[Input, Err, Output, StreamedInput, StreamedOutput] + def ordinal: Int + def input: Input + def endpoint: Endpoint[NoSigv4Operation, Input, Err, Output, StreamedInput, StreamedOutput] +} + +object NoSigv4Operation { + + object reified extends NoSigv4Gen[NoSigv4Operation] { + def doThing(): DoThing = DoThing(DoThingInput()) + } + class Transformed[P[_, _, _, _, _], P1[_ ,_ ,_ ,_ ,_]](alg: NoSigv4Gen[P], f: PolyFunction5[P, P1]) extends NoSigv4Gen[P1] { + def doThing(): P1[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = f[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing](this.alg.doThing()) + } + + def toPolyFunction[P[_, _, _, _, _]](impl: NoSigv4Gen[P]): PolyFunction5[NoSigv4Operation, P] = new PolyFunction5[NoSigv4Operation, P] { + def apply[I, E, O, SI, SO](op: NoSigv4Operation[I, E, O, SI, SO]): P[I, E, O, SI, SO] = op.run(impl) + } + final case class DoThing(input: DoThingInput) extends NoSigv4Operation[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] { + def run[F[_, _, _, _, _]](impl: NoSigv4Gen[F]): F[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = impl.doThing() + def ordinal: Int = 0 + def endpoint: smithy4s.Endpoint[NoSigv4Operation,DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = DoThing + } + object DoThing extends smithy4s.Endpoint[NoSigv4Operation,DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] { + val schema: OperationSchema[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = Schema.operation(ShapeId("smithy4s.example.aws", "DoThing")) + .withInput(DoThingInput.schema) + .withOutput(DoThingOutput.schema) + def wrap(input: DoThingInput): DoThing = DoThing(input) + } +} + diff --git a/modules/bootstrapped/src/generated/smithy4s/example/aws/PrefixDiffersFromSigningName.scala b/modules/bootstrapped/src/generated/smithy4s/example/aws/PrefixDiffersFromSigningName.scala new file mode 100644 index 000000000..92ab5df03 --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/aws/PrefixDiffersFromSigningName.scala @@ -0,0 +1,105 @@ +package smithy4s.example.aws + +import smithy4s.Endpoint +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.Service +import smithy4s.ShapeId +import smithy4s.Transformation +import smithy4s.kinds.BiFunctorAlgebra +import smithy4s.kinds.FunctorAlgebra +import smithy4s.kinds.PolyFunction5 +import smithy4s.kinds.toPolyFunction5.const5 +import smithy4s.schema.OperationSchema + +/** Mirrors the shape of AWS SES v2: the endpoint prefix ("email"), the ARN + * namespace ("ses") and the sigv4 signing name ("ses") are all different from + * each other, so the host and the credential scope must be resolved + * independently. + */ +trait PrefixDiffersFromSigningNameGen[F[_, _, _, _, _]] { + self => + + def doThing(): F[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] + +} + +object PrefixDiffersFromSigningNameGen extends Service.Mixin[PrefixDiffersFromSigningNameGen, PrefixDiffersFromSigningNameOperation] { + + val id: ShapeId = ShapeId("smithy4s.example.aws", "PrefixDiffersFromSigningName") + val version: String = "" + + val hints: Hints = Hints( + aws.api.Service(sdkId = "PrefixDiffersFromSigningName", arnNamespace = Some(aws.api.ArnNamespace("ses")), cloudFormationName = None, cloudTrailEventSource = None, docId = None, endpointPrefix = Some("email"), cloudWatchNamespace = None), + aws.auth.Sigv4(name = "ses"), + aws.protocols.AwsJson1_0(http = None, eventStreamHttp = None), + Hints.dynamic(ShapeId("smithy.api", "documentation"), smithy4s.Document.fromString("Mirrors the shape of AWS SES v2: the endpoint prefix (\"email\"), the ARN\nnamespace (\"ses\") and the sigv4 signing name (\"ses\") are all different from\neach other, so the host and the credential scope must be resolved\nindependently.")), + ).lazily + + def apply[F[_]](implicit F: Impl[F]): F.type = F + + object ErrorAware { + def apply[F[_, _]](implicit F: ErrorAware[F]): F.type = F + type Default[F[+_, +_]] = Constant[smithy4s.kinds.stubs.Kind2[F]#toKind5] + } + + val endpoints: Vector[smithy4s.Endpoint[PrefixDiffersFromSigningNameOperation, _, _, _, _, _]] = Vector( + PrefixDiffersFromSigningNameOperation.DoThing, + ) + + def input[I, E, O, SI, SO](op: PrefixDiffersFromSigningNameOperation[I, E, O, SI, SO]): I = op.input + def ordinal[I, E, O, SI, SO](op: PrefixDiffersFromSigningNameOperation[I, E, O, SI, SO]): Int = op.ordinal + override def endpoint[I, E, O, SI, SO](op: PrefixDiffersFromSigningNameOperation[I, E, O, SI, SO]) = op.endpoint + class Constant[P[-_, +_, +_, +_, +_]](value: P[Any, Nothing, Nothing, Nothing, Nothing]) extends PrefixDiffersFromSigningNameOperation.Transformed[PrefixDiffersFromSigningNameOperation, P](reified, const5(value)) + type Default[F[+_]] = Constant[smithy4s.kinds.stubs.Kind1[F]#toKind5] + def reified: PrefixDiffersFromSigningNameGen[PrefixDiffersFromSigningNameOperation] = PrefixDiffersFromSigningNameOperation.reified + def mapK5[P[_, _, _, _, _], P1[_, _, _, _, _]](alg: PrefixDiffersFromSigningNameGen[P], f: PolyFunction5[P, P1]): PrefixDiffersFromSigningNameGen[P1] = new PrefixDiffersFromSigningNameOperation.Transformed(alg, f) + def fromPolyFunction[P[_, _, _, _, _]](f: PolyFunction5[PrefixDiffersFromSigningNameOperation, P]): PrefixDiffersFromSigningNameGen[P] = new PrefixDiffersFromSigningNameOperation.Transformed(reified, f) + def toPolyFunction[P[_, _, _, _, _]](impl: PrefixDiffersFromSigningNameGen[P]): PolyFunction5[PrefixDiffersFromSigningNameOperation, P] = PrefixDiffersFromSigningNameOperation.toPolyFunction(impl) + + + implicit final class TransformFunctorOps[F[_]](private val alg: FunctorAlgebra[PrefixDiffersFromSigningNameGen, F]) extends AnyVal { + def transform: Transformation.PartiallyApplied[FunctorAlgebra[PrefixDiffersFromSigningNameGen, F]] = Transformation.of(alg) + } + + implicit final class TransformBifunctorOps[F[_, _]](private val alg: BiFunctorAlgebra[PrefixDiffersFromSigningNameGen, F]) extends AnyVal { + def transform: Transformation.PartiallyApplied[BiFunctorAlgebra[PrefixDiffersFromSigningNameGen, F]] = Transformation.of(alg) + } + + implicit final class TransformOps[F[_, _, _, _, _]](private val alg: PrefixDiffersFromSigningNameGen[F]) extends AnyVal { + def transform: Transformation.PartiallyApplied[PrefixDiffersFromSigningNameGen[F]] = Transformation.of(alg) + } +} + +sealed trait PrefixDiffersFromSigningNameOperation[Input, Err, Output, StreamedInput, StreamedOutput] { + def run[F[_, _, _, _, _]](impl: PrefixDiffersFromSigningNameGen[F]): F[Input, Err, Output, StreamedInput, StreamedOutput] + def ordinal: Int + def input: Input + def endpoint: Endpoint[PrefixDiffersFromSigningNameOperation, Input, Err, Output, StreamedInput, StreamedOutput] +} + +object PrefixDiffersFromSigningNameOperation { + + object reified extends PrefixDiffersFromSigningNameGen[PrefixDiffersFromSigningNameOperation] { + def doThing(): DoThing = DoThing(DoThingInput()) + } + class Transformed[P[_, _, _, _, _], P1[_ ,_ ,_ ,_ ,_]](alg: PrefixDiffersFromSigningNameGen[P], f: PolyFunction5[P, P1]) extends PrefixDiffersFromSigningNameGen[P1] { + def doThing(): P1[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = f[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing](this.alg.doThing()) + } + + def toPolyFunction[P[_, _, _, _, _]](impl: PrefixDiffersFromSigningNameGen[P]): PolyFunction5[PrefixDiffersFromSigningNameOperation, P] = new PolyFunction5[PrefixDiffersFromSigningNameOperation, P] { + def apply[I, E, O, SI, SO](op: PrefixDiffersFromSigningNameOperation[I, E, O, SI, SO]): P[I, E, O, SI, SO] = op.run(impl) + } + final case class DoThing(input: DoThingInput) extends PrefixDiffersFromSigningNameOperation[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] { + def run[F[_, _, _, _, _]](impl: PrefixDiffersFromSigningNameGen[F]): F[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = impl.doThing() + def ordinal: Int = 0 + def endpoint: smithy4s.Endpoint[PrefixDiffersFromSigningNameOperation,DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = DoThing + } + object DoThing extends smithy4s.Endpoint[PrefixDiffersFromSigningNameOperation,DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] { + val schema: OperationSchema[DoThingInput, Nothing, DoThingOutput, Nothing, Nothing] = Schema.operation(ShapeId("smithy4s.example.aws", "DoThing")) + .withInput(DoThingInput.schema) + .withOutput(DoThingOutput.schema) + def wrap(input: DoThingInput): DoThing = DoThing(input) + } +} + diff --git a/modules/bootstrapped/src/generated/smithy4s/example/aws/package.scala b/modules/bootstrapped/src/generated/smithy4s/example/aws/package.scala index a4687c845..6d1016ba6 100644 --- a/modules/bootstrapped/src/generated/smithy4s/example/aws/package.scala +++ b/modules/bootstrapped/src/generated/smithy4s/example/aws/package.scala @@ -1,8 +1,16 @@ package smithy4s.example package object aws { + type DottedPrefix[F[_]] = smithy4s.kinds.FunctorAlgebra[DottedPrefixGen, F] + val DottedPrefix = DottedPrefixGen type MyThing[F[_]] = smithy4s.kinds.FunctorAlgebra[MyThingGen, F] val MyThing = MyThingGen + type PrefixDiffersFromSigningName[F[_]] = smithy4s.kinds.FunctorAlgebra[PrefixDiffersFromSigningNameGen, F] + val PrefixDiffersFromSigningName = PrefixDiffersFromSigningNameGen + type NoEndpointPrefix[F[_]] = smithy4s.kinds.FunctorAlgebra[NoEndpointPrefixGen, F] + val NoEndpointPrefix = NoEndpointPrefixGen + type NoSigv4[F[_]] = smithy4s.kinds.FunctorAlgebra[NoSigv4Gen, F] + val NoSigv4 = NoSigv4Gen } \ No newline at end of file diff --git a/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala b/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala index 8759b5a2a..db6f4c9ad 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala @@ -1237,6 +1237,10 @@ private[codegen] class SmithyToIR( .filterNot(_.toShapeId().getNamespace() == "smithy.synthetic") // smithy.rules traits contain massive endpoint routing JSON and aren't used at runtime .filterNot(_.toShapeId().getNamespace() == "smithy.rules") + // aws.endpoints traits describe endpoint resolution, which we don't + // implement, and have no published Scala bindings - so referencing them + // in hints produces code that doesn't compile. + .filterNot(_.toShapeId().getNamespace() == "aws.endpoints") // enumValue can be derived from enum schemas anyway, so we're removing it from hints .filterNot(_.toShapeId() == EnumValueTrait.ID) // remove box trait diff --git a/sampleSpecs/aws_example.smithy b/sampleSpecs/aws_example.smithy index 37ad8f6d7..27ccb3436 100644 --- a/sampleSpecs/aws_example.smithy +++ b/sampleSpecs/aws_example.smithy @@ -4,3 +4,58 @@ namespace smithy4s.example.aws @aws.api#service(sdkId: "MyThing", endpointPrefix: "mything") service MyAwsService {} + +/// Mirrors the shape of AWS SES v2: the endpoint prefix ("email"), the ARN +/// namespace ("ses") and the sigv4 signing name ("ses") are all different from +/// each other, so the host and the credential scope must be resolved +/// independently. +@aws.auth#sigv4(name: "ses") +@aws.protocols#awsJson1_0 +@aws.api#service( + sdkId: "PrefixDiffersFromSigningName" + arnNamespace: "ses" + endpointPrefix: "email" +) +service PrefixDiffersFromSigningName { + operations: [DoThing] +} + +/// Mirrors the shape of AWS SageMaker (issue #1568): the endpoint prefix is +/// dotted and differs from the signing name, which matches the ARN namespace. +@aws.auth#sigv4(name: "sagemaker") +@aws.protocols#awsJson1_1 +@aws.api#service( + sdkId: "DottedPrefix" + arnNamespace: "sagemaker" + endpointPrefix: "api.sagemaker" +) +service DottedPrefix { + operations: [DoThing] +} + +/// Mirrors the shape of the AWS Account API (issue #1532): no endpoint prefix +/// at all, so both the host and the signing name fall back to the ARN +/// namespace. Before the fix, the host was derived from the *operation* name. +@aws.auth#sigv4(name: "account") +@aws.protocols#awsJson1_0 +@aws.api#service(sdkId: "NoEndpointPrefix", arnNamespace: "account") +service NoEndpointPrefix { + operations: [DoThing] +} + +/// A service with no sigv4 trait, to check that the signing name falls back to +/// the ARN namespace rather than to the endpoint prefix. +@aws.protocols#awsJson1_0 +@aws.api#service( + sdkId: "NoSigv4" + arnNamespace: "arnnamespace" + endpointPrefix: "endpointprefix" +) +service NoSigv4 { + operations: [DoThing] +} + +operation DoThing { + input := {} + output := {} +}