diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml index d8af636504..fc44ba70e6 100644 --- a/.github/workflows/manual.yml +++ b/.github/workflows/manual.yml @@ -35,4 +35,4 @@ jobs: manual-ci-go: uses: ./.github/workflows/test_models_go_tests.yml with: - dafny: ${{ inputs.dafny }} \ No newline at end of file + dafny: ${{ inputs.dafny }} diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index f2d599274d..2f164cb8c2 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -76,4 +76,4 @@ jobs: dafny-version: ${{ fromJson(needs.pr-populate-dafny-versions.outputs.only-new-dafny-version-list) }} uses: ./.github/workflows/test_models_go_tests.yml with: - dafny: ${{ matrix.dafny-version }} \ No newline at end of file + dafny: ${{ matrix.dafny-version }} diff --git a/.github/workflows/test_models_go_tests.yml b/.github/workflows/test_models_go_tests.yml index bccce7d1df..946630e1a9 100644 --- a/.github/workflows/test_models_go_tests.yml +++ b/.github/workflows/test_models_go_tests.yml @@ -58,7 +58,7 @@ jobs: - name: Install Go uses: actions/setup-go@v2 with: - go-version: '1.21' + go-version: "1.21" - name: Install Go imports run: | @@ -67,10 +67,10 @@ jobs: - name: Add go mod to Dafny Runtime Go shell: bash working-directory: ./DafnyRuntimeGo - run: | + run: | go mod init github.com/dafny-lang/DafnyRuntimeGo go mod tidy - + - name: Setup Java 17 for codegen uses: actions/setup-java@v3 with: @@ -91,7 +91,7 @@ jobs: with: arguments: :smithy-dafny-codegen:pTML build-root-directory: codegen - + - name: Execute smithy-dafny-codegen-test tests uses: gradle/gradle-build-action@v2 env: @@ -99,4 +99,4 @@ jobs: JUNIT_SHARD_COUNT: ${{ inputs.num_shards }} with: arguments: :smithy-dafny-codegen-test:test --tests '*smithygo*' --info - build-root-directory: codegen \ No newline at end of file + build-root-directory: codegen diff --git a/codegen/smithy-dafny-codegen-cli/src/main/java/software/amazon/polymorph/CodegenCli.java b/codegen/smithy-dafny-codegen-cli/src/main/java/software/amazon/polymorph/CodegenCli.java index 253f589759..4e12ab1308 100644 --- a/codegen/smithy-dafny-codegen-cli/src/main/java/software/amazon/polymorph/CodegenCli.java +++ b/codegen/smithy-dafny-codegen-cli/src/main/java/software/amazon/polymorph/CodegenCli.java @@ -21,13 +21,11 @@ import org.apache.commons.cli.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.loader.ModelAssembler; - import software.amazon.polymorph.CodegenEngine.TargetLanguage; import software.amazon.polymorph.smithydafny.DafnyVersion; import software.amazon.polymorph.smithyjava.generator.CodegenSubject.AwsSdkVersion; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.loader.ModelAssembler; import software.amazon.smithy.model.validation.ValidatedResult; import software.amazon.smithy.model.validation.ValidationEvent; @@ -235,12 +233,12 @@ private static Options getCliOptionsForBuild() { .build() ) .addOption( - Option - .builder() - .longOpt("output-go") - .desc(" output directory for generated Go files") - .hasArg() - .build() + Option + .builder() + .longOpt("output-go") + .desc(" output directory for generated Go files") + .hasArg() + .build() ) .addOption( Option @@ -354,8 +352,7 @@ private static Options getCliOptionsForBuild() { .valueSeparator(',') .build() ); - } - + } private static Options getCliOptionsForPatchAfterTranspile() { return new Options() @@ -557,8 +554,8 @@ static Optional parse(String[] args) throws ParseException { .ofNullable(commandLine.getOptionValue("output-dotnet")) .map(Paths::get); final Optional outputGoDir = Optional - .ofNullable(commandLine.getOptionValue("output-go")) - .map(Paths::get); + .ofNullable(commandLine.getOptionValue("output-go")) + .map(Paths::get); final Optional outputRustDir = Optional .ofNullable(commandLine.getOptionValue("output-rust")) .map(Paths::get); diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/CodegenEngine.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/CodegenEngine.java index 3d02397833..e3a234f9f3 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/CodegenEngine.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/CodegenEngine.java @@ -43,6 +43,7 @@ import software.amazon.polymorph.smithydotnet.localServiceWrapper.LocalServiceWrappedConversionCodegen; import software.amazon.polymorph.smithydotnet.localServiceWrapper.LocalServiceWrappedShimCodegen; import software.amazon.polymorph.smithygo.awssdk.DafnyGoAwsSdkClientCodegenPlugin; +import software.amazon.polymorph.smithygo.localservice.DafnyLocalServiceCodegenPlugin; import software.amazon.polymorph.smithyjava.generator.CodegenSubject.AwsSdkVersion; import software.amazon.polymorph.smithyjava.generator.awssdk.v1.JavaAwsSdkV1; import software.amazon.polymorph.smithyjava.generator.awssdk.v2.JavaAwsSdkV2; @@ -66,7 +67,6 @@ import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.utils.IoUtils; -import software.amazon.polymorph.smithygo.localservice.DafnyLocalServiceCodegenPlugin; import software.amazon.smithy.utils.Pair; public class CodegenEngine { @@ -815,25 +815,36 @@ private void handlePatching(TargetLanguage targetLanguage, Path outputDir) { private void generateGo() { if (libraryName.isEmpty()) { - throw new IllegalArgumentException("Python codegen requires a module name"); + throw new IllegalArgumentException("Go codegen requires a library name"); } - ObjectNode.Builder goSettingsBuilder = ObjectNode.builder() - .withMember("service", serviceShape.getId().toString()) - .withMember("moduleName", libraryName.get()); + ObjectNode.Builder goSettingsBuilder = ObjectNode + .builder() + .withMember("service", serviceShape.getId().toString()) + .withMember("moduleName", libraryName.get()); - final PluginContext pluginContext = PluginContext.builder() - .model(model) - .fileManifest(FileManifest.create(targetLangOutputDirs.get(TargetLanguage.GO))) - .settings(goSettingsBuilder.build()) - .build(); + final PluginContext pluginContext = PluginContext + .builder() + .model(model) + .fileManifest( + FileManifest.create(targetLangOutputDirs.get(TargetLanguage.GO)) + ) + .settings(goSettingsBuilder.build()) + .build(); - final Map smithyNamespaceToGoModuleNameMap = new HashMap<>(dependencyLibraryNames); - smithyNamespaceToGoModuleNameMap.put(serviceShape.getId().getNamespace(), libraryName.get()); + final Map smithyNamespaceToGoModuleNameMap = new HashMap<>( + dependencyLibraryNames + ); + smithyNamespaceToGoModuleNameMap.put( + serviceShape.getId().getNamespace(), + libraryName.get() + ); if (this.awsSdkStyle) { - new DafnyGoAwsSdkClientCodegenPlugin(smithyNamespaceToGoModuleNameMap).run(pluginContext); + new DafnyGoAwsSdkClientCodegenPlugin(smithyNamespaceToGoModuleNameMap) + .run(pluginContext); } else { - new DafnyLocalServiceCodegenPlugin(smithyNamespaceToGoModuleNameMap).run(pluginContext); + new DafnyLocalServiceCodegenPlugin(smithyNamespaceToGoModuleNameMap) + .run(pluginContext); } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/AwsSdkGoPointableIndex.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/AwsSdkGoPointableIndex.java index c57767d9c8..a6524a8a24 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/AwsSdkGoPointableIndex.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/AwsSdkGoPointableIndex.java @@ -20,226 +20,262 @@ import software.amazon.smithy.utils.SetUtils; public class AwsSdkGoPointableIndex implements KnowledgeIndex { - public static final NullableIndex.CheckMode DEFAULT_CHECKMODE = - NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1_NO_INPUT; - - private static final Logger LOGGER = Logger.getLogger(AwsSdkGoPointableIndex.class.getName()); - - // All types that are Go value types - private static final Set INHERENTLY_VALUE = SetUtils.of( - ShapeType.BLOB, - ShapeType.LIST, - ShapeType.SET, - ShapeType.MAP, - ShapeType.UNION, - ShapeType.DOCUMENT - ); - - // All types that are Go pointer types - private static final Set INHERENTLY_POINTABLE = SetUtils.of( - ShapeType.BIG_DECIMAL, - ShapeType.BIG_INTEGER - ); - // All types that cannot be dereferenced - private static final Set INHERENTLY_NONDEREFERENCABLE = SetUtils.of( - // built in slice/map - ShapeType.BLOB, - ShapeType.LIST, - ShapeType.SET, - ShapeType.MAP, - - // Interfaces - ShapeType.UNION, - ShapeType.DOCUMENT, - - // known pointer types. - ShapeType.BIG_DECIMAL, - ShapeType.BIG_INTEGER + public static final NullableIndex.CheckMode DEFAULT_CHECKMODE = + NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1_NO_INPUT; + + private static final Logger LOGGER = Logger.getLogger( + AwsSdkGoPointableIndex.class.getName() + ); + + // All types that are Go value types + private static final Set INHERENTLY_VALUE = SetUtils.of( + ShapeType.BLOB, + ShapeType.LIST, + ShapeType.SET, + ShapeType.MAP, + ShapeType.UNION, + ShapeType.DOCUMENT + ); + + // All types that are Go pointer types + private static final Set INHERENTLY_POINTABLE = SetUtils.of( + ShapeType.BIG_DECIMAL, + ShapeType.BIG_INTEGER + ); + + // All types that cannot be dereferenced + private static final Set INHERENTLY_NONDEREFERENCABLE = + SetUtils.of( + // built in slice/map + ShapeType.BLOB, + ShapeType.LIST, + ShapeType.SET, + ShapeType.MAP, + // Interfaces + ShapeType.UNION, + ShapeType.DOCUMENT, + // known pointer types. + ShapeType.BIG_DECIMAL, + ShapeType.BIG_INTEGER ); - // All types types that are comparable to nil - private static final Set INHERENTLY_NILLABLE = SetUtils.of( - // built in slice/map - ShapeType.BLOB, - ShapeType.LIST, - ShapeType.SET, - ShapeType.MAP, - - // Interfaces - ShapeType.UNION, - ShapeType.DOCUMENT, - - // known pointer types. - ShapeType.BIG_DECIMAL, - ShapeType.BIG_INTEGER - ); - - - - private final Model model; - private final NullableIndex nullableIndex; - private final NullableIndex.CheckMode checkMode; - private final Set pointableShapes = new HashSet<>(); - private final Set nillableShapes = new HashSet<>(); - private final Set dereferencableShapes = new HashSet<>(); - - public AwsSdkGoPointableIndex(Model model, NullableIndex.CheckMode checkMode) { - this.model = model; - this.nullableIndex = NullableIndex.of(model); - this.checkMode = checkMode; - - for (Shape shape : model.toSet()) { - if (shape.asMemberShape().isPresent()) { - MemberShape member = shape.asMemberShape().get(); - Shape targetShape = model.expectShape(member.getTarget()); - - if (isMemberPointable(member, targetShape)) { - pointableShapes.add(shape.getId()); - } - if (isMemberNillable(member, targetShape)) { - nillableShapes.add(shape.getId()); - } - if (isMemberDereferencable(member, targetShape)) { - dereferencableShapes.add(shape.getId()); - } - } else { - if (isShapePointable(shape)) { - pointableShapes.add(shape.getId()); - nillableShapes.add(shape.getId()); - } - if (isShapeNillable(shape)) { - nillableShapes.add(shape.getId()); - } - if (isShapeDereferencable(shape)) { - dereferencableShapes.add(shape.getId()); - } - } + // All types types that are comparable to nil + private static final Set INHERENTLY_NILLABLE = SetUtils.of( + // built in slice/map + ShapeType.BLOB, + ShapeType.LIST, + ShapeType.SET, + ShapeType.MAP, + // Interfaces + ShapeType.UNION, + ShapeType.DOCUMENT, + // known pointer types. + ShapeType.BIG_DECIMAL, + ShapeType.BIG_INTEGER + ); + + private final Model model; + private final NullableIndex nullableIndex; + private final NullableIndex.CheckMode checkMode; + private final Set pointableShapes = new HashSet<>(); + private final Set nillableShapes = new HashSet<>(); + private final Set dereferencableShapes = new HashSet<>(); + + public AwsSdkGoPointableIndex( + Model model, + NullableIndex.CheckMode checkMode + ) { + this.model = model; + this.nullableIndex = NullableIndex.of(model); + this.checkMode = checkMode; + + for (Shape shape : model.toSet()) { + if (shape.asMemberShape().isPresent()) { + MemberShape member = shape.asMemberShape().get(); + Shape targetShape = model.expectShape(member.getTarget()); + + if (isMemberPointable(member, targetShape)) { + pointableShapes.add(shape.getId()); + } + if (isMemberNillable(member, targetShape)) { + nillableShapes.add(shape.getId()); + } + if (isMemberDereferencable(member, targetShape)) { + dereferencableShapes.add(shape.getId()); + } + } else { + if (isShapePointable(shape)) { + pointableShapes.add(shape.getId()); + nillableShapes.add(shape.getId()); + } + if (isShapeNillable(shape)) { + nillableShapes.add(shape.getId()); } + if (isShapeDereferencable(shape)) { + dereferencableShapes.add(shape.getId()); + } + } } + } - public AwsSdkGoPointableIndex(Model model) { - this(model, DEFAULT_CHECKMODE); - } + public AwsSdkGoPointableIndex(Model model) { + this(model, DEFAULT_CHECKMODE); + } - public static AwsSdkGoPointableIndex of(Model model) { - return model.getKnowledge(AwsSdkGoPointableIndex.class, AwsSdkGoPointableIndex::new); - } + public static AwsSdkGoPointableIndex of(Model model) { + return model.getKnowledge( + AwsSdkGoPointableIndex.class, + AwsSdkGoPointableIndex::new + ); + } + + public static AwsSdkGoPointableIndex of( + Model model, + NullableIndex.CheckMode checkMode + ) { + return model.getKnowledge( + AwsSdkGoPointableIndex.class, + model1 -> new AwsSdkGoPointableIndex(model1, checkMode) + ); + } + + private boolean isMemberDereferencable( + MemberShape member, + Shape targetShape + ) { + return ( + !INHERENTLY_NONDEREFERENCABLE.contains(targetShape.getType()) && + isMemberPointable(member, targetShape) + ); + } - public static AwsSdkGoPointableIndex of(Model model, NullableIndex.CheckMode checkMode) { - return model.getKnowledge(AwsSdkGoPointableIndex.class, (model1) -> new AwsSdkGoPointableIndex(model1, checkMode)); - } + private boolean isMemberNillable(MemberShape member, Shape targetShape) { + return ( + INHERENTLY_NILLABLE.contains(targetShape.getType()) || + isMemberPointable(member, targetShape) + ); + } - private boolean isMemberDereferencable(MemberShape member, Shape targetShape) { - return !INHERENTLY_NONDEREFERENCABLE.contains(targetShape.getType()) && isMemberPointable(member, targetShape); + private boolean isMemberPointable(MemberShape member, Shape targetShape) { + // Streamed blob shapes are never pointers because they are interfaces + if (isBlobStream(targetShape)) { + return false; } - private boolean isMemberNillable(MemberShape member, Shape targetShape) { - return INHERENTLY_NILLABLE.contains(targetShape.getType()) || isMemberPointable(member, targetShape); + if ( + INHERENTLY_VALUE.contains(targetShape.getType()) || + isShapeEnum(targetShape) + ) { + return false; } - private boolean isMemberPointable(MemberShape member, Shape targetShape) { + return nullableIndex.isMemberNullable(member, checkMode); + } - // Streamed blob shapes are never pointers because they are interfaces - if (isBlobStream(targetShape)) { - return false; - } + private boolean isShapeDereferencable(Shape shape) { + return ( + !INHERENTLY_NONDEREFERENCABLE.contains(shape.getType()) && + isShapePointable(shape) + ); + } - if (INHERENTLY_VALUE.contains(targetShape.getType()) || isShapeEnum(targetShape)) { - return false; - } + private boolean isShapeNillable(Shape shape) { + return ( + INHERENTLY_NILLABLE.contains(shape.getType()) || isShapePointable(shape) + ); + } - return nullableIndex.isMemberNullable(member, checkMode); + private boolean isShapePointable(Shape shape) { + // All operation input and output shapes are pointable. + if (isOperationStruct(shape)) { + return true; } - private boolean isShapeDereferencable(Shape shape) { - return !INHERENTLY_NONDEREFERENCABLE.contains(shape.getType()) && isShapePointable(shape); + // Streamed blob shapes are never pointers because they are interfaces + if (isBlobStream(shape)) { + return false; } - private boolean isShapeNillable(Shape shape) { - return INHERENTLY_NILLABLE.contains(shape.getType()) || isShapePointable(shape); + if (shape.isServiceShape()) { + return true; } - private boolean isShapePointable(Shape shape) { - // All operation input and output shapes are pointable. - if (isOperationStruct(shape)) { - return true; - } - - // Streamed blob shapes are never pointers because they are interfaces - if (isBlobStream(shape)) { - return false; - } - - if (shape.isServiceShape()) { - return true; - } - - // This is odd because its not a go type but a function with receiver - if (shape.isOperationShape()) { - return false; - } - - if (INHERENTLY_POINTABLE.contains(shape.getType())) { - return true; - } - - if (INHERENTLY_VALUE.contains(shape.getType()) || isShapeEnum(shape)) { - return false; - } - - return nullableIndex.isNullable(shape); + // This is odd because its not a go type but a function with receiver + if (shape.isOperationShape()) { + return false; } - private boolean isShapeEnum(Shape shape) { - return shape.getType() == ShapeType.STRING && shape.hasTrait(EnumTrait.class) - || shape.getType() == ShapeType.ENUM - || shape.getType() == ShapeType.INT_ENUM; + if (INHERENTLY_POINTABLE.contains(shape.getType())) { + return true; } - private boolean isBlobStream(Shape shape) { - return shape.getType() == ShapeType.BLOB && shape.hasTrait(StreamingTrait.ID); + if (INHERENTLY_VALUE.contains(shape.getType()) || isShapeEnum(shape)) { + return false; } - public boolean isOperationStruct(Shape shape) { - NeighborProvider provider = NeighborProviderIndex.of(model).getReverseProvider(); - for (Relationship relationship : provider.getNeighbors(shape)) { - RelationshipType relationshipType = relationship.getRelationshipType(); - if (relationshipType == RelationshipType.INPUT || relationshipType == RelationshipType.OUTPUT) { - return true; - } - } - - return false; - } + return nullableIndex.isNullable(shape); + } - /** - * Returns if the shape should be generated as a Go pointer type or not. - * - * @param shape the shape to check if should be pointable type. - * @return if the shape is should be a Go pointer type. - */ - public final boolean isPointable(ToShapeId shape) { - return pointableShapes.contains(shape.toShapeId()); - } + private boolean isShapeEnum(Shape shape) { + return ( + (shape.getType() == ShapeType.STRING && + shape.hasTrait(EnumTrait.class)) || + shape.getType() == ShapeType.ENUM || + shape.getType() == ShapeType.INT_ENUM + ); + } - /** - * Returns if the Go type generated for the shape is comparable to nil. - * - * @param shape the shape to check - * @return if the shape's go type is comparable to nil - */ - public final boolean isNillable(ToShapeId shape) { - return nillableShapes.contains(shape.toShapeId()); + private boolean isBlobStream(Shape shape) { + return ( + shape.getType() == ShapeType.BLOB && shape.hasTrait(StreamingTrait.ID) + ); + } + + public boolean isOperationStruct(Shape shape) { + NeighborProvider provider = NeighborProviderIndex + .of(model) + .getReverseProvider(); + for (Relationship relationship : provider.getNeighbors(shape)) { + RelationshipType relationshipType = relationship.getRelationshipType(); + if ( + relationshipType == RelationshipType.INPUT || + relationshipType == RelationshipType.OUTPUT + ) { + return true; + } } - /** - * Returns if the Go type generated for the shape can be dereferenced. - * - * @param shape the shape to check - * @return if the shape's go type is dereferencable - */ - public final boolean isDereferencable(ToShapeId shape) { - return dereferencableShapes.contains(shape.toShapeId()); - } -} \ No newline at end of file + return false; + } + + /** + * Returns if the shape should be generated as a Go pointer type or not. + * + * @param shape the shape to check if should be pointable type. + * @return if the shape is should be a Go pointer type. + */ + public final boolean isPointable(ToShapeId shape) { + return pointableShapes.contains(shape.toShapeId()); + } + + /** + * Returns if the Go type generated for the shape is comparable to nil. + * + * @param shape the shape to check + * @return if the shape's go type is comparable to nil + */ + public final boolean isNillable(ToShapeId shape) { + return nillableShapes.contains(shape.toShapeId()); + } + + /** + * Returns if the Go type generated for the shape can be dereferenced. + * + * @param shape the shape to check + * @return if the shape's go type is dereferencable + */ + public final boolean isDereferencable(ToShapeId shape) { + return dereferencableShapes.contains(shape.toShapeId()); + } +} diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyAwsSdkClientShimGenerator.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyAwsSdkClientShimGenerator.java index f0116ce4fc..a9835297ae 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyAwsSdkClientShimGenerator.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyAwsSdkClientShimGenerator.java @@ -15,91 +15,174 @@ public class DafnyAwsSdkClientShimGenerator implements Runnable { - private final GenerationContext context; - private final ServiceShape service; - private final GoDelegator writerDelegator; - private final Model dafnyNonNormalizedModel; - private final Model awsNormalizedModel; - private final SymbolProvider symbolProvider; + private final GenerationContext context; + private final ServiceShape service; + private final GoDelegator writerDelegator; + private final Model dafnyNonNormalizedModel; + private final Model awsNormalizedModel; + private final SymbolProvider symbolProvider; - public DafnyAwsSdkClientShimGenerator(GenerationContext context, ServiceShape service) { - this.context = context; - this.service = service; - dafnyNonNormalizedModel = context.model(); - awsNormalizedModel = AddOperationShapes.execute(context.model(), service.toShapeId()); - writerDelegator = context.writerDelegator(); - symbolProvider = context.symbolProvider(); - } + public DafnyAwsSdkClientShimGenerator( + GenerationContext context, + ServiceShape service + ) { + this.context = context; + this.service = service; + dafnyNonNormalizedModel = context.model(); + awsNormalizedModel = + AddOperationShapes.execute(context.model(), service.toShapeId()); + writerDelegator = context.writerDelegator(); + symbolProvider = context.symbolProvider(); + } - @Override - public void run() { - generateShim(); - } + @Override + public void run() { + generateShim(); + } - void generateShim() { - final var namespace = "%swrapped".formatted(DafnyNameResolver.dafnyNamespace(service)); + void generateShim() { + final var namespace = + "%swrapped".formatted(DafnyNameResolver.dafnyNamespace(service)); - writerDelegator.useFileWriter("%s/shim.go".formatted(namespace), namespace, writer -> { + writerDelegator.useFileWriter( + "%s/shim.go".formatted(namespace), + namespace, + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + context.settings().getService().getNamespace() + ), + DafnyNameResolver.dafnyTypesNamespace(service) + ); + writer.addImport( + SmithyNameResolver.getGoModuleNameForSdkNamespace( + awsNormalizedModel + .expectShape(service.toShapeId(), ServiceShape.class) + .toShapeId() + .getNamespace() + ) + ); + writer.addImportFromModule( + "github.com/dafny-lang/DafnyStandardLibGo", + "Wrappers" + ); + writer.addUseImports(SmithyGoDependency.CONTEXT); + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + context.settings().getService().getNamespace() + ), + SmithyNameResolver.shapeNamespace(service) + ); + writer.write( + """ + type Shim struct { + $L + Client *$L + } + """, + DafnyNameResolver.getDafnyInterfaceClient( + service, + service.expectTrait(ServiceTrait.class) + ), + SmithyNameResolver.getAwsServiceClient( + service.expectTrait(ServiceTrait.class) + ) + ); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(context.settings().getService().getNamespace()), DafnyNameResolver.dafnyTypesNamespace(service)); - writer.addImport(SmithyNameResolver.getGoModuleNameForSdkNamespace(awsNormalizedModel.expectShape(service.toShapeId(), ServiceShape.class).toShapeId().getNamespace())); - writer.addImportFromModule("github.com/dafny-lang/DafnyStandardLibGo", "Wrappers"); - writer.addUseImports(SmithyGoDependency.CONTEXT); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(context.settings().getService().getNamespace()), SmithyNameResolver.shapeNamespace(service)); - writer.write(""" - type Shim struct { - $L - Client *$L - } - """, - DafnyNameResolver.getDafnyInterfaceClient(service, service.expectTrait(ServiceTrait.class)), - SmithyNameResolver.getAwsServiceClient(service.expectTrait(ServiceTrait.class)) + service + .getOperations() + .forEach(operation -> { + final var awsNormalizedOperation = awsNormalizedModel.expectShape( + operation, + OperationShape.class + ); + final var awsNormalizedInputShape = awsNormalizedModel.expectShape( + awsNormalizedOperation.getInputShape() + ); + final var awsNormalizedOutputShape = awsNormalizedModel.expectShape( + awsNormalizedOperation.getOutputShape() ); + final var operationShape = dafnyNonNormalizedModel.expectShape( + operation, + OperationShape.class + ); + final var inputShape = dafnyNonNormalizedModel.expectShape( + operationShape.getInputShape() + ); + final var outputShape = dafnyNonNormalizedModel.expectShape( + operationShape.getOutputShape() + ); + final var inputType = awsNormalizedInputShape.hasTrait( + UnitTypeTrait.class + ) + ? "" + : "input %s".formatted( + DafnyNameResolver.getDafnyType( + inputShape, + symbolProvider.toSymbol(inputShape) + ) + ); - service.getOperations().forEach(operation -> { - final var awsNormalizedOperation = awsNormalizedModel.expectShape(operation, OperationShape.class); - final var awsNormalizedInputShape = awsNormalizedModel.expectShape(awsNormalizedOperation.getInputShape()); - final var awsNormalizedOutputShape = awsNormalizedModel.expectShape(awsNormalizedOperation.getOutputShape()); - - final var operationShape = dafnyNonNormalizedModel.expectShape(operation, OperationShape.class); - final var inputShape = dafnyNonNormalizedModel.expectShape(operationShape.getInputShape()); - final var outputShape = dafnyNonNormalizedModel.expectShape(operationShape.getOutputShape()); - final var inputType = awsNormalizedInputShape.hasTrait(UnitTypeTrait.class) ? "" - : "input %s".formatted(DafnyNameResolver.getDafnyType(inputShape, symbolProvider.toSymbol(inputShape))); + final var typeConversion = awsNormalizedInputShape.hasTrait( + UnitTypeTrait.class + ) + ? "" + : "var native_request = %s(input)".formatted( + SmithyNameResolver.getFromDafnyMethodName( + awsNormalizedInputShape, + "" + ) + ); - final var typeConversion = awsNormalizedInputShape.hasTrait(UnitTypeTrait.class) ? "" - : "var native_request = %s(input)".formatted(SmithyNameResolver.getFromDafnyMethodName(awsNormalizedInputShape, "")); + final var clientCall = + "shim.Client.%s(context.Background() %s)".formatted( + operationShape.getId().getName(), + awsNormalizedInputShape.hasTrait(UnitTypeTrait.class) + ? "" + : ", &native_request" + ); - final var clientCall = "shim.Client.%s(context.Background() %s)".formatted(operationShape.getId().getName(), - awsNormalizedInputShape.hasTrait(UnitTypeTrait.class) ? "" : ", &native_request"); + String clientResponse, returnResponse; + if (awsNormalizedOutputShape.hasTrait(UnitTypeTrait.class)) { + clientResponse = "var _, native_error"; + returnResponse = "dafny.TupleOf()"; + writer.addImportFromModule( + "github.com/dafny-lang/DafnyRuntimeGo", + "dafny" + ); + } else { + clientResponse = "var native_response, native_error"; + returnResponse = + "%s(*native_response)".formatted( + SmithyNameResolver.getToDafnyMethodName( + awsNormalizedOutputShape, + "" + ) + ); + } - String clientResponse, returnResponse; - if (awsNormalizedOutputShape.hasTrait(UnitTypeTrait.class)) { - clientResponse = "var _, native_error"; - returnResponse = "dafny.TupleOf()"; - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - } else { - clientResponse = "var native_response, native_error"; - returnResponse = "%s(*native_response)".formatted(SmithyNameResolver.getToDafnyMethodName(awsNormalizedOutputShape, "")); + writer.write( + """ + func (shim *Shim) $L($L) Wrappers.Result { + $L + $L = $L + if native_error != nil { + return Wrappers.Companion_Result_.Create_Failure_($L.Error_ToDafny(native_error)) + } + return Wrappers.Companion_Result_.Create_Success_($L) } - - writer.write(""" - func (shim *Shim) $L($L) Wrappers.Result { - $L - $L = $L - if native_error != nil { - return Wrappers.Companion_Result_.Create_Failure_($L.Error_ToDafny(native_error)) - } - return Wrappers.Companion_Result_.Create_Success_($L) - } - """, - operationShape.getId().getName(), - inputType, typeConversion, clientResponse, clientCall, - SmithyNameResolver.shapeNamespace(service), - returnResponse - ); - }); - }); - } + """, + operationShape.getId().getName(), + inputType, + typeConversion, + clientResponse, + clientCall, + SmithyNameResolver.shapeNamespace(service), + returnResponse + ); + }); + } + ); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyAwsSdkClientTypeConversionProtocol.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyAwsSdkClientTypeConversionProtocol.java index 0b50402b28..3a82903637 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyAwsSdkClientTypeConversionProtocol.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyAwsSdkClientTypeConversionProtocol.java @@ -1,5 +1,10 @@ package software.amazon.polymorph.smithygo.awssdk; +import static software.amazon.polymorph.smithygo.localservice.DafnyLocalServiceTypeConversionProtocol.TO_DAFNY; +import static software.amazon.polymorph.smithygo.localservice.DafnyLocalServiceTypeConversionProtocol.TO_NATIVE; + +import java.util.HashSet; +import java.util.Set; import software.amazon.polymorph.smithygo.awssdk.shapevisitor.AwsSdkToDafnyShapeVisitor; import software.amazon.polymorph.smithygo.awssdk.shapevisitor.DafnyToAwsSdkShapeVisitor; import software.amazon.polymorph.smithygo.codegen.AddOperationShapes; @@ -18,359 +23,744 @@ import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.model.traits.UnitTypeTrait; -import java.util.HashSet; -import java.util.Set; - -import static software.amazon.polymorph.smithygo.localservice.DafnyLocalServiceTypeConversionProtocol.TO_DAFNY; -import static software.amazon.polymorph.smithygo.localservice.DafnyLocalServiceTypeConversionProtocol.TO_NATIVE; - -public class DafnyAwsSdkClientTypeConversionProtocol implements ProtocolGenerator { - final Model dafnyNonNormalizedModel; - final Model awsNormalizedModel; - final ServiceShape serviceShape; - public DafnyAwsSdkClientTypeConversionProtocol(Model model, ServiceShape serviceShape) { - dafnyNonNormalizedModel = model; - awsNormalizedModel = AddOperationShapes.execute(model, serviceShape.toShapeId()); - - this.serviceShape = serviceShape; - } +public class DafnyAwsSdkClientTypeConversionProtocol + implements ProtocolGenerator { + + final Model dafnyNonNormalizedModel; + final Model awsNormalizedModel; + final ServiceShape serviceShape; + + public DafnyAwsSdkClientTypeConversionProtocol( + Model model, + ServiceShape serviceShape + ) { + dafnyNonNormalizedModel = model; + awsNormalizedModel = + AddOperationShapes.execute(model, serviceShape.toShapeId()); + + this.serviceShape = serviceShape; + } + + @Override + public ShapeId getProtocol() { + return null; + } + + @Override + public ApplicationProtocol getApplicationProtocol() { + return null; + } + + @Override + public void generateSerializers(GenerationContext context) { + final Set alreadyVisited = new HashSet<>(); + final var symbolProvider = context.symbolProvider(); + final var writerDelegator = context.writerDelegator(); + serviceShape + .getOperations() + .forEach(eachOperation -> { + final var awsNormalizedOperation = awsNormalizedModel.expectShape( + eachOperation, + OperationShape.class + ); + final var awsNormalizedInputShape = awsNormalizedModel.expectShape( + awsNormalizedOperation.getInputShape() + ); + if (!alreadyVisited.contains(awsNormalizedInputShape.toShapeId())) { + alreadyVisited.add(awsNormalizedInputShape.toShapeId()); + if ( + !awsNormalizedInputShape.hasTrait(UnitTypeTrait.class) && + awsNormalizedInputShape + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + final var awsNormalizedInputToDafnyMethodName = + SmithyNameResolver.getToDafnyMethodName( + serviceShape, + awsNormalizedInputShape, + "" + ); + final var awsNormalizedInputSymbol = symbolProvider.toSymbol( + awsNormalizedInputShape + ); + final var dafnyInput = dafnyNonNormalizedModel + .expectShape(eachOperation, OperationShape.class) + .getInputShape(); + final var dafnyInputSymbol = symbolProvider.toSymbol( + dafnyNonNormalizedModel.expectShape(dafnyInput) + ); + writerDelegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImport( + SmithyNameResolver.getGoModuleNameForSdkNamespace( + awsNormalizedInputShape.toShapeId().getNamespace() + ) + ); + writer.write( + """ + func $L(nativeInput $L)($L) { + ${C|} + }""", + awsNormalizedInputToDafnyMethodName, + SmithyNameResolver.getSmithyTypeAws( + serviceShape.expectTrait(ServiceTrait.class), + awsNormalizedInputSymbol, + false + ), + DafnyNameResolver.getDafnyType( + dafnyNonNormalizedModel.expectShape(dafnyInput), + dafnyInputSymbol + ), + writer.consumer(w -> + generateRequestSerializer( + context, + awsNormalizedOperation, + context.writerDelegator() + ) + ) + ); + } + ); + } + } - @Override - public ShapeId getProtocol() { - return null; - } + final var awsNormalizedOutputShape = awsNormalizedModel.expectShape( + awsNormalizedOperation.getOutputShape() + ); + if (!alreadyVisited.contains(awsNormalizedOutputShape.toShapeId())) { + alreadyVisited.add(awsNormalizedOutputShape.toShapeId()); + if ( + !awsNormalizedOutputShape.hasTrait(UnitTypeTrait.class) && + awsNormalizedOutputShape + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + final var awsNormalizedOutputToDafnyMethodName = + SmithyNameResolver.getToDafnyMethodName( + serviceShape, + awsNormalizedOutputShape, + "" + ); + final var awsNormalizedOutputSymbol = symbolProvider.toSymbol( + awsNormalizedOutputShape + ); + final var dafnyOutput = dafnyNonNormalizedModel + .expectShape(eachOperation, OperationShape.class) + .getOutputShape(); + final var dafnyOutputSymbol = symbolProvider.toSymbol( + dafnyNonNormalizedModel.expectShape(dafnyOutput) + ); + writerDelegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImport( + SmithyNameResolver.getGoModuleNameForSdkNamespace( + awsNormalizedInputShape.toShapeId().getNamespace() + ) + ); + writer.write( + """ + func $L(nativeOutput $L)($L) { + ${C|} + }""", + awsNormalizedOutputToDafnyMethodName, + SmithyNameResolver.getSmithyTypeAws( + serviceShape.expectTrait(ServiceTrait.class), + awsNormalizedOutputSymbol, + false + ), + DafnyNameResolver.getDafnyType( + dafnyNonNormalizedModel.expectShape(dafnyOutput), + dafnyOutputSymbol + ), + writer.consumer(w -> + generateResponseSerializer( + context, + awsNormalizedOperation, + context.writerDelegator() + ) + ) + ); + } + ); + } + } + }); + generateErrorSerializer(context); + } + + @Override + public void generateDeserializers(GenerationContext context) { + final Set alreadyVisited = new HashSet<>(); + final var symbolProvider = context.symbolProvider(); + final var delegator = context.writerDelegator(); + + serviceShape + .getOperations() + .forEach(eachOperation -> { + final var awsNormalizedOperationShape = awsNormalizedModel.expectShape( + eachOperation, + OperationShape.class + ); + final var awsNormalizedInputShape = awsNormalizedModel.expectShape( + awsNormalizedOperationShape.getInputShape() + ); + if (!alreadyVisited.contains(awsNormalizedInputShape.toShapeId())) { + alreadyVisited.add(awsNormalizedInputShape.toShapeId()); + if ( + !awsNormalizedInputShape.hasTrait(UnitTypeTrait.class) && + awsNormalizedInputShape + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + final var awsNormalizedInputFromDafnyMethodName = + SmithyNameResolver.getFromDafnyMethodName( + serviceShape, + awsNormalizedInputShape, + "" + ); + final var awsNormalizedInputSymbol = symbolProvider.toSymbol( + awsNormalizedInputShape + ); + final var dafnyInput = dafnyNonNormalizedModel + .expectShape(eachOperation, OperationShape.class) + .getInputShape(); + final var dafnyInputSymbol = symbolProvider.toSymbol( + dafnyNonNormalizedModel.expectShape(dafnyInput) + ); + delegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImport( + SmithyNameResolver.getGoModuleNameForSdkNamespace( + awsNormalizedInputShape.toShapeId().getNamespace() + ) + ); + writer.write( + """ + func $L(dafnyInput $L)($L) { + ${C|} + }""", + awsNormalizedInputFromDafnyMethodName, + DafnyNameResolver.getDafnyType( + dafnyNonNormalizedModel.expectShape(dafnyInput), + dafnyInputSymbol + ), + SmithyNameResolver.getSmithyTypeAws( + serviceShape.expectTrait(ServiceTrait.class), + awsNormalizedInputSymbol, + false + ), + writer.consumer(w -> + generateRequestDeserializer( + context, + awsNormalizedOperationShape, + context.writerDelegator() + ) + ) + ); + } + ); + } + } - @Override - public ApplicationProtocol getApplicationProtocol() { - return null; - } + final var awsNormalizedOutputShape = awsNormalizedModel.expectShape( + awsNormalizedOperationShape.getOutputShape() + ); + if (!alreadyVisited.contains(awsNormalizedOutputShape.toShapeId())) { + alreadyVisited.add(awsNormalizedOutputShape.toShapeId()); + if ( + !awsNormalizedOutputShape.hasTrait(UnitTypeTrait.class) && + awsNormalizedOutputShape + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + final var awsNormalizedOutputFromDafnyMethodName = + SmithyNameResolver.getFromDafnyMethodName( + serviceShape, + awsNormalizedOutputShape, + "" + ); + final var awsNormalizedOutputSymbol = context + .symbolProvider() + .toSymbol(awsNormalizedOutputShape); + final var dafnyOutput = dafnyNonNormalizedModel + .expectShape(eachOperation, OperationShape.class) + .getOutputShape(); + final var dafnyOutputSymbol = symbolProvider.toSymbol( + dafnyNonNormalizedModel.expectShape(dafnyOutput) + ); + delegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImport( + SmithyNameResolver.getGoModuleNameForSdkNamespace( + awsNormalizedInputShape.toShapeId().getNamespace() + ) + ); + writer.write( + """ + func $L(dafnyOutput $L)($L) { + ${C|} + }""", + awsNormalizedOutputFromDafnyMethodName, + DafnyNameResolver.getDafnyType( + dafnyNonNormalizedModel.expectShape(dafnyOutput), + dafnyOutputSymbol + ), + SmithyNameResolver.getSmithyTypeAws( + serviceShape.expectTrait(ServiceTrait.class), + awsNormalizedOutputSymbol, + false + ), + writer.consumer(w -> + generateResponseDeserializer( + context, + awsNormalizedOperationShape, + context.writerDelegator() + ) + ) + ); + } + ); + } + } + }); + + generateErrorDeserializer(context); + } + + private void generateRequestSerializer( + final GenerationContext context, + final OperationShape operation, + final GoDelegator delegator + ) { + final var nonNormalizedOperation = dafnyNonNormalizedModel.expectShape( + operation.toShapeId(), + OperationShape.class + ); + final var targetShape = dafnyNonNormalizedModel.expectShape( + nonNormalizedOperation.getInputShape() + ); + delegator.useFileWriter( + "%s/%s".formatted(SmithyNameResolver.shapeNamespace(operation), TO_DAFNY), + SmithyNameResolver.shapeNamespace(operation), + writer -> { + final var input = targetShape.accept( + new AwsSdkToDafnyShapeVisitor( + context, + "nativeInput", + writer, + false, + false, + false + ) + ); + writer.write( + """ + return $L + """, + input + ); + } + ); + } + + private void generateResponseSerializer( + final GenerationContext context, + final OperationShape operation, + final GoDelegator delegator + ) { + final var nonNormalizedOperation = dafnyNonNormalizedModel.expectShape( + operation.toShapeId(), + OperationShape.class + ); + final var targetShape = dafnyNonNormalizedModel.expectShape( + nonNormalizedOperation.getOutputShape() + ); + delegator.useFileWriter( + "%s/%s".formatted(SmithyNameResolver.shapeNamespace(operation), TO_DAFNY), + SmithyNameResolver.shapeNamespace(operation), + writer -> { + final var input = targetShape.accept( + new AwsSdkToDafnyShapeVisitor( + context, + "nativeOutput", + writer, + false, + false, + false + ) + ); + writer.write( + """ + return $L + """, + input + ); + } + ); + } + + private void generateRequestDeserializer( + final GenerationContext context, + final OperationShape operation, + final GoDelegator delegator + ) { + delegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(operation), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(operation), + writer -> { + final var inputShape = operation.getInputShape(); + + final var targetShape = awsNormalizedModel.expectShape(inputShape); + final var input = targetShape.accept( + new DafnyToAwsSdkShapeVisitor(context, "dafnyInput", writer) + ); - @Override - public void generateSerializers(GenerationContext context) { - final Set alreadyVisited = new HashSet<>(); - final var symbolProvider = context.symbolProvider(); - final var writerDelegator = context.writerDelegator(); - serviceShape.getOperations().forEach(eachOperation -> { - final var awsNormalizedOperation = awsNormalizedModel.expectShape(eachOperation, OperationShape.class); - final var awsNormalizedInputShape = awsNormalizedModel.expectShape(awsNormalizedOperation.getInputShape()); - if (!alreadyVisited.contains(awsNormalizedInputShape.toShapeId())) { - alreadyVisited.add(awsNormalizedInputShape.toShapeId()); - if (!awsNormalizedInputShape.hasTrait(UnitTypeTrait.class) && awsNormalizedInputShape.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - final var awsNormalizedInputToDafnyMethodName = SmithyNameResolver.getToDafnyMethodName(serviceShape, awsNormalizedInputShape, ""); - final var awsNormalizedInputSymbol = symbolProvider.toSymbol(awsNormalizedInputShape); - final var dafnyInput = dafnyNonNormalizedModel.expectShape(eachOperation, OperationShape.class).getInputShape(); - final var dafnyInputSymbol = symbolProvider.toSymbol(dafnyNonNormalizedModel.expectShape(dafnyInput)); - writerDelegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_DAFNY), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.addImport(SmithyNameResolver.getGoModuleNameForSdkNamespace(awsNormalizedInputShape.toShapeId().getNamespace())); - writer.write(""" - func $L(nativeInput $L)($L) { - ${C|} - }""", awsNormalizedInputToDafnyMethodName, SmithyNameResolver.getSmithyTypeAws(serviceShape.expectTrait(ServiceTrait.class), awsNormalizedInputSymbol, false), - DafnyNameResolver.getDafnyType(dafnyNonNormalizedModel.expectShape(dafnyInput), dafnyInputSymbol), - writer.consumer(w -> generateRequestSerializer(context, awsNormalizedOperation, context.writerDelegator()))); - }); - } - } + writer.write( + """ + return $L + """, + input + ); + } + ); + } + + private void generateResponseDeserializer( + final GenerationContext context, + final OperationShape operation, + final GoDelegator delegator + ) { + delegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(operation), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(operation), + writer -> { + final var outputShape = operation.getOutputShape(); + + final var targetShape = awsNormalizedModel.expectShape(outputShape); + final var output = targetShape.accept( + new DafnyToAwsSdkShapeVisitor(context, "dafnyOutput", writer) + ); - final var awsNormalizedOutputShape = awsNormalizedModel.expectShape(awsNormalizedOperation.getOutputShape()); - if (!alreadyVisited.contains(awsNormalizedOutputShape.toShapeId())) { - alreadyVisited.add(awsNormalizedOutputShape.toShapeId()); - if (!awsNormalizedOutputShape.hasTrait(UnitTypeTrait.class) && awsNormalizedOutputShape.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - final var awsNormalizedOutputToDafnyMethodName = SmithyNameResolver.getToDafnyMethodName(serviceShape, awsNormalizedOutputShape, ""); - final var awsNormalizedOutputSymbol = symbolProvider.toSymbol(awsNormalizedOutputShape); - final var dafnyOutput = dafnyNonNormalizedModel.expectShape(eachOperation, OperationShape.class).getOutputShape(); - final var dafnyOutputSymbol = symbolProvider.toSymbol(dafnyNonNormalizedModel.expectShape(dafnyOutput)); - writerDelegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_DAFNY), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.addImport(SmithyNameResolver.getGoModuleNameForSdkNamespace(awsNormalizedInputShape.toShapeId().getNamespace())); - writer.write(""" - func $L(nativeOutput $L)($L) { - ${C|} - }""", awsNormalizedOutputToDafnyMethodName, - SmithyNameResolver.getSmithyTypeAws(serviceShape.expectTrait(ServiceTrait.class), awsNormalizedOutputSymbol, false), - DafnyNameResolver.getDafnyType(dafnyNonNormalizedModel.expectShape(dafnyOutput), dafnyOutputSymbol), - writer.consumer(w -> generateResponseSerializer(context, awsNormalizedOperation, context.writerDelegator()))); - }); - } + writer.write( + """ + return $L + """, + output + ); + } + ); + } + + private void generateErrorSerializer(final GenerationContext context) { + final Set alreadyVisited = new HashSet<>(); + final var errorShapes = awsNormalizedModel.getShapesWithTrait( + ErrorTrait.class + ); + + for (final var errorShape : errorShapes) { + if ( + !errorShape + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + continue; + } + if (!alreadyVisited.contains(errorShape.toShapeId())) { + alreadyVisited.add(errorShape.toShapeId()); + final var getInputToDafnyMethodName = + SmithyNameResolver.getToDafnyMethodName(serviceShape, errorShape, ""); + + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(errorShape), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace(errorShape), + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSdkNamespace( + errorShape.toShapeId().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespaceAws( + serviceShape.expectTrait(ServiceTrait.class), + true + ) + ); + writer.write( + """ + func $L(nativeInput types.$L)($L) { + ${C|} + }""", + getInputToDafnyMethodName, + context.symbolProvider().toSymbol(errorShape).getName(), + DafnyNameResolver.getDafnyBaseErrorType(errorShape), + writer.consumer(w -> { + String output = errorShape.accept( + new AwsSdkToDafnyShapeVisitor( + context, + "nativeInput", + writer, + false, + false, + false + ) + ); + writer.write( + """ + return $L + """, + output + ); + }) + ); } - }); - generateErrorSerializer(context); + ); + } } - @Override - public void generateDeserializers(GenerationContext context) { - final Set alreadyVisited = new HashSet<>(); - final var symbolProvider = context.symbolProvider(); - final var delegator = context.writerDelegator(); - - serviceShape.getOperations().forEach(eachOperation -> { - final var awsNormalizedOperationShape = awsNormalizedModel.expectShape(eachOperation, OperationShape.class); - final var awsNormalizedInputShape = awsNormalizedModel.expectShape(awsNormalizedOperationShape.getInputShape()); - if (!alreadyVisited.contains(awsNormalizedInputShape.toShapeId())) { - alreadyVisited.add(awsNormalizedInputShape.toShapeId()); - if (!awsNormalizedInputShape.hasTrait(UnitTypeTrait.class) && awsNormalizedInputShape.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - final var awsNormalizedInputFromDafnyMethodName = SmithyNameResolver.getFromDafnyMethodName(serviceShape, awsNormalizedInputShape, ""); - final var awsNormalizedInputSymbol = symbolProvider.toSymbol(awsNormalizedInputShape); - final var dafnyInput = dafnyNonNormalizedModel.expectShape(eachOperation, OperationShape.class).getInputShape(); - final var dafnyInputSymbol = symbolProvider.toSymbol(dafnyNonNormalizedModel.expectShape(dafnyInput)); - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_NATIVE), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.addImport(SmithyNameResolver.getGoModuleNameForSdkNamespace(awsNormalizedInputShape.toShapeId().getNamespace())); - writer.write(""" - func $L(dafnyInput $L)($L) { - ${C|} - }""", awsNormalizedInputFromDafnyMethodName, DafnyNameResolver.getDafnyType(dafnyNonNormalizedModel.expectShape(dafnyInput), dafnyInputSymbol), - SmithyNameResolver.getSmithyTypeAws(serviceShape.expectTrait(ServiceTrait.class), awsNormalizedInputSymbol, false), - writer.consumer(w -> generateRequestDeserializer(context, awsNormalizedOperationShape, context.writerDelegator()))); - }); + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.write( + """ + func OpaqueError_Input_ToDafny(nativeInput error)($L.Error) { + return $L.Companion_Error_.Create_Opaque_(nativeInput) + }""", + DafnyNameResolver.dafnyTypesNamespace(serviceShape), + DafnyNameResolver.dafnyTypesNamespace(serviceShape) + ); + } + ); + + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.write( + """ + func Error_ToDafny(err error)($L.Error) { + switch err.(type) { + // Service Errors + ${C|} + + default: + return OpaqueError_Input_ToDafny(err) } } - - final var awsNormalizedOutputShape = awsNormalizedModel.expectShape(awsNormalizedOperationShape.getOutputShape()); - if (!alreadyVisited.contains(awsNormalizedOutputShape.toShapeId())) { - alreadyVisited.add(awsNormalizedOutputShape.toShapeId()); - if (!awsNormalizedOutputShape.hasTrait(UnitTypeTrait.class) && awsNormalizedOutputShape.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - final var awsNormalizedOutputFromDafnyMethodName = SmithyNameResolver.getFromDafnyMethodName(serviceShape, awsNormalizedOutputShape, ""); - final var awsNormalizedOutputSymbol = context.symbolProvider().toSymbol(awsNormalizedOutputShape); - final var dafnyOutput = dafnyNonNormalizedModel.expectShape(eachOperation, OperationShape.class).getOutputShape(); - final var dafnyOutputSymbol = symbolProvider.toSymbol(dafnyNonNormalizedModel.expectShape(dafnyOutput)); - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_NATIVE), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.addImport(SmithyNameResolver.getGoModuleNameForSdkNamespace(awsNormalizedInputShape.toShapeId().getNamespace())); - writer.write(""" - func $L(dafnyOutput $L)($L) { - ${C|} - }""", awsNormalizedOutputFromDafnyMethodName, DafnyNameResolver.getDafnyType(dafnyNonNormalizedModel.expectShape(dafnyOutput), dafnyOutputSymbol), - SmithyNameResolver.getSmithyTypeAws(serviceShape.expectTrait(ServiceTrait.class), awsNormalizedOutputSymbol, false), - writer.consumer(w -> generateResponseDeserializer(context, awsNormalizedOperationShape, context.writerDelegator()))); - }); - } + """, + DafnyNameResolver.dafnyTypesNamespace(serviceShape), + writer.consumer(w -> { + for (var error : errorShapes) { + w.write( + """ + case *$L: + return $L(*err.(*$L)) + """, + SmithyNameResolver.getSmithyTypeAws( + serviceShape.expectTrait(ServiceTrait.class), + context + .symbolProvider() + .toSymbol( + awsNormalizedModel.expectShape(error.toShapeId()) + ), + true + ), + SmithyNameResolver.getToDafnyMethodName( + serviceShape, + awsNormalizedModel.expectShape(error.toShapeId()), + "" + ), + SmithyNameResolver.getSmithyTypeAws( + serviceShape.expectTrait(ServiceTrait.class), + context + .symbolProvider() + .toSymbol( + awsNormalizedModel.expectShape(error.toShapeId()) + ), + true + ) + ); + } + }) + ); + } + ); + } + + private void generateErrorDeserializer(final GenerationContext context) { + final Set alreadyVisited = new HashSet<>(); + final var errorShapes = awsNormalizedModel.getShapesWithTrait( + ErrorTrait.class + ); + for (final var errorShape : errorShapes) { + if ( + !errorShape + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + continue; + } + if (!alreadyVisited.contains(errorShape.toShapeId())) { + alreadyVisited.add(errorShape.toShapeId()); + final var getOutputFromDafnyMethodName = + SmithyNameResolver.getFromDafnyMethodName( + serviceShape, + errorShape, + "" + ); + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(errorShape), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(errorShape), + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSdkNamespace( + errorShape.toShapeId().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespaceAws( + serviceShape.expectTrait(ServiceTrait.class), + true + ) + ); + writer.write( + """ + func $L(dafnyOutput $L)(types.$L) { + ${C|} + }""", + getOutputFromDafnyMethodName, + DafnyNameResolver.getDafnyBaseErrorType(errorShape), + context.symbolProvider().toSymbol(errorShape).getName(), + writer.consumer(w -> { + String output = errorShape.accept( + new DafnyToAwsSdkShapeVisitor( + context, + "dafnyOutput", + writer + ) + ); + writer.write( + """ + return $L + """, + output + ); + }) + ); } - }); - - generateErrorDeserializer(context); - - } - - private void generateRequestSerializer( - final GenerationContext context, - final OperationShape operation, - final GoDelegator delegator - ) { - final var nonNormalizedOperation = dafnyNonNormalizedModel.expectShape(operation.toShapeId(), OperationShape.class); - final var targetShape = dafnyNonNormalizedModel.expectShape(nonNormalizedOperation.getInputShape()); - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(operation), TO_DAFNY), SmithyNameResolver.shapeNamespace(operation), writer -> { - final var input = targetShape.accept(new AwsSdkToDafnyShapeVisitor( - context, - "nativeInput", - writer, - false, false, false - )); - writer.write(""" - return $L - """, - input); - } - ); - } - - private void generateResponseSerializer( - final GenerationContext context, - final OperationShape operation, - final GoDelegator delegator - ) { - final var nonNormalizedOperation = dafnyNonNormalizedModel.expectShape(operation.toShapeId(), OperationShape.class); - final var targetShape = dafnyNonNormalizedModel.expectShape(nonNormalizedOperation.getOutputShape()); - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(operation), TO_DAFNY), SmithyNameResolver.shapeNamespace(operation), writer -> { - final var input = targetShape.accept(new AwsSdkToDafnyShapeVisitor( - context, - "nativeOutput", - writer, - false, false, false - )); - writer.write(""" - return $L - """, - input); - } - ); - } - - private void generateRequestDeserializer( - final GenerationContext context, - final OperationShape operation, - final GoDelegator delegator - ) { - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(operation), TO_NATIVE), SmithyNameResolver.shapeNamespace(operation), writer -> { - - final var inputShape = operation.getInputShape(); - - final var targetShape = awsNormalizedModel.expectShape(inputShape); - final var input = targetShape.accept(new DafnyToAwsSdkShapeVisitor( - context, - "dafnyInput", - writer - )); - - writer.write(""" - return $L - """, input); - }); + ); + } } - private void generateResponseDeserializer( - final GenerationContext context, - final OperationShape operation, - final GoDelegator delegator - ) { - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(operation), TO_NATIVE), SmithyNameResolver.shapeNamespace(operation), writer -> { - - final var outputShape = operation.getOutputShape(); - - final var targetShape = awsNormalizedModel.expectShape(outputShape); - final var output = targetShape.accept(new DafnyToAwsSdkShapeVisitor( - context, - "dafnyOutput", - writer - )); - - writer.write(""" - return $L - """, output); - }); - } - - private void generateErrorSerializer(final GenerationContext context) { - final Set alreadyVisited = new HashSet<>(); - final var errorShapes = awsNormalizedModel.getShapesWithTrait(ErrorTrait.class); - - for (final var errorShape : - errorShapes) { - if (!errorShape.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - continue; - } - if (!alreadyVisited.contains(errorShape.toShapeId())) { - alreadyVisited.add(errorShape.toShapeId()); - final var getInputToDafnyMethodName = SmithyNameResolver.getToDafnyMethodName(serviceShape, errorShape, ""); - - context.writerDelegator().useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(errorShape), TO_DAFNY), SmithyNameResolver.shapeNamespace(errorShape), writer -> { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSdkNamespace(errorShape.toShapeId().getNamespace()), SmithyNameResolver.smithyTypesNamespaceAws(serviceShape.expectTrait(ServiceTrait.class), true)); - writer.write(""" - func $L(nativeInput types.$L)($L) { - ${C|} - }""", - getInputToDafnyMethodName, context.symbolProvider().toSymbol(errorShape).getName(), DafnyNameResolver.getDafnyBaseErrorType(errorShape), - writer.consumer(w -> { - String output = errorShape.accept(new AwsSdkToDafnyShapeVisitor( - context, - "nativeInput", - writer, - false, false, false - )); - writer.write(""" - return $L - """, output); - })); - }); - } + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addUseImports(SmithyGoDependency.FMT); + writer.write( + """ + func OpaqueError_Output_FromDafny(dafnyOutput $L.Error)(error) { + return fmt.Errorf(fmt.Sprintf("%v", dafnyOutput.Dtor_obj())) + }""", + DafnyNameResolver.dafnyTypesNamespace(serviceShape) + ); } - - context.writerDelegator().useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_DAFNY), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.write(""" - func OpaqueError_Input_ToDafny(nativeInput error)($L.Error) { - return $L.Companion_Error_.Create_Opaque_(nativeInput) - }""", DafnyNameResolver.dafnyTypesNamespace(serviceShape), DafnyNameResolver.dafnyTypesNamespace(serviceShape)); - }); - - - context.writerDelegator() - .useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_DAFNY), - SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.write(""" - func Error_ToDafny(err error)($L.Error) { - switch err.(type) { - // Service Errors - ${C|} - - default: - return OpaqueError_Input_ToDafny(err) - } - } - """, DafnyNameResolver.dafnyTypesNamespace(serviceShape), - writer.consumer(w -> { - for (var error : errorShapes) { - w.write(""" - case *$L: - return $L(*err.(*$L)) - """, SmithyNameResolver.getSmithyTypeAws(serviceShape.expectTrait(ServiceTrait.class), context.symbolProvider().toSymbol(awsNormalizedModel.expectShape(error.toShapeId())), true), - SmithyNameResolver.getToDafnyMethodName(serviceShape, awsNormalizedModel.expectShape(error.toShapeId()), ""), - SmithyNameResolver.getSmithyTypeAws(serviceShape.expectTrait(ServiceTrait.class), context.symbolProvider().toSymbol(awsNormalizedModel.expectShape(error.toShapeId())), true)); - } - }) - ); - }); - } - - private void generateErrorDeserializer(final GenerationContext context) { - final Set alreadyVisited = new HashSet<>(); - final var errorShapes = awsNormalizedModel.getShapesWithTrait(ErrorTrait.class); - for (final var errorShape : - errorShapes) { - if (!errorShape.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - continue; - } - if (!alreadyVisited.contains(errorShape.toShapeId())) { - alreadyVisited.add(errorShape.toShapeId()); - final var getOutputFromDafnyMethodName = SmithyNameResolver.getFromDafnyMethodName(serviceShape, errorShape, ""); - context.writerDelegator().useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(errorShape), TO_NATIVE), SmithyNameResolver.shapeNamespace(errorShape), writer -> { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSdkNamespace(errorShape.toShapeId().getNamespace()), SmithyNameResolver.smithyTypesNamespaceAws(serviceShape.expectTrait(ServiceTrait.class), true)); - writer.write(""" - func $L(dafnyOutput $L)(types.$L) { - ${C|} - }""", - getOutputFromDafnyMethodName, DafnyNameResolver.getDafnyBaseErrorType(errorShape), context.symbolProvider().toSymbol(errorShape).getName(), - writer.consumer(w -> { - String output = errorShape.accept(new DafnyToAwsSdkShapeVisitor( - context, - "dafnyOutput", - writer - )); - writer.write(""" - return $L - """, output); - })); - }); + ); + + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.write( + """ + func Error_FromDafny(err $L.Error)(error) { + // Service Errors + ${C|} + + return OpaqueError_Output_FromDafny(err) } + """, + DafnyNameResolver.dafnyTypesNamespace(serviceShape), + writer.consumer(w -> { + for (var error : awsNormalizedModel.getShapesWithTrait( + ErrorTrait.class + )) { + w.write( + """ + if err.Is_$L() { + e := $L(err) + return &e + } + """, + error.toShapeId().getName(), + SmithyNameResolver.getFromDafnyMethodName( + serviceShape, + awsNormalizedModel.expectShape(error.toShapeId()), + "" + ) + ); + } + }) + ); } - - context.writerDelegator().useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_NATIVE), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.addUseImports(SmithyGoDependency.FMT); - writer.write(""" - func OpaqueError_Output_FromDafny(dafnyOutput $L.Error)(error) { - return fmt.Errorf(fmt.Sprintf("%v", dafnyOutput.Dtor_obj())) - }""", - DafnyNameResolver.dafnyTypesNamespace(serviceShape)); - }); - - context.writerDelegator() - .useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_NATIVE), - SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.write(""" - func Error_FromDafny(err $L.Error)(error) { - // Service Errors - ${C|} - - return OpaqueError_Output_FromDafny(err) - } - """, DafnyNameResolver.dafnyTypesNamespace(serviceShape), - writer.consumer(w -> { - for (var error : awsNormalizedModel.getShapesWithTrait(ErrorTrait.class)) { - w.write(""" - if err.Is_$L() { - e := $L(err) - return &e - } - """, error.toShapeId().getName(), SmithyNameResolver.getFromDafnyMethodName(serviceShape, awsNormalizedModel.expectShape(error.toShapeId()), "")); - } - }) - ); - }); - } + ); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyGoAwsSdkClientCodegenPlugin.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyGoAwsSdkClientCodegenPlugin.java index a8ef0ae239..1398a55c1c 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyGoAwsSdkClientCodegenPlugin.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyGoAwsSdkClientCodegenPlugin.java @@ -1,5 +1,6 @@ package software.amazon.polymorph.smithygo.awssdk; +import java.util.Map; import software.amazon.polymorph.smithygo.codegen.GenerationContext; import software.amazon.polymorph.smithygo.codegen.GoSettings; import software.amazon.polymorph.smithygo.codegen.GoWriter; @@ -9,52 +10,59 @@ import software.amazon.smithy.build.SmithyBuildPlugin; import software.amazon.smithy.codegen.core.directed.CodegenDirector; -import java.util.Map; - public class DafnyGoAwsSdkClientCodegenPlugin implements SmithyBuildPlugin { - public DafnyGoAwsSdkClientCodegenPlugin(final Map smithyNamespaceToPythonModuleNameMap) { - super(); - SmithyNameResolver.setSmithyNamespaceToGoModuleNameMap(smithyNamespaceToPythonModuleNameMap); - } + public DafnyGoAwsSdkClientCodegenPlugin( + final Map smithyNamespaceToPythonModuleNameMap + ) { + super(); + SmithyNameResolver.setSmithyNamespaceToGoModuleNameMap( + smithyNamespaceToPythonModuleNameMap + ); + } - @Override - public String getName() { - return "dafny-go-aws-sdk-client-codegen"; } + @Override + public String getName() { + return "dafny-go-aws-sdk-client-codegen"; + } - public void run(PluginContext context) { - CodegenDirector runner - = new CodegenDirector<>(); + public void run(PluginContext context) { + CodegenDirector< + GoWriter, + GoIntegration, + GenerationContext, + GoSettings + > runner = new CodegenDirector<>(); - runner.directedCodegen(new DafnyGoAwsSdkDirectedCodegen()); + runner.directedCodegen(new DafnyGoAwsSdkDirectedCodegen()); - // Set the SmithyIntegration class to look for and apply using SPI. - runner.integrationClass(GoIntegration.class); + // Set the SmithyIntegration class to look for and apply using SPI. + runner.integrationClass(GoIntegration.class); - // Set the FileManifest and Model from the plugin. - runner.fileManifest(context.getFileManifest()); + // Set the FileManifest and Model from the plugin. + runner.fileManifest(context.getFileManifest()); - // Create the GoSettings object from the plugin settings. - GoSettings settings = GoSettings.from(context.getSettings()); - runner.settings(settings); + // Create the GoSettings object from the plugin settings. + GoSettings settings = GoSettings.from(context.getSettings()); + runner.settings(settings); - runner.model(context.getModel()); + runner.model(context.getModel()); - runner.service(settings.getService()); + runner.service(settings.getService()); - // Configure the director to perform some common model transforms. - runner.performDefaultCodegenTransforms(); + // Configure the director to perform some common model transforms. + runner.performDefaultCodegenTransforms(); - // TODO: Not using below because it would break existing AWS SDKs. Maybe it should be configurable - // so generic SDKs call this by default, but AWS SDKs can opt-out of it via a setting. - // runner.createDedicatedInputsAndOutputs(); + // TODO: Not using below because it would break existing AWS SDKs. Maybe it should be configurable + // so generic SDKs call this by default, but AWS SDKs can opt-out of it via a setting. + // runner.createDedicatedInputsAndOutputs(); - // Run it! - runner.run(); - } + // Run it! + runner.run(); + } - @Override - public void execute(PluginContext context) { - this.run(context); - } + @Override + public void execute(PluginContext context) { + this.run(context); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyGoAwsSdkDirectedCodegen.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyGoAwsSdkDirectedCodegen.java index 16798586aa..ddbccecbe4 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyGoAwsSdkDirectedCodegen.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/DafnyGoAwsSdkDirectedCodegen.java @@ -16,62 +16,84 @@ import software.amazon.smithy.codegen.core.directed.GenerateStructureDirective; import software.amazon.smithy.codegen.core.directed.GenerateUnionDirective; -public class DafnyGoAwsSdkDirectedCodegen implements DirectedCodegen { - @Override - public SymbolProvider createSymbolProvider(CreateSymbolProviderDirective directive) { - return new SymbolVisitor(directive.model(), directive.settings()); +public class DafnyGoAwsSdkDirectedCodegen + implements DirectedCodegen { + + @Override + public SymbolProvider createSymbolProvider( + CreateSymbolProviderDirective directive + ) { + return new SymbolVisitor(directive.model(), directive.settings()); + } + + @Override + public GenerationContext createContext( + CreateContextDirective directive + ) { + return GenerationContext + .builder() + .model(directive.model()) + .settings(directive.settings()) + .symbolProvider(directive.symbolProvider()) + .fileManifest(directive.fileManifest()) + .integrations(directive.integrations()) + .writerDelegator( + new GoDelegator(directive.fileManifest(), directive.symbolProvider()) + ) + .protocolGenerator( + new DafnyAwsSdkClientTypeConversionProtocol( + directive.model(), + directive.service() + ) + ) + .build(); + } + + @Override + public void generateService( + GenerateServiceDirective directive + ) { + new DafnyAwsSdkClientShimGenerator(directive.context(), directive.service()) + .run(); + + var protocolGenerator = directive.context().protocolGenerator(); + if (protocolGenerator == null) { + return; } - @Override - public GenerationContext createContext(CreateContextDirective directive) { - return GenerationContext.builder() - .model(directive.model()) - .settings(directive.settings()) - .symbolProvider(directive.symbolProvider()) - .fileManifest(directive.fileManifest()) - .integrations(directive.integrations()) - .writerDelegator(new GoDelegator(directive.fileManifest(), directive.symbolProvider())) - .protocolGenerator(new DafnyAwsSdkClientTypeConversionProtocol(directive.model(), directive.service())) - .build(); - } - - @Override - public void generateService(GenerateServiceDirective directive) { - new DafnyAwsSdkClientShimGenerator(directive.context(), directive.service()).run(); - - var protocolGenerator = directive.context().protocolGenerator(); - if (protocolGenerator == null) { - return; - } - - protocolGenerator.generateSerializers(directive.context()); - - protocolGenerator.generateDeserializers(directive.context()); - - } - - @Override - public void generateStructure(GenerateStructureDirective generateStructureDirective) { - - } - - @Override - public void generateError(GenerateErrorDirective generateErrorDirective) { - - } - - @Override - public void generateUnion(GenerateUnionDirective generateUnionDirective) { - - } - - @Override - public void generateEnumShape(GenerateEnumDirective generateEnumDirective) { - - } - - @Override - public void generateIntEnumShape(GenerateIntEnumDirective generateIntEnumDirective) { - - } + protocolGenerator.generateSerializers(directive.context()); + + protocolGenerator.generateDeserializers(directive.context()); + } + + @Override + public void generateStructure( + GenerateStructureDirective< + GenerationContext, + GoSettings + > generateStructureDirective + ) {} + + @Override + public void generateError( + GenerateErrorDirective generateErrorDirective + ) {} + + @Override + public void generateUnion( + GenerateUnionDirective generateUnionDirective + ) {} + + @Override + public void generateEnumShape( + GenerateEnumDirective generateEnumDirective + ) {} + + @Override + public void generateIntEnumShape( + GenerateIntEnumDirective< + GenerationContext, + GoSettings + > generateIntEnumDirective + ) {} } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/shapevisitor/AwsSdkToDafnyShapeVisitor.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/shapevisitor/AwsSdkToDafnyShapeVisitor.java index 62d13f66b3..d89e542548 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/shapevisitor/AwsSdkToDafnyShapeVisitor.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/shapevisitor/AwsSdkToDafnyShapeVisitor.java @@ -1,5 +1,7 @@ package software.amazon.polymorph.smithygo.awssdk.shapevisitor; +import static software.amazon.polymorph.smithygo.codegen.SymbolUtils.POINTABLE; + import software.amazon.polymorph.smithygo.awssdk.AwsSdkGoPointableIndex; import software.amazon.polymorph.smithygo.codegen.GenerationContext; import software.amazon.polymorph.smithygo.codegen.GoWriter; @@ -26,394 +28,538 @@ import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.utils.StringUtils; -import static software.amazon.polymorph.smithygo.codegen.SymbolUtils.POINTABLE; - public class AwsSdkToDafnyShapeVisitor extends ShapeVisitor.Default { - private final GenerationContext context; - private final String dataSource; - private final GoWriter writer; - private final boolean isConfigShape; - private final boolean isOptional; - protected boolean isPointerType; - - public void setPointerType() { - this.isPointerType = false; + private final GenerationContext context; + private final String dataSource; + private final GoWriter writer; + private final boolean isConfigShape; + + private final boolean isOptional; + protected boolean isPointerType; + + public void setPointerType() { + this.isPointerType = false; + } + + public AwsSdkToDafnyShapeVisitor( + final GenerationContext context, + final String dataSource, + final GoWriter writer, + final boolean isConfigShape, + final boolean isOptional, + final boolean isPointerType + ) { + this.context = context; + this.dataSource = dataSource; + this.writer = writer; + this.isConfigShape = isConfigShape; + this.isOptional = isOptional; + this.isPointerType = isPointerType; + } + + @Override + protected String getDefault(Shape shape) { + throw new CodegenException( + String.format( + "Unsupported conversion of %s to %s using the %s protocol", + shape, + shape.getType(), + context.protocolGenerator().getName() + ) + ); + } + + @Override + public String blobShape(BlobShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + String nilWrapIfRequired = "nil"; + String someWrapIfRequired = "%s"; + String returnType = "dafny.Sequence"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; } - - public AwsSdkToDafnyShapeVisitor( - final GenerationContext context, - final String dataSource, - final GoWriter writer, - final boolean isConfigShape, - final boolean isOptional, - final boolean isPointerType - ) { - this.context = context; - this.dataSource = dataSource; - this.writer = writer; - this.isConfigShape = isConfigShape; - this.isOptional = isOptional; - this.isPointerType = isPointerType; + return """ + func () %s { + var v []interface{} + if %s == nil {return %s} + for _, e := range %s { + v = append(v, e) + } + return %s; + }()""".formatted( + returnType, + dataSource, + nilWrapIfRequired, + dataSource, + someWrapIfRequired.formatted("dafny.SeqOf(v...)") + ); + } + + @Override + public String structureShape(final StructureShape shape) { + final var builder = new StringBuilder(); + writer.addImportFromModule( + "github.com/dafny-lang/DafnyStandardLibGo", + "Wrappers" + ); + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + shape.toShapeId().getNamespace() + ), + DafnyNameResolver.dafnyTypesNamespace(shape) + ); + + String someWrapIfRequired = "%s"; + + String companionStruct; + String returnType; + if (shape.hasTrait(ErrorTrait.class)) { + companionStruct = + DafnyNameResolver.getDafnyErrorCompanionCreate( + shape, + context.symbolProvider().toSymbol(shape) + ); + returnType = DafnyNameResolver.getDafnyBaseErrorType(shape); + } else { + companionStruct = + DafnyNameResolver.getDafnyCompanionTypeCreate( + shape, + context.symbolProvider().toSymbol(shape) + ); + returnType = + DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ); } + String nilWrapIfRequired = returnType.concat("{}"); - @Override - protected String getDefault(Shape shape) { - throw new CodegenException(String.format( - "Unsupported conversion of %s to %s using the %s protocol", - shape, shape.getType(), context.protocolGenerator().getName())); + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; } - @Override - public String blobShape(BlobShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - String nilWrapIfRequired = "nil"; - String someWrapIfRequired = "%s"; - String returnType = "dafny.Sequence"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } - return """ - func () %s { - var v []interface{} - if %s == nil {return %s} - for _, e := range %s { - v = append(v, e) - } - return %s; - }()""".formatted(returnType, dataSource, nilWrapIfRequired, dataSource, someWrapIfRequired.formatted("dafny.SeqOf(v...)")); + var nilCheck = ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); } - - @Override - public String structureShape(final StructureShape shape) { - final var builder = new StringBuilder(); - writer.addImportFromModule("github.com/dafny-lang/DafnyStandardLibGo", "Wrappers"); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(shape.toShapeId().getNamespace()), DafnyNameResolver.dafnyTypesNamespace(shape)); - - String someWrapIfRequired = "%s"; - - String companionStruct; - String returnType; - if (shape.hasTrait(ErrorTrait.class)) { - companionStruct = DafnyNameResolver.getDafnyErrorCompanionCreate(shape, context.symbolProvider().toSymbol(shape)); - returnType = DafnyNameResolver.getDafnyBaseErrorType(shape); - } else { - companionStruct = DafnyNameResolver.getDafnyCompanionTypeCreate(shape, context.symbolProvider().toSymbol(shape)); - returnType = DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape)); - } - String nilWrapIfRequired = returnType.concat("{}"); - - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } - - var nilCheck = ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); - } - var goCodeBlock = """ - func () %s { - %s - return %s - }()"""; - - - builder.append("%1$s(".formatted(companionStruct)); - String fieldSeparator = ","; - for (final var memberShapeEntry : shape.getAllMembers().entrySet()) { - final var memberName = memberShapeEntry.getKey(); - final var memberShape = memberShapeEntry.getValue(); - final var targetShape = context.model().expectShape(memberShape.getTarget()); - builder.append("%1$s%2$s".formatted( - targetShape.accept( - new AwsSdkToDafnyShapeVisitor(context, dataSource + "." + StringUtils.capitalize(memberName), - writer, isConfigShape, memberShape.isOptional(), AwsSdkGoPointableIndex.of(context.model()).isPointable(memberShape) - )), fieldSeparator - )); - } - - - return goCodeBlock.formatted(returnType, nilCheck, someWrapIfRequired.formatted(builder.append(")").toString())); + var goCodeBlock = + """ + func () %s { + %s + return %s + }()"""; + + builder.append("%1$s(".formatted(companionStruct)); + String fieldSeparator = ","; + for (final var memberShapeEntry : shape.getAllMembers().entrySet()) { + final var memberName = memberShapeEntry.getKey(); + final var memberShape = memberShapeEntry.getValue(); + final var targetShape = context + .model() + .expectShape(memberShape.getTarget()); + builder.append( + "%1$s%2$s".formatted( + targetShape.accept( + new AwsSdkToDafnyShapeVisitor( + context, + dataSource + "." + StringUtils.capitalize(memberName), + writer, + isConfigShape, + memberShape.isOptional(), + AwsSdkGoPointableIndex + .of(context.model()) + .isPointable(memberShape) + ) + ), + fieldSeparator + ) + ); } - @Override - public String mapShape(MapShape shape) { - StringBuilder builder = new StringBuilder(); - - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - - MemberShape keyMemberShape = shape.getKey(); - final Shape keyTargetShape = context.model().expectShape(keyMemberShape.getTarget()); - MemberShape valueMemberShape = shape.getValue(); - final Shape valueTargetShape = context.model().expectShape(valueMemberShape.getTarget()); - String nilWrapIfRequired = "nil"; - String someWrapIfRequired = "%s"; - String returnType = "dafny.Map"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } - var nilCheck = ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); - } - builder.append(""" - func () %s { - %s - fieldValue := dafny.NewMapBuilder() - for key, val := range %s { - fieldValue.Add(%s, %s) - } - return %s - }()""".formatted(returnType, nilCheck, dataSource, - keyTargetShape.accept( - new AwsSdkToDafnyShapeVisitor(context, "key", writer, isConfigShape, false, false)), - valueTargetShape.accept( - new AwsSdkToDafnyShapeVisitor(context, "val", writer, isConfigShape, false, false)), - someWrapIfRequired.formatted("fieldValue.ToMap()") - ) - ); - - // Close structure - return builder.toString(); - + return goCodeBlock.formatted( + returnType, + nilCheck, + someWrapIfRequired.formatted(builder.append(")").toString()) + ); + } + + @Override + public String mapShape(MapShape shape) { + StringBuilder builder = new StringBuilder(); + + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + + MemberShape keyMemberShape = shape.getKey(); + final Shape keyTargetShape = context + .model() + .expectShape(keyMemberShape.getTarget()); + MemberShape valueMemberShape = shape.getValue(); + final Shape valueTargetShape = context + .model() + .expectShape(valueMemberShape.getTarget()); + String nilWrapIfRequired = "nil"; + String someWrapIfRequired = "%s"; + String returnType = "dafny.Map"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; } - - @Override - public String listShape(ListShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - - StringBuilder builder = new StringBuilder(); - - MemberShape memberShape = shape.getMember(); - final Shape targetShape = context.model().expectShape(memberShape.getTarget()); - - String nilWrapIfRequired = "nil"; - String someWrapIfRequired = "%s"; - String returnType = "dafny.Sequence"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } - - builder.append(""" - func () %s { - if %s == nil { return %s } - var fieldValue []interface{} = make([]interface{}, 0) - for _, val := range %s { - element := %s - fieldValue = append(fieldValue, element) - } - return %s - }()""".formatted(returnType, dataSource, nilWrapIfRequired, dataSource, - targetShape.accept( - new AwsSdkToDafnyShapeVisitor(context, "val", writer, isConfigShape, false, false) - ), someWrapIfRequired.formatted("dafny.SeqOf(fieldValue...)"))); - - // Close structure - return builder.toString(); + var nilCheck = ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); } - - @Override - public String booleanShape(BooleanShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - String nilWrapIfRequired = "false"; - String someWrapIfRequired = "%s%s"; - String returnType = "bool"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s%s)"; - returnType = "Wrappers.Option"; - } - - var dereferenceIfRequired = isPointerType ? "*" : ""; - var nilCheck = ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); - } - - return """ - func () %s { - %s - return %s - }()""".formatted(returnType, nilCheck, someWrapIfRequired.formatted(dereferenceIfRequired, dataSource)); + builder.append( + """ + func () %s { + %s + fieldValue := dafny.NewMapBuilder() + for key, val := range %s { + fieldValue.Add(%s, %s) + } + return %s + }()""".formatted( + returnType, + nilCheck, + dataSource, + keyTargetShape.accept( + new AwsSdkToDafnyShapeVisitor( + context, + "key", + writer, + isConfigShape, + false, + false + ) + ), + valueTargetShape.accept( + new AwsSdkToDafnyShapeVisitor( + context, + "val", + writer, + isConfigShape, + false, + false + ) + ), + someWrapIfRequired.formatted("fieldValue.ToMap()") + ) + ); + + // Close structure + return builder.toString(); + } + + @Override + public String listShape(ListShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + + StringBuilder builder = new StringBuilder(); + + MemberShape memberShape = shape.getMember(); + final Shape targetShape = context + .model() + .expectShape(memberShape.getTarget()); + + String nilWrapIfRequired = "nil"; + String someWrapIfRequired = "%s"; + String returnType = "dafny.Sequence"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; } - @Override - public String stringShape(StringShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - if (shape.hasTrait(EnumTrait.class)) { - String someWrapIfRequired = "%s"; - String returnType = DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape)); - if (this.isOptional) { - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } + builder.append( + """ + func () %s { + if %s == nil { return %s } + var fieldValue []interface{} = make([]interface{}, 0) + for _, val := range %s { + element := %s + fieldValue = append(fieldValue, element) + } + return %s + }()""".formatted( + returnType, + dataSource, + nilWrapIfRequired, + dataSource, + targetShape.accept( + new AwsSdkToDafnyShapeVisitor( + context, + "val", + writer, + isConfigShape, + false, + false + ) + ), + someWrapIfRequired.formatted("dafny.SeqOf(fieldValue...)") + ) + ); + + // Close structure + return builder.toString(); + } + + @Override + public String booleanShape(BooleanShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + String nilWrapIfRequired = "false"; + String someWrapIfRequired = "%s%s"; + String returnType = "bool"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s%s)"; + returnType = "Wrappers.Option"; + } - return """ - func () %s { - var index int - for _, enumVal := range %s.Values() { - index++ - if enumVal == %s{ - break; - } - } - var enum interface{} - for allEnums, i := dafny.Iterate(%s{}.AllSingletonConstructors()), 0; i < index; i++ { - var ok bool - enum, ok = allEnums() - if !ok { - break; - } - } - return %s - }()""".formatted(returnType, dataSource, dataSource, DafnyNameResolver.getDafnyCompanionStructType(shape, context.symbolProvider().toSymbol(shape)), someWrapIfRequired.formatted("enum.(%s)".formatted(DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape))))); - } else { - String nilWrapIfRequired = "nil"; - String someWrapIfRequired = "%s"; - String returnType = "dafny.Sequence"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } + var dereferenceIfRequired = isPointerType ? "*" : ""; + var nilCheck = ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); + } - var nilCheck = ""; - var dereferenceIfRequired = isPointerType ? "*" : ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); + return """ + func () %s { + %s + return %s + }()""".formatted( + returnType, + nilCheck, + someWrapIfRequired.formatted(dereferenceIfRequired, dataSource) + ); + } + + @Override + public String stringShape(StringShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + if (shape.hasTrait(EnumTrait.class)) { + String someWrapIfRequired = "%s"; + String returnType = DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ); + if (this.isOptional) { + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; + } + + return """ + func () %s { + var index int + for _, enumVal := range %s.Values() { + index++ + if enumVal == %s{ + break; + } + } + var enum interface{} + for allEnums, i := dafny.Iterate(%s{}.AllSingletonConstructors()), 0; i < index; i++ { + var ok bool + enum, ok = allEnums() + if !ok { + break; + } + } + return %s + }()""".formatted( + returnType, + dataSource, + dataSource, + DafnyNameResolver.getDafnyCompanionStructType( + shape, + context.symbolProvider().toSymbol(shape) + ), + someWrapIfRequired.formatted( + "enum.(%s)".formatted( + DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ) + ) + ) + ); + } else { + String nilWrapIfRequired = "nil"; + String someWrapIfRequired = "%s"; + String returnType = "dafny.Sequence"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; + } + + var nilCheck = ""; + var dereferenceIfRequired = isPointerType ? "*" : ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); + } + + if (shape.hasTrait(DafnyUtf8BytesTrait.class)) writer.addUseImports( + SmithyGoDependency.stdlib("unicode/utf8") + ); + + var underlyingType = shape.hasTrait(DafnyUtf8BytesTrait.class) + ? """ + dafny.SeqOf(func () []interface{} { + utf8.ValidString(%s%s) + b := []byte(%s%s) + f := make([]interface{}, len(b)) + for i, v := range b { + f[i] = v } - - if (shape.hasTrait(DafnyUtf8BytesTrait.class)) - writer.addUseImports(SmithyGoDependency.stdlib("unicode/utf8")); - - var underlyingType = shape.hasTrait(DafnyUtf8BytesTrait.class) ? """ - dafny.SeqOf(func () []interface{} { - utf8.ValidString(%s%s) - b := []byte(%s%s) - f := make([]interface{}, len(b)) - for i, v := range b { - f[i] = v - } - return f - }()...)""".formatted(dereferenceIfRequired, dataSource, dereferenceIfRequired, dataSource) : "dafny.SeqOfChars([]dafny.Char(%s%s)...)".formatted(dereferenceIfRequired, dataSource); - - return """ - func () %s { - %s - return %s - }()""".formatted(returnType, nilCheck, someWrapIfRequired.formatted(underlyingType)); - } + return f + }()...)""".formatted( + dereferenceIfRequired, + dataSource, + dereferenceIfRequired, + dataSource + ) + : "dafny.SeqOfChars([]dafny.Char(%s%s)...)".formatted( + dereferenceIfRequired, + dataSource + ); + + return """ + func () %s { + %s + return %s + }()""".formatted( + returnType, + nilCheck, + someWrapIfRequired.formatted(underlyingType) + ); } - - @Override - public String integerShape(IntegerShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - String nilWrapIfRequired = "0"; - String someWrapIfRequired = "%s%s"; - String returnType = "int"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s%s)"; - returnType = "Wrappers.Option"; - } - - var dereferenceIfRequired = isPointerType ? "*" : ""; - var nilCheck = ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); - } - - return """ - func () %s { - %s - return %s - }()""".formatted(returnType, nilCheck, someWrapIfRequired.formatted(dereferenceIfRequired, dataSource)); - + } + + @Override + public String integerShape(IntegerShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + String nilWrapIfRequired = "0"; + String someWrapIfRequired = "%s%s"; + String returnType = "int"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s%s)"; + returnType = "Wrappers.Option"; } - @Override - public String longShape(LongShape shape) { - String nilWrapIfRequired = "0"; - String someWrapIfRequired = "%s%s"; - String returnType = "int64"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s%s)"; - returnType = "Wrappers.Option"; - } - - var dereferenceIfRequired = isPointerType ? "*" : ""; - var nilCheck = ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); - } - - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - - return """ - func () %s { - %s - return %s - }()""".formatted(returnType, nilCheck, someWrapIfRequired.formatted(dereferenceIfRequired, dataSource)); + var dereferenceIfRequired = isPointerType ? "*" : ""; + var nilCheck = ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); } - @Override - public String doubleShape(DoubleShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - writer.addUseImports(SmithyGoDependency.stdlib("encoding/binary")); - writer.addUseImports(SmithyGoDependency.MATH); - - String nilWrapIfRequired = "dafny.SeqOf()"; - String someWrapIfRequired = "%s"; - String returnType = "dafny.Sequence"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } + return """ + func () %s { + %s + return %s + }()""".formatted( + returnType, + nilCheck, + someWrapIfRequired.formatted(dereferenceIfRequired, dataSource) + ); + } + + @Override + public String longShape(LongShape shape) { + String nilWrapIfRequired = "0"; + String someWrapIfRequired = "%s%s"; + String returnType = "int64"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s%s)"; + returnType = "Wrappers.Option"; + } - var dereferenceIfRequired = isPointerType ? "*" : ""; - var nilCheck = ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); - } + var dereferenceIfRequired = isPointerType ? "*" : ""; + var nilCheck = ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); + } - return """ - func () %s { - %s - var bits = math.Float64bits(%s%s) - var bytes = make([]byte, 8) - binary.LittleEndian.PutUint64(bytes, bits) - var v []interface{} - for _, e := range bytes { - v = append(v, e) - } - return %s; - }()""".formatted(returnType, nilCheck, dereferenceIfRequired, dataSource, someWrapIfRequired.formatted("dafny.SeqOf(v...)")); + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + + return """ + func () %s { + %s + return %s + }()""".formatted( + returnType, + nilCheck, + someWrapIfRequired.formatted(dereferenceIfRequired, dataSource) + ); + } + + @Override + public String doubleShape(DoubleShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + writer.addUseImports(SmithyGoDependency.stdlib("encoding/binary")); + writer.addUseImports(SmithyGoDependency.MATH); + + String nilWrapIfRequired = "dafny.SeqOf()"; + String someWrapIfRequired = "%s"; + String returnType = "dafny.Sequence"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; } - @Override - public String unionShape(UnionShape shape) { - return """ - func () Wrappers.Option { - _ = val - return Wrappers.Companion_Option_.Create_None_() - }()"""; + var dereferenceIfRequired = isPointerType ? "*" : ""; + var nilCheck = ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); } - @Override - public String timestampShape(TimestampShape shape) { - if (this.isOptional) { - return "Wrappers.Companion_Option_.Create_None_()"; - } - return "dafny.SeqOf()"; + return """ + func () %s { + %s + var bits = math.Float64bits(%s%s) + var bytes = make([]byte, 8) + binary.LittleEndian.PutUint64(bytes, bits) + var v []interface{} + for _, e := range bytes { + v = append(v, e) + } + return %s; + }()""".formatted( + returnType, + nilCheck, + dereferenceIfRequired, + dataSource, + someWrapIfRequired.formatted("dafny.SeqOf(v...)") + ); + } + + @Override + public String unionShape(UnionShape shape) { + return """ + func () Wrappers.Option { + _ = val + return Wrappers.Companion_Option_.Create_None_() + }()"""; + } + + @Override + public String timestampShape(TimestampShape shape) { + if (this.isOptional) { + return "Wrappers.Companion_Option_.Create_None_()"; } -} \ No newline at end of file + return "dafny.SeqOf()"; + } +} diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/shapevisitor/DafnyToAwsSdkShapeVisitor.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/shapevisitor/DafnyToAwsSdkShapeVisitor.java index db3d54c719..a427578d93 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/shapevisitor/DafnyToAwsSdkShapeVisitor.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/awssdk/shapevisitor/DafnyToAwsSdkShapeVisitor.java @@ -1,9 +1,11 @@ package software.amazon.polymorph.smithygo.awssdk.shapevisitor; +import java.util.Arrays; +import java.util.List; +import software.amazon.polymorph.smithygo.awssdk.AwsSdkGoPointableIndex; import software.amazon.polymorph.smithygo.codegen.GenerationContext; import software.amazon.polymorph.smithygo.codegen.GoWriter; import software.amazon.polymorph.smithygo.codegen.SmithyGoDependency; -import software.amazon.polymorph.smithygo.awssdk.AwsSdkGoPointableIndex; import software.amazon.polymorph.smithygo.codegen.SymbolUtils; import software.amazon.polymorph.smithygo.codegen.Synthetic; import software.amazon.polymorph.smithygo.codegen.knowledge.GoPointableIndex; @@ -33,363 +35,537 @@ import software.amazon.smithy.model.traits.EnumTrait; import software.amazon.smithy.utils.StringUtils; -import java.util.Arrays; -import java.util.List; - public class DafnyToAwsSdkShapeVisitor extends ShapeVisitor.Default { - private static final List shapeName = Arrays.asList("IndexSizeBytes", "ItemCount", "ProcessedSizeBytes", "TableSizeBytes"); - private final AwsSdkGoPointableIndex awsSdkGoPointableIndex; - private final GenerationContext context; - private final String dataSource; - private final GoWriter writer; - private final ServiceTrait serviceTrait; - private final boolean isOptional; - private final boolean isPointable; - public DafnyToAwsSdkShapeVisitor( - final GenerationContext context, - final String dataSource, - final GoWriter writer - ) { - this(context, dataSource, writer, false, false); - } + private static final List shapeName = Arrays.asList( + "IndexSizeBytes", + "ItemCount", + "ProcessedSizeBytes", + "TableSizeBytes" + ); + private final AwsSdkGoPointableIndex awsSdkGoPointableIndex; + private final GenerationContext context; + private final String dataSource; + private final GoWriter writer; + private final ServiceTrait serviceTrait; + private final boolean isOptional; + private final boolean isPointable; - public DafnyToAwsSdkShapeVisitor( - final GenerationContext context, - final String dataSource, - final GoWriter writer, - final boolean isOptional, - final boolean isPointable - ) { - this.context = context; - this.dataSource = dataSource; - this.writer = writer; - this.isOptional = isOptional; - this.isPointable = isPointable; - this.awsSdkGoPointableIndex = new AwsSdkGoPointableIndex(context.model()); - this.serviceTrait = context.model().expectShape(context.settings().getService(context.model()).toShapeId()).getTrait(ServiceTrait.class).get(); - } + public DafnyToAwsSdkShapeVisitor( + final GenerationContext context, + final String dataSource, + final GoWriter writer + ) { + this(context, dataSource, writer, false, false); + } - @Override - protected String getDefault(Shape shape) { - throw new CodegenException(String.format( - "Unsupported conversion of %s to %s using the %s protocol", - shape, shape.getType(), context.protocolGenerator().getName())); - } + public DafnyToAwsSdkShapeVisitor( + final GenerationContext context, + final String dataSource, + final GoWriter writer, + final boolean isOptional, + final boolean isPointable + ) { + this.context = context; + this.dataSource = dataSource; + this.writer = writer; + this.isOptional = isOptional; + this.isPointable = isPointable; + this.awsSdkGoPointableIndex = new AwsSdkGoPointableIndex(context.model()); + this.serviceTrait = + context + .model() + .expectShape(context.settings().getService(context.model()).toShapeId()) + .getTrait(ServiceTrait.class) + .get(); + } - @Override - public String blobShape(BlobShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - return """ - func () []byte { - var b []byte - if %s == nil { - return nil - } - for i := dafny.Iterate(%s) ; ; { - val, ok := i() - if !ok { - return b - } else { - b = append(b, val.(byte)) - } - } - }()""".formatted(dataSource, dataSource); - } + @Override + protected String getDefault(Shape shape) { + throw new CodegenException( + String.format( + "Unsupported conversion of %s to %s using the %s protocol", + shape, + shape.getType(), + context.protocolGenerator().getName() + ) + ); + } - @Override - public String structureShape(final StructureShape shape) { - final var builder = new StringBuilder(); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(shape.toShapeId().getNamespace()), DafnyNameResolver.dafnyTypesNamespace(shape)); - var subtype = !(awsSdkGoPointableIndex.isOperationStruct(shape) || shape.hasTrait(Synthetic.class)) - || shape.toShapeId().getName().contains("Exception"); - var nilcheck = ""; - if (this.isOptional) { - if (this.isPointable) { - nilcheck = "if %s == nil { return nil }".formatted(dataSource); - } else { - nilcheck = ""; - } - } - builder.append(""" - func() %s%s { - %s - return %s%s { - """.formatted(this.isPointable ? "*" : "", SmithyNameResolver.smithyTypesNamespaceAws(serviceTrait, subtype).concat(".").concat(shape.getId().getName()), - nilcheck, - this.isPointable ? "&" : "", - SmithyNameResolver.smithyTypesNamespaceAws(serviceTrait, subtype).concat(".").concat(shape.getId().getName())) - ); - for (final var memberShapeEntry : shape.getAllMembers().entrySet()) { - final var memberName = memberShapeEntry.getKey(); - final var memberShape = memberShapeEntry.getValue(); - final var targetShape = context.model().expectShape(memberShape.getTarget()); - //TODO: Is it ever possible for structure to be nil? - final var derivedDataSource = "%1$s%2$s%3$s%4$s".formatted(dataSource, this.isOptional ? ".(%s)".formatted(DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape))) : "", - ".Dtor_%s()".formatted(memberName), - memberShape.isOptional() ? ".UnwrapOr(nil)" : ""); - builder.append("%1$s: %2$s,".formatted( - StringUtils.capitalize(memberName), - targetShape.accept( - new DafnyToAwsSdkShapeVisitor(context, derivedDataSource, writer, memberShape.isOptional(), shapeName.contains(memberName) || awsSdkGoPointableIndex.isPointable(targetShape)) - ) - )); + @Override + public String blobShape(BlobShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + return """ + func () []byte { + var b []byte + if %s == nil { + return nil + } + for i := dafny.Iterate(%s) ; ; { + val, ok := i() + if !ok { + return b + } else { + b = append(b, val.(byte)) } + } + }()""".formatted(dataSource, dataSource); + } - return builder.append("}}()").toString(); + @Override + public String structureShape(final StructureShape shape) { + final var builder = new StringBuilder(); + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + shape.toShapeId().getNamespace() + ), + DafnyNameResolver.dafnyTypesNamespace(shape) + ); + var subtype = + !(awsSdkGoPointableIndex.isOperationStruct(shape) || + shape.hasTrait(Synthetic.class)) || + shape.toShapeId().getName().contains("Exception"); + var nilcheck = ""; + if (this.isOptional) { + if (this.isPointable) { + nilcheck = "if %s == nil { return nil }".formatted(dataSource); + } else { + nilcheck = ""; + } + } + builder.append( + """ + func() %s%s { + %s + return %s%s { + """.formatted( + this.isPointable ? "*" : "", + SmithyNameResolver + .smithyTypesNamespaceAws(serviceTrait, subtype) + .concat(".") + .concat(shape.getId().getName()), + nilcheck, + this.isPointable ? "&" : "", + SmithyNameResolver + .smithyTypesNamespaceAws(serviceTrait, subtype) + .concat(".") + .concat(shape.getId().getName()) + ) + ); + for (final var memberShapeEntry : shape.getAllMembers().entrySet()) { + final var memberName = memberShapeEntry.getKey(); + final var memberShape = memberShapeEntry.getValue(); + final var targetShape = context + .model() + .expectShape(memberShape.getTarget()); + //TODO: Is it ever possible for structure to be nil? + final var derivedDataSource = + "%1$s%2$s%3$s%4$s".formatted( + dataSource, + this.isOptional + ? ".(%s)".formatted( + DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ) + ) + : "", + ".Dtor_%s()".formatted(memberName), + memberShape.isOptional() ? ".UnwrapOr(nil)" : "" + ); + builder.append( + "%1$s: %2$s,".formatted( + StringUtils.capitalize(memberName), + targetShape.accept( + new DafnyToAwsSdkShapeVisitor( + context, + derivedDataSource, + writer, + memberShape.isOptional(), + shapeName.contains(memberName) || + awsSdkGoPointableIndex.isPointable(targetShape) + ) + ) + ) + ); } - // TODO: smithy-dafny-conversion library - @Override - public String listShape(ListShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - StringBuilder builder = new StringBuilder(); + return builder.append("}}()").toString(); + } - MemberShape memberShape = shape.getMember(); - final Shape targetShape = context.model().expectShape(memberShape.getTarget()); - var typeName = GoCodegenUtils.getType(context.symbolProvider().toSymbol(targetShape), serviceTrait); - builder.append(""" - func() []%s{ - var fieldValue []%s - %s - for i := dafny.Iterate(%s.(dafny.Sequence)); ; { - val, ok := i() - if !ok { - break - } - fieldValue = append(fieldValue, %s)} - """.formatted(typeName, typeName, this.isOptional ? """ - if %s == nil { - return nil - }""".formatted(dataSource) : "", dataSource, - targetShape.accept( - new DafnyToAwsSdkShapeVisitor(context, "val%s".formatted(memberShape.isOptional() ? ".(%s)".formatted(DafnyNameResolver.getDafnyType(targetShape, context.symbolProvider().toSymbol(targetShape))) : ""), writer, false, awsSdkGoPointableIndex.isPointable(memberShape)) - ))); + // TODO: smithy-dafny-conversion library + @Override + public String listShape(ListShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + StringBuilder builder = new StringBuilder(); - // Close structure - return builder.append("return fieldValue }()").toString(); - } + MemberShape memberShape = shape.getMember(); + final Shape targetShape = context + .model() + .expectShape(memberShape.getTarget()); + var typeName = GoCodegenUtils.getType( + context.symbolProvider().toSymbol(targetShape), + serviceTrait + ); + builder.append( + """ + func() []%s{ + var fieldValue []%s + %s + for i := dafny.Iterate(%s.(dafny.Sequence)); ; { + val, ok := i() + if !ok { + break + } + fieldValue = append(fieldValue, %s)} + """.formatted( + typeName, + typeName, + this.isOptional + ? """ + if %s == nil { + return nil + }""".formatted(dataSource) + : "", + dataSource, + targetShape.accept( + new DafnyToAwsSdkShapeVisitor( + context, + "val%s".formatted( + memberShape.isOptional() + ? ".(%s)".formatted( + DafnyNameResolver.getDafnyType( + targetShape, + context.symbolProvider().toSymbol(targetShape) + ) + ) + : "" + ), + writer, + false, + awsSdkGoPointableIndex.isPointable(memberShape) + ) + ) + ) + ); - @Override - public String mapShape(MapShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - StringBuilder builder = new StringBuilder(); + // Close structure + return builder.append("return fieldValue }()").toString(); + } - MemberShape keyMemberShape = shape.getKey(); - final Shape keyTargetShape = context.model().expectShape(keyMemberShape.getTarget()); - MemberShape valueMemberShape = shape.getValue(); - final Shape valueTargetShape = context.model().expectShape(valueMemberShape.getTarget()); - var typeName = GoCodegenUtils.getType(context.symbolProvider().toSymbol(valueTargetShape), serviceTrait); + @Override + public String mapShape(MapShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + StringBuilder builder = new StringBuilder(); - var nilCheck = ""; - if (this.isOptional) { - nilCheck = """ - if %s == nil { - return nil - } - """.formatted(dataSource); - } - builder.append(""" - func() map[string]%s { - var m map[string]%s = make(map[string]%s) - %s - for i := dafny.Iterate(%s%s.Items());; { - val, ok := i() - if !ok { - break; - } - m[%s] = %s - } - return m - }()""".formatted(typeName, typeName, typeName, nilCheck, dataSource, this.isOptional ? ".(dafny.Map)" : "", - keyTargetShape.accept( - new DafnyToAwsSdkShapeVisitor(context, "(*val.(dafny.Tuple).IndexInt(0))", writer, keyMemberShape.isOptional(), awsSdkGoPointableIndex.isPointable(keyMemberShape))), - valueTargetShape.accept( - new DafnyToAwsSdkShapeVisitor(context, "(*val.(dafny.Tuple).IndexInt(1))%s".formatted(valueMemberShape.isOptional() ? ".(%s)".formatted(DafnyNameResolver.getDafnyType(valueTargetShape, context.symbolProvider().toSymbol(valueTargetShape))) : ""), writer, false, awsSdkGoPointableIndex.isPointable(valueMemberShape)) - ) - )); - return builder.toString(); - } + MemberShape keyMemberShape = shape.getKey(); + final Shape keyTargetShape = context + .model() + .expectShape(keyMemberShape.getTarget()); + MemberShape valueMemberShape = shape.getValue(); + final Shape valueTargetShape = context + .model() + .expectShape(valueMemberShape.getTarget()); + var typeName = GoCodegenUtils.getType( + context.symbolProvider().toSymbol(valueTargetShape), + serviceTrait + ); - @Override - public String booleanShape(BooleanShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - var nilCheck = ""; - if (this.isOptional) { - if (this.isPointable) { - nilCheck = "if %s == nil { return nil }".formatted(dataSource); - } else { - nilCheck = "if %s == nil { return b }".formatted(dataSource); - } + var nilCheck = ""; + if (this.isOptional) { + nilCheck = + """ + if %s == nil { + return nil } - return """ - func() %sbool { - var b bool - %s - b = %s%s - return %sb - }()""".formatted(this.isPointable ? "*" : "", nilCheck, dataSource, this.isOptional ? ".(%s)".formatted(context.symbolProvider().toSymbol(shape).getName()) : "", - this.isPointable ? "&" : ""); + """.formatted(dataSource); } + builder.append( + """ + func() map[string]%s { + var m map[string]%s = make(map[string]%s) + %s + for i := dafny.Iterate(%s%s.Items());; { + val, ok := i() + if !ok { + break; + } + m[%s] = %s + } + return m + }()""".formatted( + typeName, + typeName, + typeName, + nilCheck, + dataSource, + this.isOptional ? ".(dafny.Map)" : "", + keyTargetShape.accept( + new DafnyToAwsSdkShapeVisitor( + context, + "(*val.(dafny.Tuple).IndexInt(0))", + writer, + keyMemberShape.isOptional(), + awsSdkGoPointableIndex.isPointable(keyMemberShape) + ) + ), + valueTargetShape.accept( + new DafnyToAwsSdkShapeVisitor( + context, + "(*val.(dafny.Tuple).IndexInt(1))%s".formatted( + valueMemberShape.isOptional() + ? ".(%s)".formatted( + DafnyNameResolver.getDafnyType( + valueTargetShape, + context.symbolProvider().toSymbol(valueTargetShape) + ) + ) + : "" + ), + writer, + false, + awsSdkGoPointableIndex.isPointable(valueMemberShape) + ) + ) + ) + ); + return builder.toString(); + } - @Override - public String stringShape(StringShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - if (shape.hasTrait(EnumTrait.class)) { - if (this.isOptional) { - return """ - func () %s.%s { - var u %s.%s - //TODO: What to do if nil - if %s == nil { - return u - } - inputEnum := %s.(%s) - index := -1; - for allEnums := dafny.Iterate(%s{}.AllSingletonConstructors()); ; { - enum, ok := allEnums() - if ok { - index++ - if enum.(%s).Equals(inputEnum) { - break; - } - } - } - return u.Values()[index] - }()""".formatted(SmithyNameResolver.smithyTypesNamespaceAws(serviceTrait, true), context.symbolProvider().toSymbol(shape).getName(), - SmithyNameResolver.smithyTypesNamespaceAws(serviceTrait, true), context.symbolProvider().toSymbol(shape).getName(), - dataSource, - dataSource, DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape)), - DafnyNameResolver.getDafnyCompanionStructType(shape, context.symbolProvider().toSymbol(shape)), - DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape))); - } else { - return """ - func () %s.%s { - var u %s.%s - - inputEnum := %s - index := -1; - for allEnums := dafny.Iterate(%s{}.AllSingletonConstructors()); ; { - enum, ok := allEnums() - if ok { - index++ - if enum.(%s).Equals(inputEnum) { - break; - } - } - } - return u.Values()[index] - }()""".formatted(SmithyNameResolver.smithyTypesNamespaceAws(serviceTrait, true), context.symbolProvider().toSymbol(shape).getName(), - SmithyNameResolver.smithyTypesNamespaceAws(serviceTrait, true), context.symbolProvider().toSymbol(shape).getName(), - dataSource, - DafnyNameResolver.getDafnyCompanionStructType(shape, context.symbolProvider().toSymbol(shape)), - DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape))); - } - } + @Override + public String booleanShape(BooleanShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + var nilCheck = ""; + if (this.isOptional) { + if (this.isPointable) { + nilCheck = "if %s == nil { return nil }".formatted(dataSource); + } else { + nilCheck = "if %s == nil { return b }".formatted(dataSource); + } + } + return """ + func() %sbool { + var b bool + %s + b = %s%s + return %sb + }()""".formatted( + this.isPointable ? "*" : "", + nilCheck, + dataSource, + this.isOptional + ? ".(%s)".formatted( + context.symbolProvider().toSymbol(shape).getName() + ) + : "", + this.isPointable ? "&" : "" + ); + } - var underlyingType = shape.hasTrait(DafnyUtf8BytesTrait.class) ? "uint8" : "dafny.Char"; - var nilCheck = ""; - if (this.isOptional) { - if (this.isPointable) { - nilCheck = "if %s == nil { return nil }".formatted(dataSource); - } else { - nilCheck = "if %s == nil { return s }".formatted(dataSource); + @Override + public String stringShape(StringShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + if (shape.hasTrait(EnumTrait.class)) { + if (this.isOptional) { + return """ + func () %s.%s { + var u %s.%s + //TODO: What to do if nil + if %s == nil { + return u } - } + inputEnum := %s.(%s) + index := -1; + for allEnums := dafny.Iterate(%s{}.AllSingletonConstructors()); ; { + enum, ok := allEnums() + if ok { + index++ + if enum.(%s).Equals(inputEnum) { + break; + } + } + } + return u.Values()[index] + }()""".formatted( + SmithyNameResolver.smithyTypesNamespaceAws(serviceTrait, true), + context.symbolProvider().toSymbol(shape).getName(), + SmithyNameResolver.smithyTypesNamespaceAws(serviceTrait, true), + context.symbolProvider().toSymbol(shape).getName(), + dataSource, + dataSource, + DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ), + DafnyNameResolver.getDafnyCompanionStructType( + shape, + context.symbolProvider().toSymbol(shape) + ), + DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ) + ); + } else { return """ - func() (%sstring) { - var s string - %s - for i := dafny.Iterate(%s) ; ; { - val, ok := i() - if !ok { - return %s[]string{s}[0] - } else { - s = s + string(val.(%s)) - } - } - }()""".formatted(this.isPointable ? "*" : "", nilCheck, dataSource, this.isPointable ? "&" : "", underlyingType); - } + func () %s.%s { + var u %s.%s - @Override - public String integerShape(IntegerShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - if (AwsSdkGoPointableIndex.of(context.model()).isPointable(shape)) { - return """ - func() *int32 { - var i int32 - if %s == nil { - return nil - } - i = %s.(int32) - return &i - }()""".formatted(dataSource, dataSource); - } else { - return "%s.(%s)".formatted(dataSource, context.symbolProvider().toSymbol(shape).getName()); - } + inputEnum := %s + index := -1; + for allEnums := dafny.Iterate(%s{}.AllSingletonConstructors()); ; { + enum, ok := allEnums() + if ok { + index++ + if enum.(%s).Equals(inputEnum) { + break; + } + } + } + return u.Values()[index] + }()""".formatted( + SmithyNameResolver.smithyTypesNamespaceAws(serviceTrait, true), + context.symbolProvider().toSymbol(shape).getName(), + SmithyNameResolver.smithyTypesNamespaceAws(serviceTrait, true), + context.symbolProvider().toSymbol(shape).getName(), + dataSource, + DafnyNameResolver.getDafnyCompanionStructType( + shape, + context.symbolProvider().toSymbol(shape) + ), + DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ) + ); + } } - @Override - public String longShape(LongShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - var nilCheck = ""; - if (this.isOptional) { - if (this.isPointable) { - nilCheck = "if %s == nil { return nil }".formatted(dataSource); - } else { - nilCheck = "if %s == nil { return i}".formatted(dataSource); - } - } - return """ - func() %sint64 { - var i int64 - %s - i = %s%s - return %si - }()""".formatted(this.isPointable ? "*" : "", nilCheck, dataSource, this.isOptional ? ".(int64)" : "", this.isPointable ? "&" : ""); + var underlyingType = shape.hasTrait(DafnyUtf8BytesTrait.class) + ? "uint8" + : "dafny.Char"; + var nilCheck = ""; + if (this.isOptional) { + if (this.isPointable) { + nilCheck = "if %s == nil { return nil }".formatted(dataSource); + } else { + nilCheck = "if %s == nil { return s }".formatted(dataSource); + } } - - @Override - public String doubleShape(DoubleShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - writer.addUseImports(SmithyGoDependency.MATH); - writer.addUseImports(SmithyGoDependency.stdlib("encoding/binary")); - var nilCheck = ""; - if (this.isOptional) { - if (this.isPointable) { - nilCheck = "if %s == nil { return nil }".formatted(dataSource); - } else { - nilCheck = "if %s == nil { var f float64; return f}".formatted(dataSource); - } + return """ + func() (%sstring) { + var s string + %s + for i := dafny.Iterate(%s) ; ; { + val, ok := i() + if !ok { + return %s[]string{s}[0] + } else { + s = s + string(val.(%s)) + } } - return """ - func () %sfloat64 { - var b []byte - %s - for i := dafny.Iterate(%s) ; ; { - val, ok := i() - if !ok { - return %s[]float64{math.Float64frombits(binary.LittleEndian.Uint64(b))}[0] - } else { - b = append(b, val.(byte)) - } - } - }()""".formatted(this.isPointable ? "*" : "", nilCheck, dataSource, this.isPointable ? "&" : ""); + }()""".formatted( + this.isPointable ? "*" : "", + nilCheck, + dataSource, + this.isPointable ? "&" : "", + underlyingType + ); + } + + @Override + public String integerShape(IntegerShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + if (AwsSdkGoPointableIndex.of(context.model()).isPointable(shape)) { + return """ + func() *int32 { + var i int32 + if %s == nil { + return nil + } + i = %s.(int32) + return &i + }()""".formatted(dataSource, dataSource); + } else { + return "%s.(%s)".formatted( + dataSource, + context.symbolProvider().toSymbol(shape).getName() + ); } + } - @Override - public String unionShape(UnionShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - return """ - func () types.%s { - _ = val - return nil - }()""".formatted(context.symbolProvider().toSymbol(shape).getName()); + @Override + public String longShape(LongShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + var nilCheck = ""; + if (this.isOptional) { + if (this.isPointable) { + nilCheck = "if %s == nil { return nil }".formatted(dataSource); + } else { + nilCheck = "if %s == nil { return i}".formatted(dataSource); + } } + return """ + func() %sint64 { + var i int64 + %s + i = %s%s + return %si + }()""".formatted( + this.isPointable ? "*" : "", + nilCheck, + dataSource, + this.isOptional ? ".(int64)" : "", + this.isPointable ? "&" : "" + ); + } - @Override - public String timestampShape(TimestampShape shape) { - return "nil"; + @Override + public String doubleShape(DoubleShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + writer.addUseImports(SmithyGoDependency.MATH); + writer.addUseImports(SmithyGoDependency.stdlib("encoding/binary")); + var nilCheck = ""; + if (this.isOptional) { + if (this.isPointable) { + nilCheck = "if %s == nil { return nil }".formatted(dataSource); + } else { + nilCheck = + "if %s == nil { var f float64; return f}".formatted(dataSource); + } } + return """ + func () %sfloat64 { + var b []byte + %s + for i := dafny.Iterate(%s) ; ; { + val, ok := i() + if !ok { + return %s[]float64{math.Float64frombits(binary.LittleEndian.Uint64(b))}[0] + } else { + b = append(b, val.(byte)) + } + } + }()""".formatted( + this.isPointable ? "*" : "", + nilCheck, + dataSource, + this.isPointable ? "&" : "" + ); + } + + @Override + public String unionShape(UnionShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + return """ + func () types.%s { + _ = val + return nil + }()""".formatted(context.symbolProvider().toSymbol(shape).getName()); + } + @Override + public String timestampShape(TimestampShape shape) { + return "nil"; + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/AddOperationShapes.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/AddOperationShapes.java index 36a3d64526..bd63a39ed6 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/AddOperationShapes.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/AddOperationShapes.java @@ -17,7 +17,6 @@ import java.util.TreeSet; import java.util.logging.Logger; - import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.AbstractShapeBuilder; @@ -31,85 +30,117 @@ * Ensures that each operation has a unique input and output shape. */ public final class AddOperationShapes { - private static final Logger LOGGER = Logger.getLogger(AddOperationShapes.class.getName()); - - private AddOperationShapes() { - } - - /** - * Processes the given model and returns a new model ensuring service operation has an unique input and output - * synthesized shape. - * - * @param model the model - * @param serviceShapeId the service shape - * @return a model with unique operation input and output shapes - */ - public static Model execute(Model model, ShapeId serviceShapeId) { - TopDownIndex topDownIndex = model.getKnowledge(TopDownIndex.class); - ServiceShape service = model.expectShape(serviceShapeId, ServiceShape.class); - TreeSet operations = new TreeSet<>(topDownIndex.getContainedOperations( - model.expectShape(serviceShapeId))); - - Model.Builder modelBuilder = model.toBuilder(); - for (OperationShape operation : operations) { - OperationShape.Builder operationBuilder = operation.toBuilder(); - ShapeId operationId = operation.getId(); - LOGGER.info(() -> "building unique input/output shapes for " + operationId); - if (operation.getInput().isPresent()) { - StructureShape newInputShape = operation.getInput() - .map(shapeId -> cloneOperationShape( - service, operationId, (StructureShape) model.expectShape(shapeId), "Input")).get(); - modelBuilder.addShape(newInputShape); - operationBuilder.input(newInputShape.toShapeId()); - } - - if (operation.getOutput().isPresent()) { - StructureShape newOutputShape = operation.getOutput() - .map(shapeId -> cloneOperationShape( - service, operationId, (StructureShape) model.expectShape(shapeId), "Output")).get(); - modelBuilder.addShape(newOutputShape); - operationBuilder.output(newOutputShape.toShapeId()); - } - - // Update operation model with the input/output shape ids - modelBuilder.addShape(operationBuilder.build()); - } - - return modelBuilder.build(); - } -// private static StructureShape emptyOperationStructure(ServiceShape service, ShapeId opShapeId, String suffix) { -// return StructureShape.builder() -// .id(ShapeId.fromParts(service.toShapeId().getNamespace(), opShapeId.getName(service) + suffix)) -// .addTrait(Synthetic.builder().build()) -// .build(); -// } - - private static StructureShape cloneOperationShape( - ServiceShape service, - ShapeId operationShapeId, - StructureShape structureShape, - String suffix - ) { - return (StructureShape) cloneShape(structureShape, operationShapeId.getName(service) + suffix); + private static final Logger LOGGER = Logger.getLogger( + AddOperationShapes.class.getName() + ); + + private AddOperationShapes() {} + + /** + * Processes the given model and returns a new model ensuring service operation has an unique input and output + * synthesized shape. + * + * @param model the model + * @param serviceShapeId the service shape + * @return a model with unique operation input and output shapes + */ + public static Model execute(Model model, ShapeId serviceShapeId) { + TopDownIndex topDownIndex = model.getKnowledge(TopDownIndex.class); + ServiceShape service = model.expectShape( + serviceShapeId, + ServiceShape.class + ); + TreeSet operations = new TreeSet<>( + topDownIndex.getContainedOperations(model.expectShape(serviceShapeId)) + ); + + Model.Builder modelBuilder = model.toBuilder(); + for (OperationShape operation : operations) { + OperationShape.Builder operationBuilder = operation.toBuilder(); + ShapeId operationId = operation.getId(); + LOGGER.info(() -> "building unique input/output shapes for " + operationId + ); + if (operation.getInput().isPresent()) { + StructureShape newInputShape = operation + .getInput() + .map(shapeId -> + cloneOperationShape( + service, + operationId, + (StructureShape) model.expectShape(shapeId), + "Input" + ) + ) + .get(); + modelBuilder.addShape(newInputShape); + operationBuilder.input(newInputShape.toShapeId()); + } + + if (operation.getOutput().isPresent()) { + StructureShape newOutputShape = operation + .getOutput() + .map(shapeId -> + cloneOperationShape( + service, + operationId, + (StructureShape) model.expectShape(shapeId), + "Output" + ) + ) + .get(); + modelBuilder.addShape(newOutputShape); + operationBuilder.output(newOutputShape.toShapeId()); + } + + // Update operation model with the input/output shape ids + modelBuilder.addShape(operationBuilder.build()); } - private static Shape cloneShape(Shape shape, String cloneShapeName) { - ShapeId cloneShapeId = ShapeId.fromParts(shape.toShapeId().getNamespace(), cloneShapeName); - - AbstractShapeBuilder builder = Shape.shapeToBuilder(shape) - .id(cloneShapeId) - .addTrait(Synthetic.builder() - .archetype(shape.getId()) - .build()); - - shape.members().forEach(memberShape -> { - builder.addMember(memberShape.toBuilder() - .id(cloneShapeId.withMember(memberShape.getMemberName())) - .build()); - }); - - - return (Shape) builder.build(); - } -} \ No newline at end of file + return modelBuilder.build(); + } + + // private static StructureShape emptyOperationStructure(ServiceShape service, ShapeId opShapeId, String suffix) { + // return StructureShape.builder() + // .id(ShapeId.fromParts(service.toShapeId().getNamespace(), opShapeId.getName(service) + suffix)) + // .addTrait(Synthetic.builder().build()) + // .build(); + // } + + private static StructureShape cloneOperationShape( + ServiceShape service, + ShapeId operationShapeId, + StructureShape structureShape, + String suffix + ) { + return (StructureShape) cloneShape( + structureShape, + operationShapeId.getName(service) + suffix + ); + } + + private static Shape cloneShape(Shape shape, String cloneShapeName) { + ShapeId cloneShapeId = ShapeId.fromParts( + shape.toShapeId().getNamespace(), + cloneShapeName + ); + + AbstractShapeBuilder builder = Shape + .shapeToBuilder(shape) + .id(cloneShapeId) + .addTrait(Synthetic.builder().archetype(shape.getId()).build()); + + shape + .members() + .forEach(memberShape -> { + builder.addMember( + memberShape + .toBuilder() + .id(cloneShapeId.withMember(memberShape.getMemberName())) + .build() + ); + }); + + return (Shape) builder.build(); + } +} diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/ApplicationProtocol.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/ApplicationProtocol.java index a436b7d90a..d66d096247 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/ApplicationProtocol.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/ApplicationProtocol.java @@ -2,10 +2,14 @@ import software.amazon.smithy.codegen.core.SymbolReference; import software.amazon.smithy.utils.SmithyUnstableApi; + /** * Represents the resolves {@link Symbol}s and references for an * application protocol (e.g., "http", "mqtt", etc). */ @SmithyUnstableApi -public record ApplicationProtocol(String name, SymbolReference requestType, SymbolReference responseType) { -} +public record ApplicationProtocol( + String name, + SymbolReference requestType, + SymbolReference responseType +) {} diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/CodegenUtils.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/CodegenUtils.java index 9e4d363813..aef04700fa 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/CodegenUtils.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/CodegenUtils.java @@ -15,6 +15,17 @@ package software.amazon.polymorph.smithygo.codegen; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.logging.Logger; import software.amazon.polymorph.smithygo.codegen.knowledge.GoPointableIndex; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; @@ -31,402 +42,422 @@ import software.amazon.smithy.model.traits.TitleTrait; import software.amazon.smithy.utils.StringUtils; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.Charset; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.logging.Logger; - /** * Utility methods likely to be needed across packages. */ public final class CodegenUtils { - private static final Logger LOGGER = Logger.getLogger(CodegenUtils.class.getName()); - - private static final String SYNTHETIC_NAMESPACE = "smithy.go.synthetic"; - - private CodegenUtils() { - } - - /** - * Executes a given shell command in a given directory. - * - * @param command The string command to execute, e.g. "go fmt". - * @param directory The directory to run the command in. - * @return Returns the console output of the command. - */ - public static String runCommand(String command, Path directory) { - String[] finalizedCommand; - if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { - finalizedCommand = new String[]{"cmd.exe", "/c", command}; - } else { - finalizedCommand = new String[]{"sh", "-c", command}; - } - - ProcessBuilder processBuilder = new ProcessBuilder(finalizedCommand) - .redirectErrorStream(true) - .directory(directory.toFile()); - - try { - Process process = processBuilder.start(); - List output = new ArrayList<>(); - - // Capture output for reporting. - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( - process.getInputStream(), Charset.defaultCharset()))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - LOGGER.finest(line); - output.add(line); - } - } - - process.waitFor(); - process.destroy(); - - String joinedOutput = String.join(System.lineSeparator(), output); - if (process.exitValue() != 0) { - throw new CodegenException(String.format( - "Command `%s` failed with output:%n%n%s", command, joinedOutput)); - } - return joinedOutput; - } catch (InterruptedException | IOException e) { - throw new CodegenException(e); - } + private static final Logger LOGGER = Logger.getLogger( + CodegenUtils.class.getName() + ); + + private static final String SYNTHETIC_NAMESPACE = "smithy.go.synthetic"; + + private CodegenUtils() {} + + /** + * Executes a given shell command in a given directory. + * + * @param command The string command to execute, e.g. "go fmt". + * @param directory The directory to run the command in. + * @return Returns the console output of the command. + */ + public static String runCommand(String command, Path directory) { + String[] finalizedCommand; + if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { + finalizedCommand = new String[] { "cmd.exe", "/c", command }; + } else { + finalizedCommand = new String[] { "sh", "-c", command }; } - /** - * Gets the name under which the given package will be exported by default. - * - * @param packageName The full package name of the exported package. - * @return The name a the package will be imported under by default. - */ - public static String getDefaultPackageImportName(String packageName) { - if (StringUtils.isBlank(packageName) || !packageName.contains("/")) { - return packageName; + ProcessBuilder processBuilder = new ProcessBuilder(finalizedCommand) + .redirectErrorStream(true) + .directory(directory.toFile()); + + try { + Process process = processBuilder.start(); + List output = new ArrayList<>(); + + // Capture output for reporting. + try ( + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader( + process.getInputStream(), + Charset.defaultCharset() + ) + ) + ) { + String line; + while ((line = bufferedReader.readLine()) != null) { + LOGGER.finest(line); + output.add(line); } - return packageName.substring(packageName.lastIndexOf('/') + 1); + } + + process.waitFor(); + process.destroy(); + + String joinedOutput = String.join(System.lineSeparator(), output); + if (process.exitValue() != 0) { + throw new CodegenException( + String.format( + "Command `%s` failed with output:%n%n%s", + command, + joinedOutput + ) + ); + } + return joinedOutput; + } catch (InterruptedException | IOException e) { + throw new CodegenException(e); } - - /** - * Gets the alias to use when referencing the given symbol outside of its namespace. - * - *

The default value is the last path component of the symbol's namespace. - * - * @param symbol The symbol whose whose namespace alias should be retrieved. - * @return The alias of the symbol's namespace. - */ - public static String getSymbolNamespaceAlias(Symbol symbol) { - return symbol.getProperty(SymbolUtils.NAMESPACE_ALIAS, String.class) - .filter(StringUtils::isNotBlank) - .orElse(CodegenUtils.getDefaultPackageImportName(symbol.getNamespace())); + } + + /** + * Gets the name under which the given package will be exported by default. + * + * @param packageName The full package name of the exported package. + * @return The name a the package will be imported under by default. + */ + public static String getDefaultPackageImportName(String packageName) { + if (StringUtils.isBlank(packageName) || !packageName.contains("/")) { + return packageName; } - - /** - * Detects if an annotated mediatype indicates JSON contents. - * - * @param mediaType The media type to inspect. - * @return If the media type indicates JSON contents. - */ - public static boolean isJsonMediaType(String mediaType) { - return mediaType.equals("application/json") || mediaType.endsWith("+json"); + return packageName.substring(packageName.lastIndexOf('/') + 1); + } + + /** + * Gets the alias to use when referencing the given symbol outside of its namespace. + * + *

The default value is the last path component of the symbol's namespace. + * + * @param symbol The symbol whose whose namespace alias should be retrieved. + * @return The alias of the symbol's namespace. + */ + public static String getSymbolNamespaceAlias(Symbol symbol) { + return symbol + .getProperty(SymbolUtils.NAMESPACE_ALIAS, String.class) + .filter(StringUtils::isNotBlank) + .orElse(CodegenUtils.getDefaultPackageImportName(symbol.getNamespace())); + } + + /** + * Detects if an annotated mediatype indicates JSON contents. + * + * @param mediaType The media type to inspect. + * @return If the media type indicates JSON contents. + */ + public static boolean isJsonMediaType(String mediaType) { + return mediaType.equals("application/json") || mediaType.endsWith("+json"); + } + + /** + * Get the namespace where synthetic types are generated at runtime. + * + * @return synthetic type namespace + */ + public static String getSyntheticTypeNamespace() { + return CodegenUtils.SYNTHETIC_NAMESPACE; + } + + /** + * Get if the passed in shape is decorated as a synthetic clone, but there is no other shape the clone is + * created from. + * + * @param shape the shape to check if its a stubbed synthetic clone without an archetype. + * @return if the shape is synthetic clone, but not based on a specific shape. + */ + public static boolean isStubSynthetic(Shape shape) { + Optional optional = shape.getTrait(Synthetic.class); + if (!optional.isPresent()) { + return false; } - /** - * Get the namespace where synthetic types are generated at runtime. - * - * @return synthetic type namespace - */ - public static String getSyntheticTypeNamespace() { - return CodegenUtils.SYNTHETIC_NAMESPACE; + Synthetic synth = optional.get(); + return !synth.getArchetype().isPresent(); + } + + /** + * Returns the operand decorated with an & if the address of the shape type can be taken. + * + * @param model API model reference + * @param pointableIndex pointable index + * @param shape shape to use + * @param operand value to decorate + * @return updated operand + */ + public static String asAddressIfAddressable( + Model model, + GoPointableIndex pointableIndex, + Shape shape, + String operand + ) { + boolean isStruct = shape.getType() == ShapeType.STRUCTURE; + if (shape.isMemberShape()) { + isStruct = + model.expectShape(shape.asMemberShape().get().getTarget()).getType() == + ShapeType.STRUCTURE; } - /** - * Get if the passed in shape is decorated as a synthetic clone, but there is no other shape the clone is - * created from. - * - * @param shape the shape to check if its a stubbed synthetic clone without an archetype. - * @return if the shape is synthetic clone, but not based on a specific shape. - */ - public static boolean isStubSynthetic(Shape shape) { - Optional optional = shape.getTrait(Synthetic.class); - if (!optional.isPresent()) { - return false; - } - - Synthetic synth = optional.get(); - return !synth.getArchetype().isPresent(); + boolean shouldAddress = pointableIndex.isPointable(shape) && isStruct; + return shouldAddress ? "&" + operand : operand; + } + + /** + * Returns the operand decorated with an "*" if the shape is dereferencable. + * + * @param pointableIndex knowledge index for if shape is pointable. + * @param shape The shape whose value needs to be read. + * @param operand The value to be read from. + * @return updated operand + */ + public static String getAsValueIfDereferencable( + GoPointableIndex pointableIndex, + Shape shape, + String operand + ) { + if (!pointableIndex.isDereferencable(shape)) { + return operand; } - /** - * Returns the operand decorated with an & if the address of the shape type can be taken. - * - * @param model API model reference - * @param pointableIndex pointable index - * @param shape shape to use - * @param operand value to decorate - * @return updated operand - */ - public static String asAddressIfAddressable( - Model model, - GoPointableIndex pointableIndex, - Shape shape, - String operand - ) { - boolean isStruct = shape.getType() == ShapeType.STRUCTURE; - if (shape.isMemberShape()) { - isStruct = model.expectShape(shape.asMemberShape().get().getTarget()).getType() == ShapeType.STRUCTURE; - } - - boolean shouldAddress = pointableIndex.isPointable(shape) && isStruct; - return shouldAddress ? "&" + operand : operand; + return '*' + operand; + } + + /** + * Returns the operand decorated as a pointer type, without creating double pointer. + * + * @param pointableIndex knowledge index for if shape is pointable. + * @param shape The shape whose value of the type. + * @param operand The value to read. + * @return updated operand + */ + public static String getTypeAsTypePointer( + GoPointableIndex pointableIndex, + Shape shape, + String operand + ) { + if (pointableIndex.isPointable(shape)) { + return operand; } - /** - * Returns the operand decorated with an "*" if the shape is dereferencable. - * - * @param pointableIndex knowledge index for if shape is pointable. - * @param shape The shape whose value needs to be read. - * @param operand The value to be read from. - * @return updated operand - */ - public static String getAsValueIfDereferencable( - GoPointableIndex pointableIndex, - Shape shape, - String operand - ) { - if (!pointableIndex.isDereferencable(shape)) { - return operand; - } - - return '*' + operand; + return '*' + operand; + } + + /** + * Get the pointer reference to operand , if symbol is pointable. + * This method can be used by deserializers to get pointer to + * operand. + * + * @param model model for api. + * @param writer The writer dependencies will be added to, if needed. + * @param pointableIndex knowledge index for if shape is pointable. + * @param shape The shape whose value needs to be assigned. + * @param operand The Operand is the value to be assigned to the symbol shape. + * @return The Operand, along with pointer reference if applicable + */ + public static String getAsPointerIfPointable( + Model model, + GoWriter writer, + GoPointableIndex pointableIndex, + Shape shape, + String operand + ) { + if (!pointableIndex.isPointable(shape)) { + return operand; } - /** - * Returns the operand decorated as a pointer type, without creating double pointer. - * - * @param pointableIndex knowledge index for if shape is pointable. - * @param shape The shape whose value of the type. - * @param operand The value to read. - * @return updated operand - */ - public static String getTypeAsTypePointer( - GoPointableIndex pointableIndex, - Shape shape, - String operand - ) { - if (pointableIndex.isPointable(shape)) { - return operand; - } - - return '*' + operand; + if (shape.isMemberShape()) { + shape = model.expectShape(shape.asMemberShape().get().getTarget()); } - /** - * Get the pointer reference to operand , if symbol is pointable. - * This method can be used by deserializers to get pointer to - * operand. - * - * @param model model for api. - * @param writer The writer dependencies will be added to, if needed. - * @param pointableIndex knowledge index for if shape is pointable. - * @param shape The shape whose value needs to be assigned. - * @param operand The Operand is the value to be assigned to the symbol shape. - * @return The Operand, along with pointer reference if applicable - */ - public static String getAsPointerIfPointable( - Model model, - GoWriter writer, - GoPointableIndex pointableIndex, - Shape shape, - String operand - ) { - if (!pointableIndex.isPointable(shape)) { - return operand; - } - - if (shape.isMemberShape()) { - shape = model.expectShape(shape.asMemberShape().get().getTarget()); - } - - String prefix = ""; - String suffix = ")"; - - switch (shape.getType()) { - case STRING: - prefix = "ptr.String("; - break; - - case BOOLEAN: - prefix = "ptr.Bool("; - break; - - case BYTE: - prefix = "ptr.Int8("; - break; - case SHORT: - prefix = "ptr.Int16("; - break; - case INTEGER: - prefix = "ptr.Int32("; - break; - case LONG: - prefix = "ptr.Int64("; - break; - - case FLOAT: - prefix = "ptr.Float32("; - break; - case DOUBLE: - prefix = "ptr.Float64("; - break; - - case TIMESTAMP: - prefix = "ptr.Time("; - break; - - default: - return '&' + operand; - } - - writer.addUseImports(SmithyGoDependency.SMITHY_PTR); - return prefix + operand + suffix; + String prefix = ""; + String suffix = ")"; + + switch (shape.getType()) { + case STRING: + prefix = "ptr.String("; + break; + case BOOLEAN: + prefix = "ptr.Bool("; + break; + case BYTE: + prefix = "ptr.Int8("; + break; + case SHORT: + prefix = "ptr.Int16("; + break; + case INTEGER: + prefix = "ptr.Int32("; + break; + case LONG: + prefix = "ptr.Int64("; + break; + case FLOAT: + prefix = "ptr.Float32("; + break; + case DOUBLE: + prefix = "ptr.Float64("; + break; + case TIMESTAMP: + prefix = "ptr.Time("; + break; + default: + return '&' + operand; } - /** - * Returns the shape unpacked as a CollectionShape. Throws an exception if the passed in - * shape is not a list or set. - * - * @param shape the list or set shape. - * @return The unpacked CollectionShape. - */ - public static CollectionShape expectCollectionShape(Shape shape) { - if (shape instanceof CollectionShape) { - return (CollectionShape) (shape); - } - - throw new CodegenException("expect shape " + shape.getId() + " to be Collection, was " + shape.getType()); + writer.addUseImports(SmithyGoDependency.SMITHY_PTR); + return prefix + operand + suffix; + } + + /** + * Returns the shape unpacked as a CollectionShape. Throws an exception if the passed in + * shape is not a list or set. + * + * @param shape the list or set shape. + * @return The unpacked CollectionShape. + */ + public static CollectionShape expectCollectionShape(Shape shape) { + if (shape instanceof CollectionShape) { + return (CollectionShape) (shape); } - /** - * Returns the shape unpacked as a MapShape. Throws an exception if the passed in - * shape is not a map. - * - * @param shape the map shape. - * @return The unpacked MapShape. - */ - public static MapShape expectMapShape(Shape shape) { - if (shape instanceof MapShape) { - return (MapShape) (shape); - } - - throw new CodegenException("expect shape " + shape.getId() + " to be Map, was " + shape.getType()); + throw new CodegenException( + "expect shape " + + shape.getId() + + " to be Collection, was " + + shape.getType() + ); + } + + /** + * Returns the shape unpacked as a MapShape. Throws an exception if the passed in + * shape is not a map. + * + * @param shape the map shape. + * @return The unpacked MapShape. + */ + public static MapShape expectMapShape(Shape shape) { + if (shape instanceof MapShape) { + return (MapShape) (shape); } - /** - * Comparator to sort ShapeMember lists alphabetically, with required members first followed by optional members. - */ - public static final class SortedMembers implements Comparator { - private final SymbolProvider symbolProvider; - - /** - * Initializes the SortedMembers. - * - * @param symbolProvider symbol provider used for codegen. - */ - public SortedMembers(SymbolProvider symbolProvider) { - this.symbolProvider = symbolProvider; - } + throw new CodegenException( + "expect shape " + shape.getId() + " to be Map, was " + shape.getType() + ); + } - @Override - public int compare(MemberShape a, MemberShape b) { - // first compare if the members are required or not, which ever member is required should win. If both - // members are required or not required, continue on to alphabetic search. - - // If a is required but b isn't return -1 so a is sorted before b - // If b is required but a isn't, return 1 so a is sorted after b - // If both a and b are required or optional, use alphabetic sorting of a and b's member name. - - int requiredMember = 0; - if (a.hasTrait(RequiredTrait.class)) { - requiredMember -= 1; - } - if (b.hasTrait(RequiredTrait.class)) { - requiredMember += 1; - } - if (requiredMember != 0) { - return requiredMember; - } - - return symbolProvider.toMemberName(a) - .compareTo(symbolProvider.toMemberName(b)); - } - } + /** + * Comparator to sort ShapeMember lists alphabetically, with required members first followed by optional members. + */ + public static final class SortedMembers implements Comparator { - /** - * Attempts to find the first member by exact name in the containing structure. If the member is not found an - * exception will be thrown. - * - * @param shape structure containing member - * @param name member name - * @return MemberShape if found - */ - public static MemberShape expectMember(StructureShape shape, String name) { - return expectMember(shape, name::equals); - } + private final SymbolProvider symbolProvider; /** - * Attempts to find the first member by name using a member name predicate in the containing structure. If the - * member is not found an exception will be thrown. + * Initializes the SortedMembers. * - * @param shape structure containing member - * @param memberNamePredicate member name to search for - * @return MemberShape if found + * @param symbolProvider symbol provider used for codegen. */ - public static MemberShape expectMember(StructureShape shape, Predicate memberNamePredicate) { - return shape.getAllMembers().values().stream() - .filter((p) -> memberNamePredicate.test(p.getMemberName())) - .findFirst() - .orElseThrow(() -> new CodegenException("did not find member in structure shape, " + shape.getId())); + public SortedMembers(SymbolProvider symbolProvider) { + this.symbolProvider = symbolProvider; } - /** - * Attempts to get the title of the API's service from the model. If unalbe to get the title the fallback value - * will be returned instead. - * - * @param shape service shape - * @param fallback string to return if service does not have a title - * @return title of service - */ - public static String getServiceTitle(ServiceShape shape, String fallback) { - return shape.getTrait(TitleTrait.class).map(TitleTrait::getValue).orElse(fallback); + @Override + public int compare(MemberShape a, MemberShape b) { + // first compare if the members are required or not, which ever member is required should win. If both + // members are required or not required, continue on to alphabetic search. + + // If a is required but b isn't return -1 so a is sorted before b + // If b is required but a isn't, return 1 so a is sorted after b + // If both a and b are required or optional, use alphabetic sorting of a and b's member name. + + int requiredMember = 0; + if (a.hasTrait(RequiredTrait.class)) { + requiredMember -= 1; + } + if (b.hasTrait(RequiredTrait.class)) { + requiredMember += 1; + } + if (requiredMember != 0) { + return requiredMember; + } + + return symbolProvider + .toMemberName(a) + .compareTo(symbolProvider.toMemberName(b)); } - - /** - * isNumber returns if the shape is a number shape. - * - * @param shape shape to check - * @return true if is a number shape. - */ - public static boolean isNumber(Shape shape) { - switch (shape.getType()) { - case BYTE: - case SHORT: - case INTEGER: - case INT_ENUM: - case LONG: - case FLOAT: - case DOUBLE: - return true; - default: - return false; - } + } + + /** + * Attempts to find the first member by exact name in the containing structure. If the member is not found an + * exception will be thrown. + * + * @param shape structure containing member + * @param name member name + * @return MemberShape if found + */ + public static MemberShape expectMember(StructureShape shape, String name) { + return expectMember(shape, name::equals); + } + + /** + * Attempts to find the first member by name using a member name predicate in the containing structure. If the + * member is not found an exception will be thrown. + * + * @param shape structure containing member + * @param memberNamePredicate member name to search for + * @return MemberShape if found + */ + public static MemberShape expectMember( + StructureShape shape, + Predicate memberNamePredicate + ) { + return shape + .getAllMembers() + .values() + .stream() + .filter(p -> memberNamePredicate.test(p.getMemberName())) + .findFirst() + .orElseThrow(() -> + new CodegenException( + "did not find member in structure shape, " + shape.getId() + ) + ); + } + + /** + * Attempts to get the title of the API's service from the model. If unalbe to get the title the fallback value + * will be returned instead. + * + * @param shape service shape + * @param fallback string to return if service does not have a title + * @return title of service + */ + public static String getServiceTitle(ServiceShape shape, String fallback) { + return shape + .getTrait(TitleTrait.class) + .map(TitleTrait::getValue) + .orElse(fallback); + } + + /** + * isNumber returns if the shape is a number shape. + * + * @param shape shape to check + * @return true if is a number shape. + */ + public static boolean isNumber(Shape shape) { + switch (shape.getType()) { + case BYTE: + case SHORT: + case INTEGER: + case INT_ENUM: + case LONG: + case FLOAT: + case DOUBLE: + return true; + default: + return false; } + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/EnumGenerator.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/EnumGenerator.java index 739c4ea685..f86ec20ccf 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/EnumGenerator.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/EnumGenerator.java @@ -19,7 +19,6 @@ import java.util.Locale; import java.util.Set; import java.util.logging.Logger; - import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.model.shapes.EnumShape; @@ -33,66 +32,101 @@ * Renders enums and their constants. */ public final class EnumGenerator implements Runnable { - private static final Logger LOGGER = Logger.getLogger(EnumGenerator.class.getName()); - - private final SymbolProvider symbolProvider; - private final GoWriter writer; - private final Shape shape; - public EnumGenerator(SymbolProvider symbolProvider, GoWriter writer, Shape shape) { - this.symbolProvider = symbolProvider; - this.writer = writer; - this.shape = shape; - } + private static final Logger LOGGER = Logger.getLogger( + EnumGenerator.class.getName() + ); - @Override - public void run() { - Symbol symbol = symbolProvider.toSymbol(shape); - EnumTrait enumTrait = shape.expectTrait(EnumTrait.class); + private final SymbolProvider symbolProvider; + private final GoWriter writer; + private final Shape shape; - writer.write("type $L string", symbol.getName()).write(""); + public EnumGenerator( + SymbolProvider symbolProvider, + GoWriter writer, + Shape shape + ) { + this.symbolProvider = symbolProvider; + this.writer = writer; + this.shape = shape; + } - // Don't generate constants if there are no explicitly modeled names. We only need to - // look at one, since Smithy validates that if one has a name then they must all have - // a name. - if (enumTrait.getValues().get(0).getName().isPresent()) { - Set constants = new LinkedHashSet<>(); - writer.openBlock("const (", ")", () -> { - for (EnumDefinition definition : enumTrait.getValues()) { - StringBuilder labelBuilder = new StringBuilder(symbol.getName()); - String name = definition.getName().get(); + @Override + public void run() { + Symbol symbol = symbolProvider.toSymbol(shape); + EnumTrait enumTrait = shape.expectTrait(EnumTrait.class); - for (String part : name.split("(?U)[\\W_]")) { - if (part.matches(".*[a-z].*") && part.matches(".*[A-Z].*")) { - // Mixed case names should not be changed other than first letter capitalized. - labelBuilder.append(StringUtils.capitalize(part)); - } else { - // For all non-mixed case parts title case first letter, followed by all other lower cased. - labelBuilder.append(StringUtils.capitalize(part.toLowerCase(Locale.US))); - } - } - String label = labelBuilder.toString(); + writer.write("type $L string", symbol.getName()).write(""); - // If camel-casing would cause a conflict, don't camel-case this enum value. - if (constants.contains(label)) { - LOGGER.warning(String.format( - "Multiple enums resolved to the same name, `%s`, using unaltered value for: %s", - label, name)); - label = name; - } - constants.add(label); + // Don't generate constants if there are no explicitly modeled names. We only need to + // look at one, since Smithy validates that if one has a name then they must all have + // a name. + if (enumTrait.getValues().get(0).getName().isPresent()) { + Set constants = new LinkedHashSet<>(); + writer + .openBlock( + "const (", + ")", + () -> { + for (EnumDefinition definition : enumTrait.getValues()) { + StringBuilder labelBuilder = new StringBuilder(symbol.getName()); + String name = definition.getName().get(); - writer.write("$L $L = $S", label, symbol.getName(), definition.getValue()); + for (String part : name.split("(?U)[\\W_]")) { + if (part.matches(".*[a-z].*") && part.matches(".*[A-Z].*")) { + // Mixed case names should not be changed other than first letter capitalized. + labelBuilder.append(StringUtils.capitalize(part)); + } else { + // For all non-mixed case parts title case first letter, followed by all other lower cased. + labelBuilder.append( + StringUtils.capitalize(part.toLowerCase(Locale.US)) + ); } - }).write(""); - } + } + String label = labelBuilder.toString(); - writer.openBlock("func ($L) Values() []$L {", "}", symbol.getName(), symbol.getName(), () -> { - writer.openBlock("return []$L{", "}", symbol.getName(), () -> { - for (EnumDefinition definition : enumTrait.getValues()) { - writer.write("$S,", definition.getValue()); - } - }); - }); + // If camel-casing would cause a conflict, don't camel-case this enum value. + if (constants.contains(label)) { + LOGGER.warning( + String.format( + "Multiple enums resolved to the same name, `%s`, using unaltered value for: %s", + label, + name + ) + ); + label = name; + } + constants.add(label); + + writer.write( + "$L $L = $S", + label, + symbol.getName(), + definition.getValue() + ); + } + } + ) + .write(""); } -} \ No newline at end of file + + writer.openBlock( + "func ($L) Values() []$L {", + "}", + symbol.getName(), + symbol.getName(), + () -> { + writer.openBlock( + "return []$L{", + "}", + symbol.getName(), + () -> { + for (EnumDefinition definition : enumTrait.getValues()) { + writer.write("$S,", definition.getValue()); + } + } + ); + } + ); + } +} diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GenerationContext.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GenerationContext.java index 7fb586c8a6..d14117be43 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GenerationContext.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GenerationContext.java @@ -1,5 +1,7 @@ package software.amazon.polymorph.smithygo.codegen; +import java.util.ArrayList; +import java.util.List; import software.amazon.polymorph.smithygo.codegen.integration.GoIntegration; import software.amazon.polymorph.smithygo.codegen.integration.ProtocolGenerator; import software.amazon.smithy.build.FileManifest; @@ -8,152 +10,160 @@ import software.amazon.smithy.model.Model; import software.amazon.smithy.utils.SmithyBuilder; -import java.util.ArrayList; -import java.util.List; - -public class GenerationContext implements CodegenContext { - private final Model model; - private final GoSettings settings; - private final SymbolProvider symbolProvider; - private final FileManifest fileManifest; - private final GoDelegator writerDelegator; - private final List integrations; - private final ProtocolGenerator protocolGenerator; - - /** - * @return Returns the protocol generator to use in code generation. - */ - public ProtocolGenerator protocolGenerator() { - return protocolGenerator; - } - - private GenerationContext(Builder builder) { - model = SmithyBuilder.requiredState("model", builder.model); - settings = SmithyBuilder.requiredState("settings", builder.settings); - symbolProvider = SmithyBuilder.requiredState("symbolProvider", builder.symbolProvider); - fileManifest = SmithyBuilder.requiredState("fileManifest", builder.fileManifest); - writerDelegator = SmithyBuilder.requiredState("writerDelegator", builder.writerDelegator); - integrations = SmithyBuilder.requiredState("integrations", builder.integrations); - protocolGenerator = SmithyBuilder.requiredState("protocolGenerator", builder.protocolGenerator); - } +public class GenerationContext + implements CodegenContext { + + private final Model model; + private final GoSettings settings; + private final SymbolProvider symbolProvider; + private final FileManifest fileManifest; + private final GoDelegator writerDelegator; + private final List integrations; + private final ProtocolGenerator protocolGenerator; + + /** + * @return Returns the protocol generator to use in code generation. + */ + public ProtocolGenerator protocolGenerator() { + return protocolGenerator; + } + + private GenerationContext(Builder builder) { + model = SmithyBuilder.requiredState("model", builder.model); + settings = SmithyBuilder.requiredState("settings", builder.settings); + symbolProvider = + SmithyBuilder.requiredState("symbolProvider", builder.symbolProvider); + fileManifest = + SmithyBuilder.requiredState("fileManifest", builder.fileManifest); + writerDelegator = + SmithyBuilder.requiredState("writerDelegator", builder.writerDelegator); + integrations = + SmithyBuilder.requiredState("integrations", builder.integrations); + protocolGenerator = + SmithyBuilder.requiredState( + "protocolGenerator", + builder.protocolGenerator + ); + } + + @Override + public Model model() { + return model; + } + + @Override + public GoSettings settings() { + return settings; + } + + @Override + public SymbolProvider symbolProvider() { + return symbolProvider; + } + + @Override + public FileManifest fileManifest() { + return fileManifest; + } + + @Override + public GoDelegator writerDelegator() { + return writerDelegator; + } + + @Override + public List integrations() { + return integrations; + } + + /** + * @return Returns a builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builds {@link GenerationContext}s. + */ + public static final class Builder + implements SmithyBuilder { + + private Model model; + private GoSettings settings; + private SymbolProvider symbolProvider; + private FileManifest fileManifest; + private GoDelegator writerDelegator; + private List integrations = new ArrayList<>(); + private ProtocolGenerator protocolGenerator; @Override - public Model model() { - return model; + public GenerationContext build() { + return new GenerationContext(this); } - @Override - public GoSettings settings() { - return settings; + /** + * @param model The model being generated. + * @return Returns the builder. + */ + public Builder model(Model model) { + this.model = model; + return this; } - @Override - public SymbolProvider symbolProvider() { - return symbolProvider; + /** + * @param settings The resolved settings for the generator. + * @return Returns the builder. + */ + public Builder settings(GoSettings settings) { + this.settings = settings; + return this; } - @Override - public FileManifest fileManifest() { - return fileManifest; + /** + * @param symbolProvider The finalized symbol provider for the generator. + * @return Returns the builder. + */ + public Builder symbolProvider(SymbolProvider symbolProvider) { + this.symbolProvider = symbolProvider; + return this; } - @Override - public GoDelegator writerDelegator() { - return writerDelegator; + /** + * @param fileManifest The file manifest being used in the generator. + * @return Returns the builder. + */ + public Builder fileManifest(FileManifest fileManifest) { + this.fileManifest = fileManifest; + return this; } - @Override - public List integrations() { - return integrations; + /** + * @param writerDelegator The writer delegator to use in the generator. + * @return Returns the builder. + */ + public Builder writerDelegator(GoDelegator writerDelegator) { + this.writerDelegator = writerDelegator; + return this; } /** - * @return Returns a builder. + * @param integrations The integrations to use in the generator. + * @return Returns the builder. */ - public static Builder builder() { - return new Builder(); + public Builder integrations(List integrations) { + this.integrations.clear(); + this.integrations.addAll(integrations); + return this; } /** - * Builds {@link GenerationContext}s. + * @param protocolGenerator The resolved protocol generator to use. + * @return Returns the builder. */ - public static final class Builder implements SmithyBuilder { - private Model model; - private GoSettings settings; - private SymbolProvider symbolProvider; - private FileManifest fileManifest; - private GoDelegator writerDelegator; - private List integrations = new ArrayList<>(); - private ProtocolGenerator protocolGenerator; - - @Override - public GenerationContext build() { - return new GenerationContext(this); - } - - /** - * @param model The model being generated. - * @return Returns the builder. - */ - public Builder model(Model model) { - this.model = model; - return this; - } - - /** - * @param settings The resolved settings for the generator. - * @return Returns the builder. - */ - public Builder settings(GoSettings settings) { - this.settings = settings; - return this; - } - - /** - * @param symbolProvider The finalized symbol provider for the generator. - * @return Returns the builder. - */ - public Builder symbolProvider(SymbolProvider symbolProvider) { - this.symbolProvider = symbolProvider; - return this; - } - - /** - * @param fileManifest The file manifest being used in the generator. - * @return Returns the builder. - */ - public Builder fileManifest(FileManifest fileManifest) { - this.fileManifest = fileManifest; - return this; - } - - /** - * @param writerDelegator The writer delegator to use in the generator. - * @return Returns the builder. - */ - public Builder writerDelegator(GoDelegator writerDelegator) { - this.writerDelegator = writerDelegator; - return this; - } - - /** - * @param integrations The integrations to use in the generator. - * @return Returns the builder. - */ - public Builder integrations(List integrations) { - this.integrations.clear(); - this.integrations.addAll(integrations); - return this; - } - - /** - * @param protocolGenerator The resolved protocol generator to use. - * @return Returns the builder. - */ - public Builder protocolGenerator(ProtocolGenerator protocolGenerator) { - this.protocolGenerator = protocolGenerator; - return this; - } - + public Builder protocolGenerator(ProtocolGenerator protocolGenerator) { + this.protocolGenerator = protocolGenerator; + return this; } + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoDelegator.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoDelegator.java index c2d5956024..76503ca034 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoDelegator.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoDelegator.java @@ -24,7 +24,8 @@ * for getting shape specific GoWriters. */ public final class GoDelegator extends WriterDelegator { - public GoDelegator(FileManifest fileManifest, SymbolProvider symbolProvider) { - super(fileManifest, symbolProvider, new GoWriter.GoWriterFactory()); - } + + public GoDelegator(FileManifest fileManifest, SymbolProvider symbolProvider) { + super(fileManifest, symbolProvider, new GoWriter.GoWriterFactory()); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoDependency.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoDependency.java index ef207ff401..1092553d69 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoDependency.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoDependency.java @@ -15,11 +15,6 @@ package software.amazon.polymorph.smithygo.codegen; -import software.amazon.smithy.codegen.core.SymbolDependency; -import software.amazon.smithy.codegen.core.SymbolDependencyContainer; -import software.amazon.smithy.utils.SetUtils; -import software.amazon.smithy.utils.SmithyBuilder; - import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -27,330 +22,377 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; +import software.amazon.smithy.codegen.core.SymbolDependency; +import software.amazon.smithy.codegen.core.SymbolDependencyContainer; +import software.amazon.smithy.utils.SetUtils; +import software.amazon.smithy.utils.SmithyBuilder; public final class GoDependency implements SymbolDependencyContainer { - private final Type type; - private final String sourcePath; - private final String importPath; - private final String alias; - private final String version; - private final Set dependencies; - private final SymbolDependency symbolDependency; - - private GoDependency(Builder builder) { - this.type = SmithyBuilder.requiredState("type", builder.type); - this.sourcePath = !this.type.equals(Type.STANDARD_LIBRARY) - ? SmithyBuilder.requiredState("sourcePath", builder.sourcePath) : ""; - this.importPath = SmithyBuilder.requiredState("importPath", builder.importPath); - this.alias = builder.alias; - this.version = SmithyBuilder.requiredState("version", builder.version); - this.dependencies = builder.dependencies; - - this.symbolDependency = SymbolDependency.builder() - .dependencyType(this.type.toString()) - .packageName(this.sourcePath) - .version(this.version) - .build(); + + private final Type type; + private final String sourcePath; + private final String importPath; + private final String alias; + private final String version; + private final Set dependencies; + private final SymbolDependency symbolDependency; + + private GoDependency(Builder builder) { + this.type = SmithyBuilder.requiredState("type", builder.type); + this.sourcePath = + !this.type.equals(Type.STANDARD_LIBRARY) + ? SmithyBuilder.requiredState("sourcePath", builder.sourcePath) + : ""; + this.importPath = + SmithyBuilder.requiredState("importPath", builder.importPath); + this.alias = builder.alias; + this.version = SmithyBuilder.requiredState("version", builder.version); + this.dependencies = builder.dependencies; + + this.symbolDependency = + SymbolDependency + .builder() + .dependencyType(this.type.toString()) + .packageName(this.sourcePath) + .version(this.version) + .build(); + } + + /** + * Get the the set of {@link GoDependency} required by this dependency. + * + * @return the set of dependencies + */ + public Set getGoDependencies() { + return this.dependencies; + } + + /** + * Get the symbol dependency representing the dependency. + * + * @return the symbol dependency + */ + public SymbolDependency getSymbolDependency() { + return symbolDependency; + } + + /** + * Get the type of dependency. + * + * @return the dependency type + */ + public Type getType() { + return type; + } + + /** + * Get the source code path of the dependency. + * + * @return the dependency source code path + */ + public String getSourcePath() { + return sourcePath; + } + + /** + * Get the import path of the dependency. + * + * @return the import path of the dependency + */ + public String getImportPath() { + return importPath; + } + + /** + * Get the alias of the module to use. + * + * @return the alias + */ + public String getAlias() { + return alias; + } + + /** + * Get the version of the dependency. + * + * @return the version + */ + public String getVersion() { + return version; + } + + @Override + public List getDependencies() { + Set symbolDependencySet = new TreeSet<>( + SetUtils.of(getSymbolDependency()) + ); + + symbolDependencySet.addAll( + resolveDependencies(getGoDependencies(), new HashSet<>(SetUtils.of(this))) + ); + + return new ArrayList<>(symbolDependencySet); + } + + private Set resolveDependencies( + Set goDependencies, + Set processed + ) { + Set symbolDependencies = new TreeSet<>(); + if (goDependencies.size() == 0) { + return symbolDependencies; } - /** - * Get the the set of {@link GoDependency} required by this dependency. - * - * @return the set of dependencies - */ - public Set getGoDependencies() { - return this.dependencies; + Set dependenciesToResolve = new TreeSet<>(); + for (GoDependency dependency : goDependencies) { + if (processed.contains(dependency)) { + continue; + } + processed.add(dependency); + symbolDependencies.add(dependency.getSymbolDependency()); + dependenciesToResolve.addAll(dependency.getGoDependencies()); } - /** - * Get the symbol dependency representing the dependency. - * - * @return the symbol dependency - */ - public SymbolDependency getSymbolDependency() { - return symbolDependency; + symbolDependencies.addAll( + resolveDependencies(dependenciesToResolve, processed) + ); + + return symbolDependencies; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (!(o instanceof GoDependency)) { + return false; } - /** - * Get the type of dependency. - * - * @return the dependency type - */ - public Type getType() { - return type; + GoDependency other = (GoDependency) o; + + return ( + this.type.equals(other.type) && + this.sourcePath.equals(other.sourcePath) && + this.importPath.equals(other.importPath) && + this.alias.equals(other.alias) && + this.version.equals(other.version) && + this.dependencies.equals(other.dependencies) + ); + } + + @Override + public int hashCode() { + return Objects.hash( + type, + sourcePath, + importPath, + alias, + version, + dependencies + ); + } + + /** + * Represents a dependency type. + */ + public enum Type { + STANDARD_LIBRARY, + DEPENDENCY; + + @Override + public String toString() { + switch (this) { + case STANDARD_LIBRARY: + return "stdlib"; + case DEPENDENCY: + return "dependency"; + default: + return "unknown"; + } } + } + + /** + * Get {@link GoDependency} representing the provided module description. + * + * @param sourcePath the root source path for the given code + * @param importPath the import path of the package + * @param version the version of source module + * @param alias a default alias to use when importing the package, can be null + * @return the dependency + */ + public static GoDependency moduleDependency( + String sourcePath, + String importPath, + String version, + String alias + ) { + Builder builder = GoDependency + .builder() + .type(Type.DEPENDENCY) + .sourcePath(sourcePath) + .importPath(importPath) + .version(version); + if (alias != null) { + builder.alias(alias); + } + return builder.build(); + } + + /** + * Get {@link GoDependency} representing the standard library import description. + * + * @param importPath the import path of the package + * @param version the version of source module + * @return the dependency + */ + public static GoDependency standardLibraryDependency( + String importPath, + String version + ) { + return GoDependency + .builder() + .type(Type.STANDARD_LIBRARY) + .importPath(importPath) + .version(version) + .build(); + } + + /** + * Get {@link GoDependency} representing the standard library import description. + * + * @param importPath the import path of the package + * @param version the version of source module + * @param alias the alias for stdlib dependency + * @return the dependency + */ + public static GoDependency standardLibraryDependency( + String importPath, + String version, + String alias + ) { + Builder builder = GoDependency + .builder() + .type(Type.STANDARD_LIBRARY) + .importPath(importPath) + .version(version); + + if (alias != null) { + builder.alias(alias); + } + return builder.build(); + } + + /** + * Builder for {@link GoDependency}. + */ + public static final class Builder implements SmithyBuilder { + + private Type type; + private String sourcePath; + private String importPath; + private String alias; + private String version; + private final Set dependencies = new TreeSet<>(); + + private Builder() {} /** - * Get the source code path of the dependency. + * Set the dependency type. * - * @return the dependency source code path + * @param type dependency type + * @return the builder */ - public String getSourcePath() { - return sourcePath; + public Builder type(Type type) { + this.type = type; + return this; } /** - * Get the import path of the dependency. + * Set the source path. * - * @return the import path of the dependency + * @param sourcePath the source path root + * @return the builder */ - public String getImportPath() { - return importPath; + public Builder sourcePath(String sourcePath) { + this.sourcePath = sourcePath; + return this; } /** - * Get the alias of the module to use. + * Set the import path. * - * @return the alias + * @param importPath the import path + * @return the builder */ - public String getAlias() { - return alias; + public Builder importPath(String importPath) { + this.importPath = importPath; + return this; } /** - * Get the version of the dependency. + * Set the dependency alias. * - * @return the version + * @param alias the alias + * @return the builder */ - public String getVersion() { - return version; - } - - @Override - public List getDependencies() { - Set symbolDependencySet = new TreeSet<>(SetUtils.of(getSymbolDependency())); - - symbolDependencySet.addAll(resolveDependencies(getGoDependencies(), new HashSet<>(SetUtils.of(this)))); - - return new ArrayList<>(symbolDependencySet); - } - - private Set resolveDependencies(Set goDependencies, Set processed) { - Set symbolDependencies = new TreeSet<>(); - if (goDependencies.size() == 0) { - return symbolDependencies; - } - - Set dependenciesToResolve = new TreeSet<>(); - for (GoDependency dependency : goDependencies) { - if (processed.contains(dependency)) { - continue; - } - processed.add(dependency); - symbolDependencies.add(dependency.getSymbolDependency()); - dependenciesToResolve.addAll(dependency.getGoDependencies()); - } - - symbolDependencies.addAll(resolveDependencies(dependenciesToResolve, processed)); - - return symbolDependencies; - } - - public static Builder builder() { - return new Builder(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } else if (!(o instanceof GoDependency)) { - return false; - } - - GoDependency other = (GoDependency) o; - - return this.type.equals(other.type) && this.sourcePath.equals(other.sourcePath) - && this.importPath.equals(other.importPath) && this.alias.equals(other.alias) - && this.version.equals(other.version) && this.dependencies.equals(other.dependencies); - } - - @Override - public int hashCode() { - return Objects.hash(type, sourcePath, importPath, alias, version, dependencies); + public Builder alias(String alias) { + this.alias = alias; + return this; } /** - * Represents a dependency type. + * Set the dependency version. + * + * @param version the version + * @return the builder */ - public enum Type { - STANDARD_LIBRARY, DEPENDENCY; - - @Override - public String toString() { - switch (this) { - case STANDARD_LIBRARY: - return "stdlib"; - case DEPENDENCY: - return "dependency"; - default: - return "unknown"; - } - } + public Builder version(String version) { + this.version = version; + return this; } /** - * Get {@link GoDependency} representing the provided module description. + * Set the collection of {@link GoDependency} required by this dependency. * - * @param sourcePath the root source path for the given code - * @param importPath the import path of the package - * @param version the version of source module - * @param alias a default alias to use when importing the package, can be null - * @return the dependency + * @param dependencies a collection of dependencies + * @return the builder */ - public static GoDependency moduleDependency(String sourcePath, String importPath, String version, String alias) { - Builder builder = GoDependency.builder() - .type(Type.DEPENDENCY) - .sourcePath(sourcePath) - .importPath(importPath) - .version(version); - if (alias != null) { - builder.alias(alias); - } - return builder.build(); + public Builder dependencies(Collection dependencies) { + this.dependencies.clear(); + this.dependencies.addAll(dependencies); + return this; } /** - * Get {@link GoDependency} representing the standard library import description. + * Add a dependency on another {@link GoDependency}. * - * @param importPath the import path of the package - * @param version the version of source module - * @return the dependency + * @param dependency the dependency + * @return the builder */ - public static GoDependency standardLibraryDependency(String importPath, String version) { - return GoDependency.builder() - .type(Type.STANDARD_LIBRARY) - .importPath(importPath) - .version(version) - .build(); + public Builder addDependency(GoDependency dependency) { + this.dependencies.add(dependency); + return this; } /** - * Get {@link GoDependency} representing the standard library import description. + * Remove a dependency on another {@link GoDependency}. * - * @param importPath the import path of the package - * @param version the version of source module - * @param alias the alias for stdlib dependency - * @return the dependency + * @param dependency the dependency + * @return the builder */ - public static GoDependency standardLibraryDependency(String importPath, String version, String alias) { - Builder builder = GoDependency.builder() - .type(Type.STANDARD_LIBRARY) - .importPath(importPath) - .version(version); - - if (alias != null) { - builder.alias(alias); - } - return builder.build(); + public Builder removeDependency(GoDependency dependency) { + this.dependencies.remove(dependency); + return this; } - /** - * Builder for {@link GoDependency}. - */ - public static final class Builder implements SmithyBuilder { - private Type type; - private String sourcePath; - private String importPath; - private String alias; - private String version; - private final Set dependencies = new TreeSet<>(); - - private Builder() { - } - - /** - * Set the dependency type. - * - * @param type dependency type - * @return the builder - */ - public Builder type(Type type) { - this.type = type; - return this; - } - - /** - * Set the source path. - * - * @param sourcePath the source path root - * @return the builder - */ - public Builder sourcePath(String sourcePath) { - this.sourcePath = sourcePath; - return this; - } - - /** - * Set the import path. - * - * @param importPath the import path - * @return the builder - */ - public Builder importPath(String importPath) { - this.importPath = importPath; - return this; - } - - /** - * Set the dependency alias. - * - * @param alias the alias - * @return the builder - */ - public Builder alias(String alias) { - this.alias = alias; - return this; - } - - /** - * Set the dependency version. - * - * @param version the version - * @return the builder - */ - public Builder version(String version) { - this.version = version; - return this; - } - - /** - * Set the collection of {@link GoDependency} required by this dependency. - * - * @param dependencies a collection of dependencies - * @return the builder - */ - public Builder dependencies(Collection dependencies) { - this.dependencies.clear(); - this.dependencies.addAll(dependencies); - return this; - } - - /** - * Add a dependency on another {@link GoDependency}. - * - * @param dependency the dependency - * @return the builder - */ - public Builder addDependency(GoDependency dependency) { - this.dependencies.add(dependency); - return this; - } - - /** - * Remove a dependency on another {@link GoDependency}. - * - * @param dependency the dependency - * @return the builder - */ - public Builder removeDependency(GoDependency dependency) { - this.dependencies.remove(dependency); - return this; - } - - @Override - public GoDependency build() { - return new GoDependency(this); - } + @Override + public GoDependency build() { + return new GoDependency(this); } + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoSettings.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoSettings.java index 0c079a9b3c..5d9871a06f 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoSettings.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoSettings.java @@ -1,175 +1,195 @@ package software.amazon.polymorph.smithygo.codegen; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.ShapeId; -import java.util.Arrays; -import java.util.Objects; -import java.util.Optional; - public class GoSettings { - private static final String SERVICE = "service"; - private static final String MODULE_NAME = "module"; - private static final String MODULE_DESCRIPTION = "moduleDescription"; - private static final String MODULE_VERSION = "moduleVersion"; - private static final String GENERATE_GO_MOD = "generateGoMod"; - private static final String GO_DIRECTIVE = "goDirective"; - - private ShapeId service; - private String moduleName; - private String moduleDescription = ""; - private String moduleVersion; - private Boolean generateGoMod = false; - private ShapeId protocol; - - /** - * Create a settings object from a configuration object node. - * - * @param config Config object to load. - * @return Returns the extracted settings. - */ - public static GoSettings from(ObjectNode config) { - GoSettings settings = new GoSettings(); - config.warnIfAdditionalProperties( - Arrays.asList(SERVICE, MODULE_NAME, MODULE_DESCRIPTION, MODULE_VERSION, GENERATE_GO_MOD, GO_DIRECTIVE)); - - settings.setService(config.expectStringMember(SERVICE).expectShapeId()); - settings.setModuleName(config.expectStringMember("moduleName").getValue()); - settings.setModuleDescription(config.getStringMemberOrDefault( - MODULE_DESCRIPTION, settings.getModuleName() + " client")); - settings.setModuleVersion(config.getStringMemberOrDefault(MODULE_VERSION, null)); - settings.setGenerateGoMod(config.getBooleanMemberOrDefault(GENERATE_GO_MOD, false)); - return settings; - } - - /** - * Gets the id of the service that is being generated. - * - * @return Returns the service id. - * @throws NullPointerException if the service has not been set. - */ - public ShapeId getService() { - return Objects.requireNonNull(service, SERVICE + " not set"); - } - - /** - * Gets the corresponding {@link ServiceShape} from a model. - * - * @param model Model to search for the service shape by ID. - * @return Returns the found {@code Service}. - * @throws NullPointerException if the service has not been set. - * @throws CodegenException if the service is invalid or not found. - */ - public ServiceShape getService(Model model) { - return model - .getShape(getService()) - .orElseThrow(() -> new CodegenException("Service shape not found: " + getService())) - .asServiceShape() - .orElseThrow(() -> new CodegenException("Shape is not a Service: " + getService())); - } - - /** - * Sets the service to generate. - * - * @param service The service to generate. - */ - public void setService(ShapeId service) { - this.service = Objects.requireNonNull(service); - } - - /** - * Gets the required module name for the module that will be generated. - * - * @return Returns the module name. - * @throws NullPointerException if the module name has not been set. - */ - public String getModuleName() { - return Objects.requireNonNull(moduleName, MODULE_NAME + " not set"); - } - - /** - * Sets the name of the module to generate. - * - * @param moduleName The name of the module to generate. - */ - public void setModuleName(String moduleName) { - this.moduleName = Objects.requireNonNull(moduleName); - } - - /** - * Gets the optional module description for the module that will be generated. - * - * @return Returns the module description. - */ - public String getModuleDescription() { - return moduleDescription; - } - - /** - * Sets the description of the module to generate. - * - * @param moduleDescription The description of the module to generate. - */ - public void setModuleDescription(String moduleDescription) { - this.moduleDescription = Objects.requireNonNull(moduleDescription); - } - - /** - * Gets the optional module version for the module that will be generated. - * - * @return Returns the module version. - */ - public Optional getModuleVersion() { - return Optional.ofNullable(moduleVersion); - } - - /** - * Sets the version of the module to generate. - * - * @param moduleVersion The version of the module to generate. - */ - public void setModuleVersion(String moduleVersion) { - if (moduleVersion != null) { - this.moduleVersion = moduleVersion; - } - } - - /** - * Gets the flag for generating go.mod file. - * - * @return Returns if go.mod will be generated (true) or not (false) - */ - public Boolean getGenerateGoMod() { - return generateGoMod; - } - - /** - * Sets the flag for generating go.mod file. - * - * @param generateGoMod If go.mod will be generated (true) or not (false) - */ - public void setGenerateGoMod(Boolean generateGoMod) { - this.generateGoMod = Objects.requireNonNull(generateGoMod); - } - - /** - * Gets the configured protocol to generate. - * - * @return Returns the configured protocol. - */ - public ShapeId getProtocol() { - return protocol; - } - /** - * Sets the protocol to generate. - * - * @param protocol Protocols to generate. - */ - public void setProtocol(ShapeId protocol) { - this.protocol = Objects.requireNonNull(protocol); + private static final String SERVICE = "service"; + private static final String MODULE_NAME = "module"; + private static final String MODULE_DESCRIPTION = "moduleDescription"; + private static final String MODULE_VERSION = "moduleVersion"; + private static final String GENERATE_GO_MOD = "generateGoMod"; + private static final String GO_DIRECTIVE = "goDirective"; + + private ShapeId service; + private String moduleName; + private String moduleDescription = ""; + private String moduleVersion; + private Boolean generateGoMod = false; + private ShapeId protocol; + + /** + * Create a settings object from a configuration object node. + * + * @param config Config object to load. + * @return Returns the extracted settings. + */ + public static GoSettings from(ObjectNode config) { + GoSettings settings = new GoSettings(); + config.warnIfAdditionalProperties( + Arrays.asList( + SERVICE, + MODULE_NAME, + MODULE_DESCRIPTION, + MODULE_VERSION, + GENERATE_GO_MOD, + GO_DIRECTIVE + ) + ); + + settings.setService(config.expectStringMember(SERVICE).expectShapeId()); + settings.setModuleName(config.expectStringMember("moduleName").getValue()); + settings.setModuleDescription( + config.getStringMemberOrDefault( + MODULE_DESCRIPTION, + settings.getModuleName() + " client" + ) + ); + settings.setModuleVersion( + config.getStringMemberOrDefault(MODULE_VERSION, null) + ); + settings.setGenerateGoMod( + config.getBooleanMemberOrDefault(GENERATE_GO_MOD, false) + ); + return settings; + } + + /** + * Gets the id of the service that is being generated. + * + * @return Returns the service id. + * @throws NullPointerException if the service has not been set. + */ + public ShapeId getService() { + return Objects.requireNonNull(service, SERVICE + " not set"); + } + + /** + * Gets the corresponding {@link ServiceShape} from a model. + * + * @param model Model to search for the service shape by ID. + * @return Returns the found {@code Service}. + * @throws NullPointerException if the service has not been set. + * @throws CodegenException if the service is invalid or not found. + */ + public ServiceShape getService(Model model) { + return model + .getShape(getService()) + .orElseThrow(() -> + new CodegenException("Service shape not found: " + getService()) + ) + .asServiceShape() + .orElseThrow(() -> + new CodegenException("Shape is not a Service: " + getService()) + ); + } + + /** + * Sets the service to generate. + * + * @param service The service to generate. + */ + public void setService(ShapeId service) { + this.service = Objects.requireNonNull(service); + } + + /** + * Gets the required module name for the module that will be generated. + * + * @return Returns the module name. + * @throws NullPointerException if the module name has not been set. + */ + public String getModuleName() { + return Objects.requireNonNull(moduleName, MODULE_NAME + " not set"); + } + + /** + * Sets the name of the module to generate. + * + * @param moduleName The name of the module to generate. + */ + public void setModuleName(String moduleName) { + this.moduleName = Objects.requireNonNull(moduleName); + } + + /** + * Gets the optional module description for the module that will be generated. + * + * @return Returns the module description. + */ + public String getModuleDescription() { + return moduleDescription; + } + + /** + * Sets the description of the module to generate. + * + * @param moduleDescription The description of the module to generate. + */ + public void setModuleDescription(String moduleDescription) { + this.moduleDescription = Objects.requireNonNull(moduleDescription); + } + + /** + * Gets the optional module version for the module that will be generated. + * + * @return Returns the module version. + */ + public Optional getModuleVersion() { + return Optional.ofNullable(moduleVersion); + } + + /** + * Sets the version of the module to generate. + * + * @param moduleVersion The version of the module to generate. + */ + public void setModuleVersion(String moduleVersion) { + if (moduleVersion != null) { + this.moduleVersion = moduleVersion; } + } + + /** + * Gets the flag for generating go.mod file. + * + * @return Returns if go.mod will be generated (true) or not (false) + */ + public Boolean getGenerateGoMod() { + return generateGoMod; + } + + /** + * Sets the flag for generating go.mod file. + * + * @param generateGoMod If go.mod will be generated (true) or not (false) + */ + public void setGenerateGoMod(Boolean generateGoMod) { + this.generateGoMod = Objects.requireNonNull(generateGoMod); + } + + /** + * Gets the configured protocol to generate. + * + * @return Returns the configured protocol. + */ + public ShapeId getProtocol() { + return protocol; + } + + /** + * Sets the protocol to generate. + * + * @param protocol Protocols to generate. + */ + public void setProtocol(ShapeId protocol) { + this.protocol = Objects.requireNonNull(protocol); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoWriter.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoWriter.java index aae981c2be..bf3ec60ac4 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoWriter.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/GoWriter.java @@ -1,5 +1,13 @@ package software.amazon.polymorph.smithygo.codegen; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.regex.Pattern; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolContainer; @@ -12,708 +20,840 @@ import software.amazon.smithy.model.traits.DeprecatedTrait; import software.amazon.smithy.utils.StringUtils; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.regex.Pattern; - public class GoWriter extends SymbolWriter { - private static final Pattern ARGUMENT_NAME_PATTERN = Pattern.compile("\\$([a-z][a-zA-Z_0-9]+)(:\\w)?"); - private final String fullPackageName; - private final ImportDeclarations imports = new ImportDeclarations(); - private final List dependencies = new ArrayList<>(); - private final boolean innerWriter; - - /** - * Initializes the GoWriter for the package and filename to be written to. - * - * @param fullPackageName package and filename to be written to. - */ - public GoWriter(String fullPackageName) { - this(fullPackageName, false); - } - - private GoWriter(String fullPackageName, boolean innerWriter) { - super(new ImportDeclarations()); - this.fullPackageName = fullPackageName; - this.innerWriter = innerWriter; - init(); - } - - private void init() { - trimBlankLines(); - trimTrailingSpaces(); - setIndentText("\t"); - putFormatter('T', new GoSymbolFormatter()); - putFormatter('P', new PointableGoSymbolFormatter()); - putFormatter('W', new GoWritableInjector()); - } - - // TODO figure out better way to annotate where the failure occurs, check templates and args - // TODO to try to find programming bugs. - - /** - * Returns a Writable for the string and args to be composed inline to another writer's contents. - * - * @param contents string to write. - * @param args Arguments to use when evaluating the contents string. - * @return Writable to be evaluated. - */ - @SafeVarargs - public static Writable goTemplate(String contents, Map... args) { - validateTemplateArgsNotNull(args); - return (GoWriter w) -> { - w.writeGoTemplate(contents, args); - }; - } - - public static final class GoWriterFactory implements SymbolWriter.Factory { - - @Override - public GoWriter apply(String filename, String namespace) { - GoWriter writer = new GoWriter(namespace); - return writer; - } - } - - /** - * Returns a Writable that can later be invoked to write the contents as template - * as a code block instead of single content of text. - * - * @param beforeNewLine text before new line - * @param afterNewLine text after new line - * @param fn closure to write - */ - public static Writable goBlockTemplate( - String beforeNewLine, - String afterNewLine, - Consumer fn - ) { - return goBlockTemplate(beforeNewLine, afterNewLine, new Map[0], fn); - } - - /** - * Returns a Writable that can later be invoked to write the contents as template - * as a code block instead of single content of text. - * - * @param beforeNewLine text before new line - * @param afterNewLine text after new line - * @param args1 template arguments - * @param fn closure to write - */ - public static Writable goBlockTemplate( - String beforeNewLine, - String afterNewLine, - Map args1, - Consumer fn - ) { - return goBlockTemplate(beforeNewLine, afterNewLine, new Map[]{args1}, fn); - } - /** - * Returns a Writable that can later be invoked to write the contents as template - * as a code block instead of single content of text. - * - * @param beforeNewLine text before new line - * @param afterNewLine text after new line - * @param args1 template arguments - * @param args2 template arguments - * @param fn closure to write - */ - public static Writable goBlockTemplate( - String beforeNewLine, - String afterNewLine, - Map args1, - Map args2, - Consumer fn - ) { - return goBlockTemplate(beforeNewLine, afterNewLine, new Map[]{args1, args2}, fn); - } - - /** - * Returns a Writable that can later be invoked to write the contents as template - * as a code block instead of single content of text. - * - * @param beforeNewLine text before new line - * @param afterNewLine text after new line - * @param args1 template arguments - * @param args2 template arguments - * @param args3 template arguments - * @param fn closure to write - */ - public static Writable goBlockTemplate( - String beforeNewLine, - String afterNewLine, - Map args1, - Map args2, - Map args3, - Consumer fn - ) { - return goBlockTemplate(beforeNewLine, afterNewLine, new Map[]{args1, args2, args3}, fn); - } - - /** - * Returns a Writable that can later be invoked to write the contents as template - * as a code block instead of single content of text. - * - * @param beforeNewLine text before new line - * @param afterNewLine text after new line - * @param args1 template arguments - * @param args2 template arguments - * @param args3 template arguments - * @param args4 template arguments - * @param fn closure to write - */ - public static Writable goBlockTemplate( - String beforeNewLine, - String afterNewLine, - Map args1, - Map args2, - Map args3, - Map args4, - Consumer fn - ) { - return goBlockTemplate(beforeNewLine, afterNewLine, new Map[]{args1, args2, args3, args4}, fn); - } - - /** - * Returns a Writable that can later be invoked to write the contents as template - * as a code block instead of single content of text. - * - * @param beforeNewLine text before new line - * @param afterNewLine text after new line - * @param args1 template arguments - * @param args2 template arguments - * @param args3 template arguments - * @param args4 template arguments - * @param args5 template arguments - * @param fn closure to write - */ - public static Writable goBlockTemplate( - String beforeNewLine, - String afterNewLine, - Map args1, - Map args2, - Map args3, - Map args4, - Map args5, - Consumer fn - ) { - return goBlockTemplate(beforeNewLine, afterNewLine, new Map[]{args1, args2, args3, args4, args5}, fn); - } - - /** - * Returns a Writable that can later be invoked to write the contents as template - * as a code block instead of single content of text. - * - * @param beforeNewLine text before new line - * @param afterNewLine text after new line - * @param args template arguments - * @param fn closure to write - */ - public static Writable goBlockTemplate( - String beforeNewLine, - String afterNewLine, - Map[] args, - Consumer fn - ) { - validateTemplateArgsNotNull(args); - return (GoWriter w) -> { - w.writeGoBlockTemplate(beforeNewLine, afterNewLine, args, fn); - }; - } - - /** - * Returns a Writable that does nothing. - * - * @return Writable that does nothing - */ - public static Writable emptyGoTemplate() { - return (GoWriter w) -> { - }; - } + private static final Pattern ARGUMENT_NAME_PATTERN = Pattern.compile( + "\\$([a-z][a-zA-Z_0-9]+)(:\\w)?" + ); + private final String fullPackageName; + private final ImportDeclarations imports = new ImportDeclarations(); + private final List dependencies = new ArrayList<>(); + private final boolean innerWriter; + + /** + * Initializes the GoWriter for the package and filename to be written to. + * + * @param fullPackageName package and filename to be written to. + */ + public GoWriter(String fullPackageName) { + this(fullPackageName, false); + } + + private GoWriter(String fullPackageName, boolean innerWriter) { + super(new ImportDeclarations()); + this.fullPackageName = fullPackageName; + this.innerWriter = innerWriter; + init(); + } + + private void init() { + trimBlankLines(); + trimTrailingSpaces(); + setIndentText("\t"); + putFormatter('T', new GoSymbolFormatter()); + putFormatter('P', new PointableGoSymbolFormatter()); + putFormatter('W', new GoWritableInjector()); + } + + // TODO figure out better way to annotate where the failure occurs, check templates and args + // TODO to try to find programming bugs. + + /** + * Returns a Writable for the string and args to be composed inline to another writer's contents. + * + * @param contents string to write. + * @param args Arguments to use when evaluating the contents string. + * @return Writable to be evaluated. + */ + @SafeVarargs + public static Writable goTemplate( + String contents, + Map... args + ) { + validateTemplateArgsNotNull(args); + return (GoWriter w) -> { + w.writeGoTemplate(contents, args); + }; + } + + public static final class GoWriterFactory + implements SymbolWriter.Factory { - /** - * Writes the contents and arguments as a template to the writer. - * - * @param contents string to write - * @param args Arguments to use when evaluating the contents string. - */ - @SafeVarargs - public final void writeGoTemplate(String contents, Map... args) { - withTemplate(contents, args, (template) -> { - try { - write(contents); - } catch (Exception e) { - throw new CodegenException("Failed to render template\n" + contents + "\nReason: " + e.getMessage(), e); - } - }); - } - - /** - * Writes the contents as template as a code block instead of single content fo text. - * - * @param beforeNewLine text before new line - * @param afterNewLine text after new line - * @param fn closure to write - */ - public void writeGoBlockTemplate( - String beforeNewLine, - String afterNewLine, - Consumer fn - ) { - writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[0], fn); - } - - /** - * Writes the contents as template as a code block instead of single content fo text. - * - * @param beforeNewLine text before new line - * @param afterNewLine text after new line - * @param arg1 first map argument - * @param fn closure to write - */ - public void writeGoBlockTemplate( - String beforeNewLine, - String afterNewLine, - Map arg1, - Consumer fn - ) { - writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[]{arg1}, fn); - } - - /** - * Writes the contents as template as a code block instead of single content fo text. - * - * @param beforeNewLine text before new line - * @param afterNewLine text after new line - * @param arg1 first map argument - * @param arg2 second map argument - * @param fn closure to write - */ - public void writeGoBlockTemplate( - String beforeNewLine, - String afterNewLine, - Map arg1, - Map arg2, - Consumer fn - ) { - writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[]{arg1, arg2}, fn); - } - - /** - * Writes the contents as template as a code block instead of single content fo text. - * - * @param beforeNewLine text before new line - * @param afterNewLine text after new line - * @param arg1 first map argument - * @param arg2 second map argument - * @param arg3 third map argument - * @param fn closure to write - */ - public void writeGoBlockTemplate( - String beforeNewLine, - String afterNewLine, - Map arg1, - Map arg2, - Map arg3, - Consumer fn - ) { - writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[]{arg1, arg2, arg3}, fn); - } - - /** - * Writes the contents as template as a code block instead of single content fo text. - * - * @param beforeNewLine text before new line - * @param afterNewLine text after new line - * @param arg1 first map argument - * @param arg2 second map argument - * @param arg3 third map argument - * @param arg4 forth map argument - * @param fn closure to write - */ - public void writeGoBlockTemplate( - String beforeNewLine, - String afterNewLine, - Map arg1, - Map arg2, - Map arg3, - Map arg4, - Consumer fn - ) { - writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[]{arg1, arg2, arg3, arg4}, fn); - } - - /** - * Writes the contents as template as a code block instead of single content fo text. - * - * @param beforeNewLine text before new line - * @param afterNewLine text after new line - * @param arg1 first map argument - * @param arg2 second map argument - * @param arg3 third map argument - * @param arg4 forth map argument - * @param arg5 forth map argument - * @param fn closure to write - */ - public void writeGoBlockTemplate( - String beforeNewLine, - String afterNewLine, - Map arg1, - Map arg2, - Map arg3, - Map arg4, - Map arg5, - Consumer fn - ) { - writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[]{arg1, arg2, arg3, arg4, arg5}, fn); - } - - public void writeGoBlockTemplate( - String beforeNewLine, - String afterNewLine, - Map[] args, - Consumer fn - ) { - withTemplate(beforeNewLine, args, (header) -> { - conditionalBlock(header, afterNewLine, true, new Object[0], fn); - }); - } - - private void withTemplate( - String template, - Map[] argMaps, - Consumer fn - ) { - pushState(); - for (var args : argMaps) { - putContext(args); + @Override + public GoWriter apply(String filename, String namespace) { + GoWriter writer = new GoWriter(namespace); + return writer; + } + } + + /** + * Returns a Writable that can later be invoked to write the contents as template + * as a code block instead of single content of text. + * + * @param beforeNewLine text before new line + * @param afterNewLine text after new line + * @param fn closure to write + */ + public static Writable goBlockTemplate( + String beforeNewLine, + String afterNewLine, + Consumer fn + ) { + return goBlockTemplate(beforeNewLine, afterNewLine, new Map[0], fn); + } + + /** + * Returns a Writable that can later be invoked to write the contents as template + * as a code block instead of single content of text. + * + * @param beforeNewLine text before new line + * @param afterNewLine text after new line + * @param args1 template arguments + * @param fn closure to write + */ + public static Writable goBlockTemplate( + String beforeNewLine, + String afterNewLine, + Map args1, + Consumer fn + ) { + return goBlockTemplate( + beforeNewLine, + afterNewLine, + new Map[] { args1 }, + fn + ); + } + + /** + * Returns a Writable that can later be invoked to write the contents as template + * as a code block instead of single content of text. + * + * @param beforeNewLine text before new line + * @param afterNewLine text after new line + * @param args1 template arguments + * @param args2 template arguments + * @param fn closure to write + */ + public static Writable goBlockTemplate( + String beforeNewLine, + String afterNewLine, + Map args1, + Map args2, + Consumer fn + ) { + return goBlockTemplate( + beforeNewLine, + afterNewLine, + new Map[] { args1, args2 }, + fn + ); + } + + /** + * Returns a Writable that can later be invoked to write the contents as template + * as a code block instead of single content of text. + * + * @param beforeNewLine text before new line + * @param afterNewLine text after new line + * @param args1 template arguments + * @param args2 template arguments + * @param args3 template arguments + * @param fn closure to write + */ + public static Writable goBlockTemplate( + String beforeNewLine, + String afterNewLine, + Map args1, + Map args2, + Map args3, + Consumer fn + ) { + return goBlockTemplate( + beforeNewLine, + afterNewLine, + new Map[] { args1, args2, args3 }, + fn + ); + } + + /** + * Returns a Writable that can later be invoked to write the contents as template + * as a code block instead of single content of text. + * + * @param beforeNewLine text before new line + * @param afterNewLine text after new line + * @param args1 template arguments + * @param args2 template arguments + * @param args3 template arguments + * @param args4 template arguments + * @param fn closure to write + */ + public static Writable goBlockTemplate( + String beforeNewLine, + String afterNewLine, + Map args1, + Map args2, + Map args3, + Map args4, + Consumer fn + ) { + return goBlockTemplate( + beforeNewLine, + afterNewLine, + new Map[] { args1, args2, args3, args4 }, + fn + ); + } + + /** + * Returns a Writable that can later be invoked to write the contents as template + * as a code block instead of single content of text. + * + * @param beforeNewLine text before new line + * @param afterNewLine text after new line + * @param args1 template arguments + * @param args2 template arguments + * @param args3 template arguments + * @param args4 template arguments + * @param args5 template arguments + * @param fn closure to write + */ + public static Writable goBlockTemplate( + String beforeNewLine, + String afterNewLine, + Map args1, + Map args2, + Map args3, + Map args4, + Map args5, + Consumer fn + ) { + return goBlockTemplate( + beforeNewLine, + afterNewLine, + new Map[] { args1, args2, args3, args4, args5 }, + fn + ); + } + + /** + * Returns a Writable that can later be invoked to write the contents as template + * as a code block instead of single content of text. + * + * @param beforeNewLine text before new line + * @param afterNewLine text after new line + * @param args template arguments + * @param fn closure to write + */ + public static Writable goBlockTemplate( + String beforeNewLine, + String afterNewLine, + Map[] args, + Consumer fn + ) { + validateTemplateArgsNotNull(args); + return (GoWriter w) -> { + w.writeGoBlockTemplate(beforeNewLine, afterNewLine, args, fn); + }; + } + + /** + * Returns a Writable that does nothing. + * + * @return Writable that does nothing + */ + public static Writable emptyGoTemplate() { + return (GoWriter w) -> {}; + } + + /** + * Writes the contents and arguments as a template to the writer. + * + * @param contents string to write + * @param args Arguments to use when evaluating the contents string. + */ + @SafeVarargs + public final void writeGoTemplate( + String contents, + Map... args + ) { + withTemplate( + contents, + args, + template -> { + try { + write(contents); + } catch (Exception e) { + throw new CodegenException( + "Failed to render template\n" + + contents + + "\nReason: " + + e.getMessage(), + e + ); } - validateContext(template); - fn.accept(template); - popState(); - } - - private GoWriter conditionalBlock( - String beforeNewLine, - String afterNewLine, - boolean conditional, - Object[] args, - Consumer fn - ) { - if (conditional) { - openBlock(beforeNewLine.trim(), args); + } + ); + } + + /** + * Writes the contents as template as a code block instead of single content fo text. + * + * @param beforeNewLine text before new line + * @param afterNewLine text after new line + * @param fn closure to write + */ + public void writeGoBlockTemplate( + String beforeNewLine, + String afterNewLine, + Consumer fn + ) { + writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[0], fn); + } + + /** + * Writes the contents as template as a code block instead of single content fo text. + * + * @param beforeNewLine text before new line + * @param afterNewLine text after new line + * @param arg1 first map argument + * @param fn closure to write + */ + public void writeGoBlockTemplate( + String beforeNewLine, + String afterNewLine, + Map arg1, + Consumer fn + ) { + writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[] { arg1 }, fn); + } + + /** + * Writes the contents as template as a code block instead of single content fo text. + * + * @param beforeNewLine text before new line + * @param afterNewLine text after new line + * @param arg1 first map argument + * @param arg2 second map argument + * @param fn closure to write + */ + public void writeGoBlockTemplate( + String beforeNewLine, + String afterNewLine, + Map arg1, + Map arg2, + Consumer fn + ) { + writeGoBlockTemplate( + beforeNewLine, + afterNewLine, + new Map[] { arg1, arg2 }, + fn + ); + } + + /** + * Writes the contents as template as a code block instead of single content fo text. + * + * @param beforeNewLine text before new line + * @param afterNewLine text after new line + * @param arg1 first map argument + * @param arg2 second map argument + * @param arg3 third map argument + * @param fn closure to write + */ + public void writeGoBlockTemplate( + String beforeNewLine, + String afterNewLine, + Map arg1, + Map arg2, + Map arg3, + Consumer fn + ) { + writeGoBlockTemplate( + beforeNewLine, + afterNewLine, + new Map[] { arg1, arg2, arg3 }, + fn + ); + } + + /** + * Writes the contents as template as a code block instead of single content fo text. + * + * @param beforeNewLine text before new line + * @param afterNewLine text after new line + * @param arg1 first map argument + * @param arg2 second map argument + * @param arg3 third map argument + * @param arg4 forth map argument + * @param fn closure to write + */ + public void writeGoBlockTemplate( + String beforeNewLine, + String afterNewLine, + Map arg1, + Map arg2, + Map arg3, + Map arg4, + Consumer fn + ) { + writeGoBlockTemplate( + beforeNewLine, + afterNewLine, + new Map[] { arg1, arg2, arg3, arg4 }, + fn + ); + } + + /** + * Writes the contents as template as a code block instead of single content fo text. + * + * @param beforeNewLine text before new line + * @param afterNewLine text after new line + * @param arg1 first map argument + * @param arg2 second map argument + * @param arg3 third map argument + * @param arg4 forth map argument + * @param arg5 forth map argument + * @param fn closure to write + */ + public void writeGoBlockTemplate( + String beforeNewLine, + String afterNewLine, + Map arg1, + Map arg2, + Map arg3, + Map arg4, + Map arg5, + Consumer fn + ) { + writeGoBlockTemplate( + beforeNewLine, + afterNewLine, + new Map[] { arg1, arg2, arg3, arg4, arg5 }, + fn + ); + } + + public void writeGoBlockTemplate( + String beforeNewLine, + String afterNewLine, + Map[] args, + Consumer fn + ) { + withTemplate( + beforeNewLine, + args, + header -> { + conditionalBlock(header, afterNewLine, true, new Object[0], fn); + } + ); + } + + private void withTemplate( + String template, + Map[] argMaps, + Consumer fn + ) { + pushState(); + for (var args : argMaps) { + putContext(args); + } + validateContext(template); + fn.accept(template); + popState(); + } + + private GoWriter conditionalBlock( + String beforeNewLine, + String afterNewLine, + boolean conditional, + Object[] args, + Consumer fn + ) { + if (conditional) { + openBlock(beforeNewLine.trim(), args); + } + + fn.accept(this); + + if (conditional) { + closeBlock(afterNewLine.trim()); + } + + return this; + } + + private static void validateTemplateArgsNotNull( + Map[] argMaps + ) { + for (var args : argMaps) { + args.forEach((k, v) -> { + if (v == null) { + throw new CodegenException( + "Template argument " + k + " cannot be null" + ); } - - fn.accept(this); - - if (conditional) { - closeBlock(afterNewLine.trim()); + }); + } + } + + private void validateContext(String template) { + var matcher = ARGUMENT_NAME_PATTERN.matcher(template); + + while (matcher.find()) { + var keyName = matcher.group(1); + var value = getContext(keyName); + if (value == null) { + throw new CodegenException( + "Go template expected " + + keyName + + " but was not present in context scope." + + " Template: \n" + + template + ); + } + } + } + + /** + * Imports one or more symbols if necessary, using the name of the + * symbol and only "USE" references. + * + * @param container Container of symbols to add. + * @return Returns the writer. + */ + public GoWriter addUseImports(SymbolContainer container) { + for (Symbol symbol : container.getSymbols()) { + addImport( + symbol, + CodegenUtils.getSymbolNamespaceAlias(symbol), + SymbolReference.ContextOption.USE + ); + } + return this; + } + + /** + * Imports a symbol reference if necessary, using the alias of the + * reference and only associated "USE" references. + * + * @param symbolReference Symbol reference to import. + * @return Returns the writer. + */ + public GoWriter addUseImports(SymbolReference symbolReference) { + return addImport( + symbolReference.getSymbol(), + symbolReference.getAlias(), + SymbolReference.ContextOption.USE + ); + } + + /** + * Adds and imports the given dependency. + * + * @param goDependency The GoDependency to import. + * @return Returns the writer. + */ + public GoWriter addUseImports(GoDependency goDependency) { + dependencies.addAll(goDependency.getDependencies()); + return addImport(goDependency.getImportPath(), goDependency.getAlias()); + } + + private GoWriter addImports(GoWriter other) { + this.imports.addImports(other.imports); + return this; + } + + private boolean isExternalNamespace(String namespace) { + return ( + !StringUtils.isBlank(namespace) && !namespace.equals(fullPackageName) + ); + } + + void addImportReferences( + Symbol symbol, + SymbolReference.ContextOption... options + ) { + for (SymbolReference reference : symbol.getReferences()) { + for (SymbolReference.ContextOption option : options) { + if (reference.hasOption(option)) { + addImport(reference.getSymbol(), reference.getAlias(), options); + break; } - - return this; - } - - private static void validateTemplateArgsNotNull(Map[] argMaps) { - for (var args : argMaps) { - args.forEach((k, v) -> { - if (v == null) { - throw new CodegenException("Template argument " + k + " cannot be null"); - } - }); + } + } + } + + /** + * Imports a package using an alias if necessary. + * + * @param packageName Package to import. + * @param as Alias to refer to the package as. + * @return Returns the writer. + */ + public GoWriter addImport(String packageName, String as) { + imports.addImport(packageName, as); + return this; + } + + public GoWriter addImportFromModule( + String moduleName, + String packageName, + String as + ) { + imports.addImport(moduleName.concat("/").concat(packageName), as); + return this; + } + + public GoWriter addImportFromModule(String moduleName, String packageName) { + imports.addImport(moduleName.concat("/").concat(packageName), ""); + return this; + } + + public GoWriter addImport(String packageName) { + imports.addImport(packageName, ""); + return this; + } + + private GoWriter addDependencies(GoWriter other) { + this.dependencies.addAll(other.getDependencies()); + return this; + } + + private boolean isTargetDeprecated(Model model, MemberShape member) { + return ( + model + .expectShape(member.getTarget()) + .getTrait(DeprecatedTrait.class) + .isPresent() && + // don't consider deprecated prelude shapes (like PrimitiveBoolean) + !Prelude.isPreludeShape(member.getTarget()) + ); + } + + @Override + public String toString() { + String contents = super.toString(); + + if (innerWriter) { + return contents; + } + + String[] packageParts = fullPackageName.split("/"); + String header = String.format( + "// Code generated by smithy-go-codegen DO NOT EDIT.%n%n" + ); + + String packageName = packageParts[packageParts.length - 1]; + if (packageName.startsWith("v") && packageParts.length >= 2) { + String remaining = packageName.substring(1); + try { + int value = Integer.parseInt(remaining); + packageName = packageParts[packageParts.length - 2]; + if (value == 0 || value == 1) { + throw new CodegenException( + "module paths vN version component must only be N >= 2" + ); } + } catch (NumberFormatException ne) { + // Do nothing + } } - private void validateContext(String template) { - var matcher = ARGUMENT_NAME_PATTERN.matcher(template); - - while (matcher.find()) { - var keyName = matcher.group(1); - var value = getContext(keyName); - if (value == null) { - throw new CodegenException( - "Go template expected " + keyName + " but was not present in context scope." - + " Template: \n" + template); - } - } - } - - /** - * Imports one or more symbols if necessary, using the name of the - * symbol and only "USE" references. - * - * @param container Container of symbols to add. - * @return Returns the writer. - */ - public GoWriter addUseImports(SymbolContainer container) { - for (Symbol symbol : container.getSymbols()) { - addImport(symbol, - CodegenUtils.getSymbolNamespaceAlias(symbol), - SymbolReference.ContextOption.USE); - } - return this; - } - - /** - * Imports a symbol reference if necessary, using the alias of the - * reference and only associated "USE" references. - * - * @param symbolReference Symbol reference to import. - * @return Returns the writer. - */ - public GoWriter addUseImports(SymbolReference symbolReference) { - return addImport(symbolReference.getSymbol(), symbolReference.getAlias(), SymbolReference.ContextOption.USE); - } - - /** - * Adds and imports the given dependency. - * - * @param goDependency The GoDependency to import. - * @return Returns the writer. - */ - public GoWriter addUseImports(GoDependency goDependency) { - dependencies.addAll(goDependency.getDependencies()); - return addImport(goDependency.getImportPath(), goDependency.getAlias()); - } - - private GoWriter addImports(GoWriter other) { - this.imports.addImports(other.imports); - return this; - } - - private boolean isExternalNamespace(String namespace) { - return !StringUtils.isBlank(namespace) && !namespace.equals(fullPackageName); - } + String packageStatement = String.format("package %s%n%n", packageName); - void addImportReferences(Symbol symbol, SymbolReference.ContextOption... options) { - for (SymbolReference reference : symbol.getReferences()) { - for (SymbolReference.ContextOption option : options) { - if (reference.hasOption(option)) { - addImport(reference.getSymbol(), reference.getAlias(), options); - break; - } - } - } - } + String importString = imports.toString(); + String strippedContents = StringUtils.stripStart(contents, null); + String strippedImportString = StringUtils.strip(importString, null); - /** - * Imports a package using an alias if necessary. - * - * @param packageName Package to import. - * @param as Alias to refer to the package as. - * @return Returns the writer. - */ - public GoWriter addImport(String packageName, String as) { - imports.addImport(packageName, as); - return this; - } - - public GoWriter addImportFromModule(String moduleName, String packageName, String as) { - imports.addImport(moduleName.concat("/").concat(packageName), as); - return this; - } - - public GoWriter addImportFromModule(String moduleName, String packageName) { - imports.addImport(moduleName.concat("/").concat(packageName), ""); - return this; - } - - public GoWriter addImport(String packageName) { - imports.addImport(packageName, ""); - return this; - } - - private GoWriter addDependencies(GoWriter other) { - this.dependencies.addAll(other.getDependencies()); - return this; + // Don't add an additional new line between explicit imports and managed imports. + if ( + !strippedImportString.isEmpty() && strippedContents.startsWith("import ") + ) { + return header + strippedImportString + "\n" + strippedContents; } + return header + packageStatement + importString + contents; + } - private boolean isTargetDeprecated(Model model, MemberShape member) { - return model.expectShape(member.getTarget()).getTrait(DeprecatedTrait.class).isPresent() - // don't consider deprecated prelude shapes (like PrimitiveBoolean) - && !Prelude.isPreludeShape(member.getTarget()); - } + /** + * Implements Go symbol formatting for the {@code $T} formatter. + */ + private class GoSymbolFormatter + implements BiFunction { @Override - public String toString() { - String contents = super.toString(); - - if (innerWriter) { - return contents; + public String apply(Object type, String indent) { + if (type instanceof Symbol) { + Symbol resolvedSymbol = (Symbol) type; + String literal = resolvedSymbol.getName(); + + boolean isSlice = resolvedSymbol + .getProperty(SymbolUtils.GO_SLICE, Boolean.class) + .orElse(false); + boolean isMap = resolvedSymbol + .getProperty(SymbolUtils.GO_MAP, Boolean.class) + .orElse(false); + if (isSlice || isMap) { + resolvedSymbol = + resolvedSymbol + .getProperty(SymbolUtils.GO_ELEMENT_TYPE, Symbol.class) + .orElseThrow(() -> + new CodegenException( + "Expected go element type property to be defined" + ) + ); + literal = + new PointableGoSymbolFormatter().apply(resolvedSymbol, "nested"); + } else if ( + !SymbolUtils.isUniverseType(resolvedSymbol) && + isExternalNamespace(resolvedSymbol.getNamespace()) + ) { + literal = formatWithNamespace(resolvedSymbol); } - - - String[] packageParts = fullPackageName.split("/"); - String header = String.format("// Code generated by smithy-go-codegen DO NOT EDIT.%n%n"); - - String packageName = packageParts[packageParts.length - 1]; - if (packageName.startsWith("v") && packageParts.length >= 2) { - String remaining = packageName.substring(1); - try { - int value = Integer.parseInt(remaining); - packageName = packageParts[packageParts.length - 2]; - if (value == 0 || value == 1) { - throw new CodegenException("module paths vN version component must only be N >= 2"); - } - } catch (NumberFormatException ne) { - // Do nothing - } + addUseImports(resolvedSymbol); + + if (isSlice) { + return "[]" + literal; + } else if (isMap) { + return "map[string]" + literal; + } else { + return literal; } + } else if (type instanceof SymbolReference) { + SymbolReference typeSymbol = (SymbolReference) type; + addImport( + typeSymbol.getSymbol(), + typeSymbol.getAlias(), + SymbolReference.ContextOption.USE + ); + return typeSymbol.getAlias(); + } else { + throw new CodegenException( + "Invalid type provided to $T. Expected a Symbol, but found `" + + type + + "`" + ); + } + } + + private String formatWithNamespace(Symbol symbol) { + if (StringUtils.isEmpty(symbol.getNamespace())) { + return symbol.getName(); + } + return String.format( + "%s.%s", + CodegenUtils.getSymbolNamespaceAlias(symbol), + symbol.getName() + ); + } + } + + /** + * Implements Go symbol formatting for the {@code $P} formatter. This is identical to the $T + * formatter, except that it will add a * to symbols that can be pointers. + */ + private class PointableGoSymbolFormatter extends GoSymbolFormatter { - String packageStatement = String.format("package %s%n%n", packageName); - - String importString = imports.toString(); - String strippedContents = StringUtils.stripStart(contents, null); - String strippedImportString = StringUtils.strip(importString, null); - - // Don't add an additional new line between explicit imports and managed imports. - if (!strippedImportString.isEmpty() && strippedContents.startsWith("import ")) { - return header + strippedImportString + "\n" + strippedContents; - } + @Override + public String apply(Object type, String indent) { + String formatted = super.apply(type, indent); + if (isPointer(type)) { + formatted = "*" + formatted; + } + return formatted; + } + + private boolean isPointer(Object type) { + if (type instanceof Symbol) { + Symbol typeSymbol = (Symbol) type; + return typeSymbol + .getProperty(SymbolUtils.POINTABLE, Boolean.class) + .orElse(false); + } else if (type instanceof SymbolReference) { + SymbolReference typeSymbol = (SymbolReference) type; + return ( + typeSymbol + .getProperty(SymbolUtils.POINTABLE, Boolean.class) + .orElse(false) || + typeSymbol + .getSymbol() + .getProperty(SymbolUtils.POINTABLE, Boolean.class) + .orElse(false) + ); + } else if (type instanceof String) { + return true; + } else { + throw new CodegenException( + "Invalid type provided to $P. Expected a Symbol, but found `" + + type + + "`" + ); + } + } + } + + class GoWritableInjector extends GoSymbolFormatter { - return header + packageStatement + importString + contents; + @Override + public String apply(Object type, String indent) { + if (!(type instanceof Writable)) { + throw new CodegenException( + "expect Writable for GoWriter W injector, but got " + type + ); + } + var innerWriter = new GoWriter(fullPackageName, true); + ((Writable) type).accept(innerWriter); + addImports(innerWriter); + addDependencies(innerWriter); + return innerWriter.toString().trim(); } + } - /** - * Implements Go symbol formatting for the {@code $T} formatter. - */ - private class GoSymbolFormatter implements BiFunction { - @Override - public String apply(Object type, String indent) { - if (type instanceof Symbol) { - Symbol resolvedSymbol = (Symbol) type; - String literal = resolvedSymbol.getName(); - - boolean isSlice = resolvedSymbol.getProperty(SymbolUtils.GO_SLICE, Boolean.class).orElse(false); - boolean isMap = resolvedSymbol.getProperty(SymbolUtils.GO_MAP, Boolean.class).orElse(false); - if (isSlice || isMap) { - resolvedSymbol = resolvedSymbol.getProperty(SymbolUtils.GO_ELEMENT_TYPE, Symbol.class) - .orElseThrow(() -> new CodegenException("Expected go element type property to be defined")); - literal = new PointableGoSymbolFormatter().apply(resolvedSymbol, "nested"); - } else if (!SymbolUtils.isUniverseType(resolvedSymbol) - && isExternalNamespace(resolvedSymbol.getNamespace())) { - literal = formatWithNamespace(resolvedSymbol); - } - addUseImports(resolvedSymbol); - - if (isSlice) { - return "[]" + literal; - } else if (isMap) { - return "map[string]" + literal; - } else { - return literal; - } - } else if (type instanceof SymbolReference) { - SymbolReference typeSymbol = (SymbolReference) type; - addImport(typeSymbol.getSymbol(), typeSymbol.getAlias(), SymbolReference.ContextOption.USE); - return typeSymbol.getAlias(); - } else { - throw new CodegenException( - "Invalid type provided to $T. Expected a Symbol, but found `" + type + "`"); - } - } + public interface Writable extends Consumer {} - private String formatWithNamespace(Symbol symbol) { - if (StringUtils.isEmpty(symbol.getNamespace())) { - return symbol.getName(); - } - return String.format("%s.%s", CodegenUtils.getSymbolNamespaceAlias(symbol), symbol.getName()); - } - } + /** + * Chains together multiple Writables that can be composed into one Writable. + */ + public static final class ChainWritable { - /** - * Implements Go symbol formatting for the {@code $P} formatter. This is identical to the $T - * formatter, except that it will add a * to symbols that can be pointers. - */ - private class PointableGoSymbolFormatter extends GoSymbolFormatter { - @Override - public String apply(Object type, String indent) { - String formatted = super.apply(type, indent); - if (isPointer(type)) { - formatted = "*" + formatted; - } - return formatted; - } + private final List writables; - private boolean isPointer(Object type) { - if (type instanceof Symbol) { - Symbol typeSymbol = (Symbol) type; - return typeSymbol.getProperty(SymbolUtils.POINTABLE, Boolean.class).orElse(false); - } else if (type instanceof SymbolReference) { - SymbolReference typeSymbol = (SymbolReference) type; - return typeSymbol.getProperty(SymbolUtils.POINTABLE, Boolean.class).orElse(false) - || typeSymbol.getSymbol().getProperty(SymbolUtils.POINTABLE, Boolean.class).orElse(false); - } else if (type instanceof String) { - return true; - } else { - throw new CodegenException( - "Invalid type provided to $P. Expected a Symbol, but found `" + type + "`"); - } - } + public ChainWritable() { + writables = new ArrayList<>(); } - class GoWritableInjector extends GoSymbolFormatter { - @Override - public String apply(Object type, String indent) { - if (!(type instanceof Writable)) { - throw new CodegenException( - "expect Writable for GoWriter W injector, but got " + type); - } - var innerWriter = new GoWriter(fullPackageName, true); - ((Writable) type).accept(innerWriter); - addImports(innerWriter); - addDependencies(innerWriter); - return innerWriter.toString().trim(); - } + public ChainWritable add(GoWriter.Writable writable) { + writables.add(writable); + return this; } - public interface Writable extends Consumer { + public ChainWritable add(Optional value, Function fn) { + value.ifPresent(t -> writables.add(fn.apply(t))); + return this; } - /** - * Chains together multiple Writables that can be composed into one Writable. - */ - public static final class ChainWritable { - private final List writables; - - public ChainWritable() { - writables = new ArrayList<>(); - } - - public ChainWritable add(GoWriter.Writable writable) { - writables.add(writable); - return this; - } - - public ChainWritable add(Optional value, Function fn) { - value.ifPresent(t -> writables.add(fn.apply(t))); - return this; - } - - public ChainWritable add(boolean include, GoWriter.Writable writable) { - if (!include) { - writables.add(writable); - } - return this; - } + public ChainWritable add(boolean include, GoWriter.Writable writable) { + if (!include) { + writables.add(writable); + } + return this; + } - public GoWriter.Writable compose() { - return (GoWriter writer) -> { - var hasPrevious = false; - for (GoWriter.Writable writable : writables) { - if (hasPrevious) { - writer.write(""); - } - hasPrevious = true; - writer.write("$W", writable); - } - }; + public GoWriter.Writable compose() { + return (GoWriter writer) -> { + var hasPrevious = false; + for (GoWriter.Writable writable : writables) { + if (hasPrevious) { + writer.write(""); + } + hasPrevious = true; + writer.write("$W", writable); } + }; } + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/ImportDeclarations.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/ImportDeclarations.java index a8c3dcf30f..bd528e9d3c 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/ImportDeclarations.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/ImportDeclarations.java @@ -1,62 +1,65 @@ package software.amazon.polymorph.smithygo.codegen; +import java.util.Map; +import java.util.TreeMap; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.ImportContainer; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.utils.StringUtils; -import java.util.Map; -import java.util.TreeMap; - public class ImportDeclarations implements ImportContainer { - private final Map imports = new TreeMap<>(); - - ImportDeclarations addImport(String importPath, String alias) { - String importAlias = CodegenUtils.getDefaultPackageImportName(importPath); - if (!StringUtils.isBlank(alias)) { - if (alias.equals(".")) { - // Global imports are generally a bad practice. - throw new CodegenException("Globally importing packages is forbidden: " + importPath); - } - importAlias = alias; - } - imports.putIfAbsent(importAlias, importPath); - return this; - } + private final Map imports = new TreeMap<>(); - ImportDeclarations addImports(ImportDeclarations other) { - other.imports.forEach((importAlias, importPath) -> { - addImport(importPath, importAlias); - }); - return this; + ImportDeclarations addImport(String importPath, String alias) { + String importAlias = CodegenUtils.getDefaultPackageImportName(importPath); + if (!StringUtils.isBlank(alias)) { + if (alias.equals(".")) { + // Global imports are generally a bad practice. + throw new CodegenException( + "Globally importing packages is forbidden: " + importPath + ); + } + importAlias = alias; } + imports.putIfAbsent(importAlias, importPath); + return this; + } - @Override - public String toString() { - if (imports.isEmpty()) { - return ""; - } - - StringBuilder builder = new StringBuilder("import (\n"); - for (Map.Entry entry : imports.entrySet()) { - builder.append('\t'); - builder.append(createImportStatement(entry)); - builder.append('\n'); - } - builder.append(")\n\n"); - return builder.toString(); - } + ImportDeclarations addImports(ImportDeclarations other) { + other.imports.forEach((importAlias, importPath) -> { + addImport(importPath, importAlias); + }); + return this; + } - private String createImportStatement(Map.Entry entry) { - String formattedPackageName = "\"" + entry.getValue() + "\""; - return CodegenUtils.getDefaultPackageImportName(entry.getValue()).equals(entry.getKey()) - ? formattedPackageName - : entry.getKey() + " " + formattedPackageName; + @Override + public String toString() { + if (imports.isEmpty()) { + return ""; } - @Override - public void importSymbol(Symbol symbol, String alias) { - addImport(symbol.getName(), alias); + StringBuilder builder = new StringBuilder("import (\n"); + for (Map.Entry entry : imports.entrySet()) { + builder.append('\t'); + builder.append(createImportStatement(entry)); + builder.append('\n'); } + builder.append(")\n\n"); + return builder.toString(); + } + + private String createImportStatement(Map.Entry entry) { + String formattedPackageName = "\"" + entry.getValue() + "\""; + return CodegenUtils + .getDefaultPackageImportName(entry.getValue()) + .equals(entry.getKey()) + ? formattedPackageName + : entry.getKey() + " " + formattedPackageName; + } + + @Override + public void importSymbol(Symbol symbol, String alias) { + addImport(symbol.getName(), alias); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/IntEnumGenerator.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/IntEnumGenerator.java index 3d23121ce3..55387111bc 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/IntEnumGenerator.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/IntEnumGenerator.java @@ -32,60 +32,87 @@ * Renders intEnums and their constants. */ public final class IntEnumGenerator implements Runnable { - private static final Logger LOGGER = Logger.getLogger(IntEnumGenerator.class.getName()); - private final SymbolProvider symbolProvider; - private final GoWriter writer; - private final IntEnumShape shape; + private static final Logger LOGGER = Logger.getLogger( + IntEnumGenerator.class.getName() + ); - public IntEnumGenerator(SymbolProvider symbolProvider, GoWriter writer, IntEnumShape shape) { - this.symbolProvider = symbolProvider; - this.writer = writer; - this.shape = shape; - } + private final SymbolProvider symbolProvider; + private final GoWriter writer; + private final IntEnumShape shape; - @Override - public void run() { - Symbol symbol = symbolProvider.toSymbol(shape); + public IntEnumGenerator( + SymbolProvider symbolProvider, + GoWriter writer, + IntEnumShape shape + ) { + this.symbolProvider = symbolProvider; + this.writer = writer; + this.shape = shape; + } -// TODO(smithy): Use type alias instead of type definition until refactoring -// protocol generators is prioritized. - writer.write("type $L = int32", symbol.getName()).write(""); + @Override + public void run() { + Symbol symbol = symbolProvider.toSymbol(shape); - Set constants = new LinkedHashSet<>(); - writer.openBlock("const (", ")", () -> { - for (Map.Entry entry : shape.getAllMembers().entrySet()) { - StringBuilder labelBuilder = new StringBuilder(symbol.getName()); - String name = entry.getKey(); + // TODO(smithy): Use type alias instead of type definition until refactoring + // protocol generators is prioritized. + writer.write("type $L = int32", symbol.getName()).write(""); - for (String part : name.split("(?U)[\\W_]")) { - if (part.matches(".*[a-z].*") && part.matches(".*[A-Z].*")) { -// Mixed case names should not be changed other than first letter capitalized. - labelBuilder.append(StringUtils.capitalize(part)); - } else { -// For all non-mixed case parts title case first letter, followed by all other lower cased. - labelBuilder.append(StringUtils.capitalize(part.toLowerCase(Locale.US))); - } - } - String label = labelBuilder.toString(); + Set constants = new LinkedHashSet<>(); + writer + .openBlock( + "const (", + ")", + () -> { + for (Map.Entry entry : shape + .getAllMembers() + .entrySet()) { + StringBuilder labelBuilder = new StringBuilder(symbol.getName()); + String name = entry.getKey(); -// If camel-casing would cause a conflict, don't camel-case this enum value. - if (constants.contains(label)) { - LOGGER.warning(String.format( - "Multiple enums resolved to the same name, `%s`, using unaltered value for: %s", - label, name)); - label = name; - } - constants.add(label); + for (String part : name.split("(?U)[\\W_]")) { + if (part.matches(".*[a-z].*") && part.matches(".*[A-Z].*")) { + // Mixed case names should not be changed other than first letter capitalized. + labelBuilder.append(StringUtils.capitalize(part)); + } else { + // For all non-mixed case parts title case first letter, followed by all other lower cased. + labelBuilder.append( + StringUtils.capitalize(part.toLowerCase(Locale.US)) + ); + } + } + String label = labelBuilder.toString(); - writer.write("$L $L = $L", label, symbol.getName(), - entry.getValue().expectTrait(EnumValueTrait.class).expectIntValue()); + // If camel-casing would cause a conflict, don't camel-case this enum value. + if (constants.contains(label)) { + LOGGER.warning( + String.format( + "Multiple enums resolved to the same name, `%s`, using unaltered value for: %s", + label, + name + ) + ); + label = name; } - }).write(""); + constants.add(label); -// TODO(smithy): type aliases don't allow defining methods on base types (e.g. int32). -// Uncomment generating the Value() method when the type alias is migrated to type definition. -/* + writer.write( + "$L $L = $L", + label, + symbol.getName(), + entry + .getValue() + .expectTrait(EnumValueTrait.class) + .expectIntValue() + ); + } + } + ) + .write(""); + // TODO(smithy): type aliases don't allow defining methods on base types (e.g. int32). + // Uncomment generating the Value() method when the type alias is migrated to type definition. + /* writer.writeDocs(String.format("Values returns all known values for %s. Note that this can be expanded in the " + "future, and so it is only as up to date as the client.%n%nThe ordering of this slice is not " + "guaranteed to be stable across updates.", symbol.getName())); @@ -97,5 +124,5 @@ public void run() { }); }); */ - } -} \ No newline at end of file + } +} diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/SmithyGoDependency.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/SmithyGoDependency.java index 20955d8ea8..c5c95d399c 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/SmithyGoDependency.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/SmithyGoDependency.java @@ -19,116 +19,171 @@ * A class of constants for dependencies used by this package. */ public final class SmithyGoDependency { - // The version in the stdlib dependencies should reflect the minimum Go version. - // The values aren't currently used, but they could potentially used to dynamically - // set the minimum go version. - public static final GoDependency BIG = stdlib("math/big"); - public static final GoDependency TIME = stdlib("time"); - public static final GoDependency FMT = stdlib("fmt"); - public static final GoDependency CONTEXT = stdlib("context"); - public static final GoDependency STRCONV = stdlib("strconv"); - public static final GoDependency BASE64 = stdlib("encoding/base64"); - public static final GoDependency NET = stdlib("net"); - public static final GoDependency NET_URL = stdlib("net/url"); - public static final GoDependency NET_HTTP = stdlib("net/http"); - public static final GoDependency NET_HTTP_TEST = stdlib("net/http/httptest"); - public static final GoDependency BYTES = stdlib("bytes"); - public static final GoDependency STRINGS = stdlib("strings"); - public static final GoDependency JSON = stdlib("encoding/json"); - public static final GoDependency IO = stdlib("io"); - public static final GoDependency IOUTIL = stdlib("io/ioutil"); - public static final GoDependency CRYPTORAND = stdlib("crypto/rand", "cryptorand"); - public static final GoDependency TESTING = stdlib("testing"); - public static final GoDependency ERRORS = stdlib("errors"); - public static final GoDependency XML = stdlib("encoding/xml"); - public static final GoDependency SYNC = stdlib("sync"); - public static final GoDependency PATH = stdlib("path"); - - public static final GoDependency SMITHY = smithy(null, "smithy"); - public static final GoDependency SMITHY_HTTP_TRANSPORT = smithy("transport/http", "smithyhttp"); - public static final GoDependency SMITHY_MIDDLEWARE = smithy("middleware"); - public static final GoDependency SMITHY_TIME = smithy("time", "smithytime"); - public static final GoDependency SMITHY_HTTP_BINDING = smithy("encoding/httpbinding"); - public static final GoDependency SMITHY_JSON = smithy("encoding/json", "smithyjson"); - public static final GoDependency SMITHY_XML = smithy("encoding/xml", "smithyxml"); - public static final GoDependency SMITHY_IO = smithy("io", "smithyio"); - public static final GoDependency SMITHY_LOGGING = smithy("logging"); - public static final GoDependency SMITHY_PTR = smithy("ptr"); - public static final GoDependency SMITHY_RAND = smithy("rand", "smithyrand"); - public static final GoDependency SMITHY_TESTING = smithy("testing", "smithytesting"); - public static final GoDependency SMITHY_WAITERS = smithy("waiter", "smithywaiter"); - public static final GoDependency SMITHY_DOCUMENT = smithy("document", "smithydocument"); - public static final GoDependency SMITHY_DOCUMENT_JSON = smithy("document/json", "smithydocumentjson"); - public static final GoDependency SMITHY_SYNC = smithy("sync", "smithysync"); - public static final GoDependency SMITHY_AUTH_BEARER = smithy("auth/bearer"); - - public static final GoDependency GO_CMP = goCmp("cmp"); - public static final GoDependency GO_CMP_OPTIONS = goCmp("cmp/cmpopts"); - - public static final GoDependency GO_JMESPATH = goJmespath(null); - public static final GoDependency MATH = stdlib("math"); - - private static final String SMITHY_SOURCE_PATH = "github.com/aws/smithy-go"; - private static final String GO_CMP_SOURCE_PATH = "github.com/google/go-cmp"; - private static final String GO_JMESPATH_SOURCE_PATH = "github.com/jmespath/go-jmespath"; - - private SmithyGoDependency() { - } - /** - * Get a {@link GoDependency} representing the standard library package import path. - * - * @param importPath standard library import path - * @return the {@link GoDependency} for the package import path - */ - public static GoDependency stdlib(String importPath) { - return GoDependency.standardLibraryDependency(importPath, Versions.GO_STDLIB); - } + // The version in the stdlib dependencies should reflect the minimum Go version. + // The values aren't currently used, but they could potentially used to dynamically + // set the minimum go version. + public static final GoDependency BIG = stdlib("math/big"); + public static final GoDependency TIME = stdlib("time"); + public static final GoDependency FMT = stdlib("fmt"); + public static final GoDependency CONTEXT = stdlib("context"); + public static final GoDependency STRCONV = stdlib("strconv"); + public static final GoDependency BASE64 = stdlib("encoding/base64"); + public static final GoDependency NET = stdlib("net"); + public static final GoDependency NET_URL = stdlib("net/url"); + public static final GoDependency NET_HTTP = stdlib("net/http"); + public static final GoDependency NET_HTTP_TEST = stdlib("net/http/httptest"); + public static final GoDependency BYTES = stdlib("bytes"); + public static final GoDependency STRINGS = stdlib("strings"); + public static final GoDependency JSON = stdlib("encoding/json"); + public static final GoDependency IO = stdlib("io"); + public static final GoDependency IOUTIL = stdlib("io/ioutil"); + public static final GoDependency CRYPTORAND = stdlib( + "crypto/rand", + "cryptorand" + ); + public static final GoDependency TESTING = stdlib("testing"); + public static final GoDependency ERRORS = stdlib("errors"); + public static final GoDependency XML = stdlib("encoding/xml"); + public static final GoDependency SYNC = stdlib("sync"); + public static final GoDependency PATH = stdlib("path"); - /** - * Get a {@link GoDependency} representing the standard library package import path with the given alias. - * - * @param importPath standard library package import path - * @param alias the package alias - * @return the {@link GoDependency} for the package import path - */ - public static GoDependency stdlib(String importPath, String alias) { - return GoDependency.standardLibraryDependency(importPath, Versions.GO_STDLIB, alias); - } + public static final GoDependency SMITHY = smithy(null, "smithy"); + public static final GoDependency SMITHY_HTTP_TRANSPORT = smithy( + "transport/http", + "smithyhttp" + ); + public static final GoDependency SMITHY_MIDDLEWARE = smithy("middleware"); + public static final GoDependency SMITHY_TIME = smithy("time", "smithytime"); + public static final GoDependency SMITHY_HTTP_BINDING = smithy( + "encoding/httpbinding" + ); + public static final GoDependency SMITHY_JSON = smithy( + "encoding/json", + "smithyjson" + ); + public static final GoDependency SMITHY_XML = smithy( + "encoding/xml", + "smithyxml" + ); + public static final GoDependency SMITHY_IO = smithy("io", "smithyio"); + public static final GoDependency SMITHY_LOGGING = smithy("logging"); + public static final GoDependency SMITHY_PTR = smithy("ptr"); + public static final GoDependency SMITHY_RAND = smithy("rand", "smithyrand"); + public static final GoDependency SMITHY_TESTING = smithy( + "testing", + "smithytesting" + ); + public static final GoDependency SMITHY_WAITERS = smithy( + "waiter", + "smithywaiter" + ); + public static final GoDependency SMITHY_DOCUMENT = smithy( + "document", + "smithydocument" + ); + public static final GoDependency SMITHY_DOCUMENT_JSON = smithy( + "document/json", + "smithydocumentjson" + ); + public static final GoDependency SMITHY_SYNC = smithy("sync", "smithysync"); + public static final GoDependency SMITHY_AUTH_BEARER = smithy("auth/bearer"); - private static GoDependency smithy(String relativePath) { - return smithy(relativePath, null); - } + public static final GoDependency GO_CMP = goCmp("cmp"); + public static final GoDependency GO_CMP_OPTIONS = goCmp("cmp/cmpopts"); - private static GoDependency smithy(String relativePath, String alias) { - return relativePackage(SMITHY_SOURCE_PATH, relativePath, Versions.SMITHY_GO, alias); - } + public static final GoDependency GO_JMESPATH = goJmespath(null); + public static final GoDependency MATH = stdlib("math"); - private static GoDependency goCmp(String relativePath) { - return relativePackage(GO_CMP_SOURCE_PATH, relativePath, Versions.GO_CMP, null); - } + private static final String SMITHY_SOURCE_PATH = "github.com/aws/smithy-go"; + private static final String GO_CMP_SOURCE_PATH = "github.com/google/go-cmp"; + private static final String GO_JMESPATH_SOURCE_PATH = + "github.com/jmespath/go-jmespath"; - private static GoDependency goJmespath(String relativePath) { - return relativePackage(GO_JMESPATH_SOURCE_PATH, relativePath, Versions.GO_JMESPATH, null); - } + private SmithyGoDependency() {} - private static GoDependency relativePackage( - String moduleImportPath, - String relativePath, - String version, - String alias - ) { - String importPath = moduleImportPath; - if (relativePath != null) { - importPath = importPath + "/" + relativePath; - } - return GoDependency.moduleDependency(moduleImportPath, importPath, version, alias); - } + /** + * Get a {@link GoDependency} representing the standard library package import path. + * + * @param importPath standard library import path + * @return the {@link GoDependency} for the package import path + */ + public static GoDependency stdlib(String importPath) { + return GoDependency.standardLibraryDependency( + importPath, + Versions.GO_STDLIB + ); + } - private static final class Versions { - private static final String GO_STDLIB = "1.15"; - private static final String GO_CMP = "v0.5.4"; - private static final String SMITHY_GO = "v1.4.0"; - private static final String GO_JMESPATH = "v0.4.0"; + /** + * Get a {@link GoDependency} representing the standard library package import path with the given alias. + * + * @param importPath standard library package import path + * @param alias the package alias + * @return the {@link GoDependency} for the package import path + */ + public static GoDependency stdlib(String importPath, String alias) { + return GoDependency.standardLibraryDependency( + importPath, + Versions.GO_STDLIB, + alias + ); + } + + private static GoDependency smithy(String relativePath) { + return smithy(relativePath, null); + } + + private static GoDependency smithy(String relativePath, String alias) { + return relativePackage( + SMITHY_SOURCE_PATH, + relativePath, + Versions.SMITHY_GO, + alias + ); + } + + private static GoDependency goCmp(String relativePath) { + return relativePackage( + GO_CMP_SOURCE_PATH, + relativePath, + Versions.GO_CMP, + null + ); + } + + private static GoDependency goJmespath(String relativePath) { + return relativePackage( + GO_JMESPATH_SOURCE_PATH, + relativePath, + Versions.GO_JMESPATH, + null + ); + } + + private static GoDependency relativePackage( + String moduleImportPath, + String relativePath, + String version, + String alias + ) { + String importPath = moduleImportPath; + if (relativePath != null) { + importPath = importPath + "/" + relativePath; } + return GoDependency.moduleDependency( + moduleImportPath, + importPath, + version, + alias + ); + } + + private static final class Versions { + + private static final String GO_STDLIB = "1.15"; + private static final String GO_CMP = "v0.5.4"; + private static final String SMITHY_GO = "v1.4.0"; + private static final String GO_JMESPATH = "v0.4.0"; + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/StructureGenerator.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/StructureGenerator.java index 8f0415c4ec..74ef4633e8 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/StructureGenerator.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/StructureGenerator.java @@ -15,14 +15,21 @@ package software.amazon.polymorph.smithygo.codegen; +import static software.amazon.polymorph.smithygo.codegen.SymbolUtils.POINTABLE; + +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; import software.amazon.polymorph.smithygo.localservice.nameresolver.SmithyNameResolver; import software.amazon.polymorph.traits.DafnyUtf8BytesTrait; import software.amazon.polymorph.traits.ReferenceTrait; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.MemberShape; +import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.model.traits.LengthTrait; @@ -31,147 +38,193 @@ import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.SetUtils; -import static software.amazon.polymorph.smithygo.codegen.SymbolUtils.POINTABLE; - -import java.math.BigDecimal; -import java.util.HashSet; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; - /** * Renders structures. */ public final class StructureGenerator implements Runnable { - private static final Set ERROR_MEMBER_NAMES = SetUtils.of("ErrorMessage", "Message", "ErrorCodeOverride"); - - private final Model model; - private final SymbolProvider symbolProvider; - private final GoWriter writer; - private final StructureShape shape; - private final GenerationContext context; - private final ValidationGenerator validationGenerator; - - public StructureGenerator( - final GenerationContext context, - GoWriter writer, - StructureShape shape) { - this.context = context; - this.model = context.model(); - this.symbolProvider = context.symbolProvider(); - this.writer = writer; - this.shape = shape; - this.validationGenerator = new ValidationGenerator(model, symbolProvider, writer); - } - @Override - public void run() { - if (!shape.hasTrait(ErrorTrait.class)) { - renderStructure(() -> { - }); + private static final Set ERROR_MEMBER_NAMES = SetUtils.of( + "ErrorMessage", + "Message", + "ErrorCodeOverride" + ); + + private final Model model; + private final SymbolProvider symbolProvider; + private final GoWriter writer; + private final StructureShape shape; + private final GenerationContext context; + private final ValidationGenerator validationGenerator; + + public StructureGenerator( + final GenerationContext context, + GoWriter writer, + StructureShape shape + ) { + this.context = context; + this.model = context.model(); + this.symbolProvider = context.symbolProvider(); + this.writer = writer; + this.shape = shape; + this.validationGenerator = + new ValidationGenerator(model, symbolProvider, writer); + } + + @Override + public void run() { + if (!shape.hasTrait(ErrorTrait.class)) { + renderStructure(() -> {}); + } else { + renderErrorStructure(); + } + } + + /** + * Renders a non-error structure. + * + * @param runnable A runnable that runs before the structure definition is closed. This can be used to write + * additional members. + */ + public void renderStructure(Runnable runnable) { + renderStructure(runnable, false); + } + + /** + * Renders a non-error structure. + * + * @param runnable A runnable that runs before the structure definition is closed. This can be used to write + * additional members. + * @param isInputStructure A boolean indicating if input variants for member symbols should be used. + */ + public void renderStructure(Runnable runnable, boolean isInputStructure) { + writer.addImport("fmt"); + Symbol symbol = symbolProvider.toSymbol(shape); + writer.openBlock("type $L struct {", symbol.getName()); + CodegenUtils.SortedMembers sortedMembers = new CodegenUtils.SortedMembers( + symbolProvider + ); + shape + .getAllMembers() + .values() + .stream() + .filter(memberShape -> !StreamingTrait.isEventStream(model, memberShape)) + .sorted(sortedMembers) + .forEach(member -> { + writer.write(""); + + String memberName = symbolProvider.toMemberName(member); + + Symbol memberSymbol = symbolProvider.toSymbol(member); + + var targetShape = model.expectShape(member.getTarget()); + + if (isInputStructure) { + memberSymbol = + memberSymbol + .getProperty(SymbolUtils.INPUT_VARIANT, Symbol.class) + .orElse(memberSymbol); + } + var namespace = SmithyNameResolver.smithyTypesNamespace(targetShape); + + if (targetShape.hasTrait(ReferenceTrait.class)) { + memberSymbol = + memberSymbol.getProperty("Referred", Symbol.class).get(); + var refShape = targetShape.expectTrait(ReferenceTrait.class); + if (refShape.isService()) { + namespace = + SmithyNameResolver.shapeNamespace( + model.expectShape(refShape.getReferentId()) + ); + } + if ( + !member + .toShapeId() + .getNamespace() + .equals(refShape.getReferentId().getNamespace()) + ) { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + refShape.getReferentId().getNamespace() + ), + namespace + ); + } } else { - renderErrorStructure(); + if ( + !member + .toShapeId() + .getNamespace() + .equals(targetShape.toShapeId().getNamespace()) && + !targetShape.toShapeId().getNamespace().startsWith("smithy") && + targetShape.asStructureShape().isPresent() + ) { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + targetShape.toShapeId().getNamespace() + ), + namespace + ); + } } - } - /** - * Renders a non-error structure. - * - * @param runnable A runnable that runs before the structure definition is closed. This can be used to write - * additional members. - */ - public void renderStructure(Runnable runnable) { - renderStructure(runnable, false); - } - - /** - * Renders a non-error structure. - * - * @param runnable A runnable that runs before the structure definition is closed. This can be used to write - * additional members. - * @param isInputStructure A boolean indicating if input variants for member symbols should be used. - */ - public void renderStructure(Runnable runnable, boolean isInputStructure) { - writer.addImport("fmt"); - Symbol symbol = symbolProvider.toSymbol(shape); - writer.openBlock("type $L struct {", symbol.getName()); - CodegenUtils.SortedMembers sortedMembers = new CodegenUtils.SortedMembers(symbolProvider); - shape.getAllMembers().values().stream() - .filter(memberShape -> !StreamingTrait.isEventStream(model, memberShape)) - .sorted(sortedMembers) - .forEach((member) -> { - writer.write(""); - - String memberName = symbolProvider.toMemberName(member); - - Symbol memberSymbol = symbolProvider.toSymbol(member); - - var targetShape = model.expectShape(member.getTarget()); - - if (isInputStructure) { - memberSymbol = memberSymbol.getProperty(SymbolUtils.INPUT_VARIANT, Symbol.class) - .orElse(memberSymbol); - } - var namespace = SmithyNameResolver.smithyTypesNamespace(targetShape); - - if (targetShape.hasTrait(ReferenceTrait.class)) { - memberSymbol = memberSymbol.getProperty("Referred", Symbol.class).get(); - var refShape = targetShape.expectTrait(ReferenceTrait.class); - if (refShape.isService()) { - namespace = SmithyNameResolver.shapeNamespace(model.expectShape(refShape.getReferentId())); - } - if (!member.toShapeId().getNamespace().equals(refShape.getReferentId().getNamespace())) { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(refShape.getReferentId().getNamespace()), namespace); - } - } else { - if (!member.toShapeId().getNamespace().equals(targetShape.toShapeId().getNamespace()) && !targetShape.toShapeId().getNamespace().startsWith("smithy") && targetShape.asStructureShape().isPresent()) { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(targetShape.toShapeId().getNamespace()), namespace); - } - } - - writer.write("$L $P", memberName, memberSymbol); - - }); - writer.closeBlock("}").write(""); - validationGenerator.renderValidator(shape, isInputStructure); - } - - /** - * Renders an error structure and supporting methods. - */ - private void renderErrorStructure() { - Symbol structureSymbol = symbolProvider.toSymbol(shape); - writer.addUseImports(SmithyGoDependency.FMT); - ErrorTrait errorTrait = shape.expectTrait(ErrorTrait.class); - - // Write out a struct to hold the error data. - writer.openBlock("type $L struct {", "}", structureSymbol.getName(), () -> { - writer.write("$LBaseException", context.settings().getService().getName()); - Set memberNameSet = new HashSet<>(); - // TODO: Revisit if message has to be strictly pointer or not (even with required trait). - // When any shape is required we don't add pointer in local service but AWS SDK does. - for (MemberShape member : shape.getAllMembers().values()) { - String memberName = symbolProvider.toMemberName(member); - memberNameSet.add(memberName); - writer.write("$L $P", memberName, symbolProvider.toSymbol(member)); - } - - // The message is the only part of the standard APIError interface that isn't known ahead of time. - // Message is a pointer mostly for the sake of consistency. - - // If Message and ErrorCodeOverride is not defined in model. - if (!memberNameSet.contains("Message")) { - writer.write("Message *string").write(""); - } - if (!memberNameSet.contains("ErrorCodeOverride")) { - writer.write("ErrorCodeOverride *string").write(""); - } - - }).write(""); - - // write the Error method to satisfy the standard error interface - writer.openBlock("func (e $L) Error() string {", "}", structureSymbol.getName(), () -> { - writer.write("return fmt.Sprintf(\"%s: %s\", e.ErrorCodeOverride, e.Message)"); - }); - } + writer.write("$L $P", memberName, memberSymbol); + }); + writer.closeBlock("}").write(""); + validationGenerator.renderValidator(shape, isInputStructure); + } + + /** + * Renders an error structure and supporting methods. + */ + private void renderErrorStructure() { + Symbol structureSymbol = symbolProvider.toSymbol(shape); + writer.addUseImports(SmithyGoDependency.FMT); + ErrorTrait errorTrait = shape.expectTrait(ErrorTrait.class); + + // Write out a struct to hold the error data. + writer + .openBlock( + "type $L struct {", + "}", + structureSymbol.getName(), + () -> { + writer.write( + "$LBaseException", + context.settings().getService().getName() + ); + Set memberNameSet = new HashSet<>(); + // TODO: Revisit if message has to be strictly pointer or not (even with required trait). + // When any shape is required we don't add pointer in local service but AWS SDK does. + for (MemberShape member : shape.getAllMembers().values()) { + String memberName = symbolProvider.toMemberName(member); + memberNameSet.add(memberName); + writer.write("$L $P", memberName, symbolProvider.toSymbol(member)); + } + + // The message is the only part of the standard APIError interface that isn't known ahead of time. + // Message is a pointer mostly for the sake of consistency. + + // If Message and ErrorCodeOverride is not defined in model. + if (!memberNameSet.contains("Message")) { + writer.write("Message *string").write(""); + } + if (!memberNameSet.contains("ErrorCodeOverride")) { + writer.write("ErrorCodeOverride *string").write(""); + } + } + ) + .write(""); + + // write the Error method to satisfy the standard error interface + writer.openBlock( + "func (e $L) Error() string {", + "}", + structureSymbol.getName(), + () -> { + writer.write( + "return fmt.Sprintf(\"%s: %s\", e.ErrorCodeOverride, e.Message)" + ); + } + ); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/SymbolUtils.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/SymbolUtils.java index 390a6919d8..ec7d1aba80 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/SymbolUtils.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/SymbolUtils.java @@ -23,173 +23,217 @@ */ public final class SymbolUtils { - public static final String POINTABLE = "pointable"; - public static final String NAMESPACE_ALIAS = "namespaceAlias"; - public static final String GO_UNIVERSE_TYPE = "universeType"; - public static final String GO_SLICE = "goSlice"; - public static final String GO_MAP = "goMap"; - public static final String GO_ELEMENT_TYPE = "goElementType"; - - // Used when a given shape must be represented differently on input. - public static final String INPUT_VARIANT = "inputVariant"; - - private SymbolUtils() { - } - - /** - * Create a value symbol builder. - * - * @param typeName the name of the type. - * @return the symbol builder type. - */ - public static Symbol.Builder createValueSymbolBuilder(String typeName) { - return Symbol.builder() - .putProperty(POINTABLE, false) - .name(typeName); - } - - /** - * Create a pointable symbol builder. - * - * @param typeName the name of the type. - * @return the symbol builder. - */ - public static Symbol.Builder createPointableSymbolBuilder(String typeName) { - return Symbol.builder() - .putProperty(POINTABLE, true) - .name(typeName); - } - - /** - * Create a value symbol builder. - * - * @param shape the shape that the type is for. - * @param typeName the name of the type. - * @return the symbol builder. - */ - public static Symbol.Builder createValueSymbolBuilder(Shape shape, String typeName) { - return createValueSymbolBuilder(typeName).putProperty("shape", shape); - } - - /** - * Create a pointable symbol builder. - * - * @param shape the shape that the type is for. - * @param typeName the name of the type. - * @return the symbol builder. - */ - public static Symbol.Builder createPointableSymbolBuilder(Shape shape, String typeName) { - return createPointableSymbolBuilder(typeName).putProperty("shape", shape); - } - - /** - * Create a pointable symbol builder. - * - * @param typeName the name of the type. - * @param namespace the namespace of the type. - * @return the symbol builder. - */ - public static Symbol.Builder createPointableSymbolBuilder(String typeName, String namespace) { - return createPointableSymbolBuilder(typeName).namespace(namespace, "."); - } - - /** - * Create a value symbol builder. - * - * @param typeName the name of the type. - * @param namespace the namespace of the type. - * @return the symbol builder. - */ - public static Symbol.Builder createValueSymbolBuilder(String typeName, String namespace) { - return createValueSymbolBuilder(typeName).namespace(namespace, "."); - } - - /** - * Create a pointable symbol builder. - * - * @param shape the shape that the type is for. - * @param typeName the name of the type. - * @param namespace the namespace of the type. - * @return the symbol builder. - */ - public static Symbol.Builder createPointableSymbolBuilder(Shape shape, String typeName, String namespace) { - return createPointableSymbolBuilder(shape, typeName).namespace(namespace, "."); - } - - /** - * Create a value symbol builder. - * - * @param shape the shape that the type is for. - * @param typeName the name of the type. - * @param namespace the namespace of the type. - * @return the symbol builder. - */ - public static Symbol.Builder createValueSymbolBuilder(Shape shape, String typeName, String namespace) { - return createValueSymbolBuilder(shape, typeName).namespace(namespace, "."); - } - - /** - * Create a pointable symbol builder. - * - * @param shape the shape that the type is for. - * @param typeName the name of the type. - * @param namespace the namespace of the type. - * @return the symbol builder. - */ - public static Symbol.Builder createPointableSymbolBuilder(Shape shape, String typeName, GoDependency namespace) { - return setImportedNamespace(createPointableSymbolBuilder(shape, typeName), namespace); - } - - /** - * Create a value symbol builder. - * - * @param shape the shape that the type is for. - * @param typeName the name of the type. - * @param namespace the namespace of the type. - * @return the symbol builder. - */ - public static Symbol.Builder createValueSymbolBuilder(Shape shape, String typeName, GoDependency namespace) { - return setImportedNamespace(createValueSymbolBuilder(shape, typeName), namespace); - } - - /** - * Create a pointable symbol builder. - * - * @param typeName the name of the type. - * @param namespace the namespace of the type. - * @return the symbol builder. - */ - public static Symbol.Builder createPointableSymbolBuilder(String typeName, GoDependency namespace) { - return setImportedNamespace(createPointableSymbolBuilder(typeName), namespace); - } - - /** - * Create a value symbol builder. - * - * @param typeName the name of the type. - * @param namespace the namespace of the type. - * @return the symbol builder. - */ - public static Symbol.Builder createValueSymbolBuilder(String typeName, GoDependency namespace) { - return setImportedNamespace(createValueSymbolBuilder(typeName), namespace); - } - - private static Symbol.Builder setImportedNamespace(Symbol.Builder builder, GoDependency dependency) { - return builder.namespace(dependency.getImportPath(), ".") - .addDependency(dependency) - .putProperty(NAMESPACE_ALIAS, dependency.getAlias()); - } - - /** - * Go declares several built-in language types in what is known as the Universe block. This function determines - * whether the provided symbol represents a Go universe type. - * - * @param symbol the symbol to check - * @return whether the symbol type is in the Go universe block - * @see The Go Programming Language Specification - */ - public static boolean isUniverseType(Symbol symbol) { - return symbol.getProperty(SymbolUtils.GO_UNIVERSE_TYPE, Boolean.class) - .orElse(false); - } + public static final String POINTABLE = "pointable"; + public static final String NAMESPACE_ALIAS = "namespaceAlias"; + public static final String GO_UNIVERSE_TYPE = "universeType"; + public static final String GO_SLICE = "goSlice"; + public static final String GO_MAP = "goMap"; + public static final String GO_ELEMENT_TYPE = "goElementType"; + + // Used when a given shape must be represented differently on input. + public static final String INPUT_VARIANT = "inputVariant"; + + private SymbolUtils() {} + + /** + * Create a value symbol builder. + * + * @param typeName the name of the type. + * @return the symbol builder type. + */ + public static Symbol.Builder createValueSymbolBuilder(String typeName) { + return Symbol.builder().putProperty(POINTABLE, false).name(typeName); + } + + /** + * Create a pointable symbol builder. + * + * @param typeName the name of the type. + * @return the symbol builder. + */ + public static Symbol.Builder createPointableSymbolBuilder(String typeName) { + return Symbol.builder().putProperty(POINTABLE, true).name(typeName); + } + + /** + * Create a value symbol builder. + * + * @param shape the shape that the type is for. + * @param typeName the name of the type. + * @return the symbol builder. + */ + public static Symbol.Builder createValueSymbolBuilder( + Shape shape, + String typeName + ) { + return createValueSymbolBuilder(typeName).putProperty("shape", shape); + } + + /** + * Create a pointable symbol builder. + * + * @param shape the shape that the type is for. + * @param typeName the name of the type. + * @return the symbol builder. + */ + public static Symbol.Builder createPointableSymbolBuilder( + Shape shape, + String typeName + ) { + return createPointableSymbolBuilder(typeName).putProperty("shape", shape); + } + + /** + * Create a pointable symbol builder. + * + * @param typeName the name of the type. + * @param namespace the namespace of the type. + * @return the symbol builder. + */ + public static Symbol.Builder createPointableSymbolBuilder( + String typeName, + String namespace + ) { + return createPointableSymbolBuilder(typeName).namespace(namespace, "."); + } + + /** + * Create a value symbol builder. + * + * @param typeName the name of the type. + * @param namespace the namespace of the type. + * @return the symbol builder. + */ + public static Symbol.Builder createValueSymbolBuilder( + String typeName, + String namespace + ) { + return createValueSymbolBuilder(typeName).namespace(namespace, "."); + } + + /** + * Create a pointable symbol builder. + * + * @param shape the shape that the type is for. + * @param typeName the name of the type. + * @param namespace the namespace of the type. + * @return the symbol builder. + */ + public static Symbol.Builder createPointableSymbolBuilder( + Shape shape, + String typeName, + String namespace + ) { + return createPointableSymbolBuilder(shape, typeName) + .namespace(namespace, "."); + } + + /** + * Create a value symbol builder. + * + * @param shape the shape that the type is for. + * @param typeName the name of the type. + * @param namespace the namespace of the type. + * @return the symbol builder. + */ + public static Symbol.Builder createValueSymbolBuilder( + Shape shape, + String typeName, + String namespace + ) { + return createValueSymbolBuilder(shape, typeName).namespace(namespace, "."); + } + + /** + * Create a pointable symbol builder. + * + * @param shape the shape that the type is for. + * @param typeName the name of the type. + * @param namespace the namespace of the type. + * @return the symbol builder. + */ + public static Symbol.Builder createPointableSymbolBuilder( + Shape shape, + String typeName, + GoDependency namespace + ) { + return setImportedNamespace( + createPointableSymbolBuilder(shape, typeName), + namespace + ); + } + + /** + * Create a value symbol builder. + * + * @param shape the shape that the type is for. + * @param typeName the name of the type. + * @param namespace the namespace of the type. + * @return the symbol builder. + */ + public static Symbol.Builder createValueSymbolBuilder( + Shape shape, + String typeName, + GoDependency namespace + ) { + return setImportedNamespace( + createValueSymbolBuilder(shape, typeName), + namespace + ); + } + + /** + * Create a pointable symbol builder. + * + * @param typeName the name of the type. + * @param namespace the namespace of the type. + * @return the symbol builder. + */ + public static Symbol.Builder createPointableSymbolBuilder( + String typeName, + GoDependency namespace + ) { + return setImportedNamespace( + createPointableSymbolBuilder(typeName), + namespace + ); + } + + /** + * Create a value symbol builder. + * + * @param typeName the name of the type. + * @param namespace the namespace of the type. + * @return the symbol builder. + */ + public static Symbol.Builder createValueSymbolBuilder( + String typeName, + GoDependency namespace + ) { + return setImportedNamespace(createValueSymbolBuilder(typeName), namespace); + } + + private static Symbol.Builder setImportedNamespace( + Symbol.Builder builder, + GoDependency dependency + ) { + return builder + .namespace(dependency.getImportPath(), ".") + .addDependency(dependency) + .putProperty(NAMESPACE_ALIAS, dependency.getAlias()); + } + + /** + * Go declares several built-in language types in what is known as the Universe block. This function determines + * whether the provided symbol represents a Go universe type. + * + * @param symbol the symbol to check + * @return whether the symbol type is in the Go universe block + * @see The Go Programming Language Specification + */ + public static boolean isUniverseType(Symbol symbol) { + return symbol + .getProperty(SymbolUtils.GO_UNIVERSE_TYPE, Boolean.class) + .orElse(false); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/SymbolVisitor.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/SymbolVisitor.java index 7bd6ee95ff..d23cbda14f 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/SymbolVisitor.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/SymbolVisitor.java @@ -15,6 +15,10 @@ package software.amazon.polymorph.smithygo.codegen; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Logger; import software.amazon.polymorph.smithygo.codegen.knowledge.GoPointableIndex; import software.amazon.polymorph.smithygo.localservice.nameresolver.SmithyNameResolver; import software.amazon.polymorph.traits.ReferenceTrait; @@ -61,11 +65,6 @@ import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.StringUtils; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.logging.Logger; - /** * Responsible for type mapping and file/identifier formatting. * @@ -73,449 +72,593 @@ * suffixed with "_". See "reserved-words.txt" for the list of words. */ public class SymbolVisitor implements SymbolProvider, ShapeVisitor { - private static final Logger LOGGER = Logger.getLogger(SymbolVisitor.class.getName()); - - private final Model model; - private final String rootModuleName; - private final ReservedWordSymbolProvider.Escaper escaper; - private final ReservedWordSymbolProvider.Escaper errorMemberEscaper; - private final Map structureSpecificMemberEscapers = new HashMap<>(); - private final GoPointableIndex pointableIndex; - private final GoSettings settings; - - public SymbolVisitor(Model model, GoSettings settings) { - this.model = model; - this.settings = settings; - this.rootModuleName = SmithyNameResolver.shapeNamespace(settings.getService(model)); - this.pointableIndex = GoPointableIndex.of(model); - - // Reserve the generated names for union members, including the unknown case. - ReservedWordsBuilder reservedNames = new ReservedWordsBuilder(); - reserveUnionMemberNames(model, reservedNames); - - ReservedWords reservedMembers = new ReservedWordsBuilder() - // Since Go only exports names if the first character is upper case and all - // the go reserved words are lower case, it's functionally impossible to conflict, - // so we only need to protect against common names. As of now there's only one. - .put("String", "String_") - .put("GetStream", "GetStream_") - .build(); - - model.shapes(StructureShape.class) - .filter(this::supportsInheritance) - .forEach(this::reserveInterfaceMemberAccessors); - - escaper = ReservedWordSymbolProvider.builder() - .nameReservedWords(reservedNames.build()) - .memberReservedWords(reservedMembers) - // Only escape words when the symbol has a definition file to - // prevent escaping intentional references to built-in types. - .escapePredicate((shape, symbol) -> !StringUtils.isEmpty(symbol.getDefinitionFile())) - .buildEscaper(); - - // Reserved words that only apply to error members. - ReservedWords reservedErrorMembers = new ReservedWordsBuilder() - .put("ErrorCode", "ErrorCode_") - .put("ErrorMessage", "ErrorMessage_") - .put("ErrorFault", "ErrorFault_") - .put("Unwrap", "Unwrap_") - .put("Error", "Error_") - .put("ErrorCodeOverride", "ErrorCodeOverride_") - .build(); - - errorMemberEscaper = ReservedWordSymbolProvider.builder() - .memberReservedWords(ReservedWords.compose(reservedMembers, reservedErrorMembers)) - .escapePredicate((shape, symbol) -> !StringUtils.isEmpty(symbol.getDefinitionFile())) - .buildEscaper(); - } - - /** - * Reserves generated member names for unions. - * - *

These have the format {UnionName}Member{MemberName}. - * - * @param model The model whose unions should be reserved. - * @param builder A reserved words builder to add on to. - */ - private void reserveUnionMemberNames(Model model, ReservedWordsBuilder builder) { - model.shapes(UnionShape.class).forEach(union -> { - for (MemberShape member : union.getAllMembers().values()) { - String memberName = formatUnionMemberName(union, member); - builder.put(memberName, escapeWithTrailingUnderscore(memberName)); - } - }); - } - - private boolean supportsInheritance(Shape shape) { - return shape.isStructureShape() && shape.hasTrait(ErrorTrait.class); - } - - /** - * Reserves Get* and Has* member names for the given structure for use as accessor methods. - * - *

These reservations will only apply to the given structure, not to other structures. - * - * @param shape The structure shape whose members should be reserved. - */ - private void reserveInterfaceMemberAccessors(StructureShape shape) { - ReservedWordsBuilder builder = new ReservedWordsBuilder(); - for (MemberShape member : shape.getAllMembers().values()) { - String name = getDefaultMemberName(member); - String getterName = "Get" + name; - String haserName = "Has" + name; - builder.put(getterName, escapeWithTrailingUnderscore(getterName)); - builder.put(haserName, escapeWithTrailingUnderscore(haserName)); - } - ReservedWordSymbolProvider.Escaper structureSpecificMemberEscaper = ReservedWordSymbolProvider.builder() - .memberReservedWords(builder.build()) - .buildEscaper(); - structureSpecificMemberEscapers.put(shape.getId(), structureSpecificMemberEscaper); - } - - private String escapeWithTrailingUnderscore(String symbolName) { - return symbolName + "_"; - } - - @Override - public Symbol toSymbol(Shape shape) { - Symbol symbol = shape.accept(this); - LOGGER.fine(() -> String.format("Creating symbol from %s: %s", shape, symbol)); - return linkArchetypeShape(shape, escaper.escapeSymbol(shape, symbol)); - } - - /** - * Links the archetype shape id for the symbol. - * - * @param shape the model shape - * @param symbol the symbol to set the archetype property on - * @return the symbol with archetype set if shape is a synthetic clone otherwise the original symbol - */ - private Symbol linkArchetypeShape(Shape shape, Symbol symbol) { - return shape.getTrait(Synthetic.class) - .map(synthetic -> symbol.toBuilder() - .putProperty("archetype", synthetic.getArchetype()) - .build()) - .orElse(symbol); - } - - @Override - public String toMemberName(MemberShape shape) { - Shape container = model.expectShape(shape.getContainer()); - if (container.isUnionShape()) { - // Union member names are not escaped as they are used to build the escape set. - return formatUnionMemberName(container.asUnionShape().get(), shape); - } - - String memberName = getDefaultMemberName(shape); - memberName = escaper.escapeMemberName(memberName); - - // Escape words reserved for the specific container. - if (structureSpecificMemberEscapers.containsKey(shape.getContainer())) { - memberName = structureSpecificMemberEscapers.get(shape.getContainer()).escapeMemberName(memberName); - } - - // Escape words that are only reserved for error members. - if (isErrorMember(shape)) { - memberName = errorMemberEscaper.escapeMemberName(memberName); - } - return memberName; - } - - private String formatUnionMemberName(UnionShape union, MemberShape member) { - return String.format("%sMember%s", getDefaultShapeName(union), getDefaultMemberName(member)); - } - - private String getDefaultShapeName(Shape shape) { - ServiceShape serviceShape = model.expectShape(settings.getService(), ServiceShape.class); - return StringUtils.capitalize(removeLeadingInvalidIdentCharacters(shape.getId().getName(serviceShape))); - } - - private String getDefaultMemberName(MemberShape shape) { - String memberName = StringUtils.capitalize(removeLeadingInvalidIdentCharacters(shape.getMemberName())); - - return memberName; - } - - private String removeLeadingInvalidIdentCharacters(String value) { - if (Character.isAlphabetic(value.charAt(0))) { - return value; - } - - int i; - for (i = 0; i < value.length(); i++) { - if (Character.isAlphabetic(value.charAt(i))) { - break; - } - } - - String remaining = value.substring(i); - if (remaining.length() == 0) { - throw new CodegenException("tried to clean name " + value + ", but resulted in empty string"); - } - - return remaining; - } - - - private boolean isErrorMember(MemberShape shape) { - return model.getShape(shape.getContainer()) - .map(container -> container.hasTrait(ErrorTrait.ID)) - .orElse(false); - } - - @Override - public Symbol blobShape(BlobShape shape) { - if (shape.hasTrait(StreamingTrait.ID)) { - Symbol inputVariant = symbolBuilderFor(shape, "Reader", SmithyGoDependency.IO).build(); - return symbolBuilderFor(shape, "ReadCloser", SmithyGoDependency.IO) - .putProperty(SymbolUtils.INPUT_VARIANT, inputVariant) - .build(); - } - return symbolBuilderFor(shape, "[]byte") - .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) - .build(); - } - - @Override - public Symbol booleanShape(BooleanShape shape) { - return symbolBuilderFor(shape, "bool") - .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) - .build(); - } - - @Override - public Symbol listShape(ListShape shape) { - return createCollectionSymbol(shape); - } - - @Override - public Symbol setShape(SetShape shape) { - // Go doesn't have a set type. Rather than hack together a set using a map, - // we instead just create a list and let the service be responsible for - // asserting that there are no duplicates. - return createCollectionSymbol(shape); - } - - private Symbol createCollectionSymbol(CollectionShape shape) { - Symbol reference = toSymbol(shape.getMember()); - // Shape name will be unused for symbols that represent a slice, but in the event it does we set the collection - // shape's name to make debugging simpler. - return symbolBuilderFor(shape, getDefaultShapeName(shape)) - .putProperty(SymbolUtils.GO_SLICE, true) - .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, - reference.getProperty(SymbolUtils.GO_UNIVERSE_TYPE, Boolean.class).orElse(false)) - .putProperty(SymbolUtils.GO_ELEMENT_TYPE, reference) - .build(); - } - - @Override - public Symbol mapShape(MapShape shape) { - Symbol reference = toSymbol(shape.getValue()); - // Shape name will be unused for symbols that represent a map, but in the event it does we set the map shape's - // name to make debugging simpler. - return symbolBuilderFor(shape, getDefaultShapeName(shape)) - .putProperty(SymbolUtils.GO_MAP, true) - .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, - reference.getProperty(SymbolUtils.GO_UNIVERSE_TYPE, Boolean.class).orElse(false)) - .putProperty(SymbolUtils.GO_ELEMENT_TYPE, reference) - .build(); - } - - private Symbol.Builder symbolBuilderFor(Shape shape, String typeName) { - if (pointableIndex.isPointable(shape)) { - return SymbolUtils.createPointableSymbolBuilder(shape, typeName, SmithyNameResolver.smithyTypesNamespace(shape)); - } - - return SymbolUtils.createValueSymbolBuilder(shape, typeName, SmithyNameResolver.smithyTypesNamespace(shape)); - } - - private Symbol.Builder symbolBuilderFor(Shape shape, String typeName, GoDependency namespace) { - if (pointableIndex.isPointable(shape)) { - return SymbolUtils.createPointableSymbolBuilder(shape, typeName, namespace); - } - - return SymbolUtils.createValueSymbolBuilder(shape, typeName, namespace); - } - private Symbol.Builder symbolBuilderFor(Shape shape, String typeName, String namespace) { - if (pointableIndex.isPointable(shape)) { - return SymbolUtils.createPointableSymbolBuilder(shape, typeName, namespace); + private static final Logger LOGGER = Logger.getLogger( + SymbolVisitor.class.getName() + ); + + private final Model model; + private final String rootModuleName; + private final ReservedWordSymbolProvider.Escaper escaper; + private final ReservedWordSymbolProvider.Escaper errorMemberEscaper; + private final Map< + ShapeId, + ReservedWordSymbolProvider.Escaper + > structureSpecificMemberEscapers = new HashMap<>(); + private final GoPointableIndex pointableIndex; + private final GoSettings settings; + + public SymbolVisitor(Model model, GoSettings settings) { + this.model = model; + this.settings = settings; + this.rootModuleName = + SmithyNameResolver.shapeNamespace(settings.getService(model)); + this.pointableIndex = GoPointableIndex.of(model); + + // Reserve the generated names for union members, including the unknown case. + ReservedWordsBuilder reservedNames = new ReservedWordsBuilder(); + reserveUnionMemberNames(model, reservedNames); + + ReservedWords reservedMembers = new ReservedWordsBuilder() + // Since Go only exports names if the first character is upper case and all + // the go reserved words are lower case, it's functionally impossible to conflict, + // so we only need to protect against common names. As of now there's only one. + .put("String", "String_") + .put("GetStream", "GetStream_") + .build(); + + model + .shapes(StructureShape.class) + .filter(this::supportsInheritance) + .forEach(this::reserveInterfaceMemberAccessors); + + escaper = + ReservedWordSymbolProvider + .builder() + .nameReservedWords(reservedNames.build()) + .memberReservedWords(reservedMembers) + // Only escape words when the symbol has a definition file to + // prevent escaping intentional references to built-in types. + .escapePredicate((shape, symbol) -> + !StringUtils.isEmpty(symbol.getDefinitionFile()) + ) + .buildEscaper(); + + // Reserved words that only apply to error members. + ReservedWords reservedErrorMembers = new ReservedWordsBuilder() + .put("ErrorCode", "ErrorCode_") + .put("ErrorMessage", "ErrorMessage_") + .put("ErrorFault", "ErrorFault_") + .put("Unwrap", "Unwrap_") + .put("Error", "Error_") + .put("ErrorCodeOverride", "ErrorCodeOverride_") + .build(); + + errorMemberEscaper = + ReservedWordSymbolProvider + .builder() + .memberReservedWords( + ReservedWords.compose(reservedMembers, reservedErrorMembers) + ) + .escapePredicate((shape, symbol) -> + !StringUtils.isEmpty(symbol.getDefinitionFile()) + ) + .buildEscaper(); + } + + /** + * Reserves generated member names for unions. + * + *

These have the format {UnionName}Member{MemberName}. + * + * @param model The model whose unions should be reserved. + * @param builder A reserved words builder to add on to. + */ + private void reserveUnionMemberNames( + Model model, + ReservedWordsBuilder builder + ) { + model + .shapes(UnionShape.class) + .forEach(union -> { + for (MemberShape member : union.getAllMembers().values()) { + String memberName = formatUnionMemberName(union, member); + builder.put(memberName, escapeWithTrailingUnderscore(memberName)); } - - return SymbolUtils.createValueSymbolBuilder(shape, typeName, namespace); - } - - @Override - public Symbol byteShape(ByteShape shape) { - return symbolBuilderFor(shape, "int8") - .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) - .build(); - } - - @Override - public Symbol shortShape(ShortShape shape) { - return symbolBuilderFor(shape, "int16") - .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) - .build(); - } - - @Override - public Symbol integerShape(IntegerShape shape) { - return symbolBuilderFor(shape, "int32") - .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) - .build(); - } - - @Override - public Symbol longShape(LongShape shape) { - return symbolBuilderFor(shape, "int64") - .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) - .build(); - } - - @Override - public Symbol floatShape(FloatShape shape) { - return symbolBuilderFor(shape, "float32") - .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) - .build(); - } - - @Override - public Symbol documentShape(DocumentShape shape) { - return null; - } - - @Override - public Symbol doubleShape(DoubleShape shape) { - return symbolBuilderFor(shape, "float64") - .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) - .build(); - } - - @Override - public Symbol bigIntegerShape(BigIntegerShape shape) { - return createBigSymbol(shape, "Int"); - } - - @Override - public Symbol bigDecimalShape(BigDecimalShape shape) { - - return createBigSymbol(shape, "Float"); - } - - private Symbol createBigSymbol(Shape shape, String symbolName) { - return symbolBuilderFor(shape, symbolName, SmithyGoDependency.BIG) - .build(); - } - - @Override - public Symbol operationShape(OperationShape shape) { - String name = getDefaultShapeName(shape); - return SymbolUtils.createPointableSymbolBuilder(shape, name, rootModuleName) - .definitionFile(String.format("./api_op_%s.go", name)) - .addDependency("./types", "./types") - .build(); - } - - @Override - public Symbol resourceShape(ResourceShape shape) { - // TODO: implement resources - return SymbolUtils.createPointableSymbolBuilder(shape, "nil").build(); - } - - @Override - public Symbol serviceShape(ServiceShape shape) { - return symbolBuilderFor(shape, "Client", rootModuleName) - .definitionFile("./%s/api_client.go".formatted(SmithyNameResolver.shapeNamespace(shape))) - .build(); - } - - @Override - public Symbol stringShape(StringShape shape) { - if (shape.hasTrait(EnumTrait.class)) { - String name = getDefaultShapeName(shape); - return symbolBuilderFor(shape, name, SmithyNameResolver.smithyTypesNamespace(settings.getService(model))) - .definitionFile("./%s/enums.go".formatted(SmithyNameResolver.smithyTypesNamespace(shape))) - .build(); - } - - return symbolBuilderFor(shape, "string") - .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) - .build(); - } - - @Override - public Symbol structureShape(StructureShape shape) { - String name = getDefaultShapeName(shape); - if (shape.getId().getNamespace().equals(CodegenUtils.getSyntheticTypeNamespace())) { - Optional boundOperationName = getNameOfBoundOperation(shape); - if (boundOperationName.isPresent()) { - return symbolBuilderFor(shape, name, rootModuleName) - .definitionFile("./api_op_" + boundOperationName.get() + ".go") - .build(); - } - } - Symbol.Builder builder = symbolBuilderFor(shape, name); - if (shape.hasTrait(ErrorTrait.ID)) { - builder.definitionFile("./%s/errors.go".formatted(SmithyNameResolver.smithyTypesNamespace(shape))); - } else { - builder.definitionFile("./%s/types.go".formatted(SmithyNameResolver.smithyTypesNamespace(shape))); - } - - if (shape.hasTrait(ReferenceTrait.class)) { - var referredShape = model.expectShape(shape.expectTrait(ReferenceTrait.class).getReferentId()); - var isService = shape.expectTrait(ReferenceTrait.class).isService(); - if (isService) { - builder.putProperty("Referred", symbolBuilderFor(referredShape, "Client", SmithyNameResolver.shapeNamespace(referredShape)) - .putProperty(SymbolUtils.POINTABLE, true).build()); - } else { - builder.putProperty("Referred", symbolBuilderFor(referredShape, "I".concat(getDefaultShapeName(referredShape))) - .putProperty(SymbolUtils.POINTABLE, false).build()); - } - - } - - return builder.build(); - } - - private Optional getNameOfBoundOperation(StructureShape shape) { - NeighborProvider provider = NeighborProviderIndex.of(model).getReverseProvider(); - for (Relationship relationship : provider.getNeighbors(shape)) { - RelationshipType relationshipType = relationship.getRelationshipType(); - if (relationshipType == RelationshipType.INPUT || relationshipType == RelationshipType.OUTPUT) { - return Optional.of(getDefaultShapeName(relationship.getNeighborShape().get())); - } - } - return Optional.empty(); - } - - @Override - public Symbol unionShape(UnionShape shape) { - String name = getDefaultShapeName(shape); - return symbolBuilderFor(shape, name, SmithyNameResolver.smithyTypesNamespace(settings.getService(model))) - .definitionFile("./types/types.go") - .build(); - } - - @Override - public Symbol memberShape(MemberShape member) { - Shape targetShape = model.expectShape(member.getTarget()); - return toSymbol(targetShape) - .toBuilder() - .putProperty(SymbolUtils.POINTABLE, pointableIndex.isPointable(member)) - .build(); - } - - @Override - public Symbol timestampShape(TimestampShape shape) { - return symbolBuilderFor(shape, "Time", SmithyGoDependency.TIME).build(); - } - - @Override - public Symbol intEnumShape(IntEnumShape shape) { - String name = getDefaultShapeName(shape); - return symbolBuilderFor(shape, name, SmithyNameResolver.smithyTypesNamespace(settings.getService(model))) - .definitionFile("./%s/enums.go".formatted(SmithyNameResolver.smithyTypesNamespace(shape))) - .build(); - } + }); + } + + private boolean supportsInheritance(Shape shape) { + return shape.isStructureShape() && shape.hasTrait(ErrorTrait.class); + } + + /** + * Reserves Get* and Has* member names for the given structure for use as accessor methods. + * + *

These reservations will only apply to the given structure, not to other structures. + * + * @param shape The structure shape whose members should be reserved. + */ + private void reserveInterfaceMemberAccessors(StructureShape shape) { + ReservedWordsBuilder builder = new ReservedWordsBuilder(); + for (MemberShape member : shape.getAllMembers().values()) { + String name = getDefaultMemberName(member); + String getterName = "Get" + name; + String haserName = "Has" + name; + builder.put(getterName, escapeWithTrailingUnderscore(getterName)); + builder.put(haserName, escapeWithTrailingUnderscore(haserName)); + } + ReservedWordSymbolProvider.Escaper structureSpecificMemberEscaper = + ReservedWordSymbolProvider + .builder() + .memberReservedWords(builder.build()) + .buildEscaper(); + structureSpecificMemberEscapers.put( + shape.getId(), + structureSpecificMemberEscaper + ); + } + + private String escapeWithTrailingUnderscore(String symbolName) { + return symbolName + "_"; + } + + @Override + public Symbol toSymbol(Shape shape) { + Symbol symbol = shape.accept(this); + LOGGER.fine(() -> + String.format("Creating symbol from %s: %s", shape, symbol) + ); + return linkArchetypeShape(shape, escaper.escapeSymbol(shape, symbol)); + } + + /** + * Links the archetype shape id for the symbol. + * + * @param shape the model shape + * @param symbol the symbol to set the archetype property on + * @return the symbol with archetype set if shape is a synthetic clone otherwise the original symbol + */ + private Symbol linkArchetypeShape(Shape shape, Symbol symbol) { + return shape + .getTrait(Synthetic.class) + .map(synthetic -> + symbol + .toBuilder() + .putProperty("archetype", synthetic.getArchetype()) + .build() + ) + .orElse(symbol); + } + + @Override + public String toMemberName(MemberShape shape) { + Shape container = model.expectShape(shape.getContainer()); + if (container.isUnionShape()) { + // Union member names are not escaped as they are used to build the escape set. + return formatUnionMemberName(container.asUnionShape().get(), shape); + } + + String memberName = getDefaultMemberName(shape); + memberName = escaper.escapeMemberName(memberName); + + // Escape words reserved for the specific container. + if (structureSpecificMemberEscapers.containsKey(shape.getContainer())) { + memberName = + structureSpecificMemberEscapers + .get(shape.getContainer()) + .escapeMemberName(memberName); + } + + // Escape words that are only reserved for error members. + if (isErrorMember(shape)) { + memberName = errorMemberEscaper.escapeMemberName(memberName); + } + return memberName; + } + + private String formatUnionMemberName(UnionShape union, MemberShape member) { + return String.format( + "%sMember%s", + getDefaultShapeName(union), + getDefaultMemberName(member) + ); + } + + private String getDefaultShapeName(Shape shape) { + ServiceShape serviceShape = model.expectShape( + settings.getService(), + ServiceShape.class + ); + return StringUtils.capitalize( + removeLeadingInvalidIdentCharacters(shape.getId().getName(serviceShape)) + ); + } + + private String getDefaultMemberName(MemberShape shape) { + String memberName = StringUtils.capitalize( + removeLeadingInvalidIdentCharacters(shape.getMemberName()) + ); + + return memberName; + } + + private String removeLeadingInvalidIdentCharacters(String value) { + if (Character.isAlphabetic(value.charAt(0))) { + return value; + } + + int i; + for (i = 0; i < value.length(); i++) { + if (Character.isAlphabetic(value.charAt(i))) { + break; + } + } + + String remaining = value.substring(i); + if (remaining.length() == 0) { + throw new CodegenException( + "tried to clean name " + value + ", but resulted in empty string" + ); + } + + return remaining; + } + + private boolean isErrorMember(MemberShape shape) { + return model + .getShape(shape.getContainer()) + .map(container -> container.hasTrait(ErrorTrait.ID)) + .orElse(false); + } + + @Override + public Symbol blobShape(BlobShape shape) { + if (shape.hasTrait(StreamingTrait.ID)) { + Symbol inputVariant = symbolBuilderFor( + shape, + "Reader", + SmithyGoDependency.IO + ) + .build(); + return symbolBuilderFor(shape, "ReadCloser", SmithyGoDependency.IO) + .putProperty(SymbolUtils.INPUT_VARIANT, inputVariant) + .build(); + } + return symbolBuilderFor(shape, "[]byte") + .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) + .build(); + } + + @Override + public Symbol booleanShape(BooleanShape shape) { + return symbolBuilderFor(shape, "bool") + .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) + .build(); + } + + @Override + public Symbol listShape(ListShape shape) { + return createCollectionSymbol(shape); + } + + @Override + public Symbol setShape(SetShape shape) { + // Go doesn't have a set type. Rather than hack together a set using a map, + // we instead just create a list and let the service be responsible for + // asserting that there are no duplicates. + return createCollectionSymbol(shape); + } + + private Symbol createCollectionSymbol(CollectionShape shape) { + Symbol reference = toSymbol(shape.getMember()); + // Shape name will be unused for symbols that represent a slice, but in the event it does we set the collection + // shape's name to make debugging simpler. + return symbolBuilderFor(shape, getDefaultShapeName(shape)) + .putProperty(SymbolUtils.GO_SLICE, true) + .putProperty( + SymbolUtils.GO_UNIVERSE_TYPE, + reference + .getProperty(SymbolUtils.GO_UNIVERSE_TYPE, Boolean.class) + .orElse(false) + ) + .putProperty(SymbolUtils.GO_ELEMENT_TYPE, reference) + .build(); + } + + @Override + public Symbol mapShape(MapShape shape) { + Symbol reference = toSymbol(shape.getValue()); + // Shape name will be unused for symbols that represent a map, but in the event it does we set the map shape's + // name to make debugging simpler. + return symbolBuilderFor(shape, getDefaultShapeName(shape)) + .putProperty(SymbolUtils.GO_MAP, true) + .putProperty( + SymbolUtils.GO_UNIVERSE_TYPE, + reference + .getProperty(SymbolUtils.GO_UNIVERSE_TYPE, Boolean.class) + .orElse(false) + ) + .putProperty(SymbolUtils.GO_ELEMENT_TYPE, reference) + .build(); + } + + private Symbol.Builder symbolBuilderFor(Shape shape, String typeName) { + if (pointableIndex.isPointable(shape)) { + return SymbolUtils.createPointableSymbolBuilder( + shape, + typeName, + SmithyNameResolver.smithyTypesNamespace(shape) + ); + } + + return SymbolUtils.createValueSymbolBuilder( + shape, + typeName, + SmithyNameResolver.smithyTypesNamespace(shape) + ); + } + + private Symbol.Builder symbolBuilderFor( + Shape shape, + String typeName, + GoDependency namespace + ) { + if (pointableIndex.isPointable(shape)) { + return SymbolUtils.createPointableSymbolBuilder( + shape, + typeName, + namespace + ); + } + + return SymbolUtils.createValueSymbolBuilder(shape, typeName, namespace); + } + + private Symbol.Builder symbolBuilderFor( + Shape shape, + String typeName, + String namespace + ) { + if (pointableIndex.isPointable(shape)) { + return SymbolUtils.createPointableSymbolBuilder( + shape, + typeName, + namespace + ); + } + + return SymbolUtils.createValueSymbolBuilder(shape, typeName, namespace); + } + + @Override + public Symbol byteShape(ByteShape shape) { + return symbolBuilderFor(shape, "int8") + .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) + .build(); + } + + @Override + public Symbol shortShape(ShortShape shape) { + return symbolBuilderFor(shape, "int16") + .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) + .build(); + } + + @Override + public Symbol integerShape(IntegerShape shape) { + return symbolBuilderFor(shape, "int32") + .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) + .build(); + } + + @Override + public Symbol longShape(LongShape shape) { + return symbolBuilderFor(shape, "int64") + .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) + .build(); + } + + @Override + public Symbol floatShape(FloatShape shape) { + return symbolBuilderFor(shape, "float32") + .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) + .build(); + } + + @Override + public Symbol documentShape(DocumentShape shape) { + return null; + } + + @Override + public Symbol doubleShape(DoubleShape shape) { + return symbolBuilderFor(shape, "float64") + .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) + .build(); + } + + @Override + public Symbol bigIntegerShape(BigIntegerShape shape) { + return createBigSymbol(shape, "Int"); + } + + @Override + public Symbol bigDecimalShape(BigDecimalShape shape) { + return createBigSymbol(shape, "Float"); + } + + private Symbol createBigSymbol(Shape shape, String symbolName) { + return symbolBuilderFor(shape, symbolName, SmithyGoDependency.BIG).build(); + } + + @Override + public Symbol operationShape(OperationShape shape) { + String name = getDefaultShapeName(shape); + return SymbolUtils + .createPointableSymbolBuilder(shape, name, rootModuleName) + .definitionFile(String.format("./api_op_%s.go", name)) + .addDependency("./types", "./types") + .build(); + } + + @Override + public Symbol resourceShape(ResourceShape shape) { + // TODO: implement resources + return SymbolUtils.createPointableSymbolBuilder(shape, "nil").build(); + } + + @Override + public Symbol serviceShape(ServiceShape shape) { + return symbolBuilderFor(shape, "Client", rootModuleName) + .definitionFile( + "./%s/api_client.go".formatted(SmithyNameResolver.shapeNamespace(shape)) + ) + .build(); + } + + @Override + public Symbol stringShape(StringShape shape) { + if (shape.hasTrait(EnumTrait.class)) { + String name = getDefaultShapeName(shape); + return symbolBuilderFor( + shape, + name, + SmithyNameResolver.smithyTypesNamespace(settings.getService(model)) + ) + .definitionFile( + "./%s/enums.go".formatted( + SmithyNameResolver.smithyTypesNamespace(shape) + ) + ) + .build(); + } + + return symbolBuilderFor(shape, "string") + .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) + .build(); + } + + @Override + public Symbol structureShape(StructureShape shape) { + String name = getDefaultShapeName(shape); + if ( + shape + .getId() + .getNamespace() + .equals(CodegenUtils.getSyntheticTypeNamespace()) + ) { + Optional boundOperationName = getNameOfBoundOperation(shape); + if (boundOperationName.isPresent()) { + return symbolBuilderFor(shape, name, rootModuleName) + .definitionFile("./api_op_" + boundOperationName.get() + ".go") + .build(); + } + } + Symbol.Builder builder = symbolBuilderFor(shape, name); + if (shape.hasTrait(ErrorTrait.ID)) { + builder.definitionFile( + "./%s/errors.go".formatted( + SmithyNameResolver.smithyTypesNamespace(shape) + ) + ); + } else { + builder.definitionFile( + "./%s/types.go".formatted( + SmithyNameResolver.smithyTypesNamespace(shape) + ) + ); + } + + if (shape.hasTrait(ReferenceTrait.class)) { + var referredShape = model.expectShape( + shape.expectTrait(ReferenceTrait.class).getReferentId() + ); + var isService = shape.expectTrait(ReferenceTrait.class).isService(); + if (isService) { + builder.putProperty( + "Referred", + symbolBuilderFor( + referredShape, + "Client", + SmithyNameResolver.shapeNamespace(referredShape) + ) + .putProperty(SymbolUtils.POINTABLE, true) + .build() + ); + } else { + builder.putProperty( + "Referred", + symbolBuilderFor( + referredShape, + "I".concat(getDefaultShapeName(referredShape)) + ) + .putProperty(SymbolUtils.POINTABLE, false) + .build() + ); + } + } + + return builder.build(); + } + + private Optional getNameOfBoundOperation(StructureShape shape) { + NeighborProvider provider = NeighborProviderIndex + .of(model) + .getReverseProvider(); + for (Relationship relationship : provider.getNeighbors(shape)) { + RelationshipType relationshipType = relationship.getRelationshipType(); + if ( + relationshipType == RelationshipType.INPUT || + relationshipType == RelationshipType.OUTPUT + ) { + return Optional.of( + getDefaultShapeName(relationship.getNeighborShape().get()) + ); + } + } + return Optional.empty(); + } + + @Override + public Symbol unionShape(UnionShape shape) { + String name = getDefaultShapeName(shape); + return symbolBuilderFor( + shape, + name, + SmithyNameResolver.smithyTypesNamespace(settings.getService(model)) + ) + .definitionFile("./types/types.go") + .build(); + } + + @Override + public Symbol memberShape(MemberShape member) { + Shape targetShape = model.expectShape(member.getTarget()); + return toSymbol(targetShape) + .toBuilder() + .putProperty(SymbolUtils.POINTABLE, pointableIndex.isPointable(member)) + .build(); + } + + @Override + public Symbol timestampShape(TimestampShape shape) { + return symbolBuilderFor(shape, "Time", SmithyGoDependency.TIME).build(); + } + + @Override + public Symbol intEnumShape(IntEnumShape shape) { + String name = getDefaultShapeName(shape); + return symbolBuilderFor( + shape, + name, + SmithyNameResolver.smithyTypesNamespace(settings.getService(model)) + ) + .definitionFile( + "./%s/enums.go".formatted( + SmithyNameResolver.smithyTypesNamespace(shape) + ) + ) + .build(); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/Synthetic.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/Synthetic.java index 5a762dd69b..e71f52778b 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/Synthetic.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/Synthetic.java @@ -15,6 +15,7 @@ package software.amazon.polymorph.smithygo.codegen; +import java.util.Optional; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.ShapeId; @@ -23,95 +24,106 @@ import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.ToSmithyBuilder; -import java.util.Optional; - /** * Defines a shape as being a clone of another modeled shape. *

* Must only be used as a runtime trait-only applied to shapes based on model processing */ -public final class Synthetic extends AbstractTrait implements ToSmithyBuilder { - public static final ShapeId ID = ShapeId.from("smithy.go.traits#Synthetic"); - - private static final String ARCHETYPE = "archetype"; - - private final Optional archetype; - - private Synthetic(Builder builder) { - super(ID, builder.getSourceLocation()); - this.archetype = builder.archetype; - } - - /** - * Get the archetype shape that this clone is based on. - * - * @return the original archetype shape - */ - public Optional getArchetype() { - return archetype; - } - - @Override - protected Node createNode() { - throw new CodegenException("attempted to serialize runtime only trait"); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } else if (!(other instanceof Synthetic)) { - return false; - } else { - Synthetic b = (Synthetic) other; - return toShapeId().equals(b.toShapeId()) && archetype.equals(b.getArchetype()); - } +public final class Synthetic + extends AbstractTrait + implements ToSmithyBuilder { + + public static final ShapeId ID = ShapeId.from("smithy.go.traits#Synthetic"); + + private static final String ARCHETYPE = "archetype"; + + private final Optional archetype; + + private Synthetic(Builder builder) { + super(ID, builder.getSourceLocation()); + this.archetype = builder.archetype; + } + + /** + * Get the archetype shape that this clone is based on. + * + * @return the original archetype shape + */ + public Optional getArchetype() { + return archetype; + } + + @Override + protected Node createNode() { + throw new CodegenException("attempted to serialize runtime only trait"); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (!(other instanceof Synthetic)) { + return false; + } else { + Synthetic b = (Synthetic) other; + return ( + toShapeId().equals(b.toShapeId()) && archetype.equals(b.getArchetype()) + ); } + } + + @Override + public int hashCode() { + return ( + toShapeId().hashCode() * 17 + + Node + .objectNode() + .withOptionalMember( + ARCHETYPE, + archetype.map(ShapeId::toString).map(Node::from) + ) + .hashCode() + ); + } + + @Override + public SmithyBuilder toBuilder() { + Builder builder = builder(); + getArchetype().ifPresent(builder::archetype); + + return builder; + } + + /** + * @return Returns a builder used to create {@link Synthetic}. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link Synthetic}. + */ + public static final class Builder + extends AbstractTraitBuilder { + + private Optional archetype = Optional.empty(); + + private Builder() {} @Override - public int hashCode() { - return toShapeId().hashCode() * 17 + Node.objectNode() - .withOptionalMember(ARCHETYPE, archetype.map(ShapeId::toString).map(Node::from)) - .hashCode(); - } - - @Override - public SmithyBuilder toBuilder() { - Builder builder = builder(); - getArchetype().ifPresent(builder::archetype); - - return builder; + public Synthetic build() { + return new Synthetic(this); } - /** - * @return Returns a builder used to create {@link Synthetic}. - */ - public static Builder builder() { - return new Builder(); + public Builder archetype(ShapeId archetype) { + this.archetype = Optional.ofNullable(archetype); + return this; } - /** - * Builder for {@link Synthetic}. - */ - public static final class Builder extends AbstractTraitBuilder { - private Optional archetype = Optional.empty(); - - private Builder() { - } - - @Override - public Synthetic build() { - return new Synthetic(this); - } - - public Builder archetype(ShapeId archetype) { - this.archetype = Optional.ofNullable(archetype); - return this; - } - - public Builder removeArchetype() { - this.archetype = Optional.empty(); - return this; - } + public Builder removeArchetype() { + this.archetype = Optional.empty(); + return this; } + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/UnionGenerator.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/UnionGenerator.java index 68cfbbfb26..7d800dac15 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/UnionGenerator.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/UnionGenerator.java @@ -5,100 +5,137 @@ import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; - import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.MemberShape; +import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.SimpleShape; import software.amazon.smithy.model.shapes.UnionShape; -import software.amazon.smithy.model.traits.StreamingTrait; -import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.traits.ErrorTrait; +import software.amazon.smithy.model.traits.StreamingTrait; public class UnionGenerator { - public static final String UNKNOWN_MEMBER_NAME = "UnknownUnionMember"; - private final Model model; - private final SymbolProvider symbolProvider; - private final UnionShape shape; - private final boolean isEventStream; + public static final String UNKNOWN_MEMBER_NAME = "UnknownUnionMember"; - public UnionGenerator(Model model, SymbolProvider symbolProvider, UnionShape shape) { - this.model = model; - this.symbolProvider = symbolProvider; - this.shape = shape; - this.isEventStream = StreamingTrait.isEventStream(shape); - } + private final Model model; + private final SymbolProvider symbolProvider; + private final UnionShape shape; + private final boolean isEventStream; - /** - * Generates the Go type definitions for the UnionShape. - * - * @param writer the writer - */ - public void generateUnion(GoWriter writer) { - Symbol symbol = symbolProvider.toSymbol(shape); - Collection memberShapes = shape.getAllMembers().values() - .stream() - .filter(memberShape -> !isEventStreamErrorMember(memberShape)) - .collect(Collectors.toCollection(TreeSet::new)); + public UnionGenerator( + Model model, + SymbolProvider symbolProvider, + UnionShape shape + ) { + this.model = model; + this.symbolProvider = symbolProvider; + this.shape = shape; + this.isEventStream = StreamingTrait.isEventStream(shape); + } - memberShapes.stream().map(symbolProvider::toMemberName).forEach(name -> { - writer.write("// " + name); - }); - writer.openBlock("type $L interface {", "}", symbol.getName(), () -> { - writer.write("is$L()", symbol.getName()); - }).write(""); + /** + * Generates the Go type definitions for the UnionShape. + * + * @param writer the writer + */ + public void generateUnion(GoWriter writer) { + Symbol symbol = symbolProvider.toSymbol(shape); + Collection memberShapes = shape + .getAllMembers() + .values() + .stream() + .filter(memberShape -> !isEventStreamErrorMember(memberShape)) + .collect(Collectors.toCollection(TreeSet::new)); - // Create structs for each member that satisfy the interface. - for (MemberShape member : memberShapes) { - Symbol memberSymbol = symbolProvider.toSymbol(member); - String exportedMemberName = symbolProvider.toMemberName(member); - Shape target = model.expectShape(member.getTarget()); + memberShapes + .stream() + .map(symbolProvider::toMemberName) + .forEach(name -> { + writer.write("// " + name); + }); + writer + .openBlock( + "type $L interface {", + "}", + symbol.getName(), + () -> { + writer.write("is$L()", symbol.getName()); + } + ) + .write(""); - writer.openBlock("type $L struct {", "}", exportedMemberName, () -> { - // Union members can't have null values, so for simple shapes we don't - // use pointers. We have to use pointers for complex shapes since, - // for example, we could still have a map that's empty or which has - // null values. - if (target instanceof SimpleShape) { - writer.write("Value $T", memberSymbol); - } else { - writer.write("Value $P", memberSymbol); - } - writer.write(""); - }); + // Create structs for each member that satisfy the interface. + for (MemberShape member : memberShapes) { + Symbol memberSymbol = symbolProvider.toSymbol(member); + String exportedMemberName = symbolProvider.toMemberName(member); + Shape target = model.expectShape(member.getTarget()); - writer.write("func (*$L) is$L() {}", exportedMemberName, symbol.getName()); + writer.openBlock( + "type $L struct {", + "}", + exportedMemberName, + () -> { + // Union members can't have null values, so for simple shapes we don't + // use pointers. We have to use pointers for complex shapes since, + // for example, we could still have a map that's empty or which has + // null values. + if (target instanceof SimpleShape) { + writer.write("Value $T", memberSymbol); + } else { + writer.write("Value $P", memberSymbol); + } + writer.write(""); } - } + ); - private boolean isEventStreamErrorMember(MemberShape memberShape) { - return isEventStream && memberShape.getMemberTrait(model, ErrorTrait.class).isPresent(); + writer.write( + "func (*$L) is$L() {}", + exportedMemberName, + symbol.getName() + ); } + } - /** - * Generates a struct for unknown union values that applies to every union in the given set. - * - * @param writer The writer to write the union to. - * @param unions A set of unions whose interfaces the union should apply to. - * @param symbolProvider A symbol provider used to get the symbols for the unions. - */ - public static void generateUnknownUnion( - final GoWriter writer, - final Collection unions, - final SymbolProvider symbolProvider - ) { - writer.openBlock("type $L struct {", "}", UNKNOWN_MEMBER_NAME, () -> { - // The tag (member) name received over the wire. - writer.write("Tag string"); - // The value received. - writer.write("Value []byte"); - writer.write(""); - }); + private boolean isEventStreamErrorMember(MemberShape memberShape) { + return ( + isEventStream && + memberShape.getMemberTrait(model, ErrorTrait.class).isPresent() + ); + } - for (UnionShape union : unions) { - writer.write("func (*$L) is$L() {}", UNKNOWN_MEMBER_NAME, symbolProvider.toSymbol(union).getName()); - } + /** + * Generates a struct for unknown union values that applies to every union in the given set. + * + * @param writer The writer to write the union to. + * @param unions A set of unions whose interfaces the union should apply to. + * @param symbolProvider A symbol provider used to get the symbols for the unions. + */ + public static void generateUnknownUnion( + final GoWriter writer, + final Collection unions, + final SymbolProvider symbolProvider + ) { + writer.openBlock( + "type $L struct {", + "}", + UNKNOWN_MEMBER_NAME, + () -> { + // The tag (member) name received over the wire. + writer.write("Tag string"); + // The value received. + writer.write("Value []byte"); + writer.write(""); + } + ); + + for (UnionShape union : unions) { + writer.write( + "func (*$L) is$L() {}", + UNKNOWN_MEMBER_NAME, + symbolProvider.toSymbol(union).getName() + ); } + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/ValidationGenerator.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/ValidationGenerator.java index 9d8ddc14a4..13107c50a3 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/ValidationGenerator.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/ValidationGenerator.java @@ -1,306 +1,395 @@ package software.amazon.polymorph.smithygo.codegen; +import static software.amazon.polymorph.smithygo.codegen.SymbolUtils.POINTABLE; + import java.math.BigDecimal; import java.util.Optional; - import software.amazon.polymorph.traits.DafnyUtf8BytesTrait; import software.amazon.polymorph.traits.ReferenceTrait; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.SimpleShape; -import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.traits.LengthTrait; import software.amazon.smithy.model.traits.RangeTrait; import software.amazon.smithy.model.traits.RequiredTrait; import software.amazon.smithy.model.traits.StreamingTrait; -import static software.amazon.polymorph.smithygo.codegen.SymbolUtils.POINTABLE; - // Renders constraint validation public class ValidationGenerator { - private final Model model; - private final SymbolProvider symbolProvider; - private final GoWriter writer; - private final CodegenUtils.SortedMembers sortedMembers; - private static final String LIST_ITEM = "item"; - private static final String MAP_KEY = "key"; - private static final String MAP_VALUE = "value"; - private static final String UNION_DATASOURCE = "unionType.Value"; + private final Model model; + private final SymbolProvider symbolProvider; + private final GoWriter writer; + private final CodegenUtils.SortedMembers sortedMembers; + + private static final String LIST_ITEM = "item"; + private static final String MAP_KEY = "key"; + private static final String MAP_VALUE = "value"; + private static final String UNION_DATASOURCE = "unionType.Value"; + + public ValidationGenerator( + final Model model, + final SymbolProvider symbolProvider, + final GoWriter writer + ) { + this.model = model; + this.symbolProvider = symbolProvider; + this.writer = writer; + this.sortedMembers = new CodegenUtils.SortedMembers(symbolProvider); + } + + public void renderValidator( + final Shape shape, + final boolean isInputStructure + ) { + Symbol symbol = symbolProvider.toSymbol(shape); + writer.openBlock("func (input $L) Validate() (error) {", symbol.getName()); + renderValidatorHelper(shape, isInputStructure, "input"); + writer.write("return nil"); + writer.closeBlock("}").write(""); + } - public ValidationGenerator( - final Model model, - final SymbolProvider symbolProvider, - final GoWriter writer + private void renderValidatorHelper( + final Shape containerShape, + final boolean isInputStructure, + final String dataSource + ) { + containerShape + .getAllMembers() + .values() + .stream() + .filter(memberShape -> !StreamingTrait.isEventStream(model, memberShape)) + .sorted(sortedMembers) + .forEach(member -> { + String memberName; + if ( + containerShape.isListShape() || containerShape.isMapShape() + ) memberName = dataSource; else memberName = + dataSource + "." + symbolProvider.toMemberName(member); + renderValidatorForEachShape( + model.expectShape(member.getTarget()), + member, + isInputStructure, + memberName + ); + }); + } + + private void renderValidatorForEachShape( + final Shape currentShape, + final MemberShape memberShape, + final boolean isInputStructure, + final String dataSource + ) { + Symbol symbol = symbolProvider.toSymbol(currentShape); + if (isInputStructure) { + symbol = + symbol + .getProperty(SymbolUtils.INPUT_VARIANT, Symbol.class) + .orElse(symbol); + } + if (currentShape.hasTrait(ReferenceTrait.class)) { + symbol = symbol.getProperty("Referred", Symbol.class).get(); + } + String pointableString = ""; + if ( + !(dataSource.equals(LIST_ITEM) || + dataSource.equals(MAP_KEY) || + dataSource.equals(MAP_VALUE) || + (dataSource.equals(UNION_DATASOURCE) && + currentShape instanceof SimpleShape)) ) { - this.model = model; - this.symbolProvider = symbolProvider; - this.writer = writer; - this.sortedMembers = new CodegenUtils.SortedMembers(symbolProvider); + if ( + (boolean) symbol.getProperty(POINTABLE, Boolean.class).orElse(false) && + memberShape.isOptional() + ) { + pointableString = "*"; + } } - - public void renderValidator (final Shape shape, final boolean isInputStructure) { - Symbol symbol = symbolProvider.toSymbol(shape); - writer.openBlock("func (input $L) Validate() (error) {", symbol.getName()); - renderValidatorHelper( shape, isInputStructure, "input"); - writer.write("return nil"); - writer.closeBlock("}").write(""); + if (currentShape.hasTrait(RangeTrait.class)) { + addRangeCheck(currentShape, dataSource, pointableString); } - - private void renderValidatorHelper (final Shape containerShape, final boolean isInputStructure, final String dataSource) { - containerShape.getAllMembers().values().stream() - .filter(memberShape -> !StreamingTrait.isEventStream(model, memberShape)) - .sorted(sortedMembers) - .forEach((member) -> { - String memberName; - if (containerShape.isListShape() || containerShape.isMapShape()) - memberName = dataSource; - else - memberName = dataSource + "." + symbolProvider.toMemberName(member); - renderValidatorForEachShape(model.expectShape(member.getTarget()), member, isInputStructure, memberName); - }); + if (currentShape.hasTrait(LengthTrait.class)) { + addLengthCheck(currentShape, dataSource, pointableString); } - - private void renderValidatorForEachShape (final Shape currentShape, final MemberShape memberShape, final boolean isInputStructure, final String dataSource) { - Symbol symbol = symbolProvider.toSymbol(currentShape); - if (isInputStructure) { - symbol = symbol.getProperty(SymbolUtils.INPUT_VARIANT, Symbol.class) - .orElse(symbol); - } - if (currentShape.hasTrait(ReferenceTrait.class)) { - symbol = symbol.getProperty("Referred", Symbol.class).get(); - } - String pointableString = ""; - if (!(dataSource.equals(LIST_ITEM) || dataSource.equals(MAP_KEY) || dataSource.equals(MAP_VALUE) || dataSource.equals(UNION_DATASOURCE) && currentShape instanceof SimpleShape)) { - if ((boolean) symbol.getProperty(POINTABLE, Boolean.class).orElse(false) && memberShape.isOptional()){ - pointableString = "*"; - } - } - if (currentShape.hasTrait(RangeTrait.class)) { - addRangeCheck(currentShape, dataSource, pointableString); - } - if (currentShape.hasTrait(LengthTrait.class)) { - addLengthCheck(currentShape, dataSource, pointableString); - } - if (currentShape.hasTrait(RequiredTrait.class)) { - addRequiredCheck(symbol, currentShape, dataSource); - } - if (currentShape.hasTrait(DafnyUtf8BytesTrait.class)) { - addUTFCheck(currentShape, dataSource, pointableString); - } - // Broke list and map into two different if else because for _, item := range %s looked good for list - // And for key, value := range %s looked good for map - if (currentShape.isListShape()) { - writer.write(""" - for _, %s := range %s { - // To avoid declared and not used error for shapes which does not need validation check - _ = item - """.formatted(LIST_ITEM, dataSource)); - renderValidatorHelper(currentShape, false, LIST_ITEM); - writer.write(""" - } - """); - } - else if (currentShape.isMapShape()) { - writer.write(""" - for %s, %s := range %s { - // To avoid declared and not used error for shapes which does not need validation check - _ = key - _ = value - """.formatted(MAP_KEY, MAP_VALUE, dataSource)); - renderValidatorHelper(currentShape, false, MAP_KEY); - renderValidatorHelper(currentShape, false, MAP_VALUE); - writer.write(""" - } - """); - } - else if (currentShape.isUnionShape()) { - writer.write(""" - switch unionType := %s.(type) { - """.formatted(dataSource)); - for (var memberInUnion : currentShape.getAllMembers().values()) { - writer.write(""" - case *%s: - """.formatted( - symbolProvider.toMemberName(memberInUnion) - )); - - renderValidatorForEachShape(model.expectShape(memberInUnion.getTarget()), memberInUnion, false, "unionType.Value"); - } - writer.write(""" - // Default case should not be reached. - default: - // To avoid used and not used error when nothing to validate - _ = unionType - panic("Unhandled union type") - } - """); - } - else { - renderValidatorHelper(currentShape, isInputStructure, dataSource); - } + if (currentShape.hasTrait(RequiredTrait.class)) { + addRequiredCheck(symbol, currentShape, dataSource); } - - private void addRangeCheck(final Shape currentShape, final String dataSource, final String pointableString) { - StringBuilder rangeCheck = new StringBuilder(); - RangeTrait rangeTraitShape = currentShape.expectTrait(RangeTrait.class); - Optional min = rangeTraitShape.getMin(); - Optional max = rangeTraitShape.getMax(); - if (pointableString.equals("*")){ - rangeCheck.append(""" - if (%s != nil) { - """.formatted(dataSource)); - } - if (min.isPresent()) { - rangeCheck.append(""" - if (%s%s < %s) { - return fmt.Errorf(\"%s has a minimum of %s but has the value of %%d.\", %s%s) - } - """.formatted( - pointableString, - dataSource, - min.get().toString(), - currentShape.getId().getName(), - min.get().toString(), - pointableString, - dataSource)); - } - if (max.isPresent()) { - rangeCheck.append(""" - if (%s%s > %s) { - return fmt.Errorf(\"%s has a maximum of %s but has the value of %%d.\", %s%s) - } - """.formatted( - pointableString, - dataSource, - max.get().toString(), - currentShape.getId().getName(), - max.get().toString(), - pointableString, - dataSource)); + if (currentShape.hasTrait(DafnyUtf8BytesTrait.class)) { + addUTFCheck(currentShape, dataSource, pointableString); + } + // Broke list and map into two different if else because for _, item := range %s looked good for list + // And for key, value := range %s looked good for map + if (currentShape.isListShape()) { + writer.write( + """ + for _, %s := range %s { + // To avoid declared and not used error for shapes which does not need validation check + _ = item + """.formatted(LIST_ITEM, dataSource) + ); + renderValidatorHelper(currentShape, false, LIST_ITEM); + writer.write( + """ } - if (pointableString.equals("*")){ - rangeCheck.append(""" + """ + ); + } else if (currentShape.isMapShape()) { + writer.write( + """ + for %s, %s := range %s { + // To avoid declared and not used error for shapes which does not need validation check + _ = key + _ = value + """.formatted(MAP_KEY, MAP_VALUE, dataSource) + ); + renderValidatorHelper(currentShape, false, MAP_KEY); + renderValidatorHelper(currentShape, false, MAP_VALUE); + writer.write( + """ + } + """ + ); + } else if (currentShape.isUnionShape()) { + writer.write( + """ + switch unionType := %s.(type) { + """.formatted(dataSource) + ); + for (var memberInUnion : currentShape.getAllMembers().values()) { + writer.write( + """ + case *%s: + """.formatted(symbolProvider.toMemberName(memberInUnion)) + ); + + renderValidatorForEachShape( + model.expectShape(memberInUnion.getTarget()), + memberInUnion, + false, + "unionType.Value" + ); + } + writer.write( + """ + // Default case should not be reached. + default: + // To avoid used and not used error when nothing to validate + _ = unionType + panic("Unhandled union type") } - """); - } - writer.write(rangeCheck); + """ + ); + } else { + renderValidatorHelper(currentShape, isInputStructure, dataSource); } + } - private void addLengthCheck(final Shape currentShape, final String dataSource, final String pointableString) { - StringBuilder lengthCheck = new StringBuilder(); - LengthTrait lengthTraitShape = currentShape.expectTrait(LengthTrait.class); - Optional min = lengthTraitShape.getMin(); - Optional max = lengthTraitShape.getMax(); - if (pointableString.equals("*")){ - lengthCheck.append(""" - if (%s != nil) { - """.formatted(dataSource)); - } - if (min.isPresent()) { - if (currentShape.hasTrait(DafnyUtf8BytesTrait.class)) { - lengthCheck.append(""" - if (utf8.RuneCountInString(%s%s) < %s) { - return fmt.Errorf(\"%s has a minimum length of %s but has the length of %%d.\", utf8.RuneCountInString(%s%s)) - } - """.formatted( - pointableString, - dataSource, - min.get().toString(), - currentShape.getId().getName(), - min.get().toString(), - pointableString, - dataSource)); - } - else { - lengthCheck.append(""" - if (len(%s%s) < %s) { - return fmt.Errorf(\"%s has a minimum length of %s but has the length of %%d.\", len(%s%s)) - } - """.formatted( - pointableString, - dataSource, - min.get().toString(), - currentShape.getId().getName(), - min.get().toString(), - pointableString, - dataSource)); - } + private void addRangeCheck( + final Shape currentShape, + final String dataSource, + final String pointableString + ) { + StringBuilder rangeCheck = new StringBuilder(); + RangeTrait rangeTraitShape = currentShape.expectTrait(RangeTrait.class); + Optional min = rangeTraitShape.getMin(); + Optional max = rangeTraitShape.getMax(); + if (pointableString.equals("*")) { + rangeCheck.append( + """ + if (%s != nil) { + """.formatted(dataSource) + ); + } + if (min.isPresent()) { + rangeCheck.append( + """ + if (%s%s < %s) { + return fmt.Errorf(\"%s has a minimum of %s but has the value of %%d.\", %s%s) } - if (max.isPresent()) { - if (currentShape.hasTrait(DafnyUtf8BytesTrait.class)) { - lengthCheck.append(""" - if (utf8.RuneCountInString(%s%s) > %s) { - return fmt.Errorf(\"%s has a maximum length of %s but has the length of %%d.\", utf8.RuneCountInString(%s%s)) - } - """.formatted( - pointableString, - dataSource, - max.get().toString(), - currentShape.getId().getName(), - max.get().toString(), - pointableString, - dataSource)); - } - else { - lengthCheck.append(""" - if (len(%s%s) > %s) { - return fmt.Errorf(\"%s has a maximum length of %s but has the length of %%d.\", len(%s%s)) - } - """.formatted( - pointableString, - dataSource, - max.get().toString(), - currentShape.getId().getName(), - max.get().toString(), - pointableString, - dataSource)); - } + """.formatted( + pointableString, + dataSource, + min.get().toString(), + currentShape.getId().getName(), + min.get().toString(), + pointableString, + dataSource + ) + ); + } + if (max.isPresent()) { + rangeCheck.append( + """ + if (%s%s > %s) { + return fmt.Errorf(\"%s has a maximum of %s but has the value of %%d.\", %s%s) } - if (pointableString.equals("*")){ - lengthCheck.append(""" - } - """); + """.formatted( + pointableString, + dataSource, + max.get().toString(), + currentShape.getId().getName(), + max.get().toString(), + pointableString, + dataSource + ) + ); + } + if (pointableString.equals("*")) { + rangeCheck.append( + """ } - writer.write(lengthCheck); + """ + ); } + writer.write(rangeCheck); + } - private void addRequiredCheck(final Symbol memberSymbol, final Shape currentShape, final String dataSource) { - StringBuilder RequiredCheck = new StringBuilder(); - if( memberSymbol.getProperty(POINTABLE).isPresent() && (boolean) memberSymbol.getProperty(POINTABLE).get()) - RequiredCheck.append(""" - if ( %s == nil ) { - return fmt.Errorf(\"%s is required but has a nil value.\") - } - """.formatted( - dataSource, - dataSource)); - writer.write(RequiredCheck); + private void addLengthCheck( + final Shape currentShape, + final String dataSource, + final String pointableString + ) { + StringBuilder lengthCheck = new StringBuilder(); + LengthTrait lengthTraitShape = currentShape.expectTrait(LengthTrait.class); + Optional min = lengthTraitShape.getMin(); + Optional max = lengthTraitShape.getMax(); + if (pointableString.equals("*")) { + lengthCheck.append( + """ + if (%s != nil) { + """.formatted(dataSource) + ); } - - private void addUTFCheck(final Shape currentShape, final String dataSource, final String pointableString) { - StringBuilder UTFCheck = new StringBuilder(); - if (pointableString.equals("*")){ - UTFCheck.append(""" - if ( %s != nil ) { - """.formatted(dataSource)); + if (min.isPresent()) { + if (currentShape.hasTrait(DafnyUtf8BytesTrait.class)) { + lengthCheck.append( + """ + if (utf8.RuneCountInString(%s%s) < %s) { + return fmt.Errorf(\"%s has a minimum length of %s but has the length of %%d.\", utf8.RuneCountInString(%s%s)) + } + """.formatted( + pointableString, + dataSource, + min.get().toString(), + currentShape.getId().getName(), + min.get().toString(), + pointableString, + dataSource + ) + ); + } else { + lengthCheck.append( + """ + if (len(%s%s) < %s) { + return fmt.Errorf(\"%s has a minimum length of %s but has the length of %%d.\", len(%s%s)) + } + """.formatted( + pointableString, + dataSource, + min.get().toString(), + currentShape.getId().getName(), + min.get().toString(), + pointableString, + dataSource + ) + ); + } + } + if (max.isPresent()) { + if (currentShape.hasTrait(DafnyUtf8BytesTrait.class)) { + lengthCheck.append( + """ + if (utf8.RuneCountInString(%s%s) > %s) { + return fmt.Errorf(\"%s has a maximum length of %s but has the length of %%d.\", utf8.RuneCountInString(%s%s)) + } + """.formatted( + pointableString, + dataSource, + max.get().toString(), + currentShape.getId().getName(), + max.get().toString(), + pointableString, + dataSource + ) + ); + } else { + lengthCheck.append( + """ + if (len(%s%s) > %s) { + return fmt.Errorf(\"%s has a maximum length of %s but has the length of %%d.\", len(%s%s)) + } + """.formatted( + pointableString, + dataSource, + max.get().toString(), + currentShape.getId().getName(), + max.get().toString(), + pointableString, + dataSource + ) + ); + } + } + if (pointableString.equals("*")) { + lengthCheck.append( + """ } - UTFCheck.append(""" - if (!utf8.ValidString(%s%s)) { - return fmt.Errorf(\"Invalid UTF bytes %%s \", %s%s) - } - """.formatted( - pointableString, - dataSource, - pointableString, - dataSource)); - if (pointableString.equals("*")){ - UTFCheck.append(""" - } - """); + """ + ); + } + writer.write(lengthCheck); + } + + private void addRequiredCheck( + final Symbol memberSymbol, + final Shape currentShape, + final String dataSource + ) { + StringBuilder RequiredCheck = new StringBuilder(); + if ( + memberSymbol.getProperty(POINTABLE).isPresent() && + (boolean) memberSymbol.getProperty(POINTABLE).get() + ) RequiredCheck.append( + """ + if ( %s == nil ) { + return fmt.Errorf(\"%s is required but has a nil value.\") + } + """.formatted(dataSource, dataSource) + ); + writer.write(RequiredCheck); + } + + private void addUTFCheck( + final Shape currentShape, + final String dataSource, + final String pointableString + ) { + StringBuilder UTFCheck = new StringBuilder(); + if (pointableString.equals("*")) { + UTFCheck.append( + """ + if ( %s != nil ) { + """.formatted(dataSource) + ); + } + UTFCheck.append( + """ + if (!utf8.ValidString(%s%s)) { + return fmt.Errorf(\"Invalid UTF bytes %%s \", %s%s) + } + """.formatted(pointableString, dataSource, pointableString, dataSource) + ); + if (pointableString.equals("*")) { + UTFCheck.append( + """ } - writer.write(UTFCheck); + """ + ); } + writer.write(UTFCheck); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/integration/GoIntegration.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/integration/GoIntegration.java index acf1f770c4..5eb8b38504 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/integration/GoIntegration.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/integration/GoIntegration.java @@ -8,5 +8,5 @@ import software.amazon.polymorph.smithygo.codegen.GoWriter; import software.amazon.smithy.codegen.core.SmithyIntegration; -public interface GoIntegration extends SmithyIntegration { -} +public interface GoIntegration + extends SmithyIntegration {} diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/integration/ProtocolGenerator.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/integration/ProtocolGenerator.java index abda4b6764..c63f0803a0 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/integration/ProtocolGenerator.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/integration/ProtocolGenerator.java @@ -14,57 +14,57 @@ */ package software.amazon.polymorph.smithygo.codegen.integration; +import static java.lang.String.format; + import software.amazon.polymorph.smithygo.codegen.ApplicationProtocol; import software.amazon.polymorph.smithygo.codegen.GenerationContext; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.utils.SmithyUnstableApi; -import static java.lang.String.format; - /** * Generates code to implement a protocol for both servers and clients. */ @SmithyUnstableApi public interface ProtocolGenerator { - /** - * Gets the supported protocol {@link ShapeId}. - * - * @return Returns the protocol supported - */ - ShapeId getProtocol(); + /** + * Gets the supported protocol {@link ShapeId}. + * + * @return Returns the protocol supported + */ + ShapeId getProtocol(); - /** - * Gets the name of the protocol. - * - *

The default implementation is the ShapeId name of the protocol trait in - * Smithy models (e.g., "aws.protocols#restJson1" would return "restJson1"). - * - * @return Returns the protocol name. - */ - default String getName() { - return getProtocol().getName(); - } + /** + * Gets the name of the protocol. + * + *

The default implementation is the ShapeId name of the protocol trait in + * Smithy models (e.g., "aws.protocols#restJson1" would return "restJson1"). + * + * @return Returns the protocol name. + */ + default String getName() { + return getProtocol().getName(); + } - /** - * Creates an application protocol for the generator. - * - * @return Returns the created application protocol. - */ - ApplicationProtocol getApplicationProtocol(); + /** + * Creates an application protocol for the generator. + * + * @return Returns the created application protocol. + */ + ApplicationProtocol getApplicationProtocol(); - /** - * Generates the code used to serialize the shapes of a service - * for requests. - * - * @param context Serialization context. - */ - void generateSerializers(GenerationContext context); + /** + * Generates the code used to serialize the shapes of a service + * for requests. + * + * @param context Serialization context. + */ + void generateSerializers(GenerationContext context); - /** - * Generates the code used to deserialize the shapes of a service - * for responses. - * - * @param context Deserialization context. - */ - void generateDeserializers(GenerationContext context); + /** + * Generates the code used to deserialize the shapes of a service + * for responses. + * + * @param context Deserialization context. + */ + void generateDeserializers(GenerationContext context); } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/knowledge/GoPointableIndex.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/knowledge/GoPointableIndex.java index 0a7e5834f4..9f490144f0 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/knowledge/GoPointableIndex.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/knowledge/GoPointableIndex.java @@ -17,6 +17,9 @@ package software.amazon.polymorph.smithygo.codegen.knowledge; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Logger; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.KnowledgeIndex; import software.amazon.smithy.model.knowledge.NeighborProviderIndex; @@ -34,253 +37,276 @@ import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.SetUtils; -import java.util.HashSet; -import java.util.Set; -import java.util.logging.Logger; - /** * An index that checks if a member or shape type should be a pointer type in Go. *

* Extends the rules of smithy's NullableIndex for Go's translation of the smithy shapes to Go types. */ public class GoPointableIndex implements KnowledgeIndex { - private static final Logger LOGGER = Logger.getLogger(GoPointableIndex.class.getName()); - - // All types that are Go value types - private static final Set INHERENTLY_VALUE = SetUtils.of( - ShapeType.BLOB, - ShapeType.LIST, - ShapeType.SET, - ShapeType.MAP, - ShapeType.UNION, - ShapeType.DOCUMENT - ); - - // All types that are Go pointer types - private static final Set INHERENTLY_POINTABLE = SetUtils.of( - ShapeType.BIG_DECIMAL, - ShapeType.BIG_INTEGER - ); - // All types that cannot be dereferenced - private static final Set INHERENTLY_NONDEREFERENCABLE = SetUtils.of( - // built in slice/map - ShapeType.BLOB, - ShapeType.LIST, - ShapeType.SET, - ShapeType.MAP, - - // Interfaces - ShapeType.UNION, - ShapeType.DOCUMENT, - - // known pointer types. - ShapeType.BIG_DECIMAL, - ShapeType.BIG_INTEGER + private static final Logger LOGGER = Logger.getLogger( + GoPointableIndex.class.getName() + ); + + // All types that are Go value types + private static final Set INHERENTLY_VALUE = SetUtils.of( + ShapeType.BLOB, + ShapeType.LIST, + ShapeType.SET, + ShapeType.MAP, + ShapeType.UNION, + ShapeType.DOCUMENT + ); + + // All types that are Go pointer types + private static final Set INHERENTLY_POINTABLE = SetUtils.of( + ShapeType.BIG_DECIMAL, + ShapeType.BIG_INTEGER + ); + + // All types that cannot be dereferenced + private static final Set INHERENTLY_NONDEREFERENCABLE = + SetUtils.of( + // built in slice/map + ShapeType.BLOB, + ShapeType.LIST, + ShapeType.SET, + ShapeType.MAP, + // Interfaces + ShapeType.UNION, + ShapeType.DOCUMENT, + // known pointer types. + ShapeType.BIG_DECIMAL, + ShapeType.BIG_INTEGER ); - // All types types that are comparable to nil - private static final Set INHERENTLY_NILLABLE = SetUtils.of( - // built in slice/map - ShapeType.BLOB, - ShapeType.LIST, - ShapeType.SET, - ShapeType.MAP, - - // Interfaces - ShapeType.UNION, - ShapeType.DOCUMENT, - - // known pointer types. - ShapeType.BIG_DECIMAL, - ShapeType.BIG_INTEGER + // All types types that are comparable to nil + private static final Set INHERENTLY_NILLABLE = SetUtils.of( + // built in slice/map + ShapeType.BLOB, + ShapeType.LIST, + ShapeType.SET, + ShapeType.MAP, + // Interfaces + ShapeType.UNION, + ShapeType.DOCUMENT, + // known pointer types. + ShapeType.BIG_DECIMAL, + ShapeType.BIG_INTEGER + ); + + // All the known pointer type if not required + private static final Set KNOWN_POINTER_TYPE = SetUtils.of( + ShapeType.BYTE, + ShapeType.SHORT, + ShapeType.INTEGER, + ShapeType.LONG, + ShapeType.FLOAT, + ShapeType.DOUBLE, + ShapeType.BIG_DECIMAL, + ShapeType.BIG_INTEGER + ); + + private final Model model; + private final NullableIndex nullableIndex; + private final Set pointableShapes = new HashSet<>(); + private final Set nillableShapes = new HashSet<>(); + private final Set dereferencableShapes = new HashSet<>(); + + public GoPointableIndex(Model model) { + this.model = model; + this.nullableIndex = NullableIndex.of(model); + + for (Shape shape : model.toSet()) { + if (shape.asMemberShape().isPresent()) { + MemberShape member = shape.asMemberShape().get(); + Shape targetShape = model.expectShape(member.getTarget()); + + if (isMemberPointable(member, targetShape)) { + pointableShapes.add(shape.getId()); + } + if (isMemberNillable(member, targetShape)) { + nillableShapes.add(shape.getId()); + } + if (isMemberDereferencable(member, targetShape)) { + dereferencableShapes.add(shape.getId()); + } + } else { + if (isShapePointable(shape)) { + pointableShapes.add(shape.getId()); + nillableShapes.add(shape.getId()); + } + if (isShapeNillable(shape)) { + nillableShapes.add(shape.getId()); + } + if (isShapeDereferencable(shape)) { + dereferencableShapes.add(shape.getId()); + } + } + } + } + + public static GoPointableIndex of(Model model) { + return model.getKnowledge(GoPointableIndex.class, GoPointableIndex::new); + } + + private boolean isMemberDereferencable( + MemberShape member, + Shape targetShape + ) { + return ( + !INHERENTLY_NONDEREFERENCABLE.contains(targetShape.getType()) && + isMemberPointable(member, targetShape) ); + } - // All the known pointer type if not required - private static final Set KNOWN_POINTER_TYPE = SetUtils.of( - ShapeType.BYTE, - ShapeType.SHORT, - ShapeType.INTEGER, - ShapeType.LONG, - ShapeType.FLOAT, - ShapeType.DOUBLE, - ShapeType.BIG_DECIMAL, - ShapeType.BIG_INTEGER + private boolean isMemberNillable(MemberShape member, Shape targetShape) { + return ( + INHERENTLY_NILLABLE.contains(targetShape.getType()) || + isMemberPointable(member, targetShape) ); + } - private final Model model; - private final NullableIndex nullableIndex; - private final Set pointableShapes = new HashSet<>(); - private final Set nillableShapes = new HashSet<>(); - private final Set dereferencableShapes = new HashSet<>(); - - public GoPointableIndex(Model model) { - this.model = model; - this.nullableIndex = NullableIndex.of(model); - - for (Shape shape : model.toSet()) { - if (shape.asMemberShape().isPresent()) { - MemberShape member = shape.asMemberShape().get(); - Shape targetShape = model.expectShape(member.getTarget()); - - if (isMemberPointable(member, targetShape)) { - pointableShapes.add(shape.getId()); - } - if (isMemberNillable(member, targetShape)) { - nillableShapes.add(shape.getId()); - } - if (isMemberDereferencable(member, targetShape)) { - dereferencableShapes.add(shape.getId()); - } - } else { - if (isShapePointable(shape)) { - pointableShapes.add(shape.getId()); - nillableShapes.add(shape.getId()); - } - if (isShapeNillable(shape)) { - nillableShapes.add(shape.getId()); - } - if (isShapeDereferencable(shape)) { - dereferencableShapes.add(shape.getId()); - } - } - } + private boolean isMemberPointable(MemberShape member, Shape targetShape) { + // Streamed blob shapes are never pointers because they are interfaces + if (isBlobStream(targetShape)) { + return false; } - public static GoPointableIndex of(Model model) { - return model.getKnowledge(GoPointableIndex.class, GoPointableIndex::new); + if (INHERENTLY_VALUE.contains(targetShape.getType())) { + return false; } - private boolean isMemberDereferencable(MemberShape member, Shape targetShape) { - return !INHERENTLY_NONDEREFERENCABLE.contains(targetShape.getType()) && isMemberPointable(member, targetShape); + //if membershape is required return it is not pointable + if (member.hasTrait(RequiredTrait.class)) { + return false; } - private boolean isMemberNillable(MemberShape member, Shape targetShape) { - return INHERENTLY_NILLABLE.contains(targetShape.getType()) || isMemberPointable(member, targetShape); + if (model.expectShape(member.getContainer()).isMapShape()) { + return false; } - private boolean isMemberPointable(MemberShape member, Shape targetShape) { - // Streamed blob shapes are never pointers because they are interfaces - if (isBlobStream(targetShape)) { - return false; - } - - if (INHERENTLY_VALUE.contains(targetShape.getType())) { - return false; - } + if (KNOWN_POINTER_TYPE.contains(targetShape.getType())) { + return true; + } - //if membershape is required return it is not pointable - if (member.hasTrait(RequiredTrait.class)) { - return false; - } + return nullableIndex.isMemberNullable( + member, + NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1_NO_INPUT + ); + } - if (model.expectShape(member.getContainer()).isMapShape()) { - return false; - } + private boolean isShapeDereferencable(Shape shape) { + return ( + !INHERENTLY_NONDEREFERENCABLE.contains(shape.getType()) && + isShapePointable(shape) + ); + } - if (KNOWN_POINTER_TYPE.contains(targetShape.getType())) { - return true; - } + private boolean isShapeNillable(Shape shape) { + return ( + INHERENTLY_NILLABLE.contains(shape.getType()) || isShapePointable(shape) + ); + } - return nullableIndex.isMemberNullable(member, NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1_NO_INPUT); + private boolean isShapePointable(Shape shape) { + // All operation input and output shapes are pointable. + if (isOperationStruct(shape)) { + return true; } - private boolean isShapeDereferencable(Shape shape) { - return !INHERENTLY_NONDEREFERENCABLE.contains(shape.getType()) && isShapePointable(shape); + // Streamed blob shapes are never pointers because they are interfaces + if (isBlobStream(shape)) { + return false; } - private boolean isShapeNillable(Shape shape) { - return INHERENTLY_NILLABLE.contains(shape.getType()) || isShapePointable(shape); + if (shape.isServiceShape()) { + return true; } - private boolean isShapePointable(Shape shape) { - // All operation input and output shapes are pointable. - if (isOperationStruct(shape)) { - return true; - } - - // Streamed blob shapes are never pointers because they are interfaces - if (isBlobStream(shape)) { - return false; - } - - if (shape.isServiceShape()) { - return true; - } - - // This is odd because its not a go type but a function with receiver - if (shape.isOperationShape()) { - return false; - } - - if (INHERENTLY_POINTABLE.contains(shape.getType())) { - return true; - } - - if (INHERENTLY_VALUE.contains(shape.getType())) { - return false; - } - - if (shape.hasTrait(RequiredTrait.class)) { - return false; - } - - if (KNOWN_POINTER_TYPE.contains(shape.getType())) { - return true; - } - - return nullableIndex.isNullable(shape); + // This is odd because its not a go type but a function with receiver + if (shape.isOperationShape()) { + return false; } - private boolean isShapeEnum(Shape shape) { - return shape.getType() == ShapeType.STRING && shape.hasTrait(EnumTrait.class) - || shape.getType() == ShapeType.ENUM - || shape.getType() == ShapeType.INT_ENUM; + if (INHERENTLY_POINTABLE.contains(shape.getType())) { + return true; } - private boolean isBlobStream(Shape shape) { - return shape.getType() == ShapeType.BLOB && shape.hasTrait(StreamingTrait.ID); + if (INHERENTLY_VALUE.contains(shape.getType())) { + return false; } - private boolean isOperationStruct(Shape shape) { - NeighborProvider provider = NeighborProviderIndex.of(model).getReverseProvider(); - for (Relationship relationship : provider.getNeighbors(shape)) { - RelationshipType relationshipType = relationship.getRelationshipType(); - if (relationshipType == RelationshipType.INPUT || relationshipType == RelationshipType.OUTPUT) { - return true; - } - } - - return false; + if (shape.hasTrait(RequiredTrait.class)) { + return false; } - /** - * Returns if the shape should be generated as a Go pointer type or not. - * - * @param shape the shape to check if should be pointable type. - * @return if the shape is should be a Go pointer type. - */ - public final boolean isPointable(ToShapeId shape) { - return pointableShapes.contains(shape.toShapeId()); + if (KNOWN_POINTER_TYPE.contains(shape.getType())) { + return true; } - /** - * Returns if the Go type generated for the shape is comparable to nil. - * - * @param shape the shape to check - * @return if the shape's go type is comparable to nil - */ - public final boolean isNillable(ToShapeId shape) { - return nillableShapes.contains(shape.toShapeId()); - } + return nullableIndex.isNullable(shape); + } + + private boolean isShapeEnum(Shape shape) { + return ( + (shape.getType() == ShapeType.STRING && + shape.hasTrait(EnumTrait.class)) || + shape.getType() == ShapeType.ENUM || + shape.getType() == ShapeType.INT_ENUM + ); + } - /** - * Returns if the Go type generated for the shape can be dereferenced. - * - * @param shape the shape to check - * @return if the shape's go type is dereferencable - */ - public final boolean isDereferencable(ToShapeId shape) { - return dereferencableShapes.contains(shape.toShapeId()); + private boolean isBlobStream(Shape shape) { + return ( + shape.getType() == ShapeType.BLOB && shape.hasTrait(StreamingTrait.ID) + ); + } + + private boolean isOperationStruct(Shape shape) { + NeighborProvider provider = NeighborProviderIndex + .of(model) + .getReverseProvider(); + for (Relationship relationship : provider.getNeighbors(shape)) { + RelationshipType relationshipType = relationship.getRelationshipType(); + if ( + relationshipType == RelationshipType.INPUT || + relationshipType == RelationshipType.OUTPUT + ) { + return true; + } } + + return false; + } + + /** + * Returns if the shape should be generated as a Go pointer type or not. + * + * @param shape the shape to check if should be pointable type. + * @return if the shape is should be a Go pointer type. + */ + public final boolean isPointable(ToShapeId shape) { + return pointableShapes.contains(shape.toShapeId()); + } + + /** + * Returns if the Go type generated for the shape is comparable to nil. + * + * @param shape the shape to check + * @return if the shape's go type is comparable to nil + */ + public final boolean isNillable(ToShapeId shape) { + return nillableShapes.contains(shape.toShapeId()); + } + + /** + * Returns if the Go type generated for the shape can be dereferenced. + * + * @param shape the shape to check + * @return if the shape's go type is dereferencable + */ + public final boolean isDereferencable(ToShapeId shape) { + return dereferencableShapes.contains(shape.toShapeId()); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/knowledge/GoUsageIndex.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/knowledge/GoUsageIndex.java index e8e00b0fa0..8bd7b5226f 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/knowledge/GoUsageIndex.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/knowledge/GoUsageIndex.java @@ -15,6 +15,9 @@ package software.amazon.polymorph.smithygo.codegen.knowledge; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.KnowledgeIndex; import software.amazon.smithy.model.knowledge.OperationIndex; @@ -27,65 +30,87 @@ import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.ToShapeId; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; - /** * Provides {@link KnowledgeIndex} of how shapes are used in the model. */ public class GoUsageIndex implements KnowledgeIndex { - private final Model model; - private final Walker walker; - private final Set inputShapes = new HashSet<>(); - private final Set outputShapes = new HashSet<>(); + private final Model model; + private final Walker walker; - public GoUsageIndex(Model model) { - this.model = model; - this.walker = new Walker(model); + private final Set inputShapes = new HashSet<>(); + private final Set outputShapes = new HashSet<>(); - TopDownIndex topDownIndex = TopDownIndex.of(model); - OperationIndex operationIndex = OperationIndex.of(model); + public GoUsageIndex(Model model) { + this.model = model; + this.walker = new Walker(model); - model.shapes(ServiceShape.class).forEach(serviceShape -> { - topDownIndex.getContainedOperations(serviceShape).forEach(operationShape -> { - StructureShape inputShape = operationIndex.getInput(operationShape).get(); - StructureShape outputShape = operationIndex.getOutput(operationShape).get(); + TopDownIndex topDownIndex = TopDownIndex.of(model); + OperationIndex operationIndex = OperationIndex.of(model); - inputShapes.addAll(walker.walkShapes(inputShape, relationship -> - relationship.getDirection() == RelationshipDirection.DIRECTED).stream() - .map(Shape::toShapeId).collect(Collectors.toList())); + model + .shapes(ServiceShape.class) + .forEach(serviceShape -> { + topDownIndex + .getContainedOperations(serviceShape) + .forEach(operationShape -> { + StructureShape inputShape = operationIndex + .getInput(operationShape) + .get(); + StructureShape outputShape = operationIndex + .getOutput(operationShape) + .get(); - outputShapes.addAll(walker.walkShapes(outputShape, relationship -> - relationship.getDirection() == RelationshipDirection.DIRECTED).stream() - .map(Shape::toShapeId).collect(Collectors.toList())); + inputShapes.addAll( + walker + .walkShapes( + inputShape, + relationship -> + relationship.getDirection() == + RelationshipDirection.DIRECTED + ) + .stream() + .map(Shape::toShapeId) + .collect(Collectors.toList()) + ); - }); - }); - } + outputShapes.addAll( + walker + .walkShapes( + outputShape, + relationship -> + relationship.getDirection() == + RelationshipDirection.DIRECTED + ) + .stream() + .map(Shape::toShapeId) + .collect(Collectors.toList()) + ); + }); + }); + } - /** - * Returns whether shape is used as part of an input to an operation. - * - * @param shape the shape - * @return whether the shape is used as input. - */ - public boolean isUsedForInput(ToShapeId shape) { - return inputShapes.contains(shape.toShapeId()); - } + /** + * Returns whether shape is used as part of an input to an operation. + * + * @param shape the shape + * @return whether the shape is used as input. + */ + public boolean isUsedForInput(ToShapeId shape) { + return inputShapes.contains(shape.toShapeId()); + } - /** - * Returns whether shape is used as output of an operation. - * - * @param shape the shape - * @return whether the shape is used as input. - */ - public boolean isUsedForOutput(ToShapeId shape) { - return outputShapes.contains(shape.toShapeId()); - } + /** + * Returns whether shape is used as output of an operation. + * + * @param shape the shape + * @return whether the shape is used as input. + */ + public boolean isUsedForOutput(ToShapeId shape) { + return outputShapes.contains(shape.toShapeId()); + } - public static GoUsageIndex of(Model model) { - return model.getKnowledge(GoUsageIndex.class, GoUsageIndex::new); - } + public static GoUsageIndex of(Model model) { + return model.getKnowledge(GoUsageIndex.class, GoUsageIndex::new); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/knowledge/GoValidationIndex.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/knowledge/GoValidationIndex.java index 60ef46d1b5..c4410f6eca 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/knowledge/GoValidationIndex.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/codegen/knowledge/GoValidationIndex.java @@ -30,6 +30,13 @@ package software.amazon.polymorph.smithygo.codegen.knowledge; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Consumer; +import java.util.stream.Collectors; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.KnowledgeIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; @@ -44,148 +51,209 @@ import software.amazon.smithy.model.traits.RequiredTrait; import software.amazon.smithy.utils.SetUtils; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; -import java.util.function.Consumer; -import java.util.stream.Collectors; - /** * Provides a knowledge index of which service operations and shapes require validation helpers. */ public class GoValidationIndex implements KnowledgeIndex { - private final Map> serviceToOperationMap = new HashMap<>(); - private final Map> serviceValidationHelpers = new HashMap<>(); - - public GoValidationIndex(Model model) { - TopDownIndex topDownIndex = model.getKnowledge(TopDownIndex.class); - Walker walker = new Walker(model); - - model.shapes(ServiceShape.class).forEach(serviceShape -> { - // Go uses unique input shapes per operation so we can index using the input shape as our key - Map inputShapeToOperation = new HashMap<>(); - Set requireValidationHelpers = new TreeSet<>(); - - // First pass is to collect member containers that contain members requiring validation - Set operations = topDownIndex.getContainedOperations(serviceShape); - operations.forEach(operationShape -> { - Shape inputShape = model.expectShape(operationShape.getInput().get()); - GoValidationIndex.walkValidationTree(walker, inputShape, shape -> { - if (shape.isMemberShape()) { - Shape container = model.expectShape(((MemberShape) shape).getContainer()); - if (isRequiredParameter(model, (MemberShape) shape, inputShape.equals(container))) { - inputShapeToOperation.put(inputShape, operationShape); - requireValidationHelpers.add(container.toShapeId()); - } - } - }); - }); - - // 2nd step is final all containers that reference the initial containers which require validation until - // we've discovered all intermediate containing types - inputShapeToOperation.keySet().forEach(input -> { - Set helpers = new TreeSet<>(); - do { - GoValidationIndex.walkValidationTree(walker, input, shape -> { - if (shape.isMemberShape()) { - MemberShape memberShape = shape.asMemberShape().get(); - Shape container = model.expectShape(memberShape.getContainer()); - Shape target = model.expectShape(memberShape.getTarget()); - if (requireValidationHelpers.contains(target.toShapeId()) - && !requireValidationHelpers.contains(container.toShapeId())) { - helpers.add(container.toShapeId()); - } - } - }); - if (helpers.isEmpty()) { - break; - } - requireValidationHelpers.addAll(helpers); - helpers.clear(); - } while (true); - }); - - serviceToOperationMap.put(serviceShape.toShapeId(), new TreeSet<>(inputShapeToOperation.values().stream() - .map(OperationShape::toShapeId).collect(Collectors.toSet()))); - serviceValidationHelpers.put(serviceShape.toShapeId(), requireValidationHelpers); - }); - } - - public static GoValidationIndex of(Model model) { - return model.getKnowledge(GoValidationIndex.class, GoValidationIndex::new); - } - - /** - * Get the set of operations that require validation. - * - * @param service service to find operations for - * @return operations requiring validation - */ - public Set getOperationsRequiringValidation(ToShapeId service) { - return serviceToOperationMap.getOrDefault(service.toShapeId(), SetUtils.of()); - } - - /** - * Get a set of shapes that require validation helpers. - * - * @param service service to find operations for - * @return operations requiring validation - */ - public Set getShapesRequiringValidationHelpers(ToShapeId service) { - return serviceValidationHelpers.getOrDefault(service.toShapeId(), SetUtils.of()); - } - - /** - * Returns whether the given shape requires a validation helper. - * - * @param shape the shape to check - * @return whether the shape requires a validation helper - */ - public boolean isValidationHelperRequired(ToShapeId shape) { - return serviceValidationHelpers.containsKey(shape.toShapeId()); - } - - /** - * Checks whether a {@link MemberShape} has any validation constraints. - * - * @param model the model - * @param shape the {@link MemberShape} to check - * @param validateHttpBindings whether http bindings should be checked for additional implicit constraints - * @return whether the {@link MemberShape} has validation costraints - */ - public static boolean hasValidation(Model model, MemberShape shape, boolean validateHttpBindings) { - return isRequiredParameter(model, shape, validateHttpBindings); - } - - /** - * Checks whether a {@link MemberShape} is marked as being required explicitly or implicitly. - * - * @param model the model - * @param shape the {@link MemberShape} to check - * @param validateHttpBindings whether http bindings should be checked for additional implicit constraints - * @return whether the {@link MemberShape} is a required parameter - */ - public static boolean isRequiredParameter(Model model, MemberShape shape, boolean validateHttpBindings) { - Optional requiredTrait = shape.getMemberTrait(model, RequiredTrait.class); - return requiredTrait.isPresent() || (validateHttpBindings && shape.getMemberTrait(model, - HttpLabelTrait.class).isPresent()); - } - - private static void walkValidationTree(Walker walker, Shape shape, Consumer visitor) { - walker.walkShapes(shape, relationship -> { - switch (relationship.getRelationshipType()) { - case STRUCTURE_MEMBER: - case UNION_MEMBER: - case MAP_VALUE: - case LIST_MEMBER: - case SET_MEMBER: - case MEMBER_TARGET: - return true; - default: - return false; + + private final Map> serviceToOperationMap = + new HashMap<>(); + private final Map> serviceValidationHelpers = + new HashMap<>(); + + public GoValidationIndex(Model model) { + TopDownIndex topDownIndex = model.getKnowledge(TopDownIndex.class); + Walker walker = new Walker(model); + + model + .shapes(ServiceShape.class) + .forEach(serviceShape -> { + // Go uses unique input shapes per operation so we can index using the input shape as our key + Map inputShapeToOperation = new HashMap<>(); + Set requireValidationHelpers = new TreeSet<>(); + + // First pass is to collect member containers that contain members requiring validation + Set operations = topDownIndex.getContainedOperations( + serviceShape + ); + operations.forEach(operationShape -> { + Shape inputShape = model.expectShape(operationShape.getInput().get()); + GoValidationIndex.walkValidationTree( + walker, + inputShape, + shape -> { + if (shape.isMemberShape()) { + Shape container = model.expectShape( + ((MemberShape) shape).getContainer() + ); + if ( + isRequiredParameter( + model, + (MemberShape) shape, + inputShape.equals(container) + ) + ) { + inputShapeToOperation.put(inputShape, operationShape); + requireValidationHelpers.add(container.toShapeId()); + } + } } - }).forEach(visitor::accept); - } + ); + }); + + // 2nd step is final all containers that reference the initial containers which require validation until + // we've discovered all intermediate containing types + inputShapeToOperation + .keySet() + .forEach(input -> { + Set helpers = new TreeSet<>(); + do { + GoValidationIndex.walkValidationTree( + walker, + input, + shape -> { + if (shape.isMemberShape()) { + MemberShape memberShape = shape.asMemberShape().get(); + Shape container = model.expectShape( + memberShape.getContainer() + ); + Shape target = model.expectShape(memberShape.getTarget()); + if ( + requireValidationHelpers.contains(target.toShapeId()) && + !requireValidationHelpers.contains(container.toShapeId()) + ) { + helpers.add(container.toShapeId()); + } + } + } + ); + if (helpers.isEmpty()) { + break; + } + requireValidationHelpers.addAll(helpers); + helpers.clear(); + } while (true); + }); + + serviceToOperationMap.put( + serviceShape.toShapeId(), + new TreeSet<>( + inputShapeToOperation + .values() + .stream() + .map(OperationShape::toShapeId) + .collect(Collectors.toSet()) + ) + ); + serviceValidationHelpers.put( + serviceShape.toShapeId(), + requireValidationHelpers + ); + }); + } + + public static GoValidationIndex of(Model model) { + return model.getKnowledge(GoValidationIndex.class, GoValidationIndex::new); + } + + /** + * Get the set of operations that require validation. + * + * @param service service to find operations for + * @return operations requiring validation + */ + public Set getOperationsRequiringValidation(ToShapeId service) { + return serviceToOperationMap.getOrDefault( + service.toShapeId(), + SetUtils.of() + ); + } + + /** + * Get a set of shapes that require validation helpers. + * + * @param service service to find operations for + * @return operations requiring validation + */ + public Set getShapesRequiringValidationHelpers(ToShapeId service) { + return serviceValidationHelpers.getOrDefault( + service.toShapeId(), + SetUtils.of() + ); + } + + /** + * Returns whether the given shape requires a validation helper. + * + * @param shape the shape to check + * @return whether the shape requires a validation helper + */ + public boolean isValidationHelperRequired(ToShapeId shape) { + return serviceValidationHelpers.containsKey(shape.toShapeId()); + } + + /** + * Checks whether a {@link MemberShape} has any validation constraints. + * + * @param model the model + * @param shape the {@link MemberShape} to check + * @param validateHttpBindings whether http bindings should be checked for additional implicit constraints + * @return whether the {@link MemberShape} has validation costraints + */ + public static boolean hasValidation( + Model model, + MemberShape shape, + boolean validateHttpBindings + ) { + return isRequiredParameter(model, shape, validateHttpBindings); + } + + /** + * Checks whether a {@link MemberShape} is marked as being required explicitly or implicitly. + * + * @param model the model + * @param shape the {@link MemberShape} to check + * @param validateHttpBindings whether http bindings should be checked for additional implicit constraints + * @return whether the {@link MemberShape} is a required parameter + */ + public static boolean isRequiredParameter( + Model model, + MemberShape shape, + boolean validateHttpBindings + ) { + Optional requiredTrait = shape.getMemberTrait( + model, + RequiredTrait.class + ); + return ( + requiredTrait.isPresent() || + (validateHttpBindings && + shape.getMemberTrait(model, HttpLabelTrait.class).isPresent()) + ); + } + + private static void walkValidationTree( + Walker walker, + Shape shape, + Consumer visitor + ) { + walker + .walkShapes( + shape, + relationship -> { + switch (relationship.getRelationshipType()) { + case STRUCTURE_MEMBER: + case UNION_MEMBER: + case MAP_VALUE: + case LIST_MEMBER: + case SET_MEMBER: + case MEMBER_TARGET: + return true; + default: + return false; + } + } + ) + .forEach(visitor::accept); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyGoPointableIndex.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyGoPointableIndex.java index 7d23954f7d..e9d301b029 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyGoPointableIndex.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyGoPointableIndex.java @@ -7,7 +7,10 @@ public class DafnyGoPointableIndex { - public static boolean isNillable(final Model model, final Shape shape) { - return !shape.hasTrait(RequiredTrait.class) && !GoCodegenUtils.isOperationStruct(model, shape); - } + public static boolean isNillable(final Model model, final Shape shape) { + return ( + !shape.hasTrait(RequiredTrait.class) && + !GoCodegenUtils.isOperationStruct(model, shape) + ); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceCodegenPlugin.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceCodegenPlugin.java index eea7ae096f..40c1b2e659 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceCodegenPlugin.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceCodegenPlugin.java @@ -1,5 +1,6 @@ package software.amazon.polymorph.smithygo.localservice; +import java.util.Map; import software.amazon.polymorph.smithygo.codegen.GenerationContext; import software.amazon.polymorph.smithygo.codegen.GoSettings; import software.amazon.polymorph.smithygo.codegen.GoWriter; @@ -9,51 +10,58 @@ import software.amazon.smithy.build.SmithyBuildPlugin; import software.amazon.smithy.codegen.core.directed.CodegenDirector; -import java.util.Map; - public class DafnyLocalServiceCodegenPlugin implements SmithyBuildPlugin { - public DafnyLocalServiceCodegenPlugin(final Map smithyNamespaceToPythonModuleNameMap) { - super(); - SmithyNameResolver.setSmithyNamespaceToGoModuleNameMap(smithyNamespaceToPythonModuleNameMap); - } - public void run(PluginContext context) { - CodegenDirector runner - = new CodegenDirector<>(); + public DafnyLocalServiceCodegenPlugin( + final Map smithyNamespaceToPythonModuleNameMap + ) { + super(); + SmithyNameResolver.setSmithyNamespaceToGoModuleNameMap( + smithyNamespaceToPythonModuleNameMap + ); + } + + public void run(PluginContext context) { + CodegenDirector< + GoWriter, + GoIntegration, + GenerationContext, + GoSettings + > runner = new CodegenDirector<>(); - runner.directedCodegen(new DafnyLocalServiceDirectedCodegen()); + runner.directedCodegen(new DafnyLocalServiceDirectedCodegen()); - // Set the SmithyIntegration class to look for and apply using SPI. - runner.integrationClass(GoIntegration.class); + // Set the SmithyIntegration class to look for and apply using SPI. + runner.integrationClass(GoIntegration.class); - // Set the FileManifest and Model from the plugin. - runner.fileManifest(context.getFileManifest()); - runner.model(context.getModel()); + // Set the FileManifest and Model from the plugin. + runner.fileManifest(context.getFileManifest()); + runner.model(context.getModel()); - // Create the GoSettings object from the plugin settings. - GoSettings settings = GoSettings.from(context.getSettings()); - runner.settings(settings); + // Create the GoSettings object from the plugin settings. + GoSettings settings = GoSettings.from(context.getSettings()); + runner.settings(settings); - runner.service(settings.getService()); + runner.service(settings.getService()); - // Configure the director to perform some common model transforms. - runner.performDefaultCodegenTransforms(); + // Configure the director to perform some common model transforms. + runner.performDefaultCodegenTransforms(); - // TODO: Not using below because it would break existing AWS SDKs. Maybe it should be configurable - // so generic SDKs call this by default, but AWS SDKs can opt-out of it via a setting. - // runner.createDedicatedInputsAndOutputs(); + // TODO: Not using below because it would break existing AWS SDKs. Maybe it should be configurable + // so generic SDKs call this by default, but AWS SDKs can opt-out of it via a setting. + // runner.createDedicatedInputsAndOutputs(); - // Run it! - runner.run(); - } + // Run it! + runner.run(); + } - @Override - public String getName() { - return "go-client-codegen"; - } + @Override + public String getName() { + return "go-client-codegen"; + } - @Override - public void execute(PluginContext context) { - this.run(context); - } + @Override + public void execute(PluginContext context) { + this.run(context); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceDirectedCodegen.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceDirectedCodegen.java index cd22bd32c5..7c3b043bee 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceDirectedCodegen.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceDirectedCodegen.java @@ -1,5 +1,6 @@ package software.amazon.polymorph.smithygo.localservice; +import java.util.logging.Logger; import software.amazon.polymorph.smithygo.codegen.EnumGenerator; import software.amazon.polymorph.smithygo.codegen.GenerationContext; import software.amazon.polymorph.smithygo.codegen.GoDelegator; @@ -20,112 +21,191 @@ import software.amazon.smithy.codegen.core.directed.GenerateStructureDirective; import software.amazon.smithy.codegen.core.directed.GenerateUnionDirective; -import java.util.logging.Logger; - -public class DafnyLocalServiceDirectedCodegen implements DirectedCodegen { - private static final Logger LOGGER = Logger.getLogger(DafnyLocalServiceDirectedCodegen.class.getName()); - @Override - public SymbolProvider createSymbolProvider(CreateSymbolProviderDirective directive) { - return new SymbolVisitor(directive.model(), directive.settings()); +public class DafnyLocalServiceDirectedCodegen + implements DirectedCodegen { + + private static final Logger LOGGER = Logger.getLogger( + DafnyLocalServiceDirectedCodegen.class.getName() + ); + + @Override + public SymbolProvider createSymbolProvider( + CreateSymbolProviderDirective directive + ) { + return new SymbolVisitor(directive.model(), directive.settings()); + } + + @Override + public GenerationContext createContext( + CreateContextDirective directive + ) { + return GenerationContext + .builder() + .model(directive.model()) + .settings(directive.settings()) + .symbolProvider(directive.symbolProvider()) + .fileManifest(directive.fileManifest()) + .integrations(directive.integrations()) + .writerDelegator( + new GoDelegator(directive.fileManifest(), directive.symbolProvider()) + ) + .protocolGenerator(new DafnyLocalServiceTypeConversionProtocol()) + .build(); + } + + @Override + public void generateService( + GenerateServiceDirective directive + ) { + if ( + !directive + .shape() + .getId() + .getNamespace() + .equals(directive.context().settings().getService().getNamespace()) + ) { + return; } + new DafnyLocalServiceGenerator(directive.context(), directive.service()) + .run(); - @Override - public GenerationContext createContext(CreateContextDirective directive) { - return GenerationContext.builder() - .model(directive.model()) - .settings(directive.settings()) - .symbolProvider(directive.symbolProvider()) - .fileManifest(directive.fileManifest()) - .integrations(directive.integrations()) - .writerDelegator(new GoDelegator(directive.fileManifest(), directive.symbolProvider())) - .protocolGenerator(new DafnyLocalServiceTypeConversionProtocol()) - .build(); + var protocolGenerator = directive.context().protocolGenerator(); + if (protocolGenerator == null) { + return; } - @Override - public void generateService(GenerateServiceDirective directive) { - if(!directive.shape().getId().getNamespace().equals(directive.context().settings().getService().getNamespace())) - { - return; - } - new DafnyLocalServiceGenerator(directive.context(), directive.service()).run(); - - var protocolGenerator = directive.context().protocolGenerator(); - if (protocolGenerator == null) { - return; - } - - protocolGenerator.generateSerializers(directive.context()); - - protocolGenerator.generateDeserializers(directive.context()); - + protocolGenerator.generateSerializers(directive.context()); + + protocolGenerator.generateDeserializers(directive.context()); + } + + @Override + public void generateStructure( + GenerateStructureDirective directive + ) { + if ( + !directive + .shape() + .getId() + .getNamespace() + .equals(directive.context().settings().getService().getNamespace()) + ) { + return; } - - @Override - public void generateStructure(GenerateStructureDirective directive) { - if(!directive.shape().getId().getNamespace().equals(directive.context().settings().getService().getNamespace())) - { - return; + directive + .context() + .writerDelegator() + .useShapeWriter( + directive.shape(), + writer -> { + StructureGenerator generator = new StructureGenerator( + directive.context(), + writer, + directive.shape() + ); + generator.run(); } - directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> { - StructureGenerator generator = new StructureGenerator( - directive.context(), - writer, - directive.shape() - ); - generator.run(); - }); + ); + } + + @Override + public void generateError( + GenerateErrorDirective directive + ) { + if ( + !directive + .shape() + .getId() + .getNamespace() + .equals(directive.context().settings().getService().getNamespace()) + ) { + return; } - - @Override - public void generateError(GenerateErrorDirective directive) { - if(!directive.shape().getId().getNamespace().equals(directive.context().settings().getService().getNamespace())) - { - return; + directive + .context() + .writerDelegator() + .useShapeWriter( + directive.shape(), + writer -> { + StructureGenerator generator = new StructureGenerator( + directive.context(), + writer, + directive.shape() + ); + generator.run(); } - directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> { - StructureGenerator generator = new StructureGenerator( - directive.context(), - writer, - directive.shape() - ); - generator.run(); - }); + ); + } + + @Override + public void generateUnion( + GenerateUnionDirective directive + ) {} + + @Override + public void generateEnumShape( + GenerateEnumDirective directive + ) { + if ( + !directive + .shape() + .getId() + .getNamespace() + .equals(directive.context().settings().getService().getNamespace()) + ) { + return; } - - @Override - public void generateUnion(GenerateUnionDirective directive) { - - } - - @Override - public void generateEnumShape(GenerateEnumDirective directive) { - if(!directive.shape().getId().getNamespace().equals(directive.context().settings().getService().getNamespace())) - { - return; + directive + .context() + .writerDelegator() + .useShapeWriter( + directive.shape(), + writer -> { + EnumGenerator enumGenerator = new EnumGenerator( + directive.symbolProvider(), + writer, + directive.shape() + ); + enumGenerator.run(); } - directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> { - EnumGenerator enumGenerator = new EnumGenerator(directive.symbolProvider(), writer, directive.shape()); - enumGenerator.run(); - }); + ); + } + + @Override + public void generateIntEnumShape( + GenerateIntEnumDirective directive + ) { + if ( + !directive + .shape() + .getId() + .getNamespace() + .equals(directive.context().settings().getService().getNamespace()) + ) { + return; } - - @Override - public void generateIntEnumShape(GenerateIntEnumDirective directive) { - if(!directive.shape().getId().getNamespace().equals(directive.context().settings().getService().getNamespace())) - { - return; + directive + .context() + .writerDelegator() + .useShapeWriter( + directive.shape(), + writer -> { + IntEnumGenerator intEnumGenerator = new IntEnumGenerator( + directive.symbolProvider(), + writer, + directive.shape().asIntEnumShape().get() + ); + intEnumGenerator.run(); } - directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> { - IntEnumGenerator intEnumGenerator = new IntEnumGenerator(directive.symbolProvider(), writer, directive.shape().asIntEnumShape().get()); - intEnumGenerator.run(); - }); - } - - @Override - public void generateResource(GenerateResourceDirective directive) { -// System.out.println("##############" + directive.shape()); -// directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> { -// }); - } + ); + } + + @Override + public void generateResource( + GenerateResourceDirective directive + ) { + // System.out.println("##############" + directive.shape()); + // directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> { + // }); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceGenerator.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceGenerator.java index c985d33cd8..f23c4c2c40 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceGenerator.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceGenerator.java @@ -3,6 +3,8 @@ package software.amazon.polymorph.smithygo.localservice; +import java.util.Collection; +import java.util.stream.Collectors; import software.amazon.polymorph.smithygo.codegen.GenerationContext; import software.amazon.polymorph.smithygo.codegen.GoDelegator; import software.amazon.polymorph.smithygo.codegen.GoWriter; @@ -25,500 +27,924 @@ import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.model.traits.UnitTypeTrait; -import java.util.Collection; -import java.util.stream.Collectors; - - public class DafnyLocalServiceGenerator implements Runnable { - private final GenerationContext context; - private final ServiceShape service; - private final TopDownIndex topDownIndex; - private final GoDelegator writerDelegator; - private final Model model; - private final SymbolProvider symbolProvider; - - public DafnyLocalServiceGenerator(GenerationContext context, ServiceShape service) { - this.context = context; - this.service = service; - model = context.model(); - topDownIndex = TopDownIndex.of(model); - writerDelegator = context.writerDelegator(); - symbolProvider = context.symbolProvider(); - } - @Override - public void run() { - writerDelegator.useShapeWriter(service, this::generateService); + private final GenerationContext context; + private final ServiceShape service; + private final TopDownIndex topDownIndex; + private final GoDelegator writerDelegator; + private final Model model; + private final SymbolProvider symbolProvider; + + public DafnyLocalServiceGenerator( + GenerationContext context, + ServiceShape service + ) { + this.context = context; + this.service = service; + model = context.model(); + topDownIndex = TopDownIndex.of(model); + writerDelegator = context.writerDelegator(); + symbolProvider = context.symbolProvider(); + } + + @Override + public void run() { + writerDelegator.useShapeWriter(service, this::generateService); + } + + private void generateService(GoWriter writer) { + if (service.hasTrait(LocalServiceTrait.class)) { + generateClient(writer); + generateUnmodelledErrors(context); + generateReferencedResources(context); + generateUnboundedStructures(context); } + generateShim(); + } + + void generateClient(GoWriter writer) { + // Generate each operation for the service. We do this here instead of via the operation visitor method to + // limit it to the operations bound to the service. + final var serviceSymbol = symbolProvider.toSymbol(service); + final var serviceTrait = service.expectTrait(LocalServiceTrait.class); + final var configSymbol = symbolProvider.toSymbol( + model.expectShape(serviceTrait.getConfigId()) + ); + + writerDelegator.useFileWriter( + "%s/types.go".formatted(SmithyNameResolver.smithyTypesNamespace(service)), + SmithyNameResolver.smithyTypesNamespace(service), + writer1 -> { + new StructureGenerator( + context, + writer1, + model.expectShape(serviceTrait.getConfigId()).asStructureShape().get() + ) + .run(); + model + .getUnionShapes() + .stream() + .filter(unionShape -> + unionShape + .getId() + .getNamespace() + .equals(service.getId().getNamespace()) + ) + .forEach(unionShape -> { + new UnionGenerator(model, symbolProvider, unionShape) + .generateUnion(writer1); + }); + } + ); + + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + context.settings().getService().getNamespace() + ), + DafnyNameResolver.dafnyTypesNamespace(service) + ); + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + context.settings().getService().getNamespace() + ), + DafnyNameResolver.dafnyNamespace(service) + ); + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + context.settings().getService().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(service) + ); + writer.addUseImports(SmithyGoDependency.CONTEXT); + + final var dafnyClient = DafnyNameResolver.getDafnyClient( + service, + serviceTrait.getSdkId() + ); + writer.write( + """ + type $T struct { + DafnyClient *$L + } + + func NewClient(clientConfig $L) (*$T, error) { + var dafnyConfig = $L(clientConfig) + var dafny_response = $L(dafnyConfig) + if (dafny_response.Is_Failure()) { + panic("Client construction failed. This should never happen") + } + var dafnyClient = dafny_response.Extract().(*$L) + client := &$T { dafnyClient } + return client, nil + } + """, + serviceSymbol, + dafnyClient, + SmithyNameResolver.getSmithyType(service, configSymbol), + serviceSymbol, + SmithyNameResolver.getToDafnyMethodName( + service, + context.model().expectShape(serviceTrait.getConfigId()), + "" + ), + DafnyNameResolver.createDafnyClient(service, serviceTrait.getSdkId()), + dafnyClient, + serviceSymbol + ); + + service + .getOperations() + .forEach(operation -> { + final var operationShape = model.expectShape( + operation, + OperationShape.class + ); + final var inputShape = model.expectShape( + operationShape.getInputShape() + ); + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + inputShape.toShapeId().getNamespace() + ), + DafnyNameResolver.dafnyTypesNamespace(inputShape) + ); + if ( + !inputShape + .toShapeId() + .getNamespace() + .equals(service.toShapeId().getNamespace()) + ) { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + inputShape.toShapeId().getNamespace() + ), + SmithyNameResolver.shapeNamespace(inputShape) + ); + } + final var outputShape = model.expectShape( + operationShape.getOutputShape() + ); + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + outputShape.toShapeId().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(outputShape) + ); + if ( + !outputShape + .toShapeId() + .getNamespace() + .equals(service.toShapeId().getNamespace()) + ) { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + outputShape.toShapeId().getNamespace() + ), + SmithyNameResolver.shapeNamespace(outputShape) + ); + } + final var inputType = inputShape.hasTrait(UnitTypeTrait.class) + ? "" + : ", params %s.%s".formatted( + SmithyNameResolver.smithyTypesNamespace(inputShape), + inputShape.toShapeId().getName() + ); + final var outputType = outputShape.hasTrait(UnitTypeTrait.class) + ? "" + : "*%s.%s,".formatted( + SmithyNameResolver.smithyTypesNamespace(outputShape), + outputShape.toShapeId().getName() + ); + String validationCheck = ""; + if (!inputType.equals("")) { + validationCheck = + """ + err := params.Validate() + if err != nil { + opaqueErr := %s.OpaqueError{ + ErrObject: err, + } + """.formatted(SmithyNameResolver.smithyTypesNamespace(inputShape)); + if (outputType.equals("")) { + validationCheck += "return opaqueErr }"; + } else { + validationCheck += "return nil, opaqueErr }"; + } + } + String baseClientCall; + if (inputShape.hasTrait(UnitTypeTrait.class)) { + baseClientCall = + "var dafny_response = client.DafnyClient.%s()".formatted( + operationShape.getId().getName() + ); + } else { + baseClientCall = + """ + var dafny_request %s = %s(params) + var dafny_response = client.DafnyClient.%s(dafny_request) + """.formatted( + DafnyNameResolver.getDafnyType( + inputShape, + symbolProvider.toSymbol(inputShape) + ), + SmithyNameResolver.getToDafnyMethodName( + service, + inputShape, + "" + ), + operationShape.getId().getName() + ); + } - private void generateService(GoWriter writer) { - if (service.hasTrait(LocalServiceTrait.class)) { - generateClient(writer); - generateUnmodelledErrors(context); - generateReferencedResources(context); - generateUnboundedStructures(context); + String returnResponse, returnError; + if (outputShape.hasTrait(UnitTypeTrait.class)) { + returnResponse = "return nil"; + returnError = "return"; + } else { + returnResponse = + """ + var native_response = %s(dafny_response.Extract().(%s)) + return &native_response, nil + """.formatted( + SmithyNameResolver.getFromDafnyMethodName( + service, + outputShape, + "" + ), + DafnyNameResolver.getDafnyType( + outputShape, + symbolProvider.toSymbol(outputShape) + ) + ); + returnError = "return nil,"; } - generateShim(); - } - void generateClient(GoWriter writer) { - // Generate each operation for the service. We do this here instead of via the operation visitor method to - // limit it to the operations bound to the service. - final var serviceSymbol = symbolProvider.toSymbol(service); - final var serviceTrait = service.expectTrait(LocalServiceTrait.class); - final var configSymbol = symbolProvider.toSymbol(model.expectShape(serviceTrait.getConfigId())); - - writerDelegator.useFileWriter("%s/types.go".formatted(SmithyNameResolver.smithyTypesNamespace(service)), SmithyNameResolver.smithyTypesNamespace(service), writer1 -> { - new StructureGenerator(context, writer1, - model.expectShape(serviceTrait.getConfigId()).asStructureShape().get()).run(); - model.getUnionShapes().stream() - .filter(unionShape -> unionShape.getId().getNamespace().equals(service.getId().getNamespace())) - .forEach(unionShape -> { - new UnionGenerator(model, symbolProvider, unionShape).generateUnion(writer1); - }); - }); - - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(context.settings().getService().getNamespace()), DafnyNameResolver.dafnyTypesNamespace(service)); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(context.settings().getService().getNamespace()), DafnyNameResolver.dafnyNamespace(service)); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(context.settings().getService().getNamespace()), SmithyNameResolver.smithyTypesNamespace(service)); + writer.write( + """ + func (client *$T) $L(ctx context.Context $L) ($L error) { + $L + $L + if (dafny_response.Is_Failure()) { + err := dafny_response.Dtor_error().($L.Error); + $L Error_FromDafny(err) + } + $L + } + """, + serviceSymbol, + operationShape.getId().getName(), + inputType, + outputType, + validationCheck, + baseClientCall, + DafnyNameResolver.dafnyTypesNamespace(service), + returnError, + returnResponse + ); + }); + } + + void generateShim() { + final var namespace = + "%swrapped".formatted(DafnyNameResolver.dafnyNamespace(service)); + + writerDelegator.useFileWriter( + "%s/shim.go".formatted(namespace), + namespace, + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + context.settings().getService().getNamespace() + ), + DafnyNameResolver.dafnyTypesNamespace(service) + ); + writer.addImportFromModule( + "github.com/dafny-lang/DafnyStandardLibGo", + "Wrappers" + ); + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + context.settings().getService().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(service) + ); writer.addUseImports(SmithyGoDependency.CONTEXT); + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + context.settings().getService().getNamespace() + ), + SmithyNameResolver.shapeNamespace(service) + ); - final var dafnyClient = DafnyNameResolver.getDafnyClient(service, serviceTrait.getSdkId()); - writer.write(""" - type $T struct { - DafnyClient *$L - } - - func NewClient(clientConfig $L) (*$T, error) { - var dafnyConfig = $L(clientConfig) - var dafny_response = $L(dafnyConfig) - if (dafny_response.Is_Failure()) { - panic("Client construction failed. This should never happen") - } - var dafnyClient = dafny_response.Extract().(*$L) - client := &$T { dafnyClient } - return client, nil - } - """, - serviceSymbol, dafnyClient, SmithyNameResolver.getSmithyType(service, configSymbol), serviceSymbol, - SmithyNameResolver.getToDafnyMethodName(service, context.model().expectShape(serviceTrait.getConfigId()), ""), - DafnyNameResolver.createDafnyClient(service, serviceTrait.getSdkId()), - dafnyClient, serviceSymbol); - - service.getOperations().forEach(operation -> { - final var operationShape = model.expectShape(operation, OperationShape.class); - final var inputShape = model.expectShape(operationShape.getInputShape()); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(inputShape.toShapeId().getNamespace()), DafnyNameResolver.dafnyTypesNamespace(inputShape)); - if (!inputShape.toShapeId().getNamespace().equals(service.toShapeId().getNamespace())) { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(inputShape.toShapeId().getNamespace()), SmithyNameResolver.shapeNamespace(inputShape)); - } - final var outputShape = model.expectShape(operationShape.getOutputShape()); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(outputShape.toShapeId().getNamespace()), SmithyNameResolver.smithyTypesNamespace(outputShape)); - if (!outputShape.toShapeId().getNamespace().equals(service.toShapeId().getNamespace())) { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(outputShape.toShapeId().getNamespace()), SmithyNameResolver.shapeNamespace(outputShape)); + if (service.hasTrait(LocalServiceTrait.class)) { + final var serviceTrait = service.expectTrait(LocalServiceTrait.class); + final var configShape = model.expectShape(serviceTrait.getConfigId()); + final var configSymbol = symbolProvider.toSymbol(configShape); + + writer.write( + """ + type Shim struct { + $L + client *$L.Client } - final var inputType = inputShape.hasTrait(UnitTypeTrait.class) ? "" - : ", params %s.%s".formatted(SmithyNameResolver.smithyTypesNamespace(inputShape), inputShape.toShapeId().getName()); - final var outputType = outputShape.hasTrait(UnitTypeTrait.class) ? "" - : "*%s.%s,".formatted(SmithyNameResolver.smithyTypesNamespace(outputShape), outputShape.toShapeId().getName()); - String validationCheck = ""; - if(!inputType.equals("")) { - validationCheck = """ - err := params.Validate() - if err != nil { - opaqueErr := %s.OpaqueError{ - ErrObject: err, - } - """.formatted(SmithyNameResolver.smithyTypesNamespace(inputShape)); - if(outputType.equals("")) { - validationCheck += "return opaqueErr }"; + """, + DafnyNameResolver.getDafnyInterfaceClient(service), + SmithyNameResolver.shapeNamespace(service) + ); + + writer.write( + """ + func Wrapped$L(inputConfig $L) Wrappers.Result { + var nativeConfig = $L.$L(inputConfig) + var nativeClient, nativeError = $L.NewClient(nativeConfig) + if nativeError != nil { + return Wrappers.Companion_Result_.Create_Failure_($L.Companion_Error_.Create_Opaque_(nativeError)) } - else{ - validationCheck += "return nil, opaqueErr }"; - } - } - String baseClientCall; - if (inputShape.hasTrait(UnitTypeTrait.class)) { - baseClientCall = "var dafny_response = client.DafnyClient.%s()".formatted(operationShape.getId().getName()); - } else { - baseClientCall = """ - var dafny_request %s = %s(params) - var dafny_response = client.DafnyClient.%s(dafny_request) - """.formatted(DafnyNameResolver.getDafnyType(inputShape, symbolProvider.toSymbol(inputShape)), - SmithyNameResolver.getToDafnyMethodName(service, inputShape, ""), operationShape.getId().getName()); - } - - String returnResponse, returnError; - if (outputShape.hasTrait(UnitTypeTrait.class)) { - returnResponse = "return nil"; - returnError = "return"; - } else { - returnResponse = """ - var native_response = %s(dafny_response.Extract().(%s)) - return &native_response, nil - """.formatted(SmithyNameResolver.getFromDafnyMethodName(service, outputShape, ""), - DafnyNameResolver.getDafnyType(outputShape, symbolProvider.toSymbol(outputShape))); - returnError = "return nil,"; + return Wrappers.Companion_Result_.Create_Success_(&Shim{client: nativeClient}) } + """, + serviceTrait.getSdkId(), + DafnyNameResolver.getDafnyType(configShape, configSymbol), + SmithyNameResolver.shapeNamespace( + model.expectShape(serviceTrait.getConfigId()) + ), + SmithyNameResolver.getFromDafnyMethodName( + service, + model.expectShape(serviceTrait.getConfigId()), + "" + ), + SmithyNameResolver.shapeNamespace(service), + DafnyNameResolver.dafnyTypesNamespace(service) + ); + } - writer.write(""" - func (client *$T) $L(ctx context.Context $L) ($L error) { - $L - $L - if (dafny_response.Is_Failure()) { - err := dafny_response.Dtor_error().($L.Error); - $L Error_FromDafny(err) - } - $L - } - """, - serviceSymbol, - operationShape.getId().getName(), - inputType, outputType, - validationCheck, - baseClientCall, DafnyNameResolver.dafnyTypesNamespace(service), returnError, returnResponse + service + .getOperations() + .forEach(operation -> { + final var operationShape = model.expectShape( + operation, + OperationShape.class ); - }); - } - - void generateShim() { - final var namespace = "%swrapped".formatted(DafnyNameResolver.dafnyNamespace(service)); - - writerDelegator.useFileWriter("%s/shim.go".formatted(namespace), namespace, writer -> { - - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(context.settings().getService().getNamespace()), DafnyNameResolver.dafnyTypesNamespace(service)); - writer.addImportFromModule("github.com/dafny-lang/DafnyStandardLibGo", "Wrappers"); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(context.settings().getService().getNamespace()), SmithyNameResolver.smithyTypesNamespace(service)); - writer.addUseImports(SmithyGoDependency.CONTEXT); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(context.settings().getService().getNamespace()), SmithyNameResolver.shapeNamespace(service)); - - if (service.hasTrait(LocalServiceTrait.class)) { - final var serviceTrait = service.expectTrait(LocalServiceTrait.class); - final var configShape = model.expectShape(serviceTrait.getConfigId()); - final var configSymbol = symbolProvider.toSymbol(configShape); - - writer.write(""" - type Shim struct { - $L - client *$L.Client - } - """, - DafnyNameResolver.getDafnyInterfaceClient(service), - SmithyNameResolver.shapeNamespace(service) + final var inputShape = model.expectShape( + operationShape.getInputShape() + ); + final var outputShape = model.expectShape( + operationShape.getOutputShape() + ); + final var inputType = inputShape.hasTrait(UnitTypeTrait.class) + ? "" + : "input %s".formatted( + DafnyNameResolver.getDafnyType( + inputShape, + symbolProvider.toSymbol(inputShape) + ) ); - writer.write(""" - func Wrapped$L(inputConfig $L) Wrappers.Result { - var nativeConfig = $L.$L(inputConfig) - var nativeClient, nativeError = $L.NewClient(nativeConfig) - if nativeError != nil { - return Wrappers.Companion_Result_.Create_Failure_($L.Companion_Error_.Create_Opaque_(nativeError)) - } - return Wrappers.Companion_Result_.Create_Success_(&Shim{client: nativeClient}) - } - """, - serviceTrait.getSdkId(), DafnyNameResolver.getDafnyType(configShape, configSymbol), - SmithyNameResolver.shapeNamespace(model.expectShape(serviceTrait.getConfigId())), SmithyNameResolver.getFromDafnyMethodName(service, model.expectShape(serviceTrait.getConfigId()), ""), - SmithyNameResolver.shapeNamespace(service), DafnyNameResolver.dafnyTypesNamespace(service) + final var typeConversion = inputShape.hasTrait(UnitTypeTrait.class) + ? "" + : "var native_request = %s(input)".formatted( + SmithyNameResolver.getFromDafnyMethodName(inputShape, "") ); - } + final var clientCall = + "shim.client.%s(context.Background() %s)".formatted( + operationShape.getId().getName(), + inputShape.hasTrait(UnitTypeTrait.class) + ? "" + : ", native_request" + ); - service.getOperations().forEach(operation -> { - final var operationShape = model.expectShape(operation, OperationShape.class); - final var inputShape = model.expectShape(operationShape.getInputShape()); - final var outputShape = model.expectShape(operationShape.getOutputShape()); - final var inputType = inputShape.hasTrait(UnitTypeTrait.class) ? "" - : "input %s".formatted(DafnyNameResolver.getDafnyType(inputShape, symbolProvider.toSymbol(inputShape))); - - final var typeConversion = inputShape.hasTrait(UnitTypeTrait.class) ? "" - : "var native_request = %s(input)".formatted(SmithyNameResolver.getFromDafnyMethodName(inputShape, "")); - - final var clientCall = "shim.client.%s(context.Background() %s)".formatted(operationShape.getId().getName(), - inputShape.hasTrait(UnitTypeTrait.class) ? "" : ", native_request"); + String clientResponse, returnResponse; + if (outputShape.hasTrait(UnitTypeTrait.class)) { + clientResponse = "var native_error"; + returnResponse = "dafny.TupleOf()"; + writer.addImportFromModule( + "github.com/dafny-lang/DafnyRuntimeGo", + "dafny" + ); + } else { + clientResponse = "var native_response, native_error"; + returnResponse = + "%s(*native_response)".formatted( + SmithyNameResolver.getToDafnyMethodName(outputShape, "") + ); + } - String clientResponse, returnResponse; - if (outputShape.hasTrait(UnitTypeTrait.class)) { - clientResponse = "var native_error"; - returnResponse = "dafny.TupleOf()"; - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - } else { - clientResponse = "var native_response, native_error"; - returnResponse = "%s(*native_response)".formatted(SmithyNameResolver.getToDafnyMethodName(outputShape, "")); + writer.write( + """ + func (shim *Shim) $L($L) Wrappers.Result { + $L + $L = $L + if native_error != nil { + return Wrappers.Companion_Result_.Create_Failure_($L.Error_ToDafny(native_error)) + } + return Wrappers.Companion_Result_.Create_Success_($L) } - - writer.write(""" - func (shim *Shim) $L($L) Wrappers.Result { - $L - $L = $L - if native_error != nil { - return Wrappers.Companion_Result_.Create_Failure_($L.Error_ToDafny(native_error)) - } - return Wrappers.Companion_Result_.Create_Success_($L) - } - """, - operationShape.getId().getName(), - inputType, typeConversion, clientResponse, clientCall, - SmithyNameResolver.shapeNamespace(service), - returnResponse - ); - }); - }); + """, + operationShape.getId().getName(), + inputType, + typeConversion, + clientResponse, + clientCall, + SmithyNameResolver.shapeNamespace(service), + returnResponse + ); + }); + } + ); + } + + void shimErrors(GoWriter writer) { + for (final var error : model.getShapesWithTrait(ErrorTrait.class)) { + writer.write( + """ + case $L.$L: + return Wrappers.Companion_Result_.Create_Failure_($L(native_error.($L.$L))) + + + """, + SmithyNameResolver.smithyTypesNamespace(error), + symbolProvider.toSymbol(error).getName(), + SmithyNameResolver.getToDafnyMethodName(service, error, ""), + SmithyNameResolver.smithyTypesNamespace(error), + symbolProvider.toSymbol(error).getName() + ); } + } - void shimErrors(GoWriter writer) { - for (final var error : model.getShapesWithTrait(ErrorTrait.class)) { - writer.write(""" - case $L.$L: - return Wrappers.Companion_Result_.Create_Failure_($L(native_error.($L.$L))) - - - """, - SmithyNameResolver.smithyTypesNamespace(error), - symbolProvider.toSymbol(error).getName(), - SmithyNameResolver.getToDafnyMethodName(service, error, ""), SmithyNameResolver.smithyTypesNamespace(error), - symbolProvider.toSymbol(error).getName()); - } - } + void resourceErrors(GoWriter writer) { + for (final var error : model.getShapesWithTrait(ErrorTrait.class)) { + writer.write( + """ + case $L: + return Wrappers.Companion_Result_.Create_Failure_($L(native_error.($L))) - void resourceErrors(GoWriter writer) { - for (final var error : model.getShapesWithTrait(ErrorTrait.class)) { - writer.write(""" - case $L: - return Wrappers.Companion_Result_.Create_Failure_($L(native_error.($L))) - - - """, - SmithyNameResolver.getSmithyType(error, symbolProvider.toSymbol(error)), SmithyNameResolver.getToDafnyMethodName(service, error, ""), - SmithyNameResolver.getSmithyType(error, symbolProvider.toSymbol(error))); - } - } - void generateUnmodelledErrors(GenerationContext context) { - writerDelegator.useFileWriter("%s/types.go".formatted(SmithyNameResolver.smithyTypesNamespace(service)), SmithyNameResolver.smithyTypesNamespace(service), writer -> { - writer.write(""" - type $LBaseException interface { - // This is a dummy method to allow type assertion since Go empty interfaces - // aren't useful for type assertion checks. No concrete class is expected to implement - // this method. This is also not exported. - interfaceBindingMethod() - } - """, service.toShapeId().getName()); - }); - writerDelegator.useFileWriter("%s/unmodelled_errors.go".formatted(SmithyNameResolver.smithyTypesNamespace(service)), SmithyNameResolver.smithyTypesNamespace(service), writer -> { - writer.addUseImports(SmithyGoDependency.FMT); - writer.write(""" - type CollectionOfErrors struct { - $LBaseException - ListOfErrors []error - Message string - } - - func (e CollectionOfErrors) Error() string { - return fmt.Sprintf("message: %s\\n err %v", e.Message, e.ListOfErrors) - } - - type OpaqueError struct { - $LBaseException - ErrObject interface{} - } - - func (e OpaqueError) Error() string { - return fmt.Sprintf("message: %v", e.ErrObject ) - } - """, service.toShapeId().getName(), service.toShapeId().getName()); - }); + """, + SmithyNameResolver.getSmithyType(error, symbolProvider.toSymbol(error)), + SmithyNameResolver.getToDafnyMethodName(service, error, ""), + SmithyNameResolver.getSmithyType(error, symbolProvider.toSymbol(error)) + ); } - - void generateUnboundedStructures(GenerationContext context) { - final var serviceOperationShapes = model.getServiceShapes().stream() - .map(topDownIndex::getContainedOperations) - .flatMap(Collection::stream) - .map(OperationShape::toShapeId) - .collect(Collectors.toSet()); - final var nonServiceOperationShapes = model.getOperationShapes() - .stream() - .map(Shape::getId) - .filter(operationShapeId -> operationShapeId.getNamespace() - .equals(service.getId().getNamespace())) - .collect(Collectors.toSet()); - nonServiceOperationShapes.removeAll(serviceOperationShapes); - for (final var operationShapeId : nonServiceOperationShapes) { - OperationShape operationShape = model.expectShape(operationShapeId, OperationShape.class); - StructureShape inputShape = model.expectShape(operationShape.getInputShape(), StructureShape.class); - writerDelegator.useShapeWriter(inputShape, w -> new StructureGenerator(context, w, inputShape).run()); - StructureShape outputShape = model.expectShape(operationShape.getOutputShape(), StructureShape.class); - writerDelegator.useShapeWriter(outputShape, w -> new StructureGenerator(context, w, outputShape).run()); - } + } + + void generateUnmodelledErrors(GenerationContext context) { + writerDelegator.useFileWriter( + "%s/types.go".formatted(SmithyNameResolver.smithyTypesNamespace(service)), + SmithyNameResolver.smithyTypesNamespace(service), + writer -> { + writer.write( + """ + type $LBaseException interface { + // This is a dummy method to allow type assertion since Go empty interfaces + // aren't useful for type assertion checks. No concrete class is expected to implement + // this method. This is also not exported. + interfaceBindingMethod() + } + """, + service.toShapeId().getName() + ); + } + ); + writerDelegator.useFileWriter( + "%s/unmodelled_errors.go".formatted( + SmithyNameResolver.smithyTypesNamespace(service) + ), + SmithyNameResolver.smithyTypesNamespace(service), + writer -> { + writer.addUseImports(SmithyGoDependency.FMT); + writer.write( + """ + type CollectionOfErrors struct { + $LBaseException + ListOfErrors []error + Message string + } + + func (e CollectionOfErrors) Error() string { + return fmt.Sprintf("message: %s\\n err %v", e.Message, e.ListOfErrors) + } + + type OpaqueError struct { + $LBaseException + ErrObject interface{} + } + + func (e OpaqueError) Error() string { + return fmt.Sprintf("message: %v", e.ErrObject ) + } + """, + service.toShapeId().getName(), + service.toShapeId().getName() + ); + } + ); + } + + void generateUnboundedStructures(GenerationContext context) { + final var serviceOperationShapes = model + .getServiceShapes() + .stream() + .map(topDownIndex::getContainedOperations) + .flatMap(Collection::stream) + .map(OperationShape::toShapeId) + .collect(Collectors.toSet()); + final var nonServiceOperationShapes = model + .getOperationShapes() + .stream() + .map(Shape::getId) + .filter(operationShapeId -> + operationShapeId.getNamespace().equals(service.getId().getNamespace()) + ) + .collect(Collectors.toSet()); + nonServiceOperationShapes.removeAll(serviceOperationShapes); + for (final var operationShapeId : nonServiceOperationShapes) { + OperationShape operationShape = model.expectShape( + operationShapeId, + OperationShape.class + ); + StructureShape inputShape = model.expectShape( + operationShape.getInputShape(), + StructureShape.class + ); + writerDelegator.useShapeWriter( + inputShape, + w -> new StructureGenerator(context, w, inputShape).run() + ); + StructureShape outputShape = model.expectShape( + operationShape.getOutputShape(), + StructureShape.class + ); + writerDelegator.useShapeWriter( + outputShape, + w -> new StructureGenerator(context, w, outputShape).run() + ); } + } + + void generateReferencedResources(GenerationContext context) { + var refResources = model.getShapesWithTrait(ReferenceTrait.class); + for (final var refResource : refResources) { + if (!refResource.expectTrait(ReferenceTrait.class).isService()) { + final var resource = refResource + .expectTrait(ReferenceTrait.class) + .getReferentId(); + final var resourceShape = model.expectShape(resource); + + if ( + !service.toShapeId().getNamespace().equals(resource.getNamespace()) + ) { + continue; + } + writerDelegator.useFileWriter( + "%s/types.go".formatted( + SmithyNameResolver.smithyTypesNamespace(service) + ), + SmithyNameResolver.smithyTypesNamespace(service), + writer -> { + writer.write( + """ + type I$L interface { + ${C|} + } + """, + resource.getName(), + writer.consumer(w -> { + model + .expectShape(resource, ResourceShape.class) + .getOperations() + .forEach(operation -> { + var operationShape = model.expectShape( + operation, + OperationShape.class + ); + w.write( + """ + $L($L) (*$L, error) + """, + operationShape.getId().getName(), + operationShape.getInputShape().getName(), + operationShape.getOutputShape().getName() + ); + }); + }) + ); + } + ); + + if ( + model + .expectShape(resource, ResourceShape.class) + .hasTrait(ExtendableTrait.class) + ) { + generateNativeResourceWrapper( + context, + model.expectShape(resource, ResourceShape.class) + ); + } - void generateReferencedResources(GenerationContext context) { - var refResources = model.getShapesWithTrait(ReferenceTrait.class); - for (final var refResource : refResources) { - if (!refResource.expectTrait(ReferenceTrait.class).isService()) { - final var resource = refResource.expectTrait(ReferenceTrait.class).getReferentId(); - final var resourceShape = model.expectShape(resource); + writerDelegator.useFileWriter( + "%s/%s.go".formatted( + SmithyNameResolver.shapeNamespace(service), + resource.getName() + ), + SmithyNameResolver.shapeNamespace(service), + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + context.settings().getService().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(service) + ); + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + resource.getNamespace() + ), + DafnyNameResolver.dafnyTypesNamespace(resourceShape) + ); + writer.write( + """ + type %s struct { + Impl %s.I%s + } + """.formatted( + resource.getName(), + DafnyNameResolver.dafnyTypesNamespace(resourceShape), + resource.getName() + ) + ); - if (!service.toShapeId().getNamespace().equals(resource.getNamespace())) { + model + .expectShape(resource, ResourceShape.class) + .getOperations() + .forEach(operation -> { + final var operationShape = model.expectShape( + operation, + OperationShape.class + ); + final var inputShape = model.expectShape( + operationShape.getInputShape() + ); - continue; - } - writerDelegator.useFileWriter("%s/types.go".formatted(SmithyNameResolver.smithyTypesNamespace(service)), SmithyNameResolver.smithyTypesNamespace(service), writer -> { - writer.write(""" - type I$L interface { - ${C|} - } - """, resource.getName(), writer.consumer((w) -> { - model.expectShape(resource, ResourceShape.class).getOperations().forEach(operation -> { - var operationShape = model.expectShape(operation, OperationShape.class); - w.write(""" - $L($L) (*$L, error) - """, operationShape.getId().getName(), operationShape.getInputShape().getName(), operationShape.getOutputShape().getName()); - }); - })); - }); - - if (model.expectShape(resource, ResourceShape.class).hasTrait(ExtendableTrait.class)) { - generateNativeResourceWrapper(context, model.expectShape(resource, ResourceShape.class)); + final var outputShape = model.expectShape( + operationShape.getOutputShape() + ); + final var inputType = inputShape.hasTrait(UnitTypeTrait.class) + ? "" + : "params %s".formatted( + SmithyNameResolver.getSmithyType( + inputShape, + symbolProvider.toSymbol(inputShape) + ) + ); + final var outputType = outputShape.hasTrait(UnitTypeTrait.class) + ? "" + : "*%s".formatted( + SmithyNameResolver.getSmithyType( + outputShape, + symbolProvider.toSymbol(outputShape) + ) + ); + + String baseClientCall; + if (inputShape.hasTrait(UnitTypeTrait.class)) { + baseClientCall = + "var dafny_response = this.Impl.%s()".formatted( + operationShape.getId().getName() + ); + } else { + baseClientCall = + """ + var dafny_request %s = %s(params) + var dafny_response = this.Impl.%s(dafny_request) + """.formatted( + DafnyNameResolver.getDafnyType( + inputShape, + symbolProvider.toSymbol(inputShape) + ), + SmithyNameResolver.getToDafnyMethodName( + service, + inputShape, + "" + ), + operationShape.getId().getName() + ); } - writerDelegator.useFileWriter("%s/%s.go".formatted(SmithyNameResolver.shapeNamespace(service), resource.getName()), SmithyNameResolver.shapeNamespace(service), writer -> { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(context.settings().getService().getNamespace()), SmithyNameResolver.smithyTypesNamespace(service)); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(resource.getNamespace()), DafnyNameResolver.dafnyTypesNamespace(resourceShape)); - writer.write(""" - type %s struct { - Impl %s.I%s - } - """.formatted(resource.getName(), DafnyNameResolver.dafnyTypesNamespace(resourceShape), resource.getName())); - - model.expectShape(resource, ResourceShape.class).getOperations().forEach(operation -> { - final var operationShape = model.expectShape(operation, OperationShape.class); - final var inputShape = model.expectShape(operationShape.getInputShape()); - - - final var outputShape = model.expectShape(operationShape.getOutputShape()); - final var inputType = inputShape.hasTrait(UnitTypeTrait.class) ? "" : "params %s".formatted(SmithyNameResolver.getSmithyType(inputShape, symbolProvider.toSymbol(inputShape))); - final var outputType = outputShape.hasTrait(UnitTypeTrait.class) ? "" : "*%s".formatted(SmithyNameResolver.getSmithyType(outputShape, symbolProvider.toSymbol(outputShape))); - - String baseClientCall; - if (inputShape.hasTrait(UnitTypeTrait.class)) { - baseClientCall = "var dafny_response = this.Impl.%s()".formatted(operationShape.getId().getName()); - } else { - baseClientCall = """ - var dafny_request %s = %s(params) - var dafny_response = this.Impl.%s(dafny_request) - """.formatted(DafnyNameResolver.getDafnyType(inputShape, symbolProvider.toSymbol(inputShape)), - SmithyNameResolver.getToDafnyMethodName(service, inputShape, ""), operationShape.getId().getName()); - } - - String returnResponse, returnError; - if (outputShape.hasTrait(UnitTypeTrait.class)) { - returnResponse = "return nil"; - returnError = "return"; - } else { - returnResponse = """ - var native_response = %s(dafny_response.Extract().(%s)) - return &native_response, nil - """.formatted(SmithyNameResolver.getFromDafnyMethodName(service, outputShape, ""), - DafnyNameResolver.getDafnyType(inputShape, symbolProvider.toSymbol(outputShape))); - returnError = "return nil,"; - } - - - writer.write(""" - func (this *$L) $L($L) ($L, error) { - $L - if (dafny_response.Is_Failure()) { - err := dafny_response.Dtor_error().($L.Error); - ${C|} - if err.Is_CollectionOfErrors() { - $L CollectionOfErrors_Output_FromDafny(err) - } - if err.Is_Opaque() { - $L OpaqueError_Output_FromDafny(err) - } - } - $L - } - """, - resource.getName(), - operationShape.getId().getName(), - inputType, outputType, - baseClientCall, - DafnyNameResolver.dafnyTypesNamespace(service), - writer.consumer(w -> { - for (var errorShape : - model.getShapesWithTrait(ErrorTrait.class)) { - w.write(""" - if err.Is_$L() { - $L $L(err) - } - """, errorShape.toShapeId().getName(), returnError, SmithyNameResolver.getFromDafnyMethodName(service, errorShape, "")); - } - }), returnError, returnError, returnResponse - ); - }); - }); - } else { - //Generate Service - } - } - } - - void generateNativeResourceWrapper(GenerationContext context, ResourceShape resourceShape) { - writerDelegator.useFileWriter("%s/NativeWrapper.go".formatted(SmithyNameResolver.shapeNamespace(service)), SmithyNameResolver.shapeNamespace(service), writer -> { - writer.addImportFromModule(context.settings().getModuleName(), SmithyNameResolver.smithyTypesNamespace(service)); - writer.addImportFromModule("github.com/dafny-lang/DafnyStandardLibGo", "Wrappers"); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(resourceShape.toShapeId().getNamespace()), DafnyNameResolver.dafnyTypesNamespace(resourceShape)); - - writer.write(""" - type NativeWrapper struct { - %s.I%s - Impl %s.I%s - } - """.formatted(DafnyNameResolver.dafnyTypesNamespace(resourceShape), resourceShape.getId().getName(), SmithyNameResolver.smithyTypesNamespace(resourceShape), resourceShape.getId().getName())); - - resourceShape.getOperations().forEach(operation -> { - final var operationShape = model.expectShape(operation, OperationShape.class); - final var inputShape = model.expectShape(operationShape.getInputShape()); - final var outputShape = model.expectShape(operationShape.getOutputShape()); - final var inputType = inputShape.hasTrait(UnitTypeTrait.class) ? "" : "input %s".formatted(DafnyNameResolver.getDafnyType(resourceShape, symbolProvider.toSymbol(inputShape))); - final var outputType = outputShape.hasTrait(UnitTypeTrait.class) ? "" : "*%s,".formatted(SmithyNameResolver.getSmithyType(outputShape, symbolProvider.toSymbol(outputShape))); - - final var typeConversion = inputShape.hasTrait(UnitTypeTrait.class) ? "" : "var native_request = %s(input)".formatted(SmithyNameResolver.getFromDafnyMethodName(service, inputShape, "")); - final var clientCall = "this.Impl.%s(%s)".formatted(operationShape.getId().getName(), inputShape.hasTrait(UnitTypeTrait.class) ? "" : "native_request"); - String clientResponse, returnResponse; + String returnResponse, returnError; if (outputShape.hasTrait(UnitTypeTrait.class)) { - clientResponse = "var native_error"; - returnResponse = "dafny.TupleOf()"; - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + returnResponse = "return nil"; + returnError = "return"; } else { - clientResponse = "var native_response, native_error"; - returnResponse = "%s(*native_response)".formatted( - SmithyNameResolver.getToDafnyMethodName(service, outputShape, "")); + returnResponse = + """ + var native_response = %s(dafny_response.Extract().(%s)) + return &native_response, nil + """.formatted( + SmithyNameResolver.getFromDafnyMethodName( + service, + outputShape, + "" + ), + DafnyNameResolver.getDafnyType( + inputShape, + symbolProvider.toSymbol(outputShape) + ) + ); + returnError = "return nil,"; } - writer.write(""" - func (this *NativeWrapper) $L($L) Wrappers.Result { - $L - $L = $L - if native_error != nil { - switch native_error.(type) { - ${C|} - case $L.CollectionOfErrors: - return Wrappers.Companion_Result_.Create_Failure_(CollectionOfErrors_Input_ToDafny(native_error.($L.CollectionOfErrors))) - default: - return Wrappers.Companion_Result_.Create_Failure_(OpaqueError_Input_ToDafny(native_error.($L.OpaqueError))) - } + + writer.write( + """ + func (this *$L) $L($L) ($L, error) { + $L + if (dafny_response.Is_Failure()) { + err := dafny_response.Dtor_error().($L.Error); + ${C|} + if err.Is_CollectionOfErrors() { + $L CollectionOfErrors_Output_FromDafny(err) + } + if err.Is_Opaque() { + $L OpaqueError_Output_FromDafny(err) + } + } + $L + } + """, + resource.getName(), + operationShape.getId().getName(), + inputType, + outputType, + baseClientCall, + DafnyNameResolver.dafnyTypesNamespace(service), + writer.consumer(w -> { + for (var errorShape : model.getShapesWithTrait( + ErrorTrait.class + )) { + w.write( + """ + if err.Is_$L() { + $L $L(err) } - return Wrappers.Companion_Result_.Create_Success_($L) - } - """, - operationShape.getId().getName(), - inputType, typeConversion, clientResponse, clientCall, - writer.consumer(w -> resourceErrors(w)), SmithyNameResolver.smithyTypesNamespace(service), SmithyNameResolver.smithyTypesNamespace(service), SmithyNameResolver.smithyTypesNamespace(service), - returnResponse + """, + errorShape.toShapeId().getName(), + returnError, + SmithyNameResolver.getFromDafnyMethodName( + service, + errorShape, + "" + ) + ); + } + }), + returnError, + returnError, + returnResponse ); - }); - }); + }); + } + ); + } else { + //Generate Service + } } + } + + void generateNativeResourceWrapper( + GenerationContext context, + ResourceShape resourceShape + ) { + writerDelegator.useFileWriter( + "%s/NativeWrapper.go".formatted( + SmithyNameResolver.shapeNamespace(service) + ), + SmithyNameResolver.shapeNamespace(service), + writer -> { + writer.addImportFromModule( + context.settings().getModuleName(), + SmithyNameResolver.smithyTypesNamespace(service) + ); + writer.addImportFromModule( + "github.com/dafny-lang/DafnyStandardLibGo", + "Wrappers" + ); + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + resourceShape.toShapeId().getNamespace() + ), + DafnyNameResolver.dafnyTypesNamespace(resourceShape) + ); + + writer.write( + """ + type NativeWrapper struct { + %s.I%s + Impl %s.I%s + } + """.formatted( + DafnyNameResolver.dafnyTypesNamespace(resourceShape), + resourceShape.getId().getName(), + SmithyNameResolver.smithyTypesNamespace(resourceShape), + resourceShape.getId().getName() + ) + ); + + resourceShape + .getOperations() + .forEach(operation -> { + final var operationShape = model.expectShape( + operation, + OperationShape.class + ); + final var inputShape = model.expectShape( + operationShape.getInputShape() + ); + final var outputShape = model.expectShape( + operationShape.getOutputShape() + ); + final var inputType = inputShape.hasTrait(UnitTypeTrait.class) + ? "" + : "input %s".formatted( + DafnyNameResolver.getDafnyType( + resourceShape, + symbolProvider.toSymbol(inputShape) + ) + ); + final var outputType = outputShape.hasTrait(UnitTypeTrait.class) + ? "" + : "*%s,".formatted( + SmithyNameResolver.getSmithyType( + outputShape, + symbolProvider.toSymbol(outputShape) + ) + ); + + final var typeConversion = inputShape.hasTrait(UnitTypeTrait.class) + ? "" + : "var native_request = %s(input)".formatted( + SmithyNameResolver.getFromDafnyMethodName( + service, + inputShape, + "" + ) + ); + final var clientCall = + "this.Impl.%s(%s)".formatted( + operationShape.getId().getName(), + inputShape.hasTrait(UnitTypeTrait.class) + ? "" + : "native_request" + ); + String clientResponse, returnResponse; + if (outputShape.hasTrait(UnitTypeTrait.class)) { + clientResponse = "var native_error"; + returnResponse = "dafny.TupleOf()"; + writer.addImportFromModule( + "github.com/dafny-lang/DafnyRuntimeGo", + "dafny" + ); + } else { + clientResponse = "var native_response, native_error"; + returnResponse = + "%s(*native_response)".formatted( + SmithyNameResolver.getToDafnyMethodName( + service, + outputShape, + "" + ) + ); + } + writer.write( + """ + func (this *NativeWrapper) $L($L) Wrappers.Result { + $L + $L = $L + if native_error != nil { + switch native_error.(type) { + ${C|} + case $L.CollectionOfErrors: + return Wrappers.Companion_Result_.Create_Failure_(CollectionOfErrors_Input_ToDafny(native_error.($L.CollectionOfErrors))) + default: + return Wrappers.Companion_Result_.Create_Failure_(OpaqueError_Input_ToDafny(native_error.($L.OpaqueError))) + } + } + return Wrappers.Companion_Result_.Create_Success_($L) + } + """, + operationShape.getId().getName(), + inputType, + typeConversion, + clientResponse, + clientCall, + writer.consumer(w -> resourceErrors(w)), + SmithyNameResolver.smithyTypesNamespace(service), + SmithyNameResolver.smithyTypesNamespace(service), + SmithyNameResolver.smithyTypesNamespace(service), + returnResponse + ); + }); + } + ); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceTypeConversionProtocol.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceTypeConversionProtocol.java index 44e2371479..229b0a1d7b 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceTypeConversionProtocol.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceTypeConversionProtocol.java @@ -1,5 +1,8 @@ package software.amazon.polymorph.smithygo.localservice; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; import software.amazon.polymorph.smithygo.codegen.ApplicationProtocol; import software.amazon.polymorph.smithygo.codegen.GenerationContext; import software.amazon.polymorph.smithygo.codegen.GoDelegator; @@ -19,629 +22,1228 @@ import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.model.traits.UnitTypeTrait; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Set; +public class DafnyLocalServiceTypeConversionProtocol + implements ProtocolGenerator { -public class DafnyLocalServiceTypeConversionProtocol implements ProtocolGenerator { - public static String TO_DAFNY = "to_dafny.go"; - public static String TO_NATIVE = "to_native.go"; + public static String TO_DAFNY = "to_dafny.go"; + public static String TO_NATIVE = "to_native.go"; - @Override - public ShapeId getProtocol() { - return ShapeId.from("aws.polymorph#localService"); - } + @Override + public ShapeId getProtocol() { + return ShapeId.from("aws.polymorph#localService"); + } - @Override - public ApplicationProtocol getApplicationProtocol() { - return null; - } + @Override + public ApplicationProtocol getApplicationProtocol() { + return null; + } + + @Override + public void generateSerializers(GenerationContext context) { + final Set alreadyVisited = new HashSet<>(); + final var model = context.model(); + final var serviceShape = model.expectShape( + context.settings().getService(), + ServiceShape.class + ); + final var symbolProvider = context.symbolProvider(); + final var writerDelegator = context.writerDelegator(); + serviceShape + .getOperations() + .forEach(eachOperation -> { + final var operation = model.expectShape( + eachOperation, + OperationShape.class + ); + final var input = model.expectShape(operation.getInputShape()); + if (!alreadyVisited.contains(input.toShapeId())) { + alreadyVisited.add(input.toShapeId()); + if ( + !input.hasTrait(UnitTypeTrait.class) && + input + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + final var inputToDafnyMethodName = + SmithyNameResolver.getToDafnyMethodName(serviceShape, input, ""); + final var inputSymbol = symbolProvider.toSymbol(input); + writerDelegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + input.toShapeId().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(input) + ); + writer.write( + """ + func $L(nativeInput $L)($L) { + ${C|} + }""", + inputToDafnyMethodName, + SmithyNameResolver.getSmithyType(input, inputSymbol), + DafnyNameResolver.getDafnyType(input, inputSymbol), + writer.consumer(w -> + generateRequestSerializer( + context, + operation, + context.writerDelegator() + ) + ) + ); + } + ); + } + } + + final var output = model.expectShape(operation.getOutputShape()); + if (!alreadyVisited.contains(output.toShapeId())) { + alreadyVisited.add(output.toShapeId()); + if ( + !output.hasTrait(UnitTypeTrait.class) && + output + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + final var outputToDafnyMethodName = + SmithyNameResolver.getToDafnyMethodName(serviceShape, output, ""); + final var outputSymbol = symbolProvider.toSymbol(output); + writerDelegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + output.toShapeId().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(output) + ); + writer.write( + """ + func $L(nativeOutput $L)($L) { + ${C|} + }""", + outputToDafnyMethodName, + SmithyNameResolver.getSmithyType(output, outputSymbol), + DafnyNameResolver.getDafnyType(output, outputSymbol), + writer.consumer(w -> + generateResponseSerializer( + context, + operation, + context.writerDelegator() + ) + ) + ); + } + ); + } + } + }); - @Override - public void generateSerializers(GenerationContext context) { - final Set alreadyVisited = new HashSet<>(); - final var model = context.model(); - final var serviceShape = model.expectShape(context.settings().getService(), ServiceShape.class); - final var symbolProvider = context.symbolProvider(); - final var writerDelegator = context.writerDelegator(); - serviceShape.getOperations().forEach(eachOperation -> { - final var operation = model.expectShape(eachOperation, OperationShape.class); + final var refResources = context + .model() + .getShapesWithTrait(ReferenceTrait.class); + for (var refResource : refResources) { + final var resource = refResource + .expectTrait(ReferenceTrait.class) + .getReferentId(); + if (!refResource.expectTrait(ReferenceTrait.class).isService()) { + var resourceShape = model.expectShape(resource, ResourceShape.class); + resourceShape + .getOperations() + .forEach(eachOperation -> { + final var operation = model.expectShape( + eachOperation, + OperationShape.class + ); final var input = model.expectShape(operation.getInputShape()); if (!alreadyVisited.contains(input.toShapeId())) { - alreadyVisited.add(input.toShapeId()); - if (!input.hasTrait(UnitTypeTrait.class) && input.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - final var inputToDafnyMethodName = SmithyNameResolver.getToDafnyMethodName(serviceShape, input, ""); - final var inputSymbol = symbolProvider.toSymbol(input); - writerDelegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_DAFNY), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(input.toShapeId().getNamespace()), SmithyNameResolver.smithyTypesNamespace(input)); - writer.write(""" - func $L(nativeInput $L)($L) { - ${C|} - }""", inputToDafnyMethodName, SmithyNameResolver.getSmithyType(input, inputSymbol), - DafnyNameResolver.getDafnyType(input, inputSymbol), - writer.consumer(w -> generateRequestSerializer(context, operation, context.writerDelegator()))); - }); - } + alreadyVisited.add(input.toShapeId()); + if ( + !input.hasTrait(UnitTypeTrait.class) && + input + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + final var inputToDafnyMethodName = + SmithyNameResolver.getToDafnyMethodName( + serviceShape, + input, + "" + ); + final var inputSymbol = symbolProvider.toSymbol(input); + writerDelegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + input.toShapeId().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(input) + ); + writer.write( + """ + func $L(nativeInput $L)($L) { + ${C|} + }""", + inputToDafnyMethodName, + SmithyNameResolver.getSmithyType(input, inputSymbol), + DafnyNameResolver.getDafnyType(input, inputSymbol), + writer.consumer(w -> + generateRequestSerializer( + context, + operation, + context.writerDelegator() + ) + ) + ); + } + ); + } } final var output = model.expectShape(operation.getOutputShape()); if (!alreadyVisited.contains(output.toShapeId())) { - alreadyVisited.add(output.toShapeId()); - if (!output.hasTrait(UnitTypeTrait.class) && output.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - final var outputToDafnyMethodName = SmithyNameResolver.getToDafnyMethodName(serviceShape, output, ""); - final var outputSymbol = symbolProvider.toSymbol(output); - writerDelegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_DAFNY), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(output.toShapeId().getNamespace()), SmithyNameResolver.smithyTypesNamespace(output)); - writer.write(""" - func $L(nativeOutput $L)($L) { - ${C|} - }""", outputToDafnyMethodName, - SmithyNameResolver.getSmithyType(output, outputSymbol), - DafnyNameResolver.getDafnyType(output, outputSymbol), - writer.consumer(w -> generateResponseSerializer(context, operation, context.writerDelegator()))); - }); - } + alreadyVisited.add(output.toShapeId()); + if ( + !output.hasTrait(UnitTypeTrait.class) && + output + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + final var outputToDafnyMethodName = + SmithyNameResolver.getToDafnyMethodName( + serviceShape, + output, + "" + ); + final var outputSymbol = symbolProvider.toSymbol(output); + writerDelegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + output.toShapeId().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(output) + ); + writer.write( + """ + func $L(nativeOutput $L)($L) { + ${C|} + }""", + outputToDafnyMethodName, + SmithyNameResolver.getSmithyType(output, outputSymbol), + DafnyNameResolver.getDafnyType(output, outputSymbol), + writer.consumer(w -> + generateResponseSerializer( + context, + operation, + context.writerDelegator() + ) + ) + ); + } + ); + } } - }); - - final var refResources = context.model().getShapesWithTrait(ReferenceTrait.class); - for (var refResource : refResources) { - final var resource = refResource.expectTrait(ReferenceTrait.class).getReferentId(); - if (!refResource.expectTrait(ReferenceTrait.class).isService()) { - var resourceShape = model.expectShape(resource, ResourceShape.class); - resourceShape.getOperations().forEach(eachOperation -> { - final var operation = model.expectShape(eachOperation, OperationShape.class); - final var input = model.expectShape(operation.getInputShape()); - if (!alreadyVisited.contains(input.toShapeId())) { - alreadyVisited.add(input.toShapeId()); - if (!input.hasTrait(UnitTypeTrait.class) && input.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - final var inputToDafnyMethodName = SmithyNameResolver.getToDafnyMethodName(serviceShape, input, ""); - final var inputSymbol = symbolProvider.toSymbol(input); - writerDelegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_DAFNY), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(input.toShapeId().getNamespace()), SmithyNameResolver.smithyTypesNamespace(input)); - writer.write(""" - func $L(nativeInput $L)($L) { - ${C|} - }""", inputToDafnyMethodName, SmithyNameResolver.getSmithyType(input, inputSymbol), - DafnyNameResolver.getDafnyType(input, inputSymbol), - writer.consumer(w -> generateRequestSerializer(context, operation, context.writerDelegator()))); - }); - } - } - - final var output = model.expectShape(operation.getOutputShape()); - if (!alreadyVisited.contains(output.toShapeId())) { - alreadyVisited.add(output.toShapeId()); - if (!output.hasTrait(UnitTypeTrait.class) && output.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - final var outputToDafnyMethodName = SmithyNameResolver.getToDafnyMethodName(serviceShape, output, ""); - final var outputSymbol = symbolProvider.toSymbol(output); - writerDelegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_DAFNY), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(output.toShapeId().getNamespace()), SmithyNameResolver.smithyTypesNamespace(output)); - writer.write(""" - func $L(nativeOutput $L)($L) { - ${C|} - }""", outputToDafnyMethodName, SmithyNameResolver.getSmithyType(output, outputSymbol), - DafnyNameResolver.getDafnyType(output, outputSymbol), - writer.consumer(w -> generateResponseSerializer(context, operation, context.writerDelegator()))); - }); - } - } - if (!alreadyVisited.contains(resourceShape.toShapeId()) && resourceShape.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - alreadyVisited.add(resourceShape.toShapeId()); - writerDelegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_DAFNY), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - var goBody = """ - return nativeResource.(*%s).Impl - """.formatted(resourceShape.getId().getName()); - if (resourceShape.hasTrait(ExtendableTrait.class)) { - goBody = """ - val, ok := nativeResource.(*%s) - if ok { - return val.Impl - } - return %s{&NativeWrapper{Impl: nativeResource}}.Impl - """.formatted(resourceShape.getId().getName(), resourceShape.getId().getName()); - } - writer.write(""" - func $L_ToDafny(nativeResource $L.I$L) $L.I$L { - $L - } - """, resourceShape.getId().getName(), SmithyNameResolver.smithyTypesNamespace(resourceShape), resourceShape.getId().getName(), DafnyNameResolver.dafnyTypesNamespace(resourceShape), resourceShape.getId().getName(), goBody); - }); + if ( + !alreadyVisited.contains(resourceShape.toShapeId()) && + resourceShape + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + alreadyVisited.add(resourceShape.toShapeId()); + writerDelegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + var goBody = + """ + return nativeResource.(*%s).Impl + """.formatted(resourceShape.getId().getName()); + if (resourceShape.hasTrait(ExtendableTrait.class)) { + goBody = + """ + val, ok := nativeResource.(*%s) + if ok { + return val.Impl + } + return %s{&NativeWrapper{Impl: nativeResource}}.Impl + """.formatted( + resourceShape.getId().getName(), + resourceShape.getId().getName() + ); + } + writer.write( + """ + func $L_ToDafny(nativeResource $L.I$L) $L.I$L { + $L } - }); + """, + resourceShape.getId().getName(), + SmithyNameResolver.smithyTypesNamespace(resourceShape), + resourceShape.getId().getName(), + DafnyNameResolver.dafnyTypesNamespace(resourceShape), + resourceShape.getId().getName(), + goBody + ); + } + ); } - } - generateErrorSerializer(context); - if (serviceShape.hasTrait(LocalServiceTrait.class)) { - generateConfigSerializer(context); - } + }); + } + } + generateErrorSerializer(context); + if (serviceShape.hasTrait(LocalServiceTrait.class)) { + generateConfigSerializer(context); } + } - @Override - public void generateDeserializers(GenerationContext context) { - final Set alreadyVisited = new HashSet<>(); - final var serviceShape = context.settings().getService(context.model()); - final var delegator = context.writerDelegator(); + @Override + public void generateDeserializers(GenerationContext context) { + final Set alreadyVisited = new HashSet<>(); + final var serviceShape = context.settings().getService(context.model()); + final var delegator = context.writerDelegator(); - serviceShape.getOperations().forEach(eachOperation -> { - var operation = context.model().expectShape(eachOperation, OperationShape.class); + serviceShape + .getOperations() + .forEach(eachOperation -> { + var operation = context + .model() + .expectShape(eachOperation, OperationShape.class); - final var input = context.model().expectShape(operation.getInputShape()); + final var input = context + .model() + .expectShape(operation.getInputShape()); + if (!alreadyVisited.contains(input.toShapeId())) { + alreadyVisited.add(input.toShapeId()); + if ( + !input.hasTrait(UnitTypeTrait.class) && + input + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + final var inputFromDafnyMethodName = + SmithyNameResolver.getFromDafnyMethodName( + serviceShape, + input, + "" + ); + final var inputSymbol = context.symbolProvider().toSymbol(input); + delegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + input.toShapeId().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(input) + ); + + writer.write( + """ + func $L(dafnyInput $L)($L) { + ${C|} + }""", + inputFromDafnyMethodName, + DafnyNameResolver.getDafnyType(input, inputSymbol), + SmithyNameResolver.getSmithyType(input, inputSymbol), + writer.consumer(w -> + generateRequestDeserializer( + context, + operation, + context.writerDelegator() + ) + ) + ); + } + ); + } + } + + final var output = context + .model() + .expectShape(operation.getOutputShape()); + if (!alreadyVisited.contains(output.toShapeId())) { + alreadyVisited.add(output.toShapeId()); + if ( + !output.hasTrait(UnitTypeTrait.class) && + output + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + final var outputFromDafnyMethodName = + SmithyNameResolver.getFromDafnyMethodName( + serviceShape, + output, + "" + ); + final var outputSymbol = context.symbolProvider().toSymbol(output); + + delegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + output.toShapeId().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(output) + ); + + writer.write( + """ + func $L(dafnyOutput $L)($L) { + ${C|} + }""", + outputFromDafnyMethodName, + DafnyNameResolver.getDafnyType(output, outputSymbol), + SmithyNameResolver.getSmithyType(output, outputSymbol), + writer.consumer(w -> + generateResponseDeserializer( + context, + operation, + context.writerDelegator() + ) + ) + ); + } + ); + } + } + }); + + var refResources = context.model().getShapesWithTrait(ReferenceTrait.class); + for (var refResource : refResources) { + final var resource = refResource + .expectTrait(ReferenceTrait.class) + .getReferentId(); + + if (!refResource.expectTrait(ReferenceTrait.class).isService()) { + var resourceShape = context + .model() + .expectShape(resource, ResourceShape.class); + resourceShape + .getOperations() + .forEach(eachOperation -> { + final var operation = context + .model() + .expectShape(eachOperation, OperationShape.class); + final var input = context + .model() + .expectShape(operation.getInputShape()); if (!alreadyVisited.contains(input.toShapeId())) { - alreadyVisited.add(input.toShapeId()); - if (!input.hasTrait(UnitTypeTrait.class) && input.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - final var inputFromDafnyMethodName = SmithyNameResolver.getFromDafnyMethodName(serviceShape, input, ""); - final var inputSymbol = context.symbolProvider().toSymbol(input); - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_NATIVE), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(input.toShapeId().getNamespace()), SmithyNameResolver.smithyTypesNamespace(input)); - - writer.write(""" - func $L(dafnyInput $L)($L) { - ${C|} - }""", inputFromDafnyMethodName, DafnyNameResolver.getDafnyType(input, inputSymbol), - SmithyNameResolver.getSmithyType(input, inputSymbol), - writer.consumer(w -> generateRequestDeserializer(context, operation, context.writerDelegator()))); - }); - } + alreadyVisited.add(input.toShapeId()); + if ( + !input.hasTrait(UnitTypeTrait.class) && + input + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + final var inputFromDafnyMethodName = + SmithyNameResolver.getFromDafnyMethodName( + serviceShape, + input, + "" + ); + final var inputSymbol = context + .symbolProvider() + .toSymbol(input); + + delegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + input.toShapeId().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(input) + ); + + writer.write( + """ + func $L(dafnyInput $L)($L) { + ${C|} + }""", + inputFromDafnyMethodName, + DafnyNameResolver.getDafnyType(input, inputSymbol), + SmithyNameResolver.getSmithyType(input, inputSymbol), + writer.consumer(w -> + generateRequestDeserializer( + context, + operation, + context.writerDelegator() + ) + ) + ); + } + ); + } } - final var output = context.model().expectShape(operation.getOutputShape()); + final var output = context + .model() + .expectShape(operation.getOutputShape()); if (!alreadyVisited.contains(output.toShapeId())) { - alreadyVisited.add(output.toShapeId()); - if (!output.hasTrait(UnitTypeTrait.class) && output.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - final var outputFromDafnyMethodName = SmithyNameResolver.getFromDafnyMethodName(serviceShape, output, ""); - final var outputSymbol = context.symbolProvider().toSymbol(output); - - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_NATIVE), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(output.toShapeId().getNamespace()), SmithyNameResolver.smithyTypesNamespace(output)); - - writer.write(""" - func $L(dafnyOutput $L)($L) { - ${C|} - }""", outputFromDafnyMethodName, DafnyNameResolver.getDafnyType(output, outputSymbol), - SmithyNameResolver.getSmithyType(output, outputSymbol), - writer.consumer(w -> generateResponseDeserializer(context, operation, context.writerDelegator()))); - }); - } + alreadyVisited.add(output.toShapeId()); + if ( + !output.hasTrait(UnitTypeTrait.class) && + output + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + final var outputFromDafnyMethodName = + SmithyNameResolver.getFromDafnyMethodName( + serviceShape, + output, + "" + ); + final var outputSymbol = context + .symbolProvider() + .toSymbol(output); + + delegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + output.toShapeId().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(output) + ); + + writer.write( + """ + func $L(dafnyOutput $L)($L) { + ${C|} + }""", + outputFromDafnyMethodName, + DafnyNameResolver.getDafnyType(output, outputSymbol), + SmithyNameResolver.getSmithyType(output, outputSymbol), + writer.consumer(w -> + generateResponseDeserializer( + context, + operation, + context.writerDelegator() + ) + ) + ); + } + ); + } } - }); - - var refResources = context.model().getShapesWithTrait(ReferenceTrait.class); - for (var refResource : refResources) { - final var resource = refResource.expectTrait(ReferenceTrait.class).getReferentId(); - - if (!refResource.expectTrait(ReferenceTrait.class).isService()) { - var resourceShape = context.model().expectShape(resource, ResourceShape.class); - resourceShape.getOperations().forEach(eachOperation -> { - final var operation = context.model().expectShape(eachOperation, OperationShape.class); - final var input = context.model().expectShape(operation.getInputShape()); - if (!alreadyVisited.contains(input.toShapeId())) { - alreadyVisited.add(input.toShapeId()); - if (!input.hasTrait(UnitTypeTrait.class) && input.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - final var inputFromDafnyMethodName = SmithyNameResolver.getFromDafnyMethodName(serviceShape, input, ""); - final var inputSymbol = context.symbolProvider().toSymbol(input); - - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_NATIVE), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(input.toShapeId().getNamespace()), SmithyNameResolver.smithyTypesNamespace(input)); - - writer.write(""" - func $L(dafnyInput $L)($L) { - ${C|} - }""", inputFromDafnyMethodName, DafnyNameResolver.getDafnyType(input, inputSymbol), - SmithyNameResolver.getSmithyType(input, inputSymbol), - writer.consumer(w -> generateRequestDeserializer(context, operation, context.writerDelegator()))); - }); - } + if ( + !alreadyVisited.contains(resourceShape.toShapeId()) && + resourceShape + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + alreadyVisited.add(resourceShape.toShapeId()); + delegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + var extendableResourceWrapperCheck = ""; + if (resourceShape.hasTrait(ExtendableTrait.class)) { + extendableResourceWrapperCheck = + """ + val, ok := dafnyResource.(*NativeWrapper) + if ok { + return val.Impl + } + """; + } + writer.write( + """ + func $L_FromDafny(dafnyResource $L.I$L)($L.I$L) { + $L + return &$L{dafnyResource} } + """, + resourceShape.getId().getName(), + DafnyNameResolver.dafnyTypesNamespace(resourceShape), + resourceShape.getId().getName(), + SmithyNameResolver.smithyTypesNamespace(resourceShape), + resourceShape.getId().getName(), + extendableResourceWrapperCheck, + resourceShape.getId().getName() + ); + } + ); + } + }); + } + } + generateErrorDeserializer(context); + if (serviceShape.hasTrait(LocalServiceTrait.class)) { + generateConfigDeserializer(context); + } + } - final var output = context.model().expectShape(operation.getOutputShape()); - if (!alreadyVisited.contains(output.toShapeId())) { - alreadyVisited.add(output.toShapeId()); - if (!output.hasTrait(UnitTypeTrait.class) && output.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - final var outputFromDafnyMethodName = SmithyNameResolver.getFromDafnyMethodName(serviceShape, output, ""); - final var outputSymbol = context.symbolProvider().toSymbol(output); - - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_NATIVE), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(output.toShapeId().getNamespace()), SmithyNameResolver.smithyTypesNamespace(output)); - - writer.write(""" - func $L(dafnyOutput $L)($L) { - ${C|} - }""", outputFromDafnyMethodName, DafnyNameResolver.getDafnyType(output, outputSymbol), - SmithyNameResolver.getSmithyType(output, outputSymbol), - writer.consumer(w -> generateResponseDeserializer(context, operation, context.writerDelegator()))); - }); - } - } - if (!alreadyVisited.contains(resourceShape.toShapeId()) && resourceShape.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - alreadyVisited.add(resourceShape.toShapeId()); - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_NATIVE), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - var extendableResourceWrapperCheck = ""; - if (resourceShape.hasTrait(ExtendableTrait.class)) { - extendableResourceWrapperCheck = """ - val, ok := dafnyResource.(*NativeWrapper) - if ok { - return val.Impl - } - """; - } - writer.write(""" - func $L_FromDafny(dafnyResource $L.I$L)($L.I$L) { - $L - return &$L{dafnyResource} - } - """, resourceShape.getId().getName(), DafnyNameResolver.dafnyTypesNamespace(resourceShape), resourceShape.getId().getName(), SmithyNameResolver.smithyTypesNamespace(resourceShape), resourceShape.getId().getName(), extendableResourceWrapperCheck, resourceShape.getId().getName()); - }); - } + private void generateRequestSerializer( + final GenerationContext context, + final OperationShape operation, + final GoDelegator delegator + ) { + final var targetShape = context + .model() + .expectShape(operation.getInputShape()); + delegator.useFileWriter( + "%s/%s".formatted(SmithyNameResolver.shapeNamespace(operation), TO_DAFNY), + SmithyNameResolver.shapeNamespace(operation), + writer -> { + final var input = targetShape.accept( + new SmithyToDafnyShapeVisitor( + context, + "nativeInput", + writer, + false, + false, + false + ) + ); + writer.write( + """ + return $L + """, + input + ); + } + ); + } - }); - } - } - generateErrorDeserializer(context); - if (serviceShape.hasTrait(LocalServiceTrait.class)) { - generateConfigDeserializer(context); - } + private void generateResponseSerializer( + final GenerationContext context, + final OperationShape operation, + final GoDelegator delegator + ) { + final var targetShape = context + .model() + .expectShape(operation.getOutputShape()); + delegator.useFileWriter( + "%s/%s".formatted(SmithyNameResolver.shapeNamespace(operation), TO_DAFNY), + SmithyNameResolver.shapeNamespace(operation), + writer -> { + final var input = targetShape.accept( + new SmithyToDafnyShapeVisitor( + context, + "nativeOutput", + writer, + false, + false, + false + ) + ); + writer.write( + """ + return $L + """, + input + ); + } + ); + } - } + private void generateRequestDeserializer( + final GenerationContext context, + final OperationShape operation, + final GoDelegator delegator + ) { + delegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(operation), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(operation), + writer -> { + final var inputShape = operation.getInputShape(); - private void generateRequestSerializer( - final GenerationContext context, - final OperationShape operation, - final GoDelegator delegator - ) { - final var targetShape = context.model().expectShape(operation.getInputShape()); - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(operation), TO_DAFNY), SmithyNameResolver.shapeNamespace(operation), writer -> { - final var input = targetShape.accept(new SmithyToDafnyShapeVisitor( - context, - "nativeInput", - writer, - false, false, false - )); - writer.write(""" - return $L - """, - input); - } + final var targetShape = context.model().expectShape(inputShape); + final var input = targetShape.accept( + new DafnyToSmithyShapeVisitor(context, "dafnyInput", writer, false) ); - } - private void generateResponseSerializer( - final GenerationContext context, - final OperationShape operation, - final GoDelegator delegator - ) { - final var targetShape = context.model().expectShape(operation.getOutputShape()); - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(operation), TO_DAFNY), SmithyNameResolver.shapeNamespace(operation), writer -> { - final var input = targetShape.accept(new SmithyToDafnyShapeVisitor( - context, - "nativeOutput", - writer, - false, false, false - )); - writer.write(""" - return $L - """, - input); - } + writer.write( + """ + return $L + """, + input ); - } + } + ); + } - private void generateRequestDeserializer( - final GenerationContext context, - final OperationShape operation, - final GoDelegator delegator - ) { - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(operation), TO_NATIVE), SmithyNameResolver.shapeNamespace(operation), writer -> { - - final var inputShape = operation.getInputShape(); - - final var targetShape = context.model().expectShape(inputShape); - final var input = targetShape.accept(new DafnyToSmithyShapeVisitor( - context, - "dafnyInput", - writer, - false - )); - - writer.write(""" - return $L - """, input); - }); - } + private void generateResponseDeserializer( + final GenerationContext context, + final OperationShape operation, + final GoDelegator delegator + ) { + delegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(operation), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(operation), + writer -> { + final var outputShape = operation.getOutputShape(); - private void generateResponseDeserializer( - final GenerationContext context, - final OperationShape operation, - final GoDelegator delegator - ) { - delegator.useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(operation), TO_NATIVE), SmithyNameResolver.shapeNamespace(operation), writer -> { - - final var outputShape = operation.getOutputShape(); - - final var targetShape = context.model().expectShape(outputShape); - final var output = targetShape.accept(new DafnyToSmithyShapeVisitor( - context, - "dafnyOutput", - writer, - false - )); - - writer.write(""" - return $L - """, output); - }); - } + final var targetShape = context.model().expectShape(outputShape); + final var output = targetShape.accept( + new DafnyToSmithyShapeVisitor(context, "dafnyOutput", writer, false) + ); - private void generateConfigSerializer(final GenerationContext context) { - final var service = context.settings().getService(context.model()); - final var localServiceTrait = service.expectTrait(LocalServiceTrait.class); - final var configShape = context.model().expectShape(localServiceTrait.getConfigId(), StructureShape.class); - final var getInputToDafnyMethodName = SmithyNameResolver.getToDafnyMethodName(service, configShape, ""); - - context.writerDelegator().useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(service), TO_DAFNY), SmithyNameResolver.shapeNamespace(service), writer -> { - writer.write(""" - func $L(nativeInput $L)($L) { - ${C|} - }""", - getInputToDafnyMethodName, SmithyNameResolver.getSmithyType(configShape, context.symbolProvider().toSymbol(configShape)), DafnyNameResolver.getDafnyType(configShape, context.symbolProvider().toSymbol(configShape)), - writer.consumer(w -> { - String output = configShape.accept(new SmithyToDafnyShapeVisitor( - context, - "nativeInput", - writer, - true, false, false - )); - writer.write(""" - return $L - """, output); - })); - }); - } + writer.write( + """ + return $L + """, + output + ); + } + ); + } - private void generateErrorSerializer(final GenerationContext context) { - final Set alreadyVisited = new HashSet<>(); - final var serviceShape = context.settings().getService(context.model()); - final var errorShapes = context.model().getShapesWithTrait(ErrorTrait.class); + private void generateConfigSerializer(final GenerationContext context) { + final var service = context.settings().getService(context.model()); + final var localServiceTrait = service.expectTrait(LocalServiceTrait.class); + final var configShape = context + .model() + .expectShape(localServiceTrait.getConfigId(), StructureShape.class); + final var getInputToDafnyMethodName = + SmithyNameResolver.getToDafnyMethodName(service, configShape, ""); - for (final var errorShape : - errorShapes) { - if (!errorShape.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - continue; - } - if (!alreadyVisited.contains(errorShape.toShapeId())) { - alreadyVisited.add(errorShape.toShapeId()); - final var getInputToDafnyMethodName = SmithyNameResolver.getToDafnyMethodName(serviceShape, errorShape, ""); - - context.writerDelegator().useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(errorShape), TO_DAFNY), SmithyNameResolver.shapeNamespace(errorShape), writer -> { - writer.write(""" - func $L(nativeInput $L)($L) { - ${C|} - }""", - getInputToDafnyMethodName, SmithyNameResolver.getSmithyType(errorShape, context.symbolProvider().toSymbol(errorShape)), DafnyNameResolver.getDafnyBaseErrorType(errorShape), - writer.consumer(w -> { - String output = errorShape.accept(new SmithyToDafnyShapeVisitor( - context, - "nativeInput", - writer, - false, false, false - )); - writer.write(""" - return $L - """, output); - })); - }); - } + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted(SmithyNameResolver.shapeNamespace(service), TO_DAFNY), + SmithyNameResolver.shapeNamespace(service), + writer -> { + writer.write( + """ + func $L(nativeInput $L)($L) { + ${C|} + }""", + getInputToDafnyMethodName, + SmithyNameResolver.getSmithyType( + configShape, + context.symbolProvider().toSymbol(configShape) + ), + DafnyNameResolver.getDafnyType( + configShape, + context.symbolProvider().toSymbol(configShape) + ), + writer.consumer(w -> { + String output = configShape.accept( + new SmithyToDafnyShapeVisitor( + context, + "nativeInput", + writer, + true, + false, + false + ) + ); + writer.write( + """ + return $L + """, + output + ); + }) + ); } + ); + } - context.writerDelegator().useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_DAFNY), SmithyNameResolver.shapeNamespace(serviceShape), writer -> { - writer.write(""" - func CollectionOfErrors_Input_ToDafny(nativeInput $L.CollectionOfErrors)($L.Error) { - var e []interface{} - for _, i2 := range nativeInput.ListOfErrors { - e = append(e, Error_ToDafny(i2)) - } - return $L.Companion_Error_.Create_CollectionOfErrors_(dafny.SeqOf(e...), dafny.SeqOfChars([]dafny.Char(nativeInput.Message)...)) - } - func OpaqueError_Input_ToDafny(nativeInput $L.OpaqueError)($L.Error) { - return $L.Companion_Error_.Create_Opaque_(nativeInput.ErrObject) - }""", SmithyNameResolver.smithyTypesNamespace(serviceShape), - DafnyNameResolver.dafnyTypesNamespace(serviceShape), - DafnyNameResolver.dafnyTypesNamespace(serviceShape), SmithyNameResolver.smithyTypesNamespace(serviceShape), DafnyNameResolver.dafnyTypesNamespace(serviceShape), DafnyNameResolver.dafnyTypesNamespace(serviceShape)); - }); - - - context.writerDelegator() - .useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(context.settings().getService(context.model())), TO_DAFNY), - SmithyNameResolver.shapeNamespace(context.settings().getService(context.model())), writer -> { - writer.write(""" - func Error_ToDafny(err error)($L.Error) { - switch err.(type) { - // Service Errors - ${C|} - //DependentErrors - ${C|} - - //Unmodelled Errors - case $L.CollectionOfErrors: - return CollectionOfErrors_Input_ToDafny(err.($L.CollectionOfErrors)) - - default: - error, ok := err.($L.OpaqueError) - if !ok { - panic("Error is not an OpaqueError") - } - return OpaqueError_Input_ToDafny(error) - } - } - """, DafnyNameResolver.dafnyTypesNamespace(serviceShape), - writer.consumer(w -> { - for (var error : serviceShape.getErrors()) { - w.write(""" - case $L: - return $L(err.($L)) - """, SmithyNameResolver.getSmithyType(context.model().expectShape(error), context.symbolProvider().toSymbol(context.model().expectShape(error))), - SmithyNameResolver.getToDafnyMethodName(serviceShape, context.model().expectShape(error), ""), - SmithyNameResolver.getSmithyType(context.model().expectShape(error), context.symbolProvider().toSymbol(context.model().expectShape(error)))); - } - }), - writer.consumer(w -> { - var dependencies = serviceShape.hasTrait(LocalServiceTrait.class) ? serviceShape.expectTrait(LocalServiceTrait.class).getDependencies() : new LinkedList(); - if (dependencies != null) { - for (var dep : dependencies) { - var depShape = context.model().expectShape(dep); - w.write(""" - case $L.$LBaseException: - return $L.Create_$L_($L.Error_ToDafny(err)) - """, SmithyNameResolver.smithyTypesNamespace(depShape), dep.getName(), DafnyNameResolver.getDafnyErrorCompanion(serviceShape), dep.getName(), SmithyNameResolver.shapeNamespace(depShape)); - } - } - }), - SmithyNameResolver.smithyTypesNamespace(serviceShape), SmithyNameResolver.smithyTypesNamespace(serviceShape), SmithyNameResolver.smithyTypesNamespace(serviceShape) - ); - }); - } + private void generateErrorSerializer(final GenerationContext context) { + final Set alreadyVisited = new HashSet<>(); + final var serviceShape = context.settings().getService(context.model()); + final var errorShapes = context + .model() + .getShapesWithTrait(ErrorTrait.class); + + for (final var errorShape : errorShapes) { + if ( + !errorShape + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + continue; + } + if (!alreadyVisited.contains(errorShape.toShapeId())) { + alreadyVisited.add(errorShape.toShapeId()); + final var getInputToDafnyMethodName = + SmithyNameResolver.getToDafnyMethodName(serviceShape, errorShape, ""); - private void generateConfigDeserializer(final GenerationContext context) { - final var serviceShape = context.settings().getService(context.model()); - final var localServiceTrait = serviceShape.expectTrait(LocalServiceTrait.class); - final var configShape = context.model().expectShape(localServiceTrait.getConfigId(), StructureShape.class); - final var getOutputFromDafnyMethodName = SmithyNameResolver.getFromDafnyMethodName(serviceShape, configShape, ""); - - context.writerDelegator().useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(serviceShape), TO_NATIVE), SmithyNameResolver.shapeNamespace(configShape), writer -> { - writer.write(""" - func $L(dafnyOutput $L)($L) { - ${C|} - }""", - getOutputFromDafnyMethodName, DafnyNameResolver.getDafnyType(configShape, context.symbolProvider().toSymbol(configShape)), SmithyNameResolver.getSmithyType(configShape, context.symbolProvider().toSymbol(configShape)), - writer.consumer(w -> { - String output = configShape.accept(new DafnyToSmithyShapeVisitor( - context, - "dafnyOutput", - writer, - true - )); - writer.write(""" - return $L - """, output); - })); - }); + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(errorShape), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace(errorShape), + writer -> { + writer.write( + """ + func $L(nativeInput $L)($L) { + ${C|} + }""", + getInputToDafnyMethodName, + SmithyNameResolver.getSmithyType( + errorShape, + context.symbolProvider().toSymbol(errorShape) + ), + DafnyNameResolver.getDafnyBaseErrorType(errorShape), + writer.consumer(w -> { + String output = errorShape.accept( + new SmithyToDafnyShapeVisitor( + context, + "nativeInput", + writer, + false, + false, + false + ) + ); + writer.write( + """ + return $L + """, + output + ); + }) + ); + } + ); + } } - private void generateErrorDeserializer(final GenerationContext context) { - final Set alreadyVisited = new HashSet<>(); - final var serviceShape = context.settings().getService(context.model()); - final var errorShapes = context.model().getShapesWithTrait(ErrorTrait.class); - for (final var errorShape : - errorShapes) { - if (!errorShape.toShapeId().getNamespace().equals(serviceShape.toShapeId().getNamespace())) { - continue; + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.write( + """ + func CollectionOfErrors_Input_ToDafny(nativeInput $L.CollectionOfErrors)($L.Error) { + var e []interface{} + for _, i2 := range nativeInput.ListOfErrors { + e = append(e, Error_ToDafny(i2)) + } + return $L.Companion_Error_.Create_CollectionOfErrors_(dafny.SeqOf(e...), dafny.SeqOfChars([]dafny.Char(nativeInput.Message)...)) } - if (!alreadyVisited.contains(errorShape.toShapeId())) { - alreadyVisited.add(errorShape.toShapeId()); - final var getOutputFromDafnyMethodName = SmithyNameResolver.getFromDafnyMethodName(serviceShape, errorShape, ""); - context.writerDelegator().useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(errorShape), TO_NATIVE), SmithyNameResolver.shapeNamespace(errorShape), writer -> { - writer.write(""" - func $L(dafnyOutput $L)($L) { - ${C|} - }""", - getOutputFromDafnyMethodName, DafnyNameResolver.getDafnyBaseErrorType(errorShape), SmithyNameResolver.getSmithyType(errorShape, context.symbolProvider().toSymbol(errorShape)), - writer.consumer(w -> { - String output = errorShape.accept(new DafnyToSmithyShapeVisitor( - context, - "dafnyOutput", - writer, - false - )); - writer.write(""" - return $L - """, output); - })); - }); + func OpaqueError_Input_ToDafny(nativeInput $L.OpaqueError)($L.Error) { + return $L.Companion_Error_.Create_Opaque_(nativeInput.ErrObject) + }""", + SmithyNameResolver.smithyTypesNamespace(serviceShape), + DafnyNameResolver.dafnyTypesNamespace(serviceShape), + DafnyNameResolver.dafnyTypesNamespace(serviceShape), + SmithyNameResolver.smithyTypesNamespace(serviceShape), + DafnyNameResolver.dafnyTypesNamespace(serviceShape), + DafnyNameResolver.dafnyTypesNamespace(serviceShape) + ); + } + ); + + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace( + context.settings().getService(context.model()) + ), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace( + context.settings().getService(context.model()) + ), + writer -> { + writer.write( + """ + func Error_ToDafny(err error)($L.Error) { + switch err.(type) { + // Service Errors + ${C|} + //DependentErrors + ${C|} + + //Unmodelled Errors + case $L.CollectionOfErrors: + return CollectionOfErrors_Input_ToDafny(err.($L.CollectionOfErrors)) + + default: + error, ok := err.($L.OpaqueError) + if !ok { + panic("Error is not an OpaqueError") + } + return OpaqueError_Input_ToDafny(error) + } } + """, + DafnyNameResolver.dafnyTypesNamespace(serviceShape), + writer.consumer(w -> { + for (var error : serviceShape.getErrors()) { + w.write( + """ + case $L: + return $L(err.($L)) + """, + SmithyNameResolver.getSmithyType( + context.model().expectShape(error), + context + .symbolProvider() + .toSymbol(context.model().expectShape(error)) + ), + SmithyNameResolver.getToDafnyMethodName( + serviceShape, + context.model().expectShape(error), + "" + ), + SmithyNameResolver.getSmithyType( + context.model().expectShape(error), + context + .symbolProvider() + .toSymbol(context.model().expectShape(error)) + ) + ); + } + }), + writer.consumer(w -> { + var dependencies = serviceShape.hasTrait(LocalServiceTrait.class) + ? serviceShape + .expectTrait(LocalServiceTrait.class) + .getDependencies() + : new LinkedList(); + if (dependencies != null) { + for (var dep : dependencies) { + var depShape = context.model().expectShape(dep); + w.write( + """ + case $L.$LBaseException: + return $L.Create_$L_($L.Error_ToDafny(err)) + """, + SmithyNameResolver.smithyTypesNamespace(depShape), + dep.getName(), + DafnyNameResolver.getDafnyErrorCompanion(serviceShape), + dep.getName(), + SmithyNameResolver.shapeNamespace(depShape) + ); + } + } + }), + SmithyNameResolver.smithyTypesNamespace(serviceShape), + SmithyNameResolver.smithyTypesNamespace(serviceShape), + SmithyNameResolver.smithyTypesNamespace(serviceShape) + ); + } + ); + } + + private void generateConfigDeserializer(final GenerationContext context) { + final var serviceShape = context.settings().getService(context.model()); + final var localServiceTrait = serviceShape.expectTrait( + LocalServiceTrait.class + ); + final var configShape = context + .model() + .expectShape(localServiceTrait.getConfigId(), StructureShape.class); + final var getOutputFromDafnyMethodName = + SmithyNameResolver.getFromDafnyMethodName(serviceShape, configShape, ""); + + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(configShape), + writer -> { + writer.write( + """ + func $L(dafnyOutput $L)($L) { + ${C|} + }""", + getOutputFromDafnyMethodName, + DafnyNameResolver.getDafnyType( + configShape, + context.symbolProvider().toSymbol(configShape) + ), + SmithyNameResolver.getSmithyType( + configShape, + context.symbolProvider().toSymbol(configShape) + ), + writer.consumer(w -> { + String output = configShape.accept( + new DafnyToSmithyShapeVisitor( + context, + "dafnyOutput", + writer, + true + ) + ); + writer.write( + """ + return $L + """, + output + ); + }) + ); } + ); + } - context.writerDelegator().useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(context.settings().getService(context.model())), TO_NATIVE), SmithyNameResolver.shapeNamespace(context.settings().getService(context.model())), writer -> { - writer.write(""" - func CollectionOfErrors_Output_FromDafny(dafnyOutput $L.Error)($L.CollectionOfErrors) { - listOfErrors := dafnyOutput.Dtor_list() - message := dafnyOutput.Dtor_message() - t := $L.CollectionOfErrors {} - for i := dafny.Iterate(listOfErrors) ; ; { - val, ok := i() - if !ok { - break; - } - err := val.($L.Error) - t.ListOfErrors = append(t.ListOfErrors, Error_FromDafny(err)) - - } - t.Message = func() (string) { - var s string - for i := dafny.Iterate(message) ; ; { - val, ok := i() - if !ok { - return s - } else { - s = s + string(val.(dafny.Char)) - } - } - }() - return t - } - func OpaqueError_Output_FromDafny(dafnyOutput $L.Error)($L.OpaqueError) { - return $L.OpaqueError { - ErrObject: dafnyOutput.Dtor_obj(), - } - }""", DafnyNameResolver.dafnyTypesNamespace(serviceShape), SmithyNameResolver.smithyTypesNamespace(serviceShape), - SmithyNameResolver.smithyTypesNamespace(serviceShape), - DafnyNameResolver.dafnyTypesNamespace(serviceShape), DafnyNameResolver.dafnyTypesNamespace(serviceShape), - SmithyNameResolver.smithyTypesNamespace(serviceShape), - SmithyNameResolver.smithyTypesNamespace(serviceShape)); - }); - - context.writerDelegator() - .useFileWriter("%s/%s".formatted(SmithyNameResolver.shapeNamespace(context.settings().getService(context.model())), TO_NATIVE), - SmithyNameResolver.shapeNamespace(context.settings().getService(context.model())), writer -> { - writer.write(""" - func Error_FromDafny(err $L.Error)(error) { - // Service Errors - ${C|} - - //DependentErrors - ${C|} - - //Unmodelled Errors - if err.Is_CollectionOfErrors() { - return CollectionOfErrors_Output_FromDafny(err) - } - - return OpaqueError_Output_FromDafny(err) - } - """, DafnyNameResolver.dafnyTypesNamespace(serviceShape), - writer.consumer(w -> { - for (var error : serviceShape.getErrors()) { - w.write(""" - if err.Is_$L() { - return $L(err) - } - """, error.getName(), SmithyNameResolver.getFromDafnyMethodName(serviceShape, context.model().expectShape(error), "")); - } - }), - writer.consumer(w -> { - var dependencies = serviceShape.hasTrait(LocalServiceTrait.class) ? serviceShape.expectTrait(LocalServiceTrait.class).getDependencies() : null; - if (dependencies == null) { - return; - } - for (var dep : dependencies) { - var depService = context.model().expectShape(dep, ServiceShape.class); - w.write(""" - if err.Is_$L() { - return $L.Error_FromDafny(err.Dtor_$L()) - } - """, depService.expectTrait(LocalServiceTrait.class).getSdkId(), SmithyNameResolver.shapeNamespace(depService), depService.expectTrait(LocalServiceTrait.class).getSdkId()); - } - }) - ); - }); + private void generateErrorDeserializer(final GenerationContext context) { + final Set alreadyVisited = new HashSet<>(); + final var serviceShape = context.settings().getService(context.model()); + final var errorShapes = context + .model() + .getShapesWithTrait(ErrorTrait.class); + for (final var errorShape : errorShapes) { + if ( + !errorShape + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + continue; + } + if (!alreadyVisited.contains(errorShape.toShapeId())) { + alreadyVisited.add(errorShape.toShapeId()); + final var getOutputFromDafnyMethodName = + SmithyNameResolver.getFromDafnyMethodName( + serviceShape, + errorShape, + "" + ); + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(errorShape), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(errorShape), + writer -> { + writer.write( + """ + func $L(dafnyOutput $L)($L) { + ${C|} + }""", + getOutputFromDafnyMethodName, + DafnyNameResolver.getDafnyBaseErrorType(errorShape), + SmithyNameResolver.getSmithyType( + errorShape, + context.symbolProvider().toSymbol(errorShape) + ), + writer.consumer(w -> { + String output = errorShape.accept( + new DafnyToSmithyShapeVisitor( + context, + "dafnyOutput", + writer, + false + ) + ); + writer.write( + """ + return $L + """, + output + ); + }) + ); + } + ); + } } + + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace( + context.settings().getService(context.model()) + ), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace( + context.settings().getService(context.model()) + ), + writer -> { + writer.write( + """ + func CollectionOfErrors_Output_FromDafny(dafnyOutput $L.Error)($L.CollectionOfErrors) { + listOfErrors := dafnyOutput.Dtor_list() + message := dafnyOutput.Dtor_message() + t := $L.CollectionOfErrors {} + for i := dafny.Iterate(listOfErrors) ; ; { + val, ok := i() + if !ok { + break; + } + err := val.($L.Error) + t.ListOfErrors = append(t.ListOfErrors, Error_FromDafny(err)) + + } + t.Message = func() (string) { + var s string + for i := dafny.Iterate(message) ; ; { + val, ok := i() + if !ok { + return s + } else { + s = s + string(val.(dafny.Char)) + } + } + }() + return t + } + func OpaqueError_Output_FromDafny(dafnyOutput $L.Error)($L.OpaqueError) { + return $L.OpaqueError { + ErrObject: dafnyOutput.Dtor_obj(), + } + }""", + DafnyNameResolver.dafnyTypesNamespace(serviceShape), + SmithyNameResolver.smithyTypesNamespace(serviceShape), + SmithyNameResolver.smithyTypesNamespace(serviceShape), + DafnyNameResolver.dafnyTypesNamespace(serviceShape), + DafnyNameResolver.dafnyTypesNamespace(serviceShape), + SmithyNameResolver.smithyTypesNamespace(serviceShape), + SmithyNameResolver.smithyTypesNamespace(serviceShape) + ); + } + ); + + context + .writerDelegator() + .useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace( + context.settings().getService(context.model()) + ), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace( + context.settings().getService(context.model()) + ), + writer -> { + writer.write( + """ + func Error_FromDafny(err $L.Error)(error) { + // Service Errors + ${C|} + + //DependentErrors + ${C|} + + //Unmodelled Errors + if err.Is_CollectionOfErrors() { + return CollectionOfErrors_Output_FromDafny(err) + } + + return OpaqueError_Output_FromDafny(err) + } + """, + DafnyNameResolver.dafnyTypesNamespace(serviceShape), + writer.consumer(w -> { + for (var error : serviceShape.getErrors()) { + w.write( + """ + if err.Is_$L() { + return $L(err) + } + """, + error.getName(), + SmithyNameResolver.getFromDafnyMethodName( + serviceShape, + context.model().expectShape(error), + "" + ) + ); + } + }), + writer.consumer(w -> { + var dependencies = serviceShape.hasTrait(LocalServiceTrait.class) + ? serviceShape + .expectTrait(LocalServiceTrait.class) + .getDependencies() + : null; + if (dependencies == null) { + return; + } + for (var dep : dependencies) { + var depService = context + .model() + .expectShape(dep, ServiceShape.class); + w.write( + """ + if err.Is_$L() { + return $L.Error_FromDafny(err.Dtor_$L()) + } + """, + depService.expectTrait(LocalServiceTrait.class).getSdkId(), + SmithyNameResolver.shapeNamespace(depService), + depService.expectTrait(LocalServiceTrait.class).getSdkId() + ); + } + }) + ); + } + ); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/nameresolver/Constants.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/nameresolver/Constants.java index e443603dff..2d1e961fc6 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/nameresolver/Constants.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/nameresolver/Constants.java @@ -1,8 +1,9 @@ package software.amazon.polymorph.smithygo.localservice.nameresolver; public class Constants { - public static final String DOT = "."; - public static final String BLANK = ""; - public static final String INTERNAL_DAFNY_TYPES = "internaldafnytypes"; - public static final String INTERNAL_DAFNY = "internaldafny"; + + public static final String DOT = "."; + public static final String BLANK = ""; + public static final String INTERNAL_DAFNY_TYPES = "internaldafnytypes"; + public static final String INTERNAL_DAFNY = "internaldafny"; } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/nameresolver/DafnyNameResolver.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/nameresolver/DafnyNameResolver.java index b671fe228b..dab3528f0a 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/nameresolver/DafnyNameResolver.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/nameresolver/DafnyNameResolver.java @@ -1,153 +1,199 @@ package software.amazon.polymorph.smithygo.localservice.nameresolver; +import static software.amazon.polymorph.smithygo.localservice.nameresolver.Constants.BLANK; +import static software.amazon.polymorph.smithygo.localservice.nameresolver.Constants.DOT; +import static software.amazon.polymorph.smithygo.localservice.nameresolver.Constants.INTERNAL_DAFNY; +import static software.amazon.polymorph.smithygo.localservice.nameresolver.Constants.INTERNAL_DAFNY_TYPES; + import software.amazon.smithy.aws.traits.ServiceTrait; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; -import software.amazon.smithy.model.traits.SensitiveTrait; - import software.amazon.smithy.model.shapes.ShapeType; import software.amazon.smithy.model.shapes.UnionShape; import software.amazon.smithy.model.traits.EnumTrait; - -import static software.amazon.polymorph.smithygo.localservice.nameresolver.Constants.BLANK; -import static software.amazon.polymorph.smithygo.localservice.nameresolver.Constants.DOT; -import static software.amazon.polymorph.smithygo.localservice.nameresolver.Constants.INTERNAL_DAFNY; -import static software.amazon.polymorph.smithygo.localservice.nameresolver.Constants.INTERNAL_DAFNY_TYPES; +import software.amazon.smithy.model.traits.SensitiveTrait; public class DafnyNameResolver { - public static String dafnyTypesNamespace(final Shape shape) { - return shape.toShapeId().getNamespace() - .replace(DOT, BLANK).toLowerCase() - .concat(INTERNAL_DAFNY_TYPES); - } - - public static String dafnyNamespace(final Shape shape) { - return shape.toShapeId().getNamespace() - .replace(DOT, BLANK).toLowerCase() - .concat(INTERNAL_DAFNY); - } - - /** - * Returns the Dafny type for a given Shape. - * - * @param shape The Shape for which the Dafny type needs to be determined. - * @param symbol The Symbol representing the Shape. - * @return The Dafny type as a String. - */ - public static String getDafnyType(final Shape shape, final Symbol symbol) { - ShapeType type = shape.getType(); - if (shape.hasTrait(EnumTrait.class)) { - type = ShapeType.ENUM; - } - switch (type) { - case INTEGER, LONG, BOOLEAN: - return symbol.getName(); - case MAP: - return "dafny.Map"; - case DOUBLE, STRING, BLOB, LIST: - return "dafny.Sequence"; - // default catches a case where users may author their own classes that implement and extend resource (ExtendableTrait) - // ENUM, STRUCTURE, UNION can be removed but for posterity it looks great to see all the shapes being covered. - case ENUM, STRUCTURE, UNION: - default: - return DafnyNameResolver.dafnyTypesNamespace(shape) - .concat(DOT) - .concat(symbol.getName()); - } - } - - public static String getDafnySubErrorType(final Shape shape, final Symbol symbol) { - return DafnyNameResolver.getDafnyBaseErrorType(shape) - .concat("_") - .concat(symbol.getName()); - } - - public static String getDafnyBaseErrorType(final Shape shape) { - return DafnyNameResolver.dafnyTypesNamespace(shape) - .concat(DOT) - .concat("Error"); - } - - public static String getDafnyCompanionType(final Shape shape, final Symbol symbol) { - return DafnyNameResolver.dafnyTypesNamespace(shape) - .concat(DOT) - .concat("Companion_%s_".formatted(symbol.getName())); - } - - public static String getDafnyErrorCompanion(final Shape shape) { - return DafnyNameResolver.dafnyTypesNamespace(shape) - .concat(DOT) - .concat("Companion_Error_"); - } - - public static String getDafnyErrorCompanionCreate(final Shape shape, final Symbol symbol) { - return DafnyNameResolver.getDafnyErrorCompanion(shape) - .concat(DOT) - .concat("Create_%s_".formatted(symbol.getName())); - } - - public static String getDafnyCompanionStructType(final Shape shape, final Symbol symbol) { - return DafnyNameResolver.dafnyTypesNamespace(shape) - .concat(DOT) - .concat("CompanionStruct_%s_".formatted(symbol.getName())); - } - - public static String getDafnyCompanionTypeCreate(final Shape shape, final Symbol symbol) { - return DafnyNameResolver.getDafnyCompanionType(shape, symbol) - .concat(DOT) - .concat("Create_%s_".formatted(symbol.getName())); - } - - /** - * Returns the path to Create_ function for creating member shape within a union shape. - * - * @param unionShape The union shape containing the member shape. - * @param memberName The name of the member shape within the union shape. - */ - public static String getDafnyCreateFuncForUnionMemberShape(final UnionShape unionShape, final String memberName) { - return "companion" - .concat(DOT) - .concat(memberName.replace(unionShape.getId().getName() + "Member", "Create_")) - .concat("_"); - } - - public static String getDafnyClient(final Shape shape, final String sdkId) { - return DafnyNameResolver.dafnyNamespace(shape) - .concat(DOT) - .concat(sdkId) - .concat("Client"); - } - public static String getDafnyInterfaceClient(final Shape shape) { - return DafnyNameResolver.dafnyTypesNamespace(shape) - .concat(DOT).concat("I") - .concat(shape.toShapeId().getName()) - .concat("Client"); - } - - public static String getDafnyInterfaceClient(final ServiceShape serviceShape, - final ServiceTrait awsSdkServiceTrait) { - return DafnyNameResolver.dafnyTypesNamespace(serviceShape) - .concat(DOT).concat("I") - .concat(awsSdkServiceTrait.getSdkId()) - .concat("Client"); - } - - - public static String createDafnyClient(final Shape shape, final String sdkId) { - return DafnyNameResolver.dafnyNamespace(shape) - .concat(".Companion_Default___") - .concat(DOT) - .concat(sdkId); - } - - public static String getDafnyDependentErrorType(final Shape shape, final String sdkId) { - return DafnyNameResolver.dafnyNamespace(shape) - .concat(".Companion_Default___") - .concat(DOT) - .concat(sdkId); - } - + public static String dafnyTypesNamespace(final Shape shape) { + return shape + .toShapeId() + .getNamespace() + .replace(DOT, BLANK) + .toLowerCase() + .concat(INTERNAL_DAFNY_TYPES); + } + + public static String dafnyNamespace(final Shape shape) { + return shape + .toShapeId() + .getNamespace() + .replace(DOT, BLANK) + .toLowerCase() + .concat(INTERNAL_DAFNY); + } + + /** + * Returns the Dafny type for a given Shape. + * + * @param shape The Shape for which the Dafny type needs to be determined. + * @param symbol The Symbol representing the Shape. + * @return The Dafny type as a String. + */ + public static String getDafnyType(final Shape shape, final Symbol symbol) { + ShapeType type = shape.getType(); + if (shape.hasTrait(EnumTrait.class)) { + type = ShapeType.ENUM; + } + switch (type) { + case INTEGER, LONG, BOOLEAN: + return symbol.getName(); + case MAP: + return "dafny.Map"; + case DOUBLE, STRING, BLOB, LIST: + return "dafny.Sequence"; + // default catches a case where users may author their own classes that implement and extend resource (ExtendableTrait) + // ENUM, STRUCTURE, UNION can be removed but for posterity it looks great to see all the shapes being covered. + case ENUM, STRUCTURE, UNION: + default: + return DafnyNameResolver + .dafnyTypesNamespace(shape) + .concat(DOT) + .concat(symbol.getName()); + } + } + + public static String getDafnySubErrorType( + final Shape shape, + final Symbol symbol + ) { + return DafnyNameResolver + .getDafnyBaseErrorType(shape) + .concat("_") + .concat(symbol.getName()); + } + + public static String getDafnyBaseErrorType(final Shape shape) { + return DafnyNameResolver + .dafnyTypesNamespace(shape) + .concat(DOT) + .concat("Error"); + } + + public static String getDafnyCompanionType( + final Shape shape, + final Symbol symbol + ) { + return DafnyNameResolver + .dafnyTypesNamespace(shape) + .concat(DOT) + .concat("Companion_%s_".formatted(symbol.getName())); + } + + public static String getDafnyErrorCompanion(final Shape shape) { + return DafnyNameResolver + .dafnyTypesNamespace(shape) + .concat(DOT) + .concat("Companion_Error_"); + } + + public static String getDafnyErrorCompanionCreate( + final Shape shape, + final Symbol symbol + ) { + return DafnyNameResolver + .getDafnyErrorCompanion(shape) + .concat(DOT) + .concat("Create_%s_".formatted(symbol.getName())); + } + + public static String getDafnyCompanionStructType( + final Shape shape, + final Symbol symbol + ) { + return DafnyNameResolver + .dafnyTypesNamespace(shape) + .concat(DOT) + .concat("CompanionStruct_%s_".formatted(symbol.getName())); + } + + public static String getDafnyCompanionTypeCreate( + final Shape shape, + final Symbol symbol + ) { + return DafnyNameResolver + .getDafnyCompanionType(shape, symbol) + .concat(DOT) + .concat("Create_%s_".formatted(symbol.getName())); + } + + /** + * Returns the path to Create_ function for creating member shape within a union shape. + * + * @param unionShape The union shape containing the member shape. + * @param memberName The name of the member shape within the union shape. + */ + public static String getDafnyCreateFuncForUnionMemberShape( + final UnionShape unionShape, + final String memberName + ) { + return "companion".concat(DOT) + .concat( + memberName.replace(unionShape.getId().getName() + "Member", "Create_") + ) + .concat("_"); + } + + public static String getDafnyClient(final Shape shape, final String sdkId) { + return DafnyNameResolver + .dafnyNamespace(shape) + .concat(DOT) + .concat(sdkId) + .concat("Client"); + } + + public static String getDafnyInterfaceClient(final Shape shape) { + return DafnyNameResolver + .dafnyTypesNamespace(shape) + .concat(DOT) + .concat("I") + .concat(shape.toShapeId().getName()) + .concat("Client"); + } + + public static String getDafnyInterfaceClient( + final ServiceShape serviceShape, + final ServiceTrait awsSdkServiceTrait + ) { + return DafnyNameResolver + .dafnyTypesNamespace(serviceShape) + .concat(DOT) + .concat("I") + .concat(awsSdkServiceTrait.getSdkId()) + .concat("Client"); + } + + public static String createDafnyClient( + final Shape shape, + final String sdkId + ) { + return DafnyNameResolver + .dafnyNamespace(shape) + .concat(".Companion_Default___") + .concat(DOT) + .concat(sdkId); + } + + public static String getDafnyDependentErrorType( + final Shape shape, + final String sdkId + ) { + return DafnyNameResolver + .dafnyNamespace(shape) + .concat(".Companion_Default___") + .concat(DOT) + .concat(sdkId); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/nameresolver/SmithyNameResolver.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/nameresolver/SmithyNameResolver.java index 92913d681a..47c7d52bf5 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/nameresolver/SmithyNameResolver.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/nameresolver/SmithyNameResolver.java @@ -1,101 +1,177 @@ package software.amazon.polymorph.smithygo.localservice.nameresolver; +import static software.amazon.polymorph.smithygo.localservice.nameresolver.Constants.BLANK; +import static software.amazon.polymorph.smithygo.localservice.nameresolver.Constants.DOT; + +import java.util.Map; import software.amazon.smithy.aws.traits.ServiceTrait; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.SimpleShape; -import java.util.Map; - -import static software.amazon.polymorph.smithygo.localservice.nameresolver.Constants.BLANK; -import static software.amazon.polymorph.smithygo.localservice.nameresolver.Constants.DOT; - public class SmithyNameResolver { - private static Map smithyNamespaceToGoModuleNameMap; - - public static void setSmithyNamespaceToGoModuleNameMap( - Map smithyNamespaceToGoModuleNameMap) { - SmithyNameResolver.smithyNamespaceToGoModuleNameMap = smithyNamespaceToGoModuleNameMap; - } - - public static String getGoModuleNameForSmithyNamespace(final String smithyNamespace) { - if (smithyNamespace.contains("smithy.")) return ""; - if (!smithyNamespaceToGoModuleNameMap.containsKey(smithyNamespace)) { - throw new IllegalArgumentException("Go module name not found for Smithy namespace: " + smithyNamespace); - } - return smithyNamespaceToGoModuleNameMap.get(smithyNamespace); + private static Map smithyNamespaceToGoModuleNameMap; + + public static void setSmithyNamespaceToGoModuleNameMap( + Map smithyNamespaceToGoModuleNameMap + ) { + SmithyNameResolver.smithyNamespaceToGoModuleNameMap = + smithyNamespaceToGoModuleNameMap; + } + + public static String getGoModuleNameForSmithyNamespace( + final String smithyNamespace + ) { + if (smithyNamespace.contains("smithy.")) return ""; + if (!smithyNamespaceToGoModuleNameMap.containsKey(smithyNamespace)) { + throw new IllegalArgumentException( + "Go module name not found for Smithy namespace: " + smithyNamespace + ); } - - public static String shapeNamespace(final Shape shape) { - return shape.toShapeId().getNamespace().replace(DOT, BLANK).toLowerCase(); + return smithyNamespaceToGoModuleNameMap.get(smithyNamespace); + } + + public static String shapeNamespace(final Shape shape) { + return shape.toShapeId().getNamespace().replace(DOT, BLANK).toLowerCase(); + } + + public static String smithyTypesNamespace(final Shape shape) { + return shape + .toShapeId() + .getNamespace() + .replace(DOT, BLANK) + .toLowerCase() + .concat("types"); + } + + public static String getGoModuleNameForSdkNamespace( + final String smithyNamespace + ) { + return getGoModuleNameForSmithyNamespace("sdk.".concat(smithyNamespace)); + } + + public static String smithyTypesNamespaceAws( + final ServiceTrait serviceTrait, + boolean isAwsSubType + ) { + if (isAwsSubType) { + return "types"; } - - public static String smithyTypesNamespace(final Shape shape) { - return shape.toShapeId().getNamespace().replace(DOT, BLANK).toLowerCase().concat("types"); + return serviceTrait.getSdkId().toLowerCase(); + } + + public static String getSmithyType(final Shape shape, final Symbol symbol) { + if ( + symbol.getNamespace().contains("smithy.") || + symbol.getName().contains("string") + ) { + return symbol.getName(); } - - public static String getGoModuleNameForSdkNamespace(final String smithyNamespace) { - return getGoModuleNameForSmithyNamespace("sdk.".concat(smithyNamespace)); + return SmithyNameResolver + .smithyTypesNamespace(shape) + .concat(DOT) + .concat(symbol.getName()); + } + + public static String getSmithyTypeAws( + final ServiceTrait serviceTrait, + final Symbol symbol, + boolean subtype + ) { + if ( + symbol.getNamespace().contains("smithy.") || + symbol.getName().equals("string") || + symbol.getName().equals("float64") + ) { + return symbol.getName(); } - - public static String smithyTypesNamespaceAws(final ServiceTrait serviceTrait, boolean isAwsSubType) { - if (isAwsSubType) { - return "types"; - } - return serviceTrait.getSdkId().toLowerCase(); + return SmithyNameResolver + .smithyTypesNamespaceAws(serviceTrait, subtype) + .concat(DOT) + .concat(symbol.getName()); + } + + public static String getSmithyType(final Shape shape) { + return SmithyNameResolver + .smithyTypesNamespace(shape) + .concat(DOT) + .concat(shape.toShapeId().getName()); + } + + public static String getToDafnyMethodName( + final ServiceShape serviceShape, + final Shape shape, + final String disambiguator + ) { + final var methodName = serviceShape + .getContextualName(shape) + .concat("_ToDafny"); + if ( + serviceShape + .toShapeId() + .getNamespace() + .equals(shape.toShapeId().getNamespace()) + ) { + return methodName; + } else { + return SmithyNameResolver + .shapeNamespace(shape) + .concat(DOT) + .concat(methodName); } - - public static String getSmithyType(final Shape shape, final Symbol symbol) { - if(symbol.getNamespace().contains("smithy.") || symbol.getName().contains("string")) { - return symbol.getName(); - } - return SmithyNameResolver.smithyTypesNamespace(shape).concat(DOT).concat(symbol.getName()); - } - - public static String getSmithyTypeAws(final ServiceTrait serviceTrait, final Symbol symbol, boolean subtype) { - if(symbol.getNamespace().contains("smithy.") || symbol.getName().equals("string") || symbol.getName().equals("float64")) { - return symbol.getName(); - } - return SmithyNameResolver.smithyTypesNamespaceAws(serviceTrait, subtype).concat(DOT).concat(symbol.getName()); - } - - public static String getSmithyType(final Shape shape) { - return SmithyNameResolver.smithyTypesNamespace(shape).concat(DOT).concat(shape.toShapeId().getName()); - } - - public static String getToDafnyMethodName(final ServiceShape serviceShape, final Shape shape, final String disambiguator) { - final var methodName = serviceShape.getContextualName(shape).concat("_ToDafny"); - if (serviceShape.toShapeId().getNamespace().equals(shape.toShapeId().getNamespace())) { - return methodName; - } else { - return SmithyNameResolver.shapeNamespace(shape).concat(DOT).concat(methodName); - } - } - - public static String getToDafnyMethodName(final Shape shape, final String disambiguator) { - final var methodName = shape.getId().getName().concat("_ToDafny"); - return SmithyNameResolver.shapeNamespace(shape).concat(DOT).concat(methodName); - } - - public static String getFromDafnyMethodName(final ServiceShape serviceShape, final Shape shape, final String disambiguator) { - final var methodName = serviceShape.getContextualName(shape).concat("_FromDafny"); - if (serviceShape.toShapeId().getNamespace().equals(shape.toShapeId().getNamespace())) { - return methodName; - } else { - return SmithyNameResolver.shapeNamespace(shape).concat(DOT).concat(methodName); - } - } - - public static String getFromDafnyMethodName(final Shape shape, final String disambiguator) { - final var methodName = shape.getId().getName().concat("_FromDafny"); - return SmithyNameResolver.shapeNamespace(shape).concat(DOT).concat(methodName); - } - - public static String getAwsServiceClient(final ServiceTrait serviceTrait) { - return SmithyNameResolver.smithyTypesNamespaceAws(serviceTrait, false) - .concat(DOT) - .concat("Client"); + } + + public static String getToDafnyMethodName( + final Shape shape, + final String disambiguator + ) { + final var methodName = shape.getId().getName().concat("_ToDafny"); + return SmithyNameResolver + .shapeNamespace(shape) + .concat(DOT) + .concat(methodName); + } + + public static String getFromDafnyMethodName( + final ServiceShape serviceShape, + final Shape shape, + final String disambiguator + ) { + final var methodName = serviceShape + .getContextualName(shape) + .concat("_FromDafny"); + if ( + serviceShape + .toShapeId() + .getNamespace() + .equals(shape.toShapeId().getNamespace()) + ) { + return methodName; + } else { + return SmithyNameResolver + .shapeNamespace(shape) + .concat(DOT) + .concat(methodName); } + } + + public static String getFromDafnyMethodName( + final Shape shape, + final String disambiguator + ) { + final var methodName = shape.getId().getName().concat("_FromDafny"); + return SmithyNameResolver + .shapeNamespace(shape) + .concat(DOT) + .concat(methodName); + } + + public static String getAwsServiceClient(final ServiceTrait serviceTrait) { + return SmithyNameResolver + .smithyTypesNamespaceAws(serviceTrait, false) + .concat(DOT) + .concat("Client"); + } } diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/shapevisitor/DafnyToSmithyShapeVisitor.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/shapevisitor/DafnyToSmithyShapeVisitor.java index 2ef2b435d0..3444cbea2b 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/shapevisitor/DafnyToSmithyShapeVisitor.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/shapevisitor/DafnyToSmithyShapeVisitor.java @@ -1,5 +1,7 @@ package software.amazon.polymorph.smithygo.localservice.shapevisitor; +import static software.amazon.polymorph.smithygo.codegen.SymbolUtils.POINTABLE; + import software.amazon.polymorph.smithygo.codegen.GenerationContext; import software.amazon.polymorph.smithygo.codegen.GoWriter; import software.amazon.polymorph.smithygo.codegen.SmithyGoDependency; @@ -27,426 +29,626 @@ import software.amazon.smithy.model.traits.EnumTrait; import software.amazon.smithy.utils.StringUtils; -import static software.amazon.polymorph.smithygo.codegen.SymbolUtils.POINTABLE; - public class DafnyToSmithyShapeVisitor extends ShapeVisitor.Default { - private final GenerationContext context; - private final String dataSource; - private final GoWriter writer; - private final boolean isConfigShape; - private final boolean isOptional; - public DafnyToSmithyShapeVisitor( - final GenerationContext context, - final String dataSource, - final GoWriter writer, - final boolean isConfigShape - ) { - this(context, dataSource, writer, isConfigShape, false); - } + private final GenerationContext context; + private final String dataSource; + private final GoWriter writer; + private final boolean isConfigShape; + private final boolean isOptional; + + public DafnyToSmithyShapeVisitor( + final GenerationContext context, + final String dataSource, + final GoWriter writer, + final boolean isConfigShape + ) { + this(context, dataSource, writer, isConfigShape, false); + } - public DafnyToSmithyShapeVisitor( - final GenerationContext context, - final String dataSource, - final GoWriter writer, - final boolean isConfigShape, - final boolean isOptional - ) { - this.context = context; - this.dataSource = dataSource; - this.writer = writer; - this.isConfigShape = isConfigShape; - this.isOptional = isOptional; + public DafnyToSmithyShapeVisitor( + final GenerationContext context, + final String dataSource, + final GoWriter writer, + final boolean isConfigShape, + final boolean isOptional + ) { + this.context = context; + this.dataSource = dataSource; + this.writer = writer; + this.isConfigShape = isConfigShape; + this.isOptional = isOptional; + } + + protected String referenceStructureShape(StructureShape shape) { + ReferenceTrait referenceTrait = shape.expectTrait(ReferenceTrait.class); + Shape resourceOrService = context + .model() + .expectShape(referenceTrait.getReferentId()); + var namespace = ""; + if (resourceOrService.asResourceShape().isPresent()) { + var resourceShape = resourceOrService.asResourceShape().get(); + if ( + !resourceOrService + .toShapeId() + .getNamespace() + .equals(context.settings().getService().getNamespace()) + ) { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + resourceOrService.toShapeId().getNamespace() + ), + SmithyNameResolver.shapeNamespace(resourceShape) + ); + namespace = + SmithyNameResolver.shapeNamespace(resourceOrService).concat("."); + } + if (!this.isOptional) { + return "%s_FromDafny(%s)".formatted( + namespace.concat(resourceShape.toShapeId().getName()), + dataSource + ); + } + return """ + func () %s.I%s { + if %s == nil { + return nil; + } + return %s + }()""".formatted( + SmithyNameResolver.smithyTypesNamespace(resourceShape), + resourceShape.getId().getName(), + dataSource, + "%s_FromDafny(%s.(%s.I%s))".formatted( + namespace.concat(resourceShape.toShapeId().getName()), + dataSource, + DafnyNameResolver.dafnyTypesNamespace(resourceShape), + resourceShape.getId().getName() + ) + ); + } else { + var serviceShape = resourceOrService.asServiceShape().get(); + if ( + !resourceOrService + .toShapeId() + .getNamespace() + .equals(context.settings().getService().getNamespace()) + ) { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + resourceOrService.toShapeId().getNamespace() + ), + SmithyNameResolver.shapeNamespace(serviceShape) + ); + namespace = + SmithyNameResolver.shapeNamespace(resourceOrService).concat("."); + } + if (!this.isOptional) { + return "%1$s{%2$s}".formatted( + namespace.concat( + context.symbolProvider().toSymbol(serviceShape).getName() + ), + dataSource + ); + } + return """ + func () *%s { + if %s == nil { + return nil; + } + return &%s{%s.(*%s)} + }()""".formatted( + namespace.concat( + context.symbolProvider().toSymbol(serviceShape).getName() + ), + dataSource, + namespace.concat( + context.symbolProvider().toSymbol(serviceShape).getName() + ), + dataSource, + DafnyNameResolver.getDafnyClient( + serviceShape, + serviceShape.toShapeId().getName() + ) + ); } + } - protected String referenceStructureShape(StructureShape shape) { - ReferenceTrait referenceTrait = shape.expectTrait(ReferenceTrait.class); - Shape resourceOrService = context.model().expectShape(referenceTrait.getReferentId()); - var namespace = ""; - if (resourceOrService.asResourceShape().isPresent()) { - var resourceShape = resourceOrService.asResourceShape().get(); - if (!resourceOrService.toShapeId().getNamespace().equals(context.settings().getService().getNamespace())) { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(resourceOrService.toShapeId().getNamespace()), SmithyNameResolver.shapeNamespace(resourceShape)); - namespace = SmithyNameResolver.shapeNamespace(resourceOrService).concat("."); - } - if (!this.isOptional) { - return "%s_FromDafny(%s)".formatted(namespace.concat(resourceShape.toShapeId().getName()), dataSource); - } - return """ - func () %s.I%s { - if %s == nil { - return nil; - } - return %s - }()""".formatted(SmithyNameResolver.smithyTypesNamespace(resourceShape), resourceShape.getId().getName(), dataSource, - "%s_FromDafny(%s.(%s.I%s))".formatted(namespace.concat(resourceShape.toShapeId().getName()), dataSource, - DafnyNameResolver.dafnyTypesNamespace(resourceShape), resourceShape.getId().getName())); + @Override + protected String getDefault(Shape shape) { + throw new CodegenException( + String.format( + "Unsupported conversion of %s to %s using the %s protocol", + shape, + shape.getType(), + context.protocolGenerator().getName() + ) + ); + } + + @Override + public String blobShape(BlobShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + return """ + func () []byte { + var b []byte + if %s == nil { + return nil + } + for i := dafny.Iterate(%s) ; ; { + val, ok := i() + if !ok { + return b } else { - var serviceShape = resourceOrService.asServiceShape().get(); - if (!resourceOrService.toShapeId().getNamespace().equals(context.settings().getService().getNamespace())) { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(resourceOrService.toShapeId().getNamespace()), SmithyNameResolver.shapeNamespace(serviceShape)); - namespace = SmithyNameResolver.shapeNamespace(resourceOrService).concat("."); - } - if (!this.isOptional) { - return "%1$s{%2$s}".formatted(namespace.concat(context.symbolProvider().toSymbol(serviceShape).getName()), dataSource); - } - return """ - func () *%s { - if %s == nil { - return nil; - } - return &%s{%s.(*%s)} - }()""".formatted(namespace.concat(context.symbolProvider().toSymbol(serviceShape).getName()), dataSource, namespace.concat(context.symbolProvider().toSymbol(serviceShape).getName()), - dataSource, DafnyNameResolver.getDafnyClient(serviceShape, serviceShape.toShapeId().getName())); + b = append(b, val.(byte)) } } + }()""".formatted(dataSource, dataSource); + } - @Override - protected String getDefault(Shape shape) { - throw new CodegenException(String.format( - "Unsupported conversion of %s to %s using the %s protocol", - shape, shape.getType(), context.protocolGenerator().getName())); + @Override + public String structureShape(final StructureShape shape) { + if (shape.hasTrait(ReferenceTrait.class)) { + return referenceStructureShape(shape); } + final var builder = new StringBuilder(); + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + shape.toShapeId().getNamespace() + ), + DafnyNameResolver.dafnyTypesNamespace(shape) + ); - @Override - public String blobShape(BlobShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - return """ - func () []byte { - var b []byte - if %s == nil { - return nil - } - for i := dafny.Iterate(%s) ; ; { - val, ok := i() - if !ok { - return b - } else { - b = append(b, val.(byte)) - } - } - }()""".formatted(dataSource, dataSource); + builder.append( + "%1$s{".formatted( + SmithyNameResolver + .smithyTypesNamespace(shape) + .concat(".") + .concat(shape.getId().getName()) + ) + ); + String fieldSeparator = ","; + for (final var memberShapeEntry : shape.getAllMembers().entrySet()) { + final var memberName = memberShapeEntry.getKey(); + final var memberShape = memberShapeEntry.getValue(); + final var targetShape = context + .model() + .expectShape(memberShape.getTarget()); + //TODO: Is it ever possible for structure to be nil? + final var derivedDataSource = + "%1$s%2$s%3$s%4$s".formatted( + dataSource, + ".Dtor_%s()".formatted(memberName), + memberShape.isOptional() ? ".UnwrapOr(nil)" : "", + memberShape.isOptional() && + targetShape.isStructureShape() && + !targetShape.hasTrait(ReferenceTrait.class) + ? ".(%s)".formatted( + DafnyNameResolver.getDafnyType( + targetShape, + context.symbolProvider().toSymbol(memberShape) + ) + ) + : "" + ); + builder.append( + "%1$s: %2$s%3$s,".formatted( + StringUtils.capitalize(memberName), + (targetShape.isStructureShape() && memberShape.isOptional()) && + !targetShape.hasTrait(ReferenceTrait.class) + ? "&" + : "", + targetShape.accept( + new DafnyToSmithyShapeVisitor( + context, + derivedDataSource, + writer, + isConfigShape, + memberShape.isOptional() + ) + ) + ) + ); } - @Override - public String structureShape(final StructureShape shape) { - if (shape.hasTrait(ReferenceTrait.class)) { - return referenceStructureShape(shape); - } - final var builder = new StringBuilder(); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(shape.toShapeId().getNamespace()), DafnyNameResolver.dafnyTypesNamespace(shape)); - - builder.append("%1$s{".formatted(SmithyNameResolver.smithyTypesNamespace(shape).concat(".").concat(shape.getId().getName()))); - String fieldSeparator = ","; - for (final var memberShapeEntry : shape.getAllMembers().entrySet()) { - final var memberName = memberShapeEntry.getKey(); - final var memberShape = memberShapeEntry.getValue(); - final var targetShape = context.model().expectShape(memberShape.getTarget()); - //TODO: Is it ever possible for structure to be nil? - final var derivedDataSource = "%1$s%2$s%3$s%4$s".formatted(dataSource, - ".Dtor_%s()".formatted(memberName), - memberShape.isOptional() ? ".UnwrapOr(nil)" : "", - memberShape.isOptional() && targetShape.isStructureShape() && !targetShape.hasTrait(ReferenceTrait.class) ? ".(%s)".formatted(DafnyNameResolver.getDafnyType(targetShape, context.symbolProvider().toSymbol(memberShape))) : ""); - builder.append("%1$s: %2$s%3$s,".formatted( - StringUtils.capitalize(memberName), - (targetShape.isStructureShape() && memberShape.isOptional()) && !targetShape.hasTrait(ReferenceTrait.class) ? "&" : "", - targetShape.accept( - new DafnyToSmithyShapeVisitor(context, derivedDataSource, writer, isConfigShape, memberShape.isOptional()) - ) - )); - } + return builder.append("}").toString(); + } - return builder.append("}").toString(); - } + // TODO: smithy-dafny-conversion library + @Override + public String listShape(ListShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + StringBuilder builder = new StringBuilder(); - // TODO: smithy-dafny-conversion library - @Override - public String listShape(ListShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - StringBuilder builder = new StringBuilder(); + MemberShape memberShape = shape.getMember(); + final Shape targetShape = context + .model() + .expectShape(memberShape.getTarget()); + var typeName = targetShape.isStructureShape() + ? context.symbolProvider().toSymbol(memberShape) + : context.symbolProvider().toSymbol(memberShape); + builder.append( + """ + func() []%s{ + var fieldValue []%s + if %s == nil { + return nil + } + for i := dafny.Iterate(%s.(dafny.Sequence)); ; { + val, ok := i() + if !ok { + break + } + fieldValue = append(fieldValue, %s)} + """.formatted( + SmithyNameResolver.getSmithyType(shape, typeName), + SmithyNameResolver.getSmithyType(shape, typeName), + dataSource, + dataSource, + targetShape.accept( + new DafnyToSmithyShapeVisitor( + context, + "val%s".formatted( + targetShape.isStructureShape() + ? ".(%s)".formatted( + DafnyNameResolver.getDafnyType( + targetShape, + context.symbolProvider().toSymbol(targetShape) + ) + ) + : "" + ), + writer, + isConfigShape + ) + ) + ) + ); - MemberShape memberShape = shape.getMember(); - final Shape targetShape = context.model().expectShape(memberShape.getTarget()); - var typeName = targetShape.isStructureShape() ? context.symbolProvider().toSymbol(memberShape) : context.symbolProvider().toSymbol(memberShape); - builder.append(""" - func() []%s{ - var fieldValue []%s - if %s == nil { - return nil - } - for i := dafny.Iterate(%s.(dafny.Sequence)); ; { - val, ok := i() - if !ok { - break - } - fieldValue = append(fieldValue, %s)} - """.formatted(SmithyNameResolver.getSmithyType(shape, typeName), SmithyNameResolver.getSmithyType(shape, typeName), dataSource, dataSource, - targetShape.accept( - new DafnyToSmithyShapeVisitor(context, "val%s".formatted(targetShape.isStructureShape() ? ".(%s)".formatted(DafnyNameResolver.getDafnyType(targetShape, context.symbolProvider().toSymbol(targetShape))) : ""), writer, isConfigShape) - ))); + // Close structure + return builder + .append("return fieldValue }()".formatted(dataSource)) + .toString(); + } - // Close structure - return builder.append("return fieldValue }()".formatted(dataSource)).toString(); - } + @Override + public String mapShape(MapShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + StringBuilder builder = new StringBuilder(); - @Override - public String mapShape(MapShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - StringBuilder builder = new StringBuilder(); + MemberShape keyMemberShape = shape.getKey(); + final Shape keyTargetShape = context + .model() + .expectShape(keyMemberShape.getTarget()); + MemberShape valueMemberShape = shape.getValue(); + final Shape valueTargetShape = context + .model() + .expectShape(valueMemberShape.getTarget()); + final var type = context + .symbolProvider() + .toSymbol(valueTargetShape) + .getName(); - MemberShape keyMemberShape = shape.getKey(); - final Shape keyTargetShape = context.model().expectShape(keyMemberShape.getTarget()); - MemberShape valueMemberShape = shape.getValue(); - final Shape valueTargetShape = context.model().expectShape(valueMemberShape.getTarget()); - final var type = context.symbolProvider().toSymbol(valueTargetShape).getName(); + builder.append( + """ + func() map[string]%s { + var m map[string]%s = make(map[string]%s) + if %s == nil { + return nil + } + for i := dafny.Iterate(%s.(dafny.Map).Items());; { + val, ok := i() + if !ok { + break; + } + m[%s] = %s + } + return m + }()""".formatted( + type, + type, + type, + dataSource, + dataSource, + keyTargetShape.accept( + new DafnyToSmithyShapeVisitor( + context, + "(*val.(dafny.Tuple).IndexInt(0))", + writer, + isConfigShape + ) + ), + valueTargetShape.accept( + new DafnyToSmithyShapeVisitor( + context, + "(*val.(dafny.Tuple).IndexInt(1))", + writer, + isConfigShape + ) + ) + ) + ); + return builder.toString(); + } - builder.append(""" - func() map[string]%s { - var m map[string]%s = make(map[string]%s) - if %s == nil { - return nil - } - for i := dafny.Iterate(%s.(dafny.Map).Items());; { - val, ok := i() - if !ok { - break; - } - m[%s] = %s - } - return m - }()""".formatted(type, type, type, dataSource, dataSource, keyTargetShape.accept( - new DafnyToSmithyShapeVisitor(context, "(*val.(dafny.Tuple).IndexInt(0))", writer, isConfigShape) - ), - valueTargetShape.accept( - new DafnyToSmithyShapeVisitor(context, "(*val.(dafny.Tuple).IndexInt(1))", writer, isConfigShape) - ) - )); - return builder.toString(); + @Override + public String booleanShape(BooleanShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + if (this.isOptional) { + return """ + func() *bool { + var b bool + if %s == nil { + return nil + } + b = %s.(%s) + return &b + }()""".formatted( + dataSource, + dataSource, + context.symbolProvider().toSymbol(shape).getName() + ); + } else { + return "%s.(%s)".formatted( + dataSource, + context.symbolProvider().toSymbol(shape).getName() + ); } + } - @Override - public String booleanShape(BooleanShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - if (this.isOptional) { - return """ - func() *bool { - var b bool - if %s == nil { - return nil - } - b = %s.(%s) - return &b - }()""".formatted(dataSource, dataSource, context.symbolProvider().toSymbol(shape).getName()); - } else { - return "%s.(%s)".formatted(dataSource, context.symbolProvider().toSymbol(shape).getName()); - } + @Override + public String stringShape(StringShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + if (shape.hasTrait(EnumTrait.class)) { + return """ + func () *%s.%s { + var u %s.%s + if %s == nil { + return nil + } + inputEnum := %s.(%s) + index := -1; + for allEnums := dafny.Iterate(%s{}.AllSingletonConstructors()); ; { + enum, ok := allEnums() + if ok { + index++ + if enum.(%s).Equals(inputEnum) { + break; + } + } + } + + return &u.Values()[index] + }()""".formatted( + SmithyNameResolver.smithyTypesNamespace(shape), + context.symbolProvider().toSymbol(shape).getName(), + SmithyNameResolver.smithyTypesNamespace(shape), + context.symbolProvider().toSymbol(shape).getName(), + dataSource, + dataSource, + DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ), + DafnyNameResolver.getDafnyCompanionStructType( + shape, + context.symbolProvider().toSymbol(shape) + ), + DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ) + ); } - @Override - public String stringShape(StringShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - if (shape.hasTrait(EnumTrait.class)) { - return """ - func () *%s.%s { - var u %s.%s - if %s == nil { - return nil - } - inputEnum := %s.(%s) - index := -1; - for allEnums := dafny.Iterate(%s{}.AllSingletonConstructors()); ; { - enum, ok := allEnums() - if ok { - index++ - if enum.(%s).Equals(inputEnum) { - break; - } - } - } - - return &u.Values()[index] - }()""".formatted(SmithyNameResolver.smithyTypesNamespace(shape), context.symbolProvider().toSymbol(shape).getName(), SmithyNameResolver.smithyTypesNamespace(shape), context.symbolProvider().toSymbol(shape).getName(), dataSource, dataSource, DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape)), DafnyNameResolver.getDafnyCompanionStructType(shape, context.symbolProvider().toSymbol(shape)), - DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape))); - } + var underlyingType = shape.hasTrait(DafnyUtf8BytesTrait.class) + ? "uint8" + : "dafny.Char"; + var strConv = "s = s + string(val.(%s))".formatted(underlyingType); + if (underlyingType == "uint8") { + strConv = + """ + // UTF bytes should be always converted from bytes to string in go + // Otherwise go treats the string as a unicode codepoint - var underlyingType = shape.hasTrait(DafnyUtf8BytesTrait.class) ? "uint8" : "dafny.Char"; - var strConv = "s = s + string(val.(%s))".formatted(underlyingType); - if( underlyingType == "uint8" ) { - strConv = """ - // UTF bytes should be always converted from bytes to string in go - // Otherwise go treats the string as a unicode codepoint - - var valUint, _ = val.(%s) - var byteSlice = []byte{valUint} - s = s + string(byteSlice) - """.formatted(underlyingType); - } - if ((boolean)isOptional) { - return """ - func() (*string) { - var s string - if %s == nil { - return nil - } - for i := dafny.Iterate(%s) ; ; { - val, ok := i() - if !ok { - return &[]string{s}[0] - } else { - %s - } - } - }()""".formatted(dataSource, dataSource, strConv); - } - else { - return """ - func() (string) { - var s string - for i := dafny.Iterate(%s) ; ; { - val, ok := i() - if !ok { - return s - } else { - %s - } - } - }()""".formatted(dataSource, strConv); - } + var valUint, _ = val.(%s) + var byteSlice = []byte{valUint} + s = s + string(byteSlice) + """.formatted(underlyingType); } + if ((boolean) isOptional) { + return """ + func() (*string) { + var s string + if %s == nil { + return nil + } + for i := dafny.Iterate(%s) ; ; { + val, ok := i() + if !ok { + return &[]string{s}[0] + } else { + %s + } + } + }()""".formatted(dataSource, dataSource, strConv); + } else { + return """ + func() (string) { + var s string + for i := dafny.Iterate(%s) ; ; { + val, ok := i() + if !ok { + return s + } else { + %s + } + } + }()""".formatted(dataSource, strConv); + } + } - @Override - public String integerShape(IntegerShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + @Override + public String integerShape(IntegerShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - if ((boolean)isOptional) { - return (""" - func() *int32 { - var b int32 - if %s == nil { - return nil - } - b = %s.(int32) - return &b - }()""".formatted(dataSource, dataSource)); - }else { - return """ - func() int32 { - var b = %s.(int32) - return b - }() - """.formatted(dataSource); - } + if ((boolean) isOptional) { + return ( + """ + func() *int32 { + var b int32 + if %s == nil { + return nil + } + b = %s.(int32) + return &b + }()""".formatted(dataSource, dataSource) + ); + } else { + return """ + func() int32 { + var b = %s.(int32) + return b + }() + """.formatted(dataSource); } + } - @Override - public String longShape(LongShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - return (""" - func() *int64 { - var b int64 - if %s == nil { - return nil - } - b = %s.(int64) - return &b - }()""").formatted(dataSource, dataSource); - } + @Override + public String longShape(LongShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + return ( + """ + func() *int64 { + var b int64 + if %s == nil { + return nil + } + b = %s.(int64) + return &b + }()""" + ).formatted(dataSource, dataSource); + } - @Override - public String doubleShape(DoubleShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - writer.addUseImports(SmithyGoDependency.MATH); - return """ - func () *float64 { - var b []byte - if %s == nil { - return nil - } - for i := dafny.Iterate(%s) ; ; { - val, ok := i() - if !ok { - return &[]float64{math.Float64frombits(binary.LittleEndian.Uint64(b))}[0] - } else { - b = append(b, val.(byte)) - } - } - }()""".formatted(dataSource, dataSource); + @Override + public String doubleShape(DoubleShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + writer.addUseImports(SmithyGoDependency.MATH); + return """ + func () *float64 { + var b []byte + if %s == nil { + return nil } - - @Override - public String unionShape(UnionShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - String nilCheck; - if (GoPointableIndex.of(context.model()).isPointable(shape) == false) { - nilCheck = ""; - } else { - nilCheck = """ - if %s == nil { - return nil - }""".formatted( - dataSource - ); + for i := dafny.Iterate(%s) ; ; { + val, ok := i() + if !ok { + return &[]float64{math.Float64frombits(binary.LittleEndian.Uint64(b))}[0] + } else { + b = append(b, val.(byte)) + } } - final String functionInit = """ - func() %s { - var union %s - %s - """.formatted( - context.symbolProvider().toSymbol(shape), - context.symbolProvider().toSymbol(shape), - nilCheck + }()""".formatted(dataSource, dataSource); + } + + @Override + public String unionShape(UnionShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + String nilCheck; + if (GoPointableIndex.of(context.model()).isPointable(shape) == false) { + nilCheck = ""; + } else { + nilCheck = + """ + if %s == nil { + return nil + }""".formatted(dataSource); + } + final String functionInit = + """ + func() %s { + var union %s + %s + """.formatted( + context.symbolProvider().toSymbol(shape), + context.symbolProvider().toSymbol(shape), + nilCheck + ); + StringBuilder eachMemberInUnion = new StringBuilder(); + for (var member : shape.getAllMembers().values()) { + final Shape targetShape = context.model().expectShape(member.getTarget()); + final String memberName = context.symbolProvider().toMemberName(member); + final String rawUnionDataSource = + "(" + + dataSource + + ".(" + + DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ) + + "))"; + // unwrap union type, assert it then convert it to its member type with Dtor_ (example: Dtor_BlobValue()). unionDataSource is not a wrapper object until now. + String unionDataSource = + rawUnionDataSource + + ".Dtor_" + + memberName.replace(shape.getId().getName() + "Member", "") + + "()"; + final Boolean isMemberShapePointable = + (GoPointableIndex.of(context.model()).isPointable(targetShape) && + GoPointableIndex.of(context.model()).isDereferencable(targetShape)) && + !targetShape.isStructureShape(); + final String pointerForPointableShape = isMemberShapePointable ? "*" : ""; + final String isMemberCheck = + """ + if ((%s).%s()) {""".formatted( + rawUnionDataSource, + memberName.replace(shape.getId().getName() + "Member", "Is_") + ); + String wrappedDataSource = ""; + if (!(targetShape.isStructureShape())) { + // All other shape except structure needs a Wrapper object but unionDataSource is not a Wrapper object. + wrappedDataSource = + """ + var dataSource = Wrappers.Companion_Option_.Create_Some_(%s)""".formatted( + unionDataSource ); - StringBuilder eachMemberInUnion = new StringBuilder(); - for (var member : shape.getAllMembers().values()) { - final Shape targetShape = context.model().expectShape(member.getTarget()); - final String memberName = context.symbolProvider().toMemberName(member); - final String rawUnionDataSource = "(" + dataSource + ".(" + DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape)) + "))"; - // unwrap union type, assert it then convert it to its member type with Dtor_ (example: Dtor_BlobValue()). unionDataSource is not a wrapper object until now. - String unionDataSource = rawUnionDataSource + ".Dtor_" + memberName.replace(shape.getId().getName() + "Member", "") + "()"; - final Boolean isMemberShapePointable = (GoPointableIndex.of(context.model()).isPointable(targetShape) && GoPointableIndex.of(context.model()).isDereferencable(targetShape)) && !targetShape.isStructureShape(); - final String pointerForPointableShape = isMemberShapePointable ? "*" : ""; - final String isMemberCheck = """ - if ((%s).%s()) {""".formatted( - rawUnionDataSource, - memberName.replace(shape.getId().getName() + "Member", "Is_") - ); - String wrappedDataSource = ""; - if (!(targetShape.isStructureShape())) { - // All other shape except structure needs a Wrapper object but unionDataSource is not a Wrapper object. - wrappedDataSource = """ - var dataSource = Wrappers.Companion_Option_.Create_Some_(%s)""".formatted(unionDataSource); - unionDataSource = "dataSource.UnwrapOr(nil)"; + unionDataSource = "dataSource.UnwrapOr(nil)"; + } + eachMemberInUnion.append( + """ + %s + %s + union = &%s.%s{ + Value: %s(%s), } - eachMemberInUnion.append(""" - %s - %s - union = &%s.%s{ - Value: %s(%s), - } - } - """.formatted( - isMemberCheck, - wrappedDataSource, - SmithyNameResolver.smithyTypesNamespace(shape), - memberName, - pointerForPointableShape, - targetShape.accept( - new DafnyToSmithyShapeVisitor(context, unionDataSource, writer, isConfigShape, isMemberShapePointable) - ))); } - return - """ - %s - %s - return union - }()""".formatted( - functionInit, - eachMemberInUnion - ); + """.formatted( + isMemberCheck, + wrappedDataSource, + SmithyNameResolver.smithyTypesNamespace(shape), + memberName, + pointerForPointableShape, + targetShape.accept( + new DafnyToSmithyShapeVisitor( + context, + unionDataSource, + writer, + isConfigShape, + isMemberShapePointable + ) + ) + ) + ); } + return """ + %s + %s + return union + }()""".formatted(functionInit, eachMemberInUnion); + } - @Override - public String timestampShape(TimestampShape shape) { - return "nil"; - } -} \ No newline at end of file + @Override + public String timestampShape(TimestampShape shape) { + return "nil"; + } +} diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/shapevisitor/SmithyToDafnyShapeVisitor.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/shapevisitor/SmithyToDafnyShapeVisitor.java index f45c6ea4a8..e2afef2d91 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/shapevisitor/SmithyToDafnyShapeVisitor.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/shapevisitor/SmithyToDafnyShapeVisitor.java @@ -1,5 +1,7 @@ package software.amazon.polymorph.smithygo.localservice.shapevisitor; +import static software.amazon.polymorph.smithygo.codegen.SymbolUtils.POINTABLE; + import software.amazon.polymorph.smithygo.codegen.GenerationContext; import software.amazon.polymorph.smithygo.codegen.GoWriter; import software.amazon.polymorph.smithygo.codegen.SmithyGoDependency; @@ -28,481 +30,681 @@ import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.utils.StringUtils; -import static software.amazon.polymorph.smithygo.codegen.SymbolUtils.POINTABLE; - public class SmithyToDafnyShapeVisitor extends ShapeVisitor.Default { - private final GenerationContext context; - private final String dataSource; - private final GoWriter writer; - private final boolean isConfigShape; - - private final boolean isOptional; - protected boolean isPointerType; - public void setPointerType() { - this.isPointerType = false; + private final GenerationContext context; + private final String dataSource; + private final GoWriter writer; + private final boolean isConfigShape; + + private final boolean isOptional; + protected boolean isPointerType; + + public void setPointerType() { + this.isPointerType = false; + } + + public SmithyToDafnyShapeVisitor( + final GenerationContext context, + final String dataSource, + final GoWriter writer, + final boolean isConfigShape, + final boolean isOptional, + final boolean isPointerType + ) { + this.context = context; + this.dataSource = dataSource; + this.writer = writer; + this.isConfigShape = isConfigShape; + this.isOptional = isOptional; + this.isPointerType = isPointerType; + } + + protected String referenceStructureShape(StructureShape shape) { + ReferenceTrait referenceTrait = shape.expectTrait(ReferenceTrait.class); + Shape resourceOrService = context + .model() + .expectShape(referenceTrait.getReferentId()); + + if (resourceOrService.asResourceShape().isPresent()) { + ResourceShape resourceShape = resourceOrService.asResourceShape().get(); + var namespace = ""; + if ( + !resourceShape + .toShapeId() + .getNamespace() + .equals(context.settings().getService().getNamespace()) + ) { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + resourceShape.toShapeId().getNamespace() + ), + SmithyNameResolver.shapeNamespace(resourceShape) + ); + namespace = + SmithyNameResolver.shapeNamespace(resourceShape).concat("."); + } + if (!this.isOptional) { + return "%s_ToDafny(%s)".formatted( + namespace.concat(resourceShape.toShapeId().getName()), + dataSource + ); + } else { + var goCodeBlock = + """ + func () Wrappers.Option { + if %s == nil { + return Wrappers.Companion_Option_.Create_None_() + } + return Wrappers.Companion_Option_.Create_Some_(%s) + }()"""; + return goCodeBlock.formatted( + dataSource, + "%s_ToDafny(%s)".formatted( + namespace.concat(resourceShape.toShapeId().getName()), + dataSource + ) + ); + } } - public SmithyToDafnyShapeVisitor( - final GenerationContext context, - final String dataSource, - final GoWriter writer, - final boolean isConfigShape, - final boolean isOptional, - final boolean isPointerType - ) { - this.context = context; - this.dataSource = dataSource; - this.writer = writer; - this.isConfigShape = isConfigShape; - this.isOptional = isOptional; - this.isPointerType = isPointerType; + if (resourceOrService.asServiceShape().isPresent()) { + ServiceShape resourceShape = resourceOrService.asServiceShape().get(); + if (!this.isOptional) { + return dataSource; + } else { + var goCodeBlock = + """ + func () Wrappers.Option { + if %s == nil { + return Wrappers.Companion_Option_.Create_None_() + } + return Wrappers.Companion_Option_.Create_Some_(%s) + }()"""; + return goCodeBlock.formatted(dataSource, dataSource); + } } - protected String referenceStructureShape(StructureShape shape) { - ReferenceTrait referenceTrait = shape.expectTrait(ReferenceTrait.class); - Shape resourceOrService = context.model().expectShape(referenceTrait.getReferentId()); - - if (resourceOrService.asResourceShape().isPresent()) { - ResourceShape resourceShape = resourceOrService.asResourceShape().get(); - var namespace = ""; - if (!resourceShape.toShapeId().getNamespace().equals(context.settings().getService().getNamespace())) { - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(resourceShape.toShapeId().getNamespace()), SmithyNameResolver.shapeNamespace(resourceShape)); - namespace = SmithyNameResolver.shapeNamespace(resourceShape).concat("."); - } - if(!this.isOptional) { - return "%s_ToDafny(%s)".formatted(namespace.concat(resourceShape.toShapeId().getName()), dataSource); - } else { - var goCodeBlock = """ - func () Wrappers.Option { - if %s == nil { - return Wrappers.Companion_Option_.Create_None_() - } - return Wrappers.Companion_Option_.Create_Some_(%s) - }()"""; - return goCodeBlock.formatted(dataSource, "%s_ToDafny(%s)".formatted(namespace.concat(resourceShape.toShapeId().getName()), dataSource)); - } - } - - if (resourceOrService.asServiceShape().isPresent()) { - ServiceShape resourceShape = resourceOrService.asServiceShape().get(); - if(!this.isOptional) { - return dataSource; - } else { - var goCodeBlock = """ - func () Wrappers.Option { - if %s == nil { - return Wrappers.Companion_Option_.Create_None_() - } - return Wrappers.Companion_Option_.Create_Some_(%s) - }()"""; - return goCodeBlock.formatted(dataSource, dataSource); - } + throw new UnsupportedOperationException( + "Unknown referenceStructureShape type: " + shape + ); + } + + @Override + protected String getDefault(Shape shape) { + throw new CodegenException( + String.format( + "Unsupported conversion of %s to %s using the %s protocol", + shape, + shape.getType(), + context.protocolGenerator().getName() + ) + ); + } + + @Override + public String blobShape(BlobShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + String nilWrapIfRequired = "nil"; + String someWrapIfRequired = "%s"; + String returnType = "dafny.Sequence"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; + } + return """ + func () %s { + var v []interface{} + if %s == nil {return %s} + for _, e := range %s { + v = append(v, e) } - - throw new UnsupportedOperationException("Unknown referenceStructureShape type: " + shape); + return %s; + }()""".formatted( + returnType, + dataSource, + nilWrapIfRequired, + dataSource, + someWrapIfRequired.formatted("dafny.SeqOf(v...)") + ); + } + + @Override + public String structureShape(final StructureShape shape) { + if (shape.hasTrait(ReferenceTrait.class)) { + return referenceStructureShape(shape); } - - @Override - protected String getDefault(Shape shape) { - throw new CodegenException(String.format( - "Unsupported conversion of %s to %s using the %s protocol", - shape, shape.getType(), context.protocolGenerator().getName())); + final var builder = new StringBuilder(); + writer.addImportFromModule( + "github.com/dafny-lang/DafnyStandardLibGo", + "Wrappers" + ); + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + shape.toShapeId().getNamespace() + ), + DafnyNameResolver.dafnyTypesNamespace(shape) + ); + + String someWrapIfRequired = "%s"; + + String companionStruct; + String returnType; + if (shape.hasTrait(ErrorTrait.class)) { + companionStruct = + DafnyNameResolver.getDafnyErrorCompanionCreate( + shape, + context.symbolProvider().toSymbol(shape) + ); + returnType = DafnyNameResolver.getDafnyBaseErrorType(shape); + } else { + companionStruct = + DafnyNameResolver.getDafnyCompanionTypeCreate( + shape, + context.symbolProvider().toSymbol(shape) + ); + returnType = + DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ); } + String nilWrapIfRequired = returnType.concat("{}"); - @Override - public String blobShape(BlobShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - String nilWrapIfRequired = "nil"; - String someWrapIfRequired = "%s"; - String returnType = "dafny.Sequence"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } - return """ - func () %s { - var v []interface{} - if %s == nil {return %s} - for _, e := range %s { - v = append(v, e) - } - return %s; - }()""".formatted(returnType, dataSource, nilWrapIfRequired, dataSource, someWrapIfRequired.formatted("dafny.SeqOf(v...)")); + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; } - @Override - public String structureShape(final StructureShape shape) { - if (shape.hasTrait(ReferenceTrait.class)) { - return referenceStructureShape(shape); - } - final var builder = new StringBuilder(); - writer.addImportFromModule("github.com/dafny-lang/DafnyStandardLibGo", "Wrappers"); - writer.addImportFromModule(SmithyNameResolver.getGoModuleNameForSmithyNamespace(shape.toShapeId().getNamespace()), DafnyNameResolver.dafnyTypesNamespace(shape)); - - String someWrapIfRequired = "%s"; - - String companionStruct; - String returnType; - if (shape.hasTrait(ErrorTrait.class)) { - companionStruct = DafnyNameResolver.getDafnyErrorCompanionCreate(shape, context.symbolProvider().toSymbol(shape)); - returnType = DafnyNameResolver.getDafnyBaseErrorType(shape); - } else { - companionStruct = DafnyNameResolver.getDafnyCompanionTypeCreate(shape, context.symbolProvider().toSymbol(shape)); - returnType = DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape)); - } - String nilWrapIfRequired = returnType.concat("{}"); - - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } - - var nilCheck = ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); - } - var goCodeBlock = """ - func () %s { - %s - return %s - }()"""; - - - builder.append("%1$s(".formatted(companionStruct)); - String fieldSeparator = ","; - for (final var memberShapeEntry : shape.getAllMembers().entrySet()) { - final var memberName = memberShapeEntry.getKey(); - final var memberShape = memberShapeEntry.getValue(); - final var targetShape = context.model().expectShape(memberShape.getTarget()); - builder.append("%1$s%2$s".formatted( - targetShape.accept( - new SmithyToDafnyShapeVisitor(context, dataSource + "." + StringUtils.capitalize(memberName), - writer, isConfigShape, memberShape.isOptional(), context.symbolProvider().toSymbol(memberShape).getProperty(POINTABLE, Boolean.class).orElse(false) - )), fieldSeparator - )); - } - - - return goCodeBlock.formatted(returnType, nilCheck, someWrapIfRequired.formatted(builder.append(")").toString())); + var nilCheck = ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); } - - @Override - public String mapShape(MapShape shape) { - StringBuilder builder = new StringBuilder(); - - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - - MemberShape keyMemberShape = shape.getKey(); - final Shape keyTargetShape = context.model().expectShape(keyMemberShape.getTarget()); - MemberShape valueMemberShape = shape.getValue(); - final Shape valueTargetShape = context.model().expectShape(valueMemberShape.getTarget()); - String nilWrapIfRequired = "nil"; - String someWrapIfRequired = "%s"; - String returnType = "dafny.Map"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } - builder.append(""" - func () %s { - if %s == nil { return %s } - fieldValue := dafny.NewMapBuilder() - for key, val := range %s { - fieldValue.Add(%s, %s) - } - return %s - }()""".formatted(returnType, dataSource, nilWrapIfRequired, dataSource, - keyTargetShape.accept( - new SmithyToDafnyShapeVisitor(context, "key", writer, isConfigShape, false, false)), - valueTargetShape.accept( - new SmithyToDafnyShapeVisitor(context, "val", writer, isConfigShape, false, false)), - someWrapIfRequired.formatted("fieldValue.ToMap()") - ) - ); - - // Close structure - return builder.toString(); - + var goCodeBlock = + """ + func () %s { + %s + return %s + }()"""; + + builder.append("%1$s(".formatted(companionStruct)); + String fieldSeparator = ","; + for (final var memberShapeEntry : shape.getAllMembers().entrySet()) { + final var memberName = memberShapeEntry.getKey(); + final var memberShape = memberShapeEntry.getValue(); + final var targetShape = context + .model() + .expectShape(memberShape.getTarget()); + builder.append( + "%1$s%2$s".formatted( + targetShape.accept( + new SmithyToDafnyShapeVisitor( + context, + dataSource + "." + StringUtils.capitalize(memberName), + writer, + isConfigShape, + memberShape.isOptional(), + context + .symbolProvider() + .toSymbol(memberShape) + .getProperty(POINTABLE, Boolean.class) + .orElse(false) + ) + ), + fieldSeparator + ) + ); } - @Override - public String listShape(ListShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - - StringBuilder builder = new StringBuilder(); - - MemberShape memberShape = shape.getMember(); - final Shape targetShape = context.model().expectShape(memberShape.getTarget()); - - String nilWrapIfRequired = "nil"; - String someWrapIfRequired = "%s"; - String returnType = "dafny.Sequence"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } - - builder.append(""" - func () %s { - if %s == nil { return %s } - var fieldValue []interface{} = make([]interface{}, 0) - for _, val := range %s { - element := %s - fieldValue = append(fieldValue, element) - } - return %s - }()""".formatted(returnType, dataSource, nilWrapIfRequired, dataSource, - targetShape.accept( - new SmithyToDafnyShapeVisitor(context, "val", writer, isConfigShape, false, false) - ), someWrapIfRequired.formatted("dafny.SeqOf(fieldValue...)"))); - - // Close structure - return builder.toString(); + return goCodeBlock.formatted( + returnType, + nilCheck, + someWrapIfRequired.formatted(builder.append(")").toString()) + ); + } + + @Override + public String mapShape(MapShape shape) { + StringBuilder builder = new StringBuilder(); + + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + + MemberShape keyMemberShape = shape.getKey(); + final Shape keyTargetShape = context + .model() + .expectShape(keyMemberShape.getTarget()); + MemberShape valueMemberShape = shape.getValue(); + final Shape valueTargetShape = context + .model() + .expectShape(valueMemberShape.getTarget()); + String nilWrapIfRequired = "nil"; + String someWrapIfRequired = "%s"; + String returnType = "dafny.Map"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; } - - @Override - public String booleanShape(BooleanShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - String nilWrapIfRequired = "nil"; - String someWrapIfRequired = "%s%s"; - String returnType = "interface {}"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s%s)"; - returnType = "Wrappers.Option"; - } - - var dereferenceIfRequired = isPointerType ? "*" : ""; - var nilCheck = ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); - } - - return """ - func () %s { - %s - return %s - }()""".formatted(returnType, nilCheck, someWrapIfRequired.formatted(dereferenceIfRequired, dataSource)); + builder.append( + """ + func () %s { + if %s == nil { return %s } + fieldValue := dafny.NewMapBuilder() + for key, val := range %s { + fieldValue.Add(%s, %s) + } + return %s + }()""".formatted( + returnType, + dataSource, + nilWrapIfRequired, + dataSource, + keyTargetShape.accept( + new SmithyToDafnyShapeVisitor( + context, + "key", + writer, + isConfigShape, + false, + false + ) + ), + valueTargetShape.accept( + new SmithyToDafnyShapeVisitor( + context, + "val", + writer, + isConfigShape, + false, + false + ) + ), + someWrapIfRequired.formatted("fieldValue.ToMap()") + ) + ); + + // Close structure + return builder.toString(); + } + + @Override + public String listShape(ListShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + + StringBuilder builder = new StringBuilder(); + + MemberShape memberShape = shape.getMember(); + final Shape targetShape = context + .model() + .expectShape(memberShape.getTarget()); + + String nilWrapIfRequired = "nil"; + String someWrapIfRequired = "%s"; + String returnType = "dafny.Sequence"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; } - @Override - public String stringShape(StringShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - if (shape.hasTrait(EnumTrait.class)) { - String nilWrapIfRequired = "nil"; - String someWrapIfRequired = "%s"; - String returnType = DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape)); - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } + builder.append( + """ + func () %s { + if %s == nil { return %s } + var fieldValue []interface{} = make([]interface{}, 0) + for _, val := range %s { + element := %s + fieldValue = append(fieldValue, element) + } + return %s + }()""".formatted( + returnType, + dataSource, + nilWrapIfRequired, + dataSource, + targetShape.accept( + new SmithyToDafnyShapeVisitor( + context, + "val", + writer, + isConfigShape, + false, + false + ) + ), + someWrapIfRequired.formatted("dafny.SeqOf(fieldValue...)") + ) + ); + + // Close structure + return builder.toString(); + } + + @Override + public String booleanShape(BooleanShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + String nilWrapIfRequired = "nil"; + String someWrapIfRequired = "%s%s"; + String returnType = "interface {}"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s%s)"; + returnType = "Wrappers.Option"; + } - var nilCheck = ""; - var dereferenceIfRequired = isPointerType ? "*" : ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); - } - return """ - func () %s { - %s - var index int - for _, enumVal := range %s.Values() { - index++ - if enumVal == %s%s{ - break; - } - } - var enum interface{} - for allEnums, i := dafny.Iterate(%s{}.AllSingletonConstructors()), 0; i < index; i++ { - var ok bool - enum, ok = allEnums() - if !ok { - break; - } - } - return %s - }()""".formatted(returnType, nilCheck, dataSource, dereferenceIfRequired, dataSource, DafnyNameResolver.getDafnyCompanionStructType(shape, context.symbolProvider().toSymbol(shape)), someWrapIfRequired.formatted("enum.(%s)".formatted(DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape))))); - } else { - String nilWrapIfRequired = "nil"; - String someWrapIfRequired = "%s"; - String returnType = "dafny.Sequence"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } + var dereferenceIfRequired = isPointerType ? "*" : ""; + var nilCheck = ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); + } - var nilCheck = ""; - var dereferenceIfRequired = isPointerType ? "*" : ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); + return """ + func () %s { + %s + return %s + }()""".formatted( + returnType, + nilCheck, + someWrapIfRequired.formatted(dereferenceIfRequired, dataSource) + ); + } + + @Override + public String stringShape(StringShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + if (shape.hasTrait(EnumTrait.class)) { + String nilWrapIfRequired = "nil"; + String someWrapIfRequired = "%s"; + String returnType = DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ); + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; + } + + var nilCheck = ""; + var dereferenceIfRequired = isPointerType ? "*" : ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); + } + return """ + func () %s { + %s + var index int + for _, enumVal := range %s.Values() { + index++ + if enumVal == %s%s{ + break; + } + } + var enum interface{} + for allEnums, i := dafny.Iterate(%s{}.AllSingletonConstructors()), 0; i < index; i++ { + var ok bool + enum, ok = allEnums() + if !ok { + break; + } + } + return %s + }()""".formatted( + returnType, + nilCheck, + dataSource, + dereferenceIfRequired, + dataSource, + DafnyNameResolver.getDafnyCompanionStructType( + shape, + context.symbolProvider().toSymbol(shape) + ), + someWrapIfRequired.formatted( + "enum.(%s)".formatted( + DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ) + ) + ) + ); + } else { + String nilWrapIfRequired = "nil"; + String someWrapIfRequired = "%s"; + String returnType = "dafny.Sequence"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; + } + + var nilCheck = ""; + var dereferenceIfRequired = isPointerType ? "*" : ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); + } + + if (shape.hasTrait(DafnyUtf8BytesTrait.class)) writer.addUseImports( + SmithyGoDependency.stdlib("unicode/utf8") + ); + + var underlyingType = shape.hasTrait(DafnyUtf8BytesTrait.class) + ? """ + dafny.SeqOf(func () []interface{} { + utf8.ValidString(%s%s) + b := []byte(%s%s) + f := make([]interface{}, len(b)) + for i, v := range b { + f[i] = v } - - if (shape.hasTrait(DafnyUtf8BytesTrait.class)) - writer.addUseImports(SmithyGoDependency.stdlib("unicode/utf8")); - - var underlyingType = shape.hasTrait(DafnyUtf8BytesTrait.class) ? """ - dafny.SeqOf(func () []interface{} { - utf8.ValidString(%s%s) - b := []byte(%s%s) - f := make([]interface{}, len(b)) - for i, v := range b { - f[i] = v - } - return f - }()...)""".formatted(dereferenceIfRequired, dataSource, dereferenceIfRequired, dataSource) : "dafny.SeqOfChars([]dafny.Char(%s%s)...)".formatted(dereferenceIfRequired, dataSource); - - return """ - func () %s { - %s - return %s - }()""".formatted(returnType, nilCheck, someWrapIfRequired.formatted(underlyingType)); - } + return f + }()...)""".formatted( + dereferenceIfRequired, + dataSource, + dereferenceIfRequired, + dataSource + ) + : "dafny.SeqOfChars([]dafny.Char(%s%s)...)".formatted( + dereferenceIfRequired, + dataSource + ); + + return """ + func () %s { + %s + return %s + }()""".formatted( + returnType, + nilCheck, + someWrapIfRequired.formatted(underlyingType) + ); } - - @Override - public String integerShape(IntegerShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - String nilWrapIfRequired = "nil"; - String someWrapIfRequired = "%s%s"; - String returnType = "interface {}"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s%s)"; - returnType = "Wrappers.Option"; - } - - var dereferenceIfRequired = isPointerType ? "*" : ""; - var nilCheck = ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); - } - - return """ - func () %s { - %s - return %s - }()""".formatted(returnType, nilCheck, someWrapIfRequired.formatted(dereferenceIfRequired, dataSource)); - + } + + @Override + public String integerShape(IntegerShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + String nilWrapIfRequired = "nil"; + String someWrapIfRequired = "%s%s"; + String returnType = "interface {}"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s%s)"; + returnType = "Wrappers.Option"; } - @Override - public String longShape(LongShape shape) { - String nilWrapIfRequired = "nil"; - String someWrapIfRequired = "%s%s"; - String returnType = "interface {}"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s%s)"; - returnType = "Wrappers.Option"; - } - - var dereferenceIfRequired = isPointerType ? "*" : ""; - var nilCheck = ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); - } - - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - - return """ - func () %s { - %s - return %s - }()""".formatted(returnType, nilCheck, someWrapIfRequired.formatted(dereferenceIfRequired, dataSource)); + var dereferenceIfRequired = isPointerType ? "*" : ""; + var nilCheck = ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); } - @Override - public String doubleShape(DoubleShape shape) { - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - writer.addUseImports(SmithyGoDependency.stdlib("encoding/binary")); - writer.addUseImports(SmithyGoDependency.MATH); - - String nilWrapIfRequired = "nil"; - String someWrapIfRequired = "%s"; - String returnType = "interface {}"; - if (this.isOptional) { - nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; - someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; - returnType = "Wrappers.Option"; - } + return """ + func () %s { + %s + return %s + }()""".formatted( + returnType, + nilCheck, + someWrapIfRequired.formatted(dereferenceIfRequired, dataSource) + ); + } + + @Override + public String longShape(LongShape shape) { + String nilWrapIfRequired = "nil"; + String someWrapIfRequired = "%s%s"; + String returnType = "interface {}"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s%s)"; + returnType = "Wrappers.Option"; + } - var dereferenceIfRequired = isPointerType ? "*" : ""; - var nilCheck = ""; - if (isPointerType) { - nilCheck = "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); - } + var dereferenceIfRequired = isPointerType ? "*" : ""; + var nilCheck = ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); + } - return """ - func () %s { - %s - var bits = math.Float64bits(%s%s) - var bytes = make([]byte, 8) - binary.LittleEndian.PutUint64(bytes, bits) - var v []interface{} - for _, e := range bytes { - v = append(v, e) - } - return %s; - }()""".formatted(returnType, nilCheck, dereferenceIfRequired, dataSource, someWrapIfRequired.formatted("dafny.SeqOf(v...)")); + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + + return """ + func () %s { + %s + return %s + }()""".formatted( + returnType, + nilCheck, + someWrapIfRequired.formatted(dereferenceIfRequired, dataSource) + ); + } + + @Override + public String doubleShape(DoubleShape shape) { + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + writer.addUseImports(SmithyGoDependency.stdlib("encoding/binary")); + writer.addUseImports(SmithyGoDependency.MATH); + + String nilWrapIfRequired = "nil"; + String someWrapIfRequired = "%s"; + String returnType = "interface {}"; + if (this.isOptional) { + nilWrapIfRequired = "Wrappers.Companion_Option_.Create_None_()"; + someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s)"; + returnType = "Wrappers.Option"; } - @Override - public String unionShape(UnionShape shape) { - final String internalDafnyType = DafnyNameResolver.getDafnyType(shape, context.symbolProvider().toSymbol(shape)); - writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); - final String functionInit = """ - func() Wrappers.Option { - switch %s.(type) {""".formatted(dataSource); - StringBuilder eachMemberInUnion = new StringBuilder(); - for (var member : shape.getAllMembers().values()) { - final String memberName = context.symbolProvider().toMemberName(member); - final Shape targetShape = context.model().expectShape(member.getTarget()); - final String someWrapIfRequired = "Wrappers.Companion_Option_.Create_Some_(%s(%s))"; - final String baseType = DafnyNameResolver.getDafnyType(targetShape, context.symbolProvider().toSymbol(targetShape)); - eachMemberInUnion.append(""" - case *%s.%s: - var companion = %s - var inputToConversion = %s - return %s - """.formatted( - SmithyNameResolver.smithyTypesNamespace(shape), - context.symbolProvider().toMemberName(member), - internalDafnyType.replace(shape.getId().getName(), "CompanionStruct_" + shape.getId().getName() + "_{}"), - targetShape.accept( - new SmithyToDafnyShapeVisitor( - context, dataSource + ".(*" + SmithyNameResolver.smithyTypesNamespace(shape) + "." + context.symbolProvider().toMemberName(member) + ").Value", writer, isConfigShape, true, false - ) - ), - someWrapIfRequired.formatted( - DafnyNameResolver.getDafnyCreateFuncForUnionMemberShape(shape, memberName), - "inputToConversion.UnwrapOr(nil)%s".formatted(baseType != "" ? ".(" + baseType + ")" : "") - ) - )); - } - final String defaultCase = """ - default: - panic("Unhandled union type") - } - }()"""; - return """ - %s - %s - %s""".formatted( - functionInit, - eachMemberInUnion, - defaultCase - ); + var dereferenceIfRequired = isPointerType ? "*" : ""; + var nilCheck = ""; + if (isPointerType) { + nilCheck = + "if %s == nil {return %s}".formatted(dataSource, nilWrapIfRequired); } - @Override - public String timestampShape(TimestampShape shape) { - return "Wrappers.Companion_Option_.Create_None_()"; + return """ + func () %s { + %s + var bits = math.Float64bits(%s%s) + var bytes = make([]byte, 8) + binary.LittleEndian.PutUint64(bytes, bits) + var v []interface{} + for _, e := range bytes { + v = append(v, e) + } + return %s; + }()""".formatted( + returnType, + nilCheck, + dereferenceIfRequired, + dataSource, + someWrapIfRequired.formatted("dafny.SeqOf(v...)") + ); + } + + @Override + public String unionShape(UnionShape shape) { + final String internalDafnyType = DafnyNameResolver.getDafnyType( + shape, + context.symbolProvider().toSymbol(shape) + ); + writer.addImportFromModule("github.com/dafny-lang/DafnyRuntimeGo", "dafny"); + final String functionInit = + """ + func() Wrappers.Option { + switch %s.(type) {""".formatted(dataSource); + StringBuilder eachMemberInUnion = new StringBuilder(); + for (var member : shape.getAllMembers().values()) { + final String memberName = context.symbolProvider().toMemberName(member); + final Shape targetShape = context.model().expectShape(member.getTarget()); + final String someWrapIfRequired = + "Wrappers.Companion_Option_.Create_Some_(%s(%s))"; + final String baseType = DafnyNameResolver.getDafnyType( + targetShape, + context.symbolProvider().toSymbol(targetShape) + ); + eachMemberInUnion.append( + """ + case *%s.%s: + var companion = %s + var inputToConversion = %s + return %s + """.formatted( + SmithyNameResolver.smithyTypesNamespace(shape), + context.symbolProvider().toMemberName(member), + internalDafnyType.replace( + shape.getId().getName(), + "CompanionStruct_" + shape.getId().getName() + "_{}" + ), + targetShape.accept( + new SmithyToDafnyShapeVisitor( + context, + dataSource + + ".(*" + + SmithyNameResolver.smithyTypesNamespace(shape) + + "." + + context.symbolProvider().toMemberName(member) + + ").Value", + writer, + isConfigShape, + true, + false + ) + ), + someWrapIfRequired.formatted( + DafnyNameResolver.getDafnyCreateFuncForUnionMemberShape( + shape, + memberName + ), + "inputToConversion.UnwrapOr(nil)%s".formatted( + baseType != "" ? ".(" + baseType + ")" : "" + ) + ) + ) + ); } -} \ No newline at end of file + final String defaultCase = + """ + default: + panic("Unhandled union type") + } + }()"""; + return """ + %s + %s + %s""".formatted(functionInit, eachMemberInUnion, defaultCase); + } + + @Override + public String timestampShape(TimestampShape shape) { + return "Wrappers.Companion_Option_.Create_None_()"; + } +} diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/utils/GoCodegenUtils.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/utils/GoCodegenUtils.java index 086859e8ee..00f7ade743 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/utils/GoCodegenUtils.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/utils/GoCodegenUtils.java @@ -13,36 +13,50 @@ public class GoCodegenUtils { - public static String getType(Symbol symbol, ServiceTrait serviceTrait) { - if (symbol.getProperty(SymbolUtils.GO_ELEMENT_TYPE, Symbol.class).isEmpty()) { - return SmithyNameResolver.getSmithyTypeAws(serviceTrait, symbol, true); - } - var type = getType(symbol.expectProperty(SymbolUtils.GO_ELEMENT_TYPE, Symbol.class), serviceTrait); - if(symbol.getProperty(SymbolUtils.GO_MAP).isPresent()) { - return "map[string]" + type; - } - if (symbol.getProperty(SymbolUtils.GO_SLICE).isPresent()) { - return "[]" + type; - } - throw new RuntimeException("Failed to determine shape type"); + public static String getType(Symbol symbol, ServiceTrait serviceTrait) { + if ( + symbol.getProperty(SymbolUtils.GO_ELEMENT_TYPE, Symbol.class).isEmpty() + ) { + return SmithyNameResolver.getSmithyTypeAws(serviceTrait, symbol, true); } - - public static Symbol getRootSymbol(Symbol symbol) { - if (symbol.getProperty(SymbolUtils.GO_ELEMENT_TYPE, Symbol.class).isEmpty()) { - return symbol; - } - return getRootSymbol(symbol.expectProperty(SymbolUtils.GO_ELEMENT_TYPE, Symbol.class)); + var type = getType( + symbol.expectProperty(SymbolUtils.GO_ELEMENT_TYPE, Symbol.class), + serviceTrait + ); + if (symbol.getProperty(SymbolUtils.GO_MAP).isPresent()) { + return "map[string]" + type; + } + if (symbol.getProperty(SymbolUtils.GO_SLICE).isPresent()) { + return "[]" + type; } + throw new RuntimeException("Failed to determine shape type"); + } - public static boolean isOperationStruct(Model model, Shape shape) { - NeighborProvider provider = NeighborProviderIndex.of(model).getReverseProvider(); - for (Relationship relationship : provider.getNeighbors(shape)) { - RelationshipType relationshipType = relationship.getRelationshipType(); - if (relationshipType == RelationshipType.INPUT || relationshipType == RelationshipType.OUTPUT) { - return true; - } - } + public static Symbol getRootSymbol(Symbol symbol) { + if ( + symbol.getProperty(SymbolUtils.GO_ELEMENT_TYPE, Symbol.class).isEmpty() + ) { + return symbol; + } + return getRootSymbol( + symbol.expectProperty(SymbolUtils.GO_ELEMENT_TYPE, Symbol.class) + ); + } - return false; + public static boolean isOperationStruct(Model model, Shape shape) { + NeighborProvider provider = NeighborProviderIndex + .of(model) + .getReverseProvider(); + for (Relationship relationship : provider.getNeighbors(shape)) { + RelationshipType relationshipType = relationship.getRelationshipType(); + if ( + relationshipType == RelationshipType.INPUT || + relationshipType == RelationshipType.OUTPUT + ) { + return true; + } } + + return false; + } }