Skip to content
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

Openapi spec #27

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion JShellAPI/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ import java.time.Instant
plugins {
id 'org.springframework.boot' version '3.2.5'
id 'io.spring.dependency-management' version '1.1.5'
id 'org.springdoc.openapi-gradle-plugin' version '1.8.0'
id 'com.google.cloud.tools.jib' version '3.4.2'
id 'com.github.johnrengelman.shadow' version '8.1.1'
}

dependencies {
implementation project(':JShellWrapper')
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'com.github.docker-java:docker-java-transport-httpclient5:3.3.6'
implementation 'com.github.docker-java:docker-java-core:3.3.6'

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
implementation 'org.springdoc:springdoc-openapi-starter-webflux-ui:2.5.0'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
}
Expand All @@ -36,4 +41,4 @@ shadowJar {
archiveBaseName.set('JShellPlaygroundBackend')
archiveClassifier.set('')
archiveVersion.set('')
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package org.togetherjava.jshellapi.rest;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.validation.constraints.Pattern;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
Expand All @@ -18,23 +24,43 @@
@RequestMapping("jshell")
@RestController
public class JShellController {
private static final String ID_REGEX = "^[a-zA-Z0-9][a-zA-Z0-9_.-]+$";

@Autowired
private JShellSessionService service;
@Autowired
private StartupScriptsService startupScriptsService;

@PostMapping("/eval/{id}")
public JShellResult eval(@PathVariable String id,
@Operation(summary = "Evaluate code in a JShell session",
description = "Evaluate code in a JShell session, create a session from this id, or use an"
+ " existing session if this id already exists.")
@ApiResponse(responseCode = "200", content = {@Content(mediaType = "application/json",
schema = @Schema(implementation = JShellResult.class))})
public JShellResult eval(
@Parameter(description = "id of the session, must follow the regex " + ID_REGEX)
@Pattern(regexp = ID_REGEX, message = "'id' doesn't match regex " + ID_REGEX)
@PathVariable String id,
@Parameter(description = "id of the startup script to use")
@RequestParam(required = false) StartupScriptId startupScriptId,
@RequestBody String code) throws DockerException {
validateId(id);
@Parameter(description = "Java code to evaluate") @RequestBody String code)
throws DockerException {
return service.session(id, startupScriptId)
.eval(code)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.CONFLICT,
"An operation is already running"));
}

@PostMapping("/eval")
public JShellResultWithId eval(@RequestParam(required = false) StartupScriptId startupScriptId,
@RequestBody String code) throws DockerException {
@Operation(summary = "Evaluate code in a JShell session",
description = "Evaluate code in a JShell session, creates a new session each time, with a random id")
@ApiResponse(responseCode = "200", content = {@Content(mediaType = "application/json",
schema = @Schema(implementation = JShellResultWithId.class))})
public JShellResultWithId eval(
@Parameter(description = "id of the startup script to use")
@RequestParam(required = false) StartupScriptId startupScriptId,
@Parameter(description = "Java code to evaluate") @RequestBody String code)
throws DockerException {
JShellService jShellService = service.session(startupScriptId);
return new JShellResultWithId(jShellService.id(),
jShellService.eval(code)
Expand All @@ -43,53 +69,58 @@ public JShellResultWithId eval(@RequestParam(required = false) StartupScriptId s
}

@PostMapping("/single-eval")
public JShellResult singleEval(@RequestParam(required = false) StartupScriptId startupScriptId,
@RequestBody String code) throws DockerException {
@Operation(summary = "Evaluate code in JShell",
description = "Evaluate code in a JShell session, creates a session that can only be used once, and has lower timeout")
@ApiResponse(responseCode = "200", content = {@Content(mediaType = "application/json",
schema = @Schema(implementation = JShellResult.class))})
public JShellResult singleEval(
@Parameter(description = "id of the startup script to use")
@RequestParam(required = false) StartupScriptId startupScriptId,
@Parameter(description = "Java code to evaluate") @RequestBody String code)
throws DockerException {
JShellService jShellService = service.oneTimeSession(startupScriptId);
return jShellService.eval(code)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.CONFLICT,
"An operation is already running"));
}

@GetMapping("/snippets/{id}")
public List<String> snippets(@PathVariable String id,
@RequestParam(required = false) boolean includeStartupScript) throws DockerException {
validateId(id);
if (!service.hasSession(id))
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Id " + id + " not found");
@Operation(summary = "Retreive all snippets from a JShell session")
@ApiResponse(responseCode = "200", content = {@Content(mediaType = "application/json",
schema = @Schema(implementation = List.class))})
public List<String> snippets(
@Parameter(description = "id of the session, must follow the regex " + ID_REGEX)
@Pattern(regexp = ID_REGEX, message = "'id' doesn't match regex " + ID_REGEX)
@PathVariable String id, @RequestParam(required = false) boolean includeStartupScript)
throws DockerException {
checkIdExists(id);
return service.session(id, null)
.snippets(includeStartupScript)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.CONFLICT,
"An operation is already running"));
}

@DeleteMapping("/{id}")
public void delete(@PathVariable String id) throws DockerException {
validateId(id);
if (!service.hasSession(id))
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Id " + id + " not found");
@Operation(summary = "Delete a JShell session")
public void delete(
@Parameter(description = "id of the session, must follow the regex " + ID_REGEX)
@Pattern(regexp = ID_REGEX, message = "'id' doesn't match regex " + ID_REGEX)
@PathVariable String id) throws DockerException {
checkIdExists(id);
service.deleteSession(id);
}

@GetMapping("/startup_script/{id}")
public String startupScript(@PathVariable StartupScriptId id) {
@Operation(summary = "Get a startup script")
public String startupScript(@Parameter(description = "id of the startup script to fetch")
@PathVariable StartupScriptId id) {
return startupScriptsService.get(id);
}

@Autowired
public void setService(JShellSessionService service) {
this.service = service;
}

@Autowired
public void setStartupScriptsService(StartupScriptsService startupScriptsService) {
this.startupScriptsService = startupScriptsService;
}

private static void validateId(String id) throws ResponseStatusException {
if (!id.matches("[a-zA-Z0-9][a-zA-Z0-9_.-]+")) {
private void checkIdExists(String id) {
if (!id.matches(ID_REGEX)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"Id " + id + " doesn't match the regex [a-zA-Z0-9][a-zA-Z0-9_.-]+");
"Id " + id + " doesn't match regex " + ID_REGEX);
}
}
}
1 change: 1 addition & 0 deletions JShellAPI/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jshellapi:
server:
error:
include-message: always
include-binding-errors: always

logging:
level:
Expand Down
2 changes: 1 addition & 1 deletion JShellWrapper/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ shadowJar {
archiveBaseName.set('JShellWrapper')
archiveClassifier.set('')
archiveVersion.set('')
}
}
Loading