アジョブジ星通信

日常系バンザイ。

SearchGalleryQueryService と NuGet.Services.Search.Client

https://api.nuget.org/v3/index.json に書いてはあるけど実態がよくわかっていなかった SearchGalleryQueryService についての話。以前の検索 API の話と同じくソースコードNuGet.Services.Search にあります。

で、 SearchQueryService と SearchGalleryQueryService の違いですが、前者は NuGet.Services.BasicSearch、後者は NuGet.Services.Search というプロジェクト名になっています。指定できるパラメータやレスポンスに違いはありますが、検索システム自体は同じものを使っています。

SearchGalleryQueryService 下のエンドポイント

index.json によると、公式の SearchGalleryQueryService は https://api-search.nuget.org/ で公開されています。その JSON を見てみると

{
  "self": "https://api-search.nuget.org:443/",
  "services": {
    "search": "https://api-search.nuget.org/search"
  },
  "versions": {
    "search": {
      "branch": "master",
      "commit": "dcc9fd6b93ff385369e7cf70558c8da71efe4b65",
      "buildDateUtc": "2014-08-27T00:09:33Z",
      "version": "3.0.43-r-master"
    }
  }
}

とあるので、さらに https://api-search.nuget.org/search を見てみると

{
  "name": "nuget-prod-0-search_IN3-search",
  "service": "search",
  "resources": {
    "range": "https://api-search.nuget.org/search/range",
    "fields": "https://api-search.nuget.org/search/fields",
    "console": "https://api-search.nuget.org/search/console",
    "diagnostics": "https://api-search.nuget.org/search/diag",
    "segments": "https://api-search.nuget.org/search/segments",
    "query": "https://api-search.nuget.org/search/query"
  }
}

API の一覧を取得することが出来ました。というわけで、これらを順番に解説していきます。

query

メインの API です。というかこれ以外は // Admin endpoints書かれているのでこれ以外を使うことはないと思います。

パラメータ
q
検索クエリ。省略すると sortBy に応じてすべてのパッケージからいい感じに取得してきます
projectType
Azure のストレージに保存されているファイルのデータからランキングを取得してくるのに使うっぽいが正体をつかめなかった……
sortBy
指定なし、または lastEdited, published, title-asc, title-desc のいずれか
luceneQuery
false を指定すると(つまりデフォルト true)複雑なクエリが使えるようになります。ここに書くと厄介なので下に解説書いておきました。
prerelease
true を指定すると pre-release パッケージも検索結果に含まれます。と思ったら指定しなくても入ってきてるんだけどなんでだ……
countOnly
true を指定すると data フィールドが返ってこなくなります
feed
https://www.nuget.org/api/v2/curated-feeds/microsoftdotnet/ とかのやつ。 microsoftdotnet と指定したらうまく動いたけど、そもそもこの curated-package とやらを一般人が作る方法がわからない……
skip
最初に飛ばす件数
take
取得する件数。デフォルト 20
explanation
true を指定すると executedQuery, diagnostics フィールドが追加されます
ignoreFilter
true を指定すると prerelease と feed を無視します。後で書くクライアントライブラリのほうでは getAllVersions という表記になってるのですが、すべてのバージョンが入ってくるかどうかは luceneQuery のほうが影響していて、こっちで挙動変わらないんですがこれは一体……
supportedFramework
対応しているフレームワークから絞り込みます。ちゃんと動きます
luceneQuery=false 時の q

このとき通常の文字列を入れればタイトル検索になりますが、 property:value という形式でプロパティの値で検索することができます。空白で区切られます。指定できるプロパティは id, packageid, version, author, authors, owner, owners, tag, tags です。 : がプロパティ指定になるのを回避するには "" で括ります。

https://api-search.nuget.org/search/query?q=author:azyobuzin&luceneQuery=false&sortBy=published&explanation=true&take=1
{
  "totalHits": 6,
  "timeTakenInMs": 3,
  "index": "ng-search-index-aug26-2014",
  "indexTimestamp": "3/12/2015 2:50:08 PM",
  "executedQuery": "rank(+(+Authors:azyobuzin) +Facet:l_s<>)",
  "data": [
    {
      "Key": 492105,
      "PackageRegistrationKey": 56163,
      "PackageRegistration": {
        "Key": 56163,
        "Id": "NuGetCalc",
        "DownloadCount": 19,
        "Owners": [
          "azyobuzin"
        ]
      },
      "Version": "1.3",
      "NormalizedVersion": "1.3.0",
      "Title": "NuGetCalc",
      "Description": "PS functions to find dependency information of nupkgs",
      "Summary": null,
      "Authors": "azyobuzin",
      "Copyright": "Copyright 2014-2015 azyobuzin",
      "Language": null,
      "Tags": "NuGet",
      "ReleaseNotes": null,
      "ProjectUrl": "https://github.com/azyobuzin/NuGetCalc",
      "IconUrl": null,
      "IsLatest": true,
      "IsLatestStable": true,
      "Listed": true,
      "Created": "2015-03-07T16:01:48.957",
      "Published": "2015-03-07T16:01:48.973",
      "LastUpdated": "2015-03-07T16:05:49.387",
      "LastEdited": "2015-03-07T16:05:49.387",
      "DownloadCount": 19,
      "FlattenedDependencies": "",
      "Dependencies": [],
      "SupportedFrameworks": [],
      "MinClientVersion": null,
      "Hash": "10htu6SSb2sZ0b4HThaO0UrfYmmxjXy9Cw56NSJM/qcZ1tMQANBOZtBNuIPwFq1w3WP1/ZG0Nqzu4F3Whnyj/A==",
      "HashAlgorithm": "SHA512",
      "PackageFileSize": 645232,
      "LicenseUrl": "https://github.com/azyobuzin/NuGetCalc/blob/26f96bcb60988d1eee60a7171afb92702b76379f/LICENSE.md",
      "RequiresLicenseAcceptance": false,
      "LicenseNames": null,
      "LicenseReportUrl": null,
      "HideLicenseReport": false,
      "Installs": 0,
      "Updates": 0,
      "diagnostics": {
        "Score": "NaN",
        "Explanation": "11.73407 = (MATCH) rank(+(+Authors:azyobuzin) +Facet:l_s<>), product of:\n  11.73407 = (MATCH) sum of:\n    11.08049 = (MATCH) weight(Authors:azyobuzin in 3668), product of:\n      0.9717512 = queryWeight(Authors:azyobuzin), product of:\n        11.40259 = idf(docFreq=14, maxDocs=494175)\n        0.08522194 = queryNorm\n      11.40259 = (MATCH) fieldWeight(Authors:azyobuzin in 3668), product of:\n        1 = tf(termFreq(Authors:azyobuzin)=1)\n        11.40259 = idf(docFreq=14, maxDocs=494175)\n        1 = fieldNorm(field=Authors, doc=3668)\n    0.6535816 = (MATCH) weight(Facet:l_s<> in 3668), product of:\n      0.2360074 = queryWeight(Facet:l_s<>), product of:\n        2.769327 = idf(docFreq=84230, maxDocs=494175)\n        0.08522194 = queryNorm\n      2.769327 = (MATCH) fieldWeight(Facet:l_s<> in 3668), product of:\n        1 = tf(termFreq(Facet:l_s<>)=1)\n        2.769327 = idf(docFreq=84230, maxDocs=494175)\n        1 = fieldNorm(field=Facet, doc=3668)\n  1 = queryBoost\n",
        "IdTerms": [
          "nugetcalc (0,9)"
        ],
        "TokenizedIdTerms": [
          "calc (5,9)",
          "get (2,5)",
          "getcalc (2,9)",
          "nu (0,2)",
          "nuget (0,5)"
        ],
        "ShingledIdTerms": [
          "nugetcalc (0,9)"
        ],
        "VersionTerms": [
          "1.3.0 (0,3)"
        ],
        "TitleTerms": [
          "calc (5,9)",
          "get (2,5)",
          "getcalc (2,9)",
          "nu (0,2)",
          "nuget (0,5)"
        ],
        "TagsTerms": [
          "nuget (0,5)"
        ],
        "DescriptionTerms": [
          "dependency (21,31)",
          "find (16,20)",
          "functions (3,12)",
          "information (32,43)",
          "nupkgs (47,53)",
          "ps (0,2)"
        ],
        "AuthorsTerms": [
          "azyobuzin (0,9)"
        ],
        "OwnersTerms": [
          "azyobuzin (0,9)"
        ],
        "PublishedDate": 20150307,
        "EditedDate": 20150307,
        "CuratedFeed": [],
        "Key": 492105,
        "Checksum": 17428532,
        "ProjectGuidRankings": [
          {
            "Id": 0
          },
          {
            "Version": 0
          },
          {
            "Checksum": 17428532
          },
          {
            "Data": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          },
          {
            "Facet": 0
          }
        ]
      }
    }
  ],
  "answeredBy": "nuget-prod-0-search_IN2-search"
}

console

query API をブラウザでテストできる便利ツールです。
https://api-search.nuget.org/search/console/

その他

内部情報過ぎてよくわかりませんでした。。。

NuGet.Services.Search.Client

そんなこんなであんまり理解できなかった API 構成ですが、ラッパーライブラリがあります。

使い方は単機能なので難しいことはないです。説明することも特に無いので CoreTweet が Silverlight5 にインストールできた最後のバージョンでも眺めてみることにしましょう。なお、 SearchClient.Search の引数のデフォルト値は API のデフォルト値とは違う場合があるので注意してください。

using System;
using Newtonsoft.Json.Linq;
using NuGet.Services.Client;
using NuGet.Services.Search.Client;
using NuGet.Services.Search.Models;

var client = new SearchClient(new Uri("https://api.nuget.org/v3/index.json"));
ServiceResponse<SearchResults> result = client.Search("CoreTweet", supportedFramework: "sl5").Result;
SearchResults content = result.ReadContent().Result;
foreach (JObject item in content.Data)
    Console.WriteLine("{0} {1}", (string)item["Title"], (string)item["Version"]);
CoreTweet 0.3.4
CoreTweet.Streaming.Reactive 0.3.4
CoreTweetSupplement 1.1

はい、簡単ですね。しかしなぜ検索 API が 2 種類もあるのでしょうね……。謎。