• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

valkyrjaio / sindri-java / 27918874223

21 Jun 2026 10:03PM UTC coverage: 95.037% (-5.0%) from 100.0%
27918874223

Pull #31

github

web-flow
Merge 6e0924991 into e3a362b61
Pull Request #31: [Generation] Fix HTTP routing data and framework-provider resolution

322 of 358 branches covered (89.94%)

Branch coverage included in aggregate %.

130 of 168 new or added lines in 5 files covered. (77.38%)

1095 of 1133 relevant lines covered (96.65%)

4.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

41.51
/src/main/java/io/sindri/ast/HttpRouteParameterReader.java
1
/*
2
 * This file is part of the Sindri package.
3
 *
4
 * (c) Melech Mizrachi <melechmizrachi@gmail.com>
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9

10
package io.sindri.ast;
11

12
import com.github.javaparser.ast.body.MethodDeclaration;
13
import com.github.javaparser.ast.expr.AnnotationExpr;
14
import com.github.javaparser.ast.expr.ArrayInitializerExpr;
15
import com.github.javaparser.ast.expr.Expression;
16
import com.github.javaparser.ast.expr.MemberValuePair;
17
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
18
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
19
import io.sindri.ast.abstract_.AstReader;
20
import io.sindri.ast.contract.HttpRouteParameterReaderContract;
21
import io.sindri.ast.data.HttpParameterData;
22
import java.util.ArrayList;
23
import java.util.List;
24
import java.util.Map;
25
import org.jspecify.annotations.Nullable;
26

27
/**
28
 * Reads {@code @Parameter} / {@code @Parameters} annotations off an HTTP controller method into
29
 * {@link HttpParameterData}. A parameter's {@code regex} may be a string literal or a reference to a
30
 * {@code Regex} constant (e.g. {@code Regex.ALPHA}); the latter is resolved to its value by
31
 * reflecting the constant off the classpath, mirroring what the framework sees at runtime.
32
 */
33
public class HttpRouteParameterReader extends AstReader
3✔
34
        implements HttpRouteParameterReaderContract {
35

36
    @Override
37
    public List<HttpParameterData> updateParameters(
38
            MethodDeclaration method, Map<String, String> importMap, String pkg) {
39
        List<HttpParameterData> parameters = new ArrayList<>();
4✔
40

41
        for (AnnotationExpr annotation : method.getAnnotations()) {
11✔
42
            switch (annotation.getNameAsString()) {
9!
43
                case "Parameter" -> addParameter(parameters, annotation, importMap, pkg);
7✔
44
                case "Parameters" -> {
NEW
45
                    for (AnnotationExpr inner : extractParametersArray(annotation)) {
×
NEW
46
                        addParameter(parameters, inner, importMap, pkg);
×
NEW
47
                    }
×
NEW
48
                }
×
49
                default -> {}
50
            }
51
        }
1✔
52

53
        return parameters;
2✔
54
    }
55

56
    private void addParameter(
57
            List<HttpParameterData> parameters,
58
            AnnotationExpr annotation,
59
            Map<String, String> importMap,
60
            String pkg) {
61
        HttpParameterData parameter = parseParameter(annotation, importMap, pkg);
6✔
62
        if (parameter != null) {
2!
63
            parameters.add(parameter);
4✔
64
        }
65
    }
1✔
66

67
    private @Nullable HttpParameterData parseParameter(
68
            AnnotationExpr annotation, Map<String, String> importMap, String pkg) {
69
        if (!(annotation instanceof NormalAnnotationExpr normal)) {
7!
NEW
70
            return null;
×
71
        }
72

73
        String name = "";
2✔
74
        String regex = "";
2✔
75
        boolean isOptional = false;
2✔
76
        boolean shouldCapture = true;
2✔
77

78
        for (MemberValuePair pair : normal.getPairs()) {
11✔
79
            switch (pair.getNameAsString()) {
9!
80
                case "name" -> name = extractStringLiteral(pair.getValue());
6✔
81
                case "regex" -> regex = resolveRegexValue(pair.getValue(), importMap, pkg);
8✔
NEW
82
                case "isOptional" -> isOptional = extractBoolean(pair.getValue());
×
NEW
83
                case "shouldCapture" -> shouldCapture = extractBoolean(pair.getValue());
×
84
                default -> {}
85
            }
86
        }
1✔
87

88
        if (name.isEmpty()) {
3!
NEW
89
            return null;
×
90
        }
91

92
        return new HttpParameterData(name, regex, null, isOptional, shouldCapture);
9✔
93
    }
94

95
    /** Resolve a parameter regex: a string literal directly, or a {@code Regex.*} constant value. */
96
    protected String resolveRegexValue(
97
            Expression expr, Map<String, String> importMap, String pkg) {
98
        if (expr.isStringLiteralExpr()) {
3!
NEW
99
            return expr.asStringLiteralExpr().getValue();
×
100
        }
101

102
        if (expr.isFieldAccessExpr()) {
3!
103
            String fqn =
2✔
104
                    resolveClassName(expr.asFieldAccessExpr().getScope().toString(), importMap, pkg);
7✔
105
            return reflectStaticStringField(fqn, expr.asFieldAccessExpr().getNameAsString());
7✔
106
        }
107

NEW
108
        return "";
×
109
    }
110

111
    private String reflectStaticStringField(String classFqn, String fieldName) {
112
        try {
113
            Object value = Class.forName(classFqn).getField(fieldName).get(null);
7✔
114
            return value != null ? value.toString() : "";
6!
NEW
115
        } catch (ReflectiveOperationException e) {
×
NEW
116
            return "";
×
117
        }
118
    }
119

120
    private boolean extractBoolean(Expression expr) {
NEW
121
        return expr.isBooleanLiteralExpr() && expr.asBooleanLiteralExpr().getValue();
×
122
    }
123

124
    private List<AnnotationExpr> extractParametersArray(AnnotationExpr annotation) {
NEW
125
        Expression value = null;
×
NEW
126
        if (annotation instanceof SingleMemberAnnotationExpr single) {
×
NEW
127
            value = single.getMemberValue();
×
NEW
128
        } else if (annotation instanceof NormalAnnotationExpr normal) {
×
NEW
129
            for (MemberValuePair pair : normal.getPairs()) {
×
NEW
130
                if (pair.getNameAsString().equals("value")) {
×
NEW
131
                    value = pair.getValue();
×
132
                }
NEW
133
            }
×
134
        }
135

NEW
136
        List<AnnotationExpr> result = new ArrayList<>();
×
NEW
137
        if (value instanceof ArrayInitializerExpr array) {
×
NEW
138
            for (Expression item : array.getValues()) {
×
NEW
139
                if (item instanceof AnnotationExpr inner) {
×
NEW
140
                    result.add(inner);
×
141
                }
NEW
142
            }
×
NEW
143
        } else if (value instanceof AnnotationExpr inner) {
×
NEW
144
            result.add(inner);
×
145
        }
146

NEW
147
        return result;
×
148
    }
149
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc