From a7e4a593fb3cd659521cece7e771e67a371a8bfb Mon Sep 17 00:00:00 2001 From: ashb155 Date: Fri, 2 Oct 2026 13:01:06 +0530 Subject: [PATCH 1/4] add tests for GetAvailableLanguages handler --- CHANGELOG.md | 1 + api/handlers/language.go | 10 ++- api/handlers/language_test.go | 157 ++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 api/handlers/language_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index afe1779..6f54810 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ Emojis for the following are chosen based on [gitmoji](https://gitmoji.dev/). - The CI workflow was updated to select the Go version via `go-version-file` ([#8](https://github.com/scribe-org/Scribe-Server/issues/8)) and later improved further ([#30](https://github.com/scribe-org/Scribe-Server/issues/30)). - Unit tests were added for the language validator functions ([#78](https://github.com/scribe-org/Scribe-Server/issues/78)). - Unit tests were added for the YAML normalizer function ([#81](https://github.com/scribe-org/Scribe-Server/issues/81)). +- Unit tests were added for the available languages handler. ### ♻️ Code Refactoring diff --git a/api/handlers/language.go b/api/handlers/language.go index 6b7aad8..374c2a1 100644 --- a/api/handlers/language.go +++ b/api/handlers/language.go @@ -24,6 +24,12 @@ import ( // MARK: Languages Endpoints +// Function variables allow mocking database queries in unit tests. +var ( + getAvailableLanguages = database.GetAvailableLanguages + getLanguageDataTypes = database.GetLanguageDataTypes +) + // GetAvailableLanguages returns a list of all supported languages and their available data types. // // @Summary List all supported languages @@ -35,7 +41,7 @@ import ( // @Failure 500 {object} models.ErrorResponse "Internal server error occurred while fetching languages" // @Router /api/v1/languages [get] func GetAvailableLanguages(c *gin.Context) { - languages, err := database.GetAvailableLanguages() + languages, err := getAvailableLanguages() if err != nil { log.Printf("Error fetching available languages: %v", err) HandleError(c, http.StatusInternalServerError, constants.ErrorFetchingLanguages) @@ -44,7 +50,7 @@ func GetAvailableLanguages(c *gin.Context) { var languageInfos []models.LanguageInfo for _, lang := range languages { - dataTypes, err := database.GetLanguageDataTypes(lang) + dataTypes, err := getLanguageDataTypes(lang) if err != nil { log.Printf("Error fetching data types for %s: %v", lang, err) continue diff --git a/api/handlers/language_test.go b/api/handlers/language_test.go new file mode 100644 index 0000000..e876e37 --- /dev/null +++ b/api/handlers/language_test.go @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package handlers + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/scribe-org/scribe-server/internal/constants" + "github.com/scribe-org/scribe-server/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// setupTestRouter creates a minimal Gin engine configured in test mode for testing the endpoint. +func setupTestRouter(t *testing.T) *gin.Engine { + t.Helper() + + previousMode := gin.Mode() + gin.SetMode(gin.TestMode) + t.Cleanup(func() { gin.SetMode(previousMode) }) + + router := gin.New() + router.GET("/api/v1/languages", GetAvailableLanguages) + return router +} + +// stubLanguageDatabase temporarily swaps package-level database function pointers for isolated unit testing, +func stubLanguageDatabase( + t *testing.T, + languagesFn func() ([]string, error), + dataTypesFn func(lang string) ([]string, error), +) { + t.Helper() + + origLanguages := getAvailableLanguages + origDataTypes := getLanguageDataTypes + + getAvailableLanguages = languagesFn + getLanguageDataTypes = dataTypesFn + + t.Cleanup(func() { + getAvailableLanguages = origLanguages + getLanguageDataTypes = origDataTypes + }) +} + +// TestGetAvailableLanguages tests the GET /api/v1/languages handler under various database scenarios. +func TestGetAvailableLanguages(t *testing.T) { + router := setupTestRouter(t) + + t.Run("200 with a populated array", func(t *testing.T) { + stubLanguageDatabase( + t, + func() ([]string, error) { + return []string{"en", "de"}, nil + }, + func(lang string) ([]string, error) { + switch lang { + case "en": + return []string{"nouns", "verbs"}, nil + case "de": + return []string{"nouns", "verbs", "adjectives"}, nil + default: + return nil, fmt.Errorf("unexpected language: %s", lang) + } + }, + ) + + req := httptest.NewRequest(http.MethodGet, "/api/v1/languages", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, "application/json; charset=utf-8", rec.Header().Get("Content-Type")) + + var response models.AvailableLanguagesResponse + require.NoError(t, json.NewDecoder(rec.Body).Decode(&response)) + + expected := models.AvailableLanguagesResponse{ + Languages: []models.LanguageInfo{ + { + Code: "en", + DataTypes: []string{"nouns", "verbs"}, + }, + { + Code: "de", + DataTypes: []string{"nouns", "verbs", "adjectives"}, + }, + }, + } + assert.Equal(t, expected, response) + }) + + t.Run("500 when GetAvailableLanguages errors", func(t *testing.T) { + stubLanguageDatabase( + t, + func() ([]string, error) { + return nil, errors.New("database connection failed") + }, + func(lang string) ([]string, error) { + return nil, nil + }, + ) + + req := httptest.NewRequest(http.MethodGet, "/api/v1/languages", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusInternalServerError, rec.Code) + assert.Equal(t, "application/json; charset=utf-8", rec.Header().Get("Content-Type")) + + var response models.ErrorResponse + require.NoError(t, json.NewDecoder(rec.Body).Decode(&response)) + assert.Equal(t, constants.ErrorFetchingLanguages, response.Error) + }) + + t.Run("partial-failure when GetLanguageDataTypes fails for a language", func(t *testing.T) { + stubLanguageDatabase( + t, + func() ([]string, error) { + return []string{"en", "fr"}, nil + }, + func(lang string) ([]string, error) { + if lang == "en" { + return []string{"nouns", "verbs"}, nil + } + return nil, errors.New("data types retrieval failed for fr") + }, + ) + + req := httptest.NewRequest(http.MethodGet, "/api/v1/languages", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, "application/json; charset=utf-8", rec.Header().Get("Content-Type")) + + var response models.AvailableLanguagesResponse + require.NoError(t, json.NewDecoder(rec.Body).Decode(&response)) + + expected := models.AvailableLanguagesResponse{ + Languages: []models.LanguageInfo{ + { + Code: "en", + DataTypes: []string{"nouns", "verbs"}, + }, + }, + } + assert.Equal(t, expected, response) + }) +} From 5d7bf2f3f2e474ed9f75fad033d2340521bf5935 Mon Sep 17 00:00:00 2001 From: ashb155 Date: Fri, 2 Oct 2026 13:32:25 +0530 Subject: [PATCH 2/4] changelog addition --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f54810..8566c12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,7 +60,7 @@ Emojis for the following are chosen based on [gitmoji](https://gitmoji.dev/). - The CI workflow was updated to select the Go version via `go-version-file` ([#8](https://github.com/scribe-org/Scribe-Server/issues/8)) and later improved further ([#30](https://github.com/scribe-org/Scribe-Server/issues/30)). - Unit tests were added for the language validator functions ([#78](https://github.com/scribe-org/Scribe-Server/issues/78)). - Unit tests were added for the YAML normalizer function ([#81](https://github.com/scribe-org/Scribe-Server/issues/81)). -- Unit tests were added for the available languages handler. +- Unit tests were added for the available languages handler ([#84](https://github.com/scribe-org/Scribe-Server/issues/84)). ### ♻️ Code Refactoring From 3481670b0217e2b89ee38ec569f916dcd3a4fc6f Mon Sep 17 00:00:00 2001 From: DeleMike Date: Sun, 4 Oct 2026 19:07:35 +0100 Subject: [PATCH 3/4] add profanity and prepositions to the language stats --- CHANGELOG.md | 1 + api/handlers/statistics.go | 2 +- database/language.go | 89 ++++++++++++++++++++++++++++++-------- database/utils.go | 2 + docs/docs.go | 10 ++++- docs/swagger.json | 10 ++++- docs/swagger.yaml | 10 ++++- models/models.go | 4 ++ 8 files changed, 104 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f240aae..f3506c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Emojis for the following are chosen based on [gitmoji](https://gitmoji.dev/). - Scribe-Data data contracts are served via Scribe-Server to tell the client applications how to process the data ([#18](https://github.com/scribe-org/Scribe-Server/issues/18), [#57](https://github.com/scribe-org/Scribe-Server/issues/57)). - Data contracts were switched from JSON to YAML for easier maintenance ([#56](https://github.com/scribe-org/Scribe-Server/pull/56)). - Statistics for available languages are now shown via the API ([#44](https://github.com/scribe-org/Scribe-Server/issues/44)). + - Language statistics include counts of prepositions and profanity, which are null for languages without them. - An entry/landing page was set up for Scribe-Server ([#48](https://github.com/scribe-org/Scribe-Server/issues/48)), followed by a dedicated deployment and download page ([#52](https://github.com/scribe-org/Scribe-Server/pull/52)). - A translation data retrieval endpoint with validation logic was implemented ([#58](https://github.com/scribe-org/Scribe-Server/issues/58), [#59](https://github.com/scribe-org/Scribe-Server/issues/59)). - SQLite databases are available for download directly from the Scribe-Server UI ([#6](https://github.com/scribe-org/Scribe-Server/issues/6)). diff --git a/api/handlers/statistics.go b/api/handlers/statistics.go index fbc9966..e3f1af2 100644 --- a/api/handlers/statistics.go +++ b/api/handlers/statistics.go @@ -16,7 +16,7 @@ import ( // GetLanguageStats handles GET /language-stats?codes=en,fr. // @Summary Get statistics for one or multiple languages -// @Description Returns the number of nouns and verbs for the specified language codes. +// @Description Returns the number of nouns, verbs, prepositions and profanity entries for the given language codes (null if a language has none). // @Tags statistics // @Accept json // @Produce json diff --git a/database/language.go b/database/language.go index 1b2581c..f651402 100644 --- a/database/language.go +++ b/database/language.go @@ -11,6 +11,8 @@ import ( "github.com/go-sql-driver/mysql" "github.com/scribe-org/scribe-server/models" "github.com/spf13/viper" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) // MARK: Get Available Languages @@ -89,7 +91,50 @@ func GetLanguageDataTypes(lang string) ([]string, error) { // MARK: Language Statistics -// GetLanguageStat retrieves noun and verb statistics for a specific language. +// statDataTypes are the data types counted in language statistics in the order they're queried. +var statDataTypes = []string{"nouns", "verbs", "prepositions", "profanity"} + +// requiredStatDataTypes are the data types a language needs to be included in statistics. +// Other data types such as prepositions are only available for some languages. +var requiredStatDataTypes = []string{"nouns", "verbs"} + +// buildLanguageStatQuery builds a query that counts the entries of each statistics data type available for a language. +// It returns the query and the data types it counts in order, or no data types if a required data type is missing. +func buildLanguageStatQuery(lang string, availableDataTypes []string) (string, []string, error) { + available := make(map[string]bool, len(availableDataTypes)) + for _, dataType := range availableDataTypes { + available[strings.ToLower(dataType)] = true + } + + for _, dataType := range requiredStatDataTypes { + if !available[dataType] { + return "", nil, nil + } + } + + caser := cases.Title(language.Und) + counts := make([]string, 0, len(statDataTypes)) + countedDataTypes := make([]string, 0, len(statDataTypes)) + + for _, dataType := range statDataTypes { + if !available[dataType] { + continue + } + + // Construct the table name format: ENLanguageDataNounsScribe. + tableName := strings.ToUpper(lang) + "LanguageData" + caser.String(dataType) + "Scribe" + if !IsValidTableName(tableName) { + return "", nil, fmt.Errorf("invalid table name for language: %s", lang) + } + + counts = append(counts, fmt.Sprintf("(SELECT COUNT(*) FROM %s) AS %s", tableName, dataType)) + countedDataTypes = append(countedDataTypes, dataType) + } + + return "SELECT " + strings.Join(counts, ", "), countedDataTypes, nil +} + +// GetLanguageStat retrieves statistics for the data types of a specific language. func GetLanguageStat(lan string) (map[string]any, error) { // Normalize and validate language code (e.g., "EN", "FR"). lang := strings.ToUpper(strings.TrimSpace(lan)) @@ -97,24 +142,29 @@ func GetLanguageStat(lan string) (map[string]any, error) { return nil, fmt.Errorf("invalid language code: %s", lang) } - nounsTable := lang + "LanguageDataNounsScribe" - verbsTable := lang + "LanguageDataVerbsScribe" + dataTypes, err := GetLanguageDataTypes(lang) + if err != nil { + return nil, fmt.Errorf("error fetching data types for %s: %w", lang, err) + } - if !IsValidTableName(nounsTable) || !IsValidTableName(verbsTable) { - return nil, fmt.Errorf("invalid table names for language: %s", lang) + query, countedDataTypes, err := buildLanguageStatQuery(lang, dataTypes) + if err != nil { + return nil, err } - query := fmt.Sprintf(` - SELECT - (SELECT COUNT(*) FROM %s) AS nouns, - (SELECT COUNT(*) FROM %s) AS verbs - `, nounsTable, verbsTable) + if len(countedDataTypes) == 0 { + log.Printf("⚠️ Skipping %s — missing nouns or verbs table", lang) + return nil, nil + } // Execute the query. - row := DB.QueryRow(query) + counts := make([]int, len(countedDataTypes)) + scanTargets := make([]any, len(counts)) + for i := range counts { + scanTargets[i] = &counts[i] + } - var nouns, verbs int - err := row.Scan(&nouns, &verbs) + err = DB.QueryRow(query).Scan(scanTargets...) if err != nil { if mysqlErr, ok := err.(*mysql.MySQLError); ok && mysqlErr.Number == 1146 { log.Printf("⚠️ Skipping %s — missing table: %s", lang, mysqlErr.Message) @@ -128,14 +178,15 @@ func GetLanguageStat(lan string) (map[string]any, error) { return nil, fmt.Errorf("error scanning stats for %s: %w", lang, err) } - return map[string]any{ - "code": strings.ToLower(lang), - "nouns": nouns, - "verbs": verbs, - }, nil + stat := map[string]any{"code": strings.ToLower(lang)} + for i, dataType := range countedDataTypes { + stat[dataType] = counts[i] + } + + return stat, nil } -// GetAllLanguageStats retrieves statistics for all available languages (only nouns and verbs). +// GetAllLanguageStats retrieves statistics for all available languages that have nouns and verbs. func GetAllLanguageStats() ([]models.LanguageStatisticsReponse, error) { availableLanguages, err := GetAvailableLanguages() if err != nil { diff --git a/database/utils.go b/database/utils.go index 436ffef..aa14bce 100644 --- a/database/utils.go +++ b/database/utils.go @@ -122,5 +122,7 @@ func BuildLanguageStatResponse(code string, stat map[string]any) models.Language LanguageName: &langName, Nouns: ToIntPtr(stat["nouns"]), Verbs: ToIntPtr(stat["verbs"]), + Prepositions: ToIntPtr(stat["prepositions"]), + Profanity: ToIntPtr(stat["profanity"]), } } diff --git a/docs/docs.go b/docs/docs.go index 7247c47..8e58d88 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -178,7 +178,7 @@ const docTemplate = `{ }, "/api/v1/language-stats": { "get": { - "description": "Returns the number of nouns and verbs for the specified language codes.", + "description": "Returns the number of nouns, verbs, prepositions and profanity entries for the given language codes (null if a language has none).", "consumes": [ "application/json" ], @@ -423,6 +423,14 @@ const docTemplate = `{ "description": "Count of noun entries", "type": "integer" }, + "prepositions": { + "description": "Count of preposition entries (null if the language has no prepositions)", + "type": "integer" + }, + "profanity": { + "description": "Count of profanity entries (null if the language has no profanity)", + "type": "integer" + }, "verbs": { "description": "Count of verb entries", "type": "integer" diff --git a/docs/swagger.json b/docs/swagger.json index b07f502..8a8c01a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -172,7 +172,7 @@ }, "/api/v1/language-stats": { "get": { - "description": "Returns the number of nouns and verbs for the specified language codes.", + "description": "Returns the number of nouns, verbs, prepositions and profanity entries for the given language codes (null if a language has none).", "consumes": [ "application/json" ], @@ -417,6 +417,14 @@ "description": "Count of noun entries", "type": "integer" }, + "prepositions": { + "description": "Count of preposition entries (null if the language has no prepositions)", + "type": "integer" + }, + "profanity": { + "description": "Count of profanity entries (null if the language has no profanity)", + "type": "integer" + }, "verbs": { "description": "Count of verb entries", "type": "integer" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 8710b53..44ce787 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -71,6 +71,12 @@ definitions: nouns: description: Count of noun entries type: integer + prepositions: + description: Count of preposition entries (null if the language has no prepositions) + type: integer + profanity: + description: Count of profanity entries (null if the language has no profanity) + type: integer verbs: description: Count of verb entries type: integer @@ -239,8 +245,8 @@ paths: get: consumes: - application/json - description: Returns the number of nouns and verbs for the specified language - codes. + description: Returns the number of nouns, verbs, prepositions and profanity + entries for the given language codes (null if a language has none). parameters: - description: 'Comma-separated list of language codes to filter (e.g., ' in: query diff --git a/models/models.go b/models/models.go index 34117ec..e90eccc 100644 --- a/models/models.go +++ b/models/models.go @@ -116,4 +116,8 @@ type LanguageStatisticsReponse struct { Nouns *int `json:"nouns"` // Count of verb entries Verbs *int `json:"verbs"` + // Count of preposition entries (null if the language has no prepositions) + Prepositions *int `json:"prepositions"` + // Count of profanity entries (null if the language has no profanity) + Profanity *int `json:"profanity"` } From 3b66ece13661f5392fdcf009334265ff20b7875d Mon Sep 17 00:00:00 2001 From: DeleMike Date: Sun, 4 Oct 2026 19:08:05 +0100 Subject: [PATCH 4/4] add test files for new updates --- database/language_test.go | 80 +++++++++++++++++++++++++++++++++++++++ database/utils_test.go | 23 +++++++++-- 2 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 database/language_test.go diff --git a/database/language_test.go b/database/language_test.go new file mode 100644 index 0000000..cfac639 --- /dev/null +++ b/database/language_test.go @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package database + +import ( + "reflect" + "strings" + "testing" +) + +func TestBuildLanguageStatQueryAllDataTypes(t *testing.T) { + t.Parallel() + query, dataTypes, err := buildLanguageStatQuery("de", []string{"nouns", "prepositions", "profanity", "verbs"}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + want := []string{"nouns", "verbs", "prepositions", "profanity"} + if !reflect.DeepEqual(dataTypes, want) { + t.Fatalf("data types %v, want %v", dataTypes, want) + } + + for _, part := range []string{ + "(SELECT COUNT(*) FROM DELanguageDataNounsScribe) AS nouns", + "(SELECT COUNT(*) FROM DELanguageDataVerbsScribe) AS verbs", + "(SELECT COUNT(*) FROM DELanguageDataPrepositionsScribe) AS prepositions", + "(SELECT COUNT(*) FROM DELanguageDataProfanityScribe) AS profanity", + } { + if !strings.Contains(query, part) { + t.Fatalf("query %q missing %q", query, part) + } + } +} + +func TestBuildLanguageStatQueryOptionalDataTypesMissing(t *testing.T) { + t.Parallel() + // English has no prepositions table, which shouldn't exclude the language. + query, dataTypes, err := buildLanguageStatQuery("en", []string{"nouns", "profanity", "verbs"}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + want := []string{"nouns", "verbs", "profanity"} + if !reflect.DeepEqual(dataTypes, want) { + t.Fatalf("data types %v, want %v", dataTypes, want) + } + if strings.Contains(query, "Prepositions") { + t.Fatalf("query %q should not count prepositions", query) + } +} + +func TestBuildLanguageStatQueryRequiredDataTypeMissing(t *testing.T) { + t.Parallel() + for _, available := range [][]string{ + {"verbs", "profanity"}, + {"nouns", "prepositions"}, + {}, + } { + query, dataTypes, err := buildLanguageStatQuery("fr", available) + if err != nil { + t.Fatalf("unexpected error for %v: %v", available, err) + } + if query != "" || len(dataTypes) != 0 { + t.Fatalf("expected no query for %v, got %q %v", available, query, dataTypes) + } + } +} + +func TestBuildLanguageStatQueryIgnoresOtherDataTypes(t *testing.T) { + t.Parallel() + _, dataTypes, err := buildLanguageStatQuery("sv", []string{"emojikeywords", "nouns", "verbs"}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + want := []string{"nouns", "verbs"} + if !reflect.DeepEqual(dataTypes, want) { + t.Fatalf("data types %v, want %v", dataTypes, want) + } +} diff --git a/database/utils_test.go b/database/utils_test.go index b0ae926..f85ef1b 100644 --- a/database/utils_test.go +++ b/database/utils_test.go @@ -56,15 +56,32 @@ func TestBuildLanguageStatResponse(t *testing.T) { if resp.LanguageName == nil || *resp.LanguageName != "English" { t.Fatalf("name %+v", resp.LanguageName) } - if resp.Nouns != nil || resp.Verbs != nil { - t.Fatalf("expected nil counts, nouns=%v verbs=%v", resp.Nouns, resp.Verbs) + if resp.Nouns != nil || resp.Verbs != nil || resp.Prepositions != nil || resp.Profanity != nil { + t.Fatalf( + "expected nil counts, nouns=%v verbs=%v prepositions=%v profanity=%v", + resp.Nouns, resp.Verbs, resp.Prepositions, resp.Profanity, + ) } - resp = BuildLanguageStatResponse("de", map[string]any{"nouns": 3, "verbs": int64(9)}) + resp = BuildLanguageStatResponse("de", map[string]any{ + "nouns": 3, "verbs": int64(9), "prepositions": 2, "profanity": 1, + }) if resp.Nouns == nil || *resp.Nouns != 3 { t.Fatalf("nouns %+v", resp.Nouns) } if resp.Verbs == nil || *resp.Verbs != 9 { t.Fatalf("verbs %+v", resp.Verbs) } + if resp.Prepositions == nil || *resp.Prepositions != 2 { + t.Fatalf("prepositions %+v", resp.Prepositions) + } + if resp.Profanity == nil || *resp.Profanity != 1 { + t.Fatalf("profanity %+v", resp.Profanity) + } + + // Languages without prepositions have a nil count. + resp = BuildLanguageStatResponse("en", map[string]any{"nouns": 3, "verbs": 9, "profanity": 1}) + if resp.Prepositions != nil { + t.Fatalf("expected nil prepositions, got %v", *resp.Prepositions) + } }