Generazione della Documentazione OpenAPI

OpenAPI è una specifica open source per descrivere la progettazione di interfacce API RESTful. Definisce strutture di richiesta e risposta API, parametri, tipi di ritorno, codici di errore e altri dettagli in formato JSON o YAML, rendendo la comunicazione tra client e server più esplicita e standardizzata.

OpenAPI era originariamente la versione open source della specifica Swagger ed è ora diventato un progetto indipendente supportato da molte grandi aziende e sviluppatori. Utilizzare la specifica OpenAPI aiuta i team di sviluppo a collaborare meglio, ridurre i costi di comunicazione e migliorare l'efficienza dello sviluppo. Inoltre, OpenAPI fornisce agli sviluppatori strumenti per generare automaticamente documentazione API, dati mock e casi di test, facilitando il lavoro di sviluppo e testing.

Salvo fornisce integrazione OpenAPI (modificata da utoipa). Salvo estrae elegantemente automaticamente le informazioni rilevanti sui tipi di dati OpenAPI da Handler in base alle sue caratteristiche. Salvo integra anche diverse interfacce OpenAPI open source popolari come SwaggerUI, Scalar, RapiDoc e ReDoc.

Poiché i nomi dei tipi Rust possono essere lunghi e non sempre adatti all'uso in OpenAPI, salvo-oapi fornisce il tipo Namer, che consente di personalizzare le regole per modificare i nomi dei tipi in OpenAPI secondo necessità.

Codice di Esempio

main.rs
Cargo.toml
use salvo::oapi::extract::*;
use salvo::prelude::*;

#[endpoint]
async fn hello(name: QueryParam<String, false>) -> String {
    format!("Hello, {}!", name.as_deref().unwrap_or("World"))
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt().init();

    let router = Router::new().push(Router::with_path("hello").get(hello));

    let doc = OpenApi::new("test api", "0.0.1").merge_router(&router);

    let router = router
        .unshift(doc.into_router("/api-doc/openapi.json"))
        .unshift(SwaggerUi::new("/api-doc/openapi.json").into_router("/swagger-ui"));

    let acceptor = TcpListener::new("0.0.0.0:8698").bind().await;
    Server::new(acceptor).serve(router).await;
}

Inserisci http://localhost:8698/swagger-ui nel tuo browser per vedere la pagina Swagger UI.

L'integrazione OpenAPI in Salvo è piuttosto elegante. Per l'esempio sopra, rispetto a un normale progetto Salvo, abbiamo semplicemente eseguito i seguenti passaggi:

  • Abilita la feature oapi in Cargo.toml: salvo = { workspace = true, features = ["oapi"] };

  • Sostituisci #[handler] con #[endpoint];

  • Usa name: QueryParam<String, false> per ottenere il valore della stringa di query. Quando visiti http://localhost/hello?name=chris, la stringa di query name verrà analizzata. Il false in QueryParam<String, false> significa che questo parametro è opzionale. Se visiti http://localhost/hello, non darà errore. Al contrario, se è QueryParam<String, true>, significa che questo parametro deve essere fornito, altrimenti verrà restituito un errore.

  • Crea OpenAPI e il corrispondente Router. Il merge_router in OpenApi::new("test api", "0.0.1").merge_router(&router) significa che questo OpenAPI ottiene le informazioni documentali necessarie analizzando una certa rotta e le sue sotto-rotte. Alcuni Handler delle rotte potrebbero non fornire informazioni per generare documenti, e queste rotte verranno ignorate, come gli Handler definiti usando la macro #[handler] invece della macro #[endpoint]. Cioè, nei progetti reali, per motivi come l'avanzamento dello sviluppo, puoi scegliere di non generare documenti OpenAPI, o generare parzialmente documenti OpenAPI. Successivamente, puoi gradualmente aumentare il numero di interfacce OpenAPI generate, e tutto ciò che devi fare è cambiare #[handler] in #[endpoint] e modificare la firma della funzione.

Estrattori di Dati

Puoi importare estrattori di dati comuni predefiniti tramite use salvo::oapi::extract::*;. L'estrattore fornirà alcune informazioni necessarie a Salvo in modo che Salvo possa generare documenti OpenAPI.

  • QueryParam<T, const REQUIRED: bool>: Un estrattore per estrarre dati dalle stringhe di query. QueryParam<T, false> significa che questo parametro non è obbligatorio e può essere omesso. QueryParam<T, true> significa che questo parametro è obbligatorio e non può essere omesso. Se non viene fornito, verrà restituito un errore;

  • HeaderParam<T, const REQUIRED: bool>: Un estrattore per estrarre dati dall'intestazione della richiesta. HeaderParam<T, false> significa che questo parametro non è obbligatorio e può essere omesso. HeaderParam<T, true> significa che questo parametro è obbligatorio e non può essere omesso. Se non viene fornito, verrà restituito un errore;

  • CookieParam<T, const REQUIRED: bool>: Un estrattore per estrarre dati dal cookie della richiesta. CookieParam<T, false> significa che questo parametro non è obbligatorio e può essere omesso. CookieParam<T, true> significa che questo parametro è obbligatorio e non può essere omesso. Se non viene fornito, verrà restituito un errore;

  • PathParam<T>: Un estrattore per estrarre i parametri del percorso dall'URL della richiesta. Se questo parametro non esiste, la corrispondenza della rotta non avrà successo, quindi non esiste il caso in cui possa essere omesso;

  • FormBody<T>: Estrae informazioni dal modulo inviato dalla richiesta;

  • JsonBody<T>: Estrae informazioni dal payload in formato JSON inviato dalla richiesta;

#[endpoint]

Quando si generano documenti OpenAPI, è necessario utilizzare la macro #[endpoint] invece della normale macro #[handler]. In realtà è una versione potenziata della macro #[handler].

  • Può ottenere le informazioni necessarie per generare OpenAPI tramite la firma della funzione;

  • Per le informazioni che non sono comode da fornire tramite la firma, puoi fornirle direttamente aggiungendo attributi nella macro #[endpoint]. Le informazioni fornite in questo modo verranno unite con le informazioni ottenute tramite la firma della funzione. Se c'è un conflitto, sovrascriverà le informazioni fornite dalla firma della funzione.

Puoi usare l'attributo integrato di Rust #[deprecated] per contrassegnare un Handler come obsoleto. Sebbene l'attributo #[deprecated] supporti l'aggiunta di informazioni come il motivo della deprecazione e la versione, OpenAPI non lo supporta, quindi queste informazioni verranno ignorate durante la generazione di OpenAPI.

La parte del commento di documentazione nel codice verrà estratta automaticamente per generare OpenAPI. La prima riga viene utilizzata per generare summary, e l'intera parte del commento verrà utilizzata per generare description.

/// Questo è un riepilogo dell'operazione
///
/// Tutte le righe del commento di documentazione saranno incluse nella descrizione dell'operazione.
#[endpoint]
fn endpoint() {}

ToSchema

Puoi usare #[derive(ToSchema)] per definire strutture di dati:

#[derive(ToSchema)]
struct Pet {
    id: u64,
    name: String,
}

Puoi usare #[salvo(schema(...))] per definire impostazioni opzionali:

  • example = ... può essere json!(...). json!(...) verrà analizzato da serde_json::json! come serde_json::Value.

    #[derive(ToSchema)]
    #[salvo(schema(example = json!({"name": "bob the cat", "id": 0})))]
    struct Pet {
        id: u64,
        name: String,
    }
  • xml(...) può essere usato per definire le proprietà dell'oggetto Xml:

    #[derive(ToSchema)]
    struct Pet {
        id: u64,
        #[salvo(schema(xml(name = "pet_name", prefix = "u")))]
        name: String,
    }

ToParameters

Genera [parametri di percorso][path_parameters] dai campi della struttura.

Questa è l'implementazione #[derive] del tratto [ToParameters][to_parameters].

Di solito, i parametri di percorso devono essere definiti in [#[salvo_oapi::endpoint(...parameters(...))]][path_parameters] dell'endpoint. Tuttavia, quando si usa una [struct][struct] per definire i parametri, i passaggi sopra possono essere omessi. Tuttavia, se è necessario fornire una descrizione o modificare la configurazione predefinita, allora i parametri di percorso di tipo [primitive types][primitive] e [String][std_string] o i parametri di percorso in stile [tuple] devono ancora essere definiti in parameters(...).

Puoi usare l'attributo integrato di Rust #[deprecated] per contrassegnare i campi come deprecati, il che si rifletterà nella specifica OpenAPI generata.

L'attributo #[deprecated] supporta l'aggiunta di informazioni extra come il motivo della deprecazione o la versione da cui è deprecato, ma OpenAPI non lo supporta. OpenAPI supporta solo un valore booleano per determinare se è deprecato. Sebbene sia perfettamente possibile dichiarare una deprecazione con un motivo, come #[deprecated = "There is better way to do this"], questo motivo non sarà presentato nella specifica OpenAPI.

Il commento di documentazione sul campo della struttura verrà utilizzato come descrizione del parametro nella specifica OpenAPI generata.

#[derive(salvo_oapi::ToParameters, serde::Deserialize)]
struct Query {
    /// Interroga gli elementi todo per nome.
    name: String
}

Attributi Contenitore ToParameters per #[salvo(parameters(...))]

I seguenti attributi possono essere utilizzati nell'attributo contenitore #[salvo(parameters(…))] della struttura derivata da ToParameters

  • names(...) Definisce un elenco separato da virgole di nomi per i campi senza nome della struttura utilizzati come parametri di percorso. Supportato solo su strutture senza nome.
  • style = ... Definisce il metodo di serializzazione per tutti i parametri, specificato da [ParameterStyle][style]. Il valore predefinito è basato sull'attributo parameter_in.
  • default_parameter_in = ... Definisce la posizione predefinita utilizzata dai parametri di questo campo. Il valore di questa posizione proviene da [parameter::ParameterIn][in_enum]. Se questo attributo non viene fornito, il valore predefinito è query.
  • rename_all = ... può essere utilizzato come alternativa a rename_all di serde. Fornisce effettivamente la stessa funzionalità.

Usa names per definire un nome per un singolo parametro senza nome.

# use salvo_oapi::ToParameters;

#[derive(ToParameters, serde::Deserialize)]
#[salvo(parameters(names("id")))]
struct Id(u64);

Usa names per definire nomi per più parametri senza nome.

# use salvo_oapi::ToParameters;

#[derive(ToParameters, serde::Deserialize)]
#[salvo(parameters(names("id", "name")))]
struct IdAndName(u64, String);

Attributi Campo ToParameters per #[salvo(parameter(...))]

I seguenti attributi possono essere utilizzati sui campi della struttura #[salvo(parameter(...))]:

  • style = ... Definisce come i parametri vengono serializzati da [ParameterStyle][style]. Il valore predefinito è basato sull'attributo parameter_in.

  • parameter_in = ... Usa il valore da [parameter::ParameterIn][in_enum] per definire dove si trova questo parametro di campo. Se questo valore non viene fornito, il valore predefinito è query.

  • explode Definisce se creare una nuova coppia parameter=value per ogni parametro in object o array.

  • allow_reserved Definisce se i caratteri riservati :/?#[]@!$&'()*+,;= sono consentiti nel valore del parametro.

  • example = ... può essere un riferimento a un metodo o json!(...). L'esempio dato sovrascriverà qualsiasi esempio del tipo di parametro sottostante.

  • value_type = ... può essere utilizzato per sovrascrivere il tipo predefinito utilizzato dai campi nella specifica OpenAPI. Questo è utile quando il tipo predefinito non corrisponde al tipo effettivo, ad esempio, quando si utilizzano tipi di terze parti non definiti in [ToSchema][to_schema] o tipi [primitive][primitive]. Il valore può essere qualsiasi tipo Rust che può essere serializzato in JSON in circostanze normali, o un tipo personalizzato come _Object_._Object`_ che verrà visualizzato come un oggetto OpenAPI generico.

  • inline Se abilitato, la definizione di questo tipo di campo deve provenire da [ToSchema][to_schema], e questa definizione sarà in linea.

  • default = ... può essere un riferimento a un metodo o json!(...).

  • format = ... può essere una variante dell'enum [KnownFormat][known_format], o un valore aperto in forma di stringa. Per impostazione predefinita, il formato è dedotto dal tipo dell'attributo secondo la specifica OpenApi.

  • write_only Definisce che l'attributo è utilizzato solo per operazioni di scrittura POST,PUT,PATCH e non per GET.

  • read_only Definisce che l'attributo è utilizzato solo per operazioni di lettura GET e non per POST,PUT,PATCH.

  • nullable Definisce se l'attributo può essere null (nota che questo è diverso da non richiesto).

  • required = ... è utilizzato per forzare il parametro a essere obbligatorio. Vedi regole.

  • rename = ... può essere utilizzato come alternativa a rename di serde. Fornisce effettivamente la stessa funzionalità.

  • multiple_of = ... è utilizzato per definire un multiplo del valore. Il valore del parametro è considerato valido solo quando il valore del parametro è diviso per il valore di questa parola chiave e il risultato è un intero. Il valore multiplo deve essere strettamente maggiore di 0.

  • maximum = ... è utilizzato per definire il limite superiore del valore, incluso il valore corrente.

  • minimum = ... è utilizzato per definire il limite inferiore del valore, incluso il valore corrente.

  • exclusive_maximum = ... è utilizzato per definire il limite superiore del valore, escluso il valore corrente.

  • exclusive_minimum = ... è utilizzato per definire il limite inferiore del valore, escluso il valore corrente.

  • max_length = ... è utilizzato per definire la lunghezza massima di un valore di tipo string.

  • min_length = ... è utilizzato per definire la lunghezza minima di un valore di tipo string.

  • pattern = ... è utilizzato per definire un'espressione regolare valida a cui il valore del campo deve corrispondere. L'espressione regolare adotta la versione ECMA-262.

  • max_items = ... può essere utilizzato per definire il numero massimo di elementi consentiti in un campo di tipo array. Il valore deve essere un intero non negativo.

  • min_items = ... può essere utilizzato per definire il numero minimo di elementi consentiti in un campo di tipo array. Il valore deve essere un intero non negativo.

  • with_schema = ... utilizza un riferimento a una funzione per creare uno schema invece dello schema predefinito. La funzione deve soddisfare la definizione fn() -> Into<RefOr<Schema>>. Non riceve alcun parametro e deve restituire qualsiasi valore che possa essere convertito in RefOr<Schema>.

  • additional_properties = ... è utilizzato per definire tipi a forma libera per map, come HashMap e BTreeMap. I tipi a forma libera consentono di utilizzare qualsiasi tipo nei valori della mappa. I formati supportati sono additional_properties e additional_properties = true.

Regole di nullabilità e obbligatorietà dei campi

Alcune delle regole per la nullabilità e l'obbligatorietà degli attributi dei campi ToParameters possono essere utilizzate anche per gli attributi dei campi ToSchema. [Vedi regole](https