Skip to content

Add model bundle API #643

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 10, 2025
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
57 changes: 57 additions & 0 deletions model-bundler/bundle-api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
plugins {
application
id("smithy-java.module-conventions")
id("software.amazon.smithy.gradle.smithy-base")
}

description = "This module implements the model-bundler utility"

extra["displayName"] = "Smithy :: Java :: Model Bundler"
extra["moduleName"] = "software.amazon.smithy.java.modelbundle.api"

dependencies {
smithyBuild(project(":codegen:plugins:types-codegen"))

implementation(project(":core"))
implementation(libs.smithy.model)
api(project(":client:client-auth-api"))
api(project(":client:client-core"))
api(project(":dynamic-schemas"))
}

afterEvaluate {
val typePath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-type-codegen")
sourceSets {
main {
java {
srcDir(typePath)
include("software/**")
}
resources {
srcDir(typePath)
include("META-INF/**")
}
}
}
}

tasks.named("compileJava") {
dependsOn("smithyBuild")
}

// Needed because sources-jar needs to run after smithy-build is done
tasks.sourcesJar {
mustRunAfter("compileJava")
}

tasks.processResources {
dependsOn("compileJava")
}

sourceSets {
main {
java {
srcDir("model")
}
}
}
4 changes: 4 additions & 0 deletions model-bundler/bundle-api/license.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
14 changes: 14 additions & 0 deletions model-bundler/bundle-api/model/bundle.smithy
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
$version: "2"

namespace software.amazon.smithy.modelbundle.api

structure Bundle {
@required
configType: String

@required
serviceName: String

@required
config: Document
}
9 changes: 9 additions & 0 deletions model-bundler/bundle-api/smithy-build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"version": "1.0",
"plugins": {
"java-type-codegen": {
"namespace": "software.amazon.smithy.modelbundle.api",
"headerFile": "license.txt"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.modelbundle.api;

import static software.amazon.smithy.modelbundle.api.StaticAuthSchemeResolver.staticScheme;

import software.amazon.smithy.java.client.core.RequestOverrideConfig;
import software.amazon.smithy.java.client.core.auth.identity.IdentityResolver;
import software.amazon.smithy.java.client.core.auth.scheme.AuthScheme;
import software.amazon.smithy.java.client.core.endpoint.EndpointResolver;
import software.amazon.smithy.java.core.serde.document.Document;
import software.amazon.smithy.model.shapes.ShapeId;

/**
* A ConfigProvider is used to parse a bundle of service information (model, auth configuration, endpoints, etc.) and
* configure outgoing client calls as necessary.
*
* <p>Implementations of this interface can define a wrapper type that adds additional parameters to vended MCP tools.
* For example, an AWS auth provider can make a wrapper that adds the region and AWS credential profile name as
* arguments to tools generated for AWS APIs. A wrapper type does not need to be defined if no per-request parameters
* need to be injected.
*
* <p>The ConfigProvider is responsible for configuring outbound client calls with endpoint, identity, and auth resolver
* mechanisms. The default implementation of {@link #adaptConfig(T)} orchestrates the calls to all other ConfigProvider
* APIs and should not be overridden. If an override is needed, the {@code super} method should be called and the
* returned RequestOverrideConfig.Builder should be modified.
*
* @param <T> the type of configuration parsed by this ConfigProvider
*/
public interface ConfigProvider<T> {
/**
* Returns the ShapeId of the wrapper type that this config provider uses.
*
* @return this config provider's wrapper type, or {@code null} if it doesn't use a wrapper
*/
default ShapeId wrapperType() {
return null;
}

/**
* Parses the given document into this ConfigProvider's {@linkplain #wrapperType() wrapper type}.
* If this ConfigProvider has no wrapper type, this method returns null.
*
* @param input the document to parse
* @return the parsed wrapper type
*/
T parse(Document input);

/**
* Returns an identity resolver for the service being called, with optional values provided by the
* parsed wrapper (if present).
*
* @param args the {@linkplain #parse(Document) parsed data wrapper} containing provider-specific arguments
* @return an {@link IdentityResolver} that provides identity information
*/
IdentityResolver<?> identityResolver(T args);

/**
* Returns an auth scheme for the service being called, with optional values provided by the
* parsed wrapper (if present).
*
* @param args the {@linkplain #parse(Document) parsed data wrapper} containing provider-specific arguments
* @return an {@link AuthScheme} that implements the service's required auth mechanism
*/
AuthScheme<?, ?> authScheme(T args);

/**
* Returns an endpoint resolver for the service being called, with optional values provided by the
* parsed wrapper (if present).
*
* @param args the {@linkplain #parse(Document) parsed data wrapper} containing provider-specific arguments
* @return an {@link EndpointResolver} that provides the endpoint to call
*/
EndpointResolver endpointResolver(T args);

/**
* Adapts an outgoing request to use the {@linkplain #authScheme(Object) auth}, {@linkplain #identityResolver(Object) identity},
* and {@linkplain #endpointResolver(Object) endpoint} specified by this ConfigProvider.
*
* @param args the {@linkplain #parse(Document) parsed data wrapper} containing provider-specific arguments
* @return a fully-configured {@link RequestOverrideConfig.Builder} that can be used to make the request
*/
default RequestOverrideConfig.Builder adaptConfig(T args) {
return RequestOverrideConfig.builder()
.authSchemeResolver(StaticAuthSchemeResolver.INSTANCE)
.putSupportedAuthSchemes(staticScheme(authScheme(args)))
.addIdentityResolver(identityResolver(args))
.endpointResolver(endpointResolver(args));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.modelbundle.api;

import software.amazon.smithy.java.core.serde.document.Document;

public interface ConfigProviderFactory {
String identifier();

ConfigProvider<?> createAuthFactory(Document input);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.modelbundle.api;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import software.amazon.smithy.java.core.serde.document.Document;

public final class ConfigProviders {
private static final Map<String, ConfigProviderFactory> PROVIDERS;

static {
Map<String, ConfigProviderFactory> providers = new HashMap<>();
for (var provider : ServiceLoader.load(ConfigProviderFactory.class)) {
providers.put(provider.identifier(), provider);
}
PROVIDERS = Collections.unmodifiableMap(providers);
}

private final Map<String, ConfigProviderFactory> providers;

public ConfigProviders(Builder builder) {
this.providers = builder.providers;
}

public ConfigProvider getProvider(String identifier, Document input) {
var provider = providers.get(identifier);
if (provider == null) {
throw new NullPointerException("no auth provider named " + identifier);
}

return provider.createAuthFactory(input);
}

public static Builder builder() {
return new Builder();
}

public static final class Builder {
private final Map<String, ConfigProviderFactory> providers = new HashMap<>(PROVIDERS);

private Builder() {

}

public Builder addProvider(ConfigProviderFactory provider) {
providers.put(provider.identifier(), provider);
return this;
}

public ConfigProviders build() {
return new ConfigProviders(this);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.modelbundle.api;

import java.util.List;
import software.amazon.smithy.java.auth.api.AuthProperties;
import software.amazon.smithy.java.auth.api.Signer;
import software.amazon.smithy.java.auth.api.identity.Identity;
import software.amazon.smithy.java.client.core.auth.identity.IdentityResolver;
import software.amazon.smithy.java.client.core.auth.identity.IdentityResolvers;
import software.amazon.smithy.java.client.core.auth.scheme.AuthScheme;
import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeOption;
import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeResolver;
import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeResolverParams;
import software.amazon.smithy.java.context.Context;
import software.amazon.smithy.model.shapes.ShapeId;

final class StaticAuthSchemeResolver implements AuthSchemeResolver {
static final StaticAuthSchemeResolver INSTANCE = new StaticAuthSchemeResolver();
static final ShapeId CONFIGURED_AUTH = ShapeId.from("modelbundle#configuredAuth");
private static final List<AuthSchemeOption> AUTH_SCHEME_OPTION = List.of(new AuthSchemeOption(CONFIGURED_AUTH));

@Override
public List<AuthSchemeOption> resolveAuthScheme(AuthSchemeResolverParams params) {
return AUTH_SCHEME_OPTION;
}

static <RequestT, IdentityT extends Identity> AuthScheme<RequestT, IdentityT> staticScheme(
AuthScheme<RequestT, IdentityT> actual
) {
return new AuthScheme<>() {
@Override
public ShapeId schemeId() {
return StaticAuthSchemeResolver.CONFIGURED_AUTH;
}

@Override
public Class<RequestT> requestClass() {
return actual.requestClass();
}

@Override
public Class<IdentityT> identityClass() {
return actual.identityClass();
}

@Override
public Signer<RequestT, IdentityT> signer() {
return actual.signer();
}

@Override
public IdentityResolver<IdentityT> identityResolver(IdentityResolvers resolvers) {
return actual.identityResolver(resolvers);
}

@Override
public AuthProperties getSignerProperties(Context context) {
return actual.getSignerProperties(context);
}

@Override
public AuthProperties getIdentityProperties(Context context) {
return actual.getIdentityProperties(context);
}
};
}
}
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,5 @@ include(":mcp")
include(":mcp:mcp-schemas")
include(":server:server-mcp")


include(":model-bundler:bundle-api")