Skip to content

Commit

Permalink
fix: record metadata correctly for HTTP methods
Browse files Browse the repository at this point in the history
fixes #4148
  • Loading branch information
stuartwdouglas committed Jan 23, 2025
1 parent b4dea5e commit 3ec9efc
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;

import jakarta.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.ResponseHeader;
import org.jboss.resteasy.reactive.ResponseStatus;
import org.jboss.resteasy.reactive.RestPath;
Expand All @@ -27,7 +27,6 @@ public GetResponse get(@RestPath int userId, @RestPath int postId) {
.setNested(new Nested().setGoodStuff("This is good stuff"));
}


@GET
@Path("/getquery")
@ResponseHeader(name = "Get", value = "Header from FTL")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.jboss.resteasy.reactive.server.core.parameters.ParameterExtractor;
import org.jboss.resteasy.reactive.server.mapping.URITemplate;
import org.jboss.resteasy.reactive.server.processor.scanning.MethodScanner;
import org.jetbrains.annotations.NotNull;

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
Expand All @@ -26,7 +27,6 @@
import xyz.block.ftl.runtime.builtin.HttpRequest;
import xyz.block.ftl.runtime.builtin.HttpResponse;
import xyz.block.ftl.schema.v1.Array;
import xyz.block.ftl.schema.v1.Decl;
import xyz.block.ftl.schema.v1.IngressPathComponent;
import xyz.block.ftl.schema.v1.IngressPathLiteral;
import xyz.block.ftl.schema.v1.IngressPathParameter;
Expand Down Expand Up @@ -81,23 +81,25 @@ public SchemaContributorBuildItem registerHttpHandlers(
return new SchemaContributorBuildItem(new Consumer<ModuleBuilder>() {
@Override
public void accept(ModuleBuilder moduleBuilder) {
//TODO: make this composable so it is not just one big method, build items should contribute to the schema
Type stringType = Type.newBuilder().setString(xyz.block.ftl.schema.v1.String.newBuilder().build()).build();
Type pathParamType = Type.newBuilder()
.setMap(xyz.block.ftl.schema.v1.Map.newBuilder().setKey(stringType)
.setValue(stringType))
.build();
for (var endpoint : restEndpoints.getEntries()) {
//TODO: naming
var verbName = ModuleBuilder.methodToName(endpoint.getMethodInfo());
boolean base64 = false;

//TODO: handle type parameters properly
org.jboss.jandex.Type bodyParamType = VoidType.VOID;
MethodParameter[] parameters = endpoint.getResourceMethod().getParameters();

for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
var param = parameters[i];
if (param.parameterType.equals(ParameterType.BODY)) {
var httpParam = parameters[i];
if (httpParam.parameterType.equals(ParameterType.BODY)) {
bodyParamType = endpoint.getMethodInfo().parameterType(i);
break;
}
}

boolean base64 = false;
if (bodyParamType instanceof ArrayType) {
org.jboss.jandex.Type component = ((ArrayType) bodyParamType).component();
if (component instanceof PrimitiveType) {
Expand All @@ -107,21 +109,7 @@ public void accept(ModuleBuilder moduleBuilder) {

recorder.registerHttpIngress(moduleBuilder.getModuleName(), verbName, base64);

StringBuilder pathBuilder = new StringBuilder();
if (endpoint.getBasicResourceClassInfo().getPath() != null) {
pathBuilder.append(endpoint.getBasicResourceClassInfo().getPath());
}
if (endpoint.getResourceMethod().getPath() != null && !endpoint.getResourceMethod().getPath().isEmpty()) {
boolean builderEndsSlash = pathBuilder.charAt(pathBuilder.length() - 1) == '/';
boolean pathStartsSlash = endpoint.getResourceMethod().getPath().startsWith("/");
if (builderEndsSlash && pathStartsSlash) {
pathBuilder.setLength(pathBuilder.length() - 1);
} else if (!builderEndsSlash && !pathStartsSlash) {
pathBuilder.append('/');
}
pathBuilder.append(endpoint.getResourceMethod().getPath());
}
String path = pathBuilder.toString();
String path = extractPath(endpoint);
URITemplate template = new URITemplate(path, false);
List<IngressPathComponent> pathComponents = new ArrayList<>();
for (var i : template.components) {
Expand All @@ -145,54 +133,71 @@ public void accept(ModuleBuilder moduleBuilder) {
.build());
}
}
ModuleBuilder.VerbCustomization verbCustomization = new ModuleBuilder.VerbCustomization();
verbCustomization.setCustomHandling(true)
.setMetadataCallback((builder) -> {
MetadataIngress.Builder ingressBuilder = MetadataIngress.newBuilder()
.setType("http")
.setMethod(endpoint.getResourceMethod().getHttpMethod());
for (var i : pathComponents) {
ingressBuilder.addPath(i);
}
Metadata ingressMetadata = Metadata.newBuilder()
.setIngress(ingressBuilder
.build())
.build();
builder.addMetadata(ingressMetadata);
})
.setIgnoreParameter((i) -> {
return !parameters[i].parameterType.equals(ParameterType.BODY)
&& !parameters[i].parameterType.equals(ParameterType.CUSTOM);
})
.setRequestType((requestTypeParam) -> {
return Type.newBuilder()
.setRef(Ref.newBuilder().setModule(ModuleBuilder.BUILTIN)
.setName(HttpRequest.class.getSimpleName())
.addTypeParameters(requestTypeParam)
.addTypeParameters(pathParamType)
.addTypeParameters(Type.newBuilder()
.setMap(xyz.block.ftl.schema.v1.Map.newBuilder().setKey(stringType)
.setValue(Type.newBuilder()
.setArray(
Array.newBuilder().setElement(stringType)))
.build())))
.build();
})
.setResponseType((responseTypeParam) -> {
return Type.newBuilder()
.setRef(Ref.newBuilder().setModule(ModuleBuilder.BUILTIN)
.setName(HttpResponse.class.getSimpleName())
.addTypeParameters(responseTypeParam)
.addTypeParameters(Type.newBuilder().setUnit(Unit.newBuilder())))
.build();
});

//TODO: process path properly
MetadataIngress.Builder ingressBuilder = MetadataIngress.newBuilder()
.setType("http")
.setMethod(endpoint.getResourceMethod().getHttpMethod());
for (var i : pathComponents) {
ingressBuilder.addPath(i);
}
Metadata ingressMetadata = Metadata.newBuilder()
.setIngress(ingressBuilder
.build())
.build();
Type requestTypeParam = moduleBuilder.buildType(bodyParamType, true, Nullability.NOT_NULL);
Type responseTypeParam = moduleBuilder.buildType(endpoint.getMethodInfo().returnType(), true,
Nullability.NOT_NULL);
Type stringType = Type.newBuilder().setString(xyz.block.ftl.schema.v1.String.newBuilder().build()).build();
Type pathParamType = Type.newBuilder()
.setMap(xyz.block.ftl.schema.v1.Map.newBuilder().setKey(stringType)
.setValue(stringType))
.build();
moduleBuilder
.addDecls(Decl.newBuilder().setVerb(xyz.block.ftl.schema.v1.Verb.newBuilder()
.addMetadata(ingressMetadata)
.setName(verbName)
.setPos(PositionUtils.forMethod(endpoint.getMethodInfo()))
.setExport(true)
.setRequest(Type.newBuilder()
.setRef(Ref.newBuilder().setModule(ModuleBuilder.BUILTIN)
.setName(HttpRequest.class.getSimpleName())
.addTypeParameters(requestTypeParam)
.addTypeParameters(pathParamType)
.addTypeParameters(Type.newBuilder()
.setMap(xyz.block.ftl.schema.v1.Map.newBuilder().setKey(stringType)
.setValue(Type.newBuilder()
.setArray(
Array.newBuilder().setElement(stringType)))
.build())))
.build())
.setResponse(Type.newBuilder()
.setRef(Ref.newBuilder().setModule(ModuleBuilder.BUILTIN)
.setName(HttpResponse.class.getSimpleName())
.addTypeParameters(responseTypeParam)
.addTypeParameters(Type.newBuilder().setUnit(Unit.newBuilder())))
.build()))
.build());
moduleBuilder.registerVerbMethod(endpoint.getMethodInfo(), endpoint.getActualClassInfo().name().toString(),
false, ModuleBuilder.BodyType.ALLOWED, verbCustomization);
}
}
});

}

private static @NotNull String extractPath(ResteasyReactiveResourceMethodEntriesBuildItem.Entry endpoint) {
StringBuilder pathBuilder = new StringBuilder();
if (endpoint.getBasicResourceClassInfo().getPath() != null) {
pathBuilder.append(endpoint.getBasicResourceClassInfo().getPath());
}
if (endpoint.getResourceMethod().getPath() != null && !endpoint.getResourceMethod().getPath().isEmpty()) {
boolean builderEndsSlash = pathBuilder.charAt(pathBuilder.length() - 1) == '/';
boolean pathStartsSlash = endpoint.getResourceMethod().getPath().startsWith("/");
if (builderEndsSlash && pathStartsSlash) {
pathBuilder.setLength(pathBuilder.length() - 1);
} else if (!builderEndsSlash && !pathStartsSlash) {
pathBuilder.append('/');
}
pathBuilder.append(endpoint.getResourceMethod().getPath());
}
return pathBuilder.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;

import org.jboss.jandex.AnnotationTarget;
Expand Down Expand Up @@ -179,14 +179,21 @@ public static Class<?> loadClass(org.jboss.jandex.Type param) throws ClassNotFou
default:
throw new RuntimeException("Unknown primitive type " + param.asPrimitiveType().primitive());
}
} else {
return loadClass(array.componentType()).arrayType();
}
}
throw new RuntimeException("Unknown type " + param.kind());

}

public void registerVerbMethod(MethodInfo method, String className,
boolean exported, BodyType bodyType, Consumer<Verb.Builder> metadataCallback) {
boolean exported, BodyType bodyType) {
registerVerbMethod(method, className, exported, bodyType, new VerbCustomization());
}

public void registerVerbMethod(MethodInfo method, String className,
boolean exported, BodyType bodyType, VerbCustomization customization) {
try {
List<Class<?>> parameterTypes = new ArrayList<>();
List<VerbRegistry.ParameterSupplier> paramMappers = new ArrayList<>();
Expand All @@ -202,6 +209,9 @@ public void registerVerbMethod(MethodInfo method, String className,
var pos = -1;
for (var param : method.parameters()) {
pos++;
if (customization.ignoreParameter.apply(pos)) {
continue;
}
if (param.hasAnnotation(Secret.class)) {
Class<?> paramType = ModuleBuilder.loadClass(param.type());
parameterTypes.add(paramType);
Expand Down Expand Up @@ -276,18 +286,19 @@ public void registerVerbMethod(MethodInfo method, String className,
verbBuilder.addMetadata(Metadata.newBuilder().setPublisher(publisherMetadata));
}

recorder.registerVerb(moduleName, verbName, method.name(), parameterTypes,
Class.forName(className, false, Thread.currentThread().getContextClassLoader()), paramMappers,
method.returnType() == VoidType.VOID);

if (!customization.customHandling) {
recorder.registerVerb(moduleName, verbName, method.name(), parameterTypes,
Class.forName(className, false, Thread.currentThread().getContextClassLoader()), paramMappers,
method.returnType() == VoidType.VOID);
}
verbBuilder.setName(verbName)
.setExport(exported)
.setPos(PositionUtils.forMethod(method))
.setRequest(buildType(bodyParamType, exported, bodyParamNullability))
.setResponse(buildType(method.returnType(), exported, method))
.setRequest(customization.requestType.apply(buildType(bodyParamType, exported, bodyParamNullability)))
.setResponse(customization.responseType.apply(buildType(method.returnType(), exported, method)))
.addAllComments(comments.getComments(verbName));
if (metadataCallback != null) {
metadataCallback.accept(verbBuilder);
if (customization.metadataCallback != null) {
customization.metadataCallback.accept(verbBuilder);
}
addDecls(Decl.newBuilder().setVerb(verbBuilder)
.build());
Expand Down Expand Up @@ -681,4 +692,58 @@ String validateName(String className, String name) {
}
return name;
}

public static class VerbCustomization {
private Consumer<Verb.Builder> metadataCallback = b -> {
};
private Function<Integer, Boolean> ignoreParameter = i -> false;
private Function<Type, Type> requestType = Function.identity();
private Function<Type, Type> responseType = Function.identity();
private boolean customHandling;

public Consumer<Verb.Builder> getMetadataCallback() {
return metadataCallback;
}

public VerbCustomization setMetadataCallback(Consumer<Verb.Builder> metadataCallback) {
this.metadataCallback = metadataCallback;
return this;
}

public Function<Integer, Boolean> getIgnoreParameter() {
return ignoreParameter;
}

public VerbCustomization setIgnoreParameter(Function<Integer, Boolean> ignoreParameter) {
this.ignoreParameter = ignoreParameter;
return this;
}

public Function<Type, Type> getRequestType() {
return requestType;
}

public VerbCustomization setRequestType(Function<Type, Type> requestType) {
this.requestType = requestType;
return this;
}

public Function<Type, Type> getResponseType() {
return responseType;
}

public VerbCustomization setResponseType(Function<Type, Type> responseType) {
this.responseType = responseType;
return this;
}

public boolean isCustomHandling() {
return customHandling;
}

public VerbCustomization setCustomHandling(boolean customHandling) {
this.customHandling = customHandling;
return this;
}
}
}
Loading

0 comments on commit 3ec9efc

Please sign in to comment.