diff --git a/codegen-client-test/build.gradle.kts b/codegen-client-test/build.gradle.kts index f3796a6911f13e90481d561a6c855e6af2de5df0..3709d4e594bbcf53dcf7a740cf5a127e79249cbf 100644 --- a/codegen-client-test/build.gradle.kts +++ b/codegen-client-test/build.gradle.kts @@ -93,8 +93,8 @@ val allCodegenTests = "../codegen-core/common-test-models".let { commonModels -> imports = listOf("$commonModels/naming-obstacle-course-structs.smithy"), ), CodegenTest("aws.protocoltests.json#TestService", "endpoint-rules"), - CodegenTest("com.aws.example.rust#PokemonService", "pokemon-service-client", imports = listOf("$commonModels/pokemon.smithy", "$commonModels/pokemon-common.smithy")), - CodegenTest("com.aws.example.rust#PokemonService", "pokemon-service-awsjson-client", imports = listOf("$commonModels/pokemon-awsjson.smithy", "$commonModels/pokemon-common.smithy")), + CodegenTest("com.aws.example#PokemonService", "pokemon-service-client", imports = listOf("$commonModels/pokemon.smithy", "$commonModels/pokemon-common.smithy")), + CodegenTest("com.aws.example#PokemonService", "pokemon-service-awsjson-client", imports = listOf("$commonModels/pokemon-awsjson.smithy", "$commonModels/pokemon-common.smithy")), ) } diff --git a/codegen-core/common-test-models/pokemon-awsjson.smithy b/codegen-core/common-test-models/pokemon-awsjson.smithy index 7d3a9ae02183a6e5d03f5cb36f011c9cc6e290c9..12e455ecdd07644a19a45692a60174b033f4539e 100644 --- a/codegen-core/common-test-models/pokemon-awsjson.smithy +++ b/codegen-core/common-test-models/pokemon-awsjson.smithy @@ -4,7 +4,7 @@ $version: "1.0" // This is a temporary model to test AwsJson 1.0 with @streaming. // This model will be removed when protocol tests support @streaming. -namespace com.aws.example.rust +namespace com.aws.example use aws.protocols#awsJson1_0 use smithy.framework#ValidationException diff --git a/codegen-core/common-test-models/pokemon.smithy b/codegen-core/common-test-models/pokemon.smithy index 20e56e381aa9f3dc4e757491ecb0488bc417c8c8..745f51d93c7cc42ecb8243f3d347da562fee8c1f 100644 --- a/codegen-core/common-test-models/pokemon.smithy +++ b/codegen-core/common-test-models/pokemon.smithy @@ -1,6 +1,6 @@ $version: "1.0" -namespace com.aws.example.rust +namespace com.aws.example use aws.protocols#restJson1 use smithy.framework#ValidationException @@ -20,7 +20,8 @@ service PokemonService { GetServerStatistics, DoNothing, CapturePokemon, - CheckHealth + CheckHealth, + StreamPokemonRadio ], } @@ -146,3 +147,19 @@ structure MasterBallUnsuccessful { @error("client") structure ThrottlingError {} + +/// Fetch a radio song from the database and stream it back as a playable audio. +@readonly +@http(uri: "/radio", method: "GET") +operation StreamPokemonRadio { + output: StreamPokemonRadioOutput +} + +@output +structure StreamPokemonRadioOutput { + @httpPayload + data: StreamingBlob +} + +@streaming +blob StreamingBlob \ No newline at end of file diff --git a/codegen-server-test/build.gradle.kts b/codegen-server-test/build.gradle.kts index 2dbeebca0a2f12ce19d242930c4cfb7c19ed0eaf..c4b2f00dbad7992785e0064d1db0aa8490591ea5 100644 --- a/codegen-server-test/build.gradle.kts +++ b/codegen-server-test/build.gradle.kts @@ -84,12 +84,12 @@ val allCodegenTests = "../codegen-core/common-test-models".let { commonModels -> CodegenTest("com.amazonaws.ebs#Ebs", "ebs", imports = listOf("$commonModels/ebs.json")), CodegenTest("com.amazonaws.s3#AmazonS3", "s3"), CodegenTest( - "com.aws.example.rust#PokemonService", + "com.aws.example#PokemonService", "pokemon-service-server-sdk", imports = listOf("$commonModels/pokemon.smithy", "$commonModels/pokemon-common.smithy"), ), CodegenTest( - "com.aws.example.rust#PokemonService", + "com.aws.example#PokemonService", "pokemon-service-awsjson-server-sdk", imports = listOf("$commonModels/pokemon-awsjson.smithy", "$commonModels/pokemon-common.smithy"), ), diff --git a/codegen-server-test/python/build.gradle.kts b/codegen-server-test/python/build.gradle.kts index e70e2b0e5d6b27ad25889db62f57f73de131f54f..423d8a4619a8d9c7f117bb2ee3c913783cda064e 100644 --- a/codegen-server-test/python/build.gradle.kts +++ b/codegen-server-test/python/build.gradle.kts @@ -41,7 +41,11 @@ dependencies { val allCodegenTests = "../../codegen-core/common-test-models".let { commonModels -> listOf( CodegenTest("com.amazonaws.simple#SimpleService", "simple", imports = listOf("$commonModels/simple.smithy")), - CodegenTest("com.aws.example.python#PokemonService", "pokemon-service-server-sdk"), + CodegenTest( + "com.aws.example#PokemonService", + "pokemon-service-server-sdk", + imports = listOf("$commonModels/pokemon.smithy", "$commonModels/pokemon-common.smithy"), + ), CodegenTest( "com.amazonaws.ebs#Ebs", "ebs", imports = listOf("$commonModels/ebs.json"), diff --git a/codegen-server-test/python/model/pokemon-common.smithy b/codegen-server-test/python/model/pokemon-common.smithy deleted file mode 120000 index 31ad0d9f44228b0e41abeb216f532196b4359f05..0000000000000000000000000000000000000000 --- a/codegen-server-test/python/model/pokemon-common.smithy +++ /dev/null @@ -1 +0,0 @@ -../../../codegen-core/common-test-models/pokemon-common.smithy \ No newline at end of file diff --git a/codegen-server-test/python/model/pokemon.smithy b/codegen-server-test/python/model/pokemon.smithy deleted file mode 100644 index b019d183a20c62ee80bee4bc6312313b83b347ed..0000000000000000000000000000000000000000 --- a/codegen-server-test/python/model/pokemon.smithy +++ /dev/null @@ -1,126 +0,0 @@ -/// TODO(https://github.com/awslabs/smithy-rs/issues/1508) -/// Reconcile this model with the main one living inside codegen-server-test/model/pokemon.smithy -/// once the Python implementation supports Streaming and Union shapes. -$version: "1.0" - -namespace com.aws.example.python - -use aws.protocols#restJson1 -use com.aws.example#CheckHealth -use com.aws.example#DoNothing -use com.aws.example#GetServerStatistics -use com.aws.example#PokemonSpecies -use com.aws.example#Storage -use smithy.framework#ValidationException - -/// The Pokémon Service allows you to retrieve information about Pokémon species. -@title("Pokémon Service") -@restJson1 -service PokemonService { - version: "2021-12-01" - resources: [PokemonSpecies] - operations: [ - GetServerStatistics - DoNothing - CapturePokemon - CheckHealth - StreamPokemonRadio - ] -} - -/// Capture Pokémons via event streams. -@http(uri: "/capture-pokemon-event/{region}", method: "POST") -operation CapturePokemon { - input: CapturePokemonEventsInput - output: CapturePokemonEventsOutput - errors: [ - UnsupportedRegionError - ThrottlingError - ValidationException - ] -} - -@input -structure CapturePokemonEventsInput { - @httpPayload - events: AttemptCapturingPokemonEvent - @httpLabel - @required - region: String -} - -@output -structure CapturePokemonEventsOutput { - @httpPayload - events: CapturePokemonEvents -} - -@streaming -union AttemptCapturingPokemonEvent { - event: CapturingEvent - masterball_unsuccessful: MasterBallUnsuccessful -} - -structure CapturingEvent { - @eventPayload - payload: CapturingPayload -} - -structure CapturingPayload { - name: String - pokeball: String -} - -@streaming -union CapturePokemonEvents { - event: CaptureEvent - invalid_pokeball: InvalidPokeballError - throttlingError: ThrottlingError -} - -structure CaptureEvent { - @eventHeader - name: String - @eventHeader - captured: Boolean - @eventHeader - shiny: Boolean - @eventPayload - pokedex_update: Blob -} - -@error("server") -structure UnsupportedRegionError { - @required - region: String -} - -@error("client") -structure InvalidPokeballError { - @required - pokeball: String -} - -@error("server") -structure MasterBallUnsuccessful { - message: String -} - -@error("client") -structure ThrottlingError {} - -/// Fetch the radio song from the database and stream it back as a playable audio. -@readonly -@http(uri: "/radio", method: "GET") -operation StreamPokemonRadio { - output: StreamPokemonRadioOutput -} - -@output -structure StreamPokemonRadioOutput { - @httpPayload - data: StreamingBlob -} - -@streaming -blob StreamingBlob diff --git a/examples/pokemon-service-common/Cargo.toml b/examples/pokemon-service-common/Cargo.toml index 704055c813257367b0b74fa849df3a3dce28c206..d0012e178b2645c5217494a63719e17098eddfa8 100644 --- a/examples/pokemon-service-common/Cargo.toml +++ b/examples/pokemon-service-common/Cargo.toml @@ -13,12 +13,11 @@ rand = "0.8" tracing = "0.1" tracing-subscriber = { version = "0.3.16", features = ["env-filter", "json"] } tokio = { version = "1", default-features = false, features = ["time"] } +tower = "0.4" # Local paths +aws-smithy-client = { path = "../../rust-runtime/aws-smithy-client" } aws-smithy-http = { path = "../../rust-runtime/aws-smithy-http" } aws-smithy-http-server = { path = "../../rust-runtime/aws-smithy-http-server" } pokemon-service-client = { path = "../pokemon-service-client" } pokemon-service-server-sdk = { path = "../pokemon-service-server-sdk" } - -[dev-dependencies] -tower = "0.4" diff --git a/examples/pokemon-service-common/src/lib.rs b/examples/pokemon-service-common/src/lib.rs index 0658151a05817b34b10fcde3bab09b3bd2b2f5da..d8618e0cce38bd3394af158bd2afb3bfc7501dfc 100644 --- a/examples/pokemon-service-common/src/lib.rs +++ b/examples/pokemon-service-common/src/lib.rs @@ -15,7 +15,8 @@ use std::{ }; use async_stream::stream; -use aws_smithy_http::operation::Request; +use aws_smithy_client::{conns, hyper_ext::Adapter}; +use aws_smithy_http::{body::SdkBody, byte_stream::ByteStream, operation::Request}; use aws_smithy_http_server::Extension; use http::{ uri::{Authority, Scheme}, @@ -24,7 +25,8 @@ use http::{ use pokemon_service_server_sdk::{ error, input, model, model::CapturingPayload, output, types::Blob, }; -use rand::Rng; +use rand::{seq::SliceRandom, Rng}; +use tower::Service; use tracing_subscriber::{prelude::*, EnvFilter}; const PIKACHU_ENGLISH_FLAVOR_TEXT: &str = @@ -327,6 +329,37 @@ pub async fn check_health(_input: input::CheckHealthInput) -> output::CheckHealt output::CheckHealthOutput {} } +const RADIO_STREAMS: [&str; 2] = [ + "https://ia800107.us.archive.org/33/items/299SoundEffectCollection/102%20Palette%20Town%20Theme.mp3", + "https://ia600408.us.archive.org/29/items/PocketMonstersGreenBetaLavenderTownMusicwwwFlvtoCom/Pocket%20Monsters%20Green%20Beta-%20Lavender%20Town%20Music-%5Bwww_flvto_com%5D.mp3", +]; + +/// Streams a random Pokémon song. +pub async fn stream_pokemon_radio( + _input: input::StreamPokemonRadioInput, +) -> output::StreamPokemonRadioOutput { + let radio_stream_url = RADIO_STREAMS + .choose(&mut rand::thread_rng()) + .expect("`RADIO_STREAMS` is empty") + .parse::() + .expect("Invalid url in `RADIO_STREAMS`"); + + let mut connector = Adapter::builder().build(conns::https()); + let result = connector + .call( + http::Request::builder() + .uri(radio_stream_url) + .body(SdkBody::empty()) + .unwrap(), + ) + .await + .unwrap(); + + output::StreamPokemonRadioOutput { + data: ByteStream::new(result.into_body()), + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/examples/pokemon-service-lambda/src/main.rs b/examples/pokemon-service-lambda/src/main.rs index d81034d27e88e30ad39c0fc9c36a03518c66592a..2f08e072b56c1c7bebbf80a8b5acef305c0a5af1 100644 --- a/examples/pokemon-service-lambda/src/main.rs +++ b/examples/pokemon-service-lambda/src/main.rs @@ -9,7 +9,7 @@ use aws_smithy_http_server::{routing::LambdaHandler, AddExtensionLayer}; use pokemon_service_common::{ capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics, - setup_tracing, State, + setup_tracing, stream_pokemon_radio, State, }; use pokemon_service_lambda::get_storage_lambda; use pokemon_service_server_sdk::PokemonService; @@ -28,6 +28,7 @@ pub async fn main() { .capture_pokemon(capture_pokemon) .do_nothing(do_nothing) .check_health(check_health) + .stream_pokemon_radio(stream_pokemon_radio) .build() .expect("failed to build an instance of PokemonService") // Set up shared state and middlewares. diff --git a/examples/pokemon-service-tls/src/main.rs b/examples/pokemon-service-tls/src/main.rs index 2a9ef679d2e9e66903219e1f35fe2de54b06b396..a67d145042273e1b43544001768a2e96655cde69 100644 --- a/examples/pokemon-service-tls/src/main.rs +++ b/examples/pokemon-service-tls/src/main.rs @@ -34,7 +34,7 @@ use tokio_rustls::{ use pokemon_service_common::{ capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics, - get_storage, setup_tracing, State, + get_storage, setup_tracing, stream_pokemon_radio, State, }; use pokemon_service_server_sdk::PokemonService; use pokemon_service_tls::{DEFAULT_ADDRESS, DEFAULT_PORT, DEFAULT_TEST_CERT, DEFAULT_TEST_KEY}; @@ -71,6 +71,7 @@ pub async fn main() { .capture_pokemon(capture_pokemon) .do_nothing(do_nothing) .check_health(check_health) + .stream_pokemon_radio(stream_pokemon_radio) .build() .expect("failed to build an instance of PokemonService") // Set up shared state and middlewares. diff --git a/examples/pokemon-service/src/main.rs b/examples/pokemon-service/src/main.rs index 7a865abfafdfe8a3b0b582ebf30cc9f6380e80ce..db7878995b53176e6927ba755fd4e233d05d605b 100644 --- a/examples/pokemon-service/src/main.rs +++ b/examples/pokemon-service/src/main.rs @@ -23,7 +23,8 @@ use pokemon_service::{ do_nothing_but_log_request_ids, get_storage_with_local_approved, DEFAULT_ADDRESS, DEFAULT_PORT, }; use pokemon_service_common::{ - capture_pokemon, check_health, get_pokemon_species, get_server_statistics, setup_tracing, State, + capture_pokemon, check_health, get_pokemon_species, get_server_statistics, setup_tracing, + stream_pokemon_radio, State, }; use pokemon_service_server_sdk::PokemonService; @@ -67,6 +68,7 @@ pub async fn main() { .capture_pokemon(capture_pokemon) .do_nothing(do_nothing_but_log_request_ids) .check_health(check_health) + .stream_pokemon_radio(stream_pokemon_radio) .build() .expect("failed to build an instance of PokemonService"); diff --git a/examples/python/Makefile b/examples/python/Makefile index 56213fe28989aa7f1c5caea0fd7007e423b94945..74c63f05a8402366c96e3cf7d94f958f2c8fb6ce 100644 --- a/examples/python/Makefile +++ b/examples/python/Makefile @@ -45,7 +45,10 @@ release: codegen $(MAKE) generate-stubs $(MAKE) build-wheel-release -run: build +run: build install-wheel + python3 $(CUR_DIR)/pokemon_service.py + +run-release: release install-wheel python3 $(CUR_DIR)/pokemon_service.py py-check: install-wheel diff --git a/examples/python/pokemon_service.py b/examples/python/pokemon_service.py index 7ee4a7284fc705646e99ead205d5122ee8c90682..eeb6445eb3d3fd0f613dd875326f3e26e0cb3b03 100644 --- a/examples/python/pokemon_service.py +++ b/examples/python/pokemon_service.py @@ -17,8 +17,10 @@ from pokemon_service_server_sdk.error import ( MasterBallUnsuccessful, ResourceNotFoundException, UnsupportedRegionError, + StorageAccessNotAuthorized, ) from pokemon_service_server_sdk.input import ( + GetStorageInput, CapturePokemonInput, CheckHealthInput, DoNothingInput, @@ -28,8 +30,14 @@ from pokemon_service_server_sdk.input import ( ) from pokemon_service_server_sdk.logging import TracingHandler from pokemon_service_server_sdk.middleware import MiddlewareException, Request, Response -from pokemon_service_server_sdk.model import CaptureEvent, CapturePokemonEvents, FlavorText, Language +from pokemon_service_server_sdk.model import ( + CaptureEvent, + CapturePokemonEvents, + FlavorText, + Language, +) from pokemon_service_server_sdk.output import ( + GetStorageOutput, CapturePokemonOutput, CheckHealthOutput, DoNothingOutput, @@ -164,7 +172,11 @@ async def check_content_type_header(request: Request, next: Next) -> Response: if content_type == "application/json": logging.debug("found valid `application/json` content type") else: - logging.warning("invalid content type %s, dumping headers: %s", content_type, request.headers.items()) + logging.warning( + "invalid content type %s, dumping headers: %s", + content_type, + request.headers.items(), + ) return await next(request) @@ -198,9 +210,24 @@ def do_nothing(_: DoNothingInput) -> DoNothingOutput: return DoNothingOutput() +# Retrieves the user's storage. +@app.get_storage +def get_storage(input: GetStorageInput) -> GetStorageOutput: + logging.debug("attempting to authenticate storage user") + + # We currently only support Ash and he has nothing stored + if input.user != "ash" or input.passcode != "pikachu123": + logging.debug("authentication failed") + raise StorageAccessNotAuthorized() + + return GetStorageOutput([]) + + # Get the translation of a Pokémon specie or an error. @app.get_pokemon_species -def get_pokemon_species(input: GetPokemonSpeciesInput, context: Context) -> GetPokemonSpeciesOutput: +def get_pokemon_species( + input: GetPokemonSpeciesInput, context: Context +) -> GetPokemonSpeciesOutput: if context.lambda_ctx is not None: logging.debug( "lambda Context: %s", @@ -218,7 +245,9 @@ def get_pokemon_species(input: GetPokemonSpeciesInput, context: Context) -> GetP if flavor_text_entries: logging.debug("total requests executed: %s", context.get_calls_count()) logging.info("found description for Pokémon %s", input.name) - return GetPokemonSpeciesOutput(name=input.name, flavor_text_entries=flavor_text_entries) + return GetPokemonSpeciesOutput( + name=input.name, flavor_text_entries=flavor_text_entries + ) else: logging.warning("description for Pokémon %s not in the database", input.name) raise ResourceNotFoundException("Requested Pokémon not available") @@ -226,7 +255,9 @@ def get_pokemon_species(input: GetPokemonSpeciesInput, context: Context) -> GetP # Get the number of requests served by this server. @app.get_server_statistics -def get_server_statistics(_: GetServerStatisticsInput, context: Context) -> GetServerStatisticsOutput: +def get_server_statistics( + _: GetServerStatisticsInput, context: Context +) -> GetServerStatisticsOutput: calls_count = context.get_calls_count() logging.debug("the service handled %d requests", calls_count) return GetServerStatisticsOutput(calls_count=calls_count) @@ -393,7 +424,9 @@ def capture_pokemon(input: CapturePokemonInput) -> CapturePokemonOutput: # Stream a random Pokémon song. @app.stream_pokemon_radio -async def stream_pokemon_radio(_: StreamPokemonRadioInput, context: Context) -> StreamPokemonRadioOutput: +async def stream_pokemon_radio( + _: StreamPokemonRadioInput, context: Context +) -> StreamPokemonRadioOutput: import aiohttp radio_url = context.get_random_radio_stream()