Przejdź do treści

Wiązania protokołów

protocol jest osobną deklaracją najwyższego poziomu, wiążącą powierzchnię komunikacyjną z zadaniami. Nie nadaje zadaniu platformy. Poniższy opis oddziela składnię języka, interpretację tekstu wiązań i generowanie kodu.

Składnia

protocolDeclaration
    : PROTOCOL IDENTIFIER protocolBody
    ;
protocolBody
    : LBRACE protocolContent* RBRACE
    ;
protocolContent
    : protocolBody
    | ~(LBRACE | RBRACE)
    ;

B01 — nazwa i ciało

Po protocol wymagany jest identyfikator oraz ciało w klamrach. Ciało może być puste. Parser zachowuje jego treść i dopuszcza zagnieżdżone klamry; nie definiuje ustrukturyzowanej gramatyki metod, ścieżek ani argumentów wiązania. Poprawne parsowanie nie oznacza, że treść zostanie zinterpretowana.

Nazwy rest, grpc, ws, mqtt, coap, amqp czy własna nazwa są składniowo identyfikatorami. Akceptacja nazwy nie dowodzi dostępności adaptera.

B02 — obecna normalizacja nazw

DistributedTopologyLowerer sprowadza nazwę do małych liter, a następnie stosuje heurystykę: nazwa zawierająca rest, grpc, websocket, mqtt, coap, amqp lub kafka jest przypisywana do odpowiedniej rodziny; ws oznacza websocket. Pozostałe nazwy pozostają bez takiej zmiany. Sprawdzenia są uporządkowane w kodzie. Jest to zachowanie implementacji, nie pełny kontrakt rejestru protokołów ani mechanizmu rozszerzania języka.

Rozpoznawanie wiązań

B03 — postacie HTTP

Obecny algorytm szuka w ciele dwóch postaci, za pomocą wyrażeń regularnych:

protocol rest {
    GET("/items/{id}", Read(path id: Int))
}
protocol rest {
    GET /items/{id} -> Read(path id: Int)
}

Rozpoznawane metody to GET, POST, PUT, PATCH, DELETE, bez rozróżniania wielkości liter. HEAD i OPTIONS nie należą do tych wzorców. Postać funkcyjna wymaga ścieżki w podwójnych cudzysłowach; postać ze strzałką przechwytuje tekst ścieżki przed ->. Nazwa zadania jest dopasowywana jako identyfikator ASCII. Argumenty są przechwytywane do pierwszego ); nie jest to parser dowolnych zagnieżdżonych wyrażeń.

Te wzorce są obecnie przeszukiwane w każdej deklaracji protocol, nie tylko w deklaracji nazwanej rest. Rodzina zapisana w planie wynika jednak z nazwy deklaracji; znalezienie zapisu HTTP nie zmienia jej automatycznie na rest.

B04 — sprawdzanie zadania i powtórzeń

Rozpoznane wiązanie HTTP musi wskazywać istniejące zadanie; brak deklaracji powoduje błąd D6001. Dopasowanie nazwy zadania rozróżnia wielkość liter. Kontrola istnienia nie sprawdza pełnej zgodności sygnatury ani platformy wykonania.

W obrębie jednej deklaracji identyczna trójka (metoda, tekst ścieżki, zadanie) jest dodawana do planu tylko raz. To usuwanie powtórzeń w planie, nie reguła ponownego dostarczania żądań. Dwa różne zadania pod tą samą metodą i ścieżką nie są tym mechanizmem rozstrzygane jako konflikt. Nie ma wspólnego zbioru powtórzeń dla wszystkich deklaracji. Oba wzorce są przeszukiwane osobno, więc kolejność w planie nie ustanawia kolejności obsługi żądań.

B05 — źródła parametrów

Z tekstu argumentów algorytm zbiera zapisy path nazwa:, query nazwa:, body nazwa: i header nazwa:. W planie powstaje mapa nazwy parametru na źródło. Typ po dwukropku nie jest przez ten mechanizm porównywany z typem parametru zadania. Nie ma tu dowodu kontroli kompletności mapy, poprawności nazw, wymaganych argumentów ani zgodności zmiennych ścieżki z sygnaturą.

B06 — pozostałe rodziny

Dla rodziny innej niż rest dodatkowe przeszukiwanie znajduje teksty postaci Nazwa(. Jeżeli nazwa jest zadeklarowanym zadaniem, dodaje wiązanie bez metody i ścieżki. Nieznane nazwy w tym dodatkowym przeszukiwaniu są pomijane.

Dlatego protocol grpc { Read } nie tworzy wiązania przez tę ścieżkę, a protocol grpc { Read() } może je utworzyć. Missing() w tym przeszukiwaniu nie daje D6001. Nie należy z tego wyprowadzać reguł wykonywania RPC, publikacji MQTT, generowania .proto ani subskrypcji WebSocket.

Sprawdzone przykłady

Pary poniżej przechodzą rzeczywiste CLI w osobnych projektach. Potwierdzają przyjęcie albo odrzucenie źródła; nie są próbami obsługi żądań przez uruchomiony serwer.

Przykład poprawny

task Ping() @server -> Text {
    return "ok"
}

protocol rest {
    GET("/ping", Ping())
}

Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.

Przykład błędny

protocol { GET("/ping", Ping()) }

Powód: Deklaracja protocol wymaga nazwy.

Kontrola: przykład odrzucony przez check; kody: P0001.

Pełny zakres weryfikacji przykładów.

Przykład: Rozpoznane wiązanie i nieznane zadanie

Poprawny zapis

task Read(id: Int) @server -> Int { return id }
protocol rest { GET("/items/{id}", Read(path id: Int)) }

Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.

Błędny zapis

task Read(id: Int) @server -> Int { return id }
protocol rest { GET("/items", Missing()) }

Powód: Rozpoznane wiązanie HTTP wskazuje nieistniejące zadanie Missing.

Kontrola: przykład odrzucony przez check; kody: D6001.

Pełny zakres weryfikacji przykładów.

Przykład: Postać ze strzałką

Poprawny zapis

task Read(id: Int) @server -> Int { return id }
protocol rest { GET /items/{id} -> Read(path id: Int) }

Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.

Błędny zapis

task Read(id: Int) @server -> Int { return id }
protocol rest { GET /items -> Missing() }

Powód: Rozpoznana postać ze strzałką również wymaga istniejącego zadania.

Kontrola: przykład odrzucony przez check; kody: D6001.

Pełny zakres weryfikacji przykładów.

Co obecnie generuje ścieżka REST w Go

GoCodeGenerator wybiera z planu wiązania rodziny rest dotyczące zadań z adnotacją @server. Dla wpisów z metodą i ścieżką emituje rejestrację net/http i wywołanie zadania. Parametry bierze z sygnatury zadania, nie z typów w tekście wiązania.

Gdy mapa nie wskazuje źródła parametru, generator wybiera path, jeśli ścieżka zawiera {nazwaParametru}, w przeciwnym razie query. Jawne body lub typ inny niż IRNamedType prowadzą do dekodowania JSON z ciała. Pozostałe przypadki korzystają z odczytu ścieżki, nagłówka lub zapytania i konwersji według typu. To opis aktualnego generatora, nie uniwersalny kontrakt wszystkich adapterów.

Generator normalizuje zapis {id:Typ} do {id} i usuwa spacje ze ścieżki. Zadanie bez typu wyjścia prowadzi do odpowiedzi 204; z typem wyjścia — do kodowania wyniku jako JSON. Nie dowodzi to poprawnego mapowania dowolnego typu wyniku, w szczególności typu komunikacyjnego, na odpowiedź protokołu.

Powstaje również openapi.json w wersji formatu 3.0.3, zawierający ścieżki, metody, nazwę zadania jako operationId i ogólną odpowiedź 200. Ten generator nie buduje pełnych schematów parametrów i wyników; odpowiedź 200 w opisie nie odzwierciedla wszystkich gałęzi handlera.

Test Go_RestProtocol_EmitsTypedRouteAndOpenApiAsync sprawdza tekst wygenerowanej trasy, odczyt parametru ścieżki, wywołanie zadania i obecność operationId. Nie uruchamia serwera ani nie wysyła żądania HTTP.

Wyniki prób granicznych

Próby używały zadania Read(id: Int) @server -> Int.

Próba Wynik check Granica dowodu
Obie opisane postacie GET do Read przyjęte Rozpoznawany zapis; brak dowodu obsługi żądania.
GET do nieistniejącego Missing odrzucone, D6001 Sprawdzanie zadania w rozpoznanym wzorcu HTTP.
Read(query absent: Text) przyjęte bez diagnostyk Błędna nazwa parametru nie została wykryta.
Read(query id: Text) przyjęte bez diagnostyk Typ Text nie został porównany z Int.
HEAD("/items", Missing()) w rest przyjęte bez diagnostyk HEAD nie jest rozpoznawany; nie potwierdza poprawnego wiązania.
protocol grpc { Missing Read } przyjęte bez diagnostyk Same nazwy nie spełniają wzorca wywołania.
protocol grpc { Missing() Read() } przyjęte bez diagnostyk Dodatkowe przeszukiwanie pomija nieznane zadanie.
protocol custom { Read() } przyjęte bez diagnostyk Nie potwierdza istnienia wykonawcy custom.
Dwa identyczne wpisy GET przyjęte bez diagnostyk Brak błędu powtórzenia; algorytm planu usuwa identyczne trójki.
protocol rest { nonsense: true } przyjęte bez diagnostyk Nieznana treść nie uzyskuje przez to znaczenia.

Jawne luki

  • Ustrukturyzowana gramatyka wnętrza wiązania i diagnostyki nierozpoznanych wpisów.
  • Jednolite sprawdzanie nieistniejących zadań dla wszystkich form i rodzin.
  • Zgodność nazw i typów parametrów, kompletność argumentów oraz powtórzone źródła tego samego parametru. Obecna mapa nie ustanawia kontraktu takich konfliktów.
  • Walidacja platformy zadania, konfliktów tras oraz spójności schematów OpenAPI z rzeczywistymi odpowiedziami i typami danych.
  • Pełne kontrakty adapterów innych niż opisana ścieżka REST w Go.
  • TLS, uwierzytelnianie, autoryzacja, błędy domenowe, anulowanie, retry i gwarancje dostarczenia. Sam zapis protocol ich nie ustanawia.

Dobór transportu krawędzi pipeline jest osobnym etapem. Obecny DistributedTopologyLowerer.LowerEdges korzysta z platform i rodzaju komunikacji, a nie z listy jawnych wiązań protokołów. Historyczna tabela automatycznego doboru w spec/08-protocols.md różni się od tego algorytmu; nie przedstawiamy jej jako zweryfikowanej semantyki. Zobacz także pipeline i rozmieszczenie.

Podstawa w repozytorium

  • grammar/JunexisLang.g4: trzy reguły zamieszczone powyżej.
  • spec/08-protocols.md: deklaracyjny podział zadania i protokołu; opis kierunku nie jest dowodem wszystkich wymienionych tam adapterów.
  • compiler/JunexisLang.Compiler.Topology/DistributedTopologyLowerer.cs: wzorce, normalizacja nazw, sprawdzanie zadań i mapa źródeł parametrów.
  • compiler/JunexisLang.Compiler.CodeGen/GoCodeGenerator.cs: EmitRestProtocolBindings, EmitRestParameterBinding, GenerateOpenApi.
  • compiler/JunexisLang.Compiler.Tests/Topology/DistributedTopologyLowererTests.cs i compiler/JunexisLang.Compiler.CodeGen.Tests/GoProtocolCodeGenTests.cs: sprawdzenie planu i tekstu wygenerowanych artefaktów.