2020-07-03 13:05:23 +02:00
package nouritsu ;
import fi.iki.elonen.NanoHTTPD ;
import fi.iki.elonen.NanoHTTPD.Response.Status ;
2020-07-04 00:38:18 +02:00
import io.vavr.Tuple ;
2020-07-03 13:05:23 +02:00
import io.vavr.Tuple2 ;
import io.vavr.collection.HashMap ;
import io.vavr.collection.Map ;
import io.vavr.control.Either ;
import io.vavr.control.Try ;
2020-07-04 00:38:18 +02:00
import nouritsu.types.Resp ;
import nouritsu.types.ShoppingItem ;
import nouritsu.types.Store ;
2020-07-03 13:05:23 +02:00
import java.io.IOException ;
2020-07-04 00:38:18 +02:00
import java.util.function.BiFunction ;
2020-07-03 13:05:23 +02:00
import java.util.function.Function ;
public class Nouritsu extends NanoHTTPD {
private final Dao dao ;
public Nouritsu ( int port , Dao dao ) throws IOException {
super ( port ) ;
start ( 10_000 , false ) ;
this . dao = dao ;
}
@Override
public Response serve ( IHTTPSession session ) {
return Try . of ( ( ) - > processRequest ( session , dao ) )
. fold (
2020-07-04 00:38:18 +02:00
// This won’t be exposed to the outside world, so returning the error message is fine
2020-07-03 13:05:23 +02:00
err - > newFixedLengthResponse ( Status . INTERNAL_ERROR , MIME_PLAINTEXT , err . getMessage ( ) ) ,
response - > newFixedLengthResponse ( response . status ( ) , MIME_PLAINTEXT , response . text ( ) )
) ;
}
private static final String HELP = """
Valid endpoints are ( not case sensitive ) :
/ addStore / ? name = storeName & sections = first , second , third
/ addItem / ? name = itemName & category = itemCategory
2020-07-04 00:38:18 +02:00
/ addToList / ? user = userId & item = itemName
2020-07-03 13:05:23 +02:00
/ getList / ? user = userId & store = optionalStoreName
/ clearList / ? user = userId
" " " ;
2020-07-04 00:38:18 +02:00
private static Resp processRequest ( IHTTPSession session , Dao dao ) {
// Type inference can’t handle this :feelsBadMan:
BiFunction < Map < String , String > , Dao , Resp > handler =
switch ( session . getUri ( ) . replaceAll ( " / " , " " ) . toLowerCase ( ) ) {
case " addstore " - > Nouritsu : : addStore ;
case " additem " - > Nouritsu : : addItem ;
case " addtolist " - > Nouritsu : : addToList ;
case " getlist " - > Nouritsu : : getList ;
case " clearlist " - > Nouritsu : : clearList ;
default - > ( _1 , _2 ) - > Resp . NOT_FOUND . apply ( HELP ) ;
} ;
return handler . apply ( extractParams ( session ) , dao ) ;
2020-07-03 13:05:23 +02:00
}
2020-07-04 00:38:18 +02:00
// Repack the parameters into a vavr Map<Key, Head(Value)>.
// Each parameter should only occur once per request.
private static Map < String , String > extractParams ( IHTTPSession session ) {
2020-07-03 13:05:23 +02:00
return session . getParameters ( )
. entrySet ( )
. stream ( )
2020-07-04 00:38:18 +02:00
. map ( Tuple : : fromEntry )
. collect ( HashMap . collector ( ) )
// guaranteed to be non-empty
. mapValues ( list - > list . get ( 0 ) ) ;
2020-07-03 13:05:23 +02:00
}
2020-07-04 00:38:18 +02:00
private static Resp clearList ( Map < String , String > params , Dao dao ) {
2020-07-03 13:05:23 +02:00
return getFromParams ( params , " user " )
2020-07-04 00:38:18 +02:00
. map ( dao : : clearList )
. fold ( Function . identity ( ) , Resp . CREATED ) ;
2020-07-03 13:05:23 +02:00
}
2020-07-04 00:38:18 +02:00
private static Resp getList ( Map < String , String > params , Dao dao ) {
2020-07-03 13:05:23 +02:00
return getFromParams ( params , " user " )
. flatMap ( dao : : getList )
2020-07-04 00:38:18 +02:00
. map ( shoppingItems - > shoppingItems . mkString ( " , " ) )
. fold ( Function . identity ( ) , Resp . OK ) ;
2020-07-03 13:05:23 +02:00
}
2020-07-04 00:38:18 +02:00
private static Resp addToList ( Map < String , String > params , Dao dao ) {
return getAsTuple ( params , " user " , " item " )
// This is where Kotlin-like destructuring would be really nice to have:
// flatMap(id, item -> ...)
. flatMap ( t - > dao . addToList ( t . _1 , t . _2 ) )
. fold ( Function . identity ( ) , Resp . CREATED ) ;
2020-07-03 13:05:23 +02:00
}
2020-07-04 00:38:18 +02:00
private static Resp addItem ( Map < String , String > params , Dao dao ) {
return getAsTuple ( params , " name " , " category " )
. flatMap ( t - > ShoppingItem . tryParse ( t . _1 , t . _2 ) )
. map ( dao : : addItem )
. fold ( Function . identity ( ) , Resp . CREATED ) ;
2020-07-03 13:05:23 +02:00
}
2020-07-04 00:38:18 +02:00
private static Resp addStore ( Map < String , String > params , Dao dao ) {
return getAsTuple ( params , " name " , " categories " )
. flatMap ( t - > Store . tryParse ( t . _1 , t . _2 ) )
. flatMap ( dao : : addStore )
. fold ( Function . identity ( ) , Resp . CREATED ) ;
2020-07-03 13:05:23 +02:00
}
2020-07-04 00:38:18 +02:00
private static Either < Resp , String > getFromParams ( Map < String , String > params , String key ) {
return params . get ( key ) . toEither ( Resp . NOT_FOUND . apply ( " Parameter “%s” not found " . formatted ( key ) ) ) ;
}
// Get k1 and k2 as a tuple in Either.Right or Either.Left if k1 or k2 is/are not in the map.
private static Either < Resp , Tuple2 < String , String > > getAsTuple ( Map < String , String > params , String k1 , String k2 ) {
return getFromParams ( params , k1 )
. map ( Tuple : : of )
. flatMap ( name - > getFromParams ( params , k2 ) . map ( name : : append ) ) ;
2020-07-03 13:05:23 +02:00
}
public static void main ( String [ ] args ) {
try {
new Nouritsu ( 14523 , new Dao ( ) ) ;
} catch ( IOException e ) {
e . printStackTrace ( ) ;
}
}
}