11package faucet
22
33import (
4+ "context"
5+ "encoding/json"
46 "errors"
57 "fmt"
68 "io"
79 "net/http"
810 "regexp"
911
1012 "github.com/gnolang/faucet/spec"
11- "github.com/gnolang/faucet/writer"
12- httpWriter "github.com/gnolang/faucet/writer/http"
1313 "github.com/gnolang/gno/tm2/pkg/crypto"
1414 "github.com/gnolang/gno/tm2/pkg/std"
1515)
1616
17- const (
18- unableToHandleRequest = "unable to handle faucet request"
19- faucetSuccess = "successfully executed faucet transfer"
20- )
17+ const faucetSuccess = "successfully executed faucet transfer"
2118
2219const DefaultDripMethod = "drip" // the default JSON-RPC method for a faucet drip
2320
@@ -27,81 +24,106 @@ var (
2724 errInvalidMethod = errors .New ("unknown RPC method call" )
2825)
2926
30- // drip is a single Faucet transfer request
31- type drip struct {
32- amount std.Coins
33- to crypto.Address
34- }
27+ // wrapJSONRPC wraps the given handler and middlewares into a JSON-RPC 2.0 pipeline
28+ func wrapJSONRPC (handlerFn HandlerFunc , mws ... Middleware ) http.HandlerFunc {
29+ callChain := chainMiddlewares (mws ... )(handlerFn )
3530
36- var amountRegex = regexp .MustCompile (`^\d+ugnot$` )
31+ return func (w http.ResponseWriter , r * http.Request ) {
32+ // Grab the request(s)
33+ requests , err := parseRequests (r .Body )
34+ if err != nil {
35+ http .Error (
36+ w ,
37+ fmt .Sprintf ("unable to read request: %s" , err .Error ()),
38+ http .StatusBadRequest ,
39+ )
3740
38- // defaultHTTPHandler is the default faucet transfer handler
39- func (f * Faucet ) defaultHTTPHandler (w http.ResponseWriter , r * http.Request ) {
40- // Load the requests
41- requestBody , readErr := io .ReadAll (r .Body )
42- if readErr != nil {
43- http .Error (
44- w ,
45- "unable to read request" ,
46- http .StatusBadRequest ,
47- )
41+ return
42+ }
4843
49- return
50- }
44+ var (
45+ ctx = r . Context ()
5146
52- // Extract the requests
53- requests , err := spec .ExtractBaseRequests (requestBody )
54- if err != nil {
55- http .Error (
56- w ,
57- "invalid request body" ,
58- http .StatusBadRequest ,
47+ responses = make (spec.BaseJSONResponses , 0 )
5948 )
6049
61- return
62- }
50+ for _ , req := range requests {
51+ // Make sure it's a valid base request
52+ if ! spec .IsValidBaseRequest (req ) {
53+ responses = append (responses , spec .NewJSONResponse (
54+ req .ID ,
55+ nil ,
56+ spec .NewJSONError ("invalid JSON-RPC 2.0 request" , spec .InvalidRequestErrorCode ),
57+ ))
58+
59+ continue
60+ }
61+
62+ // Parse the request.
63+ // This executes all the middlewares, and
64+ // finally the base handler for the endpoint
65+ resp := callChain (ctx , req )
66+
67+ responses = append (responses , resp )
68+ }
69+
70+ w .Header ().Set ("Content-Type" , JSONMimeType )
71+
72+ // Create the encoder
73+ enc := json .NewEncoder (w )
74+
75+ if len (responses ) == 1 {
76+ // Write the JSON response as a single response
77+ _ = enc .Encode (responses [0 ]) //nolint:errcheck // Fine to leave unchecked
6378
64- // Handle the requests
65- w . Header (). Set ( "Content-Type" , jsonMimeType )
66- f . handleRequest (
67- httpWriter . New ( f . logger , w ),
68- requests ,
69- )
79+ return
80+ }
81+
82+ // Write the JSON response as a batch
83+ _ = enc . Encode ( responses ) //nolint:errcheck // Fine to leave unchecked
84+ }
7085}
7186
72- // handleRequest is the common default faucet handler
73- func ( f * Faucet ) handleRequest ( writer writer. ResponseWriter , requests spec. BaseJSONRequests ) {
74- // Parse all JSON-RPC requests
75- responses := make (spec. BaseJSONResponses , len ( requests ))
87+ // chainMiddlewares combines the given middlewares
88+ func chainMiddlewares ( mw ... Middleware ) Middleware {
89+ return func ( final HandlerFunc ) HandlerFunc {
90+ h := final
7691
77- for i , req := range requests {
78- f .logger .Debug ("incoming request" , "request" , req )
92+ for i := len (mw ) - 1 ; i >= 0 ; i -- {
93+ h = mw [i ](h )
94+ }
7995
80- responses [ i ] = f . handleSingleRequest ( req )
96+ return h
8197 }
98+ }
8299
83- if len (responses ) == 1 {
84- // Write the JSON response as a single response
85- writer .WriteResponse (responses [0 ])
100+ // parseRequests parses the JSON-RPC requests from the request body
101+ func parseRequests (body io.Reader ) (spec.BaseJSONRequests , error ) {
102+ // Load the requests
103+ requestBody , readErr := io .ReadAll (body )
104+ if readErr != nil {
105+ return nil , fmt .Errorf ("unable to read request: %w" , readErr )
106+ }
86107
87- return
108+ // Extract the requests
109+ requests , err := spec .ExtractBaseRequests (requestBody )
110+ if err != nil {
111+ return nil , fmt .Errorf ("invalid request body: %w" , err )
88112 }
89113
90- // Write the JSON response as a batch
91- writer .WriteResponse (responses )
114+ return requests , nil
92115}
93116
94- // handleSingleRequest validates and executes one drip request
95- func (f * Faucet ) handleSingleRequest (req * spec.BaseJSONRequest ) * spec.BaseJSONResponse {
96- // Make sure it's a valid base request
97- if ! spec .IsValidBaseRequest (req ) {
98- return spec .NewJSONResponse (
99- req .ID ,
100- nil ,
101- spec .NewJSONError ("invalid JSON-RPC 2.0 request" , spec .InvalidRequestErrorCode ),
102- )
103- }
117+ // drip is a single Faucet transfer request
118+ type drip struct {
119+ amount std.Coins
120+ to crypto.Address
121+ }
104122
123+ var amountRegex = regexp .MustCompile (`^\d+ugnot$` )
124+
125+ // defaultHTTPHandler is the default faucet transfer handler
126+ func (f * Faucet ) defaultHTTPHandler (_ context.Context , req * spec.BaseJSONRequest ) * spec.BaseJSONResponse {
105127 // Make sure the method call on "/" is "drip"
106128 if req .Method != DefaultDripMethod {
107129 return spec .NewJSONResponse (
0 commit comments