I have a golang api application. I've defined a set of routes and handlers. However, the mux router only ever returns the last route.
When I request /api/info I get this in my logging:
9:0:38 app | 2018/02/05 09:00:38 GET /api/info Users Create 308.132µs
Why is that routing to the wrong route?
routing package:
// NewRouter establishes the root application router
func NewRouter(context *config.ApplicationContext, routes Routes, notFoundHandler http.HandlerFunc) *mux.Router {
router := mux.NewRouter()
router.NotFoundHandler = notFoundHandler
for _, route := range routes {
router.
PathPrefix("/api").
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
// TODO: fix HandlerFunc. Right now, it is overriding previous routes and setting a single handler for all
// this means that the last route is the only router with a handler
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logRoute(setJSONHeader(route.HandlerFunc), route.Name)(context, w, r)
})
}
return router
}
func logRoute(inner ContextHandlerFunc, name string) ContextHandlerFunc {
return func(c *config.ApplicationContext, w http.ResponseWriter, r *http.Request) {
start := time.Now()
inner(c, w, r)
log.Printf(
"%s\t%s\t%s\t%s",
r.Method,
r.RequestURI,
name,
time.Since(start),
)
}
}
func setJSONHeader(inner ContextHandlerFunc) ContextHandlerFunc {
return func(c *config.ApplicationContext, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
inner(c, w, r)
}
}
main package:
var context = config.ApplicationContext{
Database: database.NewDatabase().Store,
}
var routes = router.Routes{
router.Route{"Info", "GET", "/info", handlers.InfoShow},
router.Route{"Users Create", "POST", "/users/create", handlers.UsersCreate},
}
func main() {
notFoundHandler := handlers.Errors404
router := router.NewRouter(&context, routes, notFoundHandler)
port := os.Getenv("PORT")
log.Fatal(http.ListenAndServe(":"+port, router))
}
If I visit /api/info it will attempt to call a POST to /users/create. However, if I remove the second route, it will correctly route to the InfoShow handler.
Why is mux overriding the first route? I'm fairly certain there's something wrong with
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logRoute(setJSONHeader(route.HandlerFunc), route.Name)(context, w, r)
})
but I'm not sure why that would cause it to map over the first route.
Ideas?
Reading through your code and gorilla/mux, I think I know the issue. You're using the for loop variable route, and specifically its field HanderFunc, in the function literal, but because of how function literals work, the value of that field is not evaluated until the that function literal is called. In Go, the second variable in a range loop is reused on each iteration, rather than created anew, and so after the for loop, if it's still in scope of anything (like your function literal), it will contain the value of the last loop iteration. Here's an example of what I mean:
https://play.golang.org/p/Xx62tuwhtgG
package main
import (
"fmt"
)
func main() {
var funcs []func()
ints := []int{1, 2, 3, 4, 5}
// How you're doing it
for i, a := range ints {
fmt.Printf("Loop i: %v, a: %v\n", i, a)
funcs = append(funcs, func() {
fmt.Printf("Lambda i: %v, a: %v\n", i, a)
})
}
for _, f := range funcs {
f()
}
fmt.Println("-------------")
// How you *should* do it
funcs = nil
for i, a := range ints {
i := i
a := a
fmt.Printf("Loop i: %v, a: %v\n", i, a)
funcs = append(funcs, func() {
fmt.Printf("Lambda i: %v, a: %v\n", i, a)
})
}
for _, f := range funcs {
f()
}
}
In the first example, i and a are being reused on each loop iteration, and aren't evaluated for their values in the lambda (function literal) until that lambda is actually called (by the funcs loop). To fix that, you can shadow a and i by redeclaring them inside the scope of the loop iteration (but outside the lambda's scope). This makes a separate copy for each iteration, to avoid issues with reuse of the same variable.
Specifically for your code, if you change your code to the following, it should work:
for _, route := range routes {
route := route // make a copy of the route for use in the lambda
// or alternatively, make scoped vars for the name and handler func
router.
PathPrefix("/api").
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
// TODO: fix HandlerFunc. Right now, it is overriding previous routes and setting a single handler for all
// this means that the last route is the only router with a handler
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logRoute(setJSONHeader(route.HandlerFunc), route.Name)(context, w, r)
})
}
Related
I have created a swagger specification which produces "application/zip"
/config:
get:
produces:
- application/zip
responses:
200: # OK
description: All config files
schema:
type: string
format: binary
I have implemented the handlers for this endpoint but I get this error
http: panic serving 127.0.0.1:20366: applicationZip producer has not yet been implemented
This error is coming from this code
func NewSampleAPI(spec *loads.Document) *SampleAPI {
return &SampleAPI{
...
ApplicationZipProducer: runtime.ProducerFunc(func(w io.Writer, data interface{}) error {
return errors.NotImplemented("applicationZip producer has not yet been implemented")
}),
After investigating this error my findings are that we need to implement something like this
api := operations.NewSampleAPI(swaggerSpec)
api.ApplicationZipProducer = func(w io.Writer, data interface{}) error {
...
}
So my question is that
what should we put in this Producer and why is it necessary to implement this because there is no implementation for "application/json" ?
Is "application/json" Producer is implemented by default and we need to implement other producers?
Note: I am using swagger "2.0" spec
Since you have used "application/zip" as response content then you might have implemented the backend code which might be returning io.ReadCloser.
Then your Producer will look like this
api.ApplicationZipProducer = runtime.ProducerFunc(func(w io.Writer, data interface{}) error {
if w == nil {
return errors.New("ApplicationZipProducer requires a writer") // early exit
}
if data == nil {
return errors.New("no data given to produce zip from")
}
if zp, ok := data.(io.ReadCloser); ok {
b, err := ioutil.ReadAll(zp)
if err != nil {
return fmt.Errorf("application zip producer: %v", err)
}
_, err = w.Write(b)
return nil
}
return fmt.Errorf("%v (%T) is not supported by the ApplicationZipProducer, %s", data, data)
})
This will parse the data interface into io.ReadCloser and read data from it and then it will fill into the io.Writer
Note: If your main purpose is just send file as attachment then you should use "application/octet-stream" and its producer is implemented by default
I need to verify if the user has permission for some routes.
I have made 3 "scopes" (guest, auth-user, admin) and now I don't know how to check if the user has access to these routes.
I'm trying to implement auth-middleware and this middleware should check if the user has the correct cookie or token. (I'm able to print out a cookie from request header), but I have no idea how to import, use actix_identity, and have access to id parameter inside this middleware.
I believe that my problem isn't only regarding Actix-identity, but I'm not able to pass parameters inside middleware.
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
let cookie_key = conf.server.key;
// Register http routes
let mut server = HttpServer::new(move || {
App::new()
// Enable logger
.wrap(Logger::default())
.wrap(IdentityService::new(
CookieIdentityPolicy::new(cookie_key.as_bytes())
.name("auth-cookie")
.path("/")
.secure(false),
))
//limit the maximum amount of data that server will accept
.data(web::JsonConfig::default().limit(4096))
//normal routes
.service(web::resource("/").route(web::get().to(status)))
// .configure(routes)
.service(
web::scope("/api")
// guest endpoints
.service(web::resource("/user_login").route(web::post().to(login)))
.service(web::resource("/user_logout").route(web::post().to(logout)))
// admin endpoints
.service(
web::scope("/admin")
// .wrap(AdminAuthMiddleware)
.service(
web::resource("/create_admin").route(web::post().to(create_admin)),
)
.service(
web::resource("/delete_admin/{username}/{_:/?}")
.route(web::delete().to(delete_admin)),
),
)
//user auth routes
.service(
web::scope("/auth")
// .wrap(UserAuthMiddleware)
.service(web::resource("/get_user").route(web::get().to(get_user))),
),
)
});
// Enables us to hot reload the server
let mut listenfd = ListenFd::from_env();
server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() {
server.listen(l)?
} else {
server.bind(ip)?
};
server.run().await
resources that I have tried:
Creating authentication middleware for Actix API
https://www.jamesbaum.co.uk/blether/creating-authentication-middleware-actix-rust-react/
Actix-web token validation in middleware https://users.rust-lang.org/t/actix-web-token-validation-in-middleware/38205
Actix middleware examples https://github.com/actix/examples/tree/master/middleware
Maybe I think completely wrong and auth-middleware isn't the best solution for my problem.
I hope that you can help me create "protected routes"
Try extractors instead
Trying to implement this pattern in Actix 3 I banged my head for awhile trying to use middleware, basically making a guard and then figuring out how to pass data from the middleware into the handler. It was painful and eventually I realized that I was working against Actix rather than with it.
Finally I learned out that the way to get information to a handler is to create a struct (AuthedUser, perhaps?) and implement the FromRequest trait on that struct.
Then every handler that asks for an AuthedUser in the function signature will be auth gated and if the user is logged in will have any user information you attach to AuthedUser in the FromRequest::from_request method.
Actix refers to these structs that implement FromRequest as extractors. It's a bit of magic that could use more attention in the guide.
The following does not use middleware(a little bit more work is needed) but it solves the problem with the bear minimum and seems to be the approach suggested in documentation:
#[macro_use]
extern crate actix_web;
use actix::prelude::*;
use actix_identity::{CookieIdentityPolicy, Identity, IdentityService};
use actix_web::{
dev::Payload, error::ErrorUnauthorized, web, App, Error, FromRequest, HttpRequest,
HttpResponse, HttpServer, Responder,
};
use log::{info, warn};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, pin::Pin, sync::RwLock};
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
struct Sessions {
map: HashMap<String, User>,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[serde(rename_all = "camelCase")]
struct Login {
id: String,
username: String,
scope: Scope,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
enum Scope {
Guest,
User,
Admin,
}
impl Default for Scope {
fn default() -> Self {
Scope::Guest
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[serde(rename_all = "camelCase")]
struct User {
id: String,
first_name: Option<String>,
last_name: Option<String>,
authorities: Scope,
}
impl FromRequest for User {
type Config = ();
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<User, Error>>>>;
fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future {
let fut = Identity::from_request(req, pl);
let sessions: Option<&web::Data<RwLock<Sessions>>> = req.app_data();
if sessions.is_none() {
warn!("sessions is empty(none)!");
return Box::pin(async { Err(ErrorUnauthorized("unauthorized")) });
}
let sessions = sessions.unwrap().clone();
Box::pin(async move {
if let Some(identity) = fut.await?.identity() {
if let Some(user) = sessions
.read()
.unwrap()
.map
.get(&identity)
.map(|x| x.clone())
{
return Ok(user);
}
};
Err(ErrorUnauthorized("unauthorized"))
})
}
}
#[get("/admin")]
async fn admin(user: User) -> impl Responder {
if user.authorities != Scope::Admin {
return HttpResponse::Unauthorized().finish();
}
HttpResponse::Ok().body("You are an admin")
}
#[get("/account")]
async fn account(user: User) -> impl Responder {
web::Json(user)
}
#[post("/login")]
async fn login(
login: web::Json<Login>,
sessions: web::Data<RwLock<Sessions>>,
identity: Identity,
) -> impl Responder {
let id = login.id.to_string();
let scope = &login.scope;
//let user = fetch_user(login).await // from db?
identity.remember(id.clone());
let user = User {
id: id.clone(),
last_name: Some(String::from("Doe")),
first_name: Some(String::from("John")),
authorities: scope.clone(),
};
sessions.write().unwrap().map.insert(id, user.clone());
info!("login user: {:?}", user);
HttpResponse::Ok().json(user)
}
#[post("/logout")]
async fn logout(sessions: web::Data<RwLock<Sessions>>, identity: Identity) -> impl Responder {
if let Some(id) = identity.identity() {
identity.forget();
if let Some(user) = sessions.write().unwrap().map.remove(&id) {
warn!("logout user: {:?}", user);
}
}
HttpResponse::Unauthorized().finish()
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
let sessions = web::Data::new(RwLock::new(Sessions {
map: HashMap::new(),
}));
HttpServer::new(move || {
App::new()
.app_data(sessions.clone())
.wrap(IdentityService::new(
CookieIdentityPolicy::new(&[0; 32])
.name("test")
.secure(false),
))
.service(account)
.service(login)
.service(logout)
.service(admin)
})
.bind("127.0.0.1:8088")?
.run()
.await
}
You can clone and run it here: https://github.com/geofmureithi/actix-acl-example
I think actix-web grants crate is perfect for you.
It allows you to check authorization using Guard, or a procedural macro (see examples on github).
It also integrates nicely with existing authorization middleware (like actix-web-httpauth).
A couple of examples for clarity:
proc-macro way
#[get("/secure")]
#[has_permissions("ROLE_ADMIN")]
async fn macro_secured() -> HttpResponse {
HttpResponse::Ok().body("ADMIN_RESPONSE")
}
Guard way
App::new()
.wrap(GrantsMiddleware::with_extractor(extract))
.service(web::resource("/admin")
.to(|| async { HttpResponse::Ok().finish() })
.guard(PermissionGuard::new("ROLE_ADMIN".to_string())))
And you can also take a look towards actix-casbin-auth (implementation of casbin integrated into actix)
Well this is in fact quite difficult to achieve in the newest actix-web version 3.0. What I did was copy the CookieIdentityPolicy middleware from the actix-web 1.0 version and modified it to my liking. However this is not plug & play code. Here and here is my version of it. Generally I would avoid actix-web, getting a thread / actor to spawn in the background and having it perform HTTP Requests are a nightmare. Then trying to share the results with handlers even more so.
middleware doesn't look very friendly with all the generics and internal types it defines, but it is a simple struct that wrap the next service to
be called. What is the next service is determined by the chain call when you create your App or define your routes. You use a generic S in your middleware which will be monomorphized at compile time so you don't have to care about which concrete type the middleware will protect.
The following middleware use a simple config passed to your App with .data() to check if the 'token' header contains the same magic value. It either go through the next service or return a not authorized error (futures).
use crate::config::Config;
use actix_service::{Service, Transform};
use actix_web::{
dev::{ServiceRequest, ServiceResponse},
error::ErrorUnauthorized,
web::Data,
Error,
};
use futures::future::{err, ok, Either, Ready};
use std::task::{Context, Poll};
pub struct TokenAuth;
impl<S, B> Transform<S> for TokenAuth
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{ type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = TokenAuthMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(TokenAuthMiddleware { service })
}
}
pub struct TokenAuthMiddleware<S> {
service: S,
}
impl<S, B> Service for TokenAuthMiddleware<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
{ type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Either<S::Future, Ready<Result<Self::Response, Self::Error>>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
if let Some(token) = req
.headers()
.get("token")
.and_then(|token| token.to_str().ok())
{
if let Some(config) = req.app_data::<Data<Config>>() {
if token == config.token {
return Either::Left(self.service.call(req));
}
}
}
Either::Right(err(ErrorUnauthorized("not authorized")))
}
}
to protect your functions is then as simple as
#[post("/upload", wrap="TokenAuth")]
async fn upload(mut payload: Multipart) -> Result<HttpResponse, Error> {
}
Note that you need actix_service 1.x for this to compile. actix_service 2 remove the request internal type to make it generic and I couldn't make it work with the wrap="" syntax
Let's say I have a attrs: Vec<Attribute> of some function attributes and a function fn map_attribute(attr: &Attribute) -> Result<TokenStream, Error> that maps the attributes to some code.
I know that I could write something like this:
attrs.into_iter()
.map(map_attribute)
.collect::<Result<Vec<_>, _>()?
However, this is not what I want. What I want is spit out all errors at once, not stop with the first Error. Currently I do something like this:
let mut codes : Vec<TokenStream> = Vec::new();
let mut errors: Vec<Error> = Vec::new();
for attr in attrs {
match map_attribute(attr) {
Ok(code) => codes.push(code),
Err(err) => errors.push(err)
}
}
let mut error_iter = errors.into_iter();
if let Some(first) = error_iter.nth(0) {
return Err(iter.fold(first, |mut e0, e1| { e0.combine(e1); e0 }));
}
This second version does what I want, but is considerably more verbose than the first version. Is there a better / more idiomatic way to acchieve this, if possible without creating my own iterator?
The standard library does not have a convenient one-liner for this as far as I know, however the excellent itertools library does:
use itertools::Itertools; // 0.9.0
fn main() {
let foo = vec![Ok(42), Err(":("), Ok(321), Err("oh noes")];
let (codes, errors): (Vec<_>, Vec<_>)
= foo.into_iter().partition_map(From::from);
println!("codes={:?}", codes);
println!("errors={:?}", errors);
}
(Permalink to the playground)
I ended up writing my own extension for Iterator, which allows me to stop collecting codes when I encounter my first error. This is in my use case probably a bit more efficient than the answer by mcarton, since I only need the first partition bucket if the second one is empty. Also, I need to fold the errors anyways if I want to combine them into a single error.
pub trait CollectToResult
{
type Item;
fn collect_to_result(self) -> Result<Vec<Self::Item>, Error>;
}
impl<Item, I> CollectToResult for I
where
I : Iterator<Item = Result<Item, Error>>
{
type Item = Item;
fn collect_to_result(self) -> Result<Vec<Item>, Error>
{
self.fold(<Result<Vec<Item>, Error>>::Ok(Vec::new()), |res, code| {
match (code, res) {
(Ok(code), Ok(mut codes)) => { codes.push(code); Ok(codes) },
(Ok(_), Err(errors)) => Err(errors),
(Err(err), Ok(_)) => Err(err),
(Err(err), Err(mut errors)) => { errors.combine(err); Err(errors) }
}
})
}
}
Ok so I am Go Lang with the Echo framework, to try and build pdf which will load data from a database source - that bit will come later.
So this is how I am rendering my pdf html layout,
func (c *Controller) DataTest(ec echo.Context) error {
return ec.Render(http.StatusOK, "pdf.html", map[string]interface{}{
"name": "TEST",
"msg": "Hello, XXXX!",
})
}
The above function works fine, and renders the html (I built a temp path to the function). Now I want to use that function as my html template to build my pdf's.
So I am using wkhtmltopdf and the lib "github.com/SebastiaanKlippert/go-wkhtmltopdf"
This is how I would render the html in the pdf,
html, err := ioutil.ReadFile("./assets/pdf.html")
if err != nil {
return err
}
But I need to be able to update the template so that is why I am trying to render the page and take that into the pdf.
However, the Echo framework returns an error type and not of type bytes or strings and I am not sure how to update it so my rendered content is returned as bytes?
Thanks,
UPDATE
page := wkhtmltopdf.NewPageReader(bytes.NewReader(c.DataTest(data)))
This is how I am currently doing, the data is just a html string which is then turned into a slice of bytes for the NewReader.
This works fine, but I wanted to turn the DataTest function into a fully rendered html page by Echo. The problem with that is that when you return a rendered page, it is returned as an error type.
So I was trying to work out a why of updating it, so I could return the data as an html string, which would then be put in as a slice of bytes.
if you want rendered html, use echo' custom middleware. I hope it helps you.
main.go
package main
import (
"bufio"
"bytes"
"errors"
"fmt"
"html/template"
"io"
"net"
"net/http"
"github.com/labstack/echo"
)
type TemplateRegistry struct {
templates map[string]*template.Template
}
func (t *TemplateRegistry) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
tmpl, ok := t.templates[name]
if !ok {
err := errors.New("Template not found -> " + name)
return err
}
return tmpl.ExecuteTemplate(w, "base.html", data)
}
func main() {
e := echo.New()
templates := make(map[string]*template.Template)
templates["about.html"] = template.Must(template.ParseFiles("view/about.html", "view/base.html"))
e.Renderer = &TemplateRegistry{
templates: templates,
}
// add custom middleware
// e.Use(PdfMiddleware)
// only AboutHandler for Pdf
e.GET("/about", PdfMiddleware(AboutHandler))
// Start the Echo server
e.Logger.Fatal(e.Start(":8080"))
}
// custom middleware
func PdfMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
resBody := new(bytes.Buffer)
mw := io.MultiWriter(c.Response().Writer, resBody)
writer := &bodyDumpResponseWriter{Writer: mw, ResponseWriter: c.Response().Writer}
c.Response().Writer = writer
if err = next(c); err != nil {
c.Error(err)
}
// or use resBody.Bytes()
fmt.Println(resBody.String())
return
}
}
type bodyDumpResponseWriter struct {
io.Writer
http.ResponseWriter
}
func (w *bodyDumpResponseWriter) WriteHeader(code int) {
w.ResponseWriter.WriteHeader(code)
}
func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}
func (w *bodyDumpResponseWriter) Flush() {
w.ResponseWriter.(http.Flusher).Flush()
}
func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.ResponseWriter.(http.Hijacker).Hijack()
}
func AboutHandler(c echo.Context) error {
return c.Render(http.StatusOK, "about.html", map[string]interface{}{
"name": "About",
"msg": "All about Boatswain!",
})
}
view/about.html
{{define "title"}}
Boatswain Blog | {{index . "name"}}
{{end}}
{{define "body"}}
<h1>{{index . "msg"}}</h1>
<h2>This is the about page.</h2>
{{end}}
view/base.html
{{define "base.html"}}
<!DOCTYPE html>
<html>
<head>
<title>{{template "title" .}}</title>
</head>
<body>
{{template "body" .}}
</body>
</html>
{{end}}
So from what I understand you want to:
Render HTML from template
Convert HTML to PDF
Send it as an HTTP response? This part is unclear from your question, but it doesn't matter really.
So, the reason Echo returns error is because it actually not only does the template rendering, but also sending a response to client. If you want to do something else in-between, you can't use that method from echo.
Luckily, echo doesn't do anything magical there. You can just use the standard template package with the same result.
func GetHtml(filename string, data interface{}) (string, error) {
filedata, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}
asString := string(filedata)
t, err := template.New("any-name").Parse(asString)
if err != nil {
return "", err
}
var buffer bytes.Buffer
err = t.Execute(&buffer, data)
if err != nil {
return "", err
}
return buffer.String(), nil
}
There you have your function that returns a string. You can use buffer.Bytes() to have a byte array if that's preferrable.
After this you can do whatever you need to, like convert to PDF and write it back to clients using echoCtx.Response().Writer().
Hope that helps, but in future try asking more precise questions, then you are more likely to receive an accurate response.
I am writing a function that reads a Path and returns a DirEntry instance. There's some weird behaviour that I don't understand.
pub fn file_to_direntry<T: AsRef<Path>>(filepath: T) -> Result<DirEntry, Box<Error>> {
match filepath.has_parent() {
Some(parent) => {
//..
}
// has no parent
// this line would cause an error
// Err(Error { repr: Os { code: 2, message: "No such file or directory" } })
None => path_to_entry(Path::new("."), path),
}
}
fn path_to_entry<A: AsRef<Path>, B: AsRef<Path>>(path: A, filename: B) -> Result<DirEntry, Box<Error>> {
let filename: &Path = filename.as_ref();
let path: &Path = path.as_ref();
// this line prints, "" "."
println!("{:?} {:?}", path, PathBuf::from("."));
// when I replace this line to
// for entry in try!(read_dir(PathBuf::from(".")))
// it works perfectly fine
for entry in try!(read_dir(path)) {
println!("{:?}", try!(entry));
}
Err(From::from("no file found"))
}
Full code on the Rust playground
In your example code, you test with PathBuf::from("todos.txt"). This is a relative path, it does not include a starting / or c:\.
When you do let parent = pf.parent();, it will return Some(""). So the parent is an empty string, not Some("."), and also not None. parent will only return None if the path terminates in a root or prefix. In your example above, you only included the None part, but that will not be called.
read_dir will return an error if the provided path doesn't exist. That is the case when you pass an empty string like read_dir(PathBuf::from("")), but it works perfectly fine if you use read_dir(PathBuf::from(".")).