Skip to content

Commit

Permalink
feat: add openvsx endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
williamboman committed Jan 4, 2024
1 parent f468646 commit 55fce9b
Show file tree
Hide file tree
Showing 12 changed files with 294 additions and 2 deletions.
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,11 @@ path = "api/mason/sponsors.rs"
[[bin]]
name = "mason-renovate-badge"
path = "api/mason/renovate/badge.rs"

[[bin]]
name = "openvsx-versions-latest"
path = "api/openvsx/[namespace]/[extension]/versions/latest.rs"

[[bin]]
name = "openvsx-versions-all"
path = "api/openvsx/[namespace]/[extension]/versions/all.rs"
34 changes: 34 additions & 0 deletions api/openvsx/[namespace]/[extension]/versions/all.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use http::{Method, StatusCode};
use mason_registry_api::{
openvsx::{client::OpenVSXClient, manager::OpenVSXManager},
vercel::parse_url,
QueryParams,
};
use vercel_runtime::{run, Body, Error, Request, Response};

async fn handler(request: Request) -> Result<Response<Body>, Error> {
if request.method() != Method::GET {
return Ok(Response::builder()
.status(StatusCode::METHOD_NOT_ALLOWED)
.body(Body::Empty)?);
}

let url = parse_url(&request)?;
let query_params: QueryParams = (&url).into();
let extension = (&query_params).into();
let manager = OpenVSXManager::new(OpenVSXClient::new());

match manager.get_all_versions(&extension) {
Ok(versions) => mason_registry_api::vercel::ok_json(
versions,
mason_registry_api::CacheControl::PublicMedium,
),
Err(err) => mason_registry_api::vercel::err_json(err),
}
}

#[tokio::main]
async fn main() -> Result<(), Error> {
mason_registry_api::setup_tracing();
run(handler).await
}
34 changes: 34 additions & 0 deletions api/openvsx/[namespace]/[extension]/versions/latest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use http::{Method, StatusCode};
use mason_registry_api::{
openvsx::{api::OpenVSXExtensionResponse, client::OpenVSXClient, manager::OpenVSXManager},
vercel::parse_url,
QueryParams,
};
use vercel_runtime::{run, Body, Error, Request, Response};

async fn handler(request: Request) -> Result<Response<Body>, Error> {
if request.method() != Method::GET {
return Ok(Response::builder()
.status(StatusCode::METHOD_NOT_ALLOWED)
.body(Body::Empty)?);
}

let url = parse_url(&request)?;
let query_params: QueryParams = (&url).into();
let extension = (&query_params).into();
let manager = OpenVSXManager::new(OpenVSXClient::new());

match manager.get_extension(&extension) {
Ok(extension_dto) => mason_registry_api::vercel::ok_json::<OpenVSXExtensionResponse>(
extension_dto.into(),
mason_registry_api::CacheControl::PublicMedium,
),
Err(err) => mason_registry_api::vercel::err_json(err),
}
}

#[tokio::main]
async fn main() -> Result<(), Error> {
mason_registry_api::setup_tracing();
run(handler).await
}
2 changes: 1 addition & 1 deletion src/golang/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub struct GolangManager {

fn semver_sort_desc(a: &String, b: &String) -> Ordering {
let a_semver = a.strip_prefix("v").unwrap_or(a).parse::<semver::Version>();
let b_semver = b.strip_prefix("v").unwrap_or(a).parse::<semver::Version>();
let b_semver = b.strip_prefix("v").unwrap_or(b).parse::<semver::Version>();
if let (Ok(a), Ok(b)) = (&a_semver, &b_semver) {
return b.cmp(a);
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod github;
pub mod golang;
pub mod http;
pub mod npm;
pub mod openvsx;
pub mod packagist;
pub mod pypi;
pub mod renovate;
Expand Down
18 changes: 18 additions & 0 deletions src/openvsx/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use serde::Serialize;

use super::client::spec::OpenVSXExtensionDto;

#[derive(Serialize)]
pub struct OpenVSXExtensionResponse {
pub name: String,
pub version: String,
}

impl From<OpenVSXExtensionDto> for OpenVSXExtensionResponse {
fn from(dto: OpenVSXExtensionDto) -> Self {
Self {
name: format!("{}/{}", dto.namespace, dto.name),
version: dto.version,
}
}
}
61 changes: 61 additions & 0 deletions src/openvsx/client/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
pub mod spec;

use self::spec::{OpenVSXExtensionVersionsDto, OpenVSXExtensionDto};

use super::OpenVSXExtension;
use crate::http::client::{Client, HttpEndpoint};
use std::fmt::Display;

pub struct OpenVSXClient {
client: Client,
}

enum OpenVSXEndpoint<'a> {
Extension(&'a OpenVSXExtension),
ExtensionVersions(&'a OpenVSXExtension),
}

impl<'a> HttpEndpoint for OpenVSXEndpoint<'a> {
fn as_full_url(&self) -> String {
format!("https://open-vsx.org/api/{}", self)
}
}

impl<'a> Display for OpenVSXEndpoint<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OpenVSXEndpoint::Extension(ext) => {
f.write_fmt(format_args!("{}/{}", ext.namespace, ext.extension))
}
OpenVSXEndpoint::ExtensionVersions(ext) => {
f.write_fmt(format_args!("{}/{}/versions", ext.namespace, ext.extension))
}
}
}
}

impl OpenVSXClient {
pub fn new() -> Self {
Self {
client: Client::new(None),
}
}

pub fn fetch_latest_extension_version(
&self,
extension: &OpenVSXExtension,
) -> Result<OpenVSXExtensionDto, reqwest::Error> {
self.client
.get(OpenVSXEndpoint::Extension(extension))?
.json()
}

pub fn fetch_extension_versions(
&self,
extension: &OpenVSXExtension,
) -> Result<OpenVSXExtensionVersionsDto, reqwest::Error> {
self.client
.get(OpenVSXEndpoint::ExtensionVersions(extension))?
.json()
}
}
15 changes: 15 additions & 0 deletions src/openvsx/client/spec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use std::collections::HashMap;

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct OpenVSXExtensionVersionsDto {
pub versions: HashMap<String, String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct OpenVSXExtensionDto {
pub namespace: String,
pub name: String,
pub version: String,
}
45 changes: 45 additions & 0 deletions src/openvsx/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use http::StatusCode;
use thiserror::Error;

use crate::errors::ApiError;

#[derive(Error, Debug)]
pub enum OpenVSXError {
#[error("The requested resource was not found when interfacing with OpenVSX API.")]
ResourceNotFound { source: Option<reqwest::Error> },
#[error("Client error. {:?}", source.status())]
ClientError { source: reqwest::Error },
#[error("OpenVSX API had a server error. {:?}", source.status())]
ServerError { source: reqwest::Error },
#[error("Network error. {:?}", source.status())]
NetworkError { source: reqwest::Error },
}

impl ApiError for OpenVSXError {
fn status_code(&self) -> http::StatusCode {
match self {
OpenVSXError::ResourceNotFound { .. } => StatusCode::NOT_FOUND,
OpenVSXError::ClientError { .. } | OpenVSXError::NetworkError { .. } => {
StatusCode::INTERNAL_SERVER_ERROR
}
OpenVSXError::ServerError { .. } => StatusCode::BAD_GATEWAY,
}
}
}

impl From<reqwest::Error> for OpenVSXError {
fn from(req_error: reqwest::Error) -> Self {
match req_error.status() {
Some(reqwest::StatusCode::NOT_FOUND) => Self::ResourceNotFound {
source: Some(req_error),
},
Some(status_code) if status_code.is_server_error() => {
Self::ServerError { source: req_error }
}
Some(status_code) if status_code.is_client_error() => {
Self::ClientError { source: req_error }
}
Some(_) | None => Self::NetworkError { source: req_error },
}
}
}
49 changes: 49 additions & 0 deletions src/openvsx/manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::cmp::Ordering;

use super::{
client::{spec::OpenVSXExtensionDto, OpenVSXClient},
errors::OpenVSXError,
OpenVSXExtension,
};

pub struct OpenVSXManager {
client: OpenVSXClient,
}

fn semver_sort_desc(a: &String, b: &String) -> Ordering {
let a_semver = a.strip_prefix("v").unwrap_or(a).parse::<semver::Version>();
let b_semver = b.strip_prefix("v").unwrap_or(b).parse::<semver::Version>();
if let (Ok(a), Ok(b)) = (&a_semver, &b_semver) {
return b.cmp(a);
}
Ordering::Equal
}

impl OpenVSXManager {
pub fn new(client: OpenVSXClient) -> Self {
Self { client }
}

pub fn get_extension(
&self,
extension: &OpenVSXExtension,
) -> Result<OpenVSXExtensionDto, OpenVSXError> {
Ok(self.client.fetch_latest_extension_version(extension)?)
}

/// Returns all extension versions in DESCENDING order.
pub fn get_all_versions(
&self,
extension: &OpenVSXExtension,
) -> Result<Vec<String>, OpenVSXError> {
let mut unsorted_versions: Vec<String> = self
.client
.fetch_extension_versions(extension)?
.versions
.into_keys()
.collect();

unsorted_versions.sort_by(semver_sort_desc);
Ok(unsorted_versions)
}
}
27 changes: 27 additions & 0 deletions src/openvsx/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use crate::QueryParams;

pub mod api;
pub mod client;
pub mod errors;
pub mod manager;

#[derive(Debug)]
pub struct OpenVSXExtension {
pub namespace: String,
pub extension: String,
}

impl From<&QueryParams> for OpenVSXExtension {
fn from(query: &QueryParams) -> Self {
Self {
namespace: query
.get("namespace")
.expect("No [namespace] query param")
.to_owned(),
extension: query
.get("extension")
.expect("No [extension] query param")
.to_owned(),
}
}
}
2 changes: 1 addition & 1 deletion vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"ignoreCommand": "[ \"$VERCEL_ENV\" != production ]",
"functions": {
"api/**/*.rs": {
"runtime": "[email protected].0-beta.1"
"runtime": "[email protected].6"
}
},
"rewrites": [
Expand Down

0 comments on commit 55fce9b

Please sign in to comment.