Skip to content

Commit 0c51f1a

Browse files
authored
feat!: add new v30 changes (#93)
* feat(synonyms): add new synonym set classes * feat(client): register synonym set classes to client object * feat(test): add helper method to skip deprecated tests * feat(test): add util for creating test synonym sets * feat(test): cleanup synonym sets after tests * test(synonyms): add tests for synonym sets * fix(test): skip old synonym tests on v30 and above * feat(synonyms): add deprecation logging and lsp info on old synonyms * feat(serializer): add custom serializer for analytics rules * feat(analytics): update analytic event class to new api * feat(analytics): update analytic rule classes to new api * feat(test): add test helper methods for new analytics * test(analytics): test new analytics api implementation * refactor(test): use `Arrays.asList` instead of `List.of` for java 8 compat * chore(test): only remove existing resources on teardown * chore(test): remove old curation tests * chore(test): remove old curation test helpers * chore(curation): remove old override classes * feat(curation): add curation set classes * test(curation): test curation sets * ci: update typesense to v30 * fix(test): handle both prefixed and non-prefixed versions on debug check
1 parent 9221a98 commit 0c51f1a

24 files changed

+1166
-311
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
java: ['8', '11', '17']
1717
services:
1818
typesense:
19-
image: typesense/typesense:28.0.rc36
19+
image: typesense/typesense:30.0.alpha1
2020
ports:
2121
- 8108:8108/tcp
2222
volumes:
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package org.typesense.api;
22

3+
import org.typesense.model.AnalyticsEvent;
34
import org.typesense.model.AnalyticsEventCreateResponse;
4-
import org.typesense.model.AnalyticsEventCreateSchema;
5-
5+
import org.typesense.model.AnalyticsEventsResponse;
6+
import java.util.Map;
67

78
public class AnalyticsEvents {
89
private final ApiCall apiCall;
@@ -12,7 +13,11 @@ public AnalyticsEvents(ApiCall apiCall) {
1213
this.apiCall = apiCall;
1314
}
1415

15-
public AnalyticsEventCreateResponse create(AnalyticsEventCreateSchema event) throws Exception {
16+
public AnalyticsEventCreateResponse create(AnalyticsEvent event) throws Exception {
1617
return this.apiCall.post(RESOURCE_PATH, event, null, AnalyticsEventCreateResponse.class);
1718
}
19+
20+
public AnalyticsEventsResponse retrieve(Map<String, Object> params) throws Exception {
21+
return this.apiCall.get(RESOURCE_PATH, params, AnalyticsEventsResponse.class);
22+
}
1823
}
Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,42 @@
11
package org.typesense.api;
22

33
import org.typesense.api.utils.URLEncoding;
4-
import org.typesense.model.AnalyticsRuleDeleteResponse;
5-
import org.typesense.model.AnalyticsRuleSchema;
4+
import org.typesense.model.AnalyticsRuleUpdate;
65

76
public class AnalyticsRule {
87
private final ApiCall apiCall;
98
private final String ruleId;
9+
private final AnalyticsRuleSerializer serializer;
1010

1111
public AnalyticsRule(String ruleId, ApiCall apiCall) {
12-
this.apiCall = apiCall;
12+
this.apiCall = apiCall;
1313
this.ruleId = ruleId;
14+
this.serializer = new AnalyticsRuleSerializer();
15+
}
16+
17+
public AnalyticsRule(String ruleId, ApiCall apiCall, AnalyticsRuleSerializer serializer) {
18+
this.apiCall = apiCall;
19+
this.ruleId = ruleId;
20+
this.serializer = serializer;
1421
}
1522

23+
public org.typesense.model.AnalyticsRule retrieve() throws Exception {
24+
String response = this.apiCall.get(this.getEndpoint(), null, String.class);
25+
return serializer.parseFromJson(response);
26+
}
1627

17-
public AnalyticsRuleSchema retrieve() throws Exception {
18-
return this.apiCall.get(this.getEndpoint(), null, AnalyticsRuleSchema.class);
28+
public org.typesense.model.AnalyticsRule delete() throws Exception {
29+
String response = this.apiCall.delete(this.getEndpoint(), null, String.class);
30+
org.typesense.model.AnalyticsRule result = new org.typesense.model.AnalyticsRule();
31+
result.name(this.ruleId);
32+
return result;
1933
}
2034

21-
public AnalyticsRuleDeleteResponse delete() throws Exception {
22-
return this.apiCall.delete(this.getEndpoint(), null, AnalyticsRuleDeleteResponse.class);
35+
public org.typesense.model.AnalyticsRule update(AnalyticsRuleUpdate rule) throws Exception {
36+
return this.apiCall.put(this.getEndpoint(), rule, null, org.typesense.model.AnalyticsRule.class);
2337
}
2438

2539
private String getEndpoint() {
2640
return AnalyticsRules.RESOURCE_PATH + "/" + URLEncoding.encodeURIComponent(ruleId);
2741
}
28-
2942
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package org.typesense.api;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.fasterxml.jackson.databind.DeserializationFeature;
6+
import org.typesense.model.AnalyticsRule;
7+
import org.typesense.model.AnalyticsRuleCreate;
8+
import org.typesense.model.AnalyticsRuleCreateParams;
9+
import java.util.List;
10+
import java.util.ArrayList;
11+
12+
/**
13+
* Serializer for AnalyticsRule objects to avoid Jackson type discriminator issues
14+
*/
15+
public class AnalyticsRuleSerializer {
16+
17+
private final ObjectMapper objectMapper;
18+
19+
public AnalyticsRuleSerializer() {
20+
this.objectMapper = new ObjectMapper();
21+
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
22+
this.objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
23+
}
24+
25+
/**
26+
* Parse a single AnalyticsRule from JsonNode
27+
*/
28+
public AnalyticsRule parseFromJsonNode(JsonNode node) {
29+
AnalyticsRule rule = new AnalyticsRule();
30+
31+
if (node.has("name")) {
32+
rule.name(node.get("name").asText());
33+
}
34+
35+
if (node.has("type")) {
36+
String typeStr = node.get("type").asText();
37+
if ("counter".equals(typeStr)) {
38+
rule.type(AnalyticsRuleCreate.TypeEnum.COUNTER);
39+
} else if ("popular_queries".equals(typeStr)) {
40+
rule.type(AnalyticsRuleCreate.TypeEnum.POPULAR_QUERIES);
41+
} else if ("nohits_queries".equals(typeStr)) {
42+
rule.type(AnalyticsRuleCreate.TypeEnum.NOHITS_QUERIES);
43+
} else if ("log".equals(typeStr)) {
44+
rule.type(AnalyticsRuleCreate.TypeEnum.LOG);
45+
}
46+
}
47+
48+
if (node.has("collection")) {
49+
rule.collection(node.get("collection").asText());
50+
}
51+
52+
if (node.has("event_type")) {
53+
rule.eventType(node.get("event_type").asText());
54+
}
55+
56+
if (node.has("rule_tag")) {
57+
rule.ruleTag(node.get("rule_tag").asText());
58+
}
59+
60+
if (node.has("params")) {
61+
JsonNode paramsNode = node.get("params");
62+
AnalyticsRuleCreateParams params = new AnalyticsRuleCreateParams();
63+
64+
if (paramsNode.has("counter_field")) {
65+
params.counterField(paramsNode.get("counter_field").asText());
66+
}
67+
68+
if (paramsNode.has("weight")) {
69+
params.weight(paramsNode.get("weight").asInt());
70+
}
71+
72+
if (paramsNode.has("destination_collection")) {
73+
params.destinationCollection(paramsNode.get("destination_collection").asText());
74+
}
75+
76+
if (paramsNode.has("limit")) {
77+
params.limit(paramsNode.get("limit").asInt());
78+
}
79+
80+
if (paramsNode.has("capture_search_requests")) {
81+
params.captureSearchRequests(paramsNode.get("capture_search_requests").asBoolean());
82+
}
83+
84+
if (paramsNode.has("meta_fields")) {
85+
List<String> metaFields = new ArrayList<>();
86+
JsonNode metaFieldsNode = paramsNode.get("meta_fields");
87+
if (metaFieldsNode.isArray()) {
88+
for (JsonNode metaField : metaFieldsNode) {
89+
metaFields.add(metaField.asText());
90+
}
91+
}
92+
params.metaFields(metaFields);
93+
}
94+
95+
if (paramsNode.has("expand_query")) {
96+
params.expandQuery(paramsNode.get("expand_query").asBoolean());
97+
}
98+
99+
rule.params(params);
100+
}
101+
102+
return rule;
103+
}
104+
105+
/**
106+
* Parse AnalyticsRule from JSON string
107+
*/
108+
public AnalyticsRule parseFromJson(String jsonResponse) throws Exception {
109+
JsonNode rootNode = objectMapper.readTree(jsonResponse);
110+
return parseFromJsonNode(rootNode);
111+
}
112+
113+
/**
114+
* Parse a list of AnalyticsRule objects from JSON string
115+
*/
116+
public List<AnalyticsRule> parseListFromJson(String jsonResponse) throws Exception {
117+
JsonNode rootNode = objectMapper.readTree(jsonResponse);
118+
List<AnalyticsRule> rules = new ArrayList<>();
119+
120+
if (rootNode.isArray()) {
121+
for (JsonNode item : rootNode) {
122+
if (item.has("error")) {
123+
String error = item.get("error").asText();
124+
throw new RuntimeException("Analytics rule parsing failed: " + error);
125+
} else {
126+
AnalyticsRule rule = parseFromJsonNode(item);
127+
rules.add(rule);
128+
}
129+
}
130+
} else {
131+
// Single object response
132+
AnalyticsRule rule = parseFromJsonNode(rootNode);
133+
rules.add(rule);
134+
}
135+
136+
return rules;
137+
}
138+
}
Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,83 @@
11
package org.typesense.api;
22

3-
import org.typesense.api.utils.URLEncoding;
4-
import org.typesense.model.AnalyticsRuleSchema;
5-
import org.typesense.model.AnalyticsRuleUpsertSchema;
6-
import org.typesense.model.AnalyticsRulesRetrieveSchema;
3+
import org.typesense.model.AnalyticsRule;
4+
import org.typesense.model.AnalyticsRuleCreate;
5+
import java.util.List;
76

87
public class AnalyticsRules {
98

109
private final ApiCall apiCall;
10+
private final AnalyticsRuleSerializer serializer;
1111
public final static String RESOURCE_PATH = "/analytics/rules";
1212

1313
public AnalyticsRules(ApiCall apiCall) {
1414
this.apiCall = apiCall;
15+
this.serializer = new AnalyticsRuleSerializer();
16+
}
17+
18+
public AnalyticsRules(ApiCall apiCall, AnalyticsRuleSerializer serializer) {
19+
this.apiCall = apiCall;
20+
this.serializer = serializer;
1521
}
1622

17-
public AnalyticsRuleSchema create(AnalyticsRuleSchema rule) throws Exception {
18-
return this.apiCall.post(RESOURCE_PATH, rule, null, AnalyticsRuleSchema.class);
23+
public AnalyticsRulesResponse create(List<AnalyticsRuleCreate> rules) throws Exception {
24+
String response = this.apiCall.post(RESOURCE_PATH, rules, null, String.class);
25+
return parseCreateResponse(response);
1926
}
2027

21-
public AnalyticsRuleSchema upsert(String name, AnalyticsRuleUpsertSchema rule) throws Exception {
22-
return this.apiCall.put(RESOURCE_PATH + "/" + URLEncoding.encodeURIComponent(name), rule, null,
23-
AnalyticsRuleSchema.class);
28+
public List<AnalyticsRule> retrieve() throws Exception {
29+
String response = this.apiCall.get(RESOURCE_PATH, null, String.class);
30+
return parseRetrieveResponse(response);
2431
}
2532

26-
public AnalyticsRulesRetrieveSchema retrieve() throws Exception {
27-
return this.apiCall.get(RESOURCE_PATH, null, AnalyticsRulesRetrieveSchema.class);
33+
/**
34+
* Parse the create response which can be either a single AnalyticsRule or an array
35+
*/
36+
private AnalyticsRulesResponse parseCreateResponse(String jsonResponse) throws Exception {
37+
List<AnalyticsRule> rules = serializer.parseListFromJson(jsonResponse);
38+
39+
for (AnalyticsRule rule : rules) {
40+
if (rule.getName() == null) {
41+
throw new RuntimeException("Analytics rule creation failed: rule name is null");
42+
}
43+
}
44+
45+
return new AnalyticsRulesResponse(rules);
46+
}
47+
48+
/**
49+
* Parse the retrieve response which is always an array of AnalyticsRule objects
50+
*/
51+
private List<AnalyticsRule> parseRetrieveResponse(String jsonResponse) throws Exception {
52+
List<AnalyticsRule> rules = serializer.parseListFromJson(jsonResponse);
53+
54+
return rules;
2855
}
2956

57+
/**
58+
* Response wrapper for analytics rules operations
59+
*/
60+
public static class AnalyticsRulesResponse {
61+
private final List<AnalyticsRule> rules;
62+
63+
public AnalyticsRulesResponse(List<AnalyticsRule> rules) {
64+
this.rules = rules;
65+
}
66+
67+
public List<AnalyticsRule> getRules() {
68+
return rules;
69+
}
70+
71+
public AnalyticsRule getFirstRule() {
72+
return rules.isEmpty() ? null : rules.get(0);
73+
}
74+
75+
public int getCount() {
76+
return rules.size();
77+
}
78+
79+
public boolean isEmpty() {
80+
return rules.isEmpty();
81+
}
82+
}
3083
}

src/main/java/org/typesense/api/Client.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ public class Client {
2828
private Stopwords stopwords;
2929
private Map<String, StopwordsSet> individualStopwordsSets;
3030

31+
private SynonymSets synonymSets;
32+
private Map<String, SynonymSet> individualSynonymSets;
33+
34+
private CurationSets curationSets;
35+
private Map<String, CurationSet> individualCurationSets;
36+
3137
public Health health;
3238
public Operations operations;
3339
public Metrics metrics;
@@ -72,6 +78,10 @@ public Client(Configuration configuration){
7278
this.stemming = new Stemming(this.apiCall);
7379
this.stopwords = new Stopwords(this.apiCall);
7480
this.individualStopwordsSets = new HashMap<>();
81+
this.synonymSets = new SynonymSets(this.apiCall);
82+
this.individualSynonymSets = new HashMap<>();
83+
this.curationSets = new CurationSets(this.apiCall);
84+
this.individualCurationSets = new HashMap<>();
7585
}
7686

7787
public Collection collections(String name){
@@ -143,4 +153,34 @@ public StopwordsSet stopwords(String stopwordsSetId) {
143153
retVal = this.individualStopwordsSets.get(stopwordsSetId);
144154
return retVal;
145155
}
156+
157+
public SynonymSets synonymSets() {
158+
return this.synonymSets;
159+
}
160+
161+
public SynonymSet synonymSet(String synonymSetName) {
162+
SynonymSet retVal;
163+
164+
if (!this.individualSynonymSets.containsKey(synonymSetName)) {
165+
this.individualSynonymSets.put(synonymSetName, new SynonymSet(synonymSetName, this.apiCall));
166+
}
167+
168+
retVal = this.individualSynonymSets.get(synonymSetName);
169+
return retVal;
170+
}
171+
172+
public CurationSets curationSets() {
173+
return this.curationSets;
174+
}
175+
176+
public CurationSet curationSet(String curationSetName) {
177+
CurationSet retVal;
178+
179+
if (!this.individualCurationSets.containsKey(curationSetName)) {
180+
this.individualCurationSets.put(curationSetName, new CurationSet(curationSetName, this.apiCall));
181+
}
182+
183+
retVal = this.individualCurationSets.get(curationSetName);
184+
return retVal;
185+
}
146186
}

0 commit comments

Comments
 (0)