Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@
// Nested default values are not populated by the document path when missing from the wire
// (SchemaGuidedDocumentBuilder.errorCorrection is a no-op).
"RestJsonClientPopulatesNestedDefaultsWhenMissingInResponseBody [dynamic]",
"RestJsonClientPopulatesNestedDefaultValuesWhenMissing [dynamic]"
"RestJsonClientPopulatesNestedDefaultValuesWhenMissing [dynamic]",
// Event-stream initial-response comparison: the harness compares the full response StructDocument
// via AssertJ's recursive comparator, which can't introspect StructDocument's internals and reports
// a top-level difference. The event decoding itself works; this is a harness-side comparator gap.
"InitialResponseOutput [dynamic]",
"DuplexInitialResponseOutput [dynamic]"
})
public class RestJson1ProtocolTests {
private static final String EMPTY_BODY = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.concurrent.Executors;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

Expand Down Expand Up @@ -314,6 +315,9 @@ void testWithUnsupportedAuth() {

@Test
void testWithSigV4() {
// The Sprockets service models no auth, so its operations' effective auth scheme is noAuth. Even though
// --auth sigv4 registers the SigV4 scheme, the resolver picks the operation's effective scheme (noAuth),
// matching a code-generated client. The call therefore succeeds without signing (no Authorization header).
Path modelDir = createSprocketsModelFile();
String[] args = {
"smithy.example#Sprockets",
Expand All @@ -327,9 +331,9 @@ void testWithSigV4() {
};

int exitCode = commandLine.execute(args);
assertTrue(exitCode != 0);
String error = errContent.toString();
assertTrue(error.contains("No auth scheme could be resolved for operation"));
assertEquals(0, exitCode, "Expected noAuth to be used for a service that models no auth. stderr: "
+ errContent);
assertNull(lastAuthorizationHeader, "No SigV4 signing should occur when the effective scheme is noAuth");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,14 @@
*
* <ul>
* <li>No code generated types. You have to construct input and use output manually using document APIs.</li>
* <li>No support for streaming inputs or outputs.</li>
* <li>All errors are created as an {@link DocumentException} if the error is modeled, allowing document access
* to the modeled error contents. Other errors are deserialized as {@link CallException}.
* </ul>
*
* <p>Streaming blob members and event streams are supported. A streaming member is carried on the input or output
* document as a {@code DataStream} or {@code EventStream} value (see {@link Document#of(software.amazon.smithy.java
* .core.schema.Schema, software.amazon.smithy.java.io.datastream.DataStream)} and the {@code EventStream} overload),
* rather than being buffered into a finite document.
*/
public final class DynamicClient extends Client {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

package software.amazon.smithy.java.dynamicclient;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -144,10 +143,10 @@ public static DynamicOperation create(
) {
var operationSchema = schemaConverter.getSchema(shape);

List<ShapeId> authSchemes = new ArrayList<>();
for (var trait : ServiceIndex.of(model).getEffectiveAuthSchemes(service).values()) {
authSchemes.add(trait.toShapeId());
}
var authSchemes = List.copyOf(
ServiceIndex.of(model)
.getEffectiveAuthSchemes(service, shape, ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE)
.keySet());

var inputSchema = schemaConverter.getSchema(model.expectShape(shape.getInputShape()));
var outputSchema = schemaConverter.getSchema(model.expectShape(shape.getOutputShape()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void configureClient(ClientConfig.Builder config) {
@SuppressWarnings("unchecked")
private void injectAuthSchemeResolver(ClientConfig.Builder config, Model model, ShapeId service) {
var index = ServiceIndex.of(model);
var potentialAuthSchemes = index.getEffectiveAuthSchemes(service);
var potentialAuthSchemes = index.getAuthSchemes(service);
if (potentialAuthSchemes.isEmpty()) {
config.authSchemeResolver(AuthSchemeResolver.NO_AUTH);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.ShapeType;
import software.amazon.smithy.model.traits.DeprecatedTrait;
import software.amazon.smithy.model.traits.HttpBasicAuthTrait;
import software.amazon.smithy.model.traits.HttpBearerAuthTrait;
import software.amazon.smithy.model.traits.synthetic.NoAuthTrait;

public class DynamicOperationTest {
@Test
Expand Down Expand Up @@ -222,6 +225,61 @@ public void createsBidirectionalEventStreamingOperation() {
assertThat(op.outputEventBuilderSupplier(), is(notNullValue()));
}

@Test
public void resolvesEffectiveAuthSchemesPerOperation() {
Model model = Model.assembler()
.addUnparsedModel("test.smithy", """
$version: "2"

namespace smithy.example

@httpBasicAuth
@httpBearerAuth
@auth([httpBasicAuth])
service S {
operations: [InheritedAuth, OverriddenAuth, NoAuth, OptionalAuth]
}

operation InheritedAuth {
input := {}
output := {}
}

@auth([httpBearerAuth])
operation OverriddenAuth {
input := {}
output := {}
}

@auth([])
operation NoAuth {
input := {}
output := {}
}

@optionalAuth
operation OptionalAuth {
input := {}
output := {}
}
""")
.assemble()
.unwrap();

assertThat(
createOperation(model, "InheritedAuth").effectiveAuthSchemes(),
equalTo(List.of(HttpBasicAuthTrait.ID)));
assertThat(
createOperation(model, "OverriddenAuth").effectiveAuthSchemes(),
equalTo(List.of(HttpBearerAuthTrait.ID)));
assertThat(
createOperation(model, "NoAuth").effectiveAuthSchemes(),
equalTo(List.of(NoAuthTrait.ID)));
assertThat(
createOperation(model, "OptionalAuth").effectiveAuthSchemes(),
equalTo(List.of(HttpBasicAuthTrait.ID, NoAuthTrait.ID)));
}

@Test
public void convertsSchemas() {
Model model = Model.assembler()
Expand Down Expand Up @@ -267,4 +325,17 @@ public void convertsSchemas() {
assertThat(o.errorRegistry(), is(registry));
assertThat(o.effectiveAuthSchemes(), empty());
}

private static DynamicOperation createOperation(Model model, String operationName) {
var converter = new SchemaConverter(model);
var service = model.expectShape(ShapeId.from("smithy.example#S")).asServiceShape().get();
var operation = model.expectShape(ShapeId.from("smithy.example#" + operationName)).asOperationShape().get();
return DynamicOperation.create(
operation,
converter,
model,
service,
TypeRegistry.empty(),
(id, b) -> {});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import software.amazon.smithy.java.endpoints.EndpointResolver;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.HttpBasicAuthTrait;

class SimpleAuthDetectionPluginTest {

Expand All @@ -31,10 +32,13 @@ void registersAuthSchemeFactoriesForServiceAuthTraits() {

@awsJson1_0
@sigv4(name: "testservice")
@httpBasicAuth
@auth([sigv4])
service AuthService {
operations: [DoThing]
}

@auth([httpBasicAuth])
operation DoThing {
input := {}
output := {}
Expand All @@ -52,7 +56,9 @@ void registersAuthSchemeFactoriesForServiceAuthTraits() {

var authSchemes = client.config().supportedAuthSchemes();
var hasSigV4 = authSchemes.stream().anyMatch(s -> s.schemeId().equals(SigV4Trait.ID));
var hasHttpBasic = authSchemes.stream().anyMatch(s -> s.schemeId().equals(HttpBasicAuthTrait.ID));
assertThat("Expected SigV4 auth scheme to be registered", hasSigV4, is(true));
assertThat("Expected HTTP basic auth scheme to be registered", hasHttpBasic, is(true));
assertThat(client.config().authSchemeResolver(), is(AuthSchemeResolver.DEFAULT));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ private SchemaBuilder getOrCreateRecursiveSchemaBuilder(Shape shape, Set<Shape>
case UNION -> Schema.unionBuilder(schemaId(shape), convertTraits(shape));
default -> throw new UnsupportedOperationException("Expected aggregate shape: " + shape);
};
// Attach a shape-builder supplier so runtime consumers that call Schema#shapeBuilder() on a
// dynamic struct/union schema get a schema-guided document builder, matching how codegen schemas
// attach a builder for their generated POJO. Without this, code paths like the AWS event-stream
// decoder (which constructs each event variant via memberTarget().shapeBuilder()) throw
// "Schema does not have a shape builder" on the dynamic path.
if (shape.getType() == ShapeType.STRUCTURE || shape.getType() == ShapeType.UNION) {
final Shape captured = shape;
builder.builderSupplier(() -> createDocumentBuilder(getSchema(captured)));
}
SchemaBuilder previous = recursiveBuilders.putIfAbsent(shape, builder);
if (previous != null) {
builder = previous;
Expand Down
Loading
Loading