public Model preprocessModel()

in codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddS3Config.java [108:233]


    public Model preprocessModel(Model model, TypeScriptSettings settings) {
        ServiceShape serviceShape = settings.getService(model);
        if (!isS3(serviceShape)) {
            return model;
        }

        Model.Builder modelBuilder = model.toBuilder();
        boolean hasRuleset = !model.getServiceShapesWithTrait(EndpointRuleSetTrait.class).isEmpty();

        TopDownIndex topDownIndex = TopDownIndex.of(model);
        Set<StructureShape> inputShapes = new HashSet<>();
        for (OperationShape operationShape : topDownIndex.getContainedOperations(serviceShape)) {
            if (NON_BUCKET_ENDPOINT_OPERATIONS.contains(operationShape.getId().getName(serviceShape))) {
                continue;
            }
            operationShape.getInput().ifPresent(inputShapeId -> {
                StructureShape inputShape = model.expectShape(inputShapeId, StructureShape.class);
                inputShape.getMember("Bucket").ifPresent(bucketMember -> {
                    bucketMember.getTrait(DocumentationTrait.class).ifPresent(documentationTrait -> {
                        StructureShape.Builder inputShapeBuilder = inputShape.toBuilder();
                        MemberShape.Builder builder = MemberShape.shapeToBuilder(bucketMember);
                        String newDocString = documentationTrait.getValue() + "\n" + CRT_NOTIFICATION;
                        MemberShape newMemberShape = builder.addTrait(new DocumentationTrait(newDocString)).build();
                        inputShapeBuilder.addMember(newMemberShape);
                        inputShapes.add(inputShapeBuilder.build());
                    });
                });
            });
        }
        LOGGER.info("Patching " + inputShapes.size() + " input shapes with CRT notification");

        boolean expiresShapeIsPresent = model.getShape(ShapeId.from("com.amazonaws.s3#Expires")).isPresent();
        if (expiresShapeIsPresent) {
            // ExpiresString customization part 1:
            // enforce that "com.amazonaws.s3#Expires" retains type=timestamp.
            // add a shape "com.amazonaws.s3#ExpiresString" of type=string.
            Shape expiresShape = model.getShape(ShapeId.from("com.amazonaws.s3#Expires")).get();
            TimestampShape expiresTimestampShape = TimestampShape.builder()
                .id(expiresShape.getId())
                .build();
            StringShape expiresStringShape = StringShape.builder()
                .id("com.amazonaws.s3#ExpiresString")
                .build();
            modelBuilder
                .removeShape(expiresShape.getId())
                .addShapes(expiresTimestampShape, expiresStringShape);

            // ExpiresString customization part 2:
            // for any output shape member targeting Expires, add a member ExpiresString targeting ExpiresString.
            // and mark Expires deprecated in favor of ExpiresString.
            // move Expires documentation trait to ExpiresString.
            // set the httpHeader trait of ExpiresString to be ExpiresString.
            // SDK middleware will take care of copying expires header to expiresstring header prior to deserialization.
            for (OperationShape operationShape : topDownIndex.getContainedOperations(serviceShape)) {
                if (operationShape.getOutput().isEmpty()) {
                    continue;
                }
                StructureShape structureShape = model.expectShape(
                    operationShape.getOutputShape(), StructureShape.class
                );

                Set<Map.Entry<String, MemberShape>> memberEntries = structureShape
                    .getAllMembers()
                    .entrySet();
                StructureShape.Builder structureShapeBuilder = structureShape.toBuilder();

                boolean isTargetingExpires = structureShape
                    .getAllMembers()
                    .values()
                    .stream()
                    .anyMatch(memberShape -> memberShape.getTarget().equals(expiresShape.getId()));

                if (isTargetingExpires) {
                    for (Map.Entry<String, MemberShape> entry : memberEntries) {
                        String memberName = entry.getKey();
                        MemberShape memberShape = entry.getValue();

                        Optional<HttpHeaderTrait> httpHeader = memberShape.getTrait(HttpHeaderTrait.class);
                        Optional<DocumentationTrait> doc = memberShape.getTrait(DocumentationTrait.class);

                        if (memberShape.getTarget().equals(expiresShape.getId())
                            && httpHeader.isPresent()) {
                            structureShapeBuilder
                                .removeMember(memberName)
                                .addMember(
                                    memberName,
                                    expiresTimestampShape.getId(),
                                    (m) -> {
                                        m
                                            .addTrait(new DocumentationTrait("Deprecated in favor of ExpiresString."))
                                            .addTrait(httpHeader.get())
                                            .addTrait(DeprecatedTrait.builder().build());
                                    }
                                )
                                .addMember(
                                    "ExpiresString",
                                    expiresStringShape.getId(),
                                    (m) -> {
                                        m.addTrait(new HttpHeaderTrait("ExpiresString"));
                                        doc.ifPresent(m::addTrait);
                                    }
                                );
                        } else {
                            // This is done to preserve the member order
                            // and insert ExpiresString adjacent to Expires.
                            structureShapeBuilder
                                .removeMember(memberName)
                                .addMember(memberName, memberShape.getTarget(), (m) -> {
                                    m.addTraits(memberShape.getAllTraits().values());
                                });
                        }
                    }
                    modelBuilder
                        .addShape(structureShapeBuilder.build());
                }
            }
        }

        Model builtModel = modelBuilder.addShapes(inputShapes).build();
        if (hasRuleset) {
            ModelTransformer.create().mapShapes(
                builtModel, AddS3Config::removeHostPrefixTrait
            );
        }
        return builtModel;
    }