実装した graphql-java で雑に pagination 実装できるようにした - 宇宙行きたい
graphql-java で pagination 実装するの、DefaultXXX を組み合わせて結構自分でガッツリ実装しなきゃダメなのか? なんかいい感じで継承すと offset と limit だけ渡ってくるからそこの処理書けばいいみたいなクラスはないのか
— ヨシオリX (@yoshiori) 2021年11月22日
なんかサクッと返してくれるのないかなぁと探したんだけどなかった。 SimpleListConnection は全部のリスト渡さないといけないし。。。ということで雑に書いたんだけど、これをそのまま使いたいってよりもこういうことしたいんだけど自分で実装しないでもなんか汎用的なのあるんでしょ!?俺が見つけられてないだけで!!! 教えてください!!!的な気持ちで書いてみた。(出てこなかったらこれもうちょっと弄って使うけど)
var count = hogeMapper.count(); var hogeConnection = new SimpleConnection<Hoge>(count, (offset, limit) -> { return hogeMapper.getHoges(offset, limit); }).get(env);
こんな感じでページネーションしたいリストの合計サイズと offset, limit を元に list 返す関数渡すと graphql.relay.Connection
のインスタンス返してくれるやつ
import static graphql.Assert.assertNotNull; import com.google.common.base.Objects; import com.google.common.io.BaseEncoding; import graphql.relay.Connection; import graphql.relay.ConnectionCursor; import graphql.relay.DefaultConnection; import graphql.relay.DefaultEdge; import graphql.relay.DefaultPageInfo; import graphql.relay.Edge; import graphql.schema.DataFetchingEnvironment; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.BiFunction; public class SimpleConnection<T> { final SimpleConnectionCursor sizeCursor; final BiFunction<Integer, Integer, List<T> > func; public SimpleConnection(Integer size, BiFunction<Integer, Integer, List<T> > func) { this.sizeCursor = new SimpleConnectionCursor(assertNotNull(size)); this.func = assertNotNull(func); } public Connection<T> get(DataFetchingEnvironment environment){ Integer firstInteger = environment.getArgument("first"); if(firstInteger == null){ firstInteger = 20; } var limitCursor = new SimpleConnectionCursor(firstInteger); var offsetCursor = new SimpleConnectionCursor( (String) environment.getArgument("after")); if(offsetCursor.getOffset() != 0){ offsetCursor = new SimpleConnectionCursor(offsetCursor.getOffset() + 1); } List<T> list = func.apply(offsetCursor.getOffset(), limitCursor.getOffset()); if(list.isEmpty()){ return emptyConnection(); } List<Edge<T>> edges = new ArrayList<>(); int index = offsetCursor.getOffset(); for(T o : list){ edges.add(new DefaultEdge<>(o, new SimpleConnectionCursor(index++))); } var firstEdge = edges.get(0); var lastEdge = edges.get(edges.size() - 1); var pageInfo = new DefaultPageInfo( firstEdge.getCursor(), lastEdge.getCursor(), 0 < ((SimpleConnectionCursor)firstEdge.getCursor()).getOffset(), ((SimpleConnectionCursor)lastEdge.getCursor()).getOffset() < sizeCursor.getOffset() -1 ); return new DefaultConnection<>( edges, pageInfo ); } Connection<T> emptyConnection() { return new DefaultConnection<>(Collections.emptyList(), new DefaultPageInfo(null, null, false, false)); } static class SimpleConnectionCursor implements ConnectionCursor{ static final String PREFIX = "DUMMY"; final int offset; SimpleConnectionCursor(int offset) { this.offset = offset; } SimpleConnectionCursor(String cursor) { this.offset = convertToOffset(cursor); } /** * @return an opaque string that represents this cursor. */ @Override public String getValue() { return convertToCursorString(offset); } public int getOffset(){ return offset; } private int convertToOffset(String cursorString) { if (cursorString == null) { return 0; } try{ var string = new String(BaseEncoding.base64().decode(cursorString), StandardCharsets.UTF_8); return Integer.parseInt(string.substring(PREFIX.length())); }catch (IllegalArgumentException ignored){ } return 0; } private String convertToCursorString(int offset) { return BaseEncoding.base64().encode((PREFIX + Integer.toString(offset)).getBytes(StandardCharsets.UTF_8)); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SimpleConnectionCursor that = (SimpleConnectionCursor) o; return offset == that.offset; } @Override public int hashCode() { return Objects.hashCode(offset); } } }