diff --git a/.gitignore b/.gitignore index bc57e3a..f9a5716 100644 --- a/.gitignore +++ b/.gitignore @@ -2,16 +2,9 @@ .DS_Store Thumbs.db -# --- Go / Golang --- -main -code/main -*.exe -*.exe~ -*.dll -*.so -*.dylib -*.test -*.out +# --- Rust --- +target/ +Cargo.lock # --- COBOL / C --- *.out diff --git a/Dockerfile b/Dockerfile index 478ccf3..eedadb9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,19 @@ -FROM golang:latest AS builder +FROM rust:alpine AS builder +RUN apk add --no-cache musl-dev WORKDIR /app -COPY code/go.mod code/go.sum ./ -RUN go mod download -COPY code/main.go . -RUN CGO_ENABLED=0 GOOS=linux go build -o server main.go +COPY code/Cargo.toml ./ +RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release && rm -rf src +COPY code/src ./src +RUN cargo build --release FROM alpine:latest RUN apk add --no-cache \ gnucobol \ gcc \ - musl-dev + musl-dev \ + libgcc COPY api.yaml /api.yaml COPY src/ /src/ -COPY --from=builder /app/server /server +COPY --from=builder /app/target/release/rest-cobol /server EXPOSE 8080 ENTRYPOINT ["/server"] \ No newline at end of file diff --git a/README.md b/README.md index 9ef3cd7..91140b9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ # RestCOBOL +RestCobol is a "Framework", that allows to make COBOL Procedures available via Rest. +All Endpoints are defined in a single yaml file. + +## Running the Project + +Start Server with `just run`, or build the image with `just build` + +## Cobol parsing + A CopyBook will be parsed into usable JSON, like this: ```cpy 01 STUDENT. diff --git a/code/Cargo.toml b/code/Cargo.toml new file mode 100644 index 0000000..890f3f5 --- /dev/null +++ b/code/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rest-cobol" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.7" +tokio = { version = "1.0", features = ["full"] } +serde = { version = "1.0", features = ["derive"] } +serde_yaml = "0.9" +serde_json = "1.0" +tower-http = { version = "0.5", features = ["fs"] } +libloading = "0.8" diff --git a/code/go.mod b/code/go.mod deleted file mode 100644 index 04c17d0..0000000 --- a/code/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module rest-cobol - -go 1.26.4 - -require gopkg.in/yaml.v3 v3.0.1 diff --git a/code/go.sum b/code/go.sum deleted file mode 100644 index a62c313..0000000 --- a/code/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code/main.go b/code/main.go deleted file mode 100644 index a7de661..0000000 --- a/code/main.go +++ /dev/null @@ -1,74 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "io/ioutil" - "gopkg.in/yaml.v3" -) - -type ApiConfig struct { - Version string `yaml:"version"` - Project string `yaml:"project"` - Endpoints []Endpoint `yaml:"endpoints"` -} - -type Endpoint struct { - Path string `yaml:"path"` - Method string `yaml:"method"` - CobolSource string `yaml:"cobol_source"` - Procedure string `yaml:"procedure"` - Arguments []Argument `yaml:"arguments"` - ResponseCopybook string `yaml:"response_copybook"` -} - -type Argument struct { - Name string `yaml:"name"` - Target string `yaml:"target"` - Type string `yaml:"type"` - Source string `yaml:"source"` -} - -func readConf(filename string) (*ApiConfig, error) { - buf, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - c := &ApiConfig{} - err = yaml.Unmarshal(buf, c) - if err != nil { - return nil, fmt.Errorf("in file %q: %w", filename, err) - } - - return c, err -} - -func projectHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - w.Header().Set("Content-Type", "application/json") - - api, err := readConf("api.yaml") - if err != nil { - log.Fatal(err) - } - - response := map[string]string{"project": api.Project} - json.NewEncoder(w).Encode(response) -} - -func main() { - http.HandleFunc("/project", projectHandler) - - port := ":8080" - fmt.Printf("Server starting on port %s...\n", port) - if err := http.ListenAndServe(port, nil); err != nil { - log.Fatalf("Server failed to start: %v", err) - } -} \ No newline at end of file diff --git a/code/src/main.rs b/code/src/main.rs new file mode 100644 index 0000000..0d4aec8 --- /dev/null +++ b/code/src/main.rs @@ -0,0 +1,80 @@ +use axum::{ + routing::get, + Json, Router, + http::StatusCode, +}; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::Read; +use std::net::SocketAddr; + +#[derive(Debug, Serialize, Deserialize)] +struct ApiConfig { + version: String, + project: String, + endpoints: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Endpoint { + path: String, + method: String, + cobol_source: String, + procedure: String, + arguments: Vec, + response_copybook: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Argument { + name: String, + target: String, + #[serde(rename = "type")] + arg_type: String, + source: String, +} + +#[repr(C, packed)] +struct Student { + st_id: [u8; 5], + first_name: [u8; 20], + last_name: [u8; 20], +} + +#[tokio::main] +async fn main() { + eprintln!("Starting RestCOBOL Rust server..."); + let app = Router::new() + .route("/project", get(project_handler)); + + let addr = SocketAddr::from(([0, 0, 0, 0], 8080)); + eprintln!("Binding to {}...", addr); + let listener = match tokio::net::TcpListener::bind(addr).await { + Ok(l) => l, + Err(e) => { + eprintln!("Failed to bind to {}: {}", addr, e); + std::process::exit(1); + } + }; + eprintln!("Server starting on port 8080..."); + if let Err(e) = axum::serve(listener, app).await { + eprintln!("Server error: {}", e); + std::process::exit(1); + } +} + +async fn project_handler() -> Result, (StatusCode, String)> { + let config = read_conf("api.yaml").map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; + + Ok(Json(serde_json::json!({ + "project": config.project + }))) +} + +fn read_conf(filename: &str) -> Result> { + let mut file = File::open(filename)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + let config: ApiConfig = serde_yaml::from_str(&contents)?; + Ok(config) +}