├── assets └── logo.png ├── .env ├── Directory.Build.props ├── .gitignore ├── src └── Meilisearch │ ├── runtimeconfig.json │ ├── QueryParameters │ ├── DeleteDocumentsQuery.cs │ ├── KeysQuery.cs │ ├── IndexesQuery.cs │ ├── DocumentsQuery.cs │ ├── CancelTasksQuery.cs │ ├── DeleteTasksQuery.cs │ └── TasksQuery.cs │ ├── ContentType.cs │ ├── Pagination.cs │ ├── Client │ ├── MeiliSearchHealth.cs │ └── MeiliSearchVersion.cs │ ├── MultiSearchQuery.cs │ ├── FederatedSearchQuery.cs │ ├── MultiSearchResult.cs │ ├── HybridSearch.cs │ ├── Errors │ ├── MeilisearchTenantTokenExpired.cs │ ├── MeilisearchTimeoutError.cs │ ├── MeilisearchTenantTokenApiKeyUidInvalid.cs │ ├── MeilisearchTenantTokenApiKeyInvalid.cs │ ├── MeilisearchCommunicationError.cs │ ├── MeilisearchApiErrorContent.cs │ └── MeilisearchApiError.cs │ ├── FacetStat.cs │ ├── IndexSwap.cs │ ├── MultiSearchFederationOptions.cs │ ├── EmbedderSource.cs │ ├── Version.cs │ ├── Result.cs │ ├── MatchPosition.cs │ ├── ResourceResults.cs │ ├── TasksResults.cs │ ├── Faceting.cs │ ├── Converters │ ├── TaskInfoTypeConverter.cs │ ├── SortFacetValuesConverter.cs │ ├── EmbedderSourceConverter.cs │ └── AlwaysIncludeEmptyObjectConverter.cs │ ├── FederatedMultiSearchQuery.cs │ ├── Index.SimilarDocuments.cs │ ├── Stats.cs │ ├── FacetSearchResult.cs │ ├── Constants.cs │ ├── Extensions │ ├── EnumerableExtensions.cs │ ├── ObjectExtensions.cs │ ├── StringExtensions.cs │ └── HttpExtensions.cs │ ├── TenantTokenRules.cs │ ├── TypoTolerance.cs │ ├── ISearchable.cs │ ├── FacetSearchQuery.cs │ ├── EmbedderDistribution.cs │ ├── SearchQuery.cs │ ├── TenantToken.cs │ ├── SimilarDocumentsResult.cs │ ├── Index.Dictionary.cs │ ├── Index.Faceting.cs │ ├── Index.SearchCutoffMs.cs │ ├── Index.Pagination.cs │ ├── Index.Settings.cs │ ├── Index.StopWords.cs │ ├── MeilisearchMessageHandler.cs │ ├── Index.RankingRules.cs │ ├── Index.Synonyms.cs │ ├── Index.TypoTolerance.cs │ ├── Index.Tasks.cs │ ├── Index.SeparatorTokens.cs │ ├── Index.NonSeparatorTokens.cs │ ├── IndexStats.cs │ ├── Index.ProximityPrecision.cs │ ├── ISearchableJsonConverter.cs │ ├── Embedder.cs │ ├── Index.Embedders.cs │ ├── SimilarDocumentsQuery.cs │ ├── TaskResource.cs │ ├── SearchResult.cs │ ├── PaginatedSearchResult.cs │ ├── Settings.cs │ ├── Meilisearch.csproj │ ├── TaskInfo.cs │ ├── SearchQueryBase.cs │ └── TaskEndpoint.cs ├── .yamllint.yml ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── workflows │ ├── release-drafter.yml │ ├── publish.yml │ ├── pre-release-tests.yml │ └── tests.yml ├── dependabot.yml ├── scripts │ └── check-release.sh └── release-draft-template.yml ├── .cursor └── rules │ └── general.mdc ├── nginx.conf ├── tests └── Meilisearch.Tests │ ├── Properties │ └── launchSettings.json │ ├── Utils.cs │ ├── Models │ └── VectorMovie.cs │ ├── Product.cs │ ├── Datasets │ ├── movies_with_int_id.json │ ├── movies_with_string_id.json │ ├── movies_for_vector.json │ ├── movies_for_faceting.json │ ├── movies_with_info.json │ ├── products_for_distinct_search.json │ └── songs_custom_delimiter.csv │ ├── StringExtensionsTests.cs │ ├── IndexSwapTest.cs │ ├── VersionTests.cs │ ├── Meilisearch.Tests.csproj │ ├── Movie.cs │ ├── Datasets.cs │ ├── ServerConfigs │ ├── BaseUriServer.cs │ └── ProxiedUriServer.cs │ ├── TaskInfoTests.cs │ └── MultiIndexSearchTests.cs ├── docker-compose.yml ├── LICENSE └── Meilisearch.sln /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meilisearch/meilisearch-dotnet/HEAD/assets/logo.png -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | MEILISEARCH_VERSION=v1.16 2 | PROXIED_MEILISEARCH=http://nginx/api/ 3 | MEILISEARCH_URL=http://meilisearch:7700 4 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7.3 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ 6 | .idea 7 | /.idea/ 8 | .vscode 9 | /.vs 10 | *.user 11 | -------------------------------------------------------------------------------- /src/Meilisearch/runtimeconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimeOptions": { 3 | "configProperties": { 4 | "System.Globalization.Invariant": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | extends: default 2 | ignore: | 3 | node_modules 4 | rules: 5 | comments-indentation: disable 6 | line-length: disable 7 | document-start: disable 8 | brackets: disable 9 | truthy: disable 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Support questions & other 4 | url: https://discord.meilisearch.com/ 5 | about: Support is not handled here but on our Discord 6 | -------------------------------------------------------------------------------- /.cursor/rules/general.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | alwaysApply: true 3 | --- 4 | 5 | - Use Docker for local development 6 | - Run tests with `docker-compose run --rm package bash -c "dotnet test && dotnet format --verbosity normal --verify-no-changes"` 7 | -------------------------------------------------------------------------------- /src/Meilisearch/QueryParameters/DeleteDocumentsQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Meilisearch.QueryParameters 4 | { 5 | public class DeleteDocumentsQuery 6 | { 7 | [JsonPropertyName("filter")] 8 | public object Filter { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Meilisearch/ContentType.cs: -------------------------------------------------------------------------------- 1 | namespace Meilisearch 2 | { 3 | internal static class ContentType 4 | { 5 | internal const string Json = "application/json"; 6 | internal const string Ndjson = "application/x-ndjson"; 7 | internal const string Csv = "text/csv"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | events { 2 | } 3 | 4 | http { 5 | server { 6 | server_name _; 7 | # server_name your.server.url; 8 | error_log /etc/nginx/error.log debug; 9 | 10 | location /api/ { 11 | proxy_pass http://meilisearchproxy:7700/; 12 | } 13 | 14 | client_max_body_size 100M; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Meilisearch.Tests": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "https://localhost:52605;http://localhost:52606" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: release-drafter/release-drafter@v6 13 | with: 14 | config-name: release-draft-template.yml 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.RELEASE_DRAFTER_TOKEN }} 17 | -------------------------------------------------------------------------------- /src/Meilisearch/Pagination.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Pagination configuration. 7 | /// 8 | public class Pagination 9 | { 10 | /// 11 | /// Max total hits in each page 12 | /// 13 | [JsonPropertyName("maxTotalHits")] 14 | public int MaxTotalHits { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | labels: 8 | - 'dependencies' 9 | rebase-strategy: disabled 10 | 11 | - package-ecosystem: nuget 12 | directory: "/" 13 | schedule: 14 | interval: "monthly" 15 | time: "04:00" 16 | open-pull-requests-limit: 10 17 | labels: 18 | - dependencies 19 | rebase-strategy: disabled 20 | -------------------------------------------------------------------------------- /src/Meilisearch/Client/MeiliSearchHealth.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Deserialized response of the Meilisearch health. 7 | /// 8 | public class MeiliSearchHealth 9 | { 10 | /// 11 | /// Gets or sets health of Meilisearch server. 12 | /// 13 | [JsonPropertyName("status")] 14 | public string Status { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Meilisearch/MultiSearchQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Search query used in multi-index search 8 | /// 9 | public class MultiSearchQuery 10 | { 11 | /// 12 | /// The queries 13 | /// 14 | [JsonPropertyName("queries")] 15 | public List Queries { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Utils.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.Json; 3 | using System.Threading.Tasks; 4 | 5 | namespace Meilisearch.Tests 6 | { 7 | public static class JsonFileReader 8 | { 9 | public static async Task ReadAsync(string filePath) 10 | { 11 | using (var stream = File.OpenRead(filePath)) 12 | { 13 | return await JsonSerializer.DeserializeAsync(stream); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/scripts/check-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Checking if current tag matches the package version 4 | current_tag=$(echo $GITHUB_REF | cut -d '/' -f 3 | sed -r 's/^v//') 5 | file_tag=$(grep '' src/Meilisearch/Meilisearch.csproj | cut -d '>' -f 2 | cut -d '<' -f 1 | tr -d ' ') 6 | if [ "$current_tag" != "$file_tag" ]; then 7 | echo "Error: the current tag does not match the version in package file(s)." 8 | echo "$current_tag vs $file_tag" 9 | exit 1 10 | fi 11 | 12 | echo 'OK' 13 | exit 0 14 | -------------------------------------------------------------------------------- /src/Meilisearch/FederatedSearchQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Search query for federated multi-index search 7 | /// 8 | public class FederatedSearchQuery : SearchQueryBase 9 | { 10 | /// 11 | /// Federated search options 12 | /// 13 | [JsonPropertyName("federationOptions")] 14 | public MultiSearchFederationOptions FederationOptions { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Meilisearch/MultiSearchResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace Meilisearch 6 | { 7 | /// 8 | /// Search result used in multi-index search 9 | /// 10 | public class MultiSearchResult 11 | { 12 | /// 13 | /// The search results 14 | /// 15 | [JsonPropertyName("results")] 16 | public List> Results { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Meilisearch/QueryParameters/KeysQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Meilisearch.QueryParameters 2 | { 3 | /// 4 | /// A class that handles the creation of a query string for Keys. 5 | /// 6 | public class KeysQuery 7 | { 8 | /// 9 | /// Gets or sets the limit. 10 | /// 11 | public int? Limit { get; set; } 12 | 13 | /// 14 | /// Gets or sets the offset. 15 | /// 16 | public int? Offset { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request & Enhancement 💡 3 | about: Suggest a new idea for the project. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | 10 | 11 | **Description** 12 | Brief explanation of the feature. 13 | 14 | **Basic example** 15 | If the proposal involves something new or a change, include a basic example. How would you use the feature? In which context? 16 | 17 | **Other** 18 | Any other things you want to add. 19 | -------------------------------------------------------------------------------- /src/Meilisearch/QueryParameters/IndexesQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Meilisearch.QueryParameters 2 | { 3 | /// 4 | /// A class that handles the creation of a query string for Indexes. 5 | /// 6 | public class IndexesQuery 7 | { 8 | /// 9 | /// Gets or sets the limit. 10 | /// 11 | public int? Limit { get; set; } 12 | 13 | /// 14 | /// Gets or sets the offset. 15 | /// 16 | public int? Offset { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Meilisearch/HybridSearch.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Meilisearch 4 | { 5 | public class HybridSearch 6 | { 7 | /// 8 | /// Gets or sets the embedder. 9 | /// 10 | [JsonPropertyName("embedder")] 11 | public string Embedder { get; set; } 12 | 13 | /// 14 | /// Gets or sets the semantic ratio. 15 | /// 16 | [JsonPropertyName("semanticRatio")] 17 | public double SemanticRatio { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Models/VectorMovie.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch.Tests.Models 5 | { 6 | public class VectorMovie 7 | { 8 | [JsonPropertyName("id")] 9 | public string Id { get; set; } 10 | 11 | [JsonPropertyName("title")] 12 | public string Title { get; set; } 13 | 14 | [JsonPropertyName("release_year")] 15 | public int ReleaseYear { get; set; } 16 | 17 | [JsonPropertyName("_vectors")] 18 | public Dictionary Vectors { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Product.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Meilisearch.Tests 4 | { 5 | public class Product 6 | { 7 | [JsonPropertyName("id")] 8 | public int Id { get; set; } 9 | 10 | [JsonPropertyName("description")] 11 | public string Description { get; set; } 12 | 13 | [JsonPropertyName("brand")] 14 | public string Brand { get; set; } 15 | 16 | [JsonPropertyName("product_id")] 17 | public string ProductId { get; set; } 18 | 19 | [JsonPropertyName("color")] 20 | public string Color { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Meilisearch/Errors/MeilisearchTenantTokenExpired.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Represents an exception thrown when the provided expiration date is invalid or in the past. 7 | /// 8 | public class MeilisearchTenantTokenExpired : Exception 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public MeilisearchTenantTokenExpired() 14 | : base("Provide a valid UTC DateTime in the future.") 15 | { 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Meilisearch/FacetStat.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Wrapper for Facet Stats. 8 | /// 9 | public class FacetStat 10 | { 11 | /// 12 | /// Minimum value returned by FacetDistribution per facet 13 | /// 14 | [JsonPropertyName("min")] 15 | public float Min { get; set; } 16 | 17 | /// 18 | /// Maximum value returned by FacetDistribution per facet 19 | /// 20 | [JsonPropertyName("max")] 21 | public float Max { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Meilisearch/Errors/MeilisearchTimeoutError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Error sent when request not processed in expected time. 7 | /// 8 | public class MeilisearchTimeoutError : Exception 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// Handler Exception for MeilisearchTimeoutError with message. 13 | /// 14 | /// Custom error message. 15 | public MeilisearchTimeoutError(string message) 16 | : base(message) 17 | { 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Meilisearch/IndexSwap.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Model for index swaps requests. 8 | /// 9 | public class IndexSwap 10 | { 11 | [JsonPropertyName("indexes")] 12 | public List Indexes { get; private set; } 13 | 14 | [JsonPropertyName("rename")] 15 | public bool Rename { get; set; } = false; 16 | 17 | public IndexSwap(string indexA, string indexB, bool rename = false) 18 | { 19 | this.Indexes = new List { indexA, indexB }; 20 | this.Rename = rename; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 🐞 3 | about: Create a report to help us improve. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | 10 | 11 | **Description** 12 | Description of what the bug is about. 13 | 14 | **Expected behavior** 15 | What you expected to happen. 16 | 17 | **Current behavior** 18 | What happened. 19 | 20 | **Screenshots or Logs** 21 | If applicable, add screenshots or logs to help explain your problem. 22 | 23 | **Environment (please complete the following information):** 24 | - OS: [e.g. Debian GNU/Linux] 25 | - Meilisearch version: [e.g. v.0.20.0] 26 | - meilisearch-dotnet version: [e.g v0.6.0] 27 | -------------------------------------------------------------------------------- /src/Meilisearch/Errors/MeilisearchTenantTokenApiKeyUidInvalid.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Represents an exception thrown when `apiKey` is not present 7 | /// to sign correctly the Tenant Token generation. 8 | /// 9 | public class MeilisearchTenantTokenApiKeyUidInvalid : Exception 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public MeilisearchTenantTokenApiKeyUidInvalid() 15 | : base("Cannot generate a signed token without a valid apiKeyUid. Provide one in the method params.") 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Meilisearch/MultiSearchFederationOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | using Meilisearch.Converters; 5 | 6 | namespace Meilisearch 7 | { 8 | /// 9 | /// Federation options in federated multi-index search 10 | /// 11 | public class MultiSearchFederationOptions 12 | { 13 | /// 14 | /// Number of documents to skip 15 | /// 16 | [JsonPropertyName("offset")] 17 | public int Offset { get; set; } 18 | 19 | /// 20 | /// Maximum number of documents returned 21 | /// 22 | [JsonPropertyName("limit")] 23 | public int Limit { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Datasets/movies_with_int_id.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Id": 10, 4 | "Name": "Gladiator", 5 | "Genre": null 6 | }, 7 | { 8 | "Id": 11, 9 | "Name": "Interstellar", 10 | "Genre": null 11 | }, 12 | { 13 | "Id": 12, 14 | "Name": "Star Wars", 15 | "Genre": "SF" 16 | }, 17 | { 18 | "Id": 13, 19 | "Name": "Harry Potter", 20 | "Genre": "SF" 21 | }, 22 | { 23 | "Id": 14, 24 | "Name": "Iron Man", 25 | "Genre": "Action" 26 | }, 27 | { 28 | "Id": 15, 29 | "Name": "Spider-Man", 30 | "Genre": "Action" 31 | }, 32 | { 33 | "Id": 16, 34 | "Name": "Am\u00E9lie Poulain", 35 | "Genre": "French movie" 36 | } 37 | ] -------------------------------------------------------------------------------- /src/Meilisearch/Errors/MeilisearchTenantTokenApiKeyInvalid.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Represents an exception thrown when `apiKey` is not present 7 | /// to sign correctly the Tenant Token generation. 8 | /// 9 | public class MeilisearchTenantTokenApiKeyInvalid : Exception 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public MeilisearchTenantTokenApiKeyInvalid() 15 | : base("Cannot generate a signed token without a valid apiKey. Provide one in the MeilisearchClient instance or in the method params. The key MUST be at least 16 characters, or 128 bits") 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Datasets/movies_with_string_id.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Id": "10", 4 | "Name": "Gladiator", 5 | "Genre": null 6 | }, 7 | { 8 | "Id": "11", 9 | "Name": "Interstellar", 10 | "Genre": null 11 | }, 12 | { 13 | "Id": "12", 14 | "Name": "Star Wars", 15 | "Genre": "SF" 16 | }, 17 | { 18 | "Id": "13", 19 | "Name": "Harry Potter", 20 | "Genre": "SF" 21 | }, 22 | { 23 | "Id": "14", 24 | "Name": "Iron Man", 25 | "Genre": "Action" 26 | }, 27 | { 28 | "Id": "15", 29 | "Name": "Spider-Man", 30 | "Genre": "Action" 31 | }, 32 | { 33 | "Id": "16", 34 | "Name": "Am\u00E9lie Poulain", 35 | "Genre": "French movie" 36 | } 37 | ] -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Nuget 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | publish: 10 | name: Build, pack & publish 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v5 14 | - name: Install dependencies 15 | run: | 16 | sudo apt-get update 17 | - name: Check release validity 18 | run: sh .github/scripts/check-release.sh 19 | - name: Setup dotnet 20 | uses: actions/setup-dotnet@v5 21 | with: 22 | dotnet-version: 8.x 23 | - name: Pack 24 | run: dotnet pack src/Meilisearch/Meilisearch.csproj -c Release -o src/Meilisearch/bin/Release 25 | - name: Publish 26 | run: dotnet nuget push src/Meilisearch/bin/Release/*.nupkg -k ${{secrets.NUGET_API_KEY}} -s https://api.nuget.org/v3/index.json 27 | -------------------------------------------------------------------------------- /src/Meilisearch/Errors/MeilisearchCommunicationError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Error sent when trying to connecting to Meilisearch. 7 | /// 8 | public class MeilisearchCommunicationError : Exception 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// Handler Exception for MeilisearchCommunicationError with message and inner exception. 13 | /// 14 | /// Custom error message. 15 | /// Inner exception. 16 | public MeilisearchCommunicationError(string message, Exception innerException) 17 | : base(message, innerException) 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Meilisearch/EmbedderSource.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | using Meilisearch.Converters; 4 | 5 | namespace Meilisearch 6 | { 7 | /// 8 | /// Embedder source. 9 | /// 10 | [JsonConverter(typeof(EmbedderSourceConverter))] 11 | public enum EmbedderSource 12 | { 13 | /// 14 | /// OpenAI 15 | /// 16 | OpenAi, 17 | 18 | /// 19 | /// Hugging Face 20 | /// 21 | HuggingFace, 22 | 23 | /// 24 | /// Ollama 25 | /// 26 | Ollama, 27 | 28 | /// 29 | /// REST 30 | /// 31 | Rest, 32 | 33 | /// 34 | /// User-provided 35 | /// 36 | UserProvided, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Datasets/movies_for_vector.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Shazam!", 4 | "release_year": 2019, 5 | "id": "287947", 6 | "_vectors": { "manual": [0.8, 0.4, -0.5]} 7 | }, 8 | { 9 | "title": "Captain Marvel", 10 | "release_year": 2019, 11 | "id": "299537", 12 | "_vectors": { "manual": [0.6, 0.8, -0.2] } 13 | }, 14 | { 15 | "title": "Escape Room", 16 | "release_year": 2019, 17 | "id": "522681", 18 | "_vectors": { "manual": [0.1, 0.6, 0.8] } 19 | }, 20 | { 21 | "title": "How to Train Your Dragon: The Hidden World", 22 | "release_year": 2019, 23 | "id": "166428", 24 | "_vectors": { "manual": [0.7, 0.7, -0.4] } 25 | }, 26 | { 27 | "title": "All Quiet on the Western Front", 28 | "release_year": 1930, 29 | "id": "143", 30 | "_vectors": { "manual": [-0.5, 0.3, 0.85] } 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /.github/release-draft-template.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | exclude-labels: 4 | - 'skip-changelog' 5 | version-resolver: 6 | minor: 7 | labels: 8 | - 'breaking-change' 9 | default: patch 10 | categories: 11 | - title: '⚠️ Breaking changes' 12 | label: 'breaking-change' 13 | - title: '🚀 Enhancements' 14 | label: 'enhancement' 15 | - title: '🐛 Bug Fixes' 16 | label: 'bug' 17 | - title: '🔒 Security' 18 | label: 'security' 19 | - title: '⚙️ Maintenance/misc' 20 | label: 21 | - 'dependencies' 22 | - 'maintenance' 23 | - 'documentation' 24 | template: | 25 | $CHANGES 26 | 27 | Thanks again to $CONTRIBUTORS! 🎉 28 | no-changes-template: 'Changes are coming soon 😎' 29 | sort-direction: 'ascending' 30 | replacers: 31 | - search: '/(?:and )?@meili-bot,?/g' 32 | replace: '' 33 | -------------------------------------------------------------------------------- /src/Meilisearch/Version.cs: -------------------------------------------------------------------------------- 1 | namespace Meilisearch 2 | { 3 | /// 4 | /// Information regarding an API key for the Meilisearch server. 5 | /// 6 | public class Version 7 | { 8 | /// 9 | /// Extracts version from Meilisearch.csproj. 10 | /// 11 | /// Returns a formatted version. 12 | public string GetQualifiedVersion() 13 | { 14 | return $"Meilisearch .NET (v{GetVersion()})"; 15 | } 16 | 17 | /// 18 | /// Extracts the "major.minor.build" version from Meilisearch.csproj. 19 | /// 20 | /// Returns a version from the GetType as String. 21 | public string GetVersion() 22 | { 23 | return GetType().Assembly.GetName().Version.ToString(3); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Meilisearch/Client/MeiliSearchVersion.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Deserialized response of the Meilisearch version. 7 | /// 8 | public class MeiliSearchVersion 9 | { 10 | /// 11 | /// Gets or sets commit SHA for Meilisearch. 12 | /// 13 | [JsonPropertyName("commitSha")] 14 | public string CommitSha { get; set; } 15 | 16 | /// 17 | /// Gets or sets build date of the current version. 18 | /// 19 | [JsonPropertyName("commitDate")] 20 | public string CommitDate { get; set; } 21 | 22 | /// 23 | /// Gets or sets information about Meilisearch version. 24 | /// 25 | [JsonPropertyName("pkgVersion")] 26 | public string Version { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Meilisearch/Result.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Generic result class. 7 | /// When returning a list, Meilisearch stores the data in the "results" field, to allow better pagination. 8 | /// 9 | /// Type of the Meilisearch server object. Ex: keys, tasks, ... 10 | public class Result 11 | { 12 | public Result(T results, int? limit) 13 | { 14 | Results = results; 15 | Limit = limit; 16 | } 17 | 18 | /// 19 | /// Gets the "results" field. 20 | /// 21 | [JsonPropertyName("results")] 22 | public T Results { get; } 23 | 24 | /// 25 | /// Gets limit size. 26 | /// 27 | [JsonPropertyName("limit")] 28 | public int? Limit { get; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Meilisearch/MatchPosition.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Meilisearch 4 | { 5 | public class MatchPosition 6 | { 7 | public MatchPosition(int start, int length) 8 | { 9 | Start = start; 10 | Length = length; 11 | } 12 | 13 | /// 14 | /// The beginning of a matching term within a field. 15 | /// WARNING: This value is in bytes and not the number of characters. For example, ü represents two bytes but one character. 16 | /// 17 | [JsonPropertyName("start")] 18 | public int Start { get; } 19 | 20 | /// 21 | /// The length of a matching term within a field. 22 | /// WARNING: This value is in bytes and not the number of characters. For example, ü represents two bytes but one character. 23 | /// 24 | [JsonPropertyName("length")] 25 | public int Length { get; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Meilisearch/ResourceResults.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Generic result class for resources. 7 | /// When returning a list, Meilisearch stores the data in the "results" field, to allow better pagination. 8 | /// 9 | /// Type of the Meilisearch server object. Ex: keys, indexes, ... 10 | public class ResourceResults : Result 11 | { 12 | public ResourceResults(T results, int? limit, int offset, int total) 13 | : base(results, limit) 14 | { 15 | Offset = offset; 16 | Total = total; 17 | } 18 | 19 | /// 20 | /// Gets offset size. 21 | /// 22 | [JsonPropertyName("offset")] 23 | public int Offset { get; } 24 | 25 | /// 26 | /// Gets total size. 27 | /// 28 | [JsonPropertyName("total")] 29 | public int Total { get; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/StringExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Meilisearch.Extensions; 4 | 5 | using Xunit; 6 | 7 | namespace Meilisearch.Tests 8 | { 9 | public class StringExtensionsTests 10 | { 11 | [Theory] 12 | [InlineData("http://localhost:7700", "http://localhost:7700/")] 13 | [InlineData("http://localhost:7700/", "http://localhost:7700/")] 14 | [InlineData("http://localhost:7700/api", "http://localhost:7700/api/")] 15 | [InlineData("http://localhost:7700/api/", "http://localhost:7700/api/")] 16 | public void CheckUrisEndWithSlash(string actual, string expected) 17 | { 18 | Assert.Equal(expected, actual.ToSafeUri().AbsoluteUri); 19 | } 20 | 21 | [Theory] 22 | [InlineData("")] 23 | [InlineData(" ")] 24 | [InlineData(null)] 25 | public void ToSafeUriShouldThrowsArgumentException(string badUri) 26 | { 27 | Assert.Throws(badUri.ToSafeUri); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Datasets/movies_for_faceting.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Id": "10", 4 | "Name": "Gladiator", 5 | "Genre": null 6 | }, 7 | { 8 | "Id": "11", 9 | "Name": "Interstellar", 10 | "Genre": null 11 | }, 12 | { 13 | "Id": "12", 14 | "Name": "Star Wars", 15 | "Genre": "SF" 16 | }, 17 | { 18 | "Id": "13", 19 | "Name": "Harry Potter", 20 | "Genre": "SF" 21 | }, 22 | { 23 | "Id": "14", 24 | "Name": "Iron Man", 25 | "Genre": "Action" 26 | }, 27 | { 28 | "Id": "15", 29 | "Name": "Spider-Man", 30 | "Genre": "Action" 31 | }, 32 | { 33 | "Id": "16", 34 | "Name": "Am\u00E9lie Poulain", 35 | "Genre": "French movie" 36 | }, 37 | { 38 | "Id": "17", 39 | "Name": "Mission Impossible", 40 | "Genre": "Action" 41 | }, 42 | { 43 | "Id": "1344", 44 | "Name": "The Hobbit", 45 | "Genre": "sci fi" 46 | } 47 | ] -------------------------------------------------------------------------------- /src/Meilisearch/TasksResults.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Generic result class for resources. 7 | /// When returning a list, Meilisearch stores the data in the "results" field, to allow better pagination. 8 | /// 9 | /// Type of the Meilisearch server object. Ex: keys, indexes, ... 10 | public class TasksResults : Result 11 | { 12 | public TasksResults(T results, int? limit, int? from, int? next, int? total) 13 | : base(results, limit) 14 | { 15 | From = from; 16 | Next = next; 17 | Total = total; 18 | } 19 | 20 | /// 21 | /// Gets from size. 22 | /// 23 | public int? From { get; } 24 | 25 | /// 26 | /// Gets next size. 27 | /// 28 | public int? Next { get; } 29 | 30 | /// 31 | /// Gets total number of tasks. 32 | /// 33 | public int? Total { get; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Meilisearch/Faceting.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | using Meilisearch.Converters; 5 | 6 | namespace Meilisearch 7 | { 8 | /// 9 | /// Faceting configuration. 10 | /// 11 | public class Faceting 12 | { 13 | /// 14 | /// Gets or sets maxValuesPerFacet. 15 | /// 16 | [JsonPropertyName("maxValuesPerFacet")] 17 | public int MaxValuesPerFacet { get; set; } 18 | 19 | /// 20 | /// Gets or sets sortFacetValuesBy. 21 | /// 22 | [JsonPropertyName("sortFacetValuesBy")] 23 | public Dictionary SortFacetValuesBy { get; set; } 24 | } 25 | 26 | [JsonConverter(typeof(SortFacetValuesConverter))] 27 | public enum SortFacetValuesByType 28 | { 29 | /// 30 | /// Sort by alphanumerical value. 31 | /// 32 | Alpha, 33 | 34 | /// 35 | /// Sort by count value. 36 | /// 37 | Count 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | volumes: 4 | nuget: 5 | 6 | services: 7 | package: 8 | image: mcr.microsoft.com/dotnet/sdk:8.0 9 | tty: true 10 | stdin_open: true 11 | working_dir: /home/package 12 | env_file: .env 13 | depends_on: 14 | - meilisearch 15 | - nginx 16 | links: 17 | - meilisearch 18 | - nginx 19 | volumes: 20 | - ./:/home/package 21 | - nuget:/root/.nuget/packages/ 22 | 23 | meilisearch: 24 | image: getmeili/meilisearch-enterprise:${MEILISEARCH_VERSION} 25 | ports: 26 | - "7700:7700" 27 | environment: 28 | - MEILI_MASTER_KEY=masterKey 29 | - MEILI_NO_ANALYTICS=true 30 | 31 | meilisearchproxy: 32 | image: getmeili/meilisearch-enterprise:${MEILISEARCH_VERSION} 33 | ports: 34 | - "7700" 35 | environment: 36 | - MEILI_MASTER_KEY=masterKey 37 | - MEILI_NO_ANALYTICS=true 38 | 39 | nginx: 40 | image: nginx:latest 41 | container_name: production_nginx 42 | volumes: 43 | - ./nginx.conf:/etc/nginx/nginx.conf 44 | ports: 45 | - 8080:80 46 | depends_on: 47 | - meilisearchproxy 48 | -------------------------------------------------------------------------------- /src/Meilisearch/Converters/TaskInfoTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace Meilisearch.Converters 8 | { 9 | public class TaskInfoTypeConverter : JsonConverter 10 | { 11 | public override TaskInfoType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 12 | { 13 | if (reader.TokenType == JsonTokenType.String) 14 | { 15 | var enumValue = reader.GetString(); 16 | if (Enum.TryParse(enumValue, true, out var taskInfoType)) 17 | { 18 | return taskInfoType; 19 | } 20 | } 21 | 22 | // If we reach here, it means we encountered an unknown value 23 | return TaskInfoType.Unknown; 24 | } 25 | 26 | public override void Write(Utf8JsonWriter writer, TaskInfoType value, JsonSerializerOptions options) 27 | { 28 | writer.WriteStringValue(value.ToString()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 sa 4 | Copyright (c) 2020-2025 Meili SAS 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/Meilisearch/FederatedMultiSearchQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | using Meilisearch.Converters; 5 | 6 | namespace Meilisearch 7 | { 8 | /// 9 | /// Search query used in federated multi-index search 10 | /// 11 | public class FederatedMultiSearchQuery 12 | { 13 | /// 14 | /// Default constructor that ensures FederationOptions are always set 15 | /// 16 | public FederatedMultiSearchQuery() 17 | { 18 | FederationOptions = new MultiSearchFederationOptions(); 19 | } 20 | 21 | /// 22 | /// The queries 23 | /// 24 | [JsonPropertyName("queries")] 25 | public List Queries { get; set; } 26 | 27 | /// 28 | /// The federated search query options 29 | /// 30 | [JsonInclude] 31 | [JsonPropertyName("federation")] 32 | [JsonConverter(typeof(MultiSearchFederationOptionsConverter))] 33 | public MultiSearchFederationOptions FederationOptions { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Meilisearch/Converters/SortFacetValuesConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace Meilisearch.Converters 6 | { 7 | public class SortFacetValuesConverter : JsonConverter 8 | { 9 | public override SortFacetValuesByType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 10 | { 11 | if (reader.TokenType == JsonTokenType.String) 12 | { 13 | var enumValue = reader.GetString(); 14 | if (Enum.TryParse(enumValue, true, out var sortFacetValues)) 15 | { 16 | return sortFacetValues; 17 | } 18 | } 19 | 20 | // If we reach here, it means we encountered an unknown value, so we'll use meilisearch default of Alpha 21 | return SortFacetValuesByType.Alpha; 22 | } 23 | 24 | public override void Write(Utf8JsonWriter writer, SortFacetValuesByType value, JsonSerializerOptions options) 25 | { 26 | writer.WriteStringValue(value.ToString().ToLower()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Meilisearch/Errors/MeilisearchApiErrorContent.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Error sent by Meilisearch API. 7 | /// 8 | public class MeilisearchApiErrorContent 9 | { 10 | public MeilisearchApiErrorContent(string message, string code, string type, string link) 11 | { 12 | Message = message; 13 | Code = code; 14 | Type = type; 15 | Link = link; 16 | } 17 | 18 | /// 19 | /// Gets the message. 20 | /// 21 | [JsonPropertyName("message")] 22 | public string Message { get; } 23 | 24 | /// 25 | /// Gets the code. 26 | /// 27 | [JsonPropertyName("code")] 28 | public string Code { get; } 29 | 30 | /// 31 | /// Gets the type. 32 | /// 33 | [JsonPropertyName("type")] 34 | public string Type { get; } 35 | 36 | /// 37 | /// Gets the link. 38 | /// 39 | [JsonPropertyName("link")] 40 | public string Link { get; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/pre-release-tests.yml: -------------------------------------------------------------------------------- 1 | # Testing the code base against the Meilisearch pre-releases 2 | name: Pre-Release Tests 3 | 4 | # Will only run for PRs and pushes to bump-meilisearch-v* 5 | on: 6 | push: 7 | branches: bump-meilisearch-v* 8 | pull_request: 9 | branches: bump-meilisearch-v* 10 | 11 | jobs: 12 | integration_tests: 13 | name: integration-tests-against-rc 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v5 17 | - name: Setup .NET Core 18 | uses: actions/setup-dotnet@v5 19 | with: 20 | dotnet-version: 8.x 21 | - name: Get the latest Meilisearch RC 22 | run: echo "MEILISEARCH_VERSION=$(curl https://raw.githubusercontent.com/meilisearch/integration-guides/main/scripts/get-latest-meilisearch-rc.sh | bash)" >> $GITHUB_ENV 23 | - name: Meilisearch (${{ env.MEILISEARCH_VERSION }}) setup with Docker 24 | run: MEILISEARCH_VERSION=${{ env.MEILISEARCH_VERSION }} docker compose up -d 25 | - name: Install dependencies 26 | run: dotnet restore 27 | - name: Build 28 | run: dotnet build --configuration Release --no-restore 29 | - name: Run tests 30 | run: dotnet test --no-restore --verbosity normal 31 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/IndexSwapTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json; 3 | 4 | using Xunit; 5 | 6 | namespace Meilisearch.Tests 7 | { 8 | public class IndexSwapTests 9 | { 10 | [Fact] 11 | public void PreventMoreThanTwoIndexesPerObject() 12 | { 13 | var swap = new IndexSwap("indexA", "indexB"); 14 | 15 | Assert.Equal(new List { "indexA", "indexB" }, swap.Indexes); 16 | } 17 | 18 | [Fact] 19 | public void CreateExpectedJSONFormat() 20 | { 21 | var swap = new IndexSwap("indexA", "indexB"); 22 | 23 | var json = JsonSerializer.Serialize(swap); 24 | Assert.Contains("\"indexes\":[\"indexA\",\"indexB\"]", json); 25 | Assert.Contains("\"rename\":false", json); 26 | } 27 | 28 | [Fact] 29 | public void CreateExpectedJSONFormatWithRenameTrue() 30 | { 31 | var swap = new IndexSwap("indexA", "indexB", rename: true); 32 | 33 | var json = JsonSerializer.Serialize(swap); 34 | Assert.Contains("\"indexes\":[\"indexA\",\"indexB\"]", json); 35 | Assert.Contains("\"rename\":true", json); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Meilisearch/QueryParameters/DocumentsQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch.QueryParameters 5 | { 6 | /// 7 | /// A class that handles the creation of a query string for Documents. 8 | /// 9 | public class DocumentsQuery 10 | { 11 | /// 12 | /// Gets or sets the limit. 13 | /// 14 | [JsonPropertyName("limit")] 15 | public int? Limit { get; set; } 16 | 17 | /// 18 | /// Gets or sets the offset. 19 | /// 20 | [JsonPropertyName("offset")] 21 | public int? Offset { get; set; } 22 | 23 | /// 24 | /// Gets or sets the attributes to retrieve. 25 | /// 26 | [JsonPropertyName("fields")] 27 | public List Fields { get; set; } 28 | 29 | /// 30 | /// An optional filter to apply 31 | /// 32 | [JsonPropertyName("filter")] 33 | public object Filter { get; set; } 34 | 35 | /// 36 | /// An optional sort to apply 37 | /// 38 | [JsonPropertyName("sort")] 39 | public List Sort { get; set; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Meilisearch.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meilisearch", "src\Meilisearch\Meilisearch.csproj", "{1876F5BB-672A-4440-88ED-D5E14DC507AC}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meilisearch.Tests", "tests\Meilisearch.Tests\Meilisearch.Tests.csproj", "{CDAACB2F-0197-4266-B40C-D51F61136ABF}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {1876F5BB-672A-4440-88ED-D5E14DC507AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {1876F5BB-672A-4440-88ED-D5E14DC507AC}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {1876F5BB-672A-4440-88ED-D5E14DC507AC}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {1876F5BB-672A-4440-88ED-D5E14DC507AC}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {CDAACB2F-0197-4266-B40C-D51F61136ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {CDAACB2F-0197-4266-B40C-D51F61136ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {CDAACB2F-0197-4266-B40C-D51F61136ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {CDAACB2F-0197-4266-B40C-D51F61136ABF}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.SimilarDocuments.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Json; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Meilisearch 6 | { 7 | public partial class Index 8 | { 9 | /// 10 | /// Search for similar documents. 11 | /// 12 | /// The query to search for similar documents. 13 | /// The cancellation token for this call. 14 | /// The type of the documents to return. 15 | /// Returns the similar documents. 16 | public async Task> SearchSimilarDocumentsAsync( 17 | SimilarDocumentsQuery query, 18 | CancellationToken cancellationToken = default) 19 | { 20 | var responseMessage = await _http 21 | .PostAsJsonAsync( 22 | $"indexes/{Uid}/similar", 23 | query, 24 | Constants.JsonSerializerOptionsRemoveNulls, 25 | cancellationToken: cancellationToken) 26 | .ConfigureAwait(false); 27 | 28 | return await responseMessage.Content 29 | .ReadFromJsonAsync>(cancellationToken: cancellationToken) 30 | .ConfigureAwait(false); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Meilisearch/Stats.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace Meilisearch 6 | { 7 | /// 8 | /// Wrapper for index stats. 9 | /// 10 | public class Stats 11 | { 12 | public Stats(long databaseSize, DateTime? lastUpdate, IReadOnlyDictionary indexes, long usedDatabaseSize) 13 | { 14 | DatabaseSize = databaseSize; 15 | LastUpdate = lastUpdate; 16 | Indexes = indexes; 17 | UsedDatabaseSize = usedDatabaseSize; 18 | } 19 | 20 | /// 21 | /// Gets database size. 22 | /// 23 | [JsonPropertyName("databaseSize")] 24 | public long DatabaseSize { get; } 25 | 26 | /// 27 | /// Gets the total space used by the data stored in Meilisearch. 28 | /// 29 | [JsonPropertyName("usedDatabaseSize")] 30 | public long UsedDatabaseSize { get; } 31 | 32 | /// 33 | /// Gets last update timestamp. 34 | /// 35 | [JsonPropertyName("lastUpdate")] 36 | public DateTime? LastUpdate { get; } 37 | 38 | /// 39 | /// Gets index stats. 40 | /// 41 | [JsonPropertyName("indexes")] 42 | public IReadOnlyDictionary Indexes { get; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Datasets/movies_with_info.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Id": "10", 4 | "Name": "Gladiator", 5 | "Info": { 6 | "Comment": "a movie about old times", 7 | "ReviewNb": 700 8 | } 9 | }, 10 | { 11 | "Id": "11", 12 | "Name": "Interstellar", 13 | "Info": { 14 | "Comment": "the best movie", 15 | "ReviewNb": 1000 16 | } 17 | }, 18 | { 19 | "Id": "12", 20 | "Name": "Star Wars", 21 | "Info": { 22 | "Comment": "a lot of wars in the stars", 23 | "ReviewNb": 900 24 | } 25 | }, 26 | { 27 | "Id": "13", 28 | "Name": "Harry Potter", 29 | "Info": { 30 | "Comment": "a movie about a wizard boy", 31 | "ReviewNb": 900 32 | } 33 | }, 34 | { 35 | "Id": "14", 36 | "Name": "Iron Man", 37 | "Info": { 38 | "Comment": "a movie about a rich man", 39 | "ReviewNb": 800 40 | } 41 | }, 42 | { 43 | "Id": "15", 44 | "Name": "Spider-Man", 45 | "Info": { 46 | "Comment": "the spider bit the boy", 47 | "ReviewNb": 900 48 | } 49 | }, 50 | { 51 | "Id": "16", 52 | "Name": "Am\u00E9lie Poulain", 53 | "Info": { 54 | "Comment": "talks about hapiness", 55 | "ReviewNb": 800 56 | } 57 | } 58 | ] -------------------------------------------------------------------------------- /src/Meilisearch/FacetSearchResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Wrapper for FacetSearchResponse 8 | /// 9 | public class FacetSearchResult 10 | { 11 | /// 12 | /// Gets or sets the facetHits property 13 | /// 14 | [JsonPropertyName("facetHits")] 15 | public IEnumerable FacetHits { get; set; } 16 | 17 | /// 18 | /// Gets or sets the facet query 19 | /// 20 | [JsonPropertyName("facetQuery")] 21 | public string FacetQuery { get; set; } 22 | 23 | /// 24 | /// Gets or sets the processingTimeMs property 25 | /// 26 | [JsonPropertyName("processingTimeMs")] 27 | public int ProcessingTimeMs { get; set; } 28 | 29 | /// 30 | /// Wrapper for Facet Hit 31 | /// 32 | public class FacetHit 33 | { 34 | /// 35 | /// Gets or sets the value property 36 | /// 37 | [JsonPropertyName("value")] 38 | public string Value { get; set; } 39 | 40 | /// 41 | /// Gets or sets the count property 42 | /// 43 | [JsonPropertyName("count")] 44 | public int Count { get; set; } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Meilisearch/Constants.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | using Meilisearch.Converters; 5 | 6 | namespace Meilisearch 7 | { 8 | /// 9 | /// This class adds some defaults to work with Meilisearch client. 10 | /// 11 | internal static class Constants 12 | { 13 | /// 14 | /// JsonSerializer options used when serializing objects that needs to remove null values. 15 | /// 16 | internal static readonly JsonSerializerOptions JsonSerializerOptionsRemoveNulls = new JsonSerializerOptions 17 | { 18 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 19 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 20 | }; 21 | 22 | /// 23 | /// JsonSerializer options used when serializing objects that keeps null values. 24 | /// 25 | internal static readonly JsonSerializerOptions JsonSerializerOptionsWriteNulls = new JsonSerializerOptions 26 | { 27 | DefaultIgnoreCondition = JsonIgnoreCondition.Never, 28 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 29 | }; 30 | 31 | internal static string VersionErrorHintMessage(string message, string method) 32 | { 33 | return 34 | $"{message}\nHint: It might not be working because maybe you're not up to date with the Meilisearch version that ${method} call requires."; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Meilisearch/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Meilisearch.Extensions 6 | { 7 | /// 8 | /// Extensions methods for IEnumerable. 9 | /// 10 | internal static class EnumerableExtensions 11 | { 12 | /// 13 | /// Returns chunks of a list. 14 | /// 15 | /// The list to split. 16 | /// Size of the chunks. 17 | /// Type of objects in the list. 18 | /// List of chunks. 19 | /// Thrown if fullList is null. 20 | /// Throw if chunkSize is lower than 1. 21 | internal static IEnumerable> GetChunks(this IEnumerable fullList, int chunkSize) 22 | { 23 | if (fullList is null) 24 | { 25 | throw new ArgumentNullException(nameof(fullList)); 26 | } 27 | 28 | if (chunkSize < 1) 29 | { 30 | throw new ArgumentException("chunkSize value must be greater than 0", nameof(chunkSize)); 31 | } 32 | 33 | var total = fullList.Count(); 34 | var sent = 0; 35 | while (sent < total) 36 | { 37 | yield return fullList.Skip(sent).Take(chunkSize); 38 | sent += chunkSize; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Meilisearch/TenantTokenRules.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Wrapper class used to map all the supported types to be used in 7 | /// the `searchRules` claim in the Tenant Tokens. 8 | /// 9 | public class TenantTokenRules 10 | { 11 | private readonly object _rules; 12 | 13 | /// 14 | /// Initializes a new instance of the class based on a rules json object. 15 | /// 16 | /// 17 | /// 18 | /// example: 19 | /// 20 | /// {'*': {"filter": 'tag = Tale'}} 21 | /// 22 | /// 23 | public TenantTokenRules(IReadOnlyDictionary rules) 24 | { 25 | _rules = rules; 26 | } 27 | 28 | /// 29 | /// Initializes a new instance of the class based on a rules string array. 30 | /// 31 | /// 32 | /// 33 | /// example: 34 | /// 35 | /// ['books'] 36 | /// 37 | /// 38 | public TenantTokenRules(string[] rules) 39 | { 40 | _rules = rules; 41 | } 42 | 43 | /// 44 | /// Accessor method used to retrieve the searchRules claim. 45 | /// 46 | /// A object with the supported type representing the `searchRules`. 47 | public object ToClaim() 48 | { 49 | return _rules; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Meilisearch/Errors/MeilisearchApiError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Error sent by Meilisearch API. 8 | /// 9 | public class MeilisearchApiError : Exception 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// Handler Exception received from Meilisearch API. 14 | /// 15 | /// Specific error message from Meilisearch Api. 16 | public MeilisearchApiError(MeilisearchApiErrorContent apiError) 17 | : base(string.Format("MeilisearchApiError, Message: {0}, Code: {1}, Type: {2}, Link: {3}", apiError.Message, apiError.Code, apiError.Type, apiError.Link)) 18 | { 19 | Code = apiError.Code; 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// Handler Exception when Meilisearch API doesn't send a response message. 25 | /// 26 | /// Status code from http response message. 27 | /// Reason Phrase from http response message. 28 | public MeilisearchApiError(HttpStatusCode statusCode, string reasonPhrase) 29 | : base(string.Format("MeilisearchApiError, Message: {0}, Code: {1}", reasonPhrase, (int)statusCode)) 30 | { 31 | } 32 | 33 | /// 34 | /// Gets or sets the code return by MeilisearchApi. 35 | /// 36 | public string Code { get; set; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Meilisearch/TypoTolerance.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Typo Tolerance configuration. 8 | /// 9 | public class TypoTolerance 10 | { 11 | /// 12 | /// Whether the typo tolerance feature is enabled 13 | /// 14 | [JsonPropertyName("enabled")] 15 | public bool? Enabled { get; set; } 16 | 17 | /// 18 | /// Disable the typo tolerance feature on the specified document attributes. 19 | /// 20 | [JsonPropertyName("disableOnAttributes")] 21 | public IEnumerable DisableOnAttributes { get; set; } 22 | 23 | /// 24 | /// Disable the typo tolerance feature for a given set of terms in a search query. 25 | /// 26 | [JsonPropertyName("disableOnWords")] 27 | public IEnumerable DisableOnWords { get; set; } 28 | 29 | /// 30 | /// Customize the minimum word size to tolerate typos. 31 | /// 32 | [JsonPropertyName("minWordSizeForTypos")] 33 | public TypoSize MinWordSizeForTypos { get; set; } 34 | 35 | public class TypoSize 36 | { 37 | /// 38 | /// Customize the minimum word size to tolerate 1 typo. 39 | /// 40 | [JsonPropertyName("oneTypo")] 41 | public int? OneTypo { get; set; } 42 | 43 | /// 44 | /// Customize the minimum word size to tolerate 2 typos. 45 | /// 46 | [JsonPropertyName("twoTypos")] 47 | public int? TwoTypos { get; set; } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | merge_group: 9 | 10 | jobs: 11 | integration_tests: 12 | # Will not run if the event is a PR to bump-meilisearch-v* (so a pre-release PR) 13 | # Will still run for each push to bump-meilisearch-v* 14 | if: github.event_name != 'pull_request' || !startsWith(github.base_ref, 'bump-meilisearch-v') 15 | name: integration-tests 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v5 19 | - name: Setup .NET Core 20 | uses: actions/setup-dotnet@v5 21 | with: 22 | dotnet-version: "8.0.x" 23 | - name: Install dependencies 24 | run: dotnet restore 25 | - name: Build 26 | run: dotnet build --configuration Release --no-restore 27 | - name: Meilisearch (latest version) setup with Docker 28 | env: 29 | # Any docker tag is actually accepted as a valid version 30 | MEILISEARCH_VERSION: latest 31 | run: docker compose up -d 32 | - name: Run tests 33 | run: dotnet test --no-restore --verbosity normal 34 | 35 | format: 36 | name: format-check 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v5 40 | - name: Setup .NET Core 41 | uses: actions/setup-dotnet@v5 42 | with: 43 | dotnet-version: "8.0.x" 44 | - name: Check with dotnet-format 45 | run: dotnet format --version 46 | - name: Check with dotnet-format 47 | run: dotnet format --verbosity normal --verify-no-changes 48 | 49 | yaml-lint: 50 | name: Yaml linting check 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@v5 54 | - name: Yaml lint check 55 | uses: ibiqlik/action-yamllint@v3 56 | with: 57 | config_file: .yamllint.yml 58 | -------------------------------------------------------------------------------- /src/Meilisearch/ISearchable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Wrapper for Search Results. 8 | /// 9 | /// Hit type. 10 | [JsonConverter(typeof(ISearchableJsonConverterFactory))] 11 | public interface ISearchable 12 | { 13 | /// 14 | /// The uid of the index 15 | /// 16 | [JsonPropertyName("indexUid")] 17 | string IndexUid { get; } 18 | 19 | /// 20 | /// Results of the query. 21 | /// 22 | [JsonPropertyName("hits")] 23 | IReadOnlyCollection Hits { get; } 24 | 25 | /// 26 | /// Returns the number of documents matching the current search query for each given facet. 27 | /// 28 | [JsonPropertyName("facetDistribution")] 29 | IReadOnlyDictionary> FacetDistribution { get; } 30 | 31 | /// 32 | /// Processing time of the query. 33 | /// 34 | [JsonPropertyName("processingTimeMs")] 35 | int ProcessingTimeMs { get; } 36 | 37 | /// 38 | /// Query originating the response. 39 | /// 40 | [JsonPropertyName("query")] 41 | string Query { get; } 42 | 43 | /// 44 | /// Contains the location of each occurrence of queried terms across all fields. 45 | /// 46 | [JsonPropertyName("_matchesPosition")] 47 | IReadOnlyDictionary> MatchesPosition { get; } 48 | 49 | /// 50 | /// Returns the numeric min and max values per facet of the hits returned by the search query. 51 | /// 52 | [JsonPropertyName("facetStats")] 53 | IReadOnlyDictionary FacetStats { get; } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Meilisearch/Converters/EmbedderSourceConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace Meilisearch.Converters 6 | { 7 | internal class EmbedderSourceConverter : JsonConverter 8 | { 9 | public override EmbedderSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 10 | { 11 | if (reader.TokenType == JsonTokenType.String) 12 | { 13 | var enumValue = reader.GetString(); 14 | if (Enum.TryParse(enumValue, true, out var embedderSource)) 15 | { 16 | return embedderSource; 17 | } 18 | 19 | throw new JsonException($"Invalid EmbedderSource value: '{enumValue}'."); 20 | } 21 | 22 | throw new JsonException($"Expected string for EmbedderSource, but found {reader.TokenType}."); 23 | } 24 | 25 | public override void Write(Utf8JsonWriter writer, EmbedderSource value, JsonSerializerOptions options) 26 | { 27 | string source; 28 | switch (value) 29 | { 30 | case EmbedderSource.OpenAi: 31 | source = "openAi"; 32 | break; 33 | case EmbedderSource.HuggingFace: 34 | source = "huggingFace"; 35 | break; 36 | case EmbedderSource.Ollama: 37 | source = "ollama"; 38 | break; 39 | case EmbedderSource.Rest: 40 | source = "rest"; 41 | break; 42 | case EmbedderSource.UserProvided: 43 | source = "userProvided"; 44 | break; 45 | default: 46 | throw new ArgumentOutOfRangeException(nameof(value), value, null); 47 | } 48 | 49 | writer.WriteStringValue(source); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/VersionTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Net.Http; 3 | using System.Xml; 4 | 5 | using Meilisearch.Extensions; 6 | 7 | using Xunit; 8 | 9 | namespace Meilisearch.Tests 10 | { 11 | public class VersionTests 12 | { 13 | private readonly Version _version; 14 | 15 | public VersionTests() 16 | { 17 | _version = new Version(); 18 | } 19 | 20 | [Fact] 21 | public void GetQualifiedVersion() 22 | { 23 | var qualifiedVersion = _version.GetQualifiedVersion(); 24 | var version = _version.GetVersion(); 25 | 26 | Assert.Equal(qualifiedVersion, $"Meilisearch .NET (v{version})"); 27 | } 28 | 29 | [Fact] 30 | public void GetSimpleVersionFromCsprojFile() 31 | { 32 | // get the current version defined in the csproj file 33 | var xmldoc = new XmlDocument(); 34 | var currentDir = Directory.GetParent(Directory.GetCurrentDirectory()).FullName; 35 | var path = Path.Combine(currentDir, @"../../../../src/Meilisearch/Meilisearch.csproj"); 36 | xmldoc.Load(path); 37 | var mgr = new XmlNamespaceManager(xmldoc.NameTable); 38 | mgr.AddNamespace("x", "http://schemas.microsoft.com/developer/msbuild/2003"); 39 | var versionFromCsproj = xmldoc.FirstChild.FirstChild.SelectSingleNode("Version").InnerText; 40 | 41 | var value = _version.GetVersion(); 42 | 43 | Assert.NotNull(value); 44 | Assert.Equal(versionFromCsproj, value); 45 | } 46 | 47 | [Fact] 48 | public void GetDefaultUserAgentHeader() 49 | { 50 | var httpClient = new HttpClient(); 51 | httpClient.AddDefaultUserAgent(); 52 | var userAgent = string.Join(' ', httpClient.DefaultRequestHeaders.GetValues("User-Agent")); 53 | var version = new Version(); 54 | 55 | Assert.Equal(version.GetQualifiedVersion(), userAgent); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Meilisearch/FacetSearchQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Wrapper for facet search query 8 | /// 9 | public class FacetSearchQuery 10 | { 11 | /// 12 | /// Gets or sets the facetName property 13 | /// 14 | [JsonPropertyName("facetName")] 15 | public string FacetName { get; set; } 16 | 17 | /// 18 | /// Gets or sets the facetQuery property 19 | /// 20 | [JsonPropertyName("facetQuery")] 21 | public string FacetQuery { get; set; } 22 | 23 | /// 24 | /// Gets or sets the q property 25 | /// 26 | [JsonPropertyName("q")] 27 | public string Query { get; set; } 28 | 29 | /// 30 | /// Gets or sets the filter property 31 | /// 32 | [JsonPropertyName("filter")] 33 | public dynamic Filter { get; set; } 34 | 35 | /// 36 | /// Gets or sets the matchingStrategy property, can be last, all or frequency. 37 | /// 38 | [JsonPropertyName("matchingStrategy")] 39 | public string MatchingStrategy { get; set; } 40 | 41 | /// 42 | /// Gets or sets the attributesToSearchOn property 43 | /// 44 | [JsonPropertyName("attributesToSearchOn")] 45 | public IEnumerable AttributesToSearchOn { get; set; } 46 | 47 | /// 48 | /// When true, returns an exhaustive (exact) count for facet values during facet search. 49 | /// This may increase response time on large datasets. Omit or set to false to favor performance. 50 | /// Default (when null/omitted): server defaults apply. 51 | /// 52 | [JsonPropertyName("exhaustiveFacetCount")] 53 | public bool? ExhaustiveFacetCount { get; set; } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Meilisearch.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | false 6 | 7 | 8 | 9 | 1701;1702;CA1861 10 | 11 | 12 | 13 | 1701;1702;CA1861 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/Meilisearch/EmbedderDistribution.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Embedder distribution. 8 | /// 9 | public class EmbedderDistribution 10 | { 11 | private double _mean; 12 | private double _sigma; 13 | 14 | /// 15 | /// Creates a new instance of . 16 | /// 17 | /// Mean value between 0 and 1. 18 | /// Sigma value between 0 and 1. 19 | public EmbedderDistribution(double mean, double sigma) 20 | { 21 | Mean = mean; 22 | Sigma = sigma; 23 | } 24 | 25 | /// 26 | /// Gets or sets the mean. 27 | /// 28 | [JsonPropertyName("mean")] 29 | public double Mean 30 | { 31 | get => _mean; 32 | set 33 | { 34 | if (value < 0 || value > 1) 35 | { 36 | throw new ArgumentOutOfRangeException(nameof(Mean), "Mean must be between 0 and 1."); 37 | } 38 | 39 | _mean = value; 40 | } 41 | } 42 | 43 | /// 44 | /// Gets or sets the sigma. 45 | /// 46 | [JsonPropertyName("sigma")] 47 | public double Sigma 48 | { 49 | get => _sigma; 50 | set 51 | { 52 | if (value < 0 || value > 1) 53 | { 54 | throw new ArgumentOutOfRangeException(nameof(Sigma), "Sigma must be between 0 and 1."); 55 | } 56 | 57 | _sigma = value; 58 | } 59 | } 60 | 61 | /// 62 | /// Creates a new instance of with a uniform distribution. 63 | /// 64 | /// 65 | public static EmbedderDistribution Uniform() => new EmbedderDistribution(0.5, 0.5); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Meilisearch/SearchQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Meilisearch 4 | { 5 | /// 6 | /// Search Query for Meilisearch class. 7 | /// 8 | public class SearchQuery : SearchQueryBase 9 | { 10 | // pagination: 11 | 12 | /// 13 | /// Gets or sets offset for the Query. 14 | /// 15 | [JsonPropertyName("offset")] 16 | public int? Offset { get; set; } 17 | 18 | /// 19 | /// Gets or sets limits the number of results. 20 | /// 21 | [JsonPropertyName("limit")] 22 | public int? Limit { get; set; } 23 | 24 | /// 25 | /// Gets or sets hitsPerPage. 26 | /// 27 | [JsonPropertyName("hitsPerPage")] 28 | public int? HitsPerPage { get; set; } 29 | 30 | /// 31 | /// Gets or sets page. 32 | /// 33 | [JsonPropertyName("page")] 34 | public int? Page { get; set; } 35 | 36 | /// 37 | /// Sets distinct attribute at search time. 38 | /// 39 | [JsonPropertyName("distinct")] 40 | public string Distinct { get; set; } 41 | 42 | /// 43 | /// Gets or sets rankingScoreThreshold, a number between 0.0 and 1.0. 44 | /// 45 | [JsonPropertyName("rankingScoreThreshold")] 46 | public decimal? RankingScoreThreshold { get; set; } 47 | 48 | /// 49 | /// Gets or sets the hybrid search settings. 50 | /// 51 | [JsonPropertyName("hybrid")] 52 | public HybridSearch Hybrid { get; set; } 53 | 54 | /// 55 | /// Gets or sets the vector. 56 | /// 57 | [JsonPropertyName("vector")] 58 | public double[] Vector { get; set; } 59 | 60 | /// 61 | /// Gets or sets whether to retrieve vectors. 62 | /// 63 | [JsonPropertyName("retrieveVectors")] 64 | public bool RetrieveVectors { get; set; } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Meilisearch/TenantToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IdentityModel.Tokens.Jwt; 3 | using System.Security.Claims; 4 | using System.Text; 5 | using System.Text.Json; 6 | 7 | using Microsoft.IdentityModel.Tokens; 8 | 9 | namespace Meilisearch 10 | { 11 | public class TenantToken 12 | { 13 | /// 14 | /// Generates a Tenant Token in a JWT string format. 15 | /// 16 | /// JWT string 17 | public static string GenerateToken(string apiKeyUid, TenantTokenRules searchRules, string apiKey, DateTime? expiresAt) 18 | { 19 | if (string.IsNullOrEmpty(apiKeyUid)) 20 | { 21 | throw new MeilisearchTenantTokenApiKeyUidInvalid(); 22 | } 23 | 24 | if (string.IsNullOrEmpty(apiKey) || apiKey.Length < 16) 25 | { 26 | throw new MeilisearchTenantTokenApiKeyInvalid(); 27 | } 28 | 29 | if (expiresAt.HasValue && DateTime.Compare(DateTime.UtcNow, (DateTime)expiresAt) > 0) 30 | { 31 | throw new MeilisearchTenantTokenExpired(); 32 | } 33 | 34 | var rules = searchRules.ToClaim(); 35 | var isArray = rules is string[]; 36 | var valueType = isArray ? JsonClaimValueTypes.JsonArray : JsonClaimValueTypes.Json; 37 | 38 | var identity = new ClaimsIdentity(); 39 | identity.AddClaim(new Claim("apiKeyUid", apiKeyUid)); 40 | identity.AddClaim(new Claim("searchRules", JsonSerializer.Serialize(searchRules.ToClaim()), valueType)); 41 | 42 | var signingKey = Encoding.ASCII.GetBytes(apiKey); 43 | 44 | var tokenDescriptor = new SecurityTokenDescriptor 45 | { 46 | Subject = identity, 47 | Expires = expiresAt, 48 | SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(signingKey), SecurityAlgorithms.HmacSha256Signature) 49 | }; 50 | 51 | var tokenHandler = new JwtSecurityTokenHandler 52 | { 53 | SetDefaultTimesOnTokenCreation = false 54 | }; 55 | 56 | var token = tokenHandler.CreateToken(tokenDescriptor); 57 | return tokenHandler.WriteToken(token); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Meilisearch/SimilarDocumentsResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Search result for similar documents. 8 | /// 9 | public class SimilarDocumentsResult 10 | { 11 | /// 12 | /// Creates a new instance of the class. 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 20 | public SimilarDocumentsResult( 21 | IReadOnlyCollection hits, 22 | string id, 23 | int processingTimeMs, 24 | int offset, 25 | int limit, 26 | int estimatedTotalHits) 27 | { 28 | Hits = hits; 29 | Id = id; 30 | ProcessingTimeMs = processingTimeMs; 31 | Offset = offset; 32 | Limit = limit; 33 | EstimatedTotalHits = estimatedTotalHits; 34 | } 35 | 36 | /// 37 | /// Gets the hits. 38 | /// 39 | [JsonPropertyName("hits")] 40 | public IReadOnlyCollection Hits { get; } 41 | 42 | /// 43 | /// Gets the id. 44 | /// 45 | [JsonPropertyName("id")] 46 | public string Id { get; } 47 | 48 | /// 49 | /// Gets the processing time in milliseconds. 50 | /// 51 | [JsonPropertyName("processingTimeMs")] 52 | public int ProcessingTimeMs { get; } 53 | 54 | /// 55 | /// Gets the offset. 56 | /// 57 | [JsonPropertyName("offset")] 58 | public int Offset { get; } 59 | 60 | /// 61 | /// Gets the limit. 62 | /// 63 | [JsonPropertyName("limit")] 64 | public int Limit { get; } 65 | 66 | /// 67 | /// Gets the estimated total hits. 68 | /// 69 | [JsonPropertyName("estimatedTotalHits")] 70 | public int EstimatedTotalHits { get; } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.Dictionary.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http.Json; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Meilisearch 7 | { 8 | public partial class Index 9 | { 10 | /// 11 | /// Gets the dictionary of an index. 12 | /// 13 | /// The cancellation token for this call. 14 | /// Returns the dictionary. 15 | public async Task> GetDictionaryAsync(CancellationToken cancellationToken = default) 16 | { 17 | return await _http.GetFromJsonAsync($"indexes/{Uid}/settings/dictionary", cancellationToken: cancellationToken) 18 | .ConfigureAwait(false); 19 | } 20 | 21 | /// 22 | /// Updates the dictionary of an index. 23 | /// 24 | /// Dictionary object. 25 | /// The cancellation token for this call. 26 | /// Returns the task info of the asynchronous task. 27 | public async Task UpdateDictionaryAsync(IEnumerable dictionary, CancellationToken cancellationToken = default) 28 | { 29 | var responseMessage = 30 | await _http.PutAsJsonAsync($"indexes/{Uid}/settings/dictionary", dictionary, cancellationToken: cancellationToken) 31 | .ConfigureAwait(false); 32 | return await responseMessage.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 33 | } 34 | 35 | /// 36 | /// Resets the dictionary to their default values. 37 | /// 38 | /// The cancellation token for this call. 39 | /// Returns the task info of the asynchronous task. 40 | public async Task ResetDictionaryAsync(CancellationToken cancellationToken = default) 41 | { 42 | var httpResponse = await _http.DeleteAsync($"indexes/{Uid}/settings/dictionary", cancellationToken).ConfigureAwait(false); 43 | return await httpResponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Movie.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json; 3 | 4 | namespace Meilisearch.Tests 5 | { 6 | public class Movie 7 | { 8 | public string Id { get; set; } 9 | 10 | public string Name { get; set; } 11 | 12 | public string Genre { get; set; } 13 | } 14 | 15 | public struct MovieStruct 16 | { 17 | public string Id { get; set; } 18 | 19 | public string Name { get; set; } 20 | 21 | public string Genre { get; set; } 22 | } 23 | public class MovieInfo 24 | { 25 | public string Comment { get; set; } 26 | 27 | public int ReviewNb { get; set; } 28 | } 29 | 30 | public class MovieWithInfo 31 | { 32 | public string Id { get; set; } 33 | 34 | public string Name { get; set; } 35 | 36 | public MovieInfo Info { get; set; } 37 | } 38 | 39 | 40 | public class MovieWithIntId 41 | { 42 | public int Id { get; set; } 43 | 44 | public string Name { get; set; } 45 | 46 | public string Genre { get; set; } 47 | } 48 | 49 | public class FormattedMovie 50 | { 51 | public string Id { get; set; } 52 | 53 | public string Name { get; set; } 54 | 55 | public string Genre { get; set; } 56 | 57 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Naming convention used to match meilisearch.")] 58 | public Movie _Formatted { get; set; } 59 | } 60 | 61 | public class MovieWithRankingScore 62 | { 63 | public string Id { get; set; } 64 | 65 | public string Name { get; set; } 66 | 67 | public string Genre { get; set; } 68 | 69 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Naming convention used to match meilisearch.")] 70 | public double? _RankingScore { get; set; } 71 | } 72 | 73 | public class MovieWithRankingScoreDetails 74 | { 75 | public string Id { get; set; } 76 | 77 | public string Name { get; set; } 78 | 79 | public string Genre { get; set; } 80 | 81 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Naming convention used to match meilisearch.")] 82 | public IDictionary _RankingScoreDetails { get; set; } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.Faceting.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Json; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | using Meilisearch.Extensions; 6 | namespace Meilisearch 7 | { 8 | public partial class Index 9 | { 10 | /// 11 | /// Gets the faceting setting. 12 | /// 13 | /// The cancellation token for this call. 14 | /// Returns the faceting setting. 15 | public async Task GetFacetingAsync(CancellationToken cancellationToken = default) 16 | { 17 | return await _http.GetFromJsonAsync($"indexes/{Uid}/settings/faceting", cancellationToken: cancellationToken) 18 | .ConfigureAwait(false); 19 | } 20 | 21 | /// 22 | /// Updates the faceting setting. 23 | /// 24 | /// Faceting instance 25 | /// The cancellation token for this call. 26 | /// Returns the task info of the asynchronous task. 27 | public async Task UpdateFacetingAsync(Faceting faceting, CancellationToken cancellationToken = default) 28 | { 29 | var responseMessage = 30 | await _http.PatchAsJsonAsync($"indexes/{Uid}/settings/faceting", faceting, Constants.JsonSerializerOptionsRemoveNulls, cancellationToken: cancellationToken) 31 | .ConfigureAwait(false); 32 | 33 | return await responseMessage.Content.ReadFromJsonAsync(cancellationToken: cancellationToken) 34 | .ConfigureAwait(false); 35 | } 36 | 37 | /// 38 | /// Resets the faceting setting. 39 | /// 40 | /// The cancellation token for this call. 41 | /// Returns the task info of the asynchronous task. 42 | public async Task ResetFacetingAsync(CancellationToken cancellationToken = default) 43 | { 44 | var response = await _http.DeleteAsync($"indexes/{Uid}/settings/faceting", cancellationToken) 45 | .ConfigureAwait(false); 46 | 47 | return await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Meilisearch/QueryParameters/CancelTasksQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Meilisearch.QueryParameters 5 | { 6 | /// 7 | /// A class that handles the creation of a query string when cancelling tasks. 8 | /// 9 | public class CancelTasksQuery 10 | { 11 | /// 12 | /// Gets or sets the lists of indexUid to filter on. Case-sensitive. 13 | /// 14 | public List IndexUids { get; set; } 15 | 16 | /// 17 | /// Gets or sets the list of statuses to filter on. 18 | /// 19 | public List Statuses { get; set; } 20 | 21 | /// 22 | /// Gets or sets the list of types to filter on. 23 | /// 24 | public List Types { get; set; } 25 | 26 | /// 27 | /// Gets or sets the list of uids to filter on. Case-sensitive. 28 | /// 29 | public List Uids { get; set; } 30 | 31 | /// 32 | /// Gets or sets the list of canceledBy uids to filter on. Case-sensitive. 33 | /// 34 | public List CanceledBy { get; set; } 35 | 36 | /// 37 | /// Gets or sets the date before the task is enqueued to filter. 38 | /// 39 | public DateTime? BeforeEnqueuedAt { get; set; } 40 | 41 | /// 42 | /// Gets or sets the date after the task is enqueued to filter. 43 | /// 44 | public DateTime? AfterEnqueuedAt { get; set; } 45 | 46 | /// 47 | /// Gets or sets the date before the task was started to filter. 48 | /// 49 | public DateTime? BeforeStartedAt { get; set; } 50 | 51 | /// 52 | /// Gets or sets the date after the task was started to filter. 53 | /// 54 | public DateTime? AfterStartedAt { get; set; } 55 | 56 | /// 57 | /// Gets or sets the date before the task was finished to filter. 58 | /// 59 | public DateTime? BeforeFinishedAt { get; set; } 60 | 61 | /// 62 | /// Gets or sets the date after the task was finished to filter. 63 | /// 64 | public DateTime? AfterFinishedAt { get; set; } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Meilisearch/QueryParameters/DeleteTasksQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Meilisearch.QueryParameters 5 | { 6 | /// 7 | /// A class that handles the creation of a query string when deleting tasks. 8 | /// 9 | public class DeleteTasksQuery 10 | { 11 | /// 12 | /// Gets or sets the lists of indexUid to filter on. Case-sensitive. 13 | /// 14 | public List IndexUids { get; set; } 15 | 16 | /// 17 | /// Gets or sets the list of statuses to filter on. 18 | /// 19 | public List Statuses { get; set; } 20 | 21 | /// 22 | /// Gets or sets the list of types to filter on. 23 | /// 24 | public List Types { get; set; } 25 | 26 | /// 27 | /// Gets or sets the list of uids to filter on. Case-sensitive. 28 | /// 29 | public List Uids { get; set; } 30 | 31 | /// 32 | /// Gets or sets the list of canceledBy uids to filter on. Case-sensitive. 33 | /// 34 | public List CanceledBy { get; set; } 35 | 36 | /// 37 | /// Gets or sets the date before the task is enqueued to filter. 38 | /// 39 | public DateTime? BeforeEnqueuedAt { get; set; } 40 | 41 | /// 42 | /// Gets or sets the date after the task is enqueued to filter. 43 | /// 44 | public DateTime? AfterEnqueuedAt { get; set; } 45 | 46 | /// 47 | /// Gets or sets the date before the task was started to filter. 48 | /// 49 | public DateTime? BeforeStartedAt { get; set; } 50 | 51 | /// 52 | /// Gets or sets the date after the task was started to filter. 53 | /// 54 | public DateTime? AfterStartedAt { get; set; } 55 | 56 | /// 57 | /// Gets or sets the date before the task was finished to filter. 58 | /// 59 | public DateTime? BeforeFinishedAt { get; set; } 60 | 61 | /// 62 | /// Gets or sets the date after the task was finished to filter. 63 | /// 64 | public DateTime? AfterFinishedAt { get; set; } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.SearchCutoffMs.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Json; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Meilisearch 6 | { 7 | public partial class Index 8 | { 9 | 10 | /// 11 | /// Gets the search cutoff in milliseconds. 12 | /// 13 | /// Returns the search cutoff in milliseconds. 14 | public async Task GetSearchCutoffMsAsync(CancellationToken cancellationToken = default) 15 | { 16 | return await _http.GetFromJsonAsync($"indexes/{Uid}/settings/search-cutoff-ms", cancellationToken: cancellationToken) 17 | .ConfigureAwait(false); 18 | } 19 | /// 20 | /// Sets the search cutoff in milliseconds. 21 | /// 22 | /// The search cutoff in milliseconds. 23 | /// The cancellation token for this call. 24 | /// Returns the task info of the asynchronous task. 25 | public async Task UpdateSearchCutoffMsAsync(int searchCutoffMs, CancellationToken cancellationToken = default) 26 | { 27 | var responseMessage = 28 | await _http.PutAsJsonAsync($"indexes/{Uid}/settings/search-cutoff-ms", searchCutoffMs, Constants.JsonSerializerOptionsRemoveNulls, cancellationToken: cancellationToken) 29 | .ConfigureAwait(false); 30 | 31 | return await responseMessage.Content.ReadFromJsonAsync(cancellationToken: cancellationToken) 32 | .ConfigureAwait(false); 33 | } 34 | 35 | /// 36 | /// Resets the search cutoff in milliseconds. (default: 1500) 37 | /// 38 | /// The cancellation token for this call. 39 | /// Returns the task info of the asynchronous task. 40 | public async Task ResetSearchCutoffMsAsync(CancellationToken cancellationToken = default) 41 | { 42 | var responseMessage = await _http.DeleteAsync($"indexes/{Uid}/settings/search-cutoff-ms", cancellationToken) 43 | .ConfigureAwait(false); 44 | return await responseMessage.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.Pagination.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Json; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | using Meilisearch.Extensions; 6 | namespace Meilisearch 7 | { 8 | public partial class Index 9 | { 10 | /// 11 | /// Gets the pagination setting. 12 | /// 13 | /// The cancellation token for this call. 14 | /// Returns the pagination setting. 15 | public async Task GetPaginationAsync(CancellationToken cancellationToken = default) 16 | { 17 | return await _http.GetFromJsonAsync($"indexes/{Uid}/settings/pagination", cancellationToken: cancellationToken) 18 | .ConfigureAwait(false); 19 | } 20 | 21 | /// 22 | /// Updates the pagination setting. 23 | /// 24 | /// Pagination instance 25 | /// The cancellation token for this call. 26 | /// Returns the task info of the asynchronous task. 27 | public async Task UpdatePaginationAsync(Pagination pagination, CancellationToken cancellationToken = default) 28 | { 29 | var responseMessage = 30 | await _http.PatchAsJsonAsync($"indexes/{Uid}/settings/pagination", pagination, Constants.JsonSerializerOptionsRemoveNulls, cancellationToken: cancellationToken) 31 | .ConfigureAwait(false); 32 | 33 | return await responseMessage.Content.ReadFromJsonAsync(cancellationToken: cancellationToken) 34 | .ConfigureAwait(false); 35 | } 36 | 37 | /// 38 | /// Resets the pagination setting. 39 | /// 40 | /// The cancellation token for this call. 41 | /// Returns the task info of the asynchronous task. 42 | public async Task ResetPaginationAsync(CancellationToken cancellationToken = default) 43 | { 44 | var response = await _http.DeleteAsync($"indexes/{Uid}/settings/pagination", cancellationToken) 45 | .ConfigureAwait(false); 46 | 47 | return await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.Settings.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Json; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | using Meilisearch.Extensions; 6 | namespace Meilisearch 7 | { 8 | public partial class Index 9 | { 10 | /// 11 | /// Gets all the settings of an index. 12 | /// 13 | /// The cancellation token for this call. 14 | /// Returns all the settings. 15 | public async Task GetSettingsAsync(CancellationToken cancellationToken = default) 16 | { 17 | return await _http.GetFromJsonAsync($"indexes/{Uid}/settings", cancellationToken: cancellationToken) 18 | .ConfigureAwait(false); 19 | } 20 | 21 | /// 22 | /// Updates all the settings of an index. 23 | /// The settings that are not passed in parameter are not overwritten. 24 | /// 25 | /// Settings object. 26 | /// The cancellation token for this call. 27 | /// Returns the task info of the asynchronous task. 28 | public async Task UpdateSettingsAsync(Settings settings, CancellationToken cancellationToken = default) 29 | { 30 | var responseMessage = 31 | await _http.PatchAsJsonAsync($"indexes/{Uid}/settings", settings, Constants.JsonSerializerOptionsRemoveNulls, cancellationToken: cancellationToken) 32 | .ConfigureAwait(false); 33 | return await responseMessage.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 34 | } 35 | 36 | /// 37 | /// Resets all the settings to their default values. 38 | /// 39 | /// The cancellation token for this call. 40 | /// Returns the task info of the asynchronous task. 41 | public async Task ResetSettingsAsync(CancellationToken cancellationToken = default) 42 | { 43 | var httpResponse = await _http.DeleteAsync($"indexes/{Uid}/settings", cancellationToken).ConfigureAwait(false); 44 | return await httpResponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.StopWords.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http.Json; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Meilisearch 7 | { 8 | public partial class Index 9 | { 10 | /// 11 | /// Gets the stop words setting. 12 | /// 13 | /// The cancellation token for this call. 14 | /// Returns the stop words setting. 15 | public async Task> GetStopWordsAsync(CancellationToken cancellationToken = default) 16 | { 17 | return await _http.GetFromJsonAsync>($"indexes/{Uid}/settings/stop-words", cancellationToken: cancellationToken) 18 | .ConfigureAwait(false); 19 | } 20 | 21 | /// 22 | /// Updates the stop words setting. 23 | /// 24 | /// Collection of stop words. 25 | /// The cancellation token for this call. 26 | /// Returns the task info of the asynchronous task. 27 | public async Task UpdateStopWordsAsync(IEnumerable stopWords, CancellationToken cancellationToken = default) 28 | { 29 | var responseMessage = 30 | await _http.PutAsJsonAsync($"indexes/{Uid}/settings/stop-words", stopWords, Constants.JsonSerializerOptionsRemoveNulls, cancellationToken: cancellationToken) 31 | .ConfigureAwait(false); 32 | return await responseMessage.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 33 | } 34 | 35 | /// 36 | /// Resets the stop words setting. 37 | /// 38 | /// The cancellation token for this call. 39 | /// Returns the task info of the asynchronous task. 40 | public async Task ResetStopWordsAsync(CancellationToken cancellationToken = default) 41 | { 42 | var httpresponse = await _http.DeleteAsync($"indexes/{Uid}/settings/stop-words", cancellationToken) 43 | .ConfigureAwait(false); 44 | return await httpresponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Meilisearch/MeilisearchMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Net.Http.Json; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Meilisearch 7 | { 8 | /// 9 | /// Typed http request for Meilisearch. 10 | /// 11 | public class MeilisearchMessageHandler : DelegatingHandler 12 | { 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// Default message handler for Meilisearch API. 17 | /// 18 | public MeilisearchMessageHandler() 19 | { 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// Default message handler for Meilisearch API. 25 | /// 26 | /// InnerHandler. 27 | public MeilisearchMessageHandler(HttpMessageHandler innerHandler) 28 | : base(innerHandler) 29 | { 30 | } 31 | 32 | /// 33 | /// Override SendAsync to handle errors. 34 | /// 35 | /// Request. 36 | /// Cancellation Token. 37 | /// Return HttpResponseMessage. 38 | protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 39 | { 40 | try 41 | { 42 | var response = await base.SendAsync(request, cancellationToken); 43 | if (!response.IsSuccessStatusCode) 44 | { 45 | if (response.Content.Headers.ContentLength != 0) 46 | { 47 | var content = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 48 | throw new MeilisearchApiError(content); 49 | } 50 | 51 | throw new MeilisearchApiError(response.StatusCode, response.ReasonPhrase); 52 | } 53 | 54 | return response; 55 | } 56 | catch (HttpRequestException ex) 57 | { 58 | throw new MeilisearchCommunicationError("CommunicationError", ex); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.RankingRules.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http.Json; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Meilisearch 7 | { 8 | public partial class Index 9 | { 10 | /// 11 | /// Gets the ranking rules setting. 12 | /// 13 | /// The cancellation token for this call. 14 | /// Returns the ranking rules setting. 15 | public async Task> GetRankingRulesAsync(CancellationToken cancellationToken = default) 16 | { 17 | return await _http.GetFromJsonAsync>($"indexes/{Uid}/settings/ranking-rules", cancellationToken: cancellationToken) 18 | .ConfigureAwait(false); 19 | } 20 | 21 | /// 22 | /// Updates the ranking rules setting. 23 | /// 24 | /// Collection of ranking rules. 25 | /// The cancellation token for this call. 26 | /// Returns the task info of the asynchronous task. 27 | public async Task UpdateRankingRulesAsync(IEnumerable rankingRules, CancellationToken cancellationToken = default) 28 | { 29 | var responseMessage = 30 | await _http.PutAsJsonAsync($"indexes/{Uid}/settings/ranking-rules", rankingRules, Constants.JsonSerializerOptionsRemoveNulls, cancellationToken: cancellationToken) 31 | .ConfigureAwait(false); 32 | return await responseMessage.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 33 | } 34 | 35 | /// 36 | /// Resets the ranking rules setting. 37 | /// 38 | /// The cancellation token for this call. 39 | /// Returns the task info of the asynchronous task. 40 | public async Task ResetRankingRulesAsync(CancellationToken cancellationToken = default) 41 | { 42 | var httpresponse = await _http.DeleteAsync($"indexes/{Uid}/settings/ranking-rules", cancellationToken) 43 | .ConfigureAwait(false); 44 | return await httpresponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.Synonyms.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http.Json; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Meilisearch 7 | { 8 | public partial class Index 9 | { 10 | /// 11 | /// Gets the synonyms setting. 12 | /// 13 | /// The cancellation token for this call. 14 | /// Returns the synonyms setting. 15 | public async Task>> GetSynonymsAsync(CancellationToken cancellationToken = default) 16 | { 17 | return await _http.GetFromJsonAsync>>($"indexes/{Uid}/settings/synonyms", cancellationToken: cancellationToken) 18 | .ConfigureAwait(false); 19 | } 20 | 21 | /// 22 | /// Updates the synonyms setting. 23 | /// 24 | /// Collection of synonyms. 25 | /// The cancellation token for this call. 26 | /// Returns the task info of the asynchronous task. 27 | public async Task UpdateSynonymsAsync(Dictionary> synonyms, CancellationToken cancellationToken = default) 28 | { 29 | var responseMessage = 30 | await _http.PutAsJsonAsync($"indexes/{Uid}/settings/synonyms", synonyms, Constants.JsonSerializerOptionsRemoveNulls, cancellationToken: cancellationToken) 31 | .ConfigureAwait(false); 32 | return await responseMessage.Content.ReadFromJsonAsync(cancellationToken: cancellationToken) 33 | .ConfigureAwait(false); 34 | } 35 | 36 | /// 37 | /// Resets the synonyms setting. 38 | /// 39 | /// The cancellation token for this call. 40 | /// Returns the task info of the asynchronous task. 41 | public async Task ResetSynonymsAsync(CancellationToken cancellationToken = default) 42 | { 43 | var httpresponse = await _http.DeleteAsync($"indexes/{Uid}/settings/synonyms", cancellationToken) 44 | .ConfigureAwait(false); 45 | return await httpresponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.TypoTolerance.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Json; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | using Meilisearch.Extensions; 6 | namespace Meilisearch 7 | { 8 | public partial class Index 9 | { 10 | /// 11 | /// Gets the typo tolerance setting. 12 | /// 13 | /// The cancellation token for this call. 14 | /// Returns the typo tolerance setting. 15 | public async Task GetTypoToleranceAsync(CancellationToken cancellationToken = default) 16 | { 17 | return await _http.GetFromJsonAsync($"indexes/{Uid}/settings/typo-tolerance", cancellationToken: cancellationToken) 18 | .ConfigureAwait(false); 19 | } 20 | 21 | /// 22 | /// Updates the typo tolerance setting. 23 | /// 24 | /// TypoTolerance instance 25 | /// The cancellation token for this call. 26 | /// Returns the task info of the asynchronous task. 27 | public async Task UpdateTypoToleranceAsync(TypoTolerance typoTolerance, CancellationToken cancellationToken = default) 28 | { 29 | var responseMessage = 30 | await _http.PatchAsJsonAsync($"indexes/{Uid}/settings/typo-tolerance", typoTolerance, Constants.JsonSerializerOptionsRemoveNulls, cancellationToken: cancellationToken) 31 | .ConfigureAwait(false); 32 | 33 | return await responseMessage.Content.ReadFromJsonAsync(cancellationToken: cancellationToken) 34 | .ConfigureAwait(false); 35 | } 36 | 37 | /// 38 | /// Resets the typo tolerance setting. 39 | /// 40 | /// The cancellation token for this call. 41 | /// Returns the task info of the asynchronous task. 42 | public async Task ResetTypoToleranceAsync(CancellationToken cancellationToken = default) 43 | { 44 | var response = await _http.DeleteAsync($"indexes/{Uid}/settings/typo-tolerance", cancellationToken) 45 | .ConfigureAwait(false); 46 | 47 | return await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.Tasks.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | using Meilisearch.QueryParameters; 6 | 7 | namespace Meilisearch 8 | { 9 | public partial class Index 10 | { 11 | /// 12 | /// Gets the tasks. 13 | /// 14 | /// Query parameters supports by the method. 15 | /// The cancellation token for this call. 16 | /// Returns a list of the operations status. 17 | public async Task>> GetTasksAsync(TasksQuery query = null, CancellationToken cancellationToken = default) 18 | { 19 | if (query == null) 20 | { 21 | query = new TasksQuery { IndexUids = new List { this.Uid } }; 22 | } 23 | 24 | return await TaskEndpoint().GetTasksAsync(query, cancellationToken).ConfigureAwait(false); 25 | } 26 | 27 | /// 28 | /// Get on task. 29 | /// 30 | /// Uid of the task. 31 | /// The cancellation token for this call. 32 | /// Returns the tasks. 33 | public async Task GetTaskAsync(int taskUid, CancellationToken cancellationToken = default) 34 | { 35 | return await TaskEndpoint().GetTaskAsync(taskUid, cancellationToken).ConfigureAwait(false); 36 | } 37 | 38 | /// 39 | /// Waits until the asynchronous task was done. 40 | /// 41 | /// Unique identifier of the asynchronous task. 42 | /// Timeout in millisecond. 43 | /// Interval in millisecond between each check. 44 | /// The cancellation token for this call. 45 | /// Returns the task info of finished task. 46 | public async Task WaitForTaskAsync( 47 | int taskUid, 48 | double timeoutMs = 5000.0, 49 | int intervalMs = 50, 50 | CancellationToken cancellationToken = default) 51 | { 52 | return await TaskEndpoint().WaitForTaskAsync(taskUid, timeoutMs, intervalMs, cancellationToken).ConfigureAwait(false); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.SeparatorTokens.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http.Json; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Meilisearch 7 | { 8 | public partial class Index 9 | { 10 | /// 11 | /// Gets all the separator tokens settings. 12 | /// 13 | /// The cancellation token for this call. 14 | /// Returns all the configured separator tokens. 15 | public async Task> GetSeparatorTokensAsync(CancellationToken cancellationToken = default) 16 | { 17 | return await _http.GetFromJsonAsync>($"indexes/{Uid}/settings/separator-tokens", cancellationToken: cancellationToken) 18 | .ConfigureAwait(false); 19 | } 20 | 21 | /// 22 | /// Updates all the separator tokens settings. 23 | /// 24 | /// Collection of separator tokens. 25 | /// The cancellation token for this call. 26 | /// Returns the task info of the asynchronous task. 27 | public async Task UpdateSeparatorTokensAsync(IEnumerable separatorTokens, CancellationToken cancellationToken = default) 28 | { 29 | var responseMessage = 30 | await _http.PutAsJsonAsync($"indexes/{Uid}/settings/separator-tokens", separatorTokens, Constants.JsonSerializerOptionsRemoveNulls, cancellationToken: cancellationToken) 31 | .ConfigureAwait(false); 32 | return await responseMessage.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 33 | } 34 | 35 | /// 36 | /// Resets all the separator tokens settings. 37 | /// 38 | /// The cancellation token for this call. 39 | /// Returns the task info of the asynchronous task. 40 | public async Task ResetSeparatorTokensAsync(CancellationToken cancellationToken = default) 41 | { 42 | var httpresponse = await _http.DeleteAsync($"indexes/{Uid}/settings/separator-tokens", cancellationToken) 43 | .ConfigureAwait(false); 44 | return await httpresponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.NonSeparatorTokens.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http.Json; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Meilisearch 7 | { 8 | public partial class Index 9 | { 10 | /// 11 | /// Gets all the non separator tokens settings. 12 | /// 13 | /// The cancellation token for this call. 14 | /// Returns all the configured non separator tokens. 15 | public async Task> GetNonSeparatorTokensAsync(CancellationToken cancellationToken = default) 16 | { 17 | return await _http.GetFromJsonAsync>($"indexes/{Uid}/settings/non-separator-tokens", cancellationToken: cancellationToken) 18 | .ConfigureAwait(false); 19 | } 20 | 21 | /// 22 | /// Updates all the non separator tokens settings. 23 | /// 24 | /// Collection of separator tokens. 25 | /// The cancellation token for this call. 26 | /// Returns the task info of the asynchronous task. 27 | public async Task UpdateNonSeparatorTokensAsync(IEnumerable nonSeparatorTokens, CancellationToken cancellationToken = default) 28 | { 29 | var responseMessage = 30 | await _http.PutAsJsonAsync($"indexes/{Uid}/settings/non-separator-tokens", nonSeparatorTokens, Constants.JsonSerializerOptionsRemoveNulls, cancellationToken: cancellationToken) 31 | .ConfigureAwait(false); 32 | return await responseMessage.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 33 | } 34 | 35 | /// 36 | /// Resets all the non separator tokens settings. 37 | /// 38 | /// The cancellation token for this call. 39 | /// Returns the task info of the asynchronous task. 40 | public async Task ResetNonSeparatorTokensAsync(CancellationToken cancellationToken = default) 41 | { 42 | var httpresponse = await _http.DeleteAsync($"indexes/{Uid}/settings/non-separator-tokens", cancellationToken) 43 | .ConfigureAwait(false); 44 | return await httpresponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Meilisearch/IndexStats.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Wrapper for index stats. 8 | /// 9 | public class IndexStats 10 | { 11 | public IndexStats(int numberOfDocuments, bool isIndexing, IReadOnlyDictionary fieldDistribution, long rawDocumentDbSize, long avgDocumentSize, int numberOfEmbeddedDocuments, int numberOfEmbeddings) 12 | { 13 | NumberOfDocuments = numberOfDocuments; 14 | IsIndexing = isIndexing; 15 | FieldDistribution = fieldDistribution; 16 | RawDocumentDbSize = rawDocumentDbSize; 17 | AvgDocumentSize = avgDocumentSize; 18 | NumberOfEmbeddedDocuments = numberOfEmbeddedDocuments; 19 | NumberOfEmbeddings = numberOfEmbeddings; 20 | } 21 | 22 | /// 23 | /// Gets the total number of documents. 24 | /// 25 | [JsonPropertyName("numberOfDocuments")] 26 | public int NumberOfDocuments { get; } 27 | 28 | /// 29 | /// Gets a value indicating whether the index is currently indexing. 30 | /// 31 | [JsonPropertyName("isIndexing")] 32 | public bool IsIndexing { get; } 33 | 34 | /// 35 | /// Gets field distribution. 36 | /// 37 | [JsonPropertyName("fieldDistribution")] 38 | public IReadOnlyDictionary FieldDistribution { get; } 39 | 40 | /// 41 | /// Get the total size of the documents stored in Meilisearch 42 | /// 43 | [JsonPropertyName("rawDocumentDbSize")] 44 | public long RawDocumentDbSize { get; } 45 | 46 | /// 47 | /// Get the total size of the documents stored in Meilisearch divided by the number of documents 48 | /// 49 | [JsonPropertyName("avgDocumentSize")] 50 | public long AvgDocumentSize { get; } 51 | 52 | /// 53 | /// Get the number of document in index that contains at least one embedded representation 54 | /// 55 | [JsonPropertyName("numberOfEmbeddedDocuments")] 56 | public int NumberOfEmbeddedDocuments { get; } 57 | 58 | 59 | /// 60 | /// Get the total number of embeddings representation that exists in that indexes 61 | /// 62 | [JsonPropertyName("numberOfEmbeddings")] 63 | public int NumberOfEmbeddings { get; } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.ProximityPrecision.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http.Json; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | using Meilisearch.Extensions; 9 | 10 | namespace Meilisearch 11 | { 12 | public partial class Index 13 | { 14 | /// 15 | /// Gets the proximity precision setting. 16 | /// 17 | /// The cancellation token for this call. 18 | /// Returns the proximity precision setting. 19 | public async Task GetProximityPrecisionAsync(CancellationToken cancellationToken = default) 20 | { 21 | return await _http.GetFromJsonAsync($"indexes/{Uid}/settings/proximity-precision", cancellationToken: cancellationToken) 22 | .ConfigureAwait(false); 23 | } 24 | 25 | /// 26 | /// Updates the proximity precision setting. 27 | /// 28 | /// The new proximity precision setting. 29 | /// The cancellation token for this call. 30 | /// Returns the task info of the asynchronous task. 31 | public async Task UpdateProximityPrecisionAsync(string proximityPrecision, CancellationToken cancellationToken = default) 32 | { 33 | var responseMessage = 34 | await _http.PutAsJsonAsync($"indexes/{Uid}/settings/proximity-precision", proximityPrecision, cancellationToken: cancellationToken) 35 | .ConfigureAwait(false); 36 | 37 | return await responseMessage.Content.ReadFromJsonAsync(cancellationToken: cancellationToken) 38 | .ConfigureAwait(false); 39 | } 40 | 41 | /// 42 | /// Resets proximity precision setting. 43 | /// 44 | /// The cancellation token for this call. 45 | /// Returns the task info of the asynchronous task. 46 | public async Task ResetProximityPrecisionAsync(CancellationToken cancellationToken = default) 47 | { 48 | var response = await _http.DeleteAsync($"indexes/{Uid}/settings/proximity-precision", cancellationToken) 49 | .ConfigureAwait(false); 50 | 51 | return await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Meilisearch/ISearchableJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace Meilisearch 6 | { 7 | /// 8 | /// The json converter factory for 9 | /// 10 | public class ISearchableJsonConverterFactory : JsonConverterFactory 11 | { 12 | /// 13 | public override bool CanConvert(Type typeToConvert) 14 | { 15 | return typeToConvert.IsInterface 16 | && typeToConvert.IsGenericType 17 | && (typeToConvert.GetGenericTypeDefinition() == typeof(ISearchable<>)); 18 | } 19 | 20 | /// 21 | public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) 22 | { 23 | var genericArgs = typeToConvert.GetGenericArguments(); 24 | var converterType = typeof(ISearchableJsonConverter<>).MakeGenericType( 25 | genericArgs[0] 26 | ); 27 | var converter = (JsonConverter)Activator.CreateInstance(converterType); 28 | return converter; 29 | } 30 | } 31 | 32 | /// 33 | /// The json converter for 34 | /// 35 | /// 36 | public class ISearchableJsonConverter : JsonConverter> 37 | { 38 | /// 39 | public override ISearchable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 40 | { 41 | var document = JsonSerializer.Deserialize(ref reader, options); 42 | return document.TryGetProperty("page", out _) || document.TryGetProperty("hitsPerPage", out _) 43 | ? document.Deserialize>(options) 44 | : (ISearchable)document.Deserialize>(options); 45 | } 46 | 47 | /// 48 | public override void Write(Utf8JsonWriter writer, ISearchable value, JsonSerializerOptions options) 49 | { 50 | if (value is PaginatedSearchResult paginated) 51 | { 52 | JsonSerializer.Serialize(writer, paginated, options); 53 | } 54 | else if (value is SearchResult normal) 55 | { 56 | JsonSerializer.Serialize(writer, normal, options); 57 | } 58 | else 59 | { 60 | JsonSerializer.Serialize(writer, (object)value, options); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Datasets/products_for_distinct_search.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "description": "Leather Jacket", 5 | "brand": "Lee Jeans", 6 | "product_id": "123456", 7 | "color": "Brown" 8 | }, 9 | { 10 | "id": 2, 11 | "description": "Leather Jacket", 12 | "brand": "Lee Jeans", 13 | "product_id": "123456", 14 | "color": "Black" 15 | }, 16 | { 17 | "id": 3, 18 | "description": "Leather Jacket", 19 | "brand": "Lee Jeans", 20 | "product_id": "123456", 21 | "color": "Blue" 22 | }, 23 | { 24 | "id": 4, 25 | "description": "T-Shirt", 26 | "brand": "Nike", 27 | "product_id": "789012", 28 | "color": "Red" 29 | }, 30 | { 31 | "id": 5, 32 | "description": "T-Shirt", 33 | "brand": "Nike", 34 | "product_id": "789012", 35 | "color": "Blue" 36 | }, 37 | { 38 | "id": 6, 39 | "description": "Running Shoes", 40 | "brand": "Adidas", 41 | "product_id": "456789", 42 | "color": "Black" 43 | }, 44 | { 45 | "id": 7, 46 | "description": "Running Shoes", 47 | "brand": "Adidas", 48 | "product_id": "456789", 49 | "color": "White" 50 | }, 51 | { 52 | "id": 8, 53 | "description": "Hoodie", 54 | "brand": "Puma", 55 | "product_id": "987654", 56 | "color": "Gray" 57 | }, 58 | { 59 | "id": 9, 60 | "description": "Sweater", 61 | "brand": "Gap", 62 | "product_id": "234567", 63 | "color": "Green" 64 | }, 65 | { 66 | "id": 10, 67 | "description": "Sweater", 68 | "brand": "Gap", 69 | "product_id": "234567", 70 | "color": "Red" 71 | }, 72 | { 73 | "id": 11, 74 | "description": "Sweater", 75 | "brand": "Gap", 76 | "product_id": "234567", 77 | "color": "Blue" 78 | }, 79 | { 80 | "id": 12, 81 | "description": "Jeans", 82 | "brand": "Levi's", 83 | "product_id": "345678", 84 | "color": "Indigo" 85 | }, 86 | { 87 | "id": 13, 88 | "description": "Jeans", 89 | "brand": "Levi's", 90 | "product_id": "345678", 91 | "color": "Black" 92 | }, 93 | { 94 | "id": 14, 95 | "description": "Jeans", 96 | "brand": "Levi's", 97 | "product_id": "345678", 98 | "color": "Stone Wash" 99 | } 100 | ] 101 | -------------------------------------------------------------------------------- /src/Meilisearch/Embedder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Embedder configuration. 8 | /// 9 | public class Embedder 10 | { 11 | /// 12 | /// Gets or sets the source. 13 | /// 14 | [JsonPropertyName("source")] 15 | public EmbedderSource Source { get; set; } 16 | 17 | /// 18 | /// Gets or sets the URL. 19 | /// 20 | [JsonPropertyName("url")] 21 | public string Url { get; set; } 22 | 23 | /// 24 | /// Gets or sets the API key. 25 | /// 26 | [JsonPropertyName("apiKey")] 27 | public string ApiKey { get; set; } 28 | 29 | /// 30 | /// Gets or sets the model. 31 | /// 32 | [JsonPropertyName("model")] 33 | public string Model { get; set; } 34 | 35 | /// 36 | /// Gets or sets the document template. 37 | /// 38 | [JsonPropertyName("documentTemplate")] 39 | public string DocumentTemplate { get; set; } 40 | 41 | /// 42 | /// Gets or sets the document template max bytes. 43 | /// 44 | [JsonPropertyName("documentTemplateMaxBytes")] 45 | public int? DocumentTemplateMaxBytes { get; set; } 46 | 47 | /// 48 | /// Gets or sets the dimensions. 49 | /// 50 | [JsonPropertyName("dimensions")] 51 | public int? Dimensions { get; set; } 52 | 53 | /// 54 | /// Gets or sets the revision. 55 | /// 56 | [JsonPropertyName("revision")] 57 | public string Revision { get; set; } 58 | 59 | /// 60 | /// Gets or sets the distribution. 61 | /// 62 | [JsonPropertyName("distribution")] 63 | public EmbedderDistribution Distribution { get; set; } 64 | 65 | /// 66 | /// Gets or sets the request. 67 | /// 68 | [JsonPropertyName("request")] 69 | public Dictionary Request { get; set; } 70 | 71 | /// 72 | /// Gets or sets the response. 73 | /// 74 | [JsonPropertyName("response")] 75 | public Dictionary Response { get; set; } 76 | 77 | /// 78 | /// Gets or sets whether the vectors should be compressed. 79 | /// 80 | [JsonPropertyName("binaryQuantized")] 81 | public bool? BinaryQuantized { get; set; } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Meilisearch/QueryParameters/TasksQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Meilisearch.QueryParameters 5 | { 6 | /// 7 | /// A class that handles the creation of a query string for Tasks. 8 | /// 9 | public class TasksQuery 10 | { 11 | /// 12 | /// Gets or sets the Number of tasks to return. 13 | /// 14 | public int? Limit { get; set; } 15 | 16 | /// 17 | /// Gets or sets the uid of the first task returned. 18 | /// 19 | public int? From { get; set; } 20 | 21 | /// 22 | /// Gets or sets the lists of indexUid to filter on. Case-sensitive. 23 | /// 24 | public List IndexUids { get; set; } 25 | 26 | /// 27 | /// Gets or sets the lists of uid to filter on. Case-sensitive. 28 | /// 29 | public List Uids { get; set; } 30 | 31 | /// 32 | /// Gets or sets the list of statuses to filter on. 33 | /// 34 | public List Statuses { get; set; } 35 | 36 | /// 37 | /// Gets or sets the list of types to filter on. 38 | /// 39 | public List Types { get; set; } 40 | 41 | /// 42 | /// Gets or sets the list of canceledBy uids to filter on. Case-sensitive. 43 | /// 44 | public List CanceledBy { get; set; } 45 | 46 | /// 47 | /// Gets or sets the date before the task is enqueued to filter. 48 | /// 49 | public DateTime? BeforeEnqueuedAt { get; set; } 50 | 51 | /// 52 | /// Gets or sets the date after the task is enqueued to filter. 53 | /// 54 | public DateTime? AfterEnqueuedAt { get; set; } 55 | 56 | /// 57 | /// Gets or sets the date before the task was started to filter. 58 | /// 59 | public DateTime? BeforeStartedAt { get; set; } 60 | 61 | /// 62 | /// Gets or sets the date after the task was started to filter. 63 | /// 64 | public DateTime? AfterStartedAt { get; set; } 65 | 66 | /// 67 | /// Gets or sets the date before the task was finished to filter. 68 | /// 69 | public DateTime? BeforeFinishedAt { get; set; } 70 | 71 | /// 72 | /// Gets or sets the date after the task was finished to filter. 73 | /// 74 | public DateTime? AfterFinishedAt { get; set; } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Meilisearch/Converters/AlwaysIncludeEmptyObjectConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace Meilisearch.Converters 8 | { 9 | /// 10 | /// Always include property in json. MultiSearchFederationOptions will be serialized as "{}" 11 | /// 12 | public class MultiSearchFederationOptionsConverter : JsonConverter 13 | { 14 | /// 15 | /// Would override the default read logic, but here we use the default 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | public override MultiSearchFederationOptions Read(ref Utf8JsonReader reader, Type typeToConvert, 22 | JsonSerializerOptions options) 23 | { 24 | return JsonSerializer.Deserialize(ref reader, options); 25 | } 26 | 27 | /// 28 | /// Write json for MultiSearchFederationOptions and include it always as empty object 29 | /// 30 | /// 31 | /// 32 | /// 33 | public override void Write(Utf8JsonWriter writer, MultiSearchFederationOptions value, 34 | JsonSerializerOptions options) 35 | { 36 | if (value == null || !HasAnyValueSet(value)) 37 | { 38 | WriteEmptyObject(writer); 39 | } 40 | else 41 | { 42 | JsonSerializer.Serialize(writer, value); 43 | } 44 | } 45 | private static void WriteEmptyObject(Utf8JsonWriter writer) 46 | { 47 | writer.WriteStartObject(); 48 | writer.WriteEndObject(); 49 | } 50 | 51 | private bool HasAnyValueSet(MultiSearchFederationOptions value) 52 | { 53 | foreach (var property in 54 | value.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) 55 | { 56 | var propertyValue = property.GetValue(value); 57 | var defaultValue = GetDefaultValue(property.PropertyType); 58 | 59 | if (!Equals(propertyValue, defaultValue)) 60 | { 61 | return true; 62 | } 63 | } 64 | return false; 65 | } 66 | 67 | private object GetDefaultValue(Type type) 68 | { 69 | return type.IsValueType ? Activator.CreateInstance(type) : null; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Meilisearch/Index.Embedders.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http.Json; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | using Meilisearch.Extensions; 7 | 8 | namespace Meilisearch 9 | { 10 | public partial class Index 11 | { 12 | /// 13 | /// Gets the embedders setting. 14 | /// 15 | /// The cancellation token for this call. 16 | /// Returns the embedders setting. 17 | public async Task> GetEmbeddersAsync(CancellationToken cancellationToken = default) 18 | { 19 | return await _http 20 | .GetFromJsonAsync>( 21 | $"indexes/{Uid}/settings/embedders", 22 | cancellationToken: cancellationToken) 23 | .ConfigureAwait(false); 24 | } 25 | 26 | /// 27 | /// Updates the embedders setting. 28 | /// 29 | /// Collection of embedders. 30 | /// The cancellation token for this call. 31 | /// Returns the task info of the asynchronous task. 32 | public async Task UpdateEmbeddersAsync(Dictionary embedders, CancellationToken cancellationToken = default) 33 | { 34 | var responseMessage = 35 | await _http.PatchAsJsonAsync( 36 | $"indexes/{Uid}/settings/embedders", 37 | embedders, 38 | Constants.JsonSerializerOptionsRemoveNulls, 39 | cancellationToken: cancellationToken) 40 | .ConfigureAwait(false); 41 | 42 | return await responseMessage.Content 43 | .ReadFromJsonAsync(cancellationToken: cancellationToken) 44 | .ConfigureAwait(false); 45 | } 46 | 47 | /// 48 | /// Resets the embedders setting. 49 | /// 50 | /// The cancellation token for this call. 51 | /// Returns the task info of the asynchronous task. 52 | public async Task ResetEmbeddersAsync(CancellationToken cancellationToken = default) 53 | { 54 | var response = await _http 55 | .DeleteAsync($"indexes/{Uid}/settings/embedders", cancellationToken) 56 | .ConfigureAwait(false); 57 | 58 | return await response.Content 59 | .ReadFromJsonAsync(cancellationToken: cancellationToken) 60 | .ConfigureAwait(false); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Meilisearch/SimilarDocumentsQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Search query for similar documents. 8 | /// 9 | public class SimilarDocumentsQuery 10 | { 11 | /// 12 | /// Creates a new instance of the class. 13 | /// 14 | /// 15 | public SimilarDocumentsQuery(string id) 16 | { 17 | if (string.IsNullOrEmpty(id)) 18 | { 19 | throw new ArgumentNullException(nameof(id)); 20 | } 21 | 22 | Id = id; 23 | } 24 | 25 | /// 26 | /// Gets the document id. 27 | /// 28 | [JsonPropertyName("id")] 29 | public string Id { get; } 30 | 31 | /// 32 | /// Gets or sets the embedder. 33 | /// 34 | [JsonPropertyName("embedder")] 35 | public string Embedder { get; set; } 36 | 37 | /// 38 | /// Gets or sets the attributes to retrieve. 39 | /// 40 | [JsonPropertyName("attributesToRetrieve")] 41 | public string[] AttributesToRetrieve { get; set; } = new[] { "*" }; 42 | 43 | /// 44 | /// Gets or sets the offset. 45 | /// 46 | [JsonPropertyName("offset")] 47 | public int Offset { get; set; } = 0; 48 | 49 | /// 50 | /// Gets or sets the limit. 51 | /// 52 | [JsonPropertyName("limit")] 53 | public int Limit { get; set; } = 20; 54 | 55 | /// 56 | /// Gets or sets the filter. 57 | /// 58 | [JsonPropertyName("filter")] 59 | public dynamic Filter { get; set; } 60 | 61 | /// 62 | /// Gets or sets whether to show the ranking score. 63 | /// 64 | [JsonPropertyName("showRankingScore")] 65 | public bool ShowRankingScore { get; set; } 66 | 67 | /// 68 | /// Gets or sets whether to show the ranking score details. 69 | /// 70 | [JsonPropertyName("showRankingScoreDetails")] 71 | public bool ShowRankingScoreDetails { get; set; } 72 | 73 | /// 74 | /// Gets or sets the ranking score threshold. 75 | /// 76 | [JsonPropertyName("rankingScoreThreshold")] 77 | public decimal? RankingScoreThreshold { get; set; } 78 | 79 | /// 80 | /// Gets or sets whether to retrieve the vectors. 81 | /// 82 | [JsonPropertyName("retrieveVectors")] 83 | public bool RetrieveVectors { get; set; } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Meilisearch/TaskResource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace Meilisearch 6 | { 7 | /// 8 | /// Information of the regarding a task. 9 | /// 10 | public class TaskResource 11 | { 12 | public TaskResource(int uid, string indexUid, TaskInfoStatus status, TaskInfoType type, 13 | IReadOnlyDictionary details, IReadOnlyDictionary error, string duration, DateTime enqueuedAt, 14 | DateTime? startedAt, DateTime? finishedAt) 15 | { 16 | Uid = uid; 17 | IndexUid = indexUid; 18 | Status = status; 19 | Type = type; 20 | Details = details; 21 | Error = error; 22 | Duration = duration; 23 | EnqueuedAt = enqueuedAt; 24 | StartedAt = startedAt; 25 | FinishedAt = finishedAt; 26 | } 27 | 28 | /// 29 | /// The unique sequential identifier of the task. 30 | /// 31 | [JsonPropertyName("uid")] 32 | public int Uid { get; } 33 | 34 | /// 35 | /// The unique index identifier. 36 | /// 37 | [JsonPropertyName("indexUid")] 38 | public string IndexUid { get; } 39 | 40 | /// 41 | /// The status of the task. Possible values are enqueued, processing, succeeded, failed. 42 | /// 43 | [JsonPropertyName("status")] 44 | public TaskInfoStatus Status { get; } 45 | 46 | /// 47 | /// The type of task. 48 | /// 49 | [JsonPropertyName("type")] 50 | public TaskInfoType Type { get; } 51 | 52 | /// 53 | /// Detailed information on the task payload. 54 | /// 55 | [JsonPropertyName("details")] 56 | public IReadOnlyDictionary Details { get; } 57 | 58 | /// 59 | /// Error details and context. Only present when a task has the failed status. 60 | /// 61 | [JsonPropertyName("error")] 62 | public IReadOnlyDictionary Error { get; } 63 | 64 | /// 65 | /// The total elapsed time the task spent in the processing state, in ISO 8601 format. 66 | /// 67 | [JsonPropertyName("duration")] 68 | public string Duration { get; } 69 | 70 | /// 71 | /// The date and time when the task was first enqueued, in RFC 3339 format. 72 | /// 73 | [JsonPropertyName("enqueuedAt")] 74 | public DateTime EnqueuedAt { get; } 75 | 76 | /// 77 | /// The date and time when the task began processing, in RFC 3339 format. 78 | /// 79 | [JsonPropertyName("startedAt")] 80 | public DateTime? StartedAt { get; } 81 | 82 | /// 83 | /// The date and time when the task finished processing, whether failed or succeeded, in RFC 3339 format. 84 | /// 85 | [JsonPropertyName("finishedAt")] 86 | public DateTime? FinishedAt { get; } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Datasets/songs_custom_delimiter.csv: -------------------------------------------------------------------------------- 1 | id;title;album;artist;genre;country;released;duration;released-timestamp;duration-float 2 | 702481615;Armatage Shanks;Dookie: The Ultimate Critical Review;Green Day;Rock;Europe;2005;;1104537600; 3 | 888221515;Old Folks;Six Classic Albums Plus Bonus Tracks;Harold Land;Jazz;Europe;2013;6:36;1356998400;6.36 4 | 1382413601;คำขอร้อง;สำเนียงคนจันทร์ / เอาเถอะถ้าเห็นเขาดีกว่า;อิทธิพล บำรุงกุล;"Folk; World; & Country";Thailand;;;; 5 | 190889300;Track 1;Summer Breeze;Dreas;Funk / Soul;US;2008;18:56;1199145600;18.56 6 | 813645611;Slave (Alternative Version);Honky Château;Elton John;Rock;Europe;;2:53;;2.5300000000000002 7 | 394018506;Sex & Geld;Trackz Für Den Index;Mafia Clikk;Hip Hop;Germany;2006;5:02;1136073600;5.02 8 | 1522481803;Pisciaunella;Don Pepp U Pacce;Giovanni Russo (2);"Folk; World; & Country";Italy;1980;;315532800; 9 | 862296713;不知;Kiss 2001 Hong Kong Live Concert;Various;Electronic;Hong Kong;2002-04-13;;1018656000; 10 | 467946423;Rot;Be Quick Or Be Dead Vol. 3;Various;Electronic;Serbia;2013-06-20;1:00;1371686400;1 11 | 1323854803;"Simulation Project 1; ツキハナ「Moonflower」";Unlimited Dream Company;Amun Dragoon;Electronic;US;2018-04-10;2:44;1523318400;2.44 12 | 235115704;Doctor Vine;The Big F;The Big F;Rock;US;1989;5:29;599616000;5.29 13 | 249025232;"Ringel; Ringel; Reihe";Kinderlieder ABC - Der Bielefelder Kinderchor Singt 42 Lieder Von A-Z;Der Bielefelder Kinderchor;Children's;Germany;1971;;31536000; 14 | 710094000;Happy Safari = Safari Feliz;Safari Swings Again = El Safari Sigue En Su Swing;Bert Kaempfert & His Orchestra;Jazz;Argentina;1977;2:45;220924800;2.45 15 | 538632700;Take Me Up;Spring;Various;Electronic;US;2000;3:06;946684800;3.06 16 | 1556505508;Doin To Me ( Radio Version );Say My Name;Netta Dogg;Hip Hop;US;2005;;1104537600; 17 | 1067031900;Concerto For Balloon & Orchestra / Concerto For Synthesizer & Orchestra;Concerto For Balloon & Orchestra And Three Overtures;Stanyan String & Wind Ensemble;Classical;US;1977;;220924800; 18 | 137251914;"I Love The Nightlife (Disco 'Round) (Real Rapino 7"" Mix)";The Adventures Of Priscilla: Queen Of The Desert - Original Motion Picture Soundtrack;Various;Stage & Screen;US;1994;3:31;757382400;3.31 19 | 554983904;Walking On The Moon;Certifiable (Live In Buenos Aires);The Police;Rock;Malaysia;2008-11-00;;1225497600; 20 | 557616002;Two Soldiers;Jerry Garcia / David Grisman;David Grisman;"Folk; World; & Country";US;2014-04-00;4:24;1396310400;4.24 21 | 878936809;When You Gonna Learn;Live At Firenze 93;Jamiroquai;Funk / Soul;France;2004;13:01;1072915200;13.01 22 | 368960707;Sardo D.O.C.;Sardinia Pride Compilation Vol.2;Various;Hip Hop;Italy;2012-06-22;4:41;1340323200;4.41 23 | 1416041312;Sympathy For The Devil;Under Cover;Ozzy Osbourne;Rock;Russia;2005;7:11;1104537600;7.11 24 | 1260509200;Grosse Overturen;noxious effects garanty;Nocif (3);Rock;France;1990;;631152000; 25 | 1466381324;Πρινιώτης;Μουσικά Πατήματα Της Κρήτης;Αντώνης Μαρτσάκης;"Folk; World; & Country";Greece;2019;;1546300800; 26 | 256009724;Here I Stand And Face The Rain (Demo);Hunting High And Low;a-ha;Electronic;UK & Europe;2010-07-23;;1279843200; 27 | 565253008;Born Free;At His Best Goldfinger;The Royal Philharmonic Orchestra;Blues;Japan;1976;;189302400; 28 | 492519701;Others;Where Did She Go;Stephan Sulke;Rock;US;1965;2:43;-157766400;2.43 29 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/Datasets.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace Meilisearch.Tests 7 | { 8 | internal static class Datasets 9 | { 10 | private static readonly string BasePath = Path.Combine(Directory.GetCurrentDirectory(), "Datasets"); 11 | public static readonly string SmallMoviesJsonPath = Path.Combine(BasePath, "small_movies.json"); 12 | public static readonly string SongsCsvPath = Path.Combine(BasePath, "songs.csv"); 13 | public static readonly string SongsCsvCustomDelimiterPath = Path.Combine(BasePath, "songs_custom_delimiter.csv"); 14 | public static readonly string SongsNdjsonPath = Path.Combine(BasePath, "songs.ndjson"); 15 | 16 | public static readonly string MoviesWithStringIdJsonPath = Path.Combine(BasePath, "movies_with_string_id.json"); 17 | public static readonly string MoviesForFacetingJsonPath = Path.Combine(BasePath, "movies_for_faceting.json"); 18 | public static readonly string MoviesForVectorJsonPath = Path.Combine(BasePath, "movies_for_vector.json"); 19 | public static readonly string MoviesWithIntIdJsonPath = Path.Combine(BasePath, "movies_with_int_id.json"); 20 | public static readonly string MoviesWithInfoJsonPath = Path.Combine(BasePath, "movies_with_info.json"); 21 | 22 | public static readonly string ProductsForDistinctJsonPath = Path.Combine(BasePath, "products_for_distinct_search.json"); 23 | } 24 | 25 | public class DatasetSmallMovie 26 | { 27 | public string Id { get; set; } 28 | public string Title { get; set; } 29 | public string Poster { get; set; } 30 | public string Overview { get; set; } 31 | [JsonPropertyName("release_date")] 32 | [JsonConverter(typeof(UnixEpochDateTimeConverter))] 33 | public DateTime ReleaseDate { get; set; } 34 | public string Genre { get; set; } 35 | } 36 | 37 | public class DatasetSong 38 | { 39 | public string Id { get; set; } 40 | public string Title { get; set; } 41 | public string Album { get; set; } 42 | public string Artist { get; set; } 43 | public string Genre { get; set; } 44 | public string Country { get; set; } 45 | public string Released { get; set; } 46 | public string Duration { get; set; } 47 | [JsonPropertyName("released-timestamp")] 48 | public long? ReleasedTimestamp { get; set; } 49 | [JsonPropertyName("duration-float")] 50 | public double? DurationFloat { get; set; } 51 | } 52 | 53 | sealed class UnixEpochDateTimeConverter : JsonConverter 54 | { 55 | static readonly DateTime s_epoch = new DateTime(1970, 1, 1, 0, 0, 0); 56 | 57 | public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 58 | { 59 | var unixTime = reader.GetInt64(); 60 | return s_epoch.AddMilliseconds(unixTime); 61 | } 62 | 63 | public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) 64 | { 65 | var unixTime = Convert.ToInt64((value - s_epoch).TotalMilliseconds); 66 | writer.WriteNumberValue(unixTime); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Meilisearch/SearchResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Wrapper for Search Results. 8 | /// 9 | /// Hit type. 10 | public class SearchResult : ISearchable 11 | { 12 | /// 13 | /// Create a new search result where the documents are of type 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | public SearchResult(IReadOnlyCollection hits, int offset, int limit, int estimatedTotalHits, 26 | IReadOnlyDictionary> facetDistribution, 27 | int processingTimeMs, string query, 28 | IReadOnlyDictionary> matchesPosition, 29 | IReadOnlyDictionary facetStats, 30 | string indexUid) 31 | { 32 | Hits = hits; 33 | Offset = offset; 34 | Limit = limit; 35 | EstimatedTotalHits = estimatedTotalHits; 36 | FacetDistribution = facetDistribution; 37 | ProcessingTimeMs = processingTimeMs; 38 | Query = query; 39 | MatchesPosition = matchesPosition; 40 | FacetStats = facetStats; 41 | IndexUid = indexUid; 42 | } 43 | 44 | /// 45 | [JsonPropertyName("hits")] 46 | public IReadOnlyCollection Hits { get; } 47 | 48 | /// 49 | /// Number of documents skipped. 50 | /// 51 | [JsonPropertyName("offset")] 52 | public int Offset { get; } 53 | 54 | /// 55 | /// Number of documents to take. 56 | /// 57 | [JsonPropertyName("limit")] 58 | public int Limit { get; } 59 | 60 | /// 61 | /// Gets the estimated total number of hits returned by the search. 62 | /// 63 | [JsonPropertyName("estimatedTotalHits")] 64 | public int EstimatedTotalHits { get; } 65 | 66 | /// 67 | [JsonPropertyName("facetDistribution")] 68 | public IReadOnlyDictionary> FacetDistribution { get; } 69 | 70 | /// 71 | [JsonPropertyName("processingTimeMs")] 72 | public int ProcessingTimeMs { get; } 73 | 74 | /// 75 | [JsonPropertyName("query")] 76 | public string Query { get; } 77 | 78 | /// 79 | [JsonPropertyName("_matchesPosition")] 80 | public IReadOnlyDictionary> MatchesPosition { get; } 81 | 82 | /// 83 | [JsonPropertyName("facetStats")] 84 | public IReadOnlyDictionary FacetStats { get; } 85 | 86 | /// 87 | [JsonPropertyName("indexUid")] 88 | public string IndexUid { get; } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/ServerConfigs/BaseUriServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Xunit; 4 | 5 | namespace Meilisearch.Tests.ServerConfigs 6 | { 7 | public class BaseUriServer 8 | { 9 | const string CollectionFixtureName = nameof(BaseUriServer); 10 | private const string MeilisearchTestAddress = "http://localhost:7700/"; 11 | 12 | public class ConfigFixture : IndexFixture 13 | { 14 | public override string MeilisearchAddress() 15 | { 16 | return Environment.GetEnvironmentVariable("MEILISEARCH_URL") ?? MeilisearchTestAddress; 17 | } 18 | } 19 | 20 | [CollectionDefinition(CollectionFixtureName)] 21 | public class IndexCollection : ICollectionFixture 22 | { 23 | } 24 | 25 | [Collection(CollectionFixtureName)] 26 | public class DocumentTests : DocumentTests 27 | { 28 | public DocumentTests(ConfigFixture fixture) : base(fixture) 29 | { 30 | } 31 | } 32 | 33 | [Collection(CollectionFixtureName)] 34 | public class IndexTests : IndexTests 35 | { 36 | public IndexTests(ConfigFixture fixture) : base(fixture) 37 | { 38 | } 39 | } 40 | 41 | [Collection(CollectionFixtureName)] 42 | public class KeyTests : KeyTests 43 | { 44 | public KeyTests(ConfigFixture fixture) : base(fixture) 45 | { 46 | } 47 | } 48 | 49 | [Collection(CollectionFixtureName)] 50 | public class MeilisearchClientTests : MeilisearchClientTests 51 | { 52 | public MeilisearchClientTests(ConfigFixture fixture) : base(fixture) 53 | { 54 | } 55 | } 56 | 57 | [Collection(CollectionFixtureName)] 58 | public class SearchTests : SearchTests 59 | { 60 | public SearchTests(ConfigFixture fixture) : base(fixture) 61 | { 62 | } 63 | } 64 | 65 | [Collection(CollectionFixtureName)] 66 | public class MultiIndexSearchTests : MultiIndexSearchTests 67 | { 68 | public MultiIndexSearchTests(ConfigFixture fixture) : base(fixture) 69 | { 70 | } 71 | } 72 | 73 | [Collection(CollectionFixtureName)] 74 | public class FacetingSearchTests : FacetSearchTests 75 | { 76 | public FacetingSearchTests(ConfigFixture fixture) : base(fixture) 77 | { 78 | } 79 | } 80 | 81 | [Collection(CollectionFixtureName)] 82 | public class SettingsTests : SettingsTests 83 | { 84 | public SettingsTests(ConfigFixture fixture) : base(fixture) 85 | { 86 | } 87 | } 88 | 89 | [Collection(CollectionFixtureName)] 90 | public class TaskInfoTests : TaskInfoTests 91 | { 92 | public TaskInfoTests(ConfigFixture fixture) : base(fixture) 93 | { 94 | } 95 | } 96 | 97 | [Collection(CollectionFixtureName)] 98 | public class TenantTokenTests : TenantTokenTests 99 | { 100 | public TenantTokenTests(ConfigFixture fixture) : base(fixture) 101 | { 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/ServerConfigs/ProxiedUriServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Xunit; 4 | 5 | namespace Meilisearch.Tests.ServerConfigs 6 | { 7 | public class ProxiedUriServer 8 | { 9 | const string CollectionFixtureName = nameof(ProxiedUriServer); 10 | private const string MeilisearchTestAddress = "http://localhost:8080/api/"; 11 | 12 | public class ConfigFixture : IndexFixture 13 | { 14 | public override string MeilisearchAddress() 15 | { 16 | return Environment.GetEnvironmentVariable("PROXIED_MEILISEARCH") ?? MeilisearchTestAddress; 17 | } 18 | } 19 | 20 | [CollectionDefinition(CollectionFixtureName)] 21 | public class IndexCollection : ICollectionFixture 22 | { 23 | } 24 | 25 | [Collection(CollectionFixtureName)] 26 | public class DocumentTests : DocumentTests 27 | { 28 | public DocumentTests(ConfigFixture fixture) : base(fixture) 29 | { 30 | } 31 | } 32 | 33 | [Collection(CollectionFixtureName)] 34 | public class IndexTests : IndexTests 35 | { 36 | public IndexTests(ConfigFixture fixture) : base(fixture) 37 | { 38 | } 39 | } 40 | 41 | [Collection(CollectionFixtureName)] 42 | public class KeyTests : KeyTests 43 | { 44 | public KeyTests(ConfigFixture fixture) : base(fixture) 45 | { 46 | } 47 | } 48 | 49 | [Collection(CollectionFixtureName)] 50 | public class MeilisearchClientTests : MeilisearchClientTests 51 | { 52 | public MeilisearchClientTests(ConfigFixture fixture) : base(fixture) 53 | { 54 | } 55 | } 56 | 57 | [Collection(CollectionFixtureName)] 58 | public class SearchTests : SearchTests 59 | { 60 | public SearchTests(ConfigFixture fixture) : base(fixture) 61 | { 62 | } 63 | } 64 | 65 | [Collection(CollectionFixtureName)] 66 | public class MultiIndexSearchTests : MultiIndexSearchTests 67 | { 68 | public MultiIndexSearchTests(ConfigFixture fixture) : base(fixture) 69 | { 70 | } 71 | } 72 | 73 | [Collection(CollectionFixtureName)] 74 | public class FacetingSearchTests : FacetSearchTests 75 | { 76 | public FacetingSearchTests(ConfigFixture fixture) : base(fixture) 77 | { 78 | } 79 | } 80 | 81 | [Collection(CollectionFixtureName)] 82 | public class SettingsTests : SettingsTests 83 | { 84 | public SettingsTests(ConfigFixture fixture) : base(fixture) 85 | { 86 | } 87 | } 88 | 89 | [Collection(CollectionFixtureName)] 90 | public class TaskInfoTests : TaskInfoTests 91 | { 92 | public TaskInfoTests(ConfigFixture fixture) : base(fixture) 93 | { 94 | } 95 | } 96 | 97 | [Collection(CollectionFixtureName)] 98 | public class TenantTokenTests : TenantTokenTests 99 | { 100 | public TenantTokenTests(ConfigFixture fixture) : base(fixture) 101 | { 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Meilisearch/PaginatedSearchResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Wrapper for Search Results with finite pagination. 8 | /// 9 | /// Hit type. 10 | public class PaginatedSearchResult : ISearchable 11 | { 12 | /// 13 | /// Creates a new paginated search result of type 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// 26 | public PaginatedSearchResult( 27 | IReadOnlyCollection hits, 28 | int hitsPerPage, 29 | int page, 30 | int totalHits, 31 | int totalPages, 32 | IReadOnlyDictionary> facetDistribution, 33 | int processingTimeMs, 34 | string query, 35 | IReadOnlyDictionary> matchesPosition, 36 | IReadOnlyDictionary facetStats, 37 | string indexUid 38 | ) 39 | { 40 | Hits = hits; 41 | HitsPerPage = hitsPerPage; 42 | Page = page; 43 | TotalHits = totalHits; 44 | TotalPages = totalPages; 45 | FacetDistribution = facetDistribution; 46 | ProcessingTimeMs = processingTimeMs; 47 | Query = query; 48 | MatchesPosition = matchesPosition; 49 | FacetStats = facetStats; 50 | IndexUid = indexUid; 51 | } 52 | 53 | /// 54 | /// Number of documents each page. 55 | /// 56 | [JsonPropertyName("hitsPerPage")] 57 | public int HitsPerPage { get; } 58 | 59 | /// 60 | /// Number of documents to take. 61 | /// 62 | [JsonPropertyName("page")] 63 | public int Page { get; } 64 | 65 | /// 66 | /// Total number of documents' pages. 67 | /// 68 | [JsonPropertyName("totalPages")] 69 | public int TotalPages { get; } 70 | 71 | /// 72 | [JsonPropertyName("hits")] 73 | public IReadOnlyCollection Hits { get; } 74 | 75 | /// 76 | /// Gets the total number of hits returned by the search. 77 | /// 78 | [JsonPropertyName("totalHits")] 79 | public int TotalHits { get; } 80 | 81 | /// 82 | [JsonPropertyName("facetDistribution")] 83 | public IReadOnlyDictionary> FacetDistribution { get; } 84 | 85 | /// 86 | [JsonPropertyName("processingTimeMs")] 87 | public int ProcessingTimeMs { get; } 88 | 89 | /// 90 | [JsonPropertyName("query")] 91 | public string Query { get; } 92 | 93 | /// 94 | [JsonPropertyName("_matchesPosition")] 95 | public IReadOnlyDictionary> MatchesPosition { get; } 96 | 97 | /// 98 | [JsonPropertyName("facetStats")] 99 | public IReadOnlyDictionary FacetStats { get; } 100 | 101 | /// 102 | [JsonPropertyName("indexUid")] 103 | public string IndexUid { get; } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Meilisearch/Extensions/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace Meilisearch.Extensions 7 | { 8 | /// 9 | /// Meilisearch objects manipulation. 10 | /// 11 | internal static class ObjectExtensions 12 | { 13 | /// 14 | /// Transforms an Meilisearch object into a dictionary. 15 | /// 16 | /// Object to transform. 17 | /// Binding flags. 18 | /// Returns a dictionary. 19 | internal static IDictionary AsDictionary(this object source, BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) 20 | { 21 | return source.GetType().GetProperties(bindingAttr).Where(p => p.GetValue(source, null) != null).ToDictionary( 22 | propInfo => char.ToLowerInvariant(propInfo.Name[0]) + propInfo.Name.Substring(1), 23 | propInfo => propInfo.GetValue(source, null).ToString()); 24 | } 25 | 26 | /// 27 | /// Transforms any object fields into an URL encoded query string. 28 | /// 29 | /// Object to transform. 30 | /// Binding flags. 31 | /// Uri to prepend before query string. 32 | /// Returns an url encoded query string. 33 | internal static string ToQueryString(this object source, BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance, string uri = "") 34 | { 35 | var values = new List(); 36 | if (source != null) 37 | { 38 | foreach (var field in source.GetType().GetProperties(bindingAttr)) 39 | { 40 | var value = field.GetValue(source, null); 41 | var key = Uri.EscapeDataString(char.ToLowerInvariant(field.Name[0]) + field.Name.Substring(1)); 42 | 43 | if (value != null) 44 | { 45 | if (value is List stringValue) 46 | { 47 | values.Add(key + "=" + string.Join(",", stringValue)); 48 | } 49 | else if (value is List intValue) 50 | { 51 | values.Add(key + "=" + string.Join(",", intValue)); 52 | } 53 | else if (value is List taskInfoStatusValue) 54 | { 55 | values.Add(key + "=" + string.Join(",", taskInfoStatusValue.Select(x => x.ToString()))); 56 | } 57 | else if (value is List taskInfoTypeValue) 58 | { 59 | values.Add(key + "=" + string.Join(",", taskInfoTypeValue.Select(x => x.ToString()))); 60 | } 61 | else if (value is DateTime datetimeValue) 62 | { 63 | values.Add(key + "=" + Uri.EscapeDataString(datetimeValue.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz"))); 64 | } 65 | else 66 | { 67 | values.Add(key + "=" + Uri.EscapeDataString(value.ToString())); 68 | } 69 | } 70 | } 71 | } 72 | 73 | var queryString = string.Join("&", values); 74 | if (string.IsNullOrWhiteSpace(uri)) 75 | { 76 | return queryString; 77 | } 78 | 79 | if (string.IsNullOrEmpty(queryString)) 80 | { 81 | return uri; 82 | } 83 | 84 | return $"{uri}?{queryString}"; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Meilisearch/Settings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Setttings of an index. 8 | /// 9 | public class Settings 10 | { 11 | /// 12 | /// Gets or sets the ranking rules. 13 | /// 14 | [JsonPropertyName("rankingRules")] 15 | public IEnumerable RankingRules { get; set; } 16 | 17 | /// 18 | /// Gets or sets the distinct attribute. 19 | /// 20 | [JsonPropertyName("distinctAttribute")] 21 | public string DistinctAttribute { get; set; } 22 | 23 | /// 24 | /// Gets or sets the searchable attributes. 25 | /// 26 | [JsonPropertyName("searchableAttributes")] 27 | public IEnumerable SearchableAttributes { get; set; } 28 | 29 | /// 30 | /// Gets or sets the displayed attributes. 31 | /// 32 | [JsonPropertyName("displayedAttributes")] 33 | public IEnumerable DisplayedAttributes { get; set; } 34 | 35 | /// 36 | /// Gets or sets the stop-words list. 37 | /// 38 | [JsonPropertyName("stopWords")] 39 | public IEnumerable StopWords { get; set; } 40 | 41 | /// 42 | /// Gets or sets the synonyms list. 43 | /// 44 | [JsonPropertyName("synonyms")] 45 | public Dictionary> Synonyms { get; set; } 46 | 47 | /// 48 | /// Gets or sets the non separator tokens list. 49 | /// 50 | [JsonPropertyName("nonSeparatorTokens")] 51 | public List NonSeparatorTokens { get; set; } 52 | 53 | /// 54 | /// Gets or sets the separator tokens list. 55 | /// 56 | [JsonPropertyName("separatorTokens")] 57 | public List SeparatorTokens { get; set; } 58 | 59 | /// 60 | /// Gets or sets the filterable attributes. 61 | /// 62 | [JsonPropertyName("filterableAttributes")] 63 | public IEnumerable FilterableAttributes { get; set; } 64 | 65 | /// 66 | /// Gets or sets the sortable attributes. 67 | /// 68 | [JsonPropertyName("sortableAttributes")] 69 | public IEnumerable SortableAttributes { get; set; } 70 | 71 | /// 72 | /// Gets or sets the typo tolerance attributes. 73 | /// 74 | [JsonPropertyName("typoTolerance")] 75 | public TypoTolerance TypoTolerance { get; set; } 76 | 77 | /// 78 | /// Gets or sets the faceting attributes. 79 | /// 80 | [JsonPropertyName("faceting")] 81 | public Faceting Faceting { get; set; } 82 | 83 | /// 84 | /// Gets or sets the pagination attributes. 85 | /// 86 | [JsonPropertyName("pagination")] 87 | public Pagination Pagination { get; set; } 88 | 89 | /// 90 | /// Gets or sets the dictionary object. 91 | /// 92 | [JsonPropertyName("dictionary")] 93 | public IEnumerable Dictionary { get; set; } 94 | 95 | /// 96 | /// Gets or sets the proximity precision attribute. 97 | /// 98 | [JsonPropertyName("proximityPrecision")] 99 | public string ProximityPrecision { get; set; } 100 | 101 | /// 102 | /// Gets or sets the searchCutoffMs attribute. 103 | /// 104 | [JsonPropertyName("searchCutoffMs")] 105 | public int? SearchCutoffMs { get; set; } 106 | 107 | /// 108 | /// Gets or sets the embeddings attribute. 109 | /// 110 | [JsonPropertyName("embedders")] 111 | public Dictionary Embedders { get; set; } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Meilisearch/Meilisearch.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | Library 6 | MeiliSearch 7 | 0.18.0 8 | .NET wrapper for Meilisearch, an open-source search engine 9 | https://github.com/meilisearch/meilisearch-dotnet 10 | meilisearch;dotnet;sdk;search-engine;search;instant-search 11 | Meilisearch 12 | https://meilisearch.com 13 | logo.png 14 | README.md 15 | MIT 16 | True 17 | 18 | 19 | $(NoWarn);1591 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | <_Parameter1>Meilisearch.Tests 37 | 38 | 39 | 40 | 41 | Index.cs 42 | 43 | 44 | Index.cs 45 | 46 | 47 | Index.cs 48 | 49 | 50 | Index.cs 51 | 52 | 53 | Index.cs 54 | 55 | 56 | Index.cs 57 | 58 | 59 | Index.cs 60 | 61 | 62 | Index.cs 63 | 64 | 65 | Index.cs 66 | 67 | 68 | Index.cs 69 | 70 | 71 | Index.cs 72 | 73 | 74 | Index.cs 75 | 76 | 77 | Index.cs 78 | 79 | 80 | Index.cs 81 | 82 | 83 | Index.cs 84 | 85 | 86 | Index.cs 87 | 88 | 89 | Index.cs 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/Meilisearch/TaskInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | 5 | using Meilisearch.Converters; 6 | 7 | namespace Meilisearch 8 | { 9 | /// 10 | /// Information of the regarding a task. 11 | /// 12 | public class TaskInfo 13 | { 14 | public TaskInfo(int taskUid, string indexUid, TaskInfoStatus status, TaskInfoType type, 15 | IReadOnlyDictionary details, IReadOnlyDictionary error, string duration, DateTime enqueuedAt, 16 | DateTime? startedAt, DateTime? finishedAt) 17 | { 18 | TaskUid = taskUid; 19 | IndexUid = indexUid; 20 | Status = status; 21 | Type = type; 22 | Details = details; 23 | Error = error; 24 | Duration = duration; 25 | EnqueuedAt = enqueuedAt; 26 | StartedAt = startedAt; 27 | FinishedAt = finishedAt; 28 | } 29 | 30 | /// 31 | /// The unique sequential identifier of the task. 32 | /// 33 | [JsonPropertyName("taskUid")] 34 | public int TaskUid { get; } 35 | 36 | /// 37 | /// The unique index identifier. 38 | /// 39 | [JsonPropertyName("indexUid")] 40 | public string IndexUid { get; } 41 | 42 | /// 43 | /// The status of the task. Possible values are enqueued, processing, succeeded, failed. 44 | /// 45 | [JsonPropertyName("status")] 46 | public TaskInfoStatus Status { get; } 47 | 48 | /// 49 | /// The type of task. 50 | /// 51 | [JsonPropertyName("type")] 52 | public TaskInfoType Type { get; } 53 | 54 | /// 55 | /// Detailed information on the task payload. 56 | /// 57 | [JsonPropertyName("details")] 58 | public IReadOnlyDictionary Details { get; } 59 | 60 | /// 61 | /// Error details and context. Only present when a task has the failed status. 62 | /// 63 | [JsonPropertyName("error")] 64 | public IReadOnlyDictionary Error { get; } 65 | 66 | /// 67 | /// The total elapsed time the task spent in the processing state, in ISO 8601 format. 68 | /// 69 | [JsonPropertyName("duration")] 70 | public string Duration { get; } 71 | 72 | /// 73 | /// The date and time when the task was first enqueued, in RFC 3339 format. 74 | /// 75 | [JsonPropertyName("enqueuedAt")] 76 | public DateTime EnqueuedAt { get; } 77 | 78 | /// 79 | /// The date and time when the task began processing, in RFC 3339 format. 80 | /// 81 | [JsonPropertyName("startedAt")] 82 | public DateTime? StartedAt { get; } 83 | 84 | /// 85 | /// The date and time when the task finished processing, whether failed or succeeded, in RFC 3339 format. 86 | /// 87 | [JsonPropertyName("finishedAt")] 88 | public DateTime? FinishedAt { get; } 89 | 90 | /// 91 | /// A taskUid who canceled the current task. 92 | /// 93 | [JsonPropertyName("canceledBy")] 94 | public int? CanceledBy { get; } 95 | } 96 | 97 | [JsonConverter(typeof(JsonStringEnumConverter))] 98 | public enum TaskInfoStatus 99 | { 100 | Enqueued, 101 | Processing, 102 | Succeeded, 103 | Failed, 104 | Canceled 105 | } 106 | 107 | [JsonConverter(typeof(TaskInfoTypeConverter))] 108 | public enum TaskInfoType 109 | { 110 | IndexCreation, 111 | IndexUpdate, 112 | IndexDeletion, 113 | DocumentAdditionOrUpdate, 114 | DocumentDeletion, 115 | SettingsUpdate, 116 | DumpCreation, 117 | TaskCancelation, 118 | SnapshotCreation, 119 | TaskDeletion, 120 | IndexSwap, 121 | Unknown 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/TaskInfoTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | using FluentAssertions; 6 | 7 | using Meilisearch.QueryParameters; 8 | 9 | using Xunit; 10 | 11 | namespace Meilisearch.Tests 12 | { 13 | public abstract class TaskInfoTests : IAsyncLifetime where TFixture : IndexFixture 14 | { 15 | private readonly MeilisearchClient _client; 16 | private Index _index; 17 | private readonly TFixture _fixture; 18 | 19 | public TaskInfoTests(TFixture fixture) 20 | { 21 | _fixture = fixture; 22 | _client = fixture.DefaultClient; 23 | } 24 | 25 | public async Task InitializeAsync() 26 | { 27 | await _fixture.DeleteAllIndexes(); // Test context cleaned for each [Fact] 28 | _index = await _fixture.SetUpBasicIndex("BasicIndex-TaskInfoTests"); 29 | } 30 | 31 | public Task DisposeAsync() => Task.CompletedTask; 32 | 33 | [Fact] 34 | public async Task GetMultipleTaskInfoFromClient() 35 | { 36 | await _index.AddDocumentsAsync(new[] { new Movie { Id = "1" } }); 37 | var taskResponse = await _client.GetTasksAsync(); 38 | var tasks = taskResponse.Results; 39 | tasks.Count().Should().BeGreaterOrEqualTo(1); 40 | } 41 | 42 | [Fact] 43 | public async Task GetMultipleTaskInfoFromIndex() 44 | { 45 | await _index.AddDocumentsAsync(new[] { new Movie { Id = "1" } }); 46 | var taskResponse = await _index.GetTasksAsync(); 47 | var tasks = taskResponse.Results.Where(t => t.IndexUid != _index.Uid); 48 | 49 | taskResponse.Results.Count().Should().BeGreaterOrEqualTo(1); 50 | Assert.Empty(tasks); 51 | } 52 | 53 | [Fact] 54 | public async Task GetMultipleTaskInfoWithLimitFromClient() 55 | { 56 | await _index.AddDocumentsAsync(new[] { new Movie { Id = "1" } }); 57 | var tasks = await _client.GetTasksAsync(new TasksQuery { Limit = 1 }); 58 | 59 | tasks.Results.Count().Should().BeGreaterOrEqualTo(1); 60 | Assert.Equal(1, tasks.Limit); 61 | } 62 | 63 | [Fact] 64 | public async Task GetMultipleTaskInfoWithQueryParameters() 65 | { 66 | await _index.AddDocumentsAsync(new[] { new Movie { Id = "1" } }); 67 | var taskResponse = await _index.GetTasksAsync(new TasksQuery { Limit = 1, IndexUids = new List { _index.Uid } }); 68 | 69 | taskResponse.Results.Count().Should().BeGreaterOrEqualTo(1); 70 | taskResponse.Total.Should().BeGreaterThan(0); 71 | } 72 | 73 | [Fact] 74 | public async Task GetOneTaskInfo() 75 | { 76 | var task = await _index.AddDocumentsAsync(new[] { new Movie { Id = "2" } }); 77 | var fetchedTask = await _index.GetTaskAsync(task.TaskUid); 78 | fetchedTask.Should().NotBeNull(); 79 | fetchedTask.Uid.Should().BeGreaterOrEqualTo(0); 80 | } 81 | 82 | [Fact] 83 | public async Task DefaultWaitForTask() 84 | { 85 | var task = await _index.AddDocumentsAsync(new[] { new Movie { Id = "3" } }); 86 | var finishedTask = await _index.WaitForTaskAsync(task.TaskUid); 87 | Assert.Equal(finishedTask.Uid, task.TaskUid); 88 | Assert.Equal(TaskInfoStatus.Succeeded, finishedTask.Status); 89 | } 90 | 91 | [Fact] 92 | public async Task CustomWaitForTask() 93 | { 94 | var task = await _index.AddDocumentsAsync(new[] { new Movie { Id = "4" } }); 95 | var finishedTask = await _index.WaitForTaskAsync(task.TaskUid, 10000.0, 20); 96 | Assert.Equal(finishedTask.Uid, task.TaskUid); 97 | Assert.Equal(TaskInfoStatus.Succeeded, finishedTask.Status); 98 | } 99 | 100 | [Fact] 101 | public async Task WaitForTaskWithException() 102 | { 103 | var task = await _index.AddDocumentsAsync(new[] { new Movie { Id = "5" } }); 104 | await Assert.ThrowsAsync(() => _index.WaitForTaskAsync(task.TaskUid, 0.0, 20)); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Meilisearch/SearchQueryBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Meilisearch 5 | { 6 | /// 7 | /// Base properties of search query 8 | /// 9 | public class SearchQueryBase 10 | { 11 | /// 12 | /// The id of the index 13 | /// 14 | [JsonPropertyName("indexUid")] 15 | public string IndexUid { get; set; } 16 | 17 | /// 18 | /// Gets or sets query string. 19 | /// 20 | [JsonPropertyName("q")] 21 | public string Q { get; set; } 22 | 23 | /// 24 | /// Gets or sets the filter to apply to the query. 25 | /// 26 | [JsonPropertyName("filter")] 27 | public dynamic Filter { get; set; } 28 | 29 | /// 30 | /// Gets or sets attributes to retrieve. 31 | /// 32 | [JsonPropertyName("attributesToRetrieve")] 33 | public IEnumerable AttributesToRetrieve { get; set; } 34 | 35 | /// 36 | /// Gets or sets attributes to crop. 37 | /// 38 | [JsonPropertyName("attributesToCrop")] 39 | public IEnumerable AttributesToCrop { get; set; } 40 | 41 | /// 42 | /// Gets or sets attributes to search on. 43 | /// 44 | [JsonPropertyName("attributesToSearchOn")] 45 | public IEnumerable AttributesToSearchOn { get; set; } 46 | 47 | /// 48 | /// Gets or sets length used to crop field values. 49 | /// 50 | [JsonPropertyName("cropLength")] 51 | public int? CropLength { get; set; } 52 | 53 | /// 54 | /// Gets or sets attributes to highlight. 55 | /// 56 | [JsonPropertyName("attributesToHighlight")] 57 | public IEnumerable AttributesToHighlight { get; set; } 58 | 59 | /// 60 | /// Gets or sets the crop marker to apply before and/or after cropped part selected within an attribute defined in `attributesToCrop` parameter. 61 | /// 62 | [JsonPropertyName("cropMarker")] 63 | public string CropMarker { get; set; } 64 | 65 | /// 66 | /// Gets or sets the tag to put before the highlighted query terms. 67 | /// 68 | [JsonPropertyName("highlightPreTag")] 69 | public string HighlightPreTag { get; set; } 70 | 71 | /// 72 | /// Gets or sets the tag to put after the highlighted query terms. 73 | /// 74 | [JsonPropertyName("highlightPostTag")] 75 | public string HighlightPostTag { get; set; } 76 | 77 | /// 78 | /// Gets or sets the facets for the query. 79 | /// 80 | [JsonPropertyName("facets")] 81 | public IEnumerable Facets { get; set; } 82 | 83 | /// 84 | /// Gets or sets showMatchesPosition. It defines whether an object that contains information about the matches should be returned or not. 85 | /// 86 | [JsonPropertyName("showMatchesPosition")] 87 | public bool? ShowMatchesPosition { get; set; } 88 | 89 | /// 90 | /// Gets or sets the sorted attributes. 91 | /// 92 | [JsonPropertyName("sort")] 93 | public IEnumerable Sort { get; set; } 94 | 95 | /// 96 | /// Gets or sets the words matching strategy. 97 | /// 98 | [JsonPropertyName("matchingStrategy")] 99 | public string MatchingStrategy { get; set; } 100 | 101 | /// 102 | /// Gets or sets showRankingScore parameter. It defines whether the global ranking score of a document (between 0 and 1) is returned or not. 103 | /// 104 | [JsonPropertyName("showRankingScore")] 105 | public bool? ShowRankingScore { get; set; } 106 | 107 | /// 108 | /// Gets or sets showRankingScoreDetails parameter. It defines whether details on how the ranking score was computed are returned or not. 109 | /// 110 | [JsonPropertyName("showRankingScoreDetails")] 111 | public bool? ShowRankingScoreDetails { get; set; } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Meilisearch/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace Meilisearch.Extensions 7 | { 8 | internal static class StringExtensions 9 | { 10 | /// 11 | /// This method makes sure that the uri has a trailing slash. 12 | /// If not, it will append one silently. 13 | /// 14 | /// uri to Meilisearch server. 15 | /// A well formatted Uri 16 | /// Thrown when uri is not or whitespace. 17 | internal static Uri ToSafeUri(this string uri) 18 | { 19 | if (string.IsNullOrWhiteSpace(uri)) 20 | { 21 | throw new ArgumentNullException(nameof(uri)); 22 | } 23 | 24 | var trimmed = uri.Trim(); 25 | return trimmed.EndsWith("/") ? new Uri(trimmed) : new Uri($"{trimmed}/"); 26 | } 27 | 28 | /// 29 | /// Returns chunks from a CSV string. 30 | /// 31 | /// The CSV string to split. 32 | /// Size of the chunks. 33 | /// List of CSV string. 34 | /// Thrown if csvString is null. 35 | /// Throw if chunkSize is lower than 1. 36 | internal static IEnumerable GetCsvChunks(this string csvString, int chunkSize) 37 | { 38 | if (string.IsNullOrWhiteSpace(csvString)) 39 | { 40 | throw new ArgumentNullException(nameof(csvString)); 41 | } 42 | 43 | if (chunkSize < 1) 44 | { 45 | throw new ArgumentException("chunkSize value must be greater than 0", nameof(chunkSize)); 46 | } 47 | 48 | using (var sr = new StringReader(csvString)) 49 | { 50 | // We extract the CSV header on first line 51 | var csvHeader = sr.ReadLine(); 52 | 53 | var sb = new StringBuilder(); 54 | // We add the CSV header first on our chunck 55 | sb.AppendLine(csvHeader); 56 | var line = ""; 57 | var lineNumber = 0; 58 | while ((line = sr.ReadLine()) != null) 59 | { 60 | sb.AppendLine(line); 61 | ++lineNumber; 62 | 63 | if (lineNumber % chunkSize == 0) 64 | { 65 | // We return our chunk, we clear our string builder and add back on first line the CSV header 66 | yield return sb.ToString(); 67 | sb.Clear(); 68 | sb.AppendLine(csvHeader); 69 | } 70 | } 71 | 72 | // After the last line we check if we have something to send 73 | if (lineNumber % chunkSize != 0) 74 | { 75 | yield return sb.ToString(); 76 | } 77 | } 78 | } 79 | 80 | /// 81 | /// Returns chunks from a NDJSON string. 82 | /// 83 | /// The NDJSON string to split. 84 | /// Size of the chunks. 85 | /// List of NDJSON string. 86 | /// Thrown if ndjsonString is null. 87 | /// Throw if chunkSize is lower than 1. 88 | internal static IEnumerable GetNdjsonChunks(this string ndjsonString, int chunkSize) 89 | { 90 | if (string.IsNullOrWhiteSpace(ndjsonString)) 91 | { 92 | throw new ArgumentNullException(nameof(ndjsonString)); 93 | } 94 | 95 | if (chunkSize < 1) 96 | { 97 | throw new ArgumentException("chunkSize value must be greater than 0", nameof(chunkSize)); 98 | } 99 | 100 | using (var sr = new StringReader(ndjsonString)) 101 | { 102 | var sb = new StringBuilder(); 103 | var line = ""; 104 | var lineNumber = 0; 105 | while ((line = sr.ReadLine()) != null) 106 | { 107 | sb.AppendLine(line); 108 | ++lineNumber; 109 | 110 | if (lineNumber % chunkSize == 0) 111 | { 112 | // We return our chunk, we clear our string builder 113 | yield return sb.ToString(); 114 | sb.Clear(); 115 | } 116 | } 117 | 118 | // After the last line we check if we have something to send 119 | if (lineNumber % chunkSize != 0) 120 | { 121 | yield return sb.ToString(); 122 | } 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Meilisearch/Extensions/HttpExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Net.Http.Headers; 4 | using System.Net.Http.Json; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Meilisearch.Extensions 11 | { 12 | /// 13 | /// Class to communicate with the Meilisearch server without charset-utf-8 as Content-Type. 14 | /// 15 | internal static class HttpExtensions 16 | { 17 | /// 18 | /// Sends JSON payload using POST without "charset-utf-8" as Content-Type. 19 | /// 20 | /// HttpClient. 21 | /// Endpoint. 22 | /// Body sent. 23 | /// The cancellation token for this call. 24 | /// Type of the body to send. 25 | /// Returns the HTTP response from the Meilisearch server. 26 | internal static async Task PostJsonCustomAsync(this HttpClient client, string uri, T body, CancellationToken cancellationToken = default) 27 | { 28 | var payload = PrepareJsonPayload(body); 29 | 30 | return await client.PostAsync(uri, payload, cancellationToken).ConfigureAwait(false); 31 | } 32 | 33 | /// 34 | /// Sends JSON payload using POST without "charset-utf-8" as Content-Type and using JSON serializer options. 35 | /// 36 | /// HttpClient. 37 | /// Endpoint. 38 | /// Body sent. 39 | /// Json options for serialization. 40 | /// The cancellation token for this call. 41 | /// Type of the body to send. 42 | /// Returns the HTTP response from the Meilisearch server. 43 | internal static async Task PostJsonCustomAsync(this HttpClient client, string uri, T body, JsonSerializerOptions options, CancellationToken cancellationToken = default) 44 | { 45 | var payload = PrepareJsonPayload(body, options); 46 | 47 | return await client.PostAsync(uri, payload, cancellationToken).ConfigureAwait(false); 48 | } 49 | 50 | /// 51 | /// Sends JSON payload using PUT without "charset-utf-8" as Content-Type. 52 | /// 53 | /// HttpClient. 54 | /// Endpoint. 55 | /// Body sent. 56 | /// Json options for serialization. 57 | /// The cancellation token for this call. 58 | /// Type of the body to send. 59 | /// Returns the HTTP response from the Meilisearch server. 60 | internal static async Task PutJsonCustomAsync(this HttpClient client, string uri, T body, JsonSerializerOptions options, CancellationToken cancellationToken = default) 61 | { 62 | var payload = PrepareJsonPayload(body, options); 63 | 64 | return await client.PutAsync(uri, payload, cancellationToken).ConfigureAwait(false); 65 | } 66 | 67 | /// 68 | /// Add the API Key to the Authorization header. 69 | /// 70 | /// HttpClient. 71 | /// API Key. 72 | internal static void AddApiKeyToHeader(this HttpClient client, string apiKey) 73 | { 74 | if (!string.IsNullOrEmpty(apiKey)) 75 | { 76 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); 77 | } 78 | } 79 | 80 | internal static void AddDefaultUserAgent(this HttpClient client) 81 | { 82 | var version = new Version(); 83 | 84 | client.DefaultRequestHeaders.Add("User-Agent", version.GetQualifiedVersion()); 85 | } 86 | 87 | private static JsonContent PrepareJsonPayload(T body, JsonSerializerOptions options = null) 88 | { 89 | options = options ?? Constants.JsonSerializerOptionsWriteNulls; 90 | var payload = JsonContent.Create(body, new MediaTypeHeaderValue("application/json"), options); 91 | 92 | return payload; 93 | } 94 | 95 | private static Task PatchAsync(this HttpClient client, string requestUri, HttpContent content, CancellationToken cancellationToken) 96 | { 97 | var uri = new Uri(requestUri, UriKind.RelativeOrAbsolute); 98 | return client.PatchAsync(uri, content, cancellationToken); 99 | } 100 | 101 | private static Task PatchAsync(this HttpClient client, Uri requestUri, HttpContent content, CancellationToken cancellationToken) 102 | { 103 | // HttpClient.PatchAsync is not available in .NET standard and NET462 104 | var method = new HttpMethod("PATCH"); 105 | var request = new HttpRequestMessage(method, requestUri) { Content = content }; 106 | return client.SendAsync(request, cancellationToken); 107 | } 108 | 109 | internal static Task PatchAsJsonAsync(this HttpClient client, string requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default) 110 | { 111 | var content = JsonContent.Create(value, mediaType: null, options); 112 | return client.PatchAsync(requestUri, content, cancellationToken); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tests/Meilisearch.Tests/MultiIndexSearchTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text.Json; 4 | using System.Threading.Tasks; 5 | 6 | using FluentAssertions; 7 | 8 | using Xunit; 9 | 10 | namespace Meilisearch.Tests 11 | { 12 | public abstract class MultiIndexSearchTests : IAsyncLifetime 13 | where TFixture : IndexFixture 14 | { 15 | private Index _index1; 16 | private Index _index2; 17 | 18 | private readonly TFixture _fixture; 19 | 20 | public MultiIndexSearchTests(TFixture fixture) 21 | { 22 | _fixture = fixture; 23 | } 24 | 25 | public Task DisposeAsync() => Task.CompletedTask; 26 | 27 | public async Task InitializeAsync() 28 | { 29 | await _fixture.DeleteAllIndexes(); // Test context cleaned for each [Fact] 30 | _index1 = await _fixture.SetUpBasicIndex("BasicIndex-MultiSearch-Index1"); 31 | _index2 = await _fixture.SetUpBasicIndex("BasicIndex-MultiSearch-Index2"); 32 | var t1 = _index1.UpdateFilterableAttributesAsync(new[] { "genre" }); 33 | var t2 = _index2.UpdateFilterableAttributesAsync(new[] { "genre" }); 34 | await Task.WhenAll( 35 | (await Task.WhenAll(t1, t2)).Select(x => _fixture.DefaultClient.WaitForTaskAsync(x.TaskUid))); 36 | } 37 | 38 | [Fact] 39 | public async Task BasicSearch() 40 | { 41 | var result = await _fixture.DefaultClient.MultiSearchAsync(new MultiSearchQuery() 42 | { 43 | Queries = new System.Collections.Generic.List() 44 | { 45 | new SearchQuery() { IndexUid = _index1.Uid, Q = "", Filter = "genre = 'SF'" }, 46 | new SearchQuery() { IndexUid = _index2.Uid, Q = "", Filter = "genre = 'Action'" } 47 | } 48 | }); 49 | 50 | 51 | Movie GetMovie(IEnumerable movies, string id) 52 | { 53 | return movies.FirstOrDefault(x => x.Id == id); 54 | } 55 | 56 | var original1 = await _index1.GetDocumentsAsync(); 57 | var originalHits1 = original1.Results; 58 | result.Results.Should().HaveCount(2); 59 | var res1 = result.Results[0]; 60 | res1.IndexUid.Should().Be(_index1.Uid); 61 | var res1Hits = res1.Hits.Select(x => x.Deserialize(Constants.JsonSerializerOptionsWriteNulls)); 62 | res1Hits.Should().HaveCount(2); 63 | res1Hits.All(x => 64 | { 65 | var og = GetMovie(originalHits1, x.Id); 66 | return og.Name == x.Name && og.Genre == x.Genre; 67 | }).Should().BeTrue(); 68 | 69 | var original2 = await _index2.GetDocumentsAsync(); 70 | var originalHits2 = original2.Results.ToList(); 71 | var res2 = result.Results[1]; 72 | var res2Hits = res2.Hits.Select(x => x.Deserialize(Constants.JsonSerializerOptionsWriteNulls)); 73 | res2Hits.Should().HaveCount(2); 74 | res2.IndexUid.Should().Be(_index2.Uid); 75 | res1Hits.All(x => 76 | { 77 | var og = GetMovie(originalHits2, x.Id); 78 | return og.Name == x.Name && og.Genre == x.Genre; 79 | }).Should().BeTrue(); 80 | } 81 | 82 | 83 | [Fact] 84 | public async Task FederatedSearchWithNoFederationOptions() 85 | { 86 | var result = await _fixture.DefaultClient.FederatedMultiSearchAsync( 87 | new FederatedMultiSearchQuery() 88 | { 89 | Queries = new List() 90 | { 91 | new FederatedSearchQuery() { IndexUid = _index1.Uid, Q = "", Filter = "genre = 'SF'" }, 92 | new FederatedSearchQuery() 93 | { 94 | IndexUid = _index2.Uid, Q = "", Filter = "genre = 'Action'" 95 | } 96 | }, 97 | }); 98 | 99 | result.Hits.Should().HaveCount(4); 100 | } 101 | 102 | [Fact] 103 | public async Task FederatedSearchWithEmptyOptions() 104 | { 105 | var result = await _fixture.DefaultClient.FederatedMultiSearchAsync( 106 | new FederatedMultiSearchQuery() 107 | { 108 | Queries = new List() 109 | { 110 | new FederatedSearchQuery() { IndexUid = _index1.Uid, Q = "", Filter = "genre = 'SF'" }, 111 | new FederatedSearchQuery() 112 | { 113 | IndexUid = _index2.Uid, Q = "", Filter = "genre = 'Action'" 114 | } 115 | }, 116 | FederationOptions = new MultiSearchFederationOptions() { } 117 | }); 118 | 119 | result.Hits.Should().HaveCount(4); 120 | } 121 | 122 | [Fact] 123 | public async Task FederatedSearchWithLimitAndOffset() 124 | { 125 | var federatedquer = new FederatedMultiSearchQuery() 126 | { 127 | Queries = new List() 128 | { 129 | new FederatedSearchQuery() { IndexUid = _index1.Uid, Q = "", Filter = "genre = 'SF'" }, 130 | new FederatedSearchQuery() { IndexUid = _index2.Uid, Q = "", Filter = "genre = 'Action'" } 131 | }, 132 | FederationOptions = new MultiSearchFederationOptions() { Limit = 2, Offset = 0 } 133 | }; 134 | var result = await _fixture.DefaultClient.FederatedMultiSearchAsync(federatedquer 135 | ); 136 | 137 | var testJson = JsonSerializer.Serialize(federatedquer); 138 | 139 | result.Hits.Should().HaveCount(2); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Meilisearch/TaskEndpoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Net.Http.Json; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | using Meilisearch.Extensions; 9 | using Meilisearch.QueryParameters; 10 | 11 | namespace Meilisearch 12 | { 13 | 14 | /// 15 | /// Meilisearch index to search and manage documents. 16 | /// 17 | public class TaskEndpoint 18 | { 19 | private HttpClient _http; 20 | 21 | /// 22 | /// Gets the tasks. 23 | /// 24 | /// Query parameters supports by the method. 25 | /// The cancellation token for this call. 26 | /// Returns a list of the tasks. 27 | public async Task>> GetTasksAsync(TasksQuery query = default, CancellationToken cancellationToken = default) 28 | { 29 | var uri = query.ToQueryString(uri: "tasks"); 30 | return await _http.GetFromJsonAsync>>(uri, cancellationToken: cancellationToken) 31 | .ConfigureAwait(false); 32 | } 33 | 34 | /// 35 | /// Cancel tasks given a specific query. 36 | /// 37 | /// Query parameters supports by the method. 38 | /// The cancellation token for this call. 39 | /// Returns a list of the tasks. 40 | public async Task CancelTasksAsync(CancelTasksQuery query, CancellationToken cancellationToken = default) 41 | { 42 | var uri = query.ToQueryString(uri: "tasks/cancel"); 43 | 44 | var response = await _http.PostAsync(uri, null, cancellationToken: cancellationToken).ConfigureAwait(false); 45 | 46 | return await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 47 | } 48 | 49 | /// 50 | /// Delete tasks given a specific query. 51 | /// 52 | /// Query parameters supports by the method. 53 | /// The cancellation token for this call. 54 | /// Returns a list of the tasks. 55 | public async Task DeleteTasksAsync(DeleteTasksQuery query, CancellationToken cancellationToken = default) 56 | { 57 | var uri = query.ToQueryString(uri: "tasks"); 58 | 59 | var response = await _http.DeleteAsync(uri, cancellationToken).ConfigureAwait(false); 60 | 61 | return await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false); 62 | } 63 | 64 | /// 65 | /// Gets one task. 66 | /// 67 | /// Uid of the index. 68 | /// The cancellation token for this call. 69 | /// Returns the task. 70 | public async Task GetTaskAsync(int taskUid, CancellationToken cancellationToken = default) 71 | { 72 | return await _http.GetFromJsonAsync($"tasks/{taskUid}", cancellationToken: cancellationToken) 73 | .ConfigureAwait(false); 74 | } 75 | 76 | /// 77 | /// Gets the tasks from an index. 78 | /// 79 | /// Uid of the index. 80 | /// The cancellation token for this call. 81 | /// Returns a list of tasks of an index. 82 | public async Task>> GetIndexTasksAsync(string indexUid, CancellationToken cancellationToken = default) 83 | { 84 | return await _http.GetFromJsonAsync>>($"tasks?indexUid={indexUid}", cancellationToken: cancellationToken) 85 | .ConfigureAwait(false); 86 | } 87 | 88 | /// 89 | /// Waits until the asynchronous task was done. 90 | /// 91 | /// Unique identifier of the asynchronous task. 92 | /// Timeout in millisecond. 93 | /// Interval in millisecond between each check. 94 | /// The cancellation token for this call. 95 | /// Returns the task info of finished task. 96 | public async Task WaitForTaskAsync( 97 | int taskUid, 98 | double timeoutMs = 5000.0, 99 | int intervalMs = 50, 100 | CancellationToken cancellationToken = default) 101 | { 102 | var endingTime = DateTime.Now.AddMilliseconds(timeoutMs); 103 | 104 | while (DateTime.Now < endingTime) 105 | { 106 | var task = await GetTaskAsync(taskUid, cancellationToken).ConfigureAwait(false); 107 | 108 | if (task.Status != TaskInfoStatus.Enqueued && task.Status != TaskInfoStatus.Processing) 109 | { 110 | return task; 111 | } 112 | 113 | await Task.Delay(intervalMs, cancellationToken).ConfigureAwait(false); 114 | } 115 | 116 | throw new MeilisearchTimeoutError("The task " + taskUid.ToString() + " timed out."); 117 | } 118 | 119 | /// 120 | /// Initializes the Index with HTTP client. Only for internal usage. 121 | /// 122 | /// HttpRequest instance used. 123 | /// The same object with the initialization. 124 | // internal Index WithHttpClient(HttpClient client) 125 | internal TaskEndpoint WithHttpClient(HttpClient http) 126 | { 127 | _http = http; 128 | return this; 129 | } 130 | } 131 | } 132 | --------------------------------------------------------------------------------