📄 GitHubSearchProvider.cs
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Runtime.CompilerServices;
using System.Threading;
using MSearch.Domain;

namespace MSearch.SearchProviders.GitHub;

internal sealed class GitHubSearchProvider(HttpClient httpClient, string type) : ISearchProvider
{
    public async IAsyncEnumerable<SearchResult> Search(
        SearchQuery query,
        [EnumeratorCancellation] CancellationToken cancellationToken
    )
    {
        var response = await httpClient.GetAsync(
            $"search/{type}?q={Uri.EscapeDataString(query.Term)}",
            cancellationToken
        );
        if (response.StatusCode is HttpStatusCode.NotFound)
        {
            yield break;
        }
        var data = await response.Content.ReadFromJsonAsync(
            GitHubJsonSerializerContext.Default.GithubResponse,
            cancellationToken
        );
        if (data is null)
        {
            yield break;
        }
        foreach (var item in data.Items)
        {
            if (Map(item) is { } result)
            {
                yield return result;
            }
        }
    }

    private static SearchResult? Map(GitHubItem item) =>
        (item.Name ?? item.Title) is string title && (item.Description ?? item.Body) is string summary
            ? new(title, summary, new(item.Url))
            : null;
}