-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Proposal API contract #7962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Proposal API contract #7962
Changes from all commits
29ec6c2
e30825b
c29ddaf
0bad178
5109fff
0843233
81841cc
30452de
a9323f3
bc5536a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
# Versions | ||
|
||
So far there is only one version | ||
|
||
The base URL for an API should be: _baseURL_/_apiVersion_/ meaning even something such as sometestbuildsforpoe.gg/something/a/b/c/ would be valid as a base URL, as long as it has afterwards the per version required endpoints. | ||
|
||
## Metadata Endpoint | ||
|
||
In order to provide a consistent API a metadata endpoint should be provided under _baseURL_/.well-known/pathofbuilding this endpoint should provide information about the API and the version of the API. | ||
Furthermore this may allow having custom endpoints for the API. | ||
|
||
<details><summary><b>Specification</b></summary> | ||
| Feature | Field | Type | Description | | ||
| ---------------- | ------------- | ---------------- | ------------------------------------------------------------------------------------ | | ||
| League Filtering | league_filter | bool | This can be used to indicate whether the API supports filtering based on leagues | | ||
| Gem Filtering | gem_filter | bool | This can be used to indicate whether the API supports filtering based on gems | | ||
| Streams | streams | StreamInfo[] | A list of streams available to be queried against | | ||
| Update Builds | update_builds | UpdateBuildsInfo | Indicates if the API supports updating builds via PoB and which fields are supported | | ||
|
||
</details> | ||
|
||
### Types | ||
|
||
<details><summary><b>StreamInfo</b></summary> | ||
|
||
| Field | Type | Description | | ||
| ------- | ------ | ------------------------------- | | ||
| name | string | Name of the stream | | ||
| apiPath | string | API path to the stream endpoint | | ||
|
||
apiPath might be changed to a generic endpoint such as `/v1/{stream}/builds` | ||
</details> | ||
|
||
<details><summary><b>UpdateBuildsInfo</b></summary> | ||
| Field | Type | Description | | ||
| ---------- | -------- | ------------------------------------------------------ | | ||
| hasSupport | bool | indicates if the API supports updating external builds | | ||
| fields | string[] | list of fields that can be updated | | ||
|
||
Example: | ||
|
||
```json | ||
{ | ||
"hasSupport": true, | ||
"fields": ["description", "youtubeUrl"] | ||
} | ||
``` | ||
</details> | ||
|
||
## Version 1 | ||
|
||
### Response and Request types | ||
|
||
<details><summary><b>BuildInfo</b></summary> | ||
|
||
| Field | Type | Description | | ||
| ------------ | ------- | ----------------------------------- | | ||
| pobdata | string | The Path of Building data | | ||
| name | string | The name of the build | | ||
| lastModified | integer | The last modification timestamp | | ||
| buildId | string | The unique identifier for the build | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unique identifier on the website? Is that a link or just an internal id? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be an unique identifier as it could be used for updating a local cache for said build, it doesn't have to be an internal id to either pob nor the api but it needs to map to exactly one build. As going further there may be an option long-term to sync builds from sources to PoB and updating with the click of a button instead of manually reimporting. tl;dr: Unique Identifier provided by source to map to one specific build |
||
</details> | ||
|
||
### Endpoints | ||
|
||
<details> | ||
<summary><code>GET</code> <code><b>/v1/builds</b></code> <code>(Lists multiple builds)</code></summary> | ||
|
||
#### Parameters | ||
> Query Parameters | ||
|
||
| name | type | data type | description | | ||
| ------ | -------- | --------- | --------------------------------------- | | ||
| page | optional | integer | Used for pagination | | ||
| league | optional | string | Used to limit builds to a league | | ||
| gems | optional | string | Used to limit builds with specific gems | | ||
|
||
##### League | ||
|
||
These values will just be the base patch version for the league. | ||
|
||
These links are generally available via poewiki see: https://www.poewiki.net/wiki/Necropolis_league _Official Page_. | ||
|
||
Example values: | ||
|
||
| Patch | League | value | url | | ||
| ----- | ---------------------- | ----- | -------------------------------------- | | ||
| 3.25 | Settlers of Kalguur | 3.25 | https://www.pathofexile.com/settlers | | ||
| 3.24 | Necropolis | 3.24 | https://www.pathofexile.com/necropolis | | ||
| 3.23 | Affliction | 3.23 | https://www.pathofexile.com/affliction | | ||
| 3.22 | Trial of the Ancestors | 3.22 | https://www.pathofexile.com/ancestor | | ||
| 3.4 | Delve | 3.4 | https://www.pathofexile.com/delve | | ||
|
||
#### Responses | ||
|
||
| http code | content-type | response | | ||
| --------- | ------------------ | ------------------ | | ||
| `200` | `application/json` | `BuildInfo object` | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We probably need more metadata, for example a description/name of the list returned (or multiple?). pobarchives may provide a list of recent builds, pobbin would only provide popular. This need goes away if every 'provider' can only provide a single list, but currently more is possible. It must also be possible for the page to return if there are more pages available or at least indicate this was the last page. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So possibly a wrapper object such as: {
"totalPages": 100,
"buildInfos": [{}],
} something like this could then be used for assumption based "this is the last page". |
||
|
||
</details> | ||
|
||
|
||
<details> | ||
<summary><code>POST</code> <code><b>/v1/builds/{buildId}</b></code> <code>(Update a single build on the source)</code></summary> | ||
|
||
#### Parameters | ||
> Path Parameters | ||
|
||
| name | type | data type | description | | ||
| ------- | -------- | --------- | ---------------------------------------------------------- | | ||
| buildId | required | string | The build in question on the source that should be updated | | ||
|
||
#### Request | ||
|
||
| Field | Type | Description | | ||
| ---------- | -------- | ---------------------------------- | | ||
| pobdata | string | The Path of Building data | | ||
| name | string | The name of the build | | ||
| customData | string[] | A list of custom data to be stored | | ||
|
||
customData will be describable fields via the metadata endpoint, if customData is empty it is expected to be ignored and no changes should be made. | ||
|
||
#### Responses | ||
|
||
| http code | content-type | response | Description | | ||
| --------- | -------------------------- | --------------------- | ---------------------- | | ||
| `200` | `application/json` | None | Update succesful | | ||
| `201` | `application/json` | None | Succesful creation | | ||
| `400` | `text/html; charset=utf-8` | `Reason for failure` | Input may be incorrect | | ||
| `401` | `text/html; charset=utf-8` | `Reason if necessary` | Auth incorrect | | ||
| `404` | `text/html; charset=utf-8` | None | Build does not exist | | ||
|
||
</details> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
-- Path of Building | ||
-- | ||
-- Class: API Contract Builds | ||
-- API contract proposal for builds | ||
-- | ||
|
||
local dkjson = require "dkjson" | ||
|
||
|
||
---@enum EndpointType | ||
local EndpointType = { | ||
REST = 0, | ||
CSV = 1 | ||
} | ||
|
||
--example for REST vs CSV | ||
-- REST: | ||
-- {"baseUrl": "https://mypoebuilds.gg/pob-api/", "name": "MyPoEBuilds REST", "endpointType": 0, "fallbackVersion": 1} | ||
-- baseUrl meaning the actual baseUrl | ||
-- CSV: | ||
-- {"baseUrl": "https://mypoebuilds.gg/pob-builds.csv", "name": "MyPoEBuilds CSV", "endpointType": 1, "fallbackVersion": 1} | ||
-- baseUrl means here the full URL to the actual file or endpoint that returns a CSV formatted file from a GET endpoint | ||
|
||
--- APISourceInfo is the configurated data for a source from the enduser | ||
---@class APISourceInfo | ||
---@field name string | ||
---@field baseUrl string | ||
---@field endpointType EndpointType | ||
---@field fallbackVersion integer | ||
|
||
--- This data should be returned by the source | ||
---@class BuildInfo | ||
---@field pobdata string | ||
---@field name string | ||
---@field lastModified integer | ||
---@field buildId string | ||
|
||
--- This data is saved internally for caching | ||
--- Save this to disk for temporary local caching if required/desired | ||
---@class BuildInfoCache | ||
---@field pobdata string | ||
---@field name string | ||
---@field lastModified integer | ||
---@field buildId string | ||
---@field sourceName string | ||
|
||
--- API Capabilities returned by the source or defaulted to APISourceInfo | ||
---@class APICapabilities | ||
---@field name string | ||
---@field fallbackVersion integer | ||
---@field endpointType EndpointType | ||
---@field supportedVersion? integer | ||
---@field baseAPIPath? string | ||
---@field league_filter? boolean | ||
---@field gem_filter? boolean | ||
|
||
--- Build version 1 Filter option | ||
---@class BuildVersion1Filter | ||
---@field league string | ||
---@field gem string | ||
|
||
--- This primarily exists for the lua language server | ||
---@param buildInfo BuildInfo | ||
---@param source APICapabilities | ||
---@return BuildInfoCache | ||
local function buildInfoToCache(buildInfo, source) | ||
return { | ||
pobdata = buildInfo.pobdata, | ||
name = buildInfo.name, | ||
lastModified = buildInfo.lastModified, | ||
buildId = buildInfo.buildId, | ||
sourceName = source.name | ||
} | ||
end | ||
|
||
---@param t table | ||
local function tableToQueryParams(t) | ||
if #t == 0 then | ||
return "" | ||
end | ||
local query = "?" | ||
for key, value in pairs(t) do | ||
if value then | ||
local tempValue = value | ||
if type(value) == "table" then | ||
tempValue = table.concat(value, ",") | ||
end | ||
query = query .. key .. "=" .. value .. "&" | ||
end | ||
end | ||
-- remove trailing & | ||
if #t > 0 then | ||
query = query:sub(1, #query - 1) | ||
end | ||
|
||
return query | ||
end | ||
|
||
---@class APIContractBuilds | ||
---@field buildList table<string,BuildInfoCache> | ||
---@field apiCapabilities table<string, APICapabilities> | ||
local APIContractBuilds = newClass("APIContractBuilds", | ||
function(self) | ||
self.buildList = {} | ||
self.apiCapabilities = {} | ||
end | ||
) | ||
|
||
-- Switch case for GET Endpoint for Builds | ||
local getBuildVersions = { | ||
[1] = function(...) return APIContractBuilds:GetBuildsVersion1(...) end, | ||
} | ||
-- Switch case for GET Endpoint for Builds | ||
local getBuildFilterVersions = { | ||
[1] = function(data) return APIContractBuilds:GetBuildVersion1Filter(data) end, | ||
} | ||
|
||
---@param data table | ||
function APIContractBuilds:GetBuildVersion1Filter(data) | ||
return { | ||
league = data.league, | ||
gem = data.gem | ||
} | ||
end | ||
|
||
--- Gets the builds from the source | ||
---@param source APICapabilities | ||
---@param data table | ||
function APIContractBuilds:GetBuilds(source, data) | ||
local getBuildsFunction = nil | ||
local getBuildsFilterFunction = nil | ||
if not source.supportedVersion then | ||
getBuildsFunction = getBuildVersions[source.fallbackVersion] | ||
getBuildsFilterFunction = getBuildFilterVersions[source.fallbackVersion] | ||
else | ||
getBuildsFunction = getBuildVersions[source.supportedVersion] | ||
getBuildsFilterFunction = getBuildFilterVersions[source.supportedVersion] | ||
end | ||
|
||
if getBuildsFunction and getBuildsFilterFunction then | ||
getBuildsFunction(source.endpointType, getBuildsFilterFunction(data), source) | ||
end | ||
end | ||
|
||
---@param source APISourceInfo | ||
function APIContractBuilds:UpdateAPICapabilities(source) | ||
-- TODO: Which features are supported? | ||
-- What is the latest version that is supported? | ||
-- Which endpoints are supported | ||
-- Is Querying supported? (CSV won't support this probably) | ||
|
||
if source.endpointType ~= EndpointType.REST then | ||
return | ||
end | ||
|
||
launch:DownloadPage(source.baseUrl + "/.well-known/pathofbuilding", | ||
function(...) return APIContractBuilds:APICapabilitiesCallback(source, ...) end, {}) | ||
end | ||
|
||
--- Updates the API capabilities for the source | ||
---@param response table | ||
---@param errMsg any | ||
---@param source APISourceInfo | ||
function APIContractBuilds:APICapabilitiesCallback(source, response, errMsg) | ||
local parsedResponse = dkjson.decode(response.body) | ||
---@cast parsedResponse APICapabilities | ||
if errMsg or not parsedResponse.baseAPIPath then | ||
parsedResponse.baseAPIPath = source.baseUrl | ||
end | ||
parsedResponse.name = source.name | ||
parsedResponse.fallbackVersion = source.fallbackVersion | ||
parsedResponse.endpointType = source.endpointType | ||
|
||
self.apiCapabilities[source.name] = parsedResponse | ||
end | ||
|
||
--- Adds/Updates Builds for the source based on version 1 | ||
---@param response table | ||
---@param errMsg any | ||
---@param source APICapabilities | ||
function APIContractBuilds:BuildsVersion1Callback(source, response, errMsg) | ||
local parsedResponse = dkjson.decode(response.body) | ||
---@cast parsedResponse BuildInfo[] | ||
for _, build in ipairs(parsedResponse) do | ||
-- source and buildId will be used as a caching key | ||
-- to avoid buildId collissions | ||
self.buildList[common.sha1(source.name .. "-" .. build.buildId)] = buildInfoToCache(build, source) | ||
end | ||
end | ||
|
||
--- Version 1 of the API Contract for Builds | ||
---@param source APICapabilities | ||
---@param params BuildVersion1Filter | ||
function APIContractBuilds:GetBuildsVersion1(source, params) | ||
if source.endpointType == EndpointType.CSV then | ||
-- TODO: Implement CSV Parsing and Format | ||
return | ||
end | ||
|
||
launch:DownloadPage(source.baseAPIPath .. "/v1/builds" .. tableToQueryParams(params), function(...) | ||
self:BuildsVersion1Callback(source, ...) | ||
end, {}) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this contain, the build code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes or rather the standard pob code used for sharing builds, so I assume the description should make this more clear?