Skip to content

Commit b0452ed

Browse files
committed
fix(directory): align Directory facets
1 parent 9846248 commit b0452ed

4 files changed

Lines changed: 77 additions & 5 deletions

File tree

‎odp-directory/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ either InFlow or Solana through MPP. A payment filter with no options matches an
5050
advertises that payment protocol.
5151

5252
The response includes structured facets for enrollment protocols, keywords, operations, payment
53-
protocols, and payment options. Use them to refine a user or Agent query without downloading a
54-
global vocabulary.
53+
protocols, payment options, and trust protocols. Use them to refine a user or Agent query without
54+
downloading a global vocabulary.
5555

5656
Compatible results may advertise protocol names unknown to this library. The client filters those
5757
descriptors and preserves recognized enrollment, payment, and trust descriptors, including TAP.

‎odp-directory/src/main/java/org/offeringprotocol/odp/directory/DirectoryClient.java‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,14 @@ static DirectoryModels.SearchPage decodeSearchPage(String json) {
178178
if (value == null) {
179179
throw new IllegalArgumentException("Directory response is empty");
180180
}
181-
return OdpJson.treeToValue(value, DirectoryModels.SearchPage.class);
181+
DirectoryModels.SearchPage page = OdpJson.treeToValue(value, DirectoryModels.SearchPage.class);
182+
if (page.facets() != null
183+
&& page.facets().trust().stream()
184+
.anyMatch(facet -> facet.value() == null
185+
|| !"tap".equals(facet.value().name()))) {
186+
throw new IllegalArgumentException("Directory trust facets are invalid");
187+
}
188+
return page;
182189
} catch (IllegalArgumentException exception) {
183190
throw new IllegalArgumentException("Directory response is invalid", exception);
184191
}

‎odp-directory/src/main/java/org/offeringprotocol/odp/directory/DirectoryModels.java‎

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.annotation.JsonAnyGetter;
44
import com.fasterxml.jackson.annotation.JsonAnySetter;
5+
import com.fasterxml.jackson.annotation.JsonInclude;
56
import com.fasterxml.jackson.annotation.JsonProperty;
67
import java.time.Instant;
78
import java.util.Collections;
@@ -24,16 +25,33 @@ public record SearchRequest(String query, ServiceFilters filters, Integer limit)
2425
}
2526
}
2627

28+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
2729
public record ServiceFilters(
2830
List<ServiceDocument.EnrollmentProtocol> enrollment,
2931
List<String> keywords,
3032
List<OperationFilter> operations,
31-
List<PaymentFilter> payments) {
33+
List<PaymentFilter> payments,
34+
List<ServiceDocument.TrustProtocol> trust) {
35+
public ServiceFilters(
36+
List<ServiceDocument.EnrollmentProtocol> enrollment,
37+
List<String> keywords,
38+
List<OperationFilter> operations,
39+
List<PaymentFilter> payments) {
40+
this(enrollment, keywords, operations, payments, null);
41+
}
42+
3243
public ServiceFilters {
44+
if (trust != null
45+
&& (trust.size() != 1
46+
|| trust.get(0) == null
47+
|| !"tap".equals(trust.get(0).name()))) {
48+
throw new IllegalArgumentException("trust must contain exactly one tap descriptor");
49+
}
3350
enrollment = enrollment == null ? List.of() : List.copyOf(enrollment);
3451
keywords = keywords == null ? List.of() : List.copyOf(keywords);
3552
operations = operations == null ? List.of() : List.copyOf(operations);
3653
payments = payments == null ? List.of() : List.copyOf(payments);
54+
trust = trust == null ? List.of() : List.copyOf(trust);
3755
}
3856
}
3957

@@ -77,13 +95,24 @@ public record Facets(
7795
List<Facet<String>> keywords,
7896
List<Facet<OperationDescriptor>> operations,
7997
List<Facet<ServiceDocument.PaymentProtocol>> payments,
80-
@JsonProperty("payment_options") List<Facet<PaymentOptionFacetValue>> paymentOptions) {
98+
@JsonProperty("payment_options") List<Facet<PaymentOptionFacetValue>> paymentOptions,
99+
List<Facet<ServiceDocument.TrustProtocol>> trust) {
100+
public Facets(
101+
List<Facet<ServiceDocument.EnrollmentProtocol>> enrollment,
102+
List<Facet<String>> keywords,
103+
List<Facet<OperationDescriptor>> operations,
104+
List<Facet<ServiceDocument.PaymentProtocol>> payments,
105+
List<Facet<PaymentOptionFacetValue>> paymentOptions) {
106+
this(enrollment, keywords, operations, payments, paymentOptions, null);
107+
}
108+
81109
public Facets {
82110
enrollment = enrollment == null ? List.of() : List.copyOf(enrollment);
83111
keywords = keywords == null ? List.of() : List.copyOf(keywords);
84112
operations = operations == null ? List.of() : List.copyOf(operations);
85113
payments = payments == null ? List.of() : List.copyOf(payments);
86114
paymentOptions = paymentOptions == null ? List.of() : List.copyOf(paymentOptions);
115+
trust = trust == null ? List.of() : List.copyOf(trust);
87116
}
88117
}
89118

‎odp-directory/src/test/java/org/offeringprotocol/odp/directory/DirectoryClientTest.java‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
import static org.junit.jupiter.api.Assertions.assertNull;
55
import static org.junit.jupiter.api.Assertions.assertThrows;
66

7+
import java.util.Collections;
78
import java.util.List;
89
import org.junit.jupiter.api.Test;
10+
import org.offeringprotocol.odp.core.OdpJson;
911
import org.offeringprotocol.odp.core.ServiceDocument;
1012

1113
class DirectoryClientTest {
@@ -23,6 +25,24 @@ void selectsOnlyCanonicalEnvironments() {
2325
@Test
2426
void rejectsInvalidSearchLimitsBeforeTransport() {
2527
assertThrows(IllegalArgumentException.class, () -> new DirectoryModels.SearchRequest("plants", null, 101));
28+
assertThrows(
29+
IllegalArgumentException.class,
30+
() -> new DirectoryModels.ServiceFilters(
31+
null, null, null, null, List.of(new ServiceDocument.TrustProtocol("mpp"))));
32+
assertThrows(
33+
IllegalArgumentException.class,
34+
() -> new DirectoryModels.ServiceFilters(null, null, null, null, List.of()));
35+
assertThrows(
36+
IllegalArgumentException.class,
37+
() -> new DirectoryModels.ServiceFilters(null, null, null, null, Collections.singletonList(null)));
38+
}
39+
40+
@Test
41+
void omitsUnusedFiltersFromRequests() {
42+
DirectoryModels.ServiceFilters filters = new DirectoryModels.ServiceFilters(
43+
null, List.of("gpu"), null, null, List.of(new ServiceDocument.TrustProtocol("tap")));
44+
45+
assertEquals("{\"keywords\":[\"gpu\"],\"trust\":[{\"name\":\"tap\"}]}", OdpJson.write(filters));
2646
}
2747

2848
@Test
@@ -53,4 +73,20 @@ void filtersUnknownProtocolsFromDirectoryResults() {
5373
assertNull(protocols.enrollment());
5474
assertNull(page.items().get(1).protocols());
5575
}
76+
77+
@Test
78+
void decodesTypedTrustFacets() {
79+
DirectoryModels.SearchPage page = DirectoryClient.decodeSearchPage("""
80+
{"items":[],"facets":{"trust":[{"value":{"name":"tap"},"count":2}]}}
81+
""");
82+
83+
assertEquals(
84+
List.of(new DirectoryModels.Facet<>(new ServiceDocument.TrustProtocol("tap"), 2)),
85+
page.facets().trust());
86+
87+
assertThrows(
88+
IllegalArgumentException.class,
89+
() -> DirectoryClient.decodeSearchPage(
90+
"{\"items\":[],\"facets\":{\"trust\":[{\"value\":{\"name\":\"mpp\"},\"count\":2}]}}"));
91+
}
5692
}

0 commit comments

Comments
 (0)