Skip to content

Latest commit

 

History

History
405 lines (354 loc) · 15 KB

Route.md

File metadata and controls

405 lines (354 loc) · 15 KB

Route

Sommaire

Déclarer une route

Une route peut être déclarée à partir de deux choses, soit depuis la DuploInstance, soit depuis une AbstractRouteInstance.

duplo.declareRoute("GET", "/")//...
//ou
duplo.declareRoute("POST", ["/user", "/post"])//...

Le premier argument est une string qui représente la méthode de la route, les seules valeurs possibles sont GET, POST, PUT, PATCH, DELETE, OPTIONS ou HEAD. Le second argument est soit une string soit une Array<string>, il représente tous les paths qui seront associés à la route.

Construction d'une route

La déclaration d'une route a un pattern bien précis à respecter. Cet ordre imposé permettra une meilleure lisibilité. Ce principe sera le même pour la déclaration des routes abstraites et la création de process.

duplo
.declareRoute("GET", "/")
.hook(/* ... */) // vous pouvez ajouter autant de Hook que vous souhaitez
.extract(/* ... */) // vous ne pouvez avoir qu'un seul extract
.process(/* ... */) // vous pouvez avoir autant de process que vous souhaitez
.check(/* ... */) // vous pouvez avoir autant de check que vous souhaitez
.cut(/* ... */) // vous pouvez avoir autant de cut que vous souhaitez
.handler(/* ... */) // cette fonction marque l'arrét de la déclaration de la route

Chaque fonction en dessous d'une autre empêche de rappeler celles du dessus (sauf pour check, process et cut qui n'empêchent pas de se rappeler entre eux):

duplo
.declareRoute("GET", "/")

.hook(/* ... */) 
.hook(/* ... */) 
.extract(/* ... */) // hook et extract ne sont plus disponibles
// vous pouvez avoir autant de process, check et cut que vous voulez et dans l'ordre que vous voulez.
.check(/* ... */) 
.process(/* ... */)
.process(/* ... */) 
.cut(/* ... */) 
.check(/* ... */)

.handler(/* ... */)

L'ordre des process, check et cut que vous définirez sera l'ordre d'exécution de la request.

Exécution linéaire

Pour que Duplojs fonctionne correctement, il faut respecter son exécution. Une request a un chemin synchronisé et des étapes à franchir. Si vous souhaitez utiliser une réponse après une promesse, il vous faudra toujours utiliser await pour que l'exécution se fasse de manière linéaire.

duplo
.declareRoute("GET", "/user/{id}")
// hook, extract, process, checker, cut...
.handler(async (floor, response) => {

    // ✖ ne fonctionne pas
    // celà provoquera une erreur qui indiquera que rien n'a été envoyé
    new Promise(resolve => setTimeout(resolve, 1000))
    .then(() => response.code(200).info("j'effectue le dab").send());

    // ✔ fonctionne correctement
    // l'exécution est linéaire donc celà ne pose aucun problème
    await new Promise(resolve => setTimeout(resolve, 1000));
    response.code(200).info("il est mort pioupiou").send();
});

Cycle d'exécution

Les route de duplojs sont grossomodo des succesions d'exécutions de functions (Hook, AbstractRoute, Process, Checker, ...). Pour optimiser le raccord des différentes fonctions, duplojs fabrique une fonction sur mesure. C'est donc cette fonction qui détermine le cycle d'exécution.

Ordres d'appel des fonctions:

Comme dit précédemment, la fonction est sur mesure donc tout ne va pas forcément s'exécuter mais tout s'executera dans cette ordre.

Il n'est pas possible d'envoyer une réponse à n'importe quel moment du cycle, le "code" ci-dessous montre de manière plus technique le cycle d'exécution.

// try serveur
try{
    @ trouve une route qui match
    @ lance le hook onConstructRequest
    @ lance le hook onConstructResponse

    // try response
    try{
        // try erreur
        try{
            @ lance le hook beforeRouteExecution
            @ exécute abstract route
            @ lance le hook parsingBody
            @ exécute extract, checker, cut, process, handler
        }
        // catch error
        catch(exception) {
            if(exception === Error){
                @ lance le hook OnError
                @ exécute error handler
            }
            else {
                @ throw exception;
            }
        }
    }
    // catch response
    catch(exception) {
        if(exception === Reponse){
            @ lance le hook beforeSend
            @ envoie des headers
            @ sérialisation et envoi du body
            @ lance le hook afterSend
        }
        else {
            @ throw exception;
        }
    }
}
// catch serveur
catch(exception) {
    @ lance le hook onServerError
    @ envoie une Erreur 500 au client
}

Les différents trycatch servent de "goto" (d'où l'exécution linéaire), celà permet d'interrompre l'exécution depuis n'importe où (au sein des opérations). Cependant si une réponse est envoyée depuis le try serveur ou le catch réponse, ça provoquera une erreur.

.handler(function, ...any?)

duplo
.declareRoute("GET", "/")
.handler((floor, response) => {
    response.send("fait le mou, fait le mou");
});

La fonction handler est la fonction qui clôture la définition d'une route. Elle prend en argument une fonction. Cette fonction est appelée avec 2 arguments, le floor de la requête et l'objet Response. Cette fonction est la dernière action de la route, en théorie une fois arrivé ici il n'y a plus rien à vérifier !

⚠️ Si la fonction handler n'est pas applée, la route n'est pas déclarée. ⚠️

.extract(object, function?, ...any?)

La fonction extract permet de récupérer et typer des valeurs dans l'objet Request. Pour extraire les valeurs, il nous faut définir un schéma dans l'objet passé en premier paramètre. Les index du premier niveau correspondent à des clés de l'objet Request. La librairie zod (qui est directement intégrée à duplojs) est utilisée ici pour vérifier les types.

duplo
.declareRoute("PATCH", "/post/{id}")
.extract({
    params: {
        id: zod.number(), // max deep
    },
    body: zod.object({ // min deep
        title: zod.string().max(10).min(2).optinal(),
        subtitle: zod.string().max(150).optinal(),
        text: zod.string().max(1500).optinal(),
    })
})
.handler(({pickup}) => {
    pickup("id"); // id en paramètre
    pickup("body"); // body de la request

    //...
});

// égal à

duplo
.declareRoute("PATCH", "/post/{id}")
.extract({
    params: {
        id: zod.coerce.number(),
    },
    body: {
        title: zod.string().max(10).min(2).optinal(),
        subtitle: zod.string().max(150).optinal(),
        text: zod.string().max(1500).optinal(),
    },
})
.handler(({pickup}) => {
    pickup("id"); // id en paramètre
    pickup("title"); // titre du body de la request
    pickup("subtitle"); // sous-titre du body de la request
    pickup("text"); // texte du body de la request

    //...
});

En cas d'erreur de type, une réponse est directement envoyée et l'exécution du code s'arrête à la fonction extract. Par défaut, l'erreur a un code 400 et porte l'info TYPE_ERROR.${type}[.${index}]. Si vous souhaitez modifier le type de retour, il vous suffit de passer une fonction en second paramètre.

duplo
.declareRoute("PATCH", "/post/{id}")
.extract(
    {
        params: {
            id: zod.coerce.number(),
        },
    },
    (response, type, index, error) => 
        response.code(400).info(`TYPE_ERROR.${type}${index ? "." + index : ""}`).send(),
)
.handler(({pickup}) => {
    //...
});

La fonction en second paramètre prend 4 arguments, l'object Response, la clé de premier niveau (type), la clé de second niveau (index) et l'erreur zod.

.check(object, object, ...any?)

La méthode check permet d'implémenter un checker dans une route. Elle prend 2 arguments, le premier est de type Checker et le second est un objet qui permet de configurer le checker implémenté.

duplo
.declareRoute("GET", "/user/{id}")
.extract({
    params: {
        id: zod.coerce.number()
    },
    query: {
        type: zod.enum(["id", "firstname"]).optional()
    }
})
.check(
    userExist,
    {
        input: (pickup) => pickup("id"), // valeur d'entrée
        result: "user.exist", // info attendue pour continuer
        catch: (response, info) => response.code(404).info(info).send(), // action effectuée si l'info n'est pas celle attendue
        indexing: "user", // index de drop du resultat

        options: {type: "id"} // option statique
        // ou
        options: (pickup) => ({ // option dynamique
            type: pickup("type")
        })
    }
)
.handler(({pickup}, response) => {
    response.send(pickup("user"));
});

Les propriétés input et options ce ressemblent, elles servent toutes les deux à envoyer des donnés pour l'exécution du checker. Cependant input doit obligatoirement être défini contrairement aux options qui ont des valeurs par défaut. La propriété result représente l'information attendue, si le checker renvoie une autre information, la fonction de la propriété catch sera lancée ce qui interrompra la requête. La propriété indexing représente la clé d'indexation dans le floor de la data résultante du checker.

propriétés valeur definition
input function Fonction qui permet d'envoyer une valeur au checker.
result string ou string[] ou undefined Information attendue pour continuer la requête.
catch function Fonction appelée si le resultat ne convient pas.
indexing string ou undefined Propriété qui représente l'index dans le floor des data renvoyées par le checker en cas de resultat satisfaisant.
options function ou objet ou undefined Options du checker.
skip function ou undefined Propriété qui permet de sauter l'exécution checker sous certaines conditions.

.cut(function, array?, ...any?)

La fonction cut est conseillée d'être utilisée dans deux cas. Si vous avez une vérification unique qui ne sera utile que sur une seul route ou si vous avez besoin de manipuler l'objet Request. La méthode cut prend 2 arguments, le premier est une fonction et le second argument est une array. L'array correspond explicitement aux clés de l'objet renvoyé par la fonction.

duplo
.declareRoute("POST", "/video/{id}/comment")
.extract({
    params: {
        id: zod.coerce.number(),
    },
    body: {
        content: zod.string().max(240).min(1),
    }
})
.check(
    videoExist,
    {
        input: (pickup) => pickup("id"),
        result: "video.exist",
        catch: (response, info) => response.code(404).info(info).send(),
        indexing: "video",
    }
)
.cut(
    ({pickup}, response, request) => {
        const dateVideo = pickup("video").date;
        const dateComment = new Date();
        
        const fiveDayInMilisecond = 432000000;

        if(dateComment.getTime() - dateVideo.getTime() > fiveDayInMilisecond) {
            response.code(400).send();
        }

        return {
            dateComment
        }
    }, 
    ["dateComment"]
)
.handler(async ({pickup}, response) => {
    const result = await myDataBase.comment.inserte({
        video_id: pickup("id"),
        date: pickup("dateComment"),
        content: pickup("content")
    });

    response.send(result);
});

La fonction sera appelée avec 3 arguments, le premier c'est le floor de la requête, le second c'est l'objet Response et le troisième c'est l'objet Request.

.process(object, object, ...any?)

La fonction process permet d'implémenter un process dans une route. Cette méthode prend 2 arguments, le premier est de type Process et le second est un objet qui permet de configurer le process implémenté.

duplo
.declareRoute("PATCH", "/article/{articleId}")
.extract({
    params: {
        articleId: zod.coerce.number(),
    },
    body: {
        title: zod.string().max(120).min(5).optinal(),
        subTitle: zod.string().max(240).min(5).optinal(),
        content: zod.string().max(1500).min(1).optinal(),
    }
})
.check(
    articleExist,
    {
        input: (pickup) => pickup("articleId"),
        result: "article.exist",
        catch: (response, info) => response.code(404).info(info).send(),
        indexing: "article",
    }
)
.process(
    userHasRightInOrganization,
    {
        input: (pickup) => pickup("article").organization_id,
        pickup: ["currentUser"], // valeur récupérée du process

        options: { // option statique
            right: "edit_post"
        }
        // ou
        options: (pickup) => ({ // option dynamique
            right: "edit_post"
        })
    }
)
.handler(async ({pickup}, response) => {
    const result = await myDataBase.article.update({
        id: pickup("articleId"),
        title: pickup("title"),
        subTitle: pickup("subTitle"),
        content: pickup("content"),
        editer_id: pickup("currentUser").id
    });

    response.send(result);
});

Les propriétés input et options permettent de passer des données pour l'exécution du process, mais elles ne sont pas obligatoires. La propriété pickup permet de récupérer des valeurs du floor du process.

propriétés valeur definition
input function ou undefined Fonction qui permet d'envoyer une valeur au process.
pickup string[] ou undefined Cette propriété représente des clés du floor du process qui ont été drop, celà permet d'importer leurs valeurs dans la route.
options function ou objet ou undefined Options du process.
skip function ou undefined Propriété qui permet de sauter l'exécution checker sous certaines conditions.

.hook(string, function)

La fonction hook permet d'ajouter des hooks localement à une route.

duplo
.declareRoute("GET", "/")
.hook("onConstructRequest", (request) => {/* ... */})
.hook("beforeParsingBody", (request, response) => {/* ... */})
.hook("onError", (request, response, error) => {/* ... */})
.hook("beforeParsingBody", (request, response) => {/* ... */})
.handler(/* ... */);

Tous les hook disponibles pour les routes sont ici !

Retour vers le Sommaire.