OpenAPI-Dokumentationsgenerierung

OpenAPI ist eine Open-Source-Spezifikation zur Beschreibung von RESTful-API-Schnittstellendesigns. Sie definiert API-Anfrage- und Antwortstrukturen, Parameter, Rückgabetypen, Fehlercodes und andere Details im JSON- oder YAML-Format, wodurch die Kommunikation zwischen Client und Server expliziter und standardisierter wird.

OpenAPI war ursprünglich die Open-Source-Version der Swagger-Spezifikation und ist heute ein eigenständiges Projekt, das von vielen großen Unternehmen und Entwicklern unterstützt wird. Die Verwendung der OpenAPI-Spezifikation hilft Entwicklungsteams, besser zusammenzuarbeiten, Kommunikationskosten zu senken und die Entwicklungseffizienz zu steigern. Darüber hinaus bietet OpenAPI Entwicklern Werkzeuge zur automatischen Generierung von API-Dokumentation, Mock-Daten und Testfällen, was Entwicklungs- und Testarbeiten erleichtert.

Salvo bietet OpenAPI-Integration (modifiziert von utoipa). Salvo extrahiert elegant relevante OpenAPI-Datentypinformationen automatisch aus Handler basierend auf seinen eigenen Eigenschaften. Salvo integriert auch mehrere beliebte Open-Source-OpenAPI-Schnittstellen wie SwaggerUI, Scalar, RapiDoc und ReDoc.

Da Rust-Typnamen lang sein können und nicht immer für die OpenAPI-Verwendung geeignet sind, bietet salvo-oapi den Typ Namer, der es ermöglicht, Regeln anzupassen, um Typnamen in OpenAPI nach Bedarf zu ändern.

Beispielcode

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;
}

Geben Sie http://localhost:8698/swagger-ui in Ihrem Browser ein, um die Swagger-UI-Seite zu sehen.

Die OpenAPI-Integration in Salvo ist recht elegant. Für das obige Beispiel haben wir im Vergleich zu einem normalen Salvo-Projekt nur die folgenden Schritte durchgeführt:

  • Aktivieren Sie das Feature oapi in Cargo.toml: salvo = { workspace = true, features = ["oapi"] };

  • Ersetzen Sie #[handler] durch #[endpoint];

  • Verwenden Sie name: QueryParam<String, false>, um den Wert der Abfragezeichenkette zu erhalten. Wenn Sie http://localhost/hello?name=chris aufrufen, wird die name-Abfragezeichenkette geparst. Das false in QueryParam<String, false> bedeutet, dass dieser Parameter optional ist. Wenn Sie http://localhost/hello aufrufen, wird kein Fehler gemeldet. Im Gegensatz dazu bedeutet QueryParam<String, true>, dass dieser Parameter zwingend bereitgestellt werden muss, andernfalls wird ein Fehler zurückgegeben.

  • Erstellen Sie OpenAPI und den entsprechenden Router. Das merge_router in OpenApi::new("test api", "0.0.1").merge_router(&router) bedeutet, dass dieses OpenAPI die notwendigen Dokumentinformationen durch Parsen einer bestimmten Route und ihrer Unterrouten erhält. Einige Handler von Routen liefern möglicherweise keine Informationen zur Dokumentgenerierung, und diese Routen werden ignoriert, wie z.B. Handler, die mit dem #[handler]-Makro anstelle des #[endpoint]-Makros definiert wurden. Das heißt, in realen Projekten können Sie aus Gründen wie dem Entwicklungsfortschritt wählen, keine OpenAPI-Dokumente zu generieren oder nur teilweise OpenAPI-Dokumente zu generieren. Anschließend können Sie schrittweise die Anzahl der generierten OpenAPI-Schnittstellen erhöhen, und alles, was Sie tun müssen, ist, #[handler] in #[endpoint] zu ändern und die Funktionssignatur anzupassen.

Datenextraktoren

Sie können vordefinierte gängige Datenextraktoren über use salvo::oapi::extract::*; importieren. Der Extraktor liefert Salvo einige notwendige Informationen, damit Salvo OpenAPI-Dokumente generieren kann.

  • QueryParam<T, const REQUIRED: bool>: Ein Extraktor zum Extrahieren von Daten aus Abfragezeichenketten. QueryParam<T, false> bedeutet, dass dieser Parameter nicht erforderlich ist und weggelassen werden kann. QueryParam<T, true> bedeutet, dass dieser Parameter erforderlich ist und nicht weggelassen werden darf. Wenn er nicht bereitgestellt wird, wird ein Fehler zurückgegeben;

  • HeaderParam<T, const REQUIRED: bool>: Ein Extraktor zum Extrahieren von Daten aus dem Anfrageheader. HeaderParam<T, false> bedeutet, dass dieser Parameter nicht erforderlich ist und weggelassen werden kann. HeaderParam<T, true> bedeutet, dass dieser Parameter erforderlich ist und nicht weggelassen werden darf. Wenn er nicht bereitgestellt wird, wird ein Fehler zurückgegeben;

  • CookieParam<T, const REQUIRED: bool>: Ein Extraktor zum Extrahieren von Daten aus dem Anfrage-Cookie. CookieParam<T, false> bedeutet, dass dieser Parameter nicht erforderlich ist und weggelassen werden kann. CookieParam<T, true> bedeutet, dass dieser Parameter erforderlich ist und nicht weggelassen werden darf. Wenn er nicht bereitgestellt wird, wird ein Fehler zurückgegeben;

  • PathParam<T>: Ein Extraktor zum Extrahieren von Pfadparametern aus der Anfrage-URL. Wenn dieser Parameter nicht existiert, wird das Routen-Matching nicht erfolgreich sein, daher gibt es keinen Fall, in dem er weggelassen werden kann;

  • FormBody<T>: Extrahiert Informationen aus dem übermittelten Formular der Anfrage;

  • JsonBody<T>: Extrahiert Informationen aus der JSON-formatierten Nutzlast, die von der Anfrage übermittelt wird;

#[endpoint]

Bei der Generierung von OpenAPI-Dokumenten müssen Sie das #[endpoint]-Makro anstelle des regulären #[handler]-Makros verwenden. Es handelt sich tatsächlich um eine erweiterte Version des #[handler]-Makros.

  • Es kann die für die OpenAPI-Generierung notwendigen Informationen über die Funktionssignatur erhalten;

  • Für Informationen, die nicht bequem über die Signatur bereitgestellt werden können, können Sie diese direkt durch Hinzufügen von Attributen im #[endpoint]-Makro bereitstellen. Die auf diese Weise bereitgestellten Informationen werden mit den über die Funktionssignatur erhaltenen Informationen zusammengeführt. Bei Konflikten überschreiben sie die von der Funktionssignatur bereitgestellten Informationen.

Sie können das in Rust integrierte #[deprecated]-Attribut verwenden, um einen Handler als veraltet zu markieren. Obwohl das #[deprecated]-Attribut das Hinzufügen von Informationen wie dem Grund für die Veraltung und der Version unterstützt, unterstützt OpenAPI dies nicht, daher werden diese Informationen bei der OpenAPI-Generierung ignoriert.

Der Dokumentationskommentarteil im Code wird automatisch extrahiert, um OpenAPI zu generieren. Die erste Zeile wird zur Generierung von summary verwendet, und der gesamte Kommentarteil wird zur Generierung von description verwendet.

/// Dies ist eine Zusammenfassung der Operation
///
/// Alle Zeilen des Dokumentationskommentars werden in die Operationsbeschreibung aufgenommen.
#[endpoint]
fn endpoint() {}

ToSchema

Sie können #[derive(ToSchema)] verwenden, um Datenstrukturen zu definieren:

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

Sie können #[salvo(schema(...))] verwenden, um optionale Einstellungen zu definieren:

  • example = ... kann json!(...) sein. json!(...) wird von serde_json::json! als serde_json::Value geparst.

    #[derive(ToSchema)]
    #[salvo(schema(example = json!({"name": "bob the cat", "id": 0})))]
    struct Pet {
        id: u64,
        name: String,
    }
  • xml(...) kann verwendet werden, um Xml-Objekteigenschaften zu definieren:

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

ToParameters

Generiert [Pfadparameter][path_parameters] aus den Feldern der Struktur.

Dies ist die #[derive]-Implementierung des [ToParameters][to_parameters]-Traits.

Normalerweise müssen Pfadparameter in [#[salvo_oapi::endpoint(...parameters(...))]][path_parameters] des Endpoints definiert werden. Bei Verwendung einer [struct][struct] zur Parameterdefinition können diese Schritte jedoch weggelassen werden. Dennoch, wenn Sie eine Beschreibung angeben oder die Standardkonfiguration ändern müssen, dann müssen [primitive Typen][primitive] und [String][std_string]-Pfadparameter oder [Tupel]-stil Pfadparameter weiterhin in parameters(...) definiert werden.

Sie können das in Rust integrierte #[deprecated]-Attribut verwenden, um Felder als veraltet zu markieren, was sich in der generierten OpenAPI-Spezifikation widerspiegelt.

Das #[deprecated]-Attribut unterstützt das Hinzufügen zusätzlicher Informationen wie dem Grund für die Veraltung oder der Version, ab der es veraltet ist, aber OpenAPI unterstützt dies nicht. OpenAPI unterstützt nur einen booleschen Wert, um zu bestimmen, ob etwas veraltet ist. Obwohl es durchaus möglich ist, eine Veraltung mit einem Grund zu deklarieren, wie #[deprecated = "There is better way to do this"], wird dieser Grund nicht in der OpenAPI-Spezifikation dargestellt.

Der Kommentardokumentation auf dem Strukturfeld wird als Parameterbeschreibung in der generierten OpenAPI-Spezifikation verwendet.

#[derive(salvo_oapi::ToParameters, serde::Deserialize)]
struct Query {
    /// Abfrage von Todo-Elementen nach Namen.
    name: String
}

ToParameters Container-Attribute für #[salvo(parameters(...))]

Die folgenden Attribute können im Container-Attribut #[salvo(parameters(…))] der von ToParameters abgeleiteten Struktur verwendet werden:

  • names(...) Definiert eine kommagetrennte Liste von Namen für die unbenannten Felder der als Pfadparameter verwendeten Struktur. Nur bei unbenannten Strukturen unterstützt.
  • style = ... Definiert die Serialisierungsmethode für alle Parameter, angegeben durch [ParameterStyle][style]. Der Standardwert basiert auf dem parameter_in-Attribut.
  • default_parameter_in = ... Definiert die Standardposition, die von den Parametern dieses Felds verwendet wird. Der Wert dieser Position stammt aus [parameter::ParameterIn][in_enum]. Wenn dieses Attribut nicht bereitgestellt wird, ist der Standardwert query.
  • rename_all = ... Kann als Alternative zu serdes rename_all verwendet werden. Es bietet tatsächlich die gleiche Funktionalität.

Verwenden Sie names, um einen Namen für einen einzelnen unbenannten Parameter zu definieren.

# use salvo_oapi::ToParameters;

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

Verwenden Sie names, um Namen für mehrere unbenannte Parameter zu definieren.

# use salvo_oapi::ToParameters;

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

ToParameters Feld-Attribute für #[salvo(parameter(...))]

Die folgenden Attribute können auf Strukturfeldern #[salvo(parameter(...))] verwendet werden:

  • style = ... Definiert, wie Parameter durch [ParameterStyle][style] serialisiert werden. Der Standardwert basiert auf dem parameter_in-Attribut.

  • parameter_in = ... Verwenden Sie den Wert aus [parameter::ParameterIn][in_enum], um zu definieren, wo sich dieser Feldparameter befindet. Wenn dieser Wert nicht bereitgestellt wird, ist der Standardwert query.

  • explode Definiert, ob für jeden Parameter in object oder array ein neues parameter=value-Paar erstellt werden soll.

  • allow_reserved Definiert, ob reservierte Zeichen :/?#[]@!$&'()*+,;= im Parameterwert erlaubt sind.

  • example = ... Kann eine Methodenreferenz oder json!(...) sein. Das gegebene Beispiel überschreibt alle Beispiele des zugrundeliegenden Parametertyps.

  • value_type = ... Kann verwendet werden, um den Standardtyp, der von Feldern in der OpenAPI-Spezifikation verwendet wird, zu überschreiben. Dies ist nützlich, wenn der Standardtyp nicht dem tatsächlichen Typ entspricht, z.B. bei Verwendung von Drittanbietertypen, die nicht in [ToSchema][to_schema] oder [primitive Typen][primitive] definiert sind. Der Wert kann jeder Rust-Typ sein, der unter normalen Umständen zu JSON serialisiert werden kann, oder ein benutzerdefinierter Typ wie Object.Object, der als generisches OpenAPI-Objekt gerendert wird.

  • inline Wenn aktiviert, muss die Definition dieses Feldtyps aus [ToSchema][to_schema] stammen, und diese Definition wird inline gesetzt.

  • default = ... Kann eine Methodenreferenz oder json!(...) sein.

  • format = ... Kann eine Variante der [KnownFormat][known_format]-Enum oder ein offener Wert in Stringform sein. Standardmäßig wird das Format aus dem Typ des Attributs gemäß der OpenApi-Spezifikation abgeleitet.

  • write_only Definiert, dass das Attribut nur für Schreiboperationen POST,PUT,PATCH und nicht für GET verwendet wird.

  • read_only Definiert, dass das Attribut nur für Leseoperationen GET und nicht für POST,PUT,PATCH verwendet wird.

  • nullable Definiert, ob das Attribut null sein kann (beachten Sie, dass dies sich von nicht erforderlich unterscheidet).

  • required = ... Wird verwendet, um zu erzwingen, dass der Parameter erforderlich ist. Siehe Regeln.

  • rename = ... Kann als Alternative zu serdes rename verwendet werden. Es bietet tatsächlich die gleiche Funktionalität.

  • multiple_of = ... Wird verwendet, um ein Vielfaches des Werts zu definieren. Der Parameterwert gilt nur dann als gültig, wenn der Parameterwert durch den Wert dieses Schlüsselworts geteilt wird und das Ergebnis eine ganze Zahl ist. Der Vielfachwert muss strikt größer als 0 sein.

  • maximum = ... Wird verwendet, um die Obergrenze des Werts zu definieren, einschließlich des aktuellen Werts.

  • minimum = ... Wird verwendet, um die Untergrenze des Werts zu definieren, einschließlich des aktuellen Werts.

  • exclusive_maximum = ... Wird verwendet, um die Obergrenze des Werts zu definieren, ausschließlich des aktuellen Werts.

  • exclusive_minimum = ... Wird verwendet, um die Untergrenze des Werts zu definieren, ausschließlich des aktuellen Werts.

  • max_length = ... Wird verwendet, um die maximale Länge eines string-Typwerts zu definieren.

  • min_length = ... Wird verwendet, um die minimale Länge eines string-Typwerts zu definieren.

  • pattern = ... Wird verwendet, um einen gültigen regulären Ausdruck zu definieren, dem der Feldwert entsprechen muss. Der reguläre Ausdruck verwendet die ECMA-262-Version.

  • max_items = ... Kann verwendet werden, um die maximale Anzahl von Elementen zu definieren, die in einem array-Typfeld erlaubt sind. Der Wert muss eine nicht-negative Ganzzahl sein.

  • min_items = ... Kann verwendet werden, um die minimale Anzahl von Elementen zu definieren, die in einem array-Typfeld erlaubt sind. Der Wert muss eine nicht-negative Ganzzahl sein.

  • with_schema = ... Verwendet eine Funktionsreferenz, um ein schema anstelle des Standard-schema zu erstellen. Die Funktion muss der Definition fn() -> Into<RefOr<Schema>> genügen. Sie erhält keine Parameter und muss einen beliebigen Wert zurückgeben, der in RefOr<Schema> konvertiert werden kann.

  • additional_properties = ... Wird verwendet, um freiform-Typen für map zu definieren, wie z.B.